Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

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

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