727 lines
17 KiB
PHP
727 lines
17 KiB
PHP
<?php
|
|
/**
|
|
* @package Joomla.Platform
|
|
* @subpackage Cache
|
|
*
|
|
* @copyright Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
|
|
* @license GNU General Public License version 2 or later; see LICENSE
|
|
*/
|
|
|
|
defined('JPATH_PLATFORM') or die;
|
|
|
|
/**
|
|
* Joomla! Cache base object
|
|
*
|
|
* @package Joomla.Platform
|
|
* @subpackage Cache
|
|
* @since 11.1
|
|
*/
|
|
class JCache
|
|
{
|
|
/**
|
|
* @var object Storage handler
|
|
* @since 11.1
|
|
*/
|
|
public static $_handler = array();
|
|
|
|
/**
|
|
* @var array Options
|
|
* @since 11.1
|
|
*/
|
|
public $_options;
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param array $options options
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function __construct($options)
|
|
{
|
|
$conf = JFactory::getConfig();
|
|
|
|
$this->_options = array(
|
|
'cachebase' => $conf->get('cache_path', JPATH_CACHE),
|
|
'lifetime' => (int) $conf->get('cachetime'),
|
|
'language' => $conf->get('language', 'en-GB'),
|
|
'storage' => $conf->get('cache_handler', ''),
|
|
'defaultgroup' => 'default',
|
|
'locking' => true,
|
|
'locktime' => 15,
|
|
'checkTime' => true,
|
|
'caching' => ($conf->get('caching') >= 1) ? true : false);
|
|
|
|
// Overwrite default options with given options
|
|
foreach ($options as $option => $value)
|
|
{
|
|
if (isset($options[$option]) && $options[$option] !== '')
|
|
{
|
|
$this->_options[$option] = $options[$option];
|
|
}
|
|
}
|
|
|
|
if (empty($this->_options['storage']))
|
|
{
|
|
$this->_options['caching'] = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a reference to a cache adapter object, always creating it
|
|
*
|
|
* @param string $type The cache object type to instantiate
|
|
* @param array $options The array of options
|
|
*
|
|
* @return JCache A JCache object
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public static function getInstance($type = 'output', $options = array())
|
|
{
|
|
return JCacheController::getInstance($type, $options);
|
|
}
|
|
|
|
/**
|
|
* Get the storage handlers
|
|
*
|
|
* @return array An array of available storage handlers
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public static function getStores()
|
|
{
|
|
$handlers = array();
|
|
|
|
// Get an iterator and loop trough the driver classes.
|
|
$iterator = new DirectoryIterator(__DIR__ . '/storage');
|
|
|
|
foreach ($iterator as $file)
|
|
{
|
|
$fileName = $file->getFilename();
|
|
|
|
// Only load for php files.
|
|
// Note: DirectoryIterator::getExtension only available PHP >= 5.3.6
|
|
if (!$file->isFile()
|
|
|| substr($fileName, strrpos($fileName, '.') + 1) != 'php'
|
|
|| $fileName == 'helper.php')
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Derive the class name from the type.
|
|
$class = str_ireplace('.php', '', 'JCacheStorage' . ucfirst(trim($fileName)));
|
|
|
|
// If the class doesn't exist we have nothing left to do but look at the next type. We did our best.
|
|
if (!class_exists($class))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Sweet! Our class exists, so now we just need to know if it passes its test method.
|
|
if ($class::isSupported())
|
|
{
|
|
// Connector names should not have file extensions.
|
|
$handlers[] = str_ireplace('.php', '', $fileName);
|
|
}
|
|
}
|
|
|
|
return $handlers;
|
|
}
|
|
|
|
/**
|
|
* Set caching enabled state
|
|
*
|
|
* @param boolean $enabled True to enable caching
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function setCaching($enabled)
|
|
{
|
|
$this->_options['caching'] = $enabled;
|
|
}
|
|
|
|
/**
|
|
* Get caching state
|
|
*
|
|
* @return boolean Caching state
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function getCaching()
|
|
{
|
|
return $this->_options['caching'];
|
|
}
|
|
|
|
/**
|
|
* Set cache lifetime
|
|
*
|
|
* @param integer $lt Cache lifetime
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function setLifeTime($lt)
|
|
{
|
|
$this->_options['lifetime'] = $lt;
|
|
}
|
|
|
|
/**
|
|
* Get cached data by id and group
|
|
*
|
|
* @param string $id The cache data id
|
|
* @param string $group The cache data group
|
|
*
|
|
* @return mixed boolean False on failure or a cached data string
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function get($id, $group = null)
|
|
{
|
|
// Get the default group
|
|
$group = ($group) ? $group : $this->_options['defaultgroup'];
|
|
|
|
// Get the storage
|
|
$handler = $this->_getStorage();
|
|
if (!($handler instanceof Exception) && $this->_options['caching'])
|
|
{
|
|
return $handler->get($id, $group, $this->_options['checkTime']);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get a list of all cached data
|
|
*
|
|
* @return mixed Boolean false on failure or an object with a list of cache groups and data
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function getAll()
|
|
{
|
|
// Get the storage
|
|
$handler = $this->_getStorage();
|
|
if (!($handler instanceof Exception) && $this->_options['caching'])
|
|
{
|
|
return $handler->getAll();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Store the cached data by id and group
|
|
*
|
|
* @param mixed $data The data to store
|
|
* @param string $id The cache data id
|
|
* @param string $group The cache data group
|
|
*
|
|
* @return boolean True if cache stored
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function store($data, $id, $group = null)
|
|
{
|
|
// Get the default group
|
|
$group = ($group) ? $group : $this->_options['defaultgroup'];
|
|
|
|
// Get the storage and store the cached data
|
|
$handler = $this->_getStorage();
|
|
if (!($handler instanceof Exception) && $this->_options['caching'])
|
|
{
|
|
$handler->_lifetime = $this->_options['lifetime'];
|
|
return $handler->store($id, $group, $data);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Remove a cached data entry by id and group
|
|
*
|
|
* @param string $id The cache data id
|
|
* @param string $group The cache data group
|
|
*
|
|
* @return boolean True on success, false otherwise
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function remove($id, $group = null)
|
|
{
|
|
// Get the default group
|
|
$group = ($group) ? $group : $this->_options['defaultgroup'];
|
|
|
|
// Get the storage
|
|
$handler = $this->_getStorage();
|
|
if (!($handler instanceof Exception))
|
|
{
|
|
return $handler->remove($id, $group);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Clean cache for a group given a mode.
|
|
*
|
|
* group mode : cleans all cache in the group
|
|
* notgroup mode : cleans all cache not in the group
|
|
*
|
|
* @param string $group The cache data group
|
|
* @param string $mode The mode for cleaning cache [group|notgroup]
|
|
*
|
|
* @return boolean True on success, false otherwise
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function clean($group = null, $mode = 'group')
|
|
{
|
|
// Get the default group
|
|
$group = ($group) ? $group : $this->_options['defaultgroup'];
|
|
|
|
// Get the storage handler
|
|
$handler = $this->_getStorage();
|
|
if (!($handler instanceof Exception))
|
|
{
|
|
return $handler->clean($group, $mode);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Garbage collect expired cache data
|
|
*
|
|
* @return boolean True on success, false otherwise.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function gc()
|
|
{
|
|
// Get the storage handler
|
|
$handler = $this->_getStorage();
|
|
if (!($handler instanceof Exception))
|
|
{
|
|
return $handler->gc();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Set lock flag on cached item
|
|
*
|
|
* @param string $id The cache data id
|
|
* @param string $group The cache data group
|
|
* @param string $locktime The default locktime for locking the cache.
|
|
*
|
|
* @return object Properties are lock and locklooped
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function lock($id, $group = null, $locktime = null)
|
|
{
|
|
$returning = new stdClass;
|
|
$returning->locklooped = false;
|
|
|
|
// Get the default group
|
|
$group = ($group) ? $group : $this->_options['defaultgroup'];
|
|
|
|
// Get the default locktime
|
|
$locktime = ($locktime) ? $locktime : $this->_options['locktime'];
|
|
|
|
// Allow storage handlers to perform locking on their own
|
|
// NOTE drivers with lock need also unlock or unlocking will fail because of false $id
|
|
$handler = $this->_getStorage();
|
|
if (!($handler instanceof Exception) && $this->_options['locking'] == true && $this->_options['caching'] == true)
|
|
{
|
|
$locked = $handler->lock($id, $group, $locktime);
|
|
if ($locked !== false)
|
|
{
|
|
return $locked;
|
|
}
|
|
}
|
|
|
|
// Fallback
|
|
$curentlifetime = $this->_options['lifetime'];
|
|
|
|
// Set lifetime to locktime for storing in children
|
|
$this->_options['lifetime'] = $locktime;
|
|
|
|
$looptime = $locktime * 10;
|
|
$id2 = $id . '_lock';
|
|
|
|
if ($this->_options['locking'] == true && $this->_options['caching'] == true)
|
|
{
|
|
$data_lock = $this->get($id2, $group);
|
|
|
|
}
|
|
else
|
|
{
|
|
$data_lock = false;
|
|
$returning->locked = false;
|
|
}
|
|
|
|
if ($data_lock !== false)
|
|
{
|
|
$lock_counter = 0;
|
|
|
|
// Loop until you find that the lock has been released.
|
|
// That implies that data get from other thread has finished
|
|
while ($data_lock !== false)
|
|
{
|
|
|
|
if ($lock_counter > $looptime)
|
|
{
|
|
$returning->locked = false;
|
|
$returning->locklooped = true;
|
|
break;
|
|
}
|
|
|
|
usleep(100);
|
|
$data_lock = $this->get($id2, $group);
|
|
$lock_counter++;
|
|
}
|
|
}
|
|
|
|
if ($this->_options['locking'] == true && $this->_options['caching'] == true)
|
|
{
|
|
$returning->locked = $this->store(1, $id2, $group);
|
|
}
|
|
|
|
// Revert lifetime to previous one
|
|
$this->_options['lifetime'] = $curentlifetime;
|
|
|
|
return $returning;
|
|
}
|
|
|
|
/**
|
|
* Unset lock flag on cached item
|
|
*
|
|
* @param string $id The cache data id
|
|
* @param string $group The cache data group
|
|
*
|
|
* @return boolean True on success, false otherwise.
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function unlock($id, $group = null)
|
|
{
|
|
$unlock = false;
|
|
|
|
// Get the default group
|
|
$group = ($group) ? $group : $this->_options['defaultgroup'];
|
|
|
|
// Allow handlers to perform unlocking on their own
|
|
$handler = $this->_getStorage();
|
|
if (!($handler instanceof Exception) && $this->_options['caching'])
|
|
{
|
|
$unlocked = $handler->unlock($id, $group);
|
|
if ($unlocked !== false)
|
|
{
|
|
return $unlocked;
|
|
}
|
|
}
|
|
|
|
// Fallback
|
|
if ($this->_options['caching'])
|
|
{
|
|
$unlock = $this->remove($id . '_lock', $group);
|
|
}
|
|
|
|
return $unlock;
|
|
}
|
|
|
|
/**
|
|
* Get the cache storage handler
|
|
*
|
|
* @return JCacheStorage A JCacheStorage object
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public function &_getStorage()
|
|
{
|
|
$hash = md5(serialize($this->_options));
|
|
|
|
if (isset(self::$_handler[$hash]))
|
|
{
|
|
return self::$_handler[$hash];
|
|
}
|
|
|
|
self::$_handler[$hash] = JCacheStorage::getInstance($this->_options['storage'], $this->_options);
|
|
return self::$_handler[$hash];
|
|
}
|
|
|
|
/**
|
|
* Perform workarounds on retrieved cached data
|
|
*
|
|
* @param string $data Cached data
|
|
* @param array $options Array of options
|
|
*
|
|
* @return string Body of cached data
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public static function getWorkarounds($data, $options = array())
|
|
{
|
|
$app = JFactory::getApplication();
|
|
$document = JFactory::getDocument();
|
|
$body = null;
|
|
|
|
// Get the document head out of the cache.
|
|
if (isset($options['mergehead']) && $options['mergehead'] == 1 && isset($data['head']) && !empty($data['head']))
|
|
{
|
|
$document->mergeHeadData($data['head']);
|
|
}
|
|
elseif (isset($data['head']) && method_exists($document, 'setHeadData'))
|
|
{
|
|
$document->setHeadData($data['head']);
|
|
}
|
|
|
|
// If the pathway buffer is set in the cache data, get it.
|
|
if (isset($data['pathway']) && is_array($data['pathway']))
|
|
{
|
|
// Push the pathway data into the pathway object.
|
|
$pathway = $app->getPathWay();
|
|
$pathway->setPathway($data['pathway']);
|
|
}
|
|
|
|
// @todo check if the following is needed, seems like it should be in page cache
|
|
// If a module buffer is set in the cache data, get it.
|
|
if (isset($data['module']) && is_array($data['module']))
|
|
{
|
|
// Iterate through the module positions and push them into the document buffer.
|
|
foreach ($data['module'] as $name => $contents)
|
|
{
|
|
$document->setBuffer($contents, 'module', $name);
|
|
}
|
|
}
|
|
|
|
// Set cached headers.
|
|
if (isset($data['headers']) && $data['headers'])
|
|
{
|
|
foreach($data['headers'] as $header)
|
|
{
|
|
JResponse::setHeader($header['name'], $header['value']);
|
|
}
|
|
}
|
|
|
|
// The following code searches for a token in the cached page and replaces it with the
|
|
// proper token.
|
|
if (isset($data['body']))
|
|
{
|
|
$token = JSession::getFormToken();
|
|
$search = '#<input type="hidden" name="[0-9a-f]{32}" value="1" />#';
|
|
$replacement = '<input type="hidden" name="' . $token . '" value="1" />';
|
|
|
|
$data['body'] = preg_replace($search, $replacement, $data['body']);
|
|
$body = $data['body'];
|
|
}
|
|
|
|
// Get the document body out of the cache.
|
|
return $body;
|
|
}
|
|
|
|
/**
|
|
* Create workarounded data to be cached
|
|
*
|
|
* @param string $data Cached data
|
|
* @param array $options Array of options
|
|
*
|
|
* @return string Data to be cached
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public static function setWorkarounds($data, $options = array())
|
|
{
|
|
$loptions = array(
|
|
'nopathway' => 0,
|
|
'nohead' => 0,
|
|
'nomodules' => 0,
|
|
'modulemode' => 0,
|
|
);
|
|
|
|
if (isset($options['nopathway']))
|
|
{
|
|
$loptions['nopathway'] = $options['nopathway'];
|
|
}
|
|
|
|
if (isset($options['nohead']))
|
|
{
|
|
$loptions['nohead'] = $options['nohead'];
|
|
}
|
|
|
|
if (isset($options['nomodules']))
|
|
{
|
|
$loptions['nomodules'] = $options['nomodules'];
|
|
}
|
|
|
|
if (isset($options['modulemode']))
|
|
{
|
|
$loptions['modulemode'] = $options['modulemode'];
|
|
}
|
|
|
|
$app = JFactory::getApplication();
|
|
$document = JFactory::getDocument();
|
|
|
|
if ($loptions['nomodules'] != 1)
|
|
{
|
|
// Get the modules buffer before component execution.
|
|
$buffer1 = $document->getBuffer();
|
|
if (!is_array($buffer1))
|
|
{
|
|
$buffer1 = array();
|
|
}
|
|
|
|
// Make sure the module buffer is an array.
|
|
if (!isset($buffer1['module']) || !is_array($buffer1['module']))
|
|
{
|
|
$buffer1['module'] = array();
|
|
}
|
|
}
|
|
|
|
// View body data
|
|
$cached['body'] = $data;
|
|
|
|
// Document head data
|
|
if ($loptions['nohead'] != 1 && method_exists($document, 'getHeadData'))
|
|
{
|
|
|
|
if ($loptions['modulemode'] == 1)
|
|
{
|
|
$headnow = $document->getHeadData();
|
|
$unset = array('title', 'description', 'link', 'links', 'metaTags');
|
|
|
|
foreach ($unset as $un)
|
|
{
|
|
unset($headnow[$un]);
|
|
unset($options['headerbefore'][$un]);
|
|
}
|
|
|
|
$cached['head'] = array();
|
|
|
|
// Only store what this module has added
|
|
foreach ($headnow as $now => $value)
|
|
{
|
|
if (isset($options['headerbefore'][$now]))
|
|
{
|
|
// We have to serialize the content of the arrays because the may contain other arrays which is a notice in PHP 5.4 and newer
|
|
$nowvalue = array_map('serialize', $headnow[$now]);
|
|
$beforevalue = array_map('serialize', $options['headerbefore'][$now]);
|
|
$newvalue = array_diff_assoc($nowvalue, $beforevalue);
|
|
$newvalue = array_map('unserialize', $newvalue);
|
|
}
|
|
else
|
|
{
|
|
$newvalue = $headnow[$now];
|
|
}
|
|
|
|
if (!empty($newvalue))
|
|
{
|
|
$cached['head'][$now] = $newvalue;
|
|
}
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
$cached['head'] = $document->getHeadData();
|
|
}
|
|
}
|
|
|
|
// Pathway data
|
|
if ($app->isSite() && $loptions['nopathway'] != 1)
|
|
{
|
|
$pathway = $app->getPathWay();
|
|
$cached['pathway'] = isset($data['pathway']) ? $data['pathway'] : $pathway->getPathway();
|
|
}
|
|
|
|
if ($loptions['nomodules'] != 1)
|
|
{
|
|
// @todo Check if the following is needed, seems like it should be in page cache
|
|
// Get the module buffer after component execution.
|
|
$buffer2 = $document->getBuffer();
|
|
if (!is_array($buffer2))
|
|
{
|
|
$buffer2 = array();
|
|
}
|
|
|
|
// Make sure the module buffer is an array.
|
|
if (!isset($buffer2['module']) || !is_array($buffer2['module']))
|
|
{
|
|
$buffer2['module'] = array();
|
|
}
|
|
|
|
// Compare the second module buffer against the first buffer.
|
|
$cached['module'] = array_diff_assoc($buffer2['module'], $buffer1['module']);
|
|
}
|
|
|
|
// Headers data
|
|
if (isset($options['headers']) && $options['headers'])
|
|
{
|
|
$cached['headers'] = JResponse::getHeaders();
|
|
}
|
|
|
|
return $cached;
|
|
}
|
|
|
|
/**
|
|
* Create safe id for cached data from url parameters set by plugins and framework
|
|
*
|
|
* @return string md5 encoded cacheid
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public static function makeId()
|
|
{
|
|
$app = JFactory::getApplication();
|
|
|
|
// Get url parameters set by plugins
|
|
if (!empty($app->registeredurlparams))
|
|
{
|
|
$registeredurlparams = $app->registeredurlparams;
|
|
}
|
|
|
|
// Platform defaults
|
|
$registeredurlparams->format = 'WORD';
|
|
$registeredurlparams->option = 'WORD';
|
|
$registeredurlparams->view = 'WORD';
|
|
$registeredurlparams->layout = 'WORD';
|
|
$registeredurlparams->tpl = 'CMD';
|
|
$registeredurlparams->id = 'INT';
|
|
|
|
$safeuriaddon = new stdClass;
|
|
|
|
foreach ($registeredurlparams as $key => $value)
|
|
{
|
|
$safeuriaddon->$key = $app->input->get($key, null, $value);
|
|
}
|
|
|
|
return md5(serialize($safeuriaddon));
|
|
}
|
|
|
|
/**
|
|
* Add a directory where JCache should search for handlers. You may
|
|
* either pass a string or an array of directories.
|
|
*
|
|
* @param string $path A path to search.
|
|
*
|
|
* @return array An array with directory elements
|
|
*
|
|
* @since 11.1
|
|
*/
|
|
public static function addIncludePath($path = '')
|
|
{
|
|
static $paths;
|
|
|
|
if (!isset($paths))
|
|
{
|
|
$paths = array();
|
|
}
|
|
if (!empty($path) && !in_array($path, $paths))
|
|
{
|
|
jimport('joomla.filesystem.path');
|
|
array_unshift($paths, JPath::clean($path));
|
|
}
|
|
return $paths;
|
|
}
|
|
}
|