2021-02-15 21:29:38 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Utility for handling the generation and caching of css files
|
|
|
|
*/
|
|
|
|
class Less_Cache {
|
|
|
|
|
|
|
|
// directory less.php can use for storing data
|
2023-07-07 22:33:10 +02:00
|
|
|
public static $cache_dir = false;
|
2021-02-15 21:29:38 +01:00
|
|
|
|
|
|
|
// prefix for the storing data
|
2023-07-07 22:33:10 +02:00
|
|
|
public static $prefix = 'lessphp_';
|
2021-02-15 21:29:38 +01:00
|
|
|
|
|
|
|
// prefix for the storing vars
|
2023-07-07 22:33:10 +02:00
|
|
|
public static $prefix_vars = 'lessphpvars_';
|
2021-02-15 21:29:38 +01:00
|
|
|
|
|
|
|
// specifies the number of seconds after which data created by less.php will be seen as 'garbage' and potentially cleaned up
|
2023-07-07 22:33:10 +02:00
|
|
|
public static $gc_lifetime = 604800;
|
2021-02-15 21:29:38 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Save and reuse the results of compiled less files.
|
|
|
|
* The first call to Get() will generate css and save it.
|
|
|
|
* Subsequent calls to Get() with the same arguments will return the same css filename
|
|
|
|
*
|
|
|
|
* @param array $less_files Array of .less files to compile
|
|
|
|
* @param array $parser_options Array of compiler options
|
|
|
|
* @param array $modify_vars Array of variables
|
2023-07-07 22:33:10 +02:00
|
|
|
* @return string|false Name of the css file
|
2021-02-15 21:29:38 +01:00
|
|
|
*/
|
2023-07-07 22:33:10 +02:00
|
|
|
public static function Get( $less_files, $parser_options = [], $modify_vars = [] ) {
|
2021-02-15 21:29:38 +01:00
|
|
|
// check $cache_dir
|
|
|
|
if ( isset( $parser_options['cache_dir'] ) ) {
|
2023-07-07 22:33:10 +02:00
|
|
|
self::$cache_dir = $parser_options['cache_dir'];
|
2021-02-15 21:29:38 +01:00
|
|
|
}
|
|
|
|
|
2023-07-07 22:33:10 +02:00
|
|
|
if ( empty( self::$cache_dir ) ) {
|
2021-02-15 21:29:38 +01:00
|
|
|
throw new Exception( 'cache_dir not set' );
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( isset( $parser_options['prefix'] ) ) {
|
2023-07-07 22:33:10 +02:00
|
|
|
self::$prefix = $parser_options['prefix'];
|
2021-02-15 21:29:38 +01:00
|
|
|
}
|
|
|
|
|
2023-07-07 22:33:10 +02:00
|
|
|
if ( empty( self::$prefix ) ) {
|
2021-02-15 21:29:38 +01:00
|
|
|
throw new Exception( 'prefix not set' );
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( isset( $parser_options['prefix_vars'] ) ) {
|
2023-07-07 22:33:10 +02:00
|
|
|
self::$prefix_vars = $parser_options['prefix_vars'];
|
2021-02-15 21:29:38 +01:00
|
|
|
}
|
|
|
|
|
2023-07-07 22:33:10 +02:00
|
|
|
if ( empty( self::$prefix_vars ) ) {
|
2021-02-15 21:29:38 +01:00
|
|
|
throw new Exception( 'prefix_vars not set' );
|
|
|
|
}
|
|
|
|
|
|
|
|
self::CheckCacheDir();
|
|
|
|
$less_files = (array)$less_files;
|
|
|
|
|
|
|
|
// create a file for variables
|
|
|
|
if ( !empty( $modify_vars ) ) {
|
|
|
|
$lessvars = Less_Parser::serializeVars( $modify_vars );
|
2023-07-07 22:33:10 +02:00
|
|
|
$vars_file = self::$cache_dir . self::$prefix_vars . sha1( $lessvars ) . '.less';
|
2021-02-15 21:29:38 +01:00
|
|
|
|
|
|
|
if ( !file_exists( $vars_file ) ) {
|
|
|
|
file_put_contents( $vars_file, $lessvars );
|
|
|
|
}
|
|
|
|
|
2023-07-07 22:33:10 +02:00
|
|
|
$less_files += [ $vars_file => '/' ];
|
2021-02-15 21:29:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// generate name for compiled css file
|
|
|
|
$hash = md5( json_encode( $less_files ) );
|
2023-07-07 22:33:10 +02:00
|
|
|
$list_file = self::$cache_dir . self::$prefix . $hash . '.list';
|
2021-02-15 21:29:38 +01:00
|
|
|
|
|
|
|
// check cached content
|
|
|
|
if ( !isset( $parser_options['use_cache'] ) || $parser_options['use_cache'] === true ) {
|
|
|
|
if ( file_exists( $list_file ) ) {
|
|
|
|
|
|
|
|
self::ListFiles( $list_file, $list, $cached_name );
|
|
|
|
$compiled_name = self::CompiledName( $list, $hash );
|
|
|
|
|
|
|
|
// if $cached_name is the same as the $compiled name, don't regenerate
|
|
|
|
if ( !$cached_name || $cached_name === $compiled_name ) {
|
|
|
|
|
|
|
|
$output_file = self::OutputFile( $compiled_name, $parser_options );
|
|
|
|
|
|
|
|
if ( $output_file && file_exists( $output_file ) ) {
|
|
|
|
@touch( $list_file );
|
|
|
|
return basename( $output_file ); // for backwards compatibility, we just return the name of the file
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$compiled = self::Cache( $less_files, $parser_options );
|
|
|
|
if ( !$compiled ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$compiled_name = self::CompiledName( $less_files, $hash );
|
|
|
|
$output_file = self::OutputFile( $compiled_name, $parser_options );
|
|
|
|
|
|
|
|
// save the file list
|
|
|
|
$list = $less_files;
|
|
|
|
$list[] = $compiled_name;
|
|
|
|
$cache = implode( "\n", $list );
|
|
|
|
file_put_contents( $list_file, $cache );
|
|
|
|
|
|
|
|
// save the css
|
|
|
|
file_put_contents( $output_file, $compiled );
|
|
|
|
|
|
|
|
// clean up
|
|
|
|
self::CleanCache();
|
|
|
|
|
|
|
|
return basename( $output_file );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Force the compiler to regenerate the cached css file
|
|
|
|
*
|
|
|
|
* @param array $less_files Array of .less files to compile
|
|
|
|
* @param array $parser_options Array of compiler options
|
|
|
|
* @param array $modify_vars Array of variables
|
|
|
|
* @return string Name of the css file
|
|
|
|
*/
|
2023-07-07 22:33:10 +02:00
|
|
|
public static function Regen( $less_files, $parser_options = [], $modify_vars = [] ) {
|
2021-02-15 21:29:38 +01:00
|
|
|
$parser_options['use_cache'] = false;
|
|
|
|
return self::Get( $less_files, $parser_options, $modify_vars );
|
|
|
|
}
|
|
|
|
|
2023-07-07 22:33:10 +02:00
|
|
|
public static function Cache( &$less_files, $parser_options = [] ) {
|
|
|
|
$parser_options['cache_dir'] = self::$cache_dir;
|
2021-02-15 21:29:38 +01:00
|
|
|
$parser = new Less_Parser( $parser_options );
|
|
|
|
|
|
|
|
// combine files
|
|
|
|
foreach ( $less_files as $file_path => $uri_or_less ) {
|
|
|
|
|
|
|
|
// treat as less markup if there are newline characters
|
|
|
|
if ( strpos( $uri_or_less, "\n" ) !== false ) {
|
|
|
|
$parser->Parse( $uri_or_less );
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$parser->ParseFile( $file_path, $uri_or_less );
|
|
|
|
}
|
|
|
|
|
|
|
|
$compiled = $parser->getCss();
|
|
|
|
|
|
|
|
$less_files = $parser->allParsedFiles();
|
|
|
|
|
|
|
|
return $compiled;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static function OutputFile( $compiled_name, $parser_options ) {
|
|
|
|
// custom output file
|
|
|
|
if ( !empty( $parser_options['output'] ) ) {
|
|
|
|
|
|
|
|
// relative to cache directory?
|
|
|
|
if ( preg_match( '#[\\\\/]#', $parser_options['output'] ) ) {
|
|
|
|
return $parser_options['output'];
|
|
|
|
}
|
|
|
|
|
2023-07-07 22:33:10 +02:00
|
|
|
return self::$cache_dir . $parser_options['output'];
|
2021-02-15 21:29:38 +01:00
|
|
|
}
|
|
|
|
|
2023-07-07 22:33:10 +02:00
|
|
|
return self::$cache_dir . $compiled_name;
|
2021-02-15 21:29:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private static function CompiledName( $files, $extrahash ) {
|
|
|
|
// save the file list
|
2023-07-07 22:33:10 +02:00
|
|
|
$temp = [ Less_Version::cache_version ];
|
2021-02-15 21:29:38 +01:00
|
|
|
foreach ( $files as $file ) {
|
2023-07-07 22:33:10 +02:00
|
|
|
$temp[] = filemtime( $file ) . "\t" . filesize( $file ) . "\t" . $file;
|
2021-02-15 21:29:38 +01:00
|
|
|
}
|
|
|
|
|
2023-07-07 22:33:10 +02:00
|
|
|
return self::$prefix . sha1( json_encode( $temp ) . $extrahash ) . '.css';
|
2021-02-15 21:29:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public static function SetCacheDir( $dir ) {
|
2023-07-07 22:33:10 +02:00
|
|
|
self::$cache_dir = $dir;
|
2021-02-15 21:29:38 +01:00
|
|
|
self::CheckCacheDir();
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function CheckCacheDir() {
|
2023-07-07 22:33:10 +02:00
|
|
|
self::$cache_dir = str_replace( '\\', '/', self::$cache_dir );
|
|
|
|
self::$cache_dir = rtrim( self::$cache_dir, '/' ) . '/';
|
2021-02-15 21:29:38 +01:00
|
|
|
|
2023-07-07 22:33:10 +02:00
|
|
|
if ( !file_exists( self::$cache_dir ) ) {
|
|
|
|
if ( !mkdir( self::$cache_dir ) ) {
|
|
|
|
throw new Less_Exception_Parser( 'Less.php cache directory couldn\'t be created: ' . self::$cache_dir );
|
2021-02-15 21:29:38 +01:00
|
|
|
}
|
|
|
|
|
2023-07-07 22:33:10 +02:00
|
|
|
} elseif ( !is_dir( self::$cache_dir ) ) {
|
|
|
|
throw new Less_Exception_Parser( 'Less.php cache directory doesn\'t exist: ' . self::$cache_dir );
|
2021-02-15 21:29:38 +01:00
|
|
|
|
2023-07-07 22:33:10 +02:00
|
|
|
} elseif ( !is_writable( self::$cache_dir ) ) {
|
|
|
|
throw new Less_Exception_Parser( 'Less.php cache directory isn\'t writable: ' . self::$cache_dir );
|
2021-02-15 21:29:38 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Delete unused less.php files
|
|
|
|
*/
|
|
|
|
public static function CleanCache() {
|
|
|
|
static $clean = false;
|
|
|
|
|
2023-07-07 22:33:10 +02:00
|
|
|
if ( $clean || empty( self::$cache_dir ) ) {
|
2021-02-15 21:29:38 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$clean = true;
|
|
|
|
|
|
|
|
// only remove files with extensions created by less.php
|
|
|
|
// css files removed based on the list files
|
2023-07-07 22:33:10 +02:00
|
|
|
$remove_types = [ 'lesscache' => 1,'list' => 1,'less' => 1,'map' => 1 ];
|
2021-02-15 21:29:38 +01:00
|
|
|
|
2023-07-07 22:33:10 +02:00
|
|
|
$files = scandir( self::$cache_dir );
|
2021-02-15 21:29:38 +01:00
|
|
|
if ( !$files ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$check_time = time() - self::$gc_lifetime;
|
|
|
|
foreach ( $files as $file ) {
|
|
|
|
|
|
|
|
// don't delete if the file wasn't created with less.php
|
2023-07-07 22:33:10 +02:00
|
|
|
if ( strpos( $file, self::$prefix ) !== 0 ) {
|
2021-02-15 21:29:38 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$parts = explode( '.', $file );
|
|
|
|
$type = array_pop( $parts );
|
|
|
|
|
|
|
|
if ( !isset( $remove_types[$type] ) ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2023-07-07 22:33:10 +02:00
|
|
|
$full_path = self::$cache_dir . $file;
|
2021-02-15 21:29:38 +01:00
|
|
|
$mtime = filemtime( $full_path );
|
|
|
|
|
|
|
|
// don't delete if it's a relatively new file
|
|
|
|
if ( $mtime > $check_time ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// delete the list file and associated css file
|
|
|
|
if ( $type === 'list' ) {
|
|
|
|
self::ListFiles( $full_path, $list, $css_file_name );
|
|
|
|
if ( $css_file_name ) {
|
2023-07-07 22:33:10 +02:00
|
|
|
$css_file = self::$cache_dir . $css_file_name;
|
2021-02-15 21:29:38 +01:00
|
|
|
if ( file_exists( $css_file ) ) {
|
|
|
|
unlink( $css_file );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
unlink( $full_path );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the list of less files and generated css file from a list file
|
|
|
|
*/
|
|
|
|
static function ListFiles( $list_file, &$list, &$css_file_name ) {
|
|
|
|
$list = explode( "\n", file_get_contents( $list_file ) );
|
|
|
|
|
|
|
|
// pop the cached name that should match $compiled_name
|
|
|
|
$css_file_name = array_pop( $list );
|
|
|
|
|
2023-07-07 22:33:10 +02:00
|
|
|
if ( !preg_match( '/^' . self::$prefix . '[a-f0-9]+\.css$/', $css_file_name ) ) {
|
2021-02-15 21:29:38 +01:00
|
|
|
$list[] = $css_file_name;
|
|
|
|
$css_file_name = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|