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