[ Index ]

PHP Cross Reference of Akelos Framework

title

Body

[close]

/AkActionController/ -> AkCacheHandler.php (source)

   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  }


Generated: Mon Oct 27 12:43:49 2008 Cross-referenced by PHPXref 0.6