Cache.php 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. <?php
  2. require_once dirname( __FILE__ ).'/Version.php';
  3. /**
  4. * Utility for handling the generation and caching of css files
  5. *
  6. * @package Less
  7. * @subpackage cache
  8. *
  9. */
  10. class Less_Cache {
  11. // directory less.php can use for storing data
  12. public static $cache_dir = false;
  13. // prefix for the storing data
  14. public static $prefix = 'lessphp_';
  15. // prefix for the storing vars
  16. public static $prefix_vars = 'lessphpvars_';
  17. // specifies the number of seconds after which data created by less.php will be seen as 'garbage' and potentially cleaned up
  18. public static $gc_lifetime = 604800;
  19. /**
  20. * Save and reuse the results of compiled less files.
  21. * The first call to Get() will generate css and save it.
  22. * Subsequent calls to Get() with the same arguments will return the same css filename
  23. *
  24. * @param array $less_files Array of .less files to compile
  25. * @param array $parser_options Array of compiler options
  26. * @param array $modify_vars Array of variables
  27. * @return string Name of the css file
  28. */
  29. public static function Get( $less_files, $parser_options = array(), $modify_vars = array() ) {
  30. // check $cache_dir
  31. if ( isset( $parser_options['cache_dir'] ) ) {
  32. Less_Cache::$cache_dir = $parser_options['cache_dir'];
  33. }
  34. if ( empty( Less_Cache::$cache_dir ) ) {
  35. throw new Exception( 'cache_dir not set' );
  36. }
  37. if ( isset( $parser_options['prefix'] ) ) {
  38. Less_Cache::$prefix = $parser_options['prefix'];
  39. }
  40. if ( empty( Less_Cache::$prefix ) ) {
  41. throw new Exception( 'prefix not set' );
  42. }
  43. if ( isset( $parser_options['prefix_vars'] ) ) {
  44. Less_Cache::$prefix_vars = $parser_options['prefix_vars'];
  45. }
  46. if ( empty( Less_Cache::$prefix_vars ) ) {
  47. throw new Exception( 'prefix_vars not set' );
  48. }
  49. self::CheckCacheDir();
  50. $less_files = (array)$less_files;
  51. // create a file for variables
  52. if ( !empty( $modify_vars ) ) {
  53. $lessvars = Less_Parser::serializeVars( $modify_vars );
  54. $vars_file = Less_Cache::$cache_dir . Less_Cache::$prefix_vars . sha1( $lessvars ) . '.less';
  55. if ( !file_exists( $vars_file ) ) {
  56. file_put_contents( $vars_file, $lessvars );
  57. }
  58. $less_files += array( $vars_file => '/' );
  59. }
  60. // generate name for compiled css file
  61. $hash = md5( json_encode( $less_files ) );
  62. $list_file = Less_Cache::$cache_dir . Less_Cache::$prefix . $hash . '.list';
  63. // check cached content
  64. if ( !isset( $parser_options['use_cache'] ) || $parser_options['use_cache'] === true ) {
  65. if ( file_exists( $list_file ) ) {
  66. self::ListFiles( $list_file, $list, $cached_name );
  67. $compiled_name = self::CompiledName( $list, $hash );
  68. // if $cached_name is the same as the $compiled name, don't regenerate
  69. if ( !$cached_name || $cached_name === $compiled_name ) {
  70. $output_file = self::OutputFile( $compiled_name, $parser_options );
  71. if ( $output_file && file_exists( $output_file ) ) {
  72. @touch( $list_file );
  73. return basename( $output_file ); // for backwards compatibility, we just return the name of the file
  74. }
  75. }
  76. }
  77. }
  78. $compiled = self::Cache( $less_files, $parser_options );
  79. if ( !$compiled ) {
  80. return false;
  81. }
  82. $compiled_name = self::CompiledName( $less_files, $hash );
  83. $output_file = self::OutputFile( $compiled_name, $parser_options );
  84. // save the file list
  85. $list = $less_files;
  86. $list[] = $compiled_name;
  87. $cache = implode( "\n", $list );
  88. file_put_contents( $list_file, $cache );
  89. // save the css
  90. file_put_contents( $output_file, $compiled );
  91. // clean up
  92. self::CleanCache();
  93. return basename( $output_file );
  94. }
  95. /**
  96. * Force the compiler to regenerate the cached css file
  97. *
  98. * @param array $less_files Array of .less files to compile
  99. * @param array $parser_options Array of compiler options
  100. * @param array $modify_vars Array of variables
  101. * @return string Name of the css file
  102. */
  103. public static function Regen( $less_files, $parser_options = array(), $modify_vars = array() ) {
  104. $parser_options['use_cache'] = false;
  105. return self::Get( $less_files, $parser_options, $modify_vars );
  106. }
  107. public static function Cache( &$less_files, $parser_options = array() ) {
  108. // get less.php if it exists
  109. $file = dirname( __FILE__ ) . '/Less.php';
  110. if ( file_exists( $file ) && !class_exists( 'Less_Parser' ) ) {
  111. require_once $file;
  112. }
  113. $parser_options['cache_dir'] = Less_Cache::$cache_dir;
  114. $parser = new Less_Parser( $parser_options );
  115. // combine files
  116. foreach ( $less_files as $file_path => $uri_or_less ) {
  117. // treat as less markup if there are newline characters
  118. if ( strpos( $uri_or_less, "\n" ) !== false ) {
  119. $parser->Parse( $uri_or_less );
  120. continue;
  121. }
  122. $parser->ParseFile( $file_path, $uri_or_less );
  123. }
  124. $compiled = $parser->getCss();
  125. $less_files = $parser->allParsedFiles();
  126. return $compiled;
  127. }
  128. private static function OutputFile( $compiled_name, $parser_options ) {
  129. // custom output file
  130. if ( !empty( $parser_options['output'] ) ) {
  131. // relative to cache directory?
  132. if ( preg_match( '#[\\\\/]#', $parser_options['output'] ) ) {
  133. return $parser_options['output'];
  134. }
  135. return Less_Cache::$cache_dir.$parser_options['output'];
  136. }
  137. return Less_Cache::$cache_dir.$compiled_name;
  138. }
  139. private static function CompiledName( $files, $extrahash ) {
  140. // save the file list
  141. $temp = array( Less_Version::cache_version );
  142. foreach ( $files as $file ) {
  143. $temp[] = filemtime( $file )."\t".filesize( $file )."\t".$file;
  144. }
  145. return Less_Cache::$prefix.sha1( json_encode( $temp ).$extrahash ).'.css';
  146. }
  147. public static function SetCacheDir( $dir ) {
  148. Less_Cache::$cache_dir = $dir;
  149. self::CheckCacheDir();
  150. }
  151. public static function CheckCacheDir() {
  152. Less_Cache::$cache_dir = str_replace( '\\', '/', Less_Cache::$cache_dir );
  153. Less_Cache::$cache_dir = rtrim( Less_Cache::$cache_dir, '/' ).'/';
  154. if ( !file_exists( Less_Cache::$cache_dir ) ) {
  155. if ( !mkdir( Less_Cache::$cache_dir ) ) {
  156. throw new Less_Exception_Parser( 'Less.php cache directory couldn\'t be created: '.Less_Cache::$cache_dir );
  157. }
  158. } elseif ( !is_dir( Less_Cache::$cache_dir ) ) {
  159. throw new Less_Exception_Parser( 'Less.php cache directory doesn\'t exist: '.Less_Cache::$cache_dir );
  160. } elseif ( !is_writable( Less_Cache::$cache_dir ) ) {
  161. throw new Less_Exception_Parser( 'Less.php cache directory isn\'t writable: '.Less_Cache::$cache_dir );
  162. }
  163. }
  164. /**
  165. * Delete unused less.php files
  166. *
  167. */
  168. public static function CleanCache() {
  169. static $clean = false;
  170. if ( $clean || empty( Less_Cache::$cache_dir ) ) {
  171. return;
  172. }
  173. $clean = true;
  174. // only remove files with extensions created by less.php
  175. // css files removed based on the list files
  176. $remove_types = array( 'lesscache' => 1,'list' => 1,'less' => 1,'map' => 1 );
  177. $files = scandir( Less_Cache::$cache_dir );
  178. if ( !$files ) {
  179. return;
  180. }
  181. $check_time = time() - self::$gc_lifetime;
  182. foreach ( $files as $file ) {
  183. // don't delete if the file wasn't created with less.php
  184. if ( strpos( $file, Less_Cache::$prefix ) !== 0 ) {
  185. continue;
  186. }
  187. $parts = explode( '.', $file );
  188. $type = array_pop( $parts );
  189. if ( !isset( $remove_types[$type] ) ) {
  190. continue;
  191. }
  192. $full_path = Less_Cache::$cache_dir . $file;
  193. $mtime = filemtime( $full_path );
  194. // don't delete if it's a relatively new file
  195. if ( $mtime > $check_time ) {
  196. continue;
  197. }
  198. // delete the list file and associated css file
  199. if ( $type === 'list' ) {
  200. self::ListFiles( $full_path, $list, $css_file_name );
  201. if ( $css_file_name ) {
  202. $css_file = Less_Cache::$cache_dir . $css_file_name;
  203. if ( file_exists( $css_file ) ) {
  204. unlink( $css_file );
  205. }
  206. }
  207. }
  208. unlink( $full_path );
  209. }
  210. }
  211. /**
  212. * Get the list of less files and generated css file from a list file
  213. *
  214. */
  215. static function ListFiles( $list_file, &$list, &$css_file_name ) {
  216. $list = explode( "\n", file_get_contents( $list_file ) );
  217. // pop the cached name that should match $compiled_name
  218. $css_file_name = array_pop( $list );
  219. if ( !preg_match( '/^' . Less_Cache::$prefix . '[a-f0-9]+\.css$/', $css_file_name ) ) {
  220. $list[] = $css_file_name;
  221. $css_file_name = false;
  222. }
  223. }
  224. }