1<?php 2 3require_once dirname( __FILE__ ).'/Version.php'; 4 5/** 6 * Utility for handling the generation and caching of css files 7 * 8 * @package Less 9 * @subpackage cache 10 * 11 */ 12class Less_Cache { 13 14 // directory less.php can use for storing data 15 public static $cache_dir = false; 16 17 // prefix for the storing data 18 public static $prefix = 'lessphp_'; 19 20 // prefix for the storing vars 21 public static $prefix_vars = 'lessphpvars_'; 22 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; 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 Name of the css file 35 */ 36 public static function Get( $less_files, $parser_options = array(), $modify_vars = array() ) { 37 // check $cache_dir 38 if ( isset( $parser_options['cache_dir'] ) ) { 39 Less_Cache::$cache_dir = $parser_options['cache_dir']; 40 } 41 42 if ( empty( Less_Cache::$cache_dir ) ) { 43 throw new Exception( 'cache_dir not set' ); 44 } 45 46 if ( isset( $parser_options['prefix'] ) ) { 47 Less_Cache::$prefix = $parser_options['prefix']; 48 } 49 50 if ( empty( Less_Cache::$prefix ) ) { 51 throw new Exception( 'prefix not set' ); 52 } 53 54 if ( isset( $parser_options['prefix_vars'] ) ) { 55 Less_Cache::$prefix_vars = $parser_options['prefix_vars']; 56 } 57 58 if ( empty( Less_Cache::$prefix_vars ) ) { 59 throw new Exception( 'prefix_vars not set' ); 60 } 61 62 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 = Less_Cache::$cache_dir . Less_Cache::$prefix_vars . sha1( $lessvars ) . '.less'; 69 70 if ( !file_exists( $vars_file ) ) { 71 file_put_contents( $vars_file, $lessvars ); 72 } 73 74 $less_files += array( $vars_file => '/' ); 75 } 76 77 // generate name for compiled css file 78 $hash = md5( json_encode( $less_files ) ); 79 $list_file = Less_Cache::$cache_dir . Less_Cache::$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 self::CleanCache(); 120 121 return basename( $output_file ); 122 } 123 124 /** 125 * Force the compiler to regenerate the cached css file 126 * 127 * @param array $less_files Array of .less files to compile 128 * @param array $parser_options Array of compiler options 129 * @param array $modify_vars Array of variables 130 * @return string Name of the css file 131 */ 132 public static function Regen( $less_files, $parser_options = array(), $modify_vars = array() ) { 133 $parser_options['use_cache'] = false; 134 return self::Get( $less_files, $parser_options, $modify_vars ); 135 } 136 137 public static function Cache( &$less_files, $parser_options = array() ) { 138 // get less.php if it exists 139 $file = dirname( __FILE__ ) . '/Less.php'; 140 if ( file_exists( $file ) && !class_exists( 'Less_Parser' ) ) { 141 require_once $file; 142 } 143 144 $parser_options['cache_dir'] = Less_Cache::$cache_dir; 145 $parser = new Less_Parser( $parser_options ); 146 147 // combine files 148 foreach ( $less_files as $file_path => $uri_or_less ) { 149 150 // treat as less markup if there are newline characters 151 if ( strpos( $uri_or_less, "\n" ) !== false ) { 152 $parser->Parse( $uri_or_less ); 153 continue; 154 } 155 156 $parser->ParseFile( $file_path, $uri_or_less ); 157 } 158 159 $compiled = $parser->getCss(); 160 161 $less_files = $parser->allParsedFiles(); 162 163 return $compiled; 164 } 165 166 private static function OutputFile( $compiled_name, $parser_options ) { 167 // custom output file 168 if ( !empty( $parser_options['output'] ) ) { 169 170 // relative to cache directory? 171 if ( preg_match( '#[\\\\/]#', $parser_options['output'] ) ) { 172 return $parser_options['output']; 173 } 174 175 return Less_Cache::$cache_dir.$parser_options['output']; 176 } 177 178 return Less_Cache::$cache_dir.$compiled_name; 179 } 180 181 private static function CompiledName( $files, $extrahash ) { 182 // save the file list 183 $temp = array( Less_Version::cache_version ); 184 foreach ( $files as $file ) { 185 $temp[] = filemtime( $file )."\t".filesize( $file )."\t".$file; 186 } 187 188 return Less_Cache::$prefix.sha1( json_encode( $temp ).$extrahash ).'.css'; 189 } 190 191 public static function SetCacheDir( $dir ) { 192 Less_Cache::$cache_dir = $dir; 193 self::CheckCacheDir(); 194 } 195 196 public static function CheckCacheDir() { 197 Less_Cache::$cache_dir = str_replace( '\\', '/', Less_Cache::$cache_dir ); 198 Less_Cache::$cache_dir = rtrim( Less_Cache::$cache_dir, '/' ).'/'; 199 200 if ( !file_exists( Less_Cache::$cache_dir ) ) { 201 if ( !mkdir( Less_Cache::$cache_dir ) ) { 202 throw new Less_Exception_Parser( 'Less.php cache directory couldn\'t be created: '.Less_Cache::$cache_dir ); 203 } 204 205 } elseif ( !is_dir( Less_Cache::$cache_dir ) ) { 206 throw new Less_Exception_Parser( 'Less.php cache directory doesn\'t exist: '.Less_Cache::$cache_dir ); 207 208 } elseif ( !is_writable( Less_Cache::$cache_dir ) ) { 209 throw new Less_Exception_Parser( 'Less.php cache directory isn\'t writable: '.Less_Cache::$cache_dir ); 210 211 } 212 213 } 214 215 /** 216 * Delete unused less.php files 217 * 218 */ 219 public static function CleanCache() { 220 static $clean = false; 221 222 if ( $clean || empty( Less_Cache::$cache_dir ) ) { 223 return; 224 } 225 226 $clean = true; 227 228 // only remove files with extensions created by less.php 229 // css files removed based on the list files 230 $remove_types = array( 'lesscache' => 1,'list' => 1,'less' => 1,'map' => 1 ); 231 232 $files = scandir( Less_Cache::$cache_dir ); 233 if ( !$files ) { 234 return; 235 } 236 237 $check_time = time() - self::$gc_lifetime; 238 foreach ( $files as $file ) { 239 240 // don't delete if the file wasn't created with less.php 241 if ( strpos( $file, Less_Cache::$prefix ) !== 0 ) { 242 continue; 243 } 244 245 $parts = explode( '.', $file ); 246 $type = array_pop( $parts ); 247 248 if ( !isset( $remove_types[$type] ) ) { 249 continue; 250 } 251 252 $full_path = Less_Cache::$cache_dir . $file; 253 $mtime = filemtime( $full_path ); 254 255 // don't delete if it's a relatively new file 256 if ( $mtime > $check_time ) { 257 continue; 258 } 259 260 // delete the list file and associated css file 261 if ( $type === 'list' ) { 262 self::ListFiles( $full_path, $list, $css_file_name ); 263 if ( $css_file_name ) { 264 $css_file = Less_Cache::$cache_dir . $css_file_name; 265 if ( file_exists( $css_file ) ) { 266 unlink( $css_file ); 267 } 268 } 269 } 270 271 unlink( $full_path ); 272 } 273 274 } 275 276 /** 277 * Get the list of less files and generated css file from a list file 278 * 279 */ 280 static function ListFiles( $list_file, &$list, &$css_file_name ) { 281 $list = explode( "\n", file_get_contents( $list_file ) ); 282 283 // pop the cached name that should match $compiled_name 284 $css_file_name = array_pop( $list ); 285 286 if ( !preg_match( '/^' . Less_Cache::$prefix . '[a-f0-9]+\.css$/', $css_file_name ) ) { 287 $list[] = $css_file_name; 288 $css_file_name = false; 289 } 290 291 } 292 293} 294