_root = $options['cachebase']; // Workaround for php 5.3 $locked_files = &$this->_locked_files; // Remove empty locked files at script shutdown. $clearAtShutdown = function () use (&$locked_files) { foreach ($locked_files as $path => $handle) { if (is_resource($handle)) { @flock($handle, LOCK_UN); @fclose($handle); } // Delete only the existing file if it is empty. if (@filesize($path) === 0) { @unlink($path); } unset($locked_files[$path]); } }; register_shutdown_function($clearAtShutdown); } /** * Check if the cache contains data stored by ID and group * * @param string $id The cache data ID * @param string $group The cache data group * * @return boolean * * @since 3.7.0 */ public function contains($id, $group) { return $this->_checkExpire($id, $group); } /** * Get cached data by ID and group * * @param string $id The cache data ID * @param string $group The cache data group * @param boolean $checkTime True to verify cache time expiration threshold * * @return mixed Boolean false on failure or a cached data object * * @since 11.1 */ public function get($id, $group, $checkTime = true) { $path = $this->_getFilePath($id, $group); $close = false; if ($checkTime == false || ($checkTime == true && $this->_checkExpire($id, $group) === true)) { if (file_exists($path)) { if (isset($this->_locked_files[$path])) { $_fileopen = $this->_locked_files[$path]; } else { $_fileopen = @fopen($path, 'rb'); // There is no lock, we have to close file after store data $close = true; } if ($_fileopen) { // On Windows system we can not use file_get_contents on the file locked by yourself $data = stream_get_contents($_fileopen); if ($close) { @fclose($_fileopen); } if ($data !== false) { // Remove the initial die() statement return str_replace('#x#', '', $data); } } } } return false; } /** * Get all cached data * * @return mixed Boolean false on failure or a cached data object * * @since 11.1 */ public function getAll() { $path = $this->_root; $folders = $this->_folders($path); $data = array(); foreach ($folders as $folder) { $files = $this->_filesInFolder($path . '/' . $folder); $item = new JCacheStorageHelper($folder); foreach ($files as $file) { $item->updateSize(filesize($path . '/' . $folder . '/' . $file) / 1024); } $data[$folder] = $item; } return $data; } /** * Store the data to cache by ID and group * * @param string $id The cache data ID * @param string $group The cache data group * @param string $data The data to store in cache * * @return boolean * * @since 11.1 */ public function store($id, $group, $data) { $path = $this->_getFilePath($id, $group); $close = false; // Prepend a die string $data = '#x#' . $data; if (isset($this->_locked_files[$path])) { $_fileopen = $this->_locked_files[$path]; // Because lock method uses flag c+b we have to truncate it manually @ftruncate($_fileopen, 0); } else { $_fileopen = @fopen($path, 'wb'); // There is no lock, we have to close file after store data $close = true; } if ($_fileopen) { $length = strlen($data); $result = @fwrite($_fileopen, $data, $length); if ($close) { @fclose($_fileopen); } return $result === $length; } 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) { $path = $this->_getFilePath($id, $group); if (!@unlink($path)) { return false; } return true; } /** * 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 * * @since 11.1 */ public function clean($group, $mode = null) { $return = true; $folder = $group; if (trim($folder) == '') { $mode = 'notgroup'; } switch ($mode) { case 'notgroup' : $folders = $this->_folders($this->_root); for ($i = 0, $n = count($folders); $i < $n; $i++) { if ($folders[$i] != $folder) { $return |= $this->_deleteFolder($this->_root . '/' . $folders[$i]); } } break; case 'group' : default : if (is_dir($this->_root . '/' . $folder)) { $return = $this->_deleteFolder($this->_root . '/' . $folder); } break; } return (bool) $return; } /** * Garbage collect expired cache data * * @return boolean * * @since 11.1 */ public function gc() { $result = true; // Files older than lifeTime get deleted from cache $files = $this->_filesInFolder($this->_root, '', true, true, array('.svn', 'CVS', '.DS_Store', '__MACOSX', 'index.html')); foreach ($files as $file) { $time = @filemtime($file); if (($time + $this->_lifetime) < $this->_now || empty($time)) { $result |= @unlink($file); } } return (bool) $result; } /** * Test to see if the storage handler is available. * * @return boolean * * @since 12.1 */ public static function isSupported() { return is_writable(JFactory::getConfig()->get('cache_path', JPATH_CACHE)); } /** * Lock cached item * * @param string $id The cache data ID * @param string $group The cache data group * @param integer $locktime Cached item max lock time * * @return mixed Boolean false if locking failed or an object containing properties lock and locklooped * * @since 11.1 */ public function lock($id, $group, $locktime) { $returning = new stdClass; $returning->locklooped = false; $looptime = $locktime * 10; $path = $this->_getFilePath($id, $group); $_fileopen = @fopen($path, 'c+b'); if (!$_fileopen) { $returning->locked = false; return $returning; } $data_lock = (bool) @flock($_fileopen, LOCK_EX|LOCK_NB); 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) { break; } usleep(100); $data_lock = (bool) @flock($_fileopen, LOCK_EX|LOCK_NB); $lock_counter++; } $returning->locklooped = true; } if ($data_lock === true) { // Remember resource, flock release lock if you unset/close resource $this->_locked_files[$path] = $_fileopen; } $returning->locked = $data_lock; return $returning; } /** * Unlock 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) { $path = $this->_getFilePath($id, $group); if (isset($this->_locked_files[$path])) { $ret = (bool) @flock($this->_locked_files[$path], LOCK_UN); @fclose($this->_locked_files[$path]); unset($this->_locked_files[$path]); return $ret; } return true; } /** * Check if a cache object has expired * * @param string $id Cache ID to check * @param string $group The cache data group * * @return boolean True if the cache ID is valid * * @since 11.1 */ protected function _checkExpire($id, $group) { $path = $this->_getFilePath($id, $group); // Check prune period if (file_exists($path)) { $time = @filemtime($path); if (($time + $this->_lifetime) < $this->_now || empty($time)) { @unlink($path); return false; } // If now the file does not exist then return false too. if (@filesize($path) == 0) { return false; } return true; } return false; } /** * Get a cache file path from an ID/group pair * * @param string $id The cache data ID * @param string $group The cache data group * * @return boolean|string The path to the data object or boolean false if the cache directory does not exist * * @since 11.1 */ protected function _getFilePath($id, $group) { $name = $this->_getCacheId($id, $group); $dir = $this->_root . '/' . $group; // If the folder doesn't exist try to create it if (!is_dir($dir)) { // Make sure the index file is there $indexFile = $dir . '/index.html'; @mkdir($dir) && file_put_contents($indexFile, '