1 <?php
2 /**
3 * @package Joomla.Platform
4 * @subpackage Cache
5 *
6 * @copyright Copyright (C) 2005 - 2017 Open Source Matters, Inc. All rights reserved.
7 * @license GNU General Public License version 2 or later; see LICENSE
8 */
9
10 defined('JPATH_PLATFORM') or die;
11
12 /**
13 * Joomla! Cache callback type object
14 *
15 * @since 11.1
16 */
17 class JCacheControllerCallback extends JCacheController
18 {
19 /**
20 * Executes a cacheable callback if not found in cache else returns cached output and result
21 *
22 * Since arguments to this function are read with func_get_args you can pass any number of arguments to this method
23 * as long as the first argument passed is the callback definition.
24 *
25 * The callback definition can be in several forms:
26 * - Standard PHP Callback array see <https://secure.php.net/callback> [recommended]
27 * - Function name as a string eg. 'foo' for function foo()
28 * - Static method name as a string eg. 'MyClass::myMethod' for method myMethod() of class MyClass
29 *
30 * @return mixed Result of the callback
31 *
32 * @since 11.1
33 * @deprecated 4.0
34 */
35 public function call()
36 {
37 // Get callback and arguments
38 $args = func_get_args();
39 $callback = array_shift($args);
40
41 return $this->get($callback, $args);
42 }
43
44 /**
45 * Executes a cacheable callback if not found in cache else returns cached output and result
46 *
47 * @param mixed $callback Callback or string shorthand for a callback
48 * @param array $args Callback arguments
49 * @param mixed $id Cache ID
50 * @param boolean $wrkarounds True to use wrkarounds
51 * @param array $woptions Workaround options
52 *
53 * @return mixed Result of the callback
54 *
55 * @since 11.1
56 */
57 public function get($callback, $args = array(), $id = false, $wrkarounds = false, $woptions = array())
58 {
59 // Normalize callback
60 if (is_array($callback) || is_callable($callback))
61 {
62 // We have a standard php callback array -- do nothing
63 }
64 elseif (strstr($callback, '::'))
65 {
66 // This is shorthand for a static method callback classname::methodname
67 list ($class, $method) = explode('::', $callback);
68 $callback = array(trim($class), trim($method));
69 }
70 elseif (strstr($callback, '->'))
71 {
72 /*
73 * This is a really not so smart way of doing this... we provide this for backward compatability but this
74 * WILL! disappear in a future version. If you are using this syntax change your code to use the standard
75 * PHP callback array syntax: <https://secure.php.net/callback>
76 *
77 * We have to use some silly global notation to pull it off and this is very unreliable
78 */
79 list ($object_123456789, $method) = explode('->', $callback);
80 global $$object_123456789;
81 $callback = array($$object_123456789, $method);
82 }
83
84 if (!$id)
85 {
86 // Generate an ID
87 $id = $this->_makeId($callback, $args);
88 }
89
90 $data = $this->cache->get($id);
91
92 $locktest = (object) array('locked' => null, 'locklooped' => null);
93
94 if ($data === false)
95 {
96 $locktest = $this->cache->lock($id);
97
98 // If locklooped is true try to get the cached data again; it could exist now.
99 if ($locktest->locked === true && $locktest->locklooped === true)
100 {
101 $data = $this->cache->get($id);
102 }
103 }
104
105 if ($data !== false)
106 {
107 if ($locktest->locked === true)
108 {
109 $this->cache->unlock($id);
110 }
111
112 $data = unserialize(trim($data));
113
114 if ($wrkarounds)
115 {
116 echo JCache::getWorkarounds(
117 $data['output'],
118 array('mergehead' => isset($woptions['mergehead']) ? $woptions['mergehead'] : 0)
119 );
120 }
121 else
122 {
123 echo $data['output'];
124 }
125
126 return $data['result'];
127 }
128
129 if (!is_array($args))
130 {
131 $referenceArgs = !empty($args) ? array(&$args) : array();
132 }
133 else
134 {
135 $referenceArgs = &$args;
136 }
137
138 if ($locktest->locked === false && $locktest->locklooped === true)
139 {
140 // We can not store data because another process is in the middle of saving
141 return call_user_func_array($callback, $referenceArgs);
142 }
143
144 $coptions = array();
145
146 if (isset($woptions['modulemode']) && $woptions['modulemode'] == 1)
147 {
148 $document = JFactory::getDocument();
149
150 if (method_exists($document, 'getHeadData'))
151 {
152 $coptions['headerbefore'] = $document->getHeadData();
153 }
154
155 $coptions['modulemode'] = 1;
156 }
157 else
158 {
159 $coptions['modulemode'] = 0;
160 }
161
162 $coptions['nopathway'] = isset($woptions['nopathway']) ? $woptions['nopathway'] : 1;
163 $coptions['nohead'] = isset($woptions['nohead']) ? $woptions['nohead'] : 1;
164 $coptions['nomodules'] = isset($woptions['nomodules']) ? $woptions['nomodules'] : 1;
165
166 ob_start();
167 ob_implicit_flush(false);
168
169 $result = call_user_func_array($callback, $referenceArgs);
170 $output = ob_get_clean();
171
172 $data = array('result' => $result);
173
174 if ($wrkarounds)
175 {
176 $data['output'] = JCache::setWorkarounds($output, $coptions);
177 }
178 else
179 {
180 $data['output'] = $output;
181 }
182
183 // Store the cache data
184 $this->cache->store(serialize($data), $id);
185
186 if ($locktest->locked === true)
187 {
188 $this->cache->unlock($id);
189 }
190
191 echo $output;
192
193 return $result;
194 }
195
196 /**
197 * Generate a callback cache ID
198 *
199 * @param callback $callback Callback to cache
200 * @param array $args Arguments to the callback method to cache
201 *
202 * @return string MD5 Hash
203 *
204 * @since 11.1
205 */
206 protected function _makeId($callback, $args)
207 {
208 if (is_array($callback) && is_object($callback[0]))
209 {
210 $vars = get_object_vars($callback[0]);
211 $vars[] = strtolower(get_class($callback[0]));
212 $callback[0] = $vars;
213 }
214
215 // A Closure can't be serialized, so to generate the ID we'll need to get its hash
216 if (is_a($callback, 'closure'))
217 {
218 $hash = spl_object_hash($callback);
219
220 return md5($hash . serialize(array($args)));
221 }
222
223 return md5(serialize(array($callback, $args)));
224 }
225 }
226