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