/** * Execute lessphp on a .less file or a lessphp cache structure * * The lessphp cache structure contains information about a specific * less file having been parsed. It can be used as a hint for future * calls to determine whether or not a rebuild is required. * * The cache structure contains two important keys that may be used * externally: * * compiled: The final compiled CSS * updated: The time (in seconds) the CSS was last compiled * * The cache structure is a plain-ol' PHP associative array and can * be serialized and unserialized without a hitch. * * @param mixed $in Input * @param bool $force Force rebuild? * @return array lessphp cache structure */ public static function cexecute($in, $force = false) { // assume no root $root = null; if (is_string($in)) { $root = $in; } elseif (is_array($in) and isset($in['root'])) { if ($force or !isset($in['files'])) { // If we are forcing a recompile or if for some reason the // structure does not contain any file information we should // specify the root to trigger a rebuild. $root = $in['root']; } elseif (isset($in['files']) and is_array($in['files'])) { foreach ($in['files'] as $fname => $ftime) { if (!file_exists($fname) or filemtime($fname) > $ftime) { // One of the files we knew about previously has changed // so we should look at our incoming root again. $root = $in['root']; break; } } } } else { // TODO: Throw an exception? We got neither a string nor something // that looks like a compatible lessphp cache structure. return null; } if ($root !== null) { // If we have a root value which means we should rebuild. $less = new lessc($root); $out = array(); $out['root'] = $root; $out['compiled'] = $less->parse(); $out['files'] = $less->allParsedFiles(); $out['updated'] = time(); return $out; } else { // No changes, pass back the structure // we were given initially. return $in; } }
/** * Send a group of files to client (ie : JS or CSS files) * Provide coherent user caching infos (1 month) for files and allow gzip when possible * * @param array $files : array of files path to send to client (FS relative) * @param string $contentType : the content type to send to client (default : text/html) * @return void * @access public * @static */ static function sendFiles($files, $contentType = 'text/html') { //check for the closest last modification date $lastdate = ''; //check for included files in less files $includes = array(); if ($contentType == 'text/css') { foreach ($files as $key => $file) { if (pathinfo($file, PATHINFO_EXTENSION) == 'less') { $lessCache = new CMS_cache(md5($file), 'lessphp', 2592000, false); if ($lessCache->exist()) { $includes = array_merge($includes, $lessCache->load()); } } } } if ($includes) { foreach ($includes as $key => $file) { if (file_exists($file) && is_file($file)) { $lastdate = filemtime($file) > $lastdate ? filemtime($file) : $lastdate; } } } foreach ($files as $key => $file) { if (file_exists($file) && is_file($file)) { $lastdate = filemtime($file) > $lastdate ? filemtime($file) : $lastdate; } else { CMS_grandFather::raiseError('Can\'t find file : ' . $file . ', skip it.'); unset($files[$key]); } } if (file_exists($_SERVER['SCRIPT_FILENAME'])) { $lastdate = filemtime($_SERVER['SCRIPT_FILENAME']) > $lastdate ? filemtime($_SERVER['SCRIPT_FILENAME']) : $lastdate; } //check If-Modified-Since header if exists then return a 304 if needed if (isset($_SERVER['IF-MODIFIED-SINCE'])) { $ifModifiedSince = strtotime($_SERVER['IF-MODIFIED-SINCE']); } elseif (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { $ifModifiedSince = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']); } if (isset($ifModifiedSince) && $lastdate <= $ifModifiedSince) { header('HTTP/1.1 304 Not Modified'); header('Content-Type: ' . $contentType); header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $lastdate) . ' GMT'); header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 2592000) . ' GMT'); //30 days header("Cache-Control: must-revalidate"); header("Pragma: public"); exit; } $compress = 'ob_gzhandler' != ini_get('output_handler') && extension_loaded('zlib') && !ini_get('zlib.output_compression') && strpos(strtolower(@$_SERVER['HTTP_ACCEPT_ENCODING']), 'gzip') !== false; //create cache id from files, compression status and last time files access $id = md5(implode(',', $files) . '-' . $compress . '-' . $lastdate); //create cache object $cache = new CMS_cache($id, $contentType, 2592000, false); $datas = ''; if (!$cache->exist() || !($datas = $cache->load())) { // datas cache missing so create it foreach ($files as $file) { $fileData = file_get_contents($file); //strip BOM from file if exists if (substr($fileData, 0, 3) === '') { $fileData = substr($fileData, 3); } //append file origin comment if ($contentType == 'text/javascript') { $fileData = '//<<' . "\n" . '//JS file: ' . str_replace(PATH_REALROOT_FS, '', $file) . '' . "\n" . '//!>>' . "\n" . $fileData; } //append file origin comment if ($contentType == 'text/css') { //compile less files if needed if (pathinfo($file, PATHINFO_EXTENSION) == 'less') { $less = new lessc($file); $fileData = $less->parse(); $lessIncludes = $less->allParsedFiles(); if (sizeof($lessIncludes) > 1) { $lessCache = new CMS_cache(md5($file), 'lessphp', 2592000, false); $lessCache->save(array_keys($lessIncludes), array('type' => 'lessphp')); } } $fileData = '/*<<*/' . "\n" . '/* CSS file: ' . str_replace(PATH_REALROOT_FS, '', $file) . ' */' . "\n" . '/*!>>*/' . "\n" . $fileData; } $datas .= $fileData . "\n"; } //minimize JS files if needed if (!SYSTEM_DEBUG && $contentType == 'text/javascript') { $datas = JSMin::minify($datas); } //minimize CSS files if needed if (!SYSTEM_DEBUG && $contentType == 'text/css') { $datas = cssmin::minify($datas); } //compres data if needed if ($compress) { $datas = gzencode($datas, 3); } if ($cache) { $cache->save($datas, array('type' => $contentType)); } } //send headers header('Content-Type: ' . $contentType); header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $lastdate) . ' GMT'); header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 2592000) . ' GMT'); //30 days header("Cache-Control: must-revalidate"); header("Pragma: public"); //send gzip header if needed if ($compress) { header('Vary: Accept-Encoding'); // Handle proxies header("Content-Encoding: gzip"); } //send content echo $datas; exit; }