[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/libraries/src/Cache/Storage/ -> FileStorage.php (source)

   1  <?php
   2  /**
   3   * Joomla! Content Management System
   4   *
   5   * @copyright  (C) 2007 Open Source Matters, Inc. <https://www.joomla.org>
   6   * @license    GNU General Public License version 2 or later; see LICENSE.txt
   7   */
   8  
   9  namespace Joomla\CMS\Cache\Storage;
  10  
  11  \defined('JPATH_PLATFORM') or die;
  12  
  13  use Joomla\CMS\Cache\CacheStorage;
  14  use Joomla\CMS\Filesystem\File;
  15  use Joomla\CMS\Language\Text;
  16  use Joomla\CMS\Log\Log;
  17  
  18  /**
  19   * File cache storage handler
  20   *
  21   * @since  1.7.0
  22   * @note   For performance reasons this class does not use the Filesystem package's API
  23   */
  24  class FileStorage extends CacheStorage
  25  {
  26      /**
  27       * Root path
  28       *
  29       * @var    string
  30       * @since  1.7.0
  31       */
  32      protected $_root;
  33  
  34      /**
  35       * Locked resources
  36       *
  37       * @var    array
  38       * @since  3.7.0
  39       *
  40       */
  41      protected $_locked_files = array();
  42  
  43      /**
  44       * Constructor
  45       *
  46       * @param   array  $options  Optional parameters
  47       *
  48       * @since   1.7.0
  49       */
  50  	public function __construct($options = array())
  51      {
  52          parent::__construct($options);
  53          $this->_root = $options['cachebase'];
  54  
  55          // Workaround for php 5.3
  56          $locked_files = &$this->_locked_files;
  57  
  58          // Remove empty locked files at script shutdown.
  59          $clearAtShutdown = function () use (&$locked_files)
  60          {
  61              foreach ($locked_files as $path => $handle)
  62              {
  63                  if (\is_resource($handle))
  64                  {
  65                      @flock($handle, LOCK_UN);
  66                      @fclose($handle);
  67                  }
  68  
  69                  // Delete only the existing file if it is empty.
  70                  if (@filesize($path) === 0)
  71                  {
  72                      File::invalidateFileCache($path);
  73                      @unlink($path);
  74                  }
  75  
  76                  unset($locked_files[$path]);
  77              }
  78          };
  79  
  80          register_shutdown_function($clearAtShutdown);
  81      }
  82  
  83      /**
  84       * Check if the cache contains data stored by ID and group
  85       *
  86       * @param   string  $id     The cache data ID
  87       * @param   string  $group  The cache data group
  88       *
  89       * @return  boolean
  90       *
  91       * @since   3.7.0
  92       */
  93  	public function contains($id, $group)
  94      {
  95          return $this->_checkExpire($id, $group);
  96      }
  97  
  98      /**
  99       * Get cached data by ID and group
 100       *
 101       * @param   string   $id         The cache data ID
 102       * @param   string   $group      The cache data group
 103       * @param   boolean  $checkTime  True to verify cache time expiration threshold
 104       *
 105       * @return  mixed  Boolean false on failure or a cached data object
 106       *
 107       * @since   1.7.0
 108       */
 109  	public function get($id, $group, $checkTime = true)
 110      {
 111          $path  = $this->_getFilePath($id, $group);
 112          $close = false;
 113  
 114          if ($checkTime == false || ($checkTime == true && $this->_checkExpire($id, $group) === true))
 115          {
 116              if (file_exists($path))
 117              {
 118                  if (isset($this->_locked_files[$path]))
 119                  {
 120                      $_fileopen = $this->_locked_files[$path];
 121                  }
 122                  else
 123                  {
 124                      $_fileopen = @fopen($path, 'rb');
 125  
 126                      // There is no lock, we have to close file after store data
 127                      $close = true;
 128                  }
 129  
 130                  if ($_fileopen)
 131                  {
 132                      // On Windows system we can not use file_get_contents on the file locked by yourself
 133                      $data = stream_get_contents($_fileopen);
 134  
 135                      if ($close)
 136                      {
 137                          @fclose($_fileopen);
 138                      }
 139  
 140                      if ($data !== false)
 141                      {
 142                          // Remove the initial die() statement
 143                          return str_replace('<?php die("Access Denied"); ?>#x#', '', $data);
 144                      }
 145                  }
 146              }
 147          }
 148  
 149          return false;
 150      }
 151  
 152      /**
 153       * Get all cached data
 154       *
 155       * @return  mixed  Boolean false on failure or a cached data object
 156       *
 157       * @since   1.7.0
 158       */
 159  	public function getAll()
 160      {
 161          $path    = $this->_root;
 162          $folders = $this->_folders($path);
 163          $data    = array();
 164  
 165          foreach ($folders as $folder)
 166          {
 167              $files = $this->_filesInFolder($path . '/' . $folder);
 168              $item  = new CacheStorageHelper($folder);
 169  
 170              foreach ($files as $file)
 171              {
 172                  $item->updateSize(filesize($path . '/' . $folder . '/' . $file));
 173              }
 174  
 175              $data[$folder] = $item;
 176          }
 177  
 178          return $data;
 179      }
 180  
 181      /**
 182       * Store the data to cache by ID and group
 183       *
 184       * @param   string  $id     The cache data ID
 185       * @param   string  $group  The cache data group
 186       * @param   string  $data   The data to store in cache
 187       *
 188       * @return  boolean
 189       *
 190       * @since   1.7.0
 191       */
 192  	public function store($id, $group, $data)
 193      {
 194          $path  = $this->_getFilePath($id, $group);
 195          $close = false;
 196  
 197          // Prepend a die string
 198          $data = '<?php die("Access Denied"); ?>#x#' . $data;
 199  
 200          if (isset($this->_locked_files[$path]))
 201          {
 202              $_fileopen = $this->_locked_files[$path];
 203  
 204              // Because lock method uses flag c+b we have to truncate it manually
 205              @ftruncate($_fileopen, 0);
 206          }
 207          else
 208          {
 209              $_fileopen = @fopen($path, 'wb');
 210  
 211              // There is no lock, we have to close file after store data
 212              $close = true;
 213          }
 214  
 215          if ($_fileopen)
 216          {
 217              $length = \strlen($data);
 218              $result = @fwrite($_fileopen, $data, $length);
 219  
 220              if ($close)
 221              {
 222                  @fclose($_fileopen);
 223              }
 224  
 225              return $result === $length;
 226          }
 227  
 228          return false;
 229      }
 230  
 231      /**
 232       * Remove a cached data entry by ID and group
 233       *
 234       * @param   string  $id     The cache data ID
 235       * @param   string  $group  The cache data group
 236       *
 237       * @return  boolean
 238       *
 239       * @since   1.7.0
 240       */
 241  	public function remove($id, $group)
 242      {
 243          $path = $this->_getFilePath($id, $group);
 244  
 245          File::invalidateFileCache($path);
 246          if (!@unlink($path))
 247          {
 248              return false;
 249          }
 250  
 251          return true;
 252      }
 253  
 254      /**
 255       * Clean cache for a group given a mode.
 256       *
 257       * group mode    : cleans all cache in the group
 258       * notgroup mode : cleans all cache not in the group
 259       *
 260       * @param   string  $group  The cache data group
 261       * @param   string  $mode   The mode for cleaning cache [group|notgroup]
 262       *
 263       * @return  boolean
 264       *
 265       * @since   1.7.0
 266       */
 267  	public function clean($group, $mode = null)
 268      {
 269          $return = true;
 270          $folder = $group;
 271  
 272          if (trim($folder) == '')
 273          {
 274              $mode = 'notgroup';
 275          }
 276  
 277          switch ($mode)
 278          {
 279              case 'notgroup' :
 280                  $folders = $this->_folders($this->_root);
 281  
 282                  for ($i = 0, $n = \count($folders); $i < $n; $i++)
 283                  {
 284                      if ($folders[$i] != $folder)
 285                      {
 286                          $return |= $this->_deleteFolder($this->_root . '/' . $folders[$i]);
 287                      }
 288                  }
 289  
 290                  break;
 291  
 292              case 'group' :
 293              default :
 294                  if (is_dir($this->_root . '/' . $folder))
 295                  {
 296                      $return = $this->_deleteFolder($this->_root . '/' . $folder);
 297                  }
 298  
 299                  break;
 300          }
 301  
 302          return (bool) $return;
 303      }
 304  
 305      /**
 306       * Garbage collect expired cache data
 307       *
 308       * @return  boolean
 309       *
 310       * @since   1.7.0
 311       */
 312      public function gc()
 313      {
 314          $result = true;
 315  
 316          // Files older than lifeTime get deleted from cache
 317          $files = $this->_filesInFolder($this->_root, '', true, true, array('.svn', 'CVS', '.DS_Store', '__MACOSX', 'index.html'));
 318  
 319          foreach ($files as $file)
 320          {
 321              $time = @filemtime($file);
 322  
 323              if (($time + $this->_lifetime) < $this->_now || empty($time))
 324              {
 325                  File::invalidateFileCache($file);
 326                  $result |= @unlink($file);
 327              }
 328          }
 329  
 330          return (bool) $result;
 331      }
 332  
 333      /**
 334       * Lock cached item
 335       *
 336       * @param   string   $id        The cache data ID
 337       * @param   string   $group     The cache data group
 338       * @param   integer  $locktime  Cached item max lock time
 339       *
 340       * @return  mixed  Boolean false if locking failed or an object containing properties lock and locklooped
 341       *
 342       * @since   1.7.0
 343       */
 344  	public function lock($id, $group, $locktime)
 345      {
 346          $returning             = new \stdClass;
 347          $returning->locklooped = false;
 348  
 349          $looptime  = $locktime * 10;
 350          $path      = $this->_getFilePath($id, $group);
 351          $_fileopen = @fopen($path, 'c+b');
 352  
 353          if (!$_fileopen)
 354          {
 355              $returning->locked = false;
 356  
 357              return $returning;
 358          }
 359  
 360          $data_lock = (bool) @flock($_fileopen, LOCK_EX|LOCK_NB);
 361  
 362          if ($data_lock === false)
 363          {
 364              $lock_counter = 0;
 365  
 366              // Loop until you find that the lock has been released.
 367              // That implies that data get from other thread has finished
 368              while ($data_lock === false)
 369              {
 370                  if ($lock_counter > $looptime)
 371                  {
 372                      break;
 373                  }
 374  
 375                  usleep(100);
 376                  $data_lock = (bool) @flock($_fileopen, LOCK_EX|LOCK_NB);
 377                  $lock_counter++;
 378              }
 379  
 380              $returning->locklooped = true;
 381          }
 382  
 383          if ($data_lock === true)
 384          {
 385              // Remember resource, flock release lock if you unset/close resource
 386              $this->_locked_files[$path] = $_fileopen;
 387          }
 388  
 389          $returning->locked = $data_lock;
 390  
 391          return $returning;
 392      }
 393  
 394      /**
 395       * Unlock cached item
 396       *
 397       * @param   string  $id     The cache data ID
 398       * @param   string  $group  The cache data group
 399       *
 400       * @return  boolean
 401       *
 402       * @since   1.7.0
 403       */
 404  	public function unlock($id, $group = null)
 405      {
 406          $path = $this->_getFilePath($id, $group);
 407  
 408          if (isset($this->_locked_files[$path]))
 409          {
 410              $ret = (bool) @flock($this->_locked_files[$path], LOCK_UN);
 411              @fclose($this->_locked_files[$path]);
 412              unset($this->_locked_files[$path]);
 413  
 414              return $ret;
 415          }
 416  
 417          return true;
 418      }
 419  
 420      /**
 421       * Check if a cache object has expired
 422       *
 423       * Using @ error suppressor here because between if we did a file_exists() and then filemsize() there will
 424       * be a little time space when another process can delete the file and then you get PHP Warning
 425       *
 426       * @param   string  $id     Cache ID to check
 427       * @param   string  $group  The cache data group
 428       *
 429       * @return  boolean  True if the cache ID is valid
 430       *
 431       * @since   1.7.0
 432       */
 433  	protected function _checkExpire($id, $group)
 434      {
 435          $path = $this->_getFilePath($id, $group);
 436  
 437          // Check prune period
 438          if (file_exists($path))
 439          {
 440              $time = @filemtime($path);
 441  
 442              if (($time + $this->_lifetime) < $this->_now || empty($time))
 443              {
 444                  File::invalidateFileCache($path);
 445                  @unlink($path);
 446  
 447                  return false;
 448              }
 449  
 450              // If, right now, the file does not exist then return false
 451              if (@filesize($path) == 0)
 452              {
 453                  return false;
 454              }
 455  
 456              return true;
 457          }
 458  
 459          return false;
 460      }
 461  
 462      /**
 463       * Get a cache file path from an ID/group pair
 464       *
 465       * @param   string  $id     The cache data ID
 466       * @param   string  $group  The cache data group
 467       *
 468       * @return  boolean|string  The path to the data object or boolean false if the cache directory does not exist
 469       *
 470       * @since   1.7.0
 471       */
 472  	protected function _getFilePath($id, $group)
 473      {
 474          $name = $this->_getCacheId($id, $group);
 475          $dir  = $this->_root . '/' . $group;
 476  
 477          // If the folder doesn't exist try to create it
 478          if (!is_dir($dir))
 479          {
 480              // Make sure the index file is there
 481              $indexFile = $dir . '/index.html';
 482              @mkdir($dir) && file_put_contents($indexFile, '<!DOCTYPE html><title></title>');
 483          }
 484  
 485          // Make sure the folder exists
 486          if (!is_dir($dir))
 487          {
 488              return false;
 489          }
 490  
 491          return $dir . '/' . $name . '.php';
 492      }
 493  
 494      /**
 495       * Quickly delete a folder of files
 496       *
 497       * @param   string  $path  The path to the folder to delete.
 498       *
 499       * @return  boolean
 500       *
 501       * @since   1.7.0
 502       */
 503  	protected function _deleteFolder($path)
 504      {
 505          // Sanity check
 506          if (!$path || !is_dir($path) || empty($this->_root))
 507          {
 508              // Bad programmer! Bad, bad programmer!
 509              Log::add(__METHOD__ . ' ' . Text::_('JLIB_FILESYSTEM_ERROR_DELETE_BASE_DIRECTORY'), Log::WARNING, 'jerror');
 510  
 511              return false;
 512          }
 513  
 514          $path = $this->_cleanPath($path);
 515  
 516          // Check to make sure path is inside cache folder, we do not want to delete Joomla root!
 517          $pos = strpos($path, $this->_cleanPath($this->_root));
 518  
 519          if ($pos === false || $pos > 0)
 520          {
 521              Log::add(__METHOD__ . ' ' . Text::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER', __METHOD__, $path), Log::WARNING, 'jerror');
 522  
 523              return false;
 524          }
 525  
 526          // Remove all the files in folder if they exist; disable all filtering
 527          $files = $this->_filesInFolder($path, '.', false, true, array(), array());
 528  
 529          if (!empty($files) && !\is_array($files))
 530          {
 531              File::invalidateFileCache($files);
 532              if (@unlink($files) !== true)
 533              {
 534                  return false;
 535              }
 536          }
 537          elseif (!empty($files) && \is_array($files))
 538          {
 539              foreach ($files as $file)
 540              {
 541                  $file = $this->_cleanPath($file);
 542  
 543                  // 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
 544                  File::invalidateFileCache($file);
 545  
 546                  if (@unlink($file) !== true)
 547                  {
 548                      Log::add(__METHOD__ . ' ' . Text::sprintf('JLIB_FILESYSTEM_DELETE_FAILED', basename($file)), Log::WARNING, 'jerror');
 549  
 550                      return false;
 551                  }
 552              }
 553          }
 554  
 555          // Remove sub-folders of folder; disable all filtering
 556          $folders = $this->_folders($path, '.', false, true, array(), array());
 557  
 558          foreach ($folders as $folder)
 559          {
 560              if (is_link($folder))
 561              {
 562                  // Don't descend into linked directories, just delete the link.
 563                  if (@unlink($folder) !== true)
 564                  {
 565                      return false;
 566                  }
 567              }
 568              elseif ($this->_deleteFolder($folder) !== true)
 569              {
 570                  return false;
 571              }
 572          }
 573  
 574          // 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
 575          if (@rmdir($path))
 576          {
 577              return true;
 578          }
 579  
 580          Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_FOLDER_DELETE', $path), Log::WARNING, 'jerror');
 581  
 582          return false;
 583      }
 584  
 585      /**
 586       * Function to strip additional / or \ in a path name
 587       *
 588       * @param   string  $path  The path to clean
 589       * @param   string  $ds    Directory separator (optional)
 590       *
 591       * @return  string  The cleaned path
 592       *
 593       * @since   1.7.0
 594       */
 595  	protected function _cleanPath($path, $ds = DIRECTORY_SEPARATOR)
 596      {
 597          $path = trim($path);
 598  
 599          if (empty($path))
 600          {
 601              return $this->_root;
 602          }
 603  
 604          // Remove double slashes and backslashes and convert all slashes and backslashes to DIRECTORY_SEPARATOR
 605          $path = preg_replace('#[/\\\\]+#', $ds, $path);
 606  
 607          return $path;
 608      }
 609  
 610      /**
 611       * Utility function to quickly read the files in a folder.
 612       *
 613       * @param   string   $path           The path of the folder to read.
 614       * @param   string   $filter         A filter for file names.
 615       * @param   mixed    $recurse        True to recursively search into sub-folders, or an integer to specify the maximum depth.
 616       * @param   boolean  $fullpath       True to return the full path to the file.
 617       * @param   array    $exclude        Array with names of files which should not be shown in the result.
 618       * @param   array    $excludefilter  Array of folder names to exclude
 619       *
 620       * @return  array  Files in the given folder.
 621       *
 622       * @since   1.7.0
 623       */
 624  	protected function _filesInFolder($path, $filter = '.', $recurse = false, $fullpath = false,
 625          $exclude = array('.svn', 'CVS', '.DS_Store', '__MACOSX'), $excludefilter = array('^\..*', '.*~'))
 626      {
 627          $arr = array();
 628  
 629          // Check to make sure the path valid and clean
 630          $path = $this->_cleanPath($path);
 631  
 632          // Is the path a folder?
 633          if (!is_dir($path))
 634          {
 635              Log::add(__METHOD__ . ' ' . Text::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER', __METHOD__, $path), Log::WARNING, 'jerror');
 636  
 637              return false;
 638          }
 639  
 640          // Read the source directory.
 641          if (!($handle = @opendir($path)))
 642          {
 643              return $arr;
 644          }
 645  
 646          if (\count($excludefilter))
 647          {
 648              $excludefilter = '/(' . implode('|', $excludefilter) . ')/';
 649          }
 650          else
 651          {
 652              $excludefilter = '';
 653          }
 654  
 655          while (($file = readdir($handle)) !== false)
 656          {
 657              if (($file != '.') && ($file != '..') && (!\in_array($file, $exclude)) && (!$excludefilter || !preg_match($excludefilter, $file)))
 658              {
 659                  $dir   = $path . '/' . $file;
 660                  $isDir = is_dir($dir);
 661  
 662                  if ($isDir)
 663                  {
 664                      if ($recurse)
 665                      {
 666                          if (\is_int($recurse))
 667                          {
 668                              $arr2 = $this->_filesInFolder($dir, $filter, $recurse - 1, $fullpath);
 669                          }
 670                          else
 671                          {
 672                              $arr2 = $this->_filesInFolder($dir, $filter, $recurse, $fullpath);
 673                          }
 674  
 675                          $arr = array_merge($arr, $arr2);
 676                      }
 677                  }
 678                  else
 679                  {
 680                      if (preg_match("/$filter/", $file))
 681                      {
 682                          if ($fullpath)
 683                          {
 684                              $arr[] = $path . '/' . $file;
 685                          }
 686                          else
 687                          {
 688                              $arr[] = $file;
 689                          }
 690                      }
 691                  }
 692              }
 693          }
 694  
 695          closedir($handle);
 696  
 697          return $arr;
 698      }
 699  
 700      /**
 701       * Utility function to read the folders in a folder.
 702       *
 703       * @param   string   $path           The path of the folder to read.
 704       * @param   string   $filter         A filter for folder names.
 705       * @param   mixed    $recurse        True to recursively search into sub-folders, or an integer to specify the maximum depth.
 706       * @param   boolean  $fullpath       True to return the full path to the folders.
 707       * @param   array    $exclude        Array with names of folders which should not be shown in the result.
 708       * @param   array    $excludefilter  Array with regular expressions matching folders which should not be shown in the result.
 709       *
 710       * @return  array  Folders in the given folder.
 711       *
 712       * @since   1.7.0
 713       */
 714  	protected function _folders($path, $filter = '.', $recurse = false, $fullpath = false, $exclude = array('.svn', 'CVS', '.DS_Store', '__MACOSX'),
 715          $excludefilter = array('^\..*'))
 716      {
 717          $arr = array();
 718  
 719          // Check to make sure the path valid and clean
 720          $path = $this->_cleanPath($path);
 721  
 722          // Is the path a folder?
 723          if (!is_dir($path))
 724          {
 725              Log::add(__METHOD__ . ' ' . Text::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER', __METHOD__, $path), Log::WARNING, 'jerror');
 726  
 727              return false;
 728          }
 729  
 730          // Read the source directory
 731          if (!($handle = @opendir($path)))
 732          {
 733              return $arr;
 734          }
 735  
 736          if (\count($excludefilter))
 737          {
 738              $excludefilter_string = '/(' . implode('|', $excludefilter) . ')/';
 739          }
 740          else
 741          {
 742              $excludefilter_string = '';
 743          }
 744  
 745          while (($file = readdir($handle)) !== false)
 746          {
 747              if (($file != '.') && ($file != '..')
 748                  && (!\in_array($file, $exclude))
 749                  && (empty($excludefilter_string) || !preg_match($excludefilter_string, $file)))
 750              {
 751                  $dir   = $path . '/' . $file;
 752                  $isDir = is_dir($dir);
 753  
 754                  if ($isDir)
 755                  {
 756                      // Removes filtered directories
 757                      if (preg_match("/$filter/", $file))
 758                      {
 759                          if ($fullpath)
 760                          {
 761                              $arr[] = $dir;
 762                          }
 763                          else
 764                          {
 765                              $arr[] = $file;
 766                          }
 767                      }
 768  
 769                      if ($recurse)
 770                      {
 771                          if (\is_int($recurse))
 772                          {
 773                              $arr2 = $this->_folders($dir, $filter, $recurse - 1, $fullpath, $exclude, $excludefilter);
 774                          }
 775                          else
 776                          {
 777                              $arr2 = $this->_folders($dir, $filter, $recurse, $fullpath, $exclude, $excludefilter);
 778                          }
 779  
 780                          $arr = array_merge($arr, $arr2);
 781                      }
 782                  }
 783              }
 784          }
 785  
 786          closedir($handle);
 787  
 788          return $arr;
 789      }
 790  }


Generated: Wed Sep 7 05:41:13 2022 Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer