217 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			217 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| /**
 | |
|  * Class Minify_ImportProcessor
 | |
|  * @package Minify
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * Linearize a CSS/JS file by including content specified by CSS import
 | |
|  * declarations. In CSS files, relative URIs are fixed.
 | |
|  *
 | |
|  * @imports will be processed regardless of where they appear in the source
 | |
|  * files; i.e. @imports commented out or in string content will still be
 | |
|  * processed!
 | |
|  *
 | |
|  * This has a unit test but should be considered "experimental".
 | |
|  *
 | |
|  * @package Minify
 | |
|  * @author Stephen Clay <steve@mrclay.org>
 | |
|  * @author Simon Schick <simonsimcity@gmail.com>
 | |
|  */
 | |
| class Minify_ImportProcessor {
 | |
| 
 | |
|     public static $filesIncluded = array();
 | |
| 
 | |
|     public static function process($file)
 | |
|     {
 | |
|         self::$filesIncluded = array();
 | |
|         self::$_isCss = (strtolower(substr($file, -4)) === '.css');
 | |
|         $obj = new Minify_ImportProcessor(dirname($file));
 | |
|         return $obj->_getContent($file);
 | |
|     }
 | |
| 
 | |
|     // allows callback funcs to know the current directory
 | |
|     private $_currentDir = null;
 | |
| 
 | |
|     // allows callback funcs to know the directory of the file that inherits this one
 | |
|     private $_previewsDir = null;
 | |
| 
 | |
|     // allows _importCB to write the fetched content back to the obj
 | |
|     private $_importedContent = '';
 | |
| 
 | |
|     private static $_isCss = null;
 | |
| 
 | |
|     /**
 | |
|      * @param String $currentDir
 | |
|      * @param String $previewsDir Is only used internally
 | |
|      */
 | |
|     private function __construct($currentDir, $previewsDir = "")
 | |
|     {
 | |
|         $this->_currentDir = $currentDir;
 | |
|         $this->_previewsDir = $previewsDir;
 | |
|     }
 | |
| 
 | |
|     private function _getContent($file, $is_imported = false)
 | |
|     {
 | |
|         $file = realpath($file);
 | |
|         if (! $file
 | |
|             || in_array($file, self::$filesIncluded)
 | |
|             || false === ($content = @file_get_contents($file))
 | |
|         ) {
 | |
|             // file missing, already included, or failed read
 | |
|             return '';
 | |
|         }
 | |
|         self::$filesIncluded[] = realpath($file);
 | |
|         $this->_currentDir = dirname($file);
 | |
| 
 | |
|         // remove UTF-8 BOM if present
 | |
|         if (pack("CCC",0xef,0xbb,0xbf) === substr($content, 0, 3)) {
 | |
|             $content = substr($content, 3);
 | |
|         }
 | |
|         // ensure uniform EOLs
 | |
|         $content = str_replace("\r\n", "\n", $content);
 | |
| 
 | |
|         // process @imports
 | |
|         $content = preg_replace_callback(
 | |
|             '/
 | |
|                 @import\\s+
 | |
|                 (?:url\\(\\s*)?      # maybe url(
 | |
|                 [\'"]?               # maybe quote
 | |
|                 (.*?)                # 1 = URI
 | |
|                 [\'"]?               # maybe end quote
 | |
|                 (?:\\s*\\))?         # maybe )
 | |
|                 ([a-zA-Z,\\s]*)?     # 2 = media list
 | |
|                 ;                    # end token
 | |
|             /x'
 | |
|             ,array($this, '_importCB')
 | |
|             ,$content
 | |
|         );
 | |
| 
 | |
|         // You only need to rework the import-path if the script is imported
 | |
|         if (self::$_isCss && $is_imported) {
 | |
|             // rewrite remaining relative URIs
 | |
|             $content = preg_replace_callback(
 | |
|                 '/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
 | |
|                 ,array($this, '_urlCB')
 | |
|                 ,$content
 | |
|             );
 | |
|         }
 | |
| 
 | |
|         return $this->_importedContent . $content;
 | |
|     }
 | |
| 
 | |
|     private function _importCB($m)
 | |
|     {
 | |
|         $url = $m[1];
 | |
|         $mediaList = preg_replace('/\\s+/', '', $m[2]);
 | |
| 
 | |
|         if (strpos($url, '://') > 0) {
 | |
|             // protocol, leave in place for CSS, comment for JS
 | |
|             return self::$_isCss
 | |
|                 ? $m[0]
 | |
|                 : "/* Minify_ImportProcessor will not include remote content */";
 | |
|         }
 | |
|         if ('/' === $url[0]) {
 | |
|             // protocol-relative or root path
 | |
|             $url = ltrim($url, '/');
 | |
|             $file = realpath($_SERVER['DOCUMENT_ROOT']) . DIRECTORY_SEPARATOR
 | |
|                 . strtr($url, '/', DIRECTORY_SEPARATOR);
 | |
|         } else {
 | |
|             // relative to current path
 | |
|             $file = $this->_currentDir . DIRECTORY_SEPARATOR
 | |
|                 . strtr($url, '/', DIRECTORY_SEPARATOR);
 | |
|         }
 | |
|         $obj = new Minify_ImportProcessor(dirname($file), $this->_currentDir);
 | |
|         $content = $obj->_getContent($file, true);
 | |
|         if ('' === $content) {
 | |
|             // failed. leave in place for CSS, comment for JS
 | |
|             return self::$_isCss
 | |
|                 ? $m[0]
 | |
|                 : "/* Minify_ImportProcessor could not fetch '{$file}' */";
 | |
|         }
 | |
|         return (!self::$_isCss || preg_match('@(?:^$|\\ball\\b)@', $mediaList))
 | |
|             ? $content
 | |
|             : "@media {$mediaList} {\n{$content}\n}\n";
 | |
|     }
 | |
| 
 | |
|     private function _urlCB($m)
 | |
|     {
 | |
|         // $m[1] is either quoted or not
 | |
|         $quote = ($m[1][0] === "'" || $m[1][0] === '"')
 | |
|             ? $m[1][0]
 | |
|             : '';
 | |
|         $url = ($quote === '')
 | |
|             ? $m[1]
 | |
|             : substr($m[1], 1, strlen($m[1]) - 2);
 | |
|         if ('/' !== $url[0]) {
 | |
|             if (strpos($url, '//') > 0) {
 | |
|                 // probably starts with protocol, do not alter
 | |
|             } else {
 | |
|                 // prepend path with current dir separator (OS-independent)
 | |
|                 $path = $this->_currentDir
 | |
|                     . DIRECTORY_SEPARATOR . strtr($url, '/', DIRECTORY_SEPARATOR);
 | |
|                 // update the relative path by the directory of the file that imported this one
 | |
|                 $url = self::getPathDiff(realpath($this->_previewsDir), $path);
 | |
|             }
 | |
|         }
 | |
|         return "url({$quote}{$url}{$quote})";
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @param string $from
 | |
|      * @param string $to
 | |
|      * @param string $ps
 | |
|      * @return string
 | |
|      */
 | |
|     private function getPathDiff($from, $to, $ps = DIRECTORY_SEPARATOR)
 | |
|     {
 | |
|         $realFrom = $this->truepath($from);
 | |
|         $realTo = $this->truepath($to);
 | |
| 
 | |
|         $arFrom = explode($ps, rtrim($realFrom, $ps));
 | |
|         $arTo = explode($ps, rtrim($realTo, $ps));
 | |
|         while (count($arFrom) && count($arTo) && ($arFrom[0] == $arTo[0]))
 | |
|         {
 | |
|             array_shift($arFrom);
 | |
|             array_shift($arTo);
 | |
|         }
 | |
|         return str_pad("", count($arFrom) * 3, '..' . $ps) . implode($ps, $arTo);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * This function is to replace PHP's extremely buggy realpath().
 | |
|      * @param string $path The original path, can be relative etc.
 | |
|      * @return string The resolved path, it might not exist.
 | |
|      * @see http://stackoverflow.com/questions/4049856/replace-phps-realpath
 | |
|      */
 | |
|     function truepath($path)
 | |
|     {
 | |
|         // whether $path is unix or not
 | |
|         $unipath = strlen($path) == 0 || $path{0} != '/';
 | |
|         // attempts to detect if path is relative in which case, add cwd
 | |
|         if (strpos($path, ':') === false && $unipath)
 | |
|             $path = $this->_currentDir . DIRECTORY_SEPARATOR . $path;
 | |
| 
 | |
|         // resolve path parts (single dot, double dot and double delimiters)
 | |
|         $path = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $path);
 | |
|         $parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen');
 | |
|         $absolutes = array();
 | |
|         foreach ($parts as $part) {
 | |
|             if ('.' == $part)
 | |
|                 continue;
 | |
|             if ('..' == $part) {
 | |
|                 array_pop($absolutes);
 | |
|             } else {
 | |
|                 $absolutes[] = $part;
 | |
|             }
 | |
|         }
 | |
|         $path = implode(DIRECTORY_SEPARATOR, $absolutes);
 | |
|         // resolve any symlinks
 | |
|         if (file_exists($path) && linkinfo($path) > 0)
 | |
|             $path = readlink($path);
 | |
|         // put initial separator that could have been lost
 | |
|         $path = !$unipath ? '/' . $path : $path;
 | |
|         return $path;
 | |
|     }
 | |
| }
 | 
