* @copyright Copyright (c) 2008, Net Perspective, LLC * @license http://www.opensource.org/licenses/mit-license.php MIT License * * @package VanishingPoint * @subpackage Modules */ class VP_CSS implements VP_Module { /** * Holds the object's file name (will be modified by a call to render) * * @var String */ private $file; /** * Holds the object's unaltered file name (the file name passed to the contructor) * * @var String */ private $unaltered; /** * Holds the object's version * * @var String */ private $version; /** * Holds the the value to be placed in title="" * * @var String */ private $title; /** * Holds the the value to be placed in media="" * * @var String */ private $media; /** * Holds the the content of the file * * @var String */ private $content; /** *W When true, this file will be combined with others in the group * * @var Boolean */ private $combine; /** * Holds the the path to be prepeneded to the file name when displayed in the browser * * @var String */ private static $HTTPPath; /** * Holds the the path to be prepended to the file name when accessing the file on the server * * @var String */ private static $serverPath; /** * Constant to define the extension for files in this module * * @var String */ const BASE_EXTENSION = ".css"; /** * Constant to define the extension for files in this module when combined (is followed by BASE_EXTENSION) * * @var String */ const COMBINED_EXTENSION = ".comb"; /** * $file and $version are the only required parameters for the constructor. * * * * * * @param String $file The name of the file to be optimized * @param String $version The version of the file [used for caching purposes] * @param Boolean $combine Sets whether the file should be combined with others from the same group (defaults to false) * @param String $title Holds the value to be placed in title="" (defaults to empty) * @param String $media Holds the value to be placed in media="" (defaults to empty) */ public function __construct($file, $version, $combine=false, $title="", $media="") { $this->file = $this->unaltered = $file; $this->version = $version; $this->title = $title; $this->media = $media; $this->combine = $combine; } /** * setServerPath() accepts a string to use as the prepended path for files to be accessed serverside * * @param String $path The path to precede filenames for php to be able to access the file */ public static function setServerPath($path) { self::$serverPath = $path; } /** * setHTTPPath() accepts a string to use as the prepended path for files to be accessed clientside * * @param String $path The path to precede filenames for clients (such as browsers) to be able to access the file */ public static function setHTPPPath($path) { self::$HTTPPath = $path; } /** * render() runs the CSSTidy CSS minifier along with custom regular expressions to remove * comments and uses css includes ( @include url ) to combine multiple CSS files into a single * file. render() accepts a single parameter, however; this is only so that combine() can share * code with render(). * * @param Boolean $combine Defaults to false */ public function render($combine=false) { $this->load(); //Here is where I do my minifying since csstidy didn't do everything I wanted //Order is VERY imporant here... don't modify these //remove multi-line $this->contents = preg_replace("~/\*.*?\*/~","",$this->contents); $regex = "/@import url\(\"(.*?)\"\);/is"; $tmp = array(); preg_match_all($regex,$this->contents, $tmp); for($a=0; $acontents = preg_replace('~'.preg_quote($tmp[0][$a]).'~',file_get_contents(self::$serverPath.$tmp[1][$a]),$this->contents); else if(file_exists('./'.$tmp[1][$a])) $this->contents = preg_replace('~'.preg_quote($tmp[0][$a]).'~',file_get_contents('./'.$tmp[1][$a]),$this->contents); } //remove multi-line $this->contents = preg_replace("~/\*.*?\*/~","",$this->contents); preg_match_all($regex,$this->contents, $tmp); for($a=0; $acontents = preg_replace('~'.preg_quote($tmp[0][$a]).'~',file_get_contents(self::$serverPath.$tmp[1][$a]),$this->contents); else if(file_exists('./'.$tmp[1][$a])) $this->contents = preg_replace('~'.preg_quote($tmp[0][$a]).'~',file_get_contents('./'.$tmp[1][$a]),$this->contents); } require_once("csstidy/class.csstidy.php"); $css = new csstidy(); $css->set_cfg('preserve_css',true); $css->parse($this->contents); $this->contents = $css->print->plain(); //Here is where I do my minifying since csstidy didn't do everything I wanted //Order is VERY imporant here... don't modify these //remove single line comments $this->contents = preg_replace("~//(.*)~","",$this->contents); //remove new lines and carrige returns $this->contents = preg_replace("/[\n\r]/","",$this->contents); //remove multi-line comments $this->contents = preg_replace("~/\*.*?\*/~","",$this->contents); $this->file = preg_replace("/\.css/",".v".$this->version.self::BASE_EXTENSION,$this->file); if(!$combine) { $this->save(); $this->clean(); } return ''; } /** * Returns the unaltered file name. */ public function getFileName() { return self::$HTTPPath.$this->unaltered; } /** * combine() takes a group of files and makes a single file out of them to optimize HTTP Request times. * * $files is the array of VP_CSS objects passed into combine() to be combined into a single file. This is * handled completely by the Vanishing Point framework. * * &$rendered is a pointer to the array of already rendered files. combine() adds the unaltered file names to this * when it adds a file to the combination file so as to keep it from being repeated as a packed or regular javascript * file. * * @param Array $files an array of VP_CSS objects * @param Array &$rendered an array of strings passed by reference */ public static function combine(Array $files, Array &$rendered ) { $names = array(); $contents = ""; $regex = ""; foreach($files as $file) { if($file->combine) { //$file->render(true); $rendered[] = $file->getFileName(); $names[] = preg_replace("/".self::BASE_EXTENSION."/",".v".$file->version,$file->unaltered); $regexes[] = preg_replace("/".self::BASE_EXTENSION."/",".v.*",$file->unaltered); //$contents .= $file->contents; } } $file = ""; $size = count($names)-1; $regex = "=". implode("_",$regexes).".comb.css" . "="; $file = implode("_",$names); $file .= self::COMBINED_EXTENSION.self::BASE_EXTENSION; if(!file_exists(self::$serverPath.$file)) { foreach($files as $f) { if($f->combine) { $f->render(true); $contents .= "\n". $f->contents; } } if(!file_put_contents(self::$serverPath.$file,$contents)) { throw new Exception("Could not write to file: " . self::$serverPath.file); } } foreach( new DirectoryIterator( self::$serverPath ) as $f ) { $fileDir = $f->getPath(); $fileName = $f->getFilename(); if( $f->isFile() ) { if(preg_match($regex,$fileDir."/".$fileName) > 0 && $fileDir."/".$fileName != $file && $fileName != $file) { //remove any file that matches @unlink($fileDir . "/" . $fileName); } } } return ''; } /** * Returns the include with the unaltered file, for debugging purposes. */ public function debug() { return ''; } /** * Checks for existance of the file and loads the contents into a variable. Throws an exception if the file does not exist. */ private function load() { if( file_exists(self::$serverPath.$this->file) ) { //get the contents $this->contents = file_get_contents(self::$serverPath.$this->file); return true; } else { throw new Exception("File not found ('".self::$serverPath.$this->file."')"); } } /** * Puts the data from the contents variable into a file. Throws an exception if this fails. */ private function save() { //write the file and throw an exception if we have an issue if( !file_put_contents(self::$serverPath.$this->file,$this->contents) ) { throw new Exception("Cannot save file ('".$this->file."')"); } } /** * Removes old versions of files that have been versioned. Any non-versioned file will be omited due to inability to * resolve if it is old or not. */ private function clean() { //get original info $path = dirname(self::$serverPath.$this->file); $file = basename($this->unaltered, self::BASE_EXTENSION); //build regular expressions based on what has been done to the file $regex = "=" . preg_replace("/\./","\.", $file . ".v") . ".*"; $file .= ".v" . $this->version; $regex .= self::BASE_EXTENSION . '='; $file .= self::BASE_EXTENSION; //iterate through the directory and search for possible matches to remove. foreach( new DirectoryIterator( $path ) as $f ) { $fileDir = $f->getPath(); $fileName = $f->getFilename(); if( $f->isFile() ) { //check to see if the file is versioned and should be removed if(preg_match($regex,$fileDir."/".$fileName) > 0 && $fileDir."/".$fileName != $file && preg_match("/".$this->version."/", $fileName) == 0) { //remove any file that matches @unlink($fileDir . "/" . $fileName); } } } } } ?>