| [ Index ] |
PHP Cross Reference of Akelos Framework |
[Summary view] [Print] [Text view]
1 <?php 2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ 3 4 // +----------------------------------------------------------------------+ 5 // | Akelos Framework - http://www.akelos.org | 6 // +----------------------------------------------------------------------+ 7 // | Copyright (c) 2002-2006, Akelos Media, S.L. & Bermi Ferrer Martinez | 8 // | Released under the GNU Lesser General Public License, see LICENSE.txt| 9 // +----------------------------------------------------------------------+ 10 11 12 13 /** 14 * @package ActionController 15 * @subpackage Caching 16 * @author Arno Schneider 17 * @license GNU Lesser General Public License <http://www.gnu.org/copyleft/lesser.html> 18 */ 19 20 require_once (AK_LIB_DIR.DS.'AkCache.php'); 21 require_once (AK_LIB_DIR.DS.'AkRequestMimeType.php'); 22 /** 23 * 24 * 25 * Akelos supports three types of caching: 26 * 27 * - Page Caching 28 * - Action Caching 29 * - Fragment Caching 30 * 31 * 32 * == Page Caching 33 * 34 * Page caching is an approach to caching where the entire action output of 35 * is stored as in a special format containing the output and the headers 36 * which have to be sent for the response. 37 * This cache the web server can serve without even starting up the Framework. 38 * 39 * This can be as much as 100 times faster than going through the process of dynamically 40 * generating the content. 41 * Unfortunately, this incredible speed-up is only available to stateless pages 42 * where all visitors are treated the same. 43 * 44 * Content management systems -- including weblogs and wikis -- have many pages 45 * that are a great fit for this approach, but account-based systems 46 * where people log in and manipulate their own data are often less likely candidates. 47 * 48 * Specifying which actions to cache is done through the <tt>caches</tt> class method: 49 * 50 * class WeblogController extends ActionController { 51 * var $caches_page = array('show','new'); 52 * function show() { 53 * .... 54 * } 55 * 56 * function new() { 57 * .... 58 * } 59 * } 60 * 61 * This will generate caches such as weblog/show/5 and weblog/new, which match the URLs 62 * used to trigger the dynamic generation. 63 * This is how the web server is able pick up a cache when it exists by just purely reading 64 * the Requested URL. Otherwise it lets the request pass on to the Framework to generate it. 65 * 66 * Expiration of the cache is handled by deleting the cache, which results in a lazy regeneration 67 * approach where the cache is not restored before another hit is made against it. 68 * 69 * The API for doing so mimics the options from url_for and friends: 70 * 71 * class WeblogController extends ActionController { 72 * function update() { 73 .... 74 * $this->expirePage(array("action" => "show", "id" => $this->params["list"]["id"])); 75 * $this->redirectTo(array("action => "show", "id" => $this->params["list"]["id"])); 76 * } 77 * } 78 * 79 * Additionally, you can expire caches using Sweepers that act on changes in the model to determine 80 * when a cache is supposed to be expired. 81 * 82 * == Configuring the cache 83 * 84 * There are different types of caches: 85 * 86 * - File based cache using PEAR Cache_Lit 87 * - Database Cache using AdoDB 88 * - Memcache 89 * 90 * The config file for the cache can be found in BASE_DIR/config/caching.yml 91 * 92 * === File Based Cache 93 * 94 * ==== Setting the cache directory 95 * 96 * If no cache directory is set, the Cache_Lite will use the AK_TMP_DIR constant value for the 97 * cache dir. The cache dir can be set inside the Yaml-Config file as follows: 98 * 99 * enabled: true 100 * handler: 101 * type: 1 #1=PEAR,2=AdoDB,3=Memcache 102 * options: 103 * cacheDir: /tmp 104 * 105 * === Database Cache 106 * 107 * The DB cache will use the configured database settings and use the cache table to store caches. 108 * 109 * === Memcache 110 * 111 * The MemCache needs to know which memcache servers to talk to. You have to configure a set 112 * of servers inside the caching.yml: 113 * 114 * enabled: true 115 * handler: 116 * type: 3 #1=PEAR,2=AdoDB,3=Memcache 117 * options: 118 * servers: 119 * - memcache1.example.com:9810 120 * - memcache2.example.com:9810 121 * 122 * == Action Caching 123 * 124 * Action caching is similar to page caching by the fact that the entire output 125 * of the response is cached, but unlike page caching, 126 * every request still goes through the AkActionController. 127 * 128 * The key benefit of this is that filters are run before the cache is served, 129 * which allows for authentication and other restrictions on whether someone is 130 * allowed to see the cache. 131 * 132 * Example: 133 * 134 * class ListsController extends ApplicationController { 135 * 136 * var $caches_page = 'public'; 137 * var $caches_action = array('show', 'feed'); 138 * 139 * function __construct(){ 140 * $this->beforeFilter(array('authenticate' => array('except' => array('public')))); 141 * } 142 * } 143 * 144 * In this example, the public action doesn't require authentication, so it's 145 * possible to use the faster page caching method. 146 * 147 * But both the show and feed action are to be shielded behind the authenticate filter, 148 * so we need to implement those as action caches. 149 * 150 * Action caching internally uses the fragment caching and an before/after filter combination 151 * to do the job. 152 * 153 * The fragment cache is named according to both 154 * the current host and the path as well as the locale. 155 * 156 * So a page that is accessed at http://david.somewhere.com/lists/show/1 with an english locale 157 * will result in a fragment named 158 * 159 * cacheId: "en/lists/show/1", cacheGroup: "david.somewhere.com" 160 * 161 * This allows the cacher to differentiate between "david.somewhere.com/lists/" and 162 * "jamis.somewhere.com/lists/" -- which is a helpful way of assisting the 163 * subdomain-as-account-key pattern. 164 * 165 * Different representations of the same resource, 166 * e.g. <tt>http://david.somewhere.com/lists</tt> and 167 * <tt>http://david.somewhere.com/lists.xml</tt> 168 * are treated like separate requests and so are cached separately. 169 * 170 * Keep in mind when expiring an action cache that 171 * 172 * <tt>array('action' => 'lists')</tt> is not the same 173 * as <tt>array('action' => 'lists', 'format' => 'xml')</tt>. 174 * 175 * If you use the Filebased Cache, you can set modify the default action cache path 176 * by passing a "cache_path" option. 177 * 178 * This will be passed directly to _setCachesAction method. 179 * This is handy for actions with multiple possible routes that should be cached differently. 180 * 181 * If a block is given, it is called with the current controller instance. 182 * 183 * class ListsController extends ApplicationController { 184 * 185 * var $caches_page = 'public'; 186 * var $caches_action = array('show'=>array('cache_path'=>'/custom/show/'), 'feed'); 187 * 188 * function __construct(){ 189 * $this->beforeFilter(array('authenticate' => array('except' => array('public')))); 190 * } 191 * } 192 * 193 * The action cache for the action show will then be stored under the cacheId "en/custom/show". 194 * 195 * == Fragment Caching 196 * 197 * Fragment caching is used for caching various blocks within templates without caching the entire action 198 * as a whole. 199 * This is useful when certain elements of an action change frequently or depend on complicated state 200 * while other parts rarely change or can be shared amongst multiple parties. 201 * 202 * The caching is doing using the cache helper available in the Action View. 203 * A template with caching might look something like: 204 * 205 * <b>Hello {name}</b> 206 * <?php if (!$cache_helper->begin()) { ?> 207 * All the topics in the system: 208 * <?= $controller->renderPartial("topic", $Topic->findAll()); ?> 209 * <?= $cache_helper->end();} ?> 210 * 211 * This cache by default will bind to the action it was called from, 212 * so if this code was part of the view for the topics/list action, you would 213 * be able to invalidate it using: 214 * 215 * <tt>$this>expireFragment(array("controller" => "topics", "action" => "list"))</tt>. 216 * 217 * This default behavior is of limited use if you need to cache multiple fragments per action 218 * or if the action itself is cached using <tt>caches_action</tt>, 219 * so we also have the option to qualify the name of the cached fragment with something like: 220 * 221 * <?php if (!$this->cache_helper->begin('cacheFragmentKey')) { ?> 222 * 223 * Like this you can assure unique fragment caches. 224 * 225 * The expiration call for this example is: 226 * 227 * $this->expireFragment("cacheFragmentKey") 228 * 229 */ 230 class AkCacheHandler extends AkObject 231 { 232 var $cache_strip = array('<!--CACHE-SKIP-START-->.*?<!--CACHE-SKIP-END-->','<div class=\"flash_[a-z]+\">.*?<\/div>'); 233 234 /** 235 * @var AkCache 236 */ 237 var $_cache_store = false; 238 239 var $_perform_caching = true; 240 241 var $_page_cache_extension = '.html'; 242 243 var $_controller; 244 245 /** 246 * ########### Start: Page Caching ########### 247 */ 248 249 var $_lastCacheGroup; 250 251 var $_lastCacheId; 252 253 var $_include_get_parameters = array(); 254 255 var $_caches_page = array(); 256 var $_caches_action = array(); 257 258 var $_additional_headers = array(); 259 260 var $_header_separator = '@#@'; 261 262 /** 263 * ########### End: Page Caching ########### 264 */ 265 266 /** 267 * Max key size on memcache is 250 chars, 268 * to support memcache, we need to md5() the keysize in case it becomes too long 269 * 270 * @var int 271 */ 272 var $_max_cache_id_length = 240; 273 /* 274 * @var int 275 */ 276 var $_max_url_length = 120; 277 278 279 /** 280 * Sweeper 281 */ 282 var $observe = array(); 283 284 var $_Sweepers = array(); 285 286 var $_settings = array(); 287 var $_rendered_action_cache = false; 288 289 var $_caching_type = null; 290 291 /** 292 * Reads configuration options from AkActionController and the configured 293 * constants 294 * 295 * AkCache::lookupStore(true) - to detect which cache shall be used 296 * $perform_caching - to detect whether caching shall be enabled or not 297 * 298 * @param AkActionController $parent 299 */ 300 function init(&$parent, $settings = null) 301 { 302 $this->_caching_type = null; 303 $this->_action_cache_path = null; 304 $this->_action_cache_host = null; 305 if ($parent != null) { 306 $this->_controller = &$parent; 307 308 $this->_configure($settings); 309 310 } else { 311 /** 312 * We are in pagecache rendering mode 313 */ 314 $this->_loadSettings($settings); 315 316 } 317 } 318 function _getPublicLocales() 319 { 320 static $public_locales; 321 if(empty($public_locales)){ 322 $public_locales = defined('AK_PUBLIC_LOCALES') ? 323 Ak::toArray(AK_PUBLIC_LOCALES) : 324 array_keys($this->_getAvailableLocales()); 325 } 326 return $public_locales; 327 } 328 329 function _getAvailableLocales() 330 { 331 static $available_locales; 332 333 if(empty($available_locales)){ 334 $available_locales = array(); 335 $d = dir(AK_CONFIG_DIR.DS.'locales'); 336 while (false !== ($entry = $d->read())) { 337 if (preg_match('/\\.php$/', $entry)){ 338 $locale = str_replace('.php','',$entry); 339 $available_locales[$locale] = array($locale); 340 } 341 } 342 $d->close(); 343 } 344 345 return $available_locales; 346 } 347 function _startSession() 348 { 349 if(isset($_COOKIE[AK_SESSION_NAME])&& !isset($_SESSION)){ 350 require_once (AK_LIB_DIR.DS.'AkSession.php'); 351 $SessionHandler = &AkSession::initHandler(); 352 @session_start(); 353 } 354 } 355 356 function _getDefaultLanguageForUser() 357 { 358 $this->_startSession(); 359 if (isset($_SESSION['lang'])) { 360 return $_SESSION['lang']; 361 } else { 362 $langs = $this->_getPublicLocales(); 363 $browser_languages = $this->_getBrowserLanguages(); 364 // First run for full locale (en_us, en_uk) 365 foreach ($browser_languages as $browser_language){ 366 if(in_array($browser_language,$langs)){ 367 return $browser_language; 368 } 369 } 370 371 // Second run for only language code (en, es) 372 foreach ($browser_languages as $browser_language){ 373 if($pos = strpos($browser_language,'_')){ 374 $browser_language = substr($browser_language,0, $pos); 375 if(in_array($browser_language,$langs)){ 376 return $browser_language; 377 } 378 } 379 } 380 return Ak::lang(); 381 } 382 } 383 function _getDefaultLocale() 384 { 385 return Ak::lang(); 386 } 387 function _getBrowserLanguages() 388 { 389 $browser_accepted_languages = str_replace('-','_', strtolower(preg_replace('/q=[0-9\.]+,*/','',@$_SERVER['HTTP_ACCEPT_LANGUAGE']))); 390 $browser_languages = (array_diff(split(';|,',$browser_accepted_languages.','), array(''))); 391 if(empty($browser_languages)){ 392 return array($this->_getDefaultLocale()); 393 } 394 return array_unique($browser_languages); 395 } 396 397 function _loadSettings($settings = null) 398 { 399 if ($settings == null) { 400 $this->_settings = Ak::getSettings('caching', false); 401 } else if (is_array($settings)) { 402 $this->_settings = $settings; 403 } else { 404 return; 405 } 406 $this->_setCacheStore($this->_settings); 407 } 408 function _configure($settings) 409 { 410 $configuration_object = &$this->_controller; 411 $configuration_options = array('caches_page'=>'_setCachesPage', 412 'cachesPage'=>'_setCachesPage', 413 'caches_action'=>'_setCachesAction', 414 'cachesAction'=>'_setCachesAction', 415 'cache_sweeper'=>'_setCacheSweeper', 416 'cacheSweeper'=>'_setCacheSweeper', 417 'page_cache_extension'=>'_setPageCacheExtension'); 418 /** 419 * Load the configured cache store, 420 */ 421 $this->_loadSettings($settings); 422 423 if (isset($this->_controller->page_cache_extension)) { 424 $this->_page_cache_extension = $this->_controller->page_cache_extension; 425 } 426 427 if (@$this->_settings['enabled'] == true) { 428 $this->_perform_caching = true; 429 } 430 431 foreach ($configuration_options as $option => $callback) { 432 if (isset($configuration_object->$option)) { 433 if (is_array($callback)) { 434 call_user_func_array($callback,$configuration_object->$option); 435 } else { 436 $this->$callback($configuration_object->$option); 437 } 438 } 439 } 440 } 441 function _setPageCacheExtension($extension) 442 { 443 $this->_page_cache_extension = $extension; 444 } 445 function &getController() 446 { 447 $return=&$this->_controller; 448 return $return; 449 } 450 451 function &getCacheStore() 452 { 453 return $this->_cache_store; 454 } 455 456 /** 457 * ######################################################################## 458 * # 459 * # The following methods have to be callable 460 * # from AkActionController 461 * # 462 * ######################################################################## 463 */ 464 /** 465 * Is the Caching module configured and ready for usage? 466 * 467 * @return boolean 468 */ 469 function cacheConfigured() 470 { 471 return $this->_cache_store && $this->_perform_caching; 472 } 473 /* 474 * ######################################################################## 475 * # 476 * # From AkActionControllerCachingPages 477 * # 478 * ######################################################################## 479 */ 480 function expirePage($path = null, $language=null) 481 { 482 if (!$this->_perform_caching || !$this->_cache_store) return; 483 484 if ($language == null && is_array($path) && !isset($path['lang'])) { 485 $language = '*'; 486 } 487 488 if ((is_array($path) && isset($path['lang']) && $path['lang'] == '*') || $language == '*') { 489 $langs = $this->_getPublicLocales(); 490 $res = true; 491 $mpath = $path; 492 unset($mpath['lang']); 493 foreach ($langs as $lang) { 494 $res = $this->expirePage($mpath, $lang) || $res; 495 } 496 return $res; 497 } 498 $cacheId = $this->_buildCacheId($path, $language); 499 $notNormalizedCacheId = $this->_buildCacheId($path, $language, false); 500 $cacheGroup = $this->_buildCacheGroup(); 501 $notGzippedRes=$this->_cache_store->remove($cacheId,$cacheGroup); 502 $gZippedCacheId = $this->_scopeWithGzip($cacheId); 503 $gzippedRes=$this->_cache_store->remove($gZippedCacheId,$cacheGroup); 504 505 if ($notNormalizedCacheId != $cacheId) { 506 $notNormalizedNotGzippedRes=$this->_cache_store->remove($notNormalizedCacheId,$cacheGroup); 507 $notNormalizedGZippedCacheId = $this->_scopeWithGzip($notNormalizedCacheId); 508 $notNormalizedGzippedRes=$this->_cache_store->remove($notNormalizedGZippedCacheId,$cacheGroup); 509 } 510 511 512 return ($notGzippedRes || $gzippedRes); 513 } 514 function cachePage($content, $path = null, $language = null, $gzipped=false, $sendETag = false, $orgStrlen = null) 515 { 516 static $ETag; 517 518 if (!($this->_cachingAllowed() && $this->_perform_caching)) return; 519 520 $cacheId = $this->_buildCacheId($path, $language); 521 $skipEtagSending = false; 522 if ($orgStrlen != strlen($content)) $skipEtagSending = true; 523 $notNormalizedCacheId = $this->_buildCacheId($path, $language,false); 524 525 526 $removeHeaders = array(); 527 $addHeaders = array(); 528 if ($gzipped) { 529 $cacheId = $this->_scopeWithGzip($cacheId); 530 $notNormalizedCacheId = $this->_scopeWithGzip($notNormalizedCacheId); 531 $addHeaders = array('Content-Encoding'=>'gzip'); 532 } else { 533 $removeHeaders = array('content-encoding'); 534 } 535 536 $cacheGroup = $this->_buildCacheGroup(); 537 538 if ($sendETag && !headers_sent()) { 539 $ETag = Ak::uuid(); 540 $etagHeader = 'ETag: '.$ETag; 541 $this->_controller->Response->addSentHeader($etagHeader); 542 if(!$skipEtagSending) { 543 header($etagHeader); 544 } else { 545 header('Expires: '.gmdate('D, d M Y H:i:s',0)); 546 } 547 } 548 //$addHeaders['ETag'] = $ETag; 549 550 551 $cacheTimestamp = time(); 552 $content = $this->_modifyCacheContent($content,$addHeaders, $removeHeaders); 553 $filename = $this->_storePageCache($content,$cacheId,$cacheGroup); 554 $res = $this->_cache_store->save($filename,$cacheId,$cacheGroup); 555 if ($notNormalizedCacheId != $cacheId) { 556 // Store the not normalized cacheid 557 $filename = $this->_storePageCache($content,$cacheId,$cacheGroup); 558 $this->_cache_store->save($filename,$notNormalizedCacheId,$cacheGroup); 559 } 560 return $res; 561 562 } 563 function _storePageCache($content, $cacheId,$cacheGroup) 564 { 565 $filename = AK_TMP_DIR.DS.'cache'.DS.$cacheGroup.DS.$cacheId.'.php'; 566 if (!file_exists(dirname($filename))) { 567 $res = mkdir(dirname($filename),0755,true); 568 } 569 file_put_contents($filename, $content); 570 571 return $filename; 572 } 573 function _stripCacheSkipSections($content) 574 { 575 if (isset($this->_controller->cache_strip)) { 576 $cache_strip = is_array($this->_controller->cache_strip)?$this->_controller->cache_strip:array($this->_controller->cache_strip); 577 } else { 578 $cache_strip = array(); 579 } 580 $cache_strip = array_merge($this->cache_strip, $cache_strip); 581 582 foreach ($cache_strip as $strip) { 583 $content = @preg_replace('/('.$strip.')/sm','',$content); 584 if ($content===false) { 585 trigger_error(Ak::t('AkCacheHandler: cache_strip expression: %expr is not working as expected', array('%expr'=>$strip)), E_USER_ERROR); 586 return false; 587 } 588 } 589 return $content; 590 } 591 592 function x_modifyCacheContent($content,$addHeaders = array(), $removeHeaders = array()) 593 { 594 $headers = $this->_controller->Response->_headers_sent; 595 $finalHeaders = array(); 596 foreach ($headers as $header) { 597 $parts = split(': ',$header); 598 $type = $parts[0]; 599 if (!in_array(strtolower($type),$removeHeaders)) { 600 if (isset($addHeaders[$type])) { 601 $finalHeaders[] = $type.($addHeaders[$type]!==true?': '.$addHeaders[$type]:''); 602 unset($addHeaders[$type]); 603 } else { 604 $finalHeaders[] = $header; 605 } 606 } 607 } 608 foreach ($addHeaders as $type=>$val) { 609 $finalHeaders[] = $type.($val!==true?': '.$val:''); 610 } 611 $timestamp = time(); 612 $headerString = serialize($finalHeaders); 613 $content = $timestamp.$this->_header_separator.$headerString . $this->_header_separator . $content; 614 return $content; 615 } 616 function _modifyCacheContent($content,$addHeaders = array(), $removeHeaders = array()) 617 { 618 $headers = $this->_controller->Response->_headers_sent; 619 $finalHeaders = array(); 620 foreach ($headers as $header) { 621 $parts = split(': ',$header); 622 $type = $parts[0]; 623 if (!in_array(strtolower($type),$removeHeaders)) { 624 if (isset($addHeaders[$type])) { 625 $finalHeaders[] = $type.($addHeaders[$type]!==true?': '.$addHeaders[$type]:''); 626 unset($addHeaders[$type]); 627 } else { 628 $finalHeaders[] = $header; 629 } 630 } 631 } 632 foreach ($addHeaders as $type=>$val) { 633 $finalHeaders[] = $type.($val!==true?': '.$val:''); 634 } 635 $timestamp = time(); 636 $headerString = var_export($finalHeaders,true); 637 //$functionStr = file_get_contents(dirname(__FILE__).DS.'cache_page_functions.txt'); 638 $content = preg_replace('/(<\?|<\?php|\?>)/','<?php echo "\1";?>', $content); 639 $content = <<<EOF 640 <?php 641 global \$sendHeaders, \$returnHeaders, \$exit; 642 643 \$modifiedTimestamp = $timestamp; 644 \$headers = $headerString; 645 646 \$etagHeaders = AkCacheHandler::_handleEtag(\$headers, \$sendHeaders,\$returnHeaders, \$exit); 647 \$sentHeaders = array(); 648 \$sentHeaders=AkCacheHandler::_handleIfModifiedSince(\$modifiedTimestamp,\$exit, \$sendHeaders, \$returnHeaders); 649 \$acceptedEncodings = AkCacheHandler::_getAcceptedEncodings(); 650 foreach(\$headers as \$header) { 651 header(AkCacheHandler::_handleEncodingAliases(\$header, \$acceptedEncodings)); 652 653 } 654 \$addHeaders = AkCacheHandler::_sendAdditionalHeaders(\$sendHeaders, \$returnHeaders); 655 if (is_array(\$addHeaders)) { 656 \$headers = array_merge(\$addHeaders, \$headers); 657 } 658 \$headers = array_merge(\$etagHeaders, \$headers); 659 ?> 660 $content<?php 661 if (\$exit) { 662 exit(0); 663 } 664 \$sentHeaders = is_array(\$sentHeaders)?array_merge(\$sentHeaders,\$headers):\$headers; 665 return \$sentHeaders; 666 ?> 667 EOF; 668 669 return $content; 670 } 671 function _setCacheSweeper($options) 672 { 673 require_once (AK_LIB_DIR.DS.'AkActionController'.DS.'AkCacheSweeper.php'); 674 $default_options = array('only'=>array(), 675 'except'=>array()); 676 if (is_string($options)) { 677 $options = Ak::toArray($options); 678 } 679 Ak::parseOptions($options, $default_options,array(),true); 680 681 foreach ($options as $sweeper => $params) { 682 if (is_int($sweeper)) { 683 $sweeper = $params; 684 $params = array(); 685 } 686 if (isset($params['only']) && is_string($params['only'])) { 687 $params['only'] = array($params['only']); 688 } 689 if (isset($params['except']) && is_string($params['except'])) { 690 $params['except'] = array($params['except']); 691 } 692 $this->_initSweeper($sweeper, $params); 693 } 694 } 695 696 function _initSweeper($sweeper, $options = array()) 697 { 698 if (isset($options['only']) && !in_array($this->_controller->getActionName(), $options['only'])) return; 699 if (isset($options['except']) && !in_array($this->_controller->getActionName(), $options['except'])) return; 700 701 $sweeper_class = AkInflector::classify($sweeper); 702 703 if (!class_exists($sweeper_class)) { 704 $filePath = AK_APP_DIR . DS . 'sweepers' . DS . $sweeper.'.php'; 705 if (file_exists($filePath)) { 706 require_once($filePath); 707 if (!class_exists($sweeper_class)) { 708 trigger_error('Cache Sweeper "' . $sweeper_class . '" does not exist in: ' . $filePath, E_USER_ERROR); 709 } 710 } else if (AK_ENVIRONMENT == 'development') { 711 trigger_error('Cache Sweeper file does not exist: ' . $filePath, E_USER_ERROR); 712 } 713 } 714 $this->_Sweepers[] = &new $sweeper_class(&$this); 715 } 716 717 function _setCachesPage($options) 718 { 719 if (!$this->_perform_caching) return; 720 if (is_string($options)) { 721 $options = Ak::toArray($options); 722 } 723 $default_options = array('include_get_parameters'=>array(), 724 'headers'=> array('X-Cached-By'=>'Akelos')); 725 Ak::parseOptions($options, $default_options,array(),true); 726 $this->_caches_page = &$options; 727 728 $actionName = $this->_controller->getActionName(); 729 if ($this->_caching_type == null && isset($this->_caches_page[$actionName])) { 730 $this->_caching_type = 'page'; 731 $this->_include_get_parameters = $this->_caches_page[$actionName]['include_get_parameters']; 732 $this->_additional_headers = $this->_caches_page[$actionName]['headers']; 733 734 $this->_controller->prependBeforeFilter(array(&$this,'beforePageCache')); 735 $this->_controller->appendAfterFilter(array(&$this,'afterPageCache')); 736 } 737 } 738 739 function beforePageCache() 740 { 741 ob_start(); 742 return true; 743 } 744 745 function _scopeWithGzip($cacheId) 746 { 747 $cacheId = 'gzip' . DS . $cacheId; 748 return $cacheId; 749 } 750 function afterPageCache() 751 { 752 $encodings = $this->_getAcceptedEncodings(); 753 $xgzip = false; 754 $gzip = false; 755 $this->_controller->Response->addHeader('Cache-Control','private, max-age=0, must-revalidate'); 756 757 if (($gzip=in_array('gzip',$encodings)) || ($xgzip=in_array('x-gzip',$encodings))) { 758 $this->_controller->Response->addHeader('Content-Encoding',$xgzip?'x-gzip':'gzip'); 759 $gzip = $gzip || $xgzip; 760 $this->_controller->handleResponse(); 761 $contents = ob_get_clean(); 762 /** 763 * Caching unzipped content 764 */ 765 $this->cachePage($this->_stripCacheSkipSections($contents),array(),null,false,true, strlen($contents)); 766 $org_contents = $this->_gzipCache($contents); 767 echo $org_contents; 768 $contents = $this->_gzipCache($this->_stripCacheSkipSections($contents)); 769 } else { 770 $this->_controller->handleResponse(); 771 $contents = ob_get_clean(); 772 /** 773 * Caching gzipped content 774 */ 775 $gzippedContents = $this->_gzipCache($this->_stripCacheSkipSections($contents)); 776 $this->cachePage($gzippedContents,array(),null,true,true, strlen($contents)); 777 echo $contents; 778 $contents = $this->_stripCacheSkipSections($contents); 779 } 780 $this->cachePage($contents,array(),null,$gzip, false, strlen($contents)); 781 return true; 782 } 783 784 function _gzipCache($cache) 785 { 786 $pre ="\x1f\x8b\x08\x00\x00\x00\x00\x00"; 787 $gzip_size = strlen($cache); 788 $gzip_crc = crc32($cache); 789 $gzip_contents = gzcompress($cache, 9); 790 $gzip_contents = substr($gzip_contents, 0, strlen($gzip_contents) - 4); 791 $gzip_contents = $pre.$gzip_contents; 792 $gzip_contents.=pack('V', $gzip_crc); 793 $gzip_contents.=pack('V', $gzip_size); 794 return $gzip_contents; 795 } 796 797 function _buildCacheId($path, $forcedLanguage = null, $normalize = true) 798 { 799 if ($path === null) { 800 $path = @$_REQUEST['ak']; 801 802 } else if (is_array($path)) { 803 unset($path['lang']); 804 $path = $this->_pathFor($path, $normalize); 805 } else if (is_string($path)) { 806 ; 807 } 808 $path = ltrim($path,'/'); 809 if (preg_match('@^([a-z]{2,2}|[a-z]{2,2}_[a-z]{2,2})/.*$@', $path)) { 810 $parts = split('/',$path); 811 $forcedLanguage = array_shift($parts); 812 $path = implode('/',$parts); 813 } 814 $cacheId = preg_replace('|'.DS.'+|','/',$path); 815 $cacheId = rtrim($cacheId,'/'); 816 $parts = split('/',$cacheId); 817 818 $hasExtension = preg_match('/.+\..{3,4}/',$parts[count($parts)-1]); 819 if (!$hasExtension) { 820 if (isset($this->_controller)) { 821 $cacheId.= '.'.$this->_controller->Request->getFormat(); 822 } else { 823 824 list($format, $requestPath) = AkRequestMimeType::getFormat($path); 825 $cacheId.= '.'.$format; 826 } 827 } 828 829 $getParameters = $_GET; 830 unset($getParameters['ak']); 831 if (is_array($this->_include_get_parameters) && !empty($this->_include_get_parameters) && !empty($getParameters)) { 832 $cacheableGetParameters = array(); 833 foreach ($this->_include_get_parameters as $include_get) { 834 if (isset($getParameters[$include_get])) { 835 $cacheableGetParameters[] = $include_get .DS.$getParameters[$include_get]; 836 } 837 } 838 $cacheIdGetPart = implode(DS,$cacheableGetParameters); 839 $cacheId .= DS . $cacheIdGetPart; 840 } 841 $cacheId=strlen($cacheId)>$this->_max_url_length?md5($cacheId):$cacheId; 842 $cacheId = ($forcedLanguage!=null?$forcedLanguage:$this->_getDefaultLanguageForUser()).DS. $cacheId; 843 $this->_lastCacheId = preg_replace('|'.DS.'+|','/',$cacheId); 844 return $this->_lastCacheId; 845 } 846 847 function &getCachedPage($path = null,$forcedLanguage = null) 848 { 849 $false = false; 850 if (!$this->_cachingAllowed()) return $false; 851 $false = false; 852 if ($this->_cache_store!=false) { 853 if ($path === null) { 854 $path = @$_REQUEST['ak']; 855 } 856 $cacheId = $this->_buildCacheId($path, $forcedLanguage); 857 $encodings = $this->_getAcceptedEncodings(); 858 if (($gzip=in_array('gzip',$encodings)) || ($xgzip=in_array('x-gzip',$encodings))) { 859 $cacheId = $this->_scopeWithGzip($cacheId); 860 } 861 $cacheGroup = $this->_buildCacheGroup(); 862 $cache = $this->_cache_store->get($cacheId, $cacheGroup); 863 864 if ($cache != false) { 865 return $cache; 866 } else { 867 868 return $false; 869 } 870 } else { 871 return $false; 872 } 873 } 874 875 function _buildCacheGroup() 876 { 877 $this->_lastCacheGroup = $this->_convertGroup(isset($_SERVER['AK_HOST'])?$_SERVER['AK_HOST']:AK_HOST); 878 return $this->_lastCacheGroup; 879 } 880 881 function _cachingAllowed() 882 { 883 if (isset($this->_controller)) { 884 return $this->_controller->Request->isGet() && $this->_controller->Response->getStatus()==200; 885 } else { 886 return empty($_POST) && empty($_ENV['HTTP_RAW_POST_DATA']) && (isset($_SERVER['REQUEST_METHOD']) && strtolower($_SERVER['REQUEST_METHOD'])=='get'); 887 } 888 } 889 890 function _convertGroup($group) 891 { 892 if ($group == '127.0.0.1') return 'localhost'; 893 else return $group; 894 } 895 /* 896 * ######################################################################## 897 * # 898 * # From AkActionControllerCachingFragments 899 * # 900 * ######################################################################## 901 */ 902 function fragmentCacheKey($options, $parameters = array()) 903 { 904 if (isset($parameters['namespace']) && $parameters['namespace'] == 'actions') { 905 $options = $this->_actionPath($options); 906 } else { 907 if (is_array($options)) { 908 $options = $this->_pathFor($options); 909 } else if ($options==null) { 910 $options = $this->_pathFor($this->_controller->params); 911 } 912 } 913 914 $key = AkCache::expandCacheKey($options, isset($parameters['namespace'])?$parameters['namespace']:'fragments'); 915 916 return $key; 917 } 918 function _cacheTplRendered($key, $options) 919 { 920 static $_cached; 921 922 $key = $this->fragmentCachekey($key, $options); 923 if (empty($_cached)) { 924 $_cached = array(); 925 } 926 if (isset($_cached[$key])) { 927 return true; 928 } else { 929 $_cached[$key] = true; 930 return false; 931 } 932 933 } 934 function cacheTplFragmentStart($key = array(), $options = array()) 935 { 936 if (!$this->cacheConfigured()) { 937 return false; 938 } 939 $read = $this->readFragment($key, $options); 940 if ($read !== false) { 941 echo $read; 942 $this->_cacheTplRendered($key, $options); 943 return true; 944 } else { 945 ob_start(); 946 return false; 947 } 948 } 949 950 function cacheTplFragmentEnd($key = array(), $options = array()) 951 { 952 if (!$this->_cacheTplRendered($key, $options)) { 953 $contents = ob_get_clean(); 954 $this->writeFragment($key, $contents, $options); 955 } 956 return $contents; 957 } 958 959 function writeFragment($key, $content, $options = array()) 960 { 961 if (!$this->cacheConfigured()) return; 962 $key = $this->fragmentCachekey($key, $options); 963 964 return $this->_cache_store->save($content, $key, isset($options['host'])? 965 $options['host']:$this->_buildCacheGroup()); 966 } 967 968 function readFragment($key, $options = array()) 969 { 970 if (!$this->cacheConfigured()) return false; 971 972 $orgkey = $key; 973 974 $key = $this->fragmentCachekey($key, $options); 975 976 return $this->_cache_store->get($key, isset($options['host'])? 977 $options['host']:$this->_buildCacheGroup()); 978 } 979 980 function expireFragment($key, $options = array()) 981 { 982 if (!$this->cacheConfigured()) return; 983 984 if (is_array($key) && isset($key['lang']) && $key['lang'] == '*') { 985 $langs = AkLocaleManager::getPublicLocales(); 986 $res = true; 987 foreach ($langs as $lang) { 988 $key['lang'] = $lang; 989 $res = $this->expireFragment($key, $options); 990 } 991 return $res; 992 } 993 $key = $this->fragmentCachekey($key, $options); 994 995 return $this->_cache_store->remove($key, isset($options['host'])? 996 $options['host']:$this->_buildCacheGroup()); 997 } 998 /* 999 * ######################################################################## 1000 * # 1001 * # From AkActionControllerCachingActions 1002 * # 1003 * ######################################################################## 1004 */ 1005 function beforeActionCache() 1006 { 1007 if (!empty($this->_action_include_get_parameters)) { 1008 $getParameters = array(); 1009 foreach ($this->_action_include_get_parameters as $includeGet) { 1010 if (isset($_GET[$includeGet])) { 1011 $getParameters[] = $includeGet.'='.$_GET[$includeGet]; 1012 } 1013 } 1014 $getString = implode(DS,$getParameters); 1015 } else { 1016 $getString = ''; 1017 } 1018 if (empty($this->_action_cache_path)) { 1019 $path = $this->_pathFor().(!empty($getString)?DS.$getString:''); 1020 $this->_action_cache_path = $path; 1021 } 1022 $options = array(); 1023 if (!empty($this->_action_cache_host)) { 1024 $options['host'] = $this->_action_cache_host; 1025 } 1026 $options['namespace'] = 'actions'; 1027 if (($content = $this->readFragment($this->_action_cache_path, $options))!==false) { 1028 $this->_controller->renderText($content); 1029 $this->_rendered_action_cache = true; 1030 $this->_controller->performed_render = true; 1031 $format = $this->_controller->Request->getFormat(); 1032 $this->_controller->Response->addHeader('X-Cached-By','Akelos-Action-Cache'); 1033 $this->_controller->Response->setContentTypeForFormat($format); 1034 } else { 1035 ob_start(); 1036 $this->_rendered_action_cache = false; 1037 } 1038 return true; 1039 } 1040 1041 function afterActionCache() 1042 { 1043 if (!$this->_cachingAllowed() || $this->_rendered_action_cache === true) return; 1044 1045 $this->_controller->handleResponse(); 1046 $contents = ob_get_flush(); 1047 $strlenBefore = strlen($contents); 1048 $contents = $this->_stripCacheSkipSections($contents); 1049 $strlenAfter = strlen($contents); 1050 1051 if ($strlenAfter != $strlenBefore) { 1052 /** 1053 * We dont want the flash message to be cached in the browser 1054 */ 1055 $this->_controller->Response->addHeader('Expires', gmdate('D, d M Y H:i:s',0)); 1056 } 1057 1058 $options = array(); 1059 if (!empty($this->_action_cache_host)) { 1060 $options['host'] = $this->_action_cache_host; 1061 } 1062 $options['namespace'] = 'actions'; 1063 $this->writeFragment($this->_action_cache_path , $contents, $options); 1064 return true; 1065 } 1066 function getCacheType() 1067 { 1068 return $this->_caching_type; 1069 } 1070 function _setCachesAction($options) 1071 { 1072 if (!$this->_perform_caching) return; 1073 if (is_string($options)) { 1074 $options = Ak::toArray($options); 1075 } 1076 1077 $default_options = array('include_get_parameters'=>array(), 1078 'cache_path'=>''); 1079 Ak::parseOptions($options, $default_options,array(),true); 1080 $this->_caches_action = $options; 1081 1082 $actionName = $this->_controller->getActionName(); 1083 1084 if ($this->_caching_type == null && isset($this->_caches_action[$actionName])) { 1085 $this->_caching_type = 'action'; 1086 $this->_action_include_get_parameters = $this->_caches_action[$actionName]['include_get_parameters']; 1087 $path = $this->_caches_action[$actionName]['cache_path']; 1088 $parts = parse_url($path); 1089 if (isset($parts['host'])) { 1090 $this->_action_cache_host = $parts['host']; 1091 $this->_action_cache_path = $parts['path']; 1092 } else { 1093 $this->_action_cache_path = $path; 1094 } 1095 1096 if (!isset($this->_action_cache_host)) { 1097 $this->_action_cache_host = $this->_controller->Request->getHost(); 1098 } 1099 $this->_action_cache_path = $this->_actionPath($this->_action_cache_path); 1100 $this->_controller->prependBeforeFilter(array(&$this,'beforeActionCache')); 1101 $this->_controller->appendAfterFilter(array(&$this,'afterActionCache')); 1102 } 1103 1104 } 1105 1106 function _actionPath($options) 1107 { 1108 $extension = $this->_controller->Request->getFormat();//$this->_extractExtension($this->_controller->Request->getPath()); 1109 if (is_array($options)) { 1110 $path = $this->_pathFor($options); 1111 } else if ($options == null || empty($options)) { 1112 $path = $this->_pathFor(); 1113 } else { 1114 $path = $options; 1115 } 1116 $path = $this->_normalize($path); 1117 $path = $this->_addExtension($path, $extension); 1118 return $path; 1119 } 1120 1121 function expireAction($options, $params = array()) 1122 { 1123 if (is_array($options) && !isset($options['lang'])) { 1124 $options['lang'] = '*'; 1125 } 1126 $params['namespace'] = 'actions'; 1127 return $this->expireFragment($options, $params); 1128 } 1129 function _normalize($path) 1130 { 1131 $path = $path == '/' ? '/index':$path; 1132 return $path; 1133 } 1134 function _addExtension($path, $extension) 1135 { 1136 if (!empty($extension) && substr($path,-strlen($extension))!==$extension) { 1137 $path = $path.'.'.$extension; 1138 } 1139 return $path; 1140 } 1141 1142 function _extractExtension($file_path) 1143 { 1144 preg_match('/^[^\.]+\.(.+)$/',$file_path, $matches); 1145 return isset($matches[1])?$matches[1]:null; 1146 } 1147 function _pathFor($options = array(), $normalize = true) 1148 { 1149 $options = empty($options)?$this->_controller->params:$options; 1150 $options['controller'] = !isset($options['controller']) ? (isset($this->_controller->params['controller']) ? 1151 $this->_controller->params['controller']:null): 1152 $options['controller']; 1153 $options['action'] = !isset($options['action']) ? (isset($this->_controller->params['action']) ? 1154 $this->_controller->params['action']:null): 1155 $options['action']; 1156 $options['id'] = isset($options['id']) ? $options['id']: false; 1157 1158 $options['skip_relative_url_root']=true; 1159 $url = $this->_controller->urlFor($options); 1160 $parts = parse_url($url); 1161 $path = isset($parts['path'])?$parts['path']:''; 1162 if ($normalize && (!isset($options['action']) || (isset($options['action']) && $options['action']==AK_DEFAULT_ACTION && !strstr($path,'/'.AK_DEFAULT_ACTION)))) { 1163 $path = rtrim($path,'/'); 1164 $parts = preg_split('/\/+/',$path); 1165 $parts[] = AK_DEFAULT_ACTION; 1166 $path = implode('/', $parts); 1167 } 1168 $path = rtrim($path,'/'); 1169 1170 return $path; 1171 } 1172 1173 /** 1174 * ######################################################################## 1175 * # 1176 * # END OF AkActionController callable methods 1177 * # 1178 * ######################################################################### 1179 */ 1180 1181 /** 1182 * Looks up the cache store from the option array 1183 * 1184 * @param array $options 1185 */ 1186 function _setCacheStore($options=array()) 1187 { 1188 $this->_cache_store = AkCache::lookupStore($options); 1189 } 1190 1191 /** 1192 * @access protected 1193 */ 1194 function _cache($key, $options = null) 1195 { 1196 $return = false; 1197 if ($this->cacheConfigured()) { 1198 $return = $this->_cache_store->fetch(AkCache::expandCacheKey($key, $this->_controller), $options); 1199 } 1200 return $return; 1201 } 1202 1203 /** 1204 * methods used statically from the pagecache files 1205 */ 1206 function _handleIfModifiedSince($modifiedTimestamp, $exit=true,$sendHeaders = true, $returnHeaders = false) 1207 { 1208 $headers = array(); 1209 $expiryTimestamp = time() + 60*60; 1210 $time = null; 1211 if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { 1212 $time = $_SERVER['HTTP_IF_MODIFIED_SINCE']; 1213 $ifModifiedSince = preg_replace('/;.*$/', '', $time); 1214 $timestamp = strtotime($ifModifiedSince); 1215 } else { 1216 $timestamp = 0; 1217 } 1218 1219 1220 $gmTime = mktime(gmdate('H'), gmdate('i'), gmdate('s'), gmdate('m'), gmdate('d'), gmdate('Y')); 1221 $time = time(); 1222 $diff = $time - $gmTime; 1223 if ($modifiedTimestamp <= $timestamp) { 1224 if ($sendHeaders) { 1225 header('HTTP/1.1 304 Not Modified'); 1226 } 1227 if ($returnHeaders) { 1228 $headers[] = 'Status: 304'; 1229 } 1230 $addHeaders = AkCacheHandler::_sendAdditionalHeaders($sendHeaders, $returnHeaders); 1231 $headers = array_merge($addHeaders, $headers); 1232 if ($exit) { 1233 exit; 1234 } 1235 1236 if ($returnHeaders) { 1237 1238 return $headers; 1239 } 1240 } else { 1241 if ($sendHeaders) { 1242 header('Last-Modified: '.gmdate('D, d M Y H:i:s', $modifiedTimestamp).' GMT'); 1243 } else if ($returnHeaders) { 1244 $headers[]='Last-Modified: '.gmdate('D, d M Y H:i:s', $modifiedTimestamp).' GMT'; 1245 } 1246 } 1247 return $headers; 1248 } 1249 1250 function _handleEtag($headers,$sendHeaders, $returnHeaders, $exit) 1251 { 1252 $outHeaders = array(); 1253 if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) { 1254 foreach ($headers as $header) { 1255 if (stristr($header,'etag: '.$_SERVER['HTTP_IF_NONE_MATCH'])) { 1256 if ($sendHeaders) { 1257 header('HTTP/1.1 304 Not Modified'); 1258 } 1259 if ($returnHeaders) { 1260 $outHeaders[] = 'Status: 304'; 1261 1262 } 1263 if ($exit) { 1264 exit; 1265 } 1266 break; 1267 } 1268 } 1269 } 1270 return $outHeaders; 1271 } 1272 1273 1274 function _handleEncodingAliases($header, $acceptedEncodings) 1275 { 1276 $_encodingAliases = array('gzip','x-gzip', 'compress', 'x-compress'); 1277 $parts = split(': ',$header); 1278 if (strtolower($parts[0])=='content-encoding' && 1279 isset($parts[1]) && 1280 in_array($parts[1],$_encodingAliases)) { 1281 $acceptedEncodings = array_intersect($acceptedEncodings,$_encodingAliases); 1282 if (isset($acceptedEncodings[0])) { 1283 $header =$parts[0].': '.$acceptedEncodings[0]; 1284 } 1285 } 1286 return $header; 1287 } 1288 function _getAcceptedEncodings() 1289 { 1290 $encodings = isset($_SERVER['HTTP_ACCEPT_ENCODING'])?$_SERVER['HTTP_ACCEPT_ENCODING']:''; 1291 $encodings = preg_split('/\s*,\s*/',$encodings); 1292 return $encodings; 1293 } 1294 1295 function _sendAdditionalHeaders($sendHeaders = true, $returnHeaders = false) 1296 { 1297 $additionalHeaders = array('X-Cached-By: Akelos'); 1298 if ($sendHeaders) { 1299 foreach($additionalHeaders as $additionalHeader) { 1300 header($additionalHeader); 1301 } 1302 } 1303 if ($returnHeaders) { 1304 return $additionalHeaders; 1305 } 1306 } 1307 1308 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Mon Oct 27 12:43:49 2008 | Cross-referenced by PHPXref 0.6 |