Source of file CSSCrush.php
Size: 7,513 Bytes - Last Modified: 2021-12-23T10:20:55+00:00
/var/www/docs.ssmods.com/process/src/code/CSSCrush.php
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300 | <?php namespace Stampy; use Object; use Config; use Debug; use Requirements; use SS_Cache; use Flushable; class CSSCrush extends Object implements Flushable { const CACHE_KEY_PREFIX = 'csscrush'; /** * Change settings on CSSCrush * * 'plugins' => array( * 'px2em', * ), * * @see https://github.com/peteboere/css-crush/blob/master/docs/api/options.md */ private static $options = array( ); /** * Track built CSS files. * * @var array */ protected $css_tracked = array(); /** * @var array */ protected $_options = null; /** * Check if $this->init() has been called * * @var boolean */ private $_has_init = false; /** * Stops recompilation of files twice in one request * * @var boolean */ private $_has_recompiled_css = false; /** * Tracked CSS files from cache (from a previous request) * * @var array */ private $_cache_css_tracked = array(); /** * To be used with the provided or your own alternate Requirements_Backend * * @return array|false */ public function css($filename, $media = null) { if (substr($filename, 0, 9) === 'framework' || substr($filename, 0, 3) === 'cms') { // Avoid preprocessing CSS from framework/cms folders. // NOTE(Jake): Might require disabling/enabling on a per-module basis return false; } $theme = $this->getTheme(); $outputFilepath = $this->processCSS($filename); $this->css_tracked[$theme][$filename] = array( 'output_file' => BASE_PATH.'/'.$outputFilepath, 'media' => $media ); return array('filepath' => $outputFilepath, 'media' => $media); } /** * */ public function __construct() { parent::__construct(); // Avoid inclusion if it's been included with Composer if (!class_exists('CssCrush\Version')) { include(BASE_PATH.'/'.Utility::MODULE_DIR.'/thirdparty/css-crush/CssCrush.php'); } } /** * Called during /dev/build */ public function requireDefaultRecords() { // todo(Jake): Properly implement and test this. // Make options update on flush. This will // give variable access from data without slowing // down caching support. // //$cssCrush->flushOptions(); //$cssCrush->extend('updateOptions', $options); $this->recompileTrackedCSS(); // NOTE(Jake): Having this might be nice? //$this->extend('requireDefaultRecords'); } /** * Called during ?flush=all */ public static function flush() { // NOTE(Jake): Maybe move this to 'Utility' and then call // singleton("CSSCrush")->flush(). That way, the entire // function is injectorable. $self = singleton(__CLASS__); $self->recompileTrackedCSS(); // NOTE(Jake): Having this might be nice? //$self->extend('flush'); } /** * Initialize additional configurations for CSSCrush. */ public function init() { if ($this->_has_init === true) { user_error("$this->class should only call init() once.", E_USER_ERROR); return; } $this->extend('onInit'); $this->_has_init = true; } /** * Process a CSS file with CSSCrush. * * @var string */ public function processCSS($filename, $options = array()) { $filepath = BASE_PATH.'/'.$filename; $outputFilename = Utility::sanitise_filepath(dirname($filename).'/').basename($filename); $outputFilepath = Requirements::backend()->getCombinedFilesFolder().'/'.$outputFilename; $combinedOptions = array_merge($this->getOptions(), array( 'output_file' => $outputFilename, ), $options); $url = csscrush_file($filepath, $combinedOptions); return $outputFilepath; } /** * @return array */ public function getOptions() { if ($this->_options !== null) { return $this->_options; } $options = Config::inst()->get(__CLASS__, 'options'); $options = array_merge(array( 'output_dir' => BASE_PATH.'/'.Requirements::backend()->getCombinedFilesFolder(), ), $options); $this->_options = $options; return $this->_options; } /** * Get cache object * * @return Zend_Cache_Core */ protected function getCache() { // todo(Jake): Allow a custom backend (ie. Redis?) $backend = 'CSSCrush'; // todo(Jake): Allow cacheDir to be configurable $cacheDir = 'csscrush'; $cacheDir = TEMP_FOLDER . DIRECTORY_SEPARATOR . $cacheDir; if (!is_dir($cacheDir)) { mkdir($cacheDir); } SS_Cache::add_backend('CSSCrushStore', 'File', array('cache_dir' => $cacheDir)); SS_Cache::pick_backend('CSSCrushStore', $backend, 1000); // Set lifetime, allowing for 0 (infinite) lifetime $lifetime = self::config()->cacheDuration; if ($lifetime !== null) { SS_Cache::set_cache_lifetime($backend, $lifetime); } return SS_Cache::factory($backend); } /** * Load built CSS files. This function exists so cached pages * can rebuild the CSS without re-running all the page logic. */ public function recompileTrackedCSS() { if ($this->_has_recompiled_css) { return; } $this->_has_recompiled_css = true; $cssTracked = $this->loadFromCache(); if ($cssTracked === false) { return; } if ($cssTracked) { // Recompile each file foreach ($cssTracked as $filename => $css) { $this->processCSS($filename, array('cache' => 0)); } } } /** * @return array|false */ protected function loadFromCache() { $theme = $this->getTheme(); if (isset($this->_cache_css_tracked[$theme])) { return $this->_cache_css_tracked[$theme]; } $cache = $this->getCache(); $cssTracked = $cache->load(self::CACHE_KEY_PREFIX.'_'.$theme.'_tracked'); if (!$cssTracked) { return $this->_cache_css_tracked[$theme] = false; } $cssTracked = json_decode($cssTracked, true); if ($cssTracked === false || $cssTracked === null || $cssTracked === true) { return $this->_cache_css_tracked[$theme] = false; } return $this->_cache_css_tracked[$theme] = $cssTracked; } /** * At the end of the request, store all the built CSS files. * * @see CSSCrush::recompileCSS() */ public function writeToCache() { if (!$this->css_tracked) { return; } // Compare against cache file to avoid unnecessary file writing operations $cssTracked = $this->loadFromCache(); $isDifferent = true; if ($cssTracked !== false) { $theme = $this->getTheme(); if (isset($this->css_tracked[$theme])) { $isDifferent = $this->isCSSArrayDiff($cssTracked, $this->css_tracked[$theme]); } } if (!$isDifferent) { return; } foreach ($this->css_tracked as $theme => $cssTrackedFiles) { $cache = $this->getCache(); $cache->save(json_encode($cssTrackedFiles), self::CACHE_KEY_PREFIX.'_'.$theme.'_tracked'); } } /** * Get theme (or blank if in CMS / no theme) * * @return string */ protected function getTheme() { if (!Config::inst()->get('SSViewer', 'theme_enabled')) { return ''; } $result = (string)Config::inst()->get('SSViewer', 'theme'); return $result; } /** * Check if two CSS file lists are identical or not. * * @return boolean */ public function isCSSArrayDiff($arr1, $arr2) { $arr1Count = count($arr1); $arr2Count = count($arr2); if ($arr1Count !== $arr2Count) { return true; } foreach ($arr1 as $key => $value) { if (!array_key_exists($key, $arr2)) { return true; } $cssProperties = $arr1[$key]; $otherCssProperties = $arr2[$key]; foreach ($cssProperties as $property => $value) { if (!array_key_exists($property, $otherCssProperties)) { return true; } if ($cssProperties[$property] != $otherCssProperties[$property]) { return true; } } } return false; } } |