_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 JCacheController
*
* @since 11.1
*/
public static function getInstance($type = 'output', $options = array())
{
return JCacheController::getInstance($type, $options);
}
/**
* Get the storage handlers
*
* @return array
*
* @since 11.1
*/
public static function getStores()
{
$handlers = array();
// Get an iterator and loop trough the driver classes.
$iterator = new DirectoryIterator(__DIR__ . '/storage');
/** @type $file DirectoryIterator */
foreach ($iterator as $file)
{
$fileName = $file->getFilename();
// Only load for php files.
if (!$file->isFile() || $file->getExtension() != '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
*
* @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 object
*
* @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
*
* @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'])
{
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
*
* @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
*
* @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 stdClass Object with properties of 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
*
* @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
*
* @since 11.1
*/
public function &_getStorage()
{
$hash = md5(serialize($this->_options));
if (isset(self::$_handler[$hash]))
{
return self::$_handler[$hash];
}
try
{
self::$_handler[$hash] = JCacheStorage::getInstance($this->_options['storage'], $this->_options);
}
catch (RuntimeException $e)
{
self::$_handler[$hash] = $e;
JLog::add($e->getMessage(), JLog::WARNING, 'jerror');
}
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'])
&& method_exists($document, 'mergeHeadData'))
{
$document->mergeHeadData($data['head']);
}
elseif (isset($data['head']) && method_exists($document, 'setHeadData'))
{
$document->setHeadData($data['head']);
}
// Get the document MIME encoding out of the cache
if (isset($data['mime_encoding']))
{
$document->setMimeEncoding($data['mime_encoding'], true);
}
// 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.
$app->getPathway()->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)
{
$app->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 = '##';
$replacement = '';
$data['body'] = preg_replace($search, $replacement, $data['body']);
$body = $data['body'];
}
// Get the document body out of the cache.
return $body;
}
/**
* Create workarounds for 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);
// Special treatment for script and style declarations.
if (($now == 'script' || $now == 'style') && is_array($newvalue) && is_array($options['headerbefore'][$now]))
{
foreach ($newvalue as $type => $currentScriptStr)
{
if (isset($options['headerbefore'][$now][strtolower($type)]))
{
$oldScriptStr = $options['headerbefore'][$now][strtolower($type)];
if ($oldScriptStr != $currentScriptStr)
{
// Save only the appended declaration.
$newvalue[strtolower($type)] = StringHelper::substr($currentScriptStr, StringHelper::strlen($oldScriptStr));
}
}
}
}
}
else
{
$newvalue = $headnow[$now];
}
if (!empty($newvalue))
{
$cached['head'][$now] = $newvalue;
}
}
}
else
{
$cached['head'] = $document->getHeadData();
}
}
// Document MIME encoding
$cached['mime_encoding'] = $document->getMimeEncoding();
// Pathway data
if ($app->isSite() && $loptions['nopathway'] != 1)
{
$cached['pathway'] = is_array($data) && isset($data['pathway']) ? $data['pathway'] : $app->getPathway()->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'] = $app->getHeaders();
}
return $cached;
}
/**
* Create a safe ID for cached data from URL parameters
*
* @return string MD5 encoded cache ID
*
* @since 11.1
*/
public static function makeId()
{
$app = JFactory::getApplication();
$registeredurlparams = new stdClass;
// Get url parameters set by plugins
if (!empty($app->registeredurlparams))
{
$registeredurlparams = $app->registeredurlparams;
}
// Platform defaults
$defaulturlparams = array(
'format' => 'WORD',
'option' => 'WORD',
'view' => 'WORD',
'layout' => 'WORD',
'tpl' => 'CMD',
'id' => 'INT'
);
// Use platform defaults if parameter doesn't already exist.
foreach ($defaulturlparams as $param => $type)
{
if (!property_exists($registeredurlparams, $param))
{
$registeredurlparams->$param = $type;
}
}
$safeuriaddon = new stdClass;
foreach ($registeredurlparams as $key => $value)
{
$safeuriaddon->$key = $app->input->get($key, null, $value);
}
return md5(serialize($safeuriaddon));
}
/**
* Set a prefix cache key if device calls for separate caching
*
* @return string
*
* @since 3.5
*/
public static function getPlatformPrefix()
{
// No prefix when Global Config is set to no platfom specific prefix
if (!JFactory::getConfig()->get('cache_platformprefix', '0'))
{
return '';
}
$webclient = new JApplicationWebClient;
if ($webclient->mobile)
{
return 'M-';
}
return '';
}
/**
* Add a directory where JCache should search for handlers. You may either pass a string or an array of directories.
*
* @param array|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;
}
}