Merge branch 'MDL-60125-master' of git://github.com/andrewnicols/moodle
[moodle.git] / lib / lessphp / Cache.php
blobbf5bf9218f225dbaa8d2e5046e2eb4fcb639ff93
1 <?php
3 require_once( dirname(__FILE__).'/Version.php');
5 /**
6 * Utility for handling the generation and caching of css files
8 * @package Less
9 * @subpackage cache
12 class Less_Cache{
14 // directory less.php can use for storing data
15 public static $cache_dir = false;
17 // prefix for the storing data
18 public static $prefix = 'lessphp_';
20 // prefix for the storing vars
21 public static $prefix_vars = 'lessphpvars_';
23 // specifies the number of seconds after which data created by less.php will be seen as 'garbage' and potentially cleaned up
24 public static $gc_lifetime = 604800;
27 /**
28 * Save and reuse the results of compiled less files.
29 * The first call to Get() will generate css and save it.
30 * Subsequent calls to Get() with the same arguments will return the same css filename
32 * @param array $less_files Array of .less files to compile
33 * @param array $parser_options Array of compiler options
34 * @param array $modify_vars Array of variables
35 * @return string Name of the css file
37 public static function Get( $less_files, $parser_options = array(), $modify_vars = array() ){
40 //check $cache_dir
41 if( isset($parser_options['cache_dir']) ){
42 Less_Cache::$cache_dir = $parser_options['cache_dir'];
45 if( empty(Less_Cache::$cache_dir) ){
46 throw new Exception('cache_dir not set');
49 if( isset($parser_options['prefix']) ){
50 Less_Cache::$prefix = $parser_options['prefix'];
53 if( empty(Less_Cache::$prefix) ){
54 throw new Exception('prefix not set');
57 if( isset($parser_options['prefix_vars']) ){
58 Less_Cache::$prefix_vars = $parser_options['prefix_vars'];
61 if( empty(Less_Cache::$prefix_vars) ){
62 throw new Exception('prefix_vars not set');
65 self::CheckCacheDir();
66 $less_files = (array)$less_files;
69 //create a file for variables
70 if( !empty($modify_vars) ){
71 $lessvars = Less_Parser::serializeVars($modify_vars);
72 $vars_file = Less_Cache::$cache_dir . Less_Cache::$prefix_vars . sha1($lessvars) . '.less';
74 if( !file_exists($vars_file) ){
75 file_put_contents($vars_file, $lessvars);
78 $less_files += array($vars_file => '/');
82 // generate name for compiled css file
83 $hash = md5(json_encode($less_files));
84 $list_file = Less_Cache::$cache_dir . Less_Cache::$prefix . $hash . '.list';
86 // check cached content
87 if( !isset($parser_options['use_cache']) || $parser_options['use_cache'] === true ){
88 if( file_exists($list_file) ){
90 self::ListFiles($list_file, $list, $cached_name);
91 $compiled_name = self::CompiledName($list, $hash);
93 // if $cached_name is the same as the $compiled name, don't regenerate
94 if( !$cached_name || $cached_name === $compiled_name ){
96 $output_file = self::OutputFile($compiled_name, $parser_options );
98 if( $output_file && file_exists($output_file) ){
99 @touch($list_file);
100 return basename($output_file); // for backwards compatibility, we just return the name of the file
106 $compiled = self::Cache( $less_files, $parser_options );
107 if( !$compiled ){
108 return false;
111 $compiled_name = self::CompiledName( $less_files, $hash );
112 $output_file = self::OutputFile($compiled_name, $parser_options );
115 //save the file list
116 $list = $less_files;
117 $list[] = $compiled_name;
118 $cache = implode("\n",$list);
119 file_put_contents( $list_file, $cache );
122 //save the css
123 file_put_contents( $output_file, $compiled );
126 //clean up
127 self::CleanCache();
129 return basename($output_file);
133 * Force the compiler to regenerate the cached css file
135 * @param array $less_files Array of .less files to compile
136 * @param array $parser_options Array of compiler options
137 * @param array $modify_vars Array of variables
138 * @return string Name of the css file
140 public static function Regen( $less_files, $parser_options = array(), $modify_vars = array() ){
141 $parser_options['use_cache'] = false;
142 return self::Get( $less_files, $parser_options, $modify_vars );
145 public static function Cache( &$less_files, $parser_options = array() ){
148 // get less.php if it exists
149 $file = dirname(__FILE__) . '/Less.php';
150 if( file_exists($file) && !class_exists('Less_Parser') ){
151 require_once($file);
154 $parser_options['cache_dir'] = Less_Cache::$cache_dir;
155 $parser = new Less_Parser($parser_options);
158 // combine files
159 foreach($less_files as $file_path => $uri_or_less ){
161 //treat as less markup if there are newline characters
162 if( strpos($uri_or_less,"\n") !== false ){
163 $parser->Parse( $uri_or_less );
164 continue;
167 $parser->ParseFile( $file_path, $uri_or_less );
170 $compiled = $parser->getCss();
173 $less_files = $parser->allParsedFiles();
175 return $compiled;
179 private static function OutputFile( $compiled_name, $parser_options ){
181 //custom output file
182 if( !empty($parser_options['output']) ){
184 //relative to cache directory?
185 if( preg_match('#[\\\\/]#',$parser_options['output']) ){
186 return $parser_options['output'];
189 return Less_Cache::$cache_dir.$parser_options['output'];
192 return Less_Cache::$cache_dir.$compiled_name;
196 private static function CompiledName( $files, $extrahash ){
198 //save the file list
199 $temp = array(Less_Version::cache_version);
200 foreach($files as $file){
201 $temp[] = filemtime($file)."\t".filesize($file)."\t".$file;
204 return Less_Cache::$prefix.sha1(json_encode($temp).$extrahash).'.css';
208 public static function SetCacheDir( $dir ){
209 Less_Cache::$cache_dir = $dir;
212 public static function CheckCacheDir(){
214 Less_Cache::$cache_dir = str_replace('\\','/',Less_Cache::$cache_dir);
215 Less_Cache::$cache_dir = rtrim(Less_Cache::$cache_dir,'/').'/';
217 if( !file_exists(Less_Cache::$cache_dir) ){
218 if( !mkdir(Less_Cache::$cache_dir) ){
219 throw new Less_Exception_Parser('Less.php cache directory couldn\'t be created: '.Less_Cache::$cache_dir);
222 }elseif( !is_dir(Less_Cache::$cache_dir) ){
223 throw new Less_Exception_Parser('Less.php cache directory doesn\'t exist: '.Less_Cache::$cache_dir);
225 }elseif( !is_writable(Less_Cache::$cache_dir) ){
226 throw new Less_Exception_Parser('Less.php cache directory isn\'t writable: '.Less_Cache::$cache_dir);
234 * Delete unused less.php files
237 public static function CleanCache(){
238 static $clean = false;
240 if( $clean ){
241 return;
244 $files = scandir(Less_Cache::$cache_dir);
245 if( $files ){
246 $check_time = time() - self::$gc_lifetime;
247 foreach($files as $file){
249 // don't delete if the file wasn't created with less.php
250 if( strpos($file,Less_Cache::$prefix) !== 0 ){
251 continue;
254 $full_path = Less_Cache::$cache_dir . $file;
256 // make sure the file still exists
257 // css files may have already been deleted
258 if( !file_exists($full_path) ){
259 continue;
261 $mtime = filemtime($full_path);
263 // don't delete if it's a relatively new file
264 if( $mtime > $check_time ){
265 continue;
268 $parts = explode('.',$file);
269 $type = array_pop($parts);
272 // delete css files based on the list files
273 if( $type === 'css' ){
274 continue;
278 // delete the list file and associated css file
279 if( $type === 'list' ){
280 self::ListFiles($full_path, $list, $css_file_name);
281 if( $css_file_name ){
282 $css_file = Less_Cache::$cache_dir . $css_file_name;
283 if( file_exists($css_file) ){
284 unlink($css_file);
289 unlink($full_path);
293 $clean = true;
298 * Get the list of less files and generated css file from a list file
301 static function ListFiles($list_file, &$list, &$css_file_name ){
303 $list = explode("\n",file_get_contents($list_file));
305 //pop the cached name that should match $compiled_name
306 $css_file_name = array_pop($list);
308 if( !preg_match('/^' . Less_Cache::$prefix . '[a-f0-9]+\.css$/',$css_file_name) ){
309 $list[] = $css_file_name;
310 $css_file_name = false;