Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
73.61% |
53 / 72 |
|
28.57% |
2 / 7 |
CRAP | |
0.00% |
0 / 1 |
Less_Cache | |
73.61% |
53 / 72 |
|
28.57% |
2 / 7 |
82.21 | |
0.00% |
0 / 1 |
Get | |
75.56% |
34 / 45 |
|
0.00% |
0 / 1 |
22.73 | |||
Regen | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
Cache | |
80.00% |
8 / 10 |
|
0.00% |
0 / 1 |
3.07 | |||
OutputFile | |
40.00% |
2 / 5 |
|
0.00% |
0 / 1 |
4.94 | |||
CompiledName | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
SetCacheDir | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
CheckCacheDir | n/a |
0 / 0 |
n/a |
0 / 0 |
5 | |||||
CleanCache | n/a |
0 / 0 |
n/a |
0 / 0 |
10 | |||||
ListFiles | |
60.00% |
3 / 5 |
|
0.00% |
0 / 1 |
2.26 |
1 | <?php |
2 | |
3 | /** |
4 | * Utility for handling the generation and caching of css files |
5 | */ |
6 | class Less_Cache { |
7 | |
8 | /** @var string|false Directory less.php can use for storing data */ |
9 | public static $cache_dir = false; |
10 | |
11 | /** @var string Prefix for the storing data */ |
12 | public static $prefix = 'lessphp_'; |
13 | |
14 | /** @var string Prefix for the storing vars */ |
15 | public static $prefix_vars = 'lessphpvars_'; |
16 | |
17 | /** |
18 | * @var int Specifies the number of seconds after which data created by less.php will be seen |
19 | * as 'garbage' and potentially cleaned up |
20 | */ |
21 | public static $gc_lifetime = 604800; |
22 | |
23 | /** @var bool */ |
24 | private static $gc_done = false; |
25 | |
26 | /** |
27 | * Save and reuse the results of compiled less files. |
28 | * The first call to Get() will generate css and save it. |
29 | * Subsequent calls to Get() with the same arguments will return the same css filename |
30 | * |
31 | * @param array $less_files Array of .less files to compile |
32 | * @param array $parser_options Array of compiler options |
33 | * @param array $modify_vars Array of variables |
34 | * @return string|false Name of the css file |
35 | */ |
36 | public static function Get( $less_files, $parser_options = [], $modify_vars = [] ) { |
37 | // check $cache_dir |
38 | if ( isset( $parser_options['cache_dir'] ) ) { |
39 | self::$cache_dir = $parser_options['cache_dir']; |
40 | } |
41 | |
42 | if ( empty( self::$cache_dir ) ) { |
43 | throw new Exception( 'cache_dir not set' ); |
44 | } |
45 | |
46 | if ( isset( $parser_options['prefix'] ) ) { |
47 | self::$prefix = $parser_options['prefix']; |
48 | } |
49 | |
50 | if ( empty( self::$prefix ) ) { |
51 | throw new Exception( 'prefix not set' ); |
52 | } |
53 | |
54 | if ( isset( $parser_options['prefix_vars'] ) ) { |
55 | self::$prefix_vars = $parser_options['prefix_vars']; |
56 | } |
57 | |
58 | if ( empty( self::$prefix_vars ) ) { |
59 | throw new Exception( 'prefix_vars not set' ); |
60 | } |
61 | |
62 | self::$cache_dir = self::CheckCacheDir(); |
63 | $less_files = (array)$less_files; |
64 | |
65 | // create a file for variables |
66 | if ( !empty( $modify_vars ) ) { |
67 | $lessvars = Less_Parser::serializeVars( $modify_vars ); |
68 | $vars_file = self::$cache_dir . self::$prefix_vars . sha1( $lessvars ) . '.less'; |
69 | |
70 | if ( !file_exists( $vars_file ) ) { |
71 | file_put_contents( $vars_file, $lessvars ); |
72 | } |
73 | |
74 | $less_files += [ $vars_file => '/' ]; |
75 | } |
76 | |
77 | // generate name for compiled css file |
78 | $hash = md5( json_encode( $less_files ) ); |
79 | $list_file = self::$cache_dir . self::$prefix . $hash . '.list'; |
80 | |
81 | // check cached content |
82 | if ( !isset( $parser_options['use_cache'] ) || $parser_options['use_cache'] === true ) { |
83 | if ( file_exists( $list_file ) ) { |
84 | |
85 | self::ListFiles( $list_file, $list, $cached_name ); |
86 | $compiled_name = self::CompiledName( $list, $hash ); |
87 | |
88 | // if $cached_name is the same as the $compiled name, don't regenerate |
89 | if ( !$cached_name || $cached_name === $compiled_name ) { |
90 | |
91 | $output_file = self::OutputFile( $compiled_name, $parser_options ); |
92 | |
93 | if ( $output_file && file_exists( $output_file ) ) { |
94 | @touch( $list_file ); |
95 | return basename( $output_file ); // for backwards compatibility, we just return the name of the file |
96 | } |
97 | } |
98 | } |
99 | } |
100 | |
101 | $compiled = self::Cache( $less_files, $parser_options ); |
102 | if ( !$compiled ) { |
103 | return false; |
104 | } |
105 | |
106 | $compiled_name = self::CompiledName( $less_files, $hash ); |
107 | $output_file = self::OutputFile( $compiled_name, $parser_options ); |
108 | |
109 | // save the file list |
110 | $list = $less_files; |
111 | $list[] = $compiled_name; |
112 | $cache = implode( "\n", $list ); |
113 | file_put_contents( $list_file, $cache ); |
114 | |
115 | // save the css |
116 | file_put_contents( $output_file, $compiled ); |
117 | |
118 | // clean up |
119 | // Garbage collection can be slow, so run it only on cache misses, |
120 | // and at most once per process. |
121 | if ( !self::$gc_done ) { |
122 | self::$gc_done = true; |
123 | self::CleanCache(); |
124 | } |
125 | |
126 | return basename( $output_file ); |
127 | } |
128 | |
129 | /** |
130 | * Force the compiler to regenerate the cached css file |
131 | * |
132 | * @param array $less_files Array of .less files to compile |
133 | * @param array $parser_options Array of compiler options |
134 | * @param array $modify_vars Array of variables |
135 | * @return string Name of the css file |
136 | */ |
137 | public static function Regen( $less_files, $parser_options = [], $modify_vars = [] ) { |
138 | $parser_options['use_cache'] = false; |
139 | return self::Get( $less_files, $parser_options, $modify_vars ); |
140 | } |
141 | |
142 | public static function Cache( &$less_files, $parser_options = [] ) { |
143 | $parser_options['cache_dir'] = self::$cache_dir; |
144 | $parser = new Less_Parser( $parser_options ); |
145 | |
146 | // combine files |
147 | foreach ( $less_files as $file_path => $uri_or_less ) { |
148 | |
149 | // treat as less markup if there are newline characters |
150 | if ( strpos( $uri_or_less, "\n" ) !== false ) { |
151 | $parser->Parse( $uri_or_less ); |
152 | continue; |
153 | } |
154 | |
155 | $parser->ParseFile( $file_path, $uri_or_less ); |
156 | } |
157 | |
158 | $compiled = $parser->getCss(); |
159 | |
160 | $less_files = $parser->getParsedFiles(); |
161 | |
162 | return $compiled; |
163 | } |
164 | |
165 | private static function OutputFile( $compiled_name, $parser_options ) { |
166 | // custom output file |
167 | if ( !empty( $parser_options['output'] ) ) { |
168 | |
169 | // relative to cache directory? |
170 | if ( preg_match( '#[\\\\/]#', $parser_options['output'] ) ) { |
171 | return $parser_options['output']; |
172 | } |
173 | |
174 | return self::$cache_dir . $parser_options['output']; |
175 | } |
176 | |
177 | return self::$cache_dir . $compiled_name; |
178 | } |
179 | |
180 | private static function CompiledName( $files, $extrahash ) { |
181 | // save the file list |
182 | $temp = [ Less_Version::cache_version ]; |
183 | foreach ( $files as $file ) { |
184 | $temp[] = filemtime( $file ) . "\t" . filesize( $file ) . "\t" . $file; |
185 | } |
186 | |
187 | return self::$prefix . sha1( json_encode( $temp ) . $extrahash ) . '.css'; |
188 | } |
189 | |
190 | public static function SetCacheDir( $dir ) { |
191 | self::$cache_dir = self::CheckCacheDir( $dir ); |
192 | } |
193 | |
194 | /** |
195 | * @deprecated since 5.3.0 Internal for use by Less_Cache and Less_Parser only. |
196 | */ |
197 | public static function CheckCacheDir( $dir = null ) { |
198 | $dir ??= self::$cache_dir; |
199 | $dir = Less_Parser::WinPath( $dir ); |
200 | $dir = rtrim( $dir, '/' ) . '/'; |
201 | |
202 | if ( !file_exists( $dir ) ) { |
203 | if ( !mkdir( $dir ) ) { |
204 | throw new Less_Exception_Parser( 'Less.php cache directory couldn\'t be created: ' . $dir ); |
205 | } |
206 | |
207 | } elseif ( !is_dir( $dir ) ) { |
208 | throw new Less_Exception_Parser( 'Less.php cache directory doesn\'t exist: ' . $dir ); |
209 | |
210 | } elseif ( !is_writable( $dir ) ) { |
211 | throw new Less_Exception_Parser( 'Less.php cache directory isn\'t writable: ' . $dir ); |
212 | } |
213 | |
214 | return $dir; |
215 | } |
216 | |
217 | /** |
218 | * @deprecated since 5.3.0 Called automatically. Internal for use by Less_Cache and Less_Parser only. |
219 | */ |
220 | public static function CleanCache( $dir = null ) { |
221 | $dir ??= self::$cache_dir; |
222 | if ( !$dir ) { |
223 | return; |
224 | } |
225 | |
226 | // only remove files with extensions created by less.php |
227 | // css files removed based on the list files |
228 | $remove_types = [ 'lesscache' => 1, 'list' => 1, 'less' => 1, 'map' => 1 ]; |
229 | |
230 | $files = scandir( $dir ); |
231 | if ( !$files ) { |
232 | return; |
233 | } |
234 | |
235 | $check_time = time() - self::$gc_lifetime; |
236 | foreach ( $files as $file ) { |
237 | |
238 | // don't delete if the file wasn't created with less.php |
239 | if ( strpos( $file, self::$prefix ) !== 0 ) { |
240 | continue; |
241 | } |
242 | |
243 | $parts = explode( '.', $file ); |
244 | $type = array_pop( $parts ); |
245 | |
246 | if ( !isset( $remove_types[$type] ) ) { |
247 | continue; |
248 | } |
249 | |
250 | $fullPath = $dir . $file; |
251 | $mtime = filemtime( $fullPath ); |
252 | |
253 | // don't delete if it's a relatively new file |
254 | if ( $mtime > $check_time ) { |
255 | continue; |
256 | } |
257 | |
258 | // delete the list file and associated css file |
259 | if ( $type === 'list' ) { |
260 | self::ListFiles( $fullPath, $list, $css_file_name ); |
261 | if ( $css_file_name ) { |
262 | $css_file = $dir . $css_file_name; |
263 | if ( file_exists( $css_file ) ) { |
264 | unlink( $css_file ); |
265 | } |
266 | } |
267 | } |
268 | |
269 | unlink( $fullPath ); |
270 | } |
271 | } |
272 | |
273 | /** |
274 | * Get the list of less files and generated css file from a list file |
275 | */ |
276 | public static function ListFiles( $list_file, &$list, &$css_file_name ) { |
277 | $list = explode( "\n", file_get_contents( $list_file ) ); |
278 | |
279 | // pop the cached name that should match $compiled_name |
280 | $css_file_name = array_pop( $list ); |
281 | |
282 | if ( !preg_match( '/^' . self::$prefix . '[a-f0-9]+\.css$/', $css_file_name ) ) { |
283 | $list[] = $css_file_name; |
284 | $css_file_name = false; |
285 | } |
286 | } |
287 | |
288 | } |