Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
 * @internal
35
 */
36
class Cache
37
{
38
    const CACHE_VERSION = 1;
39
 
40
    /**
41
     * directory used for storing data
42
     *
43
     * @var string|false
44
     */
45
    public static $cacheDir = false;
46
 
47
    /**
48
     * prefix for the storing data
49
     *
50
     * @var string
51
     */
52
    public static $prefix = 'scssphp_';
53
 
54
    /**
55
     * force a refresh : 'once' for refreshing the first hit on a cache only, true to never use the cache in this hit
56
     *
57
     * @var bool|string
58
     */
59
    public static $forceRefresh = false;
60
 
61
    /**
62
     * specifies the number of seconds after which data cached will be seen as 'garbage' and potentially cleaned up
63
     *
64
     * @var int
65
     */
66
    public static $gcLifetime = 604800;
67
 
68
    /**
69
     * array of already refreshed cache if $forceRefresh==='once'
70
     *
71
     * @var array<string, bool>
72
     */
73
    protected static $refreshed = [];
74
 
75
    /**
76
     * Constructor
77
     *
78
     * @param array $options
79
     *
80
     * @phpstan-param array{cacheDir?: string, prefix?: string, forceRefresh?: string} $options
81
     */
82
    public function __construct($options)
83
    {
84
        // check $cacheDir
85
        if (isset($options['cacheDir'])) {
86
            self::$cacheDir = $options['cacheDir'];
87
        }
88
 
89
        if (empty(self::$cacheDir)) {
90
            throw new Exception('cacheDir not set');
91
        }
92
 
93
        if (isset($options['prefix'])) {
94
            self::$prefix = $options['prefix'];
95
        }
96
 
97
        if (empty(self::$prefix)) {
98
            throw new Exception('prefix not set');
99
        }
100
 
101
        if (isset($options['forceRefresh'])) {
102
            self::$forceRefresh = $options['forceRefresh'];
103
        }
104
 
105
        self::checkCacheDir();
106
    }
107
 
108
    /**
109
     * Get the cached result of $operation on $what,
110
     * which is known as dependant from the content of $options
111
     *
112
     * @param string   $operation    parse, compile...
113
     * @param mixed    $what         content key (e.g., filename to be treated)
114
     * @param array    $options      any option that affect the operation result on the content
115
     * @param int|null $lastModified last modified timestamp
116
     *
117
     * @return mixed
118
     *
119
     * @throws \Exception
120
     */
121
    public function getCache($operation, $what, $options = [], $lastModified = null)
122
    {
123
        $fileCache = self::$cacheDir . self::cacheName($operation, $what, $options);
124
 
125
        if (
126
            ((self::$forceRefresh === false) || (self::$forceRefresh === 'once' &&
127
            isset(self::$refreshed[$fileCache]))) && file_exists($fileCache)
128
        ) {
129
            $cacheTime = filemtime($fileCache);
130
 
131
            if (
132
                (\is_null($lastModified) || $cacheTime > $lastModified) &&
133
                $cacheTime + self::$gcLifetime > time()
134
            ) {
135
                $c = file_get_contents($fileCache);
136
                $c = unserialize($c);
137
 
138
                if (\is_array($c) && isset($c['value'])) {
139
                    return $c['value'];
140
                }
141
            }
142
        }
143
 
144
        return null;
145
    }
146
 
147
    /**
148
     * Put in cache the result of $operation on $what,
149
     * which is known as dependant from the content of $options
150
     *
151
     * @param string $operation
152
     * @param mixed  $what
153
     * @param mixed  $value
154
     * @param array  $options
155
     *
156
     * @return void
157
     */
158
    public function setCache($operation, $what, $value, $options = [])
159
    {
160
        $fileCache = self::$cacheDir . self::cacheName($operation, $what, $options);
161
 
162
        $c = ['value' => $value];
163
        $c = serialize($c);
164
 
165
        file_put_contents($fileCache, $c);
166
 
167
        if (self::$forceRefresh === 'once') {
168
            self::$refreshed[$fileCache] = true;
169
        }
170
    }
171
 
172
    /**
173
     * Get the cache name for the caching of $operation on $what,
174
     * which is known as dependant from the content of $options
175
     *
176
     * @param string $operation
177
     * @param mixed  $what
178
     * @param array  $options
179
     *
180
     * @return string
181
     */
182
    private static function cacheName($operation, $what, $options = [])
183
    {
184
        $t = [
185
          'version' => self::CACHE_VERSION,
186
          'scssphpVersion' => Version::VERSION,
187
          'operation' => $operation,
188
          'what' => $what,
189
          'options' => $options
190
        ];
191
 
192
        $t = self::$prefix
193
          . sha1(json_encode($t))
194
          . ".$operation"
195
          . ".scsscache";
196
 
197
        return $t;
198
    }
199
 
200
    /**
201
     * Check that the cache dir exists and is writeable
202
     *
203
     * @return void
204
     *
205
     * @throws \Exception
206
     */
207
    public static function checkCacheDir()
208
    {
209
        self::$cacheDir = str_replace('\\', '/', self::$cacheDir);
210
        self::$cacheDir = rtrim(self::$cacheDir, '/') . '/';
211
 
212
        if (! is_dir(self::$cacheDir)) {
213
            throw new Exception('Cache directory doesn\'t exist: ' . self::$cacheDir);
214
        }
215
 
216
        if (! is_writable(self::$cacheDir)) {
217
            throw new Exception('Cache directory isn\'t writable: ' . self::$cacheDir);
218
        }
219
    }
220
 
221
    /**
222
     * Delete unused cached files
223
     *
224
     * @return void
225
     */
226
    public static function cleanCache()
227
    {
228
        static $clean = false;
229
 
230
        if ($clean || empty(self::$cacheDir)) {
231
            return;
232
        }
233
 
234
        $clean = true;
235
 
236
        // only remove files with extensions created by SCSSPHP Cache
237
        // css files removed based on the list files
238
        $removeTypes = ['scsscache' => 1];
239
 
240
        $files = scandir(self::$cacheDir);
241
 
242
        if (! $files) {
243
            return;
244
        }
245
 
246
        $checkTime = time() - self::$gcLifetime;
247
 
248
        foreach ($files as $file) {
249
            // don't delete if the file wasn't created with SCSSPHP Cache
250
            if (strpos($file, self::$prefix) !== 0) {
251
                continue;
252
            }
253
 
254
            $parts = explode('.', $file);
255
            $type = array_pop($parts);
256
 
257
            if (! isset($removeTypes[$type])) {
258
                continue;
259
            }
260
 
261
            $fullPath = self::$cacheDir . $file;
262
            $mtime = filemtime($fullPath);
263
 
264
            // don't delete if it's a relatively new file
265
            if ($mtime > $checkTime) {
266
                continue;
267
            }
268
 
269
            unlink($fullPath);
270
        }
271
    }
272
}