|
1 <?php |
|
2 /** |
|
3 * Zend Framework |
|
4 * |
|
5 * LICENSE |
|
6 * |
|
7 * This source file is subject to the new BSD license that is bundled |
|
8 * with this package in the file LICENSE.txt. |
|
9 * It is also available through the world-wide-web at this URL: |
|
10 * http://framework.zend.com/license/new-bsd |
|
11 * If you did not receive a copy of the license and are unable to |
|
12 * obtain it through the world-wide-web, please send an email |
|
13 * to license@zend.com so we can send you a copy immediately. |
|
14 * |
|
15 * @category Zend |
|
16 * @package Zend_Cache |
|
17 * @subpackage Zend_Cache_Frontend |
|
18 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) |
|
19 * @license http://framework.zend.com/license/new-bsd New BSD License |
|
20 * @version $Id: Page.php 20096 2010-01-06 02:05:09Z bkarwin $ |
|
21 */ |
|
22 |
|
23 |
|
24 /** |
|
25 * @see Zend_Cache_Core |
|
26 */ |
|
27 require_once 'Zend/Cache/Core.php'; |
|
28 |
|
29 |
|
30 /** |
|
31 * @package Zend_Cache |
|
32 * @subpackage Zend_Cache_Frontend |
|
33 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) |
|
34 * @license http://framework.zend.com/license/new-bsd New BSD License |
|
35 */ |
|
36 class Zend_Cache_Frontend_Page extends Zend_Cache_Core |
|
37 { |
|
38 /** |
|
39 * This frontend specific options |
|
40 * |
|
41 * ====> (boolean) http_conditional : |
|
42 * - if true, http conditional mode is on |
|
43 * WARNING : http_conditional OPTION IS NOT IMPLEMENTED FOR THE MOMENT (TODO) |
|
44 * |
|
45 * ====> (boolean) debug_header : |
|
46 * - if true, a debug text is added before each cached pages |
|
47 * |
|
48 * ====> (boolean) content_type_memorization : |
|
49 * - deprecated => use memorize_headers instead |
|
50 * - if the Content-Type header is sent after the cache was started, the |
|
51 * corresponding value can be memorized and replayed when the cache is hit |
|
52 * (if false (default), the frontend doesn't take care of Content-Type header) |
|
53 * |
|
54 * ====> (array) memorize_headers : |
|
55 * - an array of strings corresponding to some HTTP headers name. Listed headers |
|
56 * will be stored with cache datas and "replayed" when the cache is hit |
|
57 * |
|
58 * ====> (array) default_options : |
|
59 * - an associative array of default options : |
|
60 * - (boolean) cache : cache is on by default if true |
|
61 * - (boolean) cacheWithXXXVariables (XXXX = 'Get', 'Post', 'Session', 'Files' or 'Cookie') : |
|
62 * if true, cache is still on even if there are some variables in this superglobal array |
|
63 * if false, cache is off if there are some variables in this superglobal array |
|
64 * - (boolean) makeIdWithXXXVariables (XXXX = 'Get', 'Post', 'Session', 'Files' or 'Cookie') : |
|
65 * if true, we have to use the content of this superglobal array to make a cache id |
|
66 * if false, the cache id won't be dependent of the content of this superglobal array |
|
67 * - (int) specific_lifetime : cache specific lifetime |
|
68 * (false => global lifetime is used, null => infinite lifetime, |
|
69 * integer => this lifetime is used), this "lifetime" is probably only |
|
70 * usefull when used with "regexps" array |
|
71 * - (array) tags : array of tags (strings) |
|
72 * - (int) priority : integer between 0 (very low priority) and 10 (maximum priority) used by |
|
73 * some particular backends |
|
74 * |
|
75 * ====> (array) regexps : |
|
76 * - an associative array to set options only for some REQUEST_URI |
|
77 * - keys are (pcre) regexps |
|
78 * - values are associative array with specific options to set if the regexp matchs on $_SERVER['REQUEST_URI'] |
|
79 * (see default_options for the list of available options) |
|
80 * - if several regexps match the $_SERVER['REQUEST_URI'], only the last one will be used |
|
81 * |
|
82 * @var array options |
|
83 */ |
|
84 protected $_specificOptions = array( |
|
85 'http_conditional' => false, |
|
86 'debug_header' => false, |
|
87 'content_type_memorization' => false, |
|
88 'memorize_headers' => array(), |
|
89 'default_options' => array( |
|
90 'cache_with_get_variables' => false, |
|
91 'cache_with_post_variables' => false, |
|
92 'cache_with_session_variables' => false, |
|
93 'cache_with_files_variables' => false, |
|
94 'cache_with_cookie_variables' => false, |
|
95 'make_id_with_get_variables' => true, |
|
96 'make_id_with_post_variables' => true, |
|
97 'make_id_with_session_variables' => true, |
|
98 'make_id_with_files_variables' => true, |
|
99 'make_id_with_cookie_variables' => true, |
|
100 'cache' => true, |
|
101 'specific_lifetime' => false, |
|
102 'tags' => array(), |
|
103 'priority' => null |
|
104 ), |
|
105 'regexps' => array() |
|
106 ); |
|
107 |
|
108 /** |
|
109 * Internal array to store some options |
|
110 * |
|
111 * @var array associative array of options |
|
112 */ |
|
113 protected $_activeOptions = array(); |
|
114 |
|
115 /** |
|
116 * If true, the page won't be cached |
|
117 * |
|
118 * @var boolean |
|
119 */ |
|
120 protected $_cancel = false; |
|
121 |
|
122 /** |
|
123 * Constructor |
|
124 * |
|
125 * @param array $options Associative array of options |
|
126 * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested |
|
127 * @throws Zend_Cache_Exception |
|
128 * @return void |
|
129 */ |
|
130 public function __construct(array $options = array()) |
|
131 { |
|
132 while (list($name, $value) = each($options)) { |
|
133 $name = strtolower($name); |
|
134 switch ($name) { |
|
135 case 'regexps': |
|
136 $this->_setRegexps($value); |
|
137 break; |
|
138 case 'default_options': |
|
139 $this->_setDefaultOptions($value); |
|
140 break; |
|
141 case 'content_type_memorization': |
|
142 $this->_setContentTypeMemorization($value); |
|
143 break; |
|
144 default: |
|
145 $this->setOption($name, $value); |
|
146 } |
|
147 } |
|
148 if (isset($this->_specificOptions['http_conditional'])) { |
|
149 if ($this->_specificOptions['http_conditional']) { |
|
150 Zend_Cache::throwException('http_conditional is not implemented for the moment !'); |
|
151 } |
|
152 } |
|
153 $this->setOption('automatic_serialization', true); |
|
154 } |
|
155 |
|
156 /** |
|
157 * Specific setter for the 'default_options' option (with some additional tests) |
|
158 * |
|
159 * @param array $options Associative array |
|
160 * @throws Zend_Cache_Exception |
|
161 * @return void |
|
162 */ |
|
163 protected function _setDefaultOptions($options) |
|
164 { |
|
165 if (!is_array($options)) { |
|
166 Zend_Cache::throwException('default_options must be an array !'); |
|
167 } |
|
168 foreach ($options as $key=>$value) { |
|
169 if (!is_string($key)) { |
|
170 Zend_Cache::throwException("invalid option [$key] !"); |
|
171 } |
|
172 $key = strtolower($key); |
|
173 if (isset($this->_specificOptions['default_options'][$key])) { |
|
174 $this->_specificOptions['default_options'][$key] = $value; |
|
175 } |
|
176 } |
|
177 } |
|
178 |
|
179 /** |
|
180 * Set the deprecated contentTypeMemorization option |
|
181 * |
|
182 * @param boolean $value value |
|
183 * @return void |
|
184 * @deprecated |
|
185 */ |
|
186 protected function _setContentTypeMemorization($value) |
|
187 { |
|
188 $found = null; |
|
189 foreach ($this->_specificOptions['memorize_headers'] as $key => $value) { |
|
190 if (strtolower($value) == 'content-type') { |
|
191 $found = $key; |
|
192 } |
|
193 } |
|
194 if ($value) { |
|
195 if (!$found) { |
|
196 $this->_specificOptions['memorize_headers'][] = 'Content-Type'; |
|
197 } |
|
198 } else { |
|
199 if ($found) { |
|
200 unset($this->_specificOptions['memorize_headers'][$found]); |
|
201 } |
|
202 } |
|
203 } |
|
204 |
|
205 /** |
|
206 * Specific setter for the 'regexps' option (with some additional tests) |
|
207 * |
|
208 * @param array $options Associative array |
|
209 * @throws Zend_Cache_Exception |
|
210 * @return void |
|
211 */ |
|
212 protected function _setRegexps($regexps) |
|
213 { |
|
214 if (!is_array($regexps)) { |
|
215 Zend_Cache::throwException('regexps option must be an array !'); |
|
216 } |
|
217 foreach ($regexps as $regexp=>$conf) { |
|
218 if (!is_array($conf)) { |
|
219 Zend_Cache::throwException('regexps option must be an array of arrays !'); |
|
220 } |
|
221 $validKeys = array_keys($this->_specificOptions['default_options']); |
|
222 foreach ($conf as $key=>$value) { |
|
223 if (!is_string($key)) { |
|
224 Zend_Cache::throwException("unknown option [$key] !"); |
|
225 } |
|
226 $key = strtolower($key); |
|
227 if (!in_array($key, $validKeys)) { |
|
228 unset($regexps[$regexp][$key]); |
|
229 } |
|
230 } |
|
231 } |
|
232 $this->setOption('regexps', $regexps); |
|
233 } |
|
234 |
|
235 /** |
|
236 * Start the cache |
|
237 * |
|
238 * @param string $id (optional) A cache id (if you set a value here, maybe you have to use Output frontend instead) |
|
239 * @param boolean $doNotDie For unit testing only ! |
|
240 * @return boolean True if the cache is hit (false else) |
|
241 */ |
|
242 public function start($id = false, $doNotDie = false) |
|
243 { |
|
244 $this->_cancel = false; |
|
245 $lastMatchingRegexp = null; |
|
246 foreach ($this->_specificOptions['regexps'] as $regexp => $conf) { |
|
247 if (preg_match("`$regexp`", $_SERVER['REQUEST_URI'])) { |
|
248 $lastMatchingRegexp = $regexp; |
|
249 } |
|
250 } |
|
251 $this->_activeOptions = $this->_specificOptions['default_options']; |
|
252 if ($lastMatchingRegexp !== null) { |
|
253 $conf = $this->_specificOptions['regexps'][$lastMatchingRegexp]; |
|
254 foreach ($conf as $key=>$value) { |
|
255 $this->_activeOptions[$key] = $value; |
|
256 } |
|
257 } |
|
258 if (!($this->_activeOptions['cache'])) { |
|
259 return false; |
|
260 } |
|
261 if (!$id) { |
|
262 $id = $this->_makeId(); |
|
263 if (!$id) { |
|
264 return false; |
|
265 } |
|
266 } |
|
267 $array = $this->load($id); |
|
268 if ($array !== false) { |
|
269 $data = $array['data']; |
|
270 $headers = $array['headers']; |
|
271 if (!headers_sent()) { |
|
272 foreach ($headers as $key=>$headerCouple) { |
|
273 $name = $headerCouple[0]; |
|
274 $value = $headerCouple[1]; |
|
275 header("$name: $value"); |
|
276 } |
|
277 } |
|
278 if ($this->_specificOptions['debug_header']) { |
|
279 echo 'DEBUG HEADER : This is a cached page !'; |
|
280 } |
|
281 echo $data; |
|
282 if ($doNotDie) { |
|
283 return true; |
|
284 } |
|
285 die(); |
|
286 } |
|
287 ob_start(array($this, '_flush')); |
|
288 ob_implicit_flush(false); |
|
289 return false; |
|
290 } |
|
291 |
|
292 /** |
|
293 * Cancel the current caching process |
|
294 */ |
|
295 public function cancel() |
|
296 { |
|
297 $this->_cancel = true; |
|
298 } |
|
299 |
|
300 /** |
|
301 * callback for output buffering |
|
302 * (shouldn't really be called manually) |
|
303 * |
|
304 * @param string $data Buffered output |
|
305 * @return string Data to send to browser |
|
306 */ |
|
307 public function _flush($data) |
|
308 { |
|
309 if ($this->_cancel) { |
|
310 return $data; |
|
311 } |
|
312 $contentType = null; |
|
313 $storedHeaders = array(); |
|
314 $headersList = headers_list(); |
|
315 foreach($this->_specificOptions['memorize_headers'] as $key=>$headerName) { |
|
316 foreach ($headersList as $headerSent) { |
|
317 $tmp = explode(':', $headerSent); |
|
318 $headerSentName = trim(array_shift($tmp)); |
|
319 if (strtolower($headerName) == strtolower($headerSentName)) { |
|
320 $headerSentValue = trim(implode(':', $tmp)); |
|
321 $storedHeaders[] = array($headerSentName, $headerSentValue); |
|
322 } |
|
323 } |
|
324 } |
|
325 $array = array( |
|
326 'data' => $data, |
|
327 'headers' => $storedHeaders |
|
328 ); |
|
329 $this->save($array, null, $this->_activeOptions['tags'], $this->_activeOptions['specific_lifetime'], $this->_activeOptions['priority']); |
|
330 return $data; |
|
331 } |
|
332 |
|
333 /** |
|
334 * Make an id depending on REQUEST_URI and superglobal arrays (depending on options) |
|
335 * |
|
336 * @return mixed|false a cache id (string), false if the cache should have not to be used |
|
337 */ |
|
338 protected function _makeId() |
|
339 { |
|
340 $tmp = $_SERVER['REQUEST_URI']; |
|
341 $array = explode('?', $tmp, 2); |
|
342 $tmp = $array[0]; |
|
343 foreach (array('Get', 'Post', 'Session', 'Files', 'Cookie') as $arrayName) { |
|
344 $tmp2 = $this->_makePartialId($arrayName, $this->_activeOptions['cache_with_' . strtolower($arrayName) . '_variables'], $this->_activeOptions['make_id_with_' . strtolower($arrayName) . '_variables']); |
|
345 if ($tmp2===false) { |
|
346 return false; |
|
347 } |
|
348 $tmp = $tmp . $tmp2; |
|
349 } |
|
350 return md5($tmp); |
|
351 } |
|
352 |
|
353 /** |
|
354 * Make a partial id depending on options |
|
355 * |
|
356 * @param string $arrayName Superglobal array name |
|
357 * @param bool $bool1 If true, cache is still on even if there are some variables in the superglobal array |
|
358 * @param bool $bool2 If true, we have to use the content of the superglobal array to make a partial id |
|
359 * @return mixed|false Partial id (string) or false if the cache should have not to be used |
|
360 */ |
|
361 protected function _makePartialId($arrayName, $bool1, $bool2) |
|
362 { |
|
363 switch ($arrayName) { |
|
364 case 'Get': |
|
365 $var = $_GET; |
|
366 break; |
|
367 case 'Post': |
|
368 $var = $_POST; |
|
369 break; |
|
370 case 'Session': |
|
371 if (isset($_SESSION)) { |
|
372 $var = $_SESSION; |
|
373 } else { |
|
374 $var = null; |
|
375 } |
|
376 break; |
|
377 case 'Cookie': |
|
378 if (isset($_COOKIE)) { |
|
379 $var = $_COOKIE; |
|
380 } else { |
|
381 $var = null; |
|
382 } |
|
383 break; |
|
384 case 'Files': |
|
385 $var = $_FILES; |
|
386 break; |
|
387 default: |
|
388 return false; |
|
389 } |
|
390 if ($bool1) { |
|
391 if ($bool2) { |
|
392 return serialize($var); |
|
393 } |
|
394 return ''; |
|
395 } |
|
396 if (count($var) > 0) { |
|
397 return false; |
|
398 } |
|
399 return ''; |
|
400 } |
|
401 |
|
402 } |