File Manager

Current Directory: /home/astoriaah/www/administrator/components/com_sh404sef/classes
Viewing File: /home/astoriaah/www/administrator/components/com_sh404sef/classes/filecache.php
<?php /** * sh404SEF - SEO extension for Joomla! * * @author Yannick Gaultier * @copyright (c) Yannick Gaultier - Weeblr llc - 2022 * @package sh404SEF * @license http://www.gnu.org/copyleft/gpl.html GNU/GPL * @version 4.24.1.4308 * @date 2022-05-31 */ // Security check to ensure this file is being included by a parent file. if (!defined('_JEXEC')) die('Direct Access to this location is not allowed.'); // load a few constants require_once JPATH_ROOT . '/administrator/components/com_sh404sef/defines.php'; //define('SH_SHOW_CACHE_STATS', 0); /** * URL caching * * @author shumisha * */ class Sh404sefClassFilecache extends Sh404sefClassBasecache { protected $_newUrlsCache = array(); protected $_newUrlsCacheCount = 0; protected $_cacheFilename = ''; protected $_cacheFilefullpath = ''; protected $_lockFilefullpath = ''; protected $_cacheFileLocked = false; protected $_urlCacheRam = 0; protected $_lockTtl = 35; // usual exec time is 30sec protected $_fileHeader = '<?php // shCache : URL cache file for sh404SEF // version %s if (!defined(\'_JEXEC\')) die(\'Direct Access to this location is not allowed.\'); $shURLCacheCreationDate = %s; '; public function __construct( $config) { parent::__construct( $config); // init cache filename, must be unique per live site, in case of multisite $liveSite = str_replace( 'administrator/', '', JURI::base()); $this->_cacheFilename = 'shCacheContent.' . md5( $liveSite); $this->_cacheFilefullpath = sh404SEF_FRONT_ABS_PATH . 'cache' . '/' . $this->_cacheFilename . '.php'; $this->_lockFilefullpath = sh404SEF_FRONT_ABS_PATH . 'cache' . '/' . $this->_cacheFilename . '.shlock'; // every now and then we clear the cache to let it refill with more recent data $this->_itsTimeToCheckTTL = mt_rand(1, SH404SEF_URL_CACHE_WRITES_TO_CHECK_TTL) == 1; // register method that will store urls created during // current page request into cache file if (!empty($this->_config->shUseURLCache)) { register_shutdown_function( array( $this, 'writeURLCacheToDisk')); } } public function getSefUrlFromCache( $nonSefUrl, & $sefUrl) { if (!$this->_config->shUseURLCache) { $sefUrl = null; $this->_urlCacheMisses += 1; return sh404SEF_URLTYPE_NONE; } // make sure cache is loaded, in case this method is called first $this->_loadURLCache(); // no urls in cache, no need to go further if (empty($this->_urlCacheCount) && empty($this->_newUrlsCacheCount)) { $sefUrl = null; $this->_urlCacheMisses += 1; return sh404SEF_URLTYPE_NONE; } $key = md5( $nonSefUrl); if(!empty( $this->_urlCache[$key])) { $tmp = explode('#', $this->_urlCache[$key]); // cache format : non-sef#sef#type $sefUrl = $tmp[1]; ShlSystem_Log::debug( 'sh404sef', 'Retrieved SEF from disk cache : '.$sefUrl.' => '.html_entity_decode( $tmp[0], ENT_QUOTES, 'UTF-8').'('.$tmp[2].')'); $this->_urlCacheHits += 1; return $tmp[2]; } if(!empty( $this->_newUrlsCache[$key])) { $tmp = explode('#', $this->_newUrlsCache[$key]); // cache format : non-sef#sef#type $sefUrl = $tmp[1]; ShlSystem_Log::debug( 'sh404sef', 'Retrieved SEF from disk cache : '.$sefUrl.' => '.html_entity_decode( $tmp[0], ENT_QUOTES, 'UTF-8').'('.$tmp[2].')'); $this->_urlCacheHits += 1; return $tmp[2]; } $this->_urlCacheMisses += 1; $this->_urlCacheMissesList[] = $nonSefUrl; return sh404SEF_URLTYPE_NONE; } // fetch an URL from cache, return null if not found public function getNonSefUrlFromCache( $sefUrl, & $nonSefUrl) { if (!$this->_config->shUseURLCache) { $nonSefUrl = null; return sh404SEF_URLTYPE_NONE; } // make sure cache is loaded, in case this method is called first $this->_loadURLCache(); // no urls in cache, no need to go further if (empty($this->_urlCacheCount) && empty($this->_newUrlsCacheCount)) { $nonSefUrl = null; return sh404SEF_URLTYPE_NONE; } // search for requested sef url in disk stored cache foreach( $this->_urlCache as $key => $value) { if (strpos( $value, $sefUrl) !== false) { $tmp = explode('#', $value); // cache format : non-sef#sef#type $nonSef = html_entity_decode( $tmp[0], ENT_QUOTES, 'UTF-8'); if ($sefUrl == $tmp[1]) { $nonSefUrl = $nonSef; ShlSystem_Log::debug( 'sh404sef', 'Retrieved Non SEF from disk cache : '.$nonSefUrl.' => '.$tmp[1].'('.$tmp[2].')'); return $tmp[2]; } } } // search also in urls waiting to be stored to disk foreach( $this->_newUrlsCache as $key => $value) { if (strpos( $value, $sefUrl) !== false) { $tmp = explode('#', $value); // cache format : non-sef#sef#type $nonSef = html_entity_decode( $tmp[0], ENT_QUOTES, 'UTF-8'); if ($sefUrl == $tmp[1]) { $nonSefUrl = $nonSef; ShlSystem_Log::debug( 'sh404sef', 'Retrieved Non SEF from mem cache : '.$nonSefUrl.' => '.$tmp[1].'('.$tmp[2].')'); return $tmp[2]; } } } return sh404SEF_URLTYPE_NONE; } public function addSefUrlToCache( $nonSefUrl, $sefUrl, $UrlType) { if (!$this->_config->shUseURLCache) { return null; } // make sure cache is loaded, in case this method is called first $this->_loadURLCache(); // insertion key $key = md5( $nonSefUrl); // already there? if(!empty($this->_urlCache[$key]) || !empty( $this->_newUrlsCache[$key])) { return true; } if (($this->_urlCacheCount + $this->_newUrlsCacheCount) >= $this->_config->shMaxURLInCache) { return null; // v 1.2.4.c added total cache size control } // Filter out non sef url which include &mosmsg, as I don't want to have a cache entry for every single msg // that can be thrown at me, including every 404 error if (strpos(strtolower($nonSefUrl), '&mosmsg')) { return null; } // new cache format : non-sef#sef#type $this->_newUrlsCache[md5($nonSefUrl)] = htmlentities( $nonSefUrl, ENT_QUOTES, 'UTF-8').'#'.$sefUrl.'#'.$UrlType; ShlSystem_Log::debug( 'sh404sef', 'Adding to URL cache : '.$sefUrl.' <= '.$nonSefUrl); $this->_newUrlsCacheCount++; return true; } public function removeUrlFromCache( $nonSefUrlList) { if (!$this->_config->shUseURLCache || empty($nonSefUrlList)) { return null; } // make sure cache is loaded, in case this method is called first $this->_loadURLCache(); $foundInDiskCache = false; $foundInMemCache = false; foreach ($nonSefUrlList as $nonSefURL) { if (!empty($this->_newUrlsCache)) { foreach ($this->_newUrlsCache as $key => $cacheItem) { // look up in memory cache $tmp = explode('#', $cacheItem); $cacheNonSef = html_entity_decode( $tmp[0], ENT_QUOTES, 'UTF-8'); if ($cacheNonSef == $nonSefURL) { unset($this->_newUrlsCache[$key]); $this->_newUrlsCacheCount--; $foundInMemCache = true; } } } // TODO: rewrite using md5 key if (!empty($this->_urlCache)) { foreach ($this->_urlCache as $key => $cacheItem) { // look up disk cache $tmp = explode('#', $cacheItem); $cacheNonSef = html_entity_decode( $tmp[0], ENT_QUOTES, 'UTF-8'); if ($cacheNonSef == $nonSefURL) { unset($this->_urlCache[$key]); $this->_urlCacheCount--; $foundInDiskCache = true; } } } } if ($foundInMemCache) { $this->_newUrlsCache = array_values($this->_newUrlsCache); // simply reindex mem cache return; } if ($foundInDiskCache) { // we need to remove these url from the disk cache file // to make it simpler, I simply rewrite the complete file $this->_newUrlsCache = (empty($this->_newUrlsCache) ? array_values($this->_urlCache) :array_merge($this->_urlCache, $this->_newUrlsCache)); $this->_urlCache = array(); // don't need disk cache anymore, as all URL are in mem cache // so we remove both on disk cache and in memory copy of on disk cache $this->purge(); // no need to write new URL list in disk file, as this will be done automatically at shutdown } } public function writeURLCacheToDisk() { // If we acquired lock at page load, we can write if (count($this->_newUrlsCache) && $this->_cacheFileLocked) { $cache = ''; $now = time(); if (!file_exists($this->_cacheFilefullpath)) { $cache = sprintf( $this->_fileHeader, $this->_config->version, $now); $this->_urlCacheCreationDate = $now; } else { // check cache TTL if (empty($this->_urlCacheCreationDate)){ // file exists, but creation date is missing : we are upgrading from a previous version $status = stat($this->_cacheFilefullpath); // lets's read from file status : use last change date as creation date if (!empty($status)) { $this->_urlCacheCreationDate = $status[9]; } } if (SH404SEF_URL_CACHE_TTL && $this->_itsTimeToCheckTTL) { // probability = 1/SH404SEF_WRITES_TO_CLEAN_LOGS if (!empty($this->_urlCacheCreationDate)){ // if we have a valid creation date, check TTL if (($now-$this->_urlCacheCreationDate) > SH404SEF_URL_CACHE_TTL*3600) { // cache must be cleared $this->_urlCache = array(); unlink($this->_cacheFilefullpath); $this->_urlCacheCreationDate = $now; $cache = sprintf( $this->_fileHeader, $this->_config->version, $now); } } } } $count = count( $this->_urlCache); $cache .= $this->_varExport( $this->_newUrlsCache, $count); // only need to write memory cache, ie: those URL added since last read of cache from disk $cache .= "\n".'$shURLCacheCreationDate='.$now.';'; $cacheFile = fopen( $this->_cacheFilefullpath,'ab'); if ($cacheFile) { fwrite( $cacheFile, $cache); fclose( $cacheFile); } } // in any case, release lock to file $this->_releaseLock(); } public function purge( $allCaches = true, $keepLocks = true) { jimport( 'joomla.filesystem.file'); jimport( 'joomla.filesystem.folder'); if(!JFolder::exists(sh404SEF_FRONT_ABS_PATH . 'cache')) { return; } if($allCaches) { // delete all sh404SEF cache files in the dir // so as to clear also potential https, or alternate domains caches jimport( 'joomla.filesystem.folder'); $files = JFolder::files( sh404SEF_FRONT_ABS_PATH . 'cache', 'shCacheContent\.', $recurse = false, $fullpath = true); if(!empty( $files)) { foreach( $files as $file) { if($keepLocks && strpos($file, '.shlock') !== false) { continue; } if (JFile::exists( $file)) { JFile::delete( $file); } } } } else { // only delete cache for current live site url if (JFile::exists( $this->_cacheFilefullpath)) { JFile::delete( $this->_cacheFilefullpath); } if (JFile::exists( $this->_lockFilefullpath)) { JFile::delete( $this->_lockFilefullpath); } } } public function getCacheStats() { // collect basic data $out = parent::getCacheStats(); // add urls waiting to be written $out .= 'In memory, waiting to be written : '. $this->_newUrlsCacheCount . '<br />'; // add ram usage of cached urls read from disk file // and stored in ram $out .= 'Ram used : '. $this->_urlCacheRam . '<br />'; return $out; } // load cached URL from disk into an array in memory protected function _loadURLCache() { static $shDiskCacheLoaded = false; if (!$shDiskCacheLoaded) { ShlSystem_Log::debug( 'sh404sef', 'Cache not loaded - trying to load '.$this->_cacheFilefullpath); if (file_exists( $this->_cacheFilefullpath)) { $startMem = function_exists('memory_get_usage')? memory_get_usage():'unavailable'; ShlSystem_Log::debug( 'sh404sef', 'Including cache file (mem = '.$startMem.')'); $this->_urlCache = array(); // we try lock the cache file until the end of the request // so as to avoid other concurrent requests writing to it // while we have some pending data $this->_acquireLock(); // read cache file content include($this->_cacheFilefullpath); $this->_urlCacheCreationDate = $shURLCacheCreationDate; $endMem = function_exists('memory_get_usage')? memory_get_usage():'unavailable'; $this->_urlCacheRam = $startMem == 'unavailable' ? $startMem: $endMem - $startMem; $shDiskCacheLoaded = !empty($this->_urlCache); $this->_urlCacheCount = !empty($this->_urlCache) ? count($this->_urlCache) : 0; ShlSystem_Log::debug( 'sh404sef', 'Cache file included : '.($startMem == 'unavailable' ? $startMem: $endMem-$startMem).' bytes used, '.$this->_urlCacheCount.' URLs'); } else { // cache file not there, create it $now = time(); $cache = sprintf( $this->_fileHeader, $this->_config->version, $now); // lock cache file before using it if ( $this->_acquireLock()) { $cacheFile = fopen( $this->_cacheFilefullpath,'ab'); if ($cacheFile) { fwrite( $cacheFile, $cache); fclose( $cacheFile); } } $this->_urlCache = array(); $this->_urlCacheCount = 0; $shDiskCacheLoaded = true; // we don't want to try again if it failed first time $this->_urlCacheCreationDate = $now; ShlSystem_Log::debug( 'sh404sef', 'Cache file does not exists'); } } } protected function _acquireLock() { $attempts = 0; $tryAgain = false; $now = time(); do { // directly attempts to create the lock. If it fails, then element is already locked $handle = @fopen( $this->_lockFilefullpath, 'x'); if ($handle) { // we could create file for writing, element is not locked. // store time, close and return $written = fwrite( $handle, $now); $closed = fclose($handle); // return true if we could write the time stamp $this->_cacheFileLocked = !empty($written) && $closed; // we don't try again, even if we failed writing -> something is not working anyway } else { // we could not create lock file, element is already locked // by another process. Check for timeout // read lock file, and check TTL if ($this->_itsTimeToCheckTTL) { $lockTime = JFile::exists($this->_lockFilefullpath) ? file_get_contents( $this->_lockFilefullpath) : 0; $lockTime = (int) trim( $lockTime); if (($now - $lockTime) > $this->_lockTtl) { // existing lock has timed out, we can release it $this->_cacheFileLocked = $this->_releaseLock( true); // we have released the lock file, we can try again grabbing it // as we test also for _cacheFileLocked, this will happen only if the release was successful $tryAgain = true; } else { $tryAgain = false; } } } } while (!$this->_cacheFileLocked && $tryAgain); return $this->_cacheFileLocked; } protected function _releaseLock( $forced = false) { if ($this->_cacheFileLocked || $forced) { jimport( 'joomla.filesystem.file'); if (JFile::exists( $this->_lockFilefullpath)) { $this->_cacheFileLocked = !JFile::delete( $this->_lockFilefullpath); } } return $this->_cacheFileLocked; } protected function _varExport( $cache, $start) { // export content of array $cache, inserting a numeric key starting at $start $size = count( $cache); if (empty($size)) { return ''; } $ret = ''; foreach($cache as $key => $value) { // use for instead of foreach to reduce memory usage $tmp = explode('#', $value); // cache format : non-sef#sef#type $nonSef = html_entity_decode( $tmp[0], ENT_QUOTES, 'UTF-8'); $ret .= "\n".'$this->_urlCache[\''.md5($nonSef).'\']=\''.$value.'\';'; } return $ret; } }