File manager - Edit - /home/opticamezl/www/newok/Cache.tar
Back
CacheControllerFactory.php 0000644 00000002507 15172755227 0011675 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Cache; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Default factory for creating CacheController objects * * @since 4.0.0 */ class CacheControllerFactory implements CacheControllerFactoryInterface { /** * Method to get an instance of a cache controller. * * @param string $type The cache object type to instantiate * @param array $options Array of options * * @return CacheController * * @since 4.0.0 * @throws \RuntimeException */ public function createCacheController($type = 'output', $options = []): CacheController { if (!$type) { $type = 'output'; } $type = strtolower(preg_replace('/[^A-Z0-9_\.-]/i', '', $type)); $class = __NAMESPACE__ . '\\Controller\\' . ucfirst($type) . 'Controller'; // The class should now be loaded if (!class_exists($class)) { throw new \RuntimeException('Unable to load Cache Controller: ' . $type, 500); } return new $class($options); } } Cache.php 0000644 00000051541 15172755227 0006303 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2005 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Cache; use Joomla\Application\Web\WebClient; use Joomla\CMS\Cache\Exception\CacheExceptionInterface; use Joomla\CMS\Factory; use Joomla\CMS\Filesystem\Path; use Joomla\CMS\Session\Session; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Joomla! Cache base object * * @since 1.7.0 */ class Cache { /** * Storage handler * * @var CacheStorage[] * @since 1.7.0 */ public static $_handler = []; /** * Cache options * * @var array * @since 1.7.0 */ public $_options; /** * Constructor * * @param array $options Cache options * * @since 1.7.0 */ public function __construct($options) { $app = Factory::getApplication(); $this->_options = [ 'cachebase' => $app->get('cache_path', JPATH_CACHE), 'lifetime' => (int) $app->get('cachetime'), 'language' => $app->get('language', 'en-GB'), 'storage' => $app->get('cache_handler', ''), 'defaultgroup' => 'default', 'locking' => true, 'locktime' => 15, 'checkTime' => true, 'caching' => ($app->get('caching') >= 1), ]; // 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->setCaching(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 CacheController * * @since 1.7.0 * * @deprecated 4.2 will be removed in 6.0 * Use the cache controller factory instead * Example: Factory::getContainer()->get(CacheControllerFactoryInterface::class)->createCacheController($type, $options); */ public static function getInstance($type = 'output', $options = []) { @trigger_error( sprintf( '%s() is deprecated. The cache controller should be fetched from the factory.', __METHOD__ ), E_USER_DEPRECATED ); return Factory::getContainer()->get(CacheControllerFactoryInterface::class)->createCacheController($type, $options); } /** * Get the storage handlers * * @return array * * @since 1.7.0 */ public static function getStores() { $handlers = []; // Get an iterator and loop through 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 === 'CacheStorageHelper.php') { continue; } // Derive the class name from the type. $class = str_ireplace('.php', '', __NAMESPACE__ . '\\Storage\\' . 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. $handler = str_ireplace('Storage.php', '', $fileName); $handler = str_ireplace('.php', '', $handler); $handlers[] = strtolower($handler); } } return $handlers; } /** * Set caching enabled state * * @param boolean $enabled True to enable caching * * @return void * * @since 1.7.0 */ public function setCaching($enabled) { $this->_options['caching'] = $enabled; } /** * Get caching state * * @return boolean * * @since 1.7.0 */ public function getCaching() { return $this->_options['caching']; } /** * Set cache lifetime * * @param integer $lt Cache lifetime in minutes * * @return void * * @since 1.7.0 */ public function setLifeTime($lt) { $this->_options['lifetime'] = $lt; } /** * 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 = null) { if (!$this->getCaching()) { return false; } // Get the default group $group = $group ?: $this->_options['defaultgroup']; return $this->_getStorage()->contains($id, $group); } /** * 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 1.7.0 */ public function get($id, $group = null) { if (!$this->getCaching()) { return false; } // Get the default group $group = $group ?: $this->_options['defaultgroup']; return $this->_getStorage()->get($id, $group, $this->_options['checkTime']); } /** * 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 1.7.0 */ public function getAll() { if (!$this->getCaching()) { return false; } return $this->_getStorage()->getAll(); } /** * 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 1.7.0 */ public function store($data, $id, $group = null) { if (!$this->getCaching()) { return false; } // Get the default group $group = $group ?: $this->_options['defaultgroup']; // Get the storage and store the cached data return $this->_getStorage()->store($id, $group, $data); } /** * 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 1.7.0 */ public function remove($id, $group = null) { // Get the default group $group = $group ?: $this->_options['defaultgroup']; try { return $this->_getStorage()->remove($id, $group); } catch (CacheExceptionInterface $e) { if (!$this->getCaching()) { return false; } throw $e; } } /** * 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 1.7.0 */ public function clean($group = null, $mode = 'group') { // Get the default group $group = $group ?: $this->_options['defaultgroup']; try { return $this->_getStorage()->clean($group, $mode); } catch (CacheExceptionInterface $e) { if (!$this->getCaching()) { return false; } throw $e; } } /** * Garbage collect expired cache data * * @return boolean * * @since 1.7.0 */ public function gc() { try { return $this->_getStorage()->gc(); } catch (CacheExceptionInterface $e) { if (!$this->getCaching()) { return false; } throw $e; } } /** * 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 1.7.0 */ public function lock($id, $group = null, $locktime = null) { $returning = new \stdClass(); $returning->locklooped = false; if (!$this->getCaching()) { $returning->locked = false; return $returning; } // Get the default group $group = $group ?: $this->_options['defaultgroup']; // Get the default 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 ($this->_options['locking'] == 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) { $data_lock = $handler->get($id2, $group, $this->_options['checkTime']); } 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 = $handler->get($id2, $group, $this->_options['checkTime']); $lock_counter++; } } if ($this->_options['locking'] == true) { $returning->locked = $handler->store($id2, $group, 1); } // 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 1.7.0 */ public function unlock($id, $group = null) { if (!$this->getCaching()) { return false; } // Get the default group $group = $group ?: $this->_options['defaultgroup']; // Allow handlers to perform unlocking on their own $handler = $this->_getStorage(); $unlocked = $handler->unlock($id, $group); if ($unlocked !== false) { return $unlocked; } // Fallback return $handler->remove($id . '_lock', $group); } /** * Get the cache storage handler * * @return CacheStorage * * @since 1.7.0 */ public function &_getStorage() { $hash = md5(serialize($this->_options)); if (isset(self::$_handler[$hash])) { return self::$_handler[$hash]; } self::$_handler[$hash] = CacheStorage::getInstance($this->_options['storage'], $this->_options); return self::$_handler[$hash]; } /** * Perform workarounds on retrieved cached data * * @param array $data Cached data * @param array $options Array of options * * @return string Body of cached data * * @since 1.7.0 */ public static function getWorkarounds($data, $options = []) { $app = Factory::getApplication(); $document = Factory::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 = Session::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 workarounds for data to be cached * * @param string $data Cached data * @param array $options Array of options * * @return array Data to be cached * * @since 1.7.0 */ public static function setWorkarounds($data, $options = []) { $loptions = [ '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 = Factory::getApplication(); $document = Factory::getDocument(); if ($loptions['nomodules'] != 1) { // Get the modules buffer before component execution. $buffer1 = $document->getBuffer(); if (!\is_array($buffer1)) { $buffer1 = []; } // Make sure the module buffer is an array. if (!isset($buffer1['module']) || !\is_array($buffer1['module'])) { $buffer1['module'] = []; } } // 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 = ['title', 'description', 'link', 'links', 'metaTags']; foreach ($unset as $key) { unset($headNow[$key]); } // Sanitize empty data foreach (\array_keys($headNow) as $key) { if (!isset($headNow[$key]) || $headNow[$key] === []) { unset($headNow[$key]); } } $cached['head'] = $headNow; } else { $cached['head'] = $document->getHeadData(); // Document MIME encoding $cached['mime_encoding'] = $document->getMimeEncoding(); } } // Pathway data if ($app->isClient('site') && $loptions['nopathway'] != 1) { $cached['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 = []; } // Make sure the module buffer is an array. if (!isset($buffer2['module']) || !\is_array($buffer2['module'])) { $buffer2['module'] = []; } // 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 1.7.0 */ public static function makeId() { $app = Factory::getApplication(); $registeredurlparams = new \stdClass(); // Get url parameters set by plugins if (!empty($app->registeredurlparams)) { $registeredurlparams = $app->registeredurlparams; } // Platform defaults $defaulturlparams = [ 'format' => 'CMD', 'option' => 'CMD', 'view' => 'CMD', 'layout' => 'CMD', 'tpl' => 'CMD', 'id' => 'STRING', ]; // 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->getInput()->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 platform specific prefix if (!Factory::getApplication()->get('cache_platformprefix', false)) { return ''; } $webclient = new WebClient(); if ($webclient->mobile) { return 'M-'; } return ''; } /** * Add a directory where Cache 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 1.7.0 */ public static function addIncludePath($path = '') { static $paths; if (!isset($paths)) { $paths = []; } if (!empty($path) && !\in_array($path, $paths)) { array_unshift($paths, Path::clean($path)); } return $paths; } } CacheControllerFactoryAwareTrait.php 0000644 00000003320 15172755227 0013653 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Cache; use Joomla\CMS\Factory; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Defines the trait for a CacheControllerFactoryInterface Aware Class. * * @since 4.2.0 */ trait CacheControllerFactoryAwareTrait { /** * CacheControllerFactoryInterface * * @var CacheControllerFactoryInterface * * @since 4.2.0 */ private $cacheControllerFactory; /** * Get the CacheControllerFactoryInterface. * * @return CacheControllerFactoryInterface * * @since 4.2.0 */ protected function getCacheControllerFactory(): CacheControllerFactoryInterface { if ($this->cacheControllerFactory) { return $this->cacheControllerFactory; } @trigger_error( sprintf('A cache controller is needed in %s. An UnexpectedValueException will be thrown in 5.0.', __CLASS__), E_USER_DEPRECATED ); return Factory::getContainer()->get(CacheControllerFactoryInterface::class); } /** * Set the cache controller factory to use. * * @param CacheControllerFactoryInterface $cacheControllerFactory The cache controller factory to use. * * @return void * * @since 4.2.0 */ public function setCacheControllerFactory(CacheControllerFactoryInterface $cacheControllerFactory = null): void { $this->cacheControllerFactory = $cacheControllerFactory; } } CacheStorage.php 0000644 00000022062 15172755227 0007624 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2007 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Cache; use Joomla\CMS\Cache\Exception\UnsupportedCacheException; use Joomla\CMS\Factory; use Joomla\CMS\Filesystem\Path; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Abstract cache storage handler * * @since 1.7.0 * @note As of 4.0 this class will be abstract */ class CacheStorage { /** * The raw object name * * @var string * @since 1.7.0 */ protected $rawname; /** * Time that the cache storage handler was instantiated * * @var integer * @since 1.7.0 */ public $_now; /** * Cache lifetime * * @var integer * @since 1.7.0 */ public $_lifetime; /** * Flag if locking is enabled * * @var boolean * @since 1.7.0 */ public $_locking; /** * Language code * * @var string * @since 1.7.0 */ public $_language; /** * Application name * * @var string * @since 1.7.0 */ public $_application; /** * Object hash * * @var string * @since 1.7.0 */ public $_hash; /** * The threshold * * @var integer * @since 4.3.0 */ public $_threshold; /** * Constructor * * @param array $options Optional parameters * * @since 1.7.0 */ public function __construct($options = []) { $app = Factory::getApplication(); $this->_hash = md5($app->get('secret', '')); $this->_application = $options['application'] ?? md5(JPATH_CONFIGURATION); $this->_language = $options['language'] ?? 'en-GB'; $this->_locking = $options['locking'] ?? true; $this->_lifetime = ($options['lifetime'] ?? $app->get('cachetime')) * 60; $this->_now = $options['now'] ?? time(); // Set time threshold value. If the lifetime is not set, default to 60 (0 is BAD) // _threshold is now available ONLY as a legacy (it's deprecated). It's no longer used in the core. if (empty($this->_lifetime)) { $this->_threshold = $this->_now - 60; $this->_lifetime = 60; } else { $this->_threshold = $this->_now - $this->_lifetime; } } /** * Returns a cache storage handler object. * * @param string $handler The cache storage handler to instantiate * @param array $options Array of handler options * * @return CacheStorage * * @since 1.7.0 * @throws \UnexpectedValueException * @throws UnsupportedCacheException */ public static function getInstance($handler = null, $options = []) { static $now = null; if (!isset($handler)) { $handler = Factory::getApplication()->get('cache_handler'); if (empty($handler)) { throw new \UnexpectedValueException('Cache Storage Handler not set.'); } } if (\is_null($now)) { $now = time(); } $options['now'] = $now; // We can't cache this since options may change... $handler = strtolower(preg_replace('/[^A-Z0-9_\.-]/i', '', $handler)); /** @var CacheStorage $class */ $class = __NAMESPACE__ . '\\Storage\\' . ucfirst($handler) . 'Storage'; if (!class_exists($class)) { $class = 'JCacheStorage' . ucfirst($handler); } if (!class_exists($class)) { // Search for the class file in the JCacheStorage include paths. $path = Path::find(self::addIncludePath(), strtolower($handler) . '.php'); if ($path === false) { throw new UnsupportedCacheException(sprintf('Unable to load Cache Storage: %s', $handler)); } \JLoader::register($class, $path); // The class should now be loaded if (!class_exists($class)) { throw new UnsupportedCacheException(sprintf('Unable to load Cache Storage: %s', $handler)); } } // Validate the cache storage is supported on this platform if (!$class::isSupported()) { throw new UnsupportedCacheException(sprintf('The %s Cache Storage is not supported on this platform.', $handler)); } return new $class($options); } /** * 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 false; } /** * 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 1.7.0 */ public function get($id, $group, $checkTime = true) { return false; } /** * Get all cached data * * @return mixed Boolean false on failure or a cached data object * * @since 1.7.0 */ public function getAll() { return false; } /** * 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 1.7.0 */ public function store($id, $group, $data) { return true; } /** * 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 1.7.0 */ public function remove($id, $group) { 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 1.7.0 */ public function clean($group, $mode = null) { return true; } /** * Flush all existing items in storage. * * @return boolean * * @since 3.6.3 */ public function flush() { return true; } /** * Garbage collect expired cache data * * @return boolean * * @since 1.7.0 */ public function gc() { return true; } /** * Test to see if the storage handler is available. * * @return boolean * * @since 3.0.0 */ public static function isSupported() { return true; } /** * 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 1.7.0 */ public function lock($id, $group, $locktime) { return false; } /** * Unlock cached item * * @param string $id The cache data ID * @param string $group The cache data group * * @return boolean * * @since 1.7.0 */ public function unlock($id, $group = null) { return false; } /** * Get a cache ID string from an ID/group pair * * @param string $id The cache data ID * @param string $group The cache data group * * @return string * * @since 1.7.0 */ protected function _getCacheId($id, $group) { $name = md5($this->_application . '-' . $id . '-' . $this->_language); $this->rawname = $this->_hash . '-' . $name; return Cache::getPlatformPrefix() . $this->_hash . '-cache-' . $group . '-' . $name; } /** * Add a directory where CacheStorage 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 1.7.0 */ public static function addIncludePath($path = '') { static $paths; if (!isset($paths)) { $paths = []; } if (!empty($path) && !\in_array($path, $paths)) { array_unshift($paths, Path::clean($path)); } return $paths; } } CacheController.php 0000644 00000011705 15172755227 0010345 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2010 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Cache; use Joomla\CMS\Factory; use Joomla\CMS\Filesystem\Path; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Public cache handler * * @since 1.7.0 * @note As of 4.0 this class will be abstract */ class CacheController { /** * Cache object * * @var Cache * @since 1.7.0 */ public $cache; /** * Array of options * * @var array * @since 1.7.0 */ public $options; /** * Constructor * * @param array $options Array of options * * @since 1.7.0 */ public function __construct($options) { $this->cache = new Cache($options); $this->options = &$this->cache->_options; // Overwrite default options with given options foreach ($options as $option => $value) { if (isset($options[$option])) { $this->options[$option] = $options[$option]; } } } /** * Magic method to proxy CacheController method calls to Cache * * @param string $name Name of the function * @param array $arguments Array of arguments for the function * * @return mixed * * @since 1.7.0 */ public function __call($name, $arguments) { return \call_user_func_array([$this->cache, $name], $arguments); } /** * Returns a reference to a cache adapter object, always creating it * * @param string $type The cache object type to instantiate; default is output. * @param array $options Array of options * * @return CacheController * * @since 1.7.0 * @throws \RuntimeException * * @deprecated 4.2 will be removed in 6.0 * Use the cache controller factory instead * Example: Factory::getContainer()->get(CacheControllerFactoryInterface::class)->createCacheController($type, $options); */ public static function getInstance($type = 'output', $options = []) { @trigger_error( sprintf( '%s() is deprecated. The cache controller should be fetched from the factory.', __METHOD__ ), E_USER_DEPRECATED ); try { return Factory::getContainer()->get(CacheControllerFactoryInterface::class)->createCacheController($type, $options); } catch (\RuntimeException $e) { $type = strtolower(preg_replace('/[^A-Z0-9_\.-]/i', '', $type)); $class = 'JCacheController' . ucfirst($type); if (!class_exists($class)) { // Search for the class file in the Cache include paths. $path = Path::find(self::addIncludePath(), strtolower($type) . '.php'); if ($path !== false) { \JLoader::register($class, $path); } // The class should now be loaded if (!class_exists($class)) { throw new \RuntimeException('Unable to load Cache Controller: ' . $type, 500); } // Only trigger a deprecation notice if the file and class are found @trigger_error( 'Support for including cache controllers using path lookup is deprecated and will be removed in 5.0.' . ' Use a custom cache controller factory instead.', E_USER_DEPRECATED ); } return new $class($options); } } /** * Add a directory where Cache should search for controllers. 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 1.7.0 * * @deprecated 4.2 will be removed in 6.0 * Use the cache controller factory instead * Example: Factory::getContainer()->get(CacheControllerFactoryInterface::class)->createCacheController($type, $options); */ public static function addIncludePath($path = '') { static $paths; if (!isset($paths)) { $paths = []; } if (!empty($path) && !\in_array($path, $paths)) { // Only trigger a deprecation notice when adding a lookup path @trigger_error( 'Support for including cache controllers using path lookup is deprecated and will be removed in 5.0.' . ' Use a custom cache controller factory instead.', E_USER_DEPRECATED ); array_unshift($paths, Path::clean($path)); } return $paths; } } CacheControllerFactoryInterface.php 0000644 00000001570 15172755227 0013515 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Cache; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Interface defining a factory which can create CacheController objects * * @since 4.0.0 */ interface CacheControllerFactoryInterface { /** * Method to get an instance of a cache controller. * * @param string $type The cache object type to instantiate * @param array $options Array of options * * @return CacheController * * @since 4.0.0 * @throws \RuntimeException */ public function createCacheController($type = 'output', $options = []): CacheController; } Storage/CacheStorageHelper.php 0000644 00000002346 15172755227 0012373 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2010 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Cache\Storage; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Cache storage helper functions. * * @since 1.7.0 */ class CacheStorageHelper { /** * Cache data group * * @var string * @since 1.7.0 */ public $group = ''; /** * Cached item size * * @var string * @since 1.7.0 */ public $size = 0; /** * Counter * * @var integer * @since 1.7.0 */ public $count = 0; /** * Constructor * * @param string $group The cache data group * * @since 1.7.0 */ public function __construct($group) { $this->group = $group; } /** * Increase cache items count. * * @param string $size Cached item size * * @return void * * @since 1.7.0 */ public function updateSize($size) { $this->size += $size; $this->count++; } } Storage/RedisStorage.php 0000644 00000020153 15172755227 0011272 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2014 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Cache\Storage; use Joomla\CMS\Cache\CacheStorage; use Joomla\CMS\Cache\Exception\CacheConnectingException; use Joomla\CMS\Factory; use Joomla\CMS\Log\Log; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Redis cache storage handler for PECL * * @since 3.4 */ class RedisStorage extends CacheStorage { /** * Redis connection object * * @var \Redis * @since 3.4 */ protected static $_redis = null; /** * Persistent session flag * * @var boolean * @since 3.4 */ protected $_persistent = false; /** * Constructor * * @param array $options Optional parameters. * * @since 3.4 */ public function __construct($options = []) { parent::__construct($options); if (static::$_redis === null) { $this->getConnection(); } } /** * Create the Redis connection * * @return \Redis|boolean Redis connection object on success, boolean on failure * * @since 3.4 * @note As of 4.0 this method will throw a JCacheExceptionConnecting object on connection failure */ protected function getConnection() { if (static::isSupported() == false) { return false; } $app = Factory::getApplication(); $this->_persistent = $app->get('redis_persist', true); $server = [ 'host' => $app->get('redis_server_host', 'localhost'), 'port' => $app->get('redis_server_port', 6379), 'auth' => $app->get('redis_server_auth', null), 'db' => (int) $app->get('redis_server_db', null), ]; // If you are trying to connect to a socket file, ignore the supplied port if ($server['host'][0] === '/') { $server['port'] = 0; } static::$_redis = new \Redis(); try { if ($this->_persistent) { $connection = static::$_redis->pconnect($server['host'], $server['port']); } else { $connection = static::$_redis->connect($server['host'], $server['port']); } } catch (\RedisException $e) { $connection = false; Log::add($e->getMessage(), Log::DEBUG); } if ($connection == false) { static::$_redis = null; throw new CacheConnectingException('Redis connection failed', 500); } try { $auth = $server['auth'] ? static::$_redis->auth($server['auth']) : true; } catch (\RedisException $e) { $auth = false; Log::add($e->getMessage(), Log::DEBUG); } if ($auth === false) { static::$_redis = null; throw new CacheConnectingException('Redis authentication failed', 500); } $select = static::$_redis->select($server['db']); if ($select == false) { static::$_redis = null; throw new CacheConnectingException('Redis failed to select database', 500); } try { static::$_redis->ping(); } catch (\RedisException $e) { static::$_redis = null; throw new CacheConnectingException('Redis ping failed', 500); } return static::$_redis; } /** * 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) { if (static::isConnected() == false) { return false; } // Redis exists returns integer values lets convert that to boolean see: https://redis.io/commands/exists return (bool) static::$_redis->exists($this->_getCacheId($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 3.4 */ public function get($id, $group, $checkTime = true) { if (static::isConnected() == false) { return false; } return static::$_redis->get($this->_getCacheId($id, $group)); } /** * Get all cached data * * @return mixed Boolean false on failure or a cached data object * * @since 3.4 */ public function getAll() { if (static::isConnected() == false) { return false; } $allKeys = static::$_redis->keys('*'); $data = []; $secret = $this->_hash; if (!empty($allKeys)) { foreach ($allKeys as $key) { $namearr = explode('-', $key); if ($namearr !== false && $namearr[0] == $secret && $namearr[1] === 'cache') { $group = $namearr[2]; if (!isset($data[$group])) { $item = new CacheStorageHelper($group); } else { $item = $data[$group]; } $item->updateSize(\strlen($key) * 8); $data[$group] = $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 3.4 */ public function store($id, $group, $data) { if (static::isConnected() == false) { return false; } static::$_redis->setex($this->_getCacheId($id, $group), $this->_lifetime, $data); return true; } /** * 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 3.4 */ public function remove($id, $group) { if (static::isConnected() == false) { return false; } return (bool) static::$_redis->del($this->_getCacheId($id, $group)); } /** * 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 3.4 */ public function clean($group, $mode = null) { if (static::isConnected() == false) { return false; } $allKeys = static::$_redis->keys('*'); if ($allKeys === false) { $allKeys = []; } $secret = $this->_hash; foreach ($allKeys as $key) { if (strpos($key, $secret . '-cache-' . $group . '-') === 0 && $mode === 'group') { static::$_redis->del($key); } if (strpos($key, $secret . '-cache-' . $group . '-') !== 0 && $mode !== 'group') { static::$_redis->del($key); } } return true; } /** * Test to see if the storage handler is available. * * @return boolean * * @since 3.4 */ public static function isSupported() { return class_exists('\\Redis'); } /** * Test to see if the Redis connection is available. * * @return boolean * * @since 3.4 */ public static function isConnected() { return static::$_redis instanceof \Redis; } } Storage/MemcachedStorage.php 0000644 00000027134 15172755227 0012100 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2012 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Cache\Storage; use Joomla\CMS\Cache\Cache; use Joomla\CMS\Cache\CacheStorage; use Joomla\CMS\Cache\Exception\CacheConnectingException; use Joomla\CMS\Factory; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Memcached cache storage handler * * @link https://www.php.net/manual/en/book.memcached.php * @since 3.0.0 */ class MemcachedStorage extends CacheStorage { /** * Memcached connection object * * @var \Memcached * @since 3.0.0 */ protected static $_db = null; /** * Payload compression level * * @var integer * @since 3.0.0 */ protected $_compress = 0; /** * Constructor * * @param array $options Optional parameters. * * @since 3.0.0 */ public function __construct($options = []) { parent::__construct($options); $this->_compress = Factory::getApplication()->get('memcached_compress', false) ? \Memcached::OPT_COMPRESSION : 0; if (static::$_db === null) { $this->getConnection(); } } /** * Create the Memcached connection * * @return void * * @since 3.0.0 * @throws \RuntimeException */ protected function getConnection() { if (!static::isSupported()) { throw new \RuntimeException('Memcached Extension is not available'); } $app = Factory::getApplication(); $host = $app->get('memcached_server_host', 'localhost'); $port = $app->get('memcached_server_port', 11211); // Create the memcached connection if ($app->get('memcached_persist', true)) { static::$_db = new \Memcached($this->_hash); $servers = static::$_db->getServerList(); if ($servers && ($servers[0]['host'] != $host || $servers[0]['port'] != $port)) { static::$_db->resetServerList(); $servers = []; } if (!$servers) { static::$_db->addServer($host, $port); } } else { static::$_db = new \Memcached(); static::$_db->addServer($host, $port); } static::$_db->setOption(\Memcached::OPT_COMPRESSION, $this->_compress); $stats = static::$_db->getStats(); $result = !empty($stats["$host:$port"]) && $stats["$host:$port"]['pid'] > 0; if (!$result) { // Null out the connection to inform the constructor it will need to attempt to connect if this class is instantiated again static::$_db = null; throw new CacheConnectingException('Could not connect to memcached server'); } } /** * Get a cache_id string from an id/group pair * * @param string $id The cache data id * @param string $group The cache data group * * @return string The cache_id string * * @since 1.7.0 */ protected function _getCacheId($id, $group) { $prefix = Cache::getPlatformPrefix(); $length = \strlen($prefix); $cache_id = parent::_getCacheId($id, $group); if ($length) { // Memcached use suffix instead of prefix $cache_id = substr($cache_id, $length) . strrev($prefix); } return $cache_id; } /** * 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) { static::$_db->get($this->_getCacheId($id, $group)); return static::$_db->getResultCode() !== \Memcached::RES_NOTFOUND; } /** * 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 3.0.0 */ public function get($id, $group, $checkTime = true) { return static::$_db->get($this->_getCacheId($id, $group)); } /** * Get all cached data * * @return mixed Boolean false on failure or a cached data object * * @since 3.0.0 */ public function getAll() { $keys = static::$_db->get($this->_hash . '-index'); $secret = $this->_hash; $data = []; if (\is_array($keys)) { foreach ($keys as $key) { if (empty($key)) { continue; } $namearr = explode('-', $key->name); if ($namearr !== false && $namearr[0] == $secret && $namearr[1] === 'cache') { $group = $namearr[2]; if (!isset($data[$group])) { $item = new CacheStorageHelper($group); } else { $item = $data[$group]; } $item->updateSize($key->size); $data[$group] = $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 3.0.0 */ public function store($id, $group, $data) { $cache_id = $this->_getCacheId($id, $group); if (!$this->lockindex()) { return false; } $index = static::$_db->get($this->_hash . '-index'); if (!\is_array($index)) { $index = []; } $tmparr = new \stdClass(); $tmparr->name = $cache_id; $tmparr->size = \strlen($data); $index[] = $tmparr; static::$_db->set($this->_hash . '-index', $index, 0); $this->unlockindex(); static::$_db->set($cache_id, $data, $this->_lifetime); return true; } /** * 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 3.0.0 */ public function remove($id, $group) { $cache_id = $this->_getCacheId($id, $group); if (!$this->lockindex()) { return false; } $index = static::$_db->get($this->_hash . '-index'); if (\is_array($index)) { foreach ($index as $key => $value) { if ($value->name == $cache_id) { unset($index[$key]); static::$_db->set($this->_hash . '-index', $index, 0); break; } } } $this->unlockindex(); return static::$_db->delete($cache_id); } /** * 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 3.0.0 */ public function clean($group, $mode = null) { if (!$this->lockindex()) { return false; } $index = static::$_db->get($this->_hash . '-index'); if (\is_array($index)) { $prefix = $this->_hash . '-cache-' . $group . '-'; foreach ($index as $key => $value) { if (strpos($value->name, $prefix) === 0 xor $mode !== 'group') { static::$_db->delete($value->name); unset($index[$key]); } } static::$_db->set($this->_hash . '-index', $index, 0); } $this->unlockindex(); return true; } /** * Flush all existing items in storage. * * @return boolean * * @since 3.6.3 */ public function flush() { if (!$this->lockindex()) { return false; } return static::$_db->flush(); } /** * Test to see if the storage handler is available. * * @return boolean * * @since 3.0.0 */ public static function isSupported() { /* * GAE and HHVM have both had instances where Memcached the class was defined but no extension was loaded. * If the class is there, we can assume support. */ return class_exists('Memcached'); } /** * 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 3.0.0 */ public function lock($id, $group, $locktime) { $returning = new \stdClass(); $returning->locklooped = false; $looptime = $locktime * 10; $cache_id = $this->_getCacheId($id, $group); $data_lock = static::$_db->add($cache_id . '_lock', 1, $locktime); 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 = static::$_db->add($cache_id . '_lock', 1, $locktime); $lock_counter++; } $returning->locklooped = true; } $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 3.0.0 */ public function unlock($id, $group = null) { $cache_id = $this->_getCacheId($id, $group) . '_lock'; return static::$_db->delete($cache_id); } /** * Lock cache index * * @return boolean * * @since 3.0.0 */ protected function lockindex() { $looptime = 300; $data_lock = static::$_db->add($this->_hash . '-index_lock', 1, 30); 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) { return false; } usleep(100); $data_lock = static::$_db->add($this->_hash . '-index_lock', 1, 30); $lock_counter++; } } return true; } /** * Unlock cache index * * @return boolean * * @since 3.0.0 */ protected function unlockindex() { return static::$_db->delete($this->_hash . '-index_lock'); } } Storage/ApcuStorage.php 0000644 00000020143 15172755227 0011113 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Cache\Storage; use Joomla\CMS\Cache\CacheStorage; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * APCu cache storage handler * * @link https://www.php.net/manual/en/ref.apcu.php * @since 3.5 */ class ApcuStorage extends CacheStorage { /** * 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 apcu_exists($this->_getCacheId($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 3.5 */ public function get($id, $group, $checkTime = true) { return apcu_fetch($this->_getCacheId($id, $group)); } /** * Get all cached data * * @return mixed Boolean false on failure or a cached data object * * @since 3.5 */ public function getAll() { $allinfo = apcu_cache_info(); $keys = $allinfo['cache_list']; $secret = $this->_hash; $data = []; foreach ($keys as $key) { if (isset($key['info'])) { // The internal key name changed with APCu 4.0.7 from key to info $name = $key['info']; } elseif (isset($key['entry_name'])) { // Some APCu modules changed the internal key name from key to entry_name $name = $key['entry_name']; } else { // A fall back for the old internal key name $name = $key['key']; } $namearr = explode('-', $name); if ($namearr !== false && $namearr[0] == $secret && $namearr[1] === 'cache') { $group = $namearr[2]; if (!isset($data[$group])) { $item = new CacheStorageHelper($group); } else { $item = $data[$group]; } $item->updateSize($key['mem_size']); $data[$group] = $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 3.5 */ public function store($id, $group, $data) { return apcu_store($this->_getCacheId($id, $group), $data, $this->_lifetime); } /** * 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 3.5 */ public function remove($id, $group) { $cache_id = $this->_getCacheId($id, $group); // The apcu_delete function returns false if the ID does not exist if (apcu_exists($cache_id)) { return apcu_delete($cache_id); } 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 3.5 */ public function clean($group, $mode = null) { $allinfo = apcu_cache_info(); $keys = $allinfo['cache_list']; $secret = $this->_hash; foreach ($keys as $key) { if (isset($key['info'])) { // The internal key name changed with APCu 4.0.7 from key to info $internalKey = $key['info']; } elseif (isset($key['entry_name'])) { // Some APCu modules changed the internal key name from key to entry_name $internalKey = $key['entry_name']; } else { // A fall back for the old internal key name $internalKey = $key['key']; } if (strpos($internalKey, $secret . '-cache-' . $group . '-') === 0 xor $mode !== 'group') { apcu_delete($internalKey); } } return true; } /** * Garbage collect expired cache data * * @return boolean * * @since 3.5 */ public function gc() { $allinfo = apcu_cache_info(); $keys = $allinfo['cache_list']; $secret = $this->_hash; foreach ($keys as $key) { if (isset($key['info'])) { // The internal key name changed with APCu 4.0.7 from key to info $internalKey = $key['info']; } elseif (isset($key['entry_name'])) { // Some APCu modules changed the internal key name from key to entry_name $internalKey = $key['entry_name']; } else { // A fall back for the old internal key name $internalKey = $key['key']; } if (strpos($internalKey, $secret . '-cache-')) { apcu_fetch($internalKey); } } return true; } /** * Test to see if the storage handler is available. * * @return boolean * * @since 3.5 */ public static function isSupported() { $supported = \extension_loaded('apcu') && ini_get('apc.enabled'); // If on the CLI interface, the `apc.enable_cli` option must also be enabled if ($supported && PHP_SAPI === 'cli') { $supported = ini_get('apc.enable_cli'); } return (bool) $supported; } /** * 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 3.5 */ public function lock($id, $group, $locktime) { $returning = new \stdClass(); $returning->locklooped = false; $looptime = $locktime * 10; $cache_id = $this->_getCacheId($id, $group) . '_lock'; $data_lock = apcu_add($cache_id, 1, $locktime); 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 = apcu_add($cache_id, 1, $locktime); $lock_counter++; } } $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 3.5 */ public function unlock($id, $group = null) { $cache_id = $this->_getCacheId($id, $group) . '_lock'; // The apcu_delete function returns false if the ID does not exist if (apcu_exists($cache_id)) { return apcu_delete($cache_id); } return true; } } Storage/WincacheStorage.php 0000644 00000013660 15172755227 0011752 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2010 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Cache\Storage; use Joomla\CMS\Cache\CacheStorage; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * WinCache cache storage handler * * @link https://www.php.net/manual/en/book.wincache.php * @since 1.7.0 * @deprecated 4.3 will be removed in 6.0 * WinCache is abandoned and not supported from PHP 8 onwards * Will be removed without replacement */ class WincacheStorage extends CacheStorage { /** * 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 * * @deprecated 4.3 will be removed in 6.0 * Will be removed without replacement */ public function contains($id, $group) { return wincache_ucache_exists($this->_getCacheId($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 1.7.0 * * @deprecated 4.3 will be removed in 6.0 * Will be removed without replacement */ public function get($id, $group, $checkTime = true) { return wincache_ucache_get($this->_getCacheId($id, $group)); } /** * Get all cached data * * @return mixed Boolean false on failure or a cached data object * * @since 1.7.0 * * @deprecated 4.3 will be removed in 6.0 * Will be removed without replacement */ public function getAll() { $allinfo = wincache_ucache_info(); $keys = $allinfo['ucache_entries']; $secret = $this->_hash; $data = []; foreach ($keys as $key) { $name = $key['key_name']; $namearr = explode('-', $name); if ($namearr !== false && $namearr[0] == $secret && $namearr[1] === 'cache') { $group = $namearr[2]; if (!isset($data[$group])) { $item = new CacheStorageHelper($group); } else { $item = $data[$group]; } if (isset($key['value_size'])) { $item->updateSize($key['value_size']); } else { // Dummy, WINCACHE version is too low. $item->updateSize(1); } $data[$group] = $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 1.7.0 * * @deprecated 4.3 will be removed in 6.0 * Will be removed without replacement */ public function store($id, $group, $data) { return wincache_ucache_set($this->_getCacheId($id, $group), $data, $this->_lifetime); } /** * 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 1.7.0 * * @deprecated 4.3 will be removed in 6.0 * Will be removed without replacement */ public function remove($id, $group) { return wincache_ucache_delete($this->_getCacheId($id, $group)); } /** * 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 1.7.0 * * @deprecated 4.3 will be removed in 6.0 * Will be removed without replacement */ public function clean($group, $mode = null) { $allinfo = wincache_ucache_info(); $keys = $allinfo['ucache_entries']; $secret = $this->_hash; foreach ($keys as $key) { if (strpos($key['key_name'], $secret . '-cache-' . $group . '-') === 0 xor $mode !== 'group') { wincache_ucache_delete($key['key_name']); } } return true; } /** * Garbage collect expired cache data * * @return boolean * * @since 1.7.0 * * @deprecated 4.3 will be removed in 6.0 * Will be removed without replacement */ public function gc() { $allinfo = wincache_ucache_info(); $keys = $allinfo['ucache_entries']; $secret = $this->_hash; foreach ($keys as $key) { if (strpos($key['key_name'], $secret . '-cache-')) { wincache_ucache_get($key['key_name']); } } return true; } /** * Test to see if the storage handler is available. * * @return boolean * * @since 3.0.0 * * @deprecated 4.3 will be removed in 6.0 * Will be removed without replacement */ public static function isSupported() { return \extension_loaded('wincache') && \function_exists('wincache_ucache_get') && !strcmp(ini_get('wincache.ucenabled'), '1'); } } Storage/FileStorage.php 0000644 00000051642 15172755227 0011112 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2007 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Cache\Storage; use Joomla\CMS\Cache\CacheStorage; use Joomla\CMS\Language\Text; use Joomla\CMS\Log\Log; use Joomla\Filesystem\File; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * File cache storage handler * * @since 1.7.0 * @note For performance reasons this class does not use the Filesystem package's API */ class FileStorage extends CacheStorage { /** * Root path * * @var string * @since 1.7.0 */ protected $_root; /** * Locked resources * * @var array * @since 3.7.0 * */ protected $_locked_files = []; /** * Constructor * * @param array $options Optional parameters * * @since 1.7.0 */ public function __construct($options = []) { parent::__construct($options); $this->_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) { File::invalidateFileCache($path); @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 1.7.0 */ 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('<?php die("Access Denied"); ?>#x#', '', $data); } } } } return false; } /** * Get all cached data * * @return mixed Boolean false on failure or a cached data object * * @since 1.7.0 */ public function getAll() { $path = $this->_root; $folders = $this->_folders($path); $data = []; foreach ($folders as $folder) { $files = $this->_filesInFolder($path . '/' . $folder); $item = new CacheStorageHelper($folder); foreach ($files as $file) { $item->updateSize(filesize($path . '/' . $folder . '/' . $file)); } $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 1.7.0 */ public function store($id, $group, $data) { $path = $this->_getFilePath($id, $group); $close = false; // Prepend a die string $data = '<?php die("Access Denied"); ?>#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 1.7.0 */ public function remove($id, $group) { $path = $this->_getFilePath($id, $group); File::invalidateFileCache($path); 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 1.7.0 */ 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 1.7.0 */ public function gc() { $result = true; // Files older than lifeTime get deleted from cache $files = $this->_filesInFolder($this->_root, '', true, true, ['.svn', 'CVS', '.DS_Store', '__MACOSX', 'index.html']); foreach ($files as $file) { $time = @filemtime($file); if (($time + $this->_lifetime) < $this->_now || empty($time)) { File::invalidateFileCache($file); $result |= @unlink($file); } } return (bool) $result; } /** * 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 1.7.0 */ 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 1.7.0 */ 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 * * Using @ error suppressor here because between if we did a file_exists() and then filemsize() there will * be a little time space when another process can delete the file and then you get PHP Warning * * @param string $id Cache ID to check * @param string $group The cache data group * * @return boolean True if the cache ID is valid * * @since 1.7.0 */ 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)) { File::invalidateFileCache($path); @unlink($path); return false; } // If, right now, the file does not exist then return false 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 1.7.0 */ 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, '<!DOCTYPE html><title></title>'); } // Make sure the folder exists if (!is_dir($dir)) { return false; } return $dir . '/' . $name . '.php'; } /** * Quickly delete a folder of files * * @param string $path The path to the folder to delete. * * @return boolean * * @since 1.7.0 */ protected function _deleteFolder($path) { // Sanity check if (!$path || !is_dir($path) || empty($this->_root)) { // Bad programmer! Bad, bad programmer! Log::add(__METHOD__ . ' ' . Text::_('JLIB_FILESYSTEM_ERROR_DELETE_BASE_DIRECTORY'), Log::WARNING, 'jerror'); return false; } $path = $this->_cleanPath($path); // Check to make sure path is inside cache folder, we do not want to delete Joomla root! $pos = strpos($path, $this->_cleanPath($this->_root)); if ($pos === false || $pos > 0) { Log::add(__METHOD__ . ' ' . Text::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER', __METHOD__, $path), Log::WARNING, 'jerror'); return false; } // Remove all the files in folder if they exist; disable all filtering $files = $this->_filesInFolder($path, '.', false, true, [], []); if (!empty($files) && !\is_array($files)) { File::invalidateFileCache($files); if (@unlink($files) !== true) { return false; } } elseif (!empty($files) && \is_array($files)) { foreach ($files as $file) { $file = $this->_cleanPath($file); // In case of restricted permissions we delete it one way or the other as long as the owner is either the webserver or the ftp File::invalidateFileCache($file); if (@unlink($file) !== true) { Log::add(__METHOD__ . ' ' . Text::sprintf('JLIB_FILESYSTEM_DELETE_FAILED', basename($file)), Log::WARNING, 'jerror'); return false; } } } // Remove sub-folders of folder; disable all filtering $folders = $this->_folders($path, '.', false, true, [], []); foreach ($folders as $folder) { if (is_link($folder)) { // Don't descend into linked directories, just delete the link. if (@unlink($folder) !== true) { return false; } } elseif ($this->_deleteFolder($folder) !== true) { return false; } } // In case of restricted permissions we zap it one way or the other as long as the owner is either the webserver or the ftp if (@rmdir($path)) { return true; } Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_FOLDER_DELETE', $path), Log::WARNING, 'jerror'); return false; } /** * Function to strip additional / or \ in a path name * * @param string $path The path to clean * @param string $ds Directory separator (optional) * * @return string The cleaned path * * @since 1.7.0 */ protected function _cleanPath($path, $ds = DIRECTORY_SEPARATOR) { $path = trim($path); if (empty($path)) { return $this->_root; } // Remove double slashes and backslashes and convert all slashes and backslashes to DIRECTORY_SEPARATOR $path = preg_replace('#[/\\\\]+#', $ds, $path); return $path; } /** * Utility function to quickly read the files in a folder. * * @param string $path The path of the folder to read. * @param string $filter A filter for file names. * @param mixed $recurse True to recursively search into sub-folders, or an integer to specify the maximum depth. * @param boolean $fullpath True to return the full path to the file. * @param array $exclude Array with names of files which should not be shown in the result. * @param array $excludefilter Array of folder names to exclude * * @return array Files in the given folder. * * @since 1.7.0 */ protected function _filesInFolder( $path, $filter = '.', $recurse = false, $fullpath = false, $exclude = ['.svn', 'CVS', '.DS_Store', '__MACOSX'], $excludefilter = ['^\..*', '.*~'] ) { $arr = []; // Check to make sure the path valid and clean $path = $this->_cleanPath($path); // Is the path a folder? if (!is_dir($path)) { Log::add(__METHOD__ . ' ' . Text::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER', __METHOD__, $path), Log::WARNING, 'jerror'); return false; } // Read the source directory. if (!($handle = @opendir($path))) { return $arr; } if (\count($excludefilter)) { $excludefilter = '/(' . implode('|', $excludefilter) . ')/'; } else { $excludefilter = ''; } while (($file = readdir($handle)) !== false) { if (($file != '.') && ($file != '..') && (!\in_array($file, $exclude)) && (!$excludefilter || !preg_match($excludefilter, $file))) { $dir = $path . '/' . $file; $isDir = is_dir($dir); if ($isDir) { if ($recurse) { if (\is_int($recurse)) { $arr2 = $this->_filesInFolder($dir, $filter, $recurse - 1, $fullpath); } else { $arr2 = $this->_filesInFolder($dir, $filter, $recurse, $fullpath); } $arr = array_merge($arr, $arr2); } } else { if (preg_match("/$filter/", $file)) { if ($fullpath) { $arr[] = $path . '/' . $file; } else { $arr[] = $file; } } } } } closedir($handle); return $arr; } /** * Utility function to read the folders in a folder. * * @param string $path The path of the folder to read. * @param string $filter A filter for folder names. * @param mixed $recurse True to recursively search into sub-folders, or an integer to specify the maximum depth. * @param boolean $fullpath True to return the full path to the folders. * @param array $exclude Array with names of folders which should not be shown in the result. * @param array $excludefilter Array with regular expressions matching folders which should not be shown in the result. * * @return array Folders in the given folder. * * @since 1.7.0 */ protected function _folders( $path, $filter = '.', $recurse = false, $fullpath = false, $exclude = ['.svn', 'CVS', '.DS_Store', '__MACOSX'], $excludefilter = ['^\..*'] ) { $arr = []; // Check to make sure the path valid and clean $path = $this->_cleanPath($path); // Is the path a folder? if (!is_dir($path)) { Log::add(__METHOD__ . ' ' . Text::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER', __METHOD__, $path), Log::WARNING, 'jerror'); return false; } // Read the source directory if (!($handle = @opendir($path))) { return $arr; } if (\count($excludefilter)) { $excludefilter_string = '/(' . implode('|', $excludefilter) . ')/'; } else { $excludefilter_string = ''; } while (($file = readdir($handle)) !== false) { if ( ($file != '.') && ($file != '..') && (!\in_array($file, $exclude)) && (empty($excludefilter_string) || !preg_match($excludefilter_string, $file)) ) { $dir = $path . '/' . $file; $isDir = is_dir($dir); if ($isDir) { // Removes filtered directories if (preg_match("/$filter/", $file)) { if ($fullpath) { $arr[] = $dir; } else { $arr[] = $file; } } if ($recurse) { if (\is_int($recurse)) { $arr2 = $this->_folders($dir, $filter, $recurse - 1, $fullpath, $exclude, $excludefilter); } else { $arr2 = $this->_folders($dir, $filter, $recurse, $fullpath, $exclude, $excludefilter); } $arr = array_merge($arr, $arr2); } } } } closedir($handle); return $arr; } } Controller/CallbackController.php 0000644 00000014345 15172755227 0013164 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2007 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Cache\Controller; use Joomla\CMS\Cache\Cache; use Joomla\CMS\Cache\CacheController; use Joomla\CMS\Document\HtmlDocument; use Joomla\CMS\Factory; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Joomla! Cache callback type object * * @since 1.7.0 */ class CallbackController extends CacheController { /** * Executes a cacheable callback if not found in cache else returns cached output and result * * @param callable $callback Callback or string shorthand for a callback * @param array $args Callback arguments * @param mixed $id Cache ID * @param boolean $wrkarounds True to use workarounds * @param array $woptions Workaround options * * @return mixed Result of the callback * * @since 1.7.0 */ public function get($callback, $args = [], $id = false, $wrkarounds = false, $woptions = []) { if (!\is_array($args)) { $referenceArgs = !empty($args) ? [&$args] : []; } else { $referenceArgs = &$args; } // Just execute the callback if caching is disabled. if (empty($this->options['caching'])) { return \call_user_func_array($callback, $referenceArgs); } if (!$id) { // Generate an ID $id = $this->_makeId($callback, $args); } $data = $this->cache->get($id); $locktest = (object) ['locked' => null, 'locklooped' => null]; if ($data === false) { $locktest = $this->cache->lock($id); // If locklooped is true try to get the cached data again; it could exist now. if ($locktest->locked === true && $locktest->locklooped === true) { $data = $this->cache->get($id); } } if ($data !== false) { if ($locktest->locked === true) { $this->cache->unlock($id); } $data = unserialize(trim($data)); if ($wrkarounds) { echo Cache::getWorkarounds( $data['output'], ['mergehead' => $woptions['mergehead'] ?? 0] ); } else { echo $data['output']; } return $data['result']; } if ($locktest->locked === false && $locktest->locklooped === true) { // We can not store data because another process is in the middle of saving return \call_user_func_array($callback, $referenceArgs); } $coptions = ['modulemode' => 0]; if (isset($woptions['modulemode']) && $woptions['modulemode'] == 1) { /** @var HtmlDocument $document */ $document = Factory::getDocument(); if (method_exists($document, 'getHeadData')) { $coptions['headerbefore'] = $document->getHeadData(); // Reset document head before rendering module. Module will cache only assets added by itself. $document->resetHeadData(); $document->getWebAssetManager()->reset(); $coptions['modulemode'] = 1; } } $coptions['nopathway'] = $woptions['nopathway'] ?? 1; $coptions['nohead'] = $woptions['nohead'] ?? 1; $coptions['nomodules'] = $woptions['nomodules'] ?? 1; ob_start(); ob_implicit_flush(false); $result = \call_user_func_array($callback, $referenceArgs); $output = ob_get_clean(); $data = ['result' => $result]; if ($wrkarounds) { $data['output'] = Cache::setWorkarounds($output, $coptions); } else { $data['output'] = $output; } // Restore document head data and merge module head data. if ($coptions['modulemode'] == 1) { $moduleHeadData = $document->getHeadData(); $document->resetHeadData(); $document->mergeHeadData($coptions['headerbefore']); $document->mergeHeadData($moduleHeadData); } // Store the cache data $this->cache->store(serialize($data), $id); if ($locktest->locked === true) { $this->cache->unlock($id); } echo $output; return $result; } /** * Store data to cache 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 * @param boolean $wrkarounds True to use wrkarounds * * @return boolean True if cache stored * * @since 4.0.0 */ public function store($data, $id, $group = null, $wrkarounds = true) { $locktest = $this->cache->lock($id, $group); if ($locktest->locked === false && $locktest->locklooped === true) { // We can not store data because another process is in the middle of saving return false; } $result = $this->cache->store(serialize($data), $id, $group); if ($locktest->locked === true) { $this->cache->unlock($id, $group); } return $result; } /** * Generate a callback cache ID * * @param mixed $callback Callback to cache * @param array $args Arguments to the callback method to cache * * @return string MD5 Hash * * @since 1.7.0 */ protected function _makeId($callback, $args) { if (\is_array($callback) && \is_object($callback[0])) { $vars = get_object_vars($callback[0]); $vars[] = strtolower(\get_class($callback[0])); $callback[0] = $vars; } // A Closure can't be serialized, so to generate the ID we'll need to get its hash if ($callback instanceof \closure) { $hash = spl_object_hash($callback); return md5($hash . serialize([$args])); } return md5(serialize([$callback, $args])); } } Controller/OutputController.php 0000644 00000005230 15172755227 0012761 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2007 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Cache\Controller; use Joomla\CMS\Cache\CacheController; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Joomla Cache output type object * * @since 1.7.0 */ class OutputController extends CacheController { /** * Cache data ID * * @var string * @since 1.7.0 */ protected $_id; /** * Cache data group * * @var string * @since 1.7.0 */ protected $_group; /** * Get stored 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 no result, cached object otherwise * * @since 1.7.0 */ public function get($id, $group = null) { $data = $this->cache->get($id, $group); if ($data === false) { $locktest = $this->cache->lock($id, $group); // If locklooped is true try to get the cached data again; it could exist now. if ($locktest->locked === true && $locktest->locklooped === true) { $data = $this->cache->get($id, $group); } if ($locktest->locked === true) { $this->cache->unlock($id, $group); } } // Check again because we might get it from second attempt if ($data !== false) { // Trim to fix unserialize errors $data = unserialize(trim($data)); } return $data; } /** * Store data to cache 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 * @param boolean $wrkarounds True to use wrkarounds * * @return boolean True if cache stored * * @since 1.7.0 */ public function store($data, $id, $group = null, $wrkarounds = true) { $locktest = $this->cache->lock($id, $group); if ($locktest->locked === false && $locktest->locklooped === true) { // We can not store data because another process is in the middle of saving return false; } $result = $this->cache->store(serialize($data), $id, $group); if ($locktest->locked === true) { $this->cache->unlock($id, $group); } return $result; } } Controller/ViewController.php 0000644 00000011340 15172755227 0012372 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2010 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Cache\Controller; use Joomla\CMS\Cache\Cache; use Joomla\CMS\Cache\CacheController; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Joomla! Cache view type object * * @since 1.7.0 */ class ViewController extends CacheController { /** * Get the cached view data * * @param object $view The view object to cache output for * @param string $method The method name of the view method to cache output for * @param mixed $id The cache data ID * @param boolean $wrkarounds True to enable workarounds. * * @return boolean True if the cache is hit (false else) * * @since 1.7.0 */ public function get($view, $method = 'display', $id = false, $wrkarounds = true) { // If an id is not given generate it from the request if (!$id) { $id = $this->_makeId($view, $method); } $data = $this->cache->get($id); $locktest = (object) ['locked' => null, 'locklooped' => null]; if ($data === false) { $locktest = $this->cache->lock($id); /* * If the loop is completed and returned true it means the lock has been set. * If looped is true try to get the cached data again; it could exist now. */ if ($locktest->locked === true && $locktest->locklooped === true) { $data = $this->cache->get($id); } // False means that locking is either turned off or maxtime has been exceeded. Execute the view. } if ($data !== false) { if ($locktest->locked === true) { $this->cache->unlock($id); } $data = unserialize(trim($data)); if ($wrkarounds) { echo Cache::getWorkarounds($data); } else { // No workarounds, so all data is stored in one piece echo $data; } return true; } // No hit so we have to execute the view if (!method_exists($view, $method)) { return false; } if ($locktest->locked === false && $locktest->locklooped === true) { // We can not store data because another process is in the middle of saving $view->$method(); return false; } // Capture and echo output ob_start(); ob_implicit_flush(false); $view->$method(); $data = ob_get_clean(); echo $data; /* * For a view we have a special case. We need to cache not only the output from the view, but the state * of the document head after the view has been rendered. This will allow us to properly cache any attached * scripts or stylesheets or links or any other modifications that the view has made to the document object */ if ($wrkarounds) { $data = Cache::setWorkarounds($data); } // Store the cache data $this->cache->store(serialize($data), $id); if ($locktest->locked === true) { $this->cache->unlock($id); } return false; } /** * Store data to cache 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 * @param boolean $wrkarounds True to use wrkarounds * * @return boolean True if cache stored * * @since 4.0.0 */ public function store($data, $id, $group = null, $wrkarounds = true) { $locktest = $this->cache->lock($id, $group); if ($locktest->locked === false && $locktest->locklooped === true) { // We can not store data because another process is in the middle of saving return false; } $result = $this->cache->store(serialize($data), $id, $group); if ($locktest->locked === true) { $this->cache->unlock($id, $group); } return $result; } /** * Generate a view cache ID. * * @param object $view The view object to cache output for * @param string $method The method name to cache for the view object * * @return string MD5 Hash * * @since 1.7.0 */ protected function _makeId($view, $method) { return md5(serialize([Cache::makeId(), \get_class($view), $method])); } } Controller/PageController.php 0000644 00000012673 15172755227 0012346 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2007 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Cache\Controller; use Joomla\CMS\Cache\Cache; use Joomla\CMS\Cache\CacheController; use Joomla\CMS\Factory; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Joomla! Cache page type object * * @since 1.7.0 */ class PageController extends CacheController { /** * ID property for the cache page object. * * @var integer * @since 1.7.0 */ protected $_id; /** * Cache group * * @var string * @since 1.7.0 */ protected $_group; /** * Cache lock test * * @var \stdClass * @since 1.7.0 */ protected $_locktest = null; /** * Get the cached page data * * @param boolean $id The cache data ID * @param string $group The cache data group * * @return mixed Boolean false on no result, cached object otherwise * * @since 1.7.0 */ public function get($id = false, $group = 'page') { // If an id is not given, generate it from the request if (!$id) { $id = $this->_makeId(); } // If the etag matches the page id ... set a no change header and exit : utilize browser cache if (!headers_sent() && isset($_SERVER['HTTP_IF_NONE_MATCH'])) { $etag = stripslashes($_SERVER['HTTP_IF_NONE_MATCH']); if ($etag == $id) { $browserCache = $this->options['browsercache'] ?? false; if ($browserCache) { $this->_noChange(); } } } // We got a cache hit... set the etag header and echo the page data $data = $this->cache->get($id, $group); $this->_locktest = (object) ['locked' => null, 'locklooped' => null]; if ($data === false) { $this->_locktest = $this->cache->lock($id, $group); // If locklooped is true try to get the cached data again; it could exist now. if ($this->_locktest->locked === true && $this->_locktest->locklooped === true) { $data = $this->cache->get($id, $group); } } if ($data !== false) { if ($this->_locktest->locked === true) { $this->cache->unlock($id, $group); } $data = unserialize(trim($data)); $data = Cache::getWorkarounds($data); $this->_setEtag($id); return $data; } // Set ID and group placeholders $this->_id = $id; $this->_group = $group; return false; } /** * Stop the cache buffer and store the cached data * * @param mixed $data The data to store * @param string $id The cache data ID * @param string $group The cache data group * @param boolean $wrkarounds True to use workarounds * * @return boolean * * @since 1.7.0 */ public function store($data, $id, $group = null, $wrkarounds = true) { if ($this->_locktest->locked === false && $this->_locktest->locklooped === true) { // We can not store data because another process is in the middle of saving return false; } // Get page data from the application object if (!$data) { $data = Factory::getApplication()->getBody(); // Only attempt to store if page data exists. if (!$data) { return false; } } // Get id and group and reset the placeholders if (!$id) { $id = $this->_id; } if (!$group) { $group = $this->_group; } if ($wrkarounds) { $data = Cache::setWorkarounds( $data, [ 'nopathway' => 1, 'nohead' => 1, 'nomodules' => 1, 'headers' => true, ] ); } $result = $this->cache->store(serialize($data), $id, $group); if ($this->_locktest->locked === true) { $this->cache->unlock($id, $group); } return $result; } /** * Generate a page cache id * * @return string MD5 Hash * * @since 1.7.0 * @todo Discuss whether this should be coupled to a data hash or a request hash ... perhaps hashed with a serialized request */ protected function _makeId() { return Cache::makeId(); } /** * There is no change in page data so send an unmodified header and die gracefully * * @return void * * @since 1.7.0 */ protected function _noChange() { $app = Factory::getApplication(); // Send not modified header and exit gracefully $app->setHeader('Status', 304, true); $app->sendHeaders(); $app->close(); } /** * Set the ETag header in the response * * @param string $etag The entity tag (etag) to set * * @return void * * @since 1.7.0 */ protected function _setEtag($etag) { Factory::getApplication()->setHeader('ETag', '"' . $etag . '"', true); } } CacheControllerFactoryAwareInterface.php 0000644 00000001472 15172755227 0014476 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Cache; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Interface to be implemented by classes depending on a cache controller factory. * * @since 4.2.0 */ interface CacheControllerFactoryAwareInterface { /** * Set the cache controller factory to use. * * @param CacheControllerFactoryInterface $factory The cache controller factory to use. * * @return void * * @since 4.2.0 */ public function setCacheControllerFactory(CacheControllerFactoryInterface $factory): void; } Exception/CacheExceptionInterface.php 0000644 00000000733 15172755227 0013736 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Cache\Exception; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Exception interface defining a cache storage error * * @since 3.7.0 */ interface CacheExceptionInterface { } Exception/UnsupportedCacheException.php 0000644 00000001040 15172755227 0014356 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Cache\Exception; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Exception class defining an unsupported cache storage object * * @since 3.6.3 */ class UnsupportedCacheException extends \RuntimeException implements CacheExceptionInterface { } Exception/CacheConnectingException.php 0000644 00000001053 15172755227 0014121 0 ustar 00 <?php /** * Joomla! Content Management System * * @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Cache\Exception; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Exception class defining an error connecting to the cache storage engine * * @since 3.6.3 */ class CacheConnectingException extends \RuntimeException implements CacheExceptionInterface { }
| ver. 1.4 |
Github
|
.
| PHP 8.3.23 | Generation time: 0 |
proxy
|
phpinfo
|
Settings