Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]

   1  <?php
   2  
   3  /**
   4   * SCSSPHP
   5   *
   6   * @copyright 2012-2020 Leaf Corcoran
   7   *
   8   * @license http://opensource.org/licenses/MIT MIT
   9   *
  10   * @link http://scssphp.github.io/scssphp
  11   */
  12  
  13  namespace ScssPhp\ScssPhp;
  14  
  15  use Exception;
  16  use ScssPhp\ScssPhp\Version;
  17  
  18  /**
  19   * The scss cache manager.
  20   *
  21   * In short:
  22   *
  23   * allow to put in cache/get from cache a generic result from a known operation on a generic dataset,
  24   * taking in account options that affects the result
  25   *
  26   * The cache manager is agnostic about data format and only the operation is expected to be described by string
  27   */
  28  
  29  /**
  30   * SCSS cache
  31   *
  32   * @author Cedric Morin <cedric@yterium.com>
  33   */
  34  class Cache
  35  {
  36      const CACHE_VERSION = 1;
  37  
  38      // directory used for storing data
  39      public static $cacheDir = false;
  40  
  41      // prefix for the storing data
  42      public static $prefix = 'scssphp_';
  43  
  44      // force a refresh : 'once' for refreshing the first hit on a cache only, true to never use the cache in this hit
  45      public static $forceRefresh = false;
  46  
  47      // specifies the number of seconds after which data cached will be seen as 'garbage' and potentially cleaned up
  48      public static $gcLifetime = 604800;
  49  
  50      // array of already refreshed cache if $forceRefresh==='once'
  51      protected static $refreshed = [];
  52  
  53      /**
  54       * Constructor
  55       *
  56       * @param array $options
  57       */
  58      public function __construct($options)
  59      {
  60          // check $cacheDir
  61          if (isset($options['cacheDir'])) {
  62              self::$cacheDir = $options['cacheDir'];
  63          }
  64  
  65          if (empty(self::$cacheDir)) {
  66              throw new Exception('cacheDir not set');
  67          }
  68  
  69          if (isset($options['prefix'])) {
  70              self::$prefix = $options['prefix'];
  71          }
  72  
  73          if (empty(self::$prefix)) {
  74              throw new Exception('prefix not set');
  75          }
  76  
  77          if (isset($options['forceRefresh'])) {
  78              self::$forceRefresh = $options['forceRefresh'];
  79          }
  80  
  81          self::checkCacheDir();
  82      }
  83  
  84      /**
  85       * Get the cached result of $operation on $what,
  86       * which is known as dependant from the content of $options
  87       *
  88       * @param string  $operation    parse, compile...
  89       * @param mixed   $what         content key (e.g., filename to be treated)
  90       * @param array   $options      any option that affect the operation result on the content
  91       * @param integer $lastModified last modified timestamp
  92       *
  93       * @return mixed
  94       *
  95       * @throws \Exception
  96       */
  97      public function getCache($operation, $what, $options = [], $lastModified = null)
  98      {
  99          $fileCache = self::$cacheDir . self::cacheName($operation, $what, $options);
 100  
 101          if (
 102              ((self::$forceRefresh === false) || (self::$forceRefresh === 'once' &&
 103              isset(self::$refreshed[$fileCache]))) && file_exists($fileCache)
 104          ) {
 105              $cacheTime = filemtime($fileCache);
 106  
 107              if (
 108                  (\is_null($lastModified) || $cacheTime > $lastModified) &&
 109                  $cacheTime + self::$gcLifetime > time()
 110              ) {
 111                  $c = file_get_contents($fileCache);
 112                  $c = unserialize($c);
 113  
 114                  if (\is_array($c) && isset($c['value'])) {
 115                      return $c['value'];
 116                  }
 117              }
 118          }
 119  
 120          return null;
 121      }
 122  
 123      /**
 124       * Put in cache the result of $operation on $what,
 125       * which is known as dependant from the content of $options
 126       *
 127       * @param string $operation
 128       * @param mixed  $what
 129       * @param mixed  $value
 130       * @param array  $options
 131       */
 132      public function setCache($operation, $what, $value, $options = [])
 133      {
 134          $fileCache = self::$cacheDir . self::cacheName($operation, $what, $options);
 135  
 136          $c = ['value' => $value];
 137          $c = serialize($c);
 138  
 139          file_put_contents($fileCache, $c);
 140  
 141          if (self::$forceRefresh === 'once') {
 142              self::$refreshed[$fileCache] = true;
 143          }
 144      }
 145  
 146      /**
 147       * Get the cache name for the caching of $operation on $what,
 148       * which is known as dependant from the content of $options
 149       *
 150       * @param string $operation
 151       * @param mixed  $what
 152       * @param array  $options
 153       *
 154       * @return string
 155       */
 156      private static function cacheName($operation, $what, $options = [])
 157      {
 158          $t = [
 159            'version' => self::CACHE_VERSION,
 160            'scssphpVersion' => Version::VERSION,
 161            'operation' => $operation,
 162            'what' => $what,
 163            'options' => $options
 164          ];
 165  
 166          $t = self::$prefix
 167            . sha1(json_encode($t))
 168            . ".$operation"
 169            . ".scsscache";
 170  
 171          return $t;
 172      }
 173  
 174      /**
 175       * Check that the cache dir exists and is writeable
 176       *
 177       * @throws \Exception
 178       */
 179      public static function checkCacheDir()
 180      {
 181          self::$cacheDir = str_replace('\\', '/', self::$cacheDir);
 182          self::$cacheDir = rtrim(self::$cacheDir, '/') . '/';
 183  
 184          if (! is_dir(self::$cacheDir)) {
 185              throw new Exception('Cache directory doesn\'t exist: ' . self::$cacheDir);
 186          }
 187  
 188          if (! is_writable(self::$cacheDir)) {
 189              throw new Exception('Cache directory isn\'t writable: ' . self::$cacheDir);
 190          }
 191      }
 192  
 193      /**
 194       * Delete unused cached files
 195       */
 196      public static function cleanCache()
 197      {
 198          static $clean = false;
 199  
 200          if ($clean || empty(self::$cacheDir)) {
 201              return;
 202          }
 203  
 204          $clean = true;
 205  
 206          // only remove files with extensions created by SCSSPHP Cache
 207          // css files removed based on the list files
 208          $removeTypes = ['scsscache' => 1];
 209  
 210          $files = scandir(self::$cacheDir);
 211  
 212          if (! $files) {
 213              return;
 214          }
 215  
 216          $checkTime = time() - self::$gcLifetime;
 217  
 218          foreach ($files as $file) {
 219              // don't delete if the file wasn't created with SCSSPHP Cache
 220              if (strpos($file, self::$prefix) !== 0) {
 221                  continue;
 222              }
 223  
 224              $parts = explode('.', $file);
 225              $type = array_pop($parts);
 226  
 227              if (! isset($removeTypes[$type])) {
 228                  continue;
 229              }
 230  
 231              $fullPath = self::$cacheDir . $file;
 232              $mtime = filemtime($fullPath);
 233  
 234              // don't delete if it's a relatively new file
 235              if ($mtime > $checkTime) {
 236                  continue;
 237              }
 238  
 239              unlink($fullPath);
 240          }
 241      }
 242  }