Version 3.4.5
This commit is contained in:
1 parent
fb6b5baabc
commit
e4478369d8
1360 files changed
+228921
-179445
No files matched your search
@@ -4,7 +4,7 @@
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
require_once 'Minify/Source.php';/*noLibHook*/
|
||||
require_once 'Minify/Source.php';
|
||||
|
||||
/**
|
||||
* Maintain a single last modification time for a group of Minify sources to
|
||||
|
||||
@@ -26,6 +26,8 @@ class Minify_CSS {
|
||||
* 'preserveComments': (default true) multi-line comments that begin
|
||||
* with "/*!" will be preserved with newlines before and after to
|
||||
* enhance readability.
|
||||
*
|
||||
* 'removeCharsets': (default true) remove all @charset at-rules
|
||||
*
|
||||
* 'prependRelativePath': (default null) if given, this string will be
|
||||
* prepended to all relative URIs in import/url declarations
|
||||
@@ -36,42 +38,56 @@ class Minify_CSS {
|
||||
* the desired files. For this to work, the files *must* exist and be
|
||||
* visible by the PHP process.
|
||||
*
|
||||
* 'symlinks': (default = array()) If the CSS file is stored in
|
||||
* a symlink-ed directory, provide an array of link paths to
|
||||
* target paths, where the link paths are within the document root. Because
|
||||
* paths need to be normalized for this to work, use "//" to substitute
|
||||
* the doc root in the link paths (the array keys). E.g.:
|
||||
* <code>
|
||||
* array('//symlink' => '/real/target/path') // unix
|
||||
* array('//static' => 'D:\\staticStorage') // Windows
|
||||
* 'symlinks': (default = array()) If the CSS file is stored in
|
||||
* a symlink-ed directory, provide an array of link paths to
|
||||
* target paths, where the link paths are within the document root. Because
|
||||
* paths need to be normalized for this to work, use "//" to substitute
|
||||
* the doc root in the link paths (the array keys). E.g.:
|
||||
* <code>
|
||||
* array('//symlink' => '/real/target/path') // unix
|
||||
* array('//static' => 'D:\\staticStorage') // Windows
|
||||
* </code>
|
||||
*
|
||||
* 'docRoot': (default = $_SERVER['DOCUMENT_ROOT'])
|
||||
* see Minify_CSS_UriRewriter::rewrite
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function minify($css, $options = array())
|
||||
{
|
||||
require_once 'Minify/CSS/Compressor.php';/*noLibHook*/
|
||||
if (isset($options['preserveComments'])
|
||||
&& !$options['preserveComments']) {
|
||||
$options = array_merge(array(
|
||||
'removeCharsets' => true,
|
||||
'preserveComments' => true,
|
||||
'currentDir' => null,
|
||||
'docRoot' => $_SERVER['DOCUMENT_ROOT'],
|
||||
'prependRelativePath' => null,
|
||||
'symlinks' => array(),
|
||||
), $options);
|
||||
|
||||
if ($options['removeCharsets']) {
|
||||
$css = preg_replace('/@charset[^;]+;\\s*/', '', $css);
|
||||
}
|
||||
require_once 'Minify/CSS/Compressor.php';
|
||||
if (! $options['preserveComments']) {
|
||||
$css = Minify_CSS_Compressor::process($css, $options);
|
||||
} else {
|
||||
require_once 'Minify/CommentPreserver.php';/*noLibHook*/
|
||||
require_once 'Minify/CommentPreserver.php';
|
||||
$css = Minify_CommentPreserver::process(
|
||||
$css
|
||||
,array('Minify_CSS_Compressor', 'process')
|
||||
,array($options)
|
||||
);
|
||||
}
|
||||
if (! isset($options['currentDir']) && ! isset($options['prependRelativePath'])) {
|
||||
if (! $options['currentDir'] && ! $options['prependRelativePath']) {
|
||||
return $css;
|
||||
}
|
||||
require_once 'Minify/CSS/UriRewriter.php';/*noLibHook*/
|
||||
if (isset($options['currentDir'])) {
|
||||
require_once 'Minify/CSS/UriRewriter.php';
|
||||
if ($options['currentDir']) {
|
||||
return Minify_CSS_UriRewriter::rewrite(
|
||||
$css
|
||||
,$options['currentDir']
|
||||
,isset($options['docRoot']) ? $options['docRoot'] : $_SERVER['DOCUMENT_ROOT']
|
||||
,isset($options['symlinks']) ? $options['symlinks'] : array()
|
||||
,$options['docRoot']
|
||||
,$options['symlinks']
|
||||
);
|
||||
} else {
|
||||
return Minify_CSS_UriRewriter::prepend(
|
||||
|
||||
@@ -36,14 +36,14 @@ class Minify_CSS_Compressor {
|
||||
}
|
||||
|
||||
/**
|
||||
* @var array options
|
||||
* @var array
|
||||
*/
|
||||
protected $_options = null;
|
||||
|
||||
/**
|
||||
* @var bool Are we "in" a hack?
|
||||
*
|
||||
* I.e. are some browsers targetted until the next comment?
|
||||
* Are we "in" a hack? I.e. are some browsers targetted until the next comment?
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $_inHack = false;
|
||||
|
||||
@@ -52,8 +52,6 @@ class Minify_CSS_Compressor {
|
||||
* Constructor
|
||||
*
|
||||
* @param array $options (currently ignored)
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
private function __construct($options) {
|
||||
$this->_options = $options;
|
||||
@@ -108,7 +106,7 @@ class Minify_CSS_Compressor {
|
||||
\\s*
|
||||
:
|
||||
\\s*
|
||||
(\\b|[#\'"]) # 3 = first character of a value
|
||||
(\\b|[#\'"-]) # 3 = first character of a value
|
||||
/x', '$1$2:$3', $css);
|
||||
|
||||
// remove ws in selectors
|
||||
@@ -236,15 +234,16 @@ class Minify_CSS_Compressor {
|
||||
*/
|
||||
protected function _fontFamilyCB($m)
|
||||
{
|
||||
$m[1] = preg_replace('/
|
||||
\\s*
|
||||
(
|
||||
"[^"]+" # 1 = family in double qutoes
|
||||
|\'[^\']+\' # or 1 = family in single quotes
|
||||
|[\\w\\-]+ # or 1 = unquoted family
|
||||
)
|
||||
\\s*
|
||||
/x', '$1', $m[1]);
|
||||
return 'font-family:' . $m[1] . $m[2];
|
||||
// Issue 210: must not eliminate WS between words in unquoted families
|
||||
$pieces = preg_split('/(\'[^\']+\'|"[^"]+")/', $m[1], null, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
|
||||
$out = 'font-family:';
|
||||
while (null !== ($piece = array_shift($pieces))) {
|
||||
if ($piece[0] !== '"' && $piece[0] !== "'") {
|
||||
$piece = preg_replace('/\\s+/', ' ', $piece);
|
||||
$piece = preg_replace('/\\s?,\\s?/', ',', $piece);
|
||||
}
|
||||
$out .= $piece;
|
||||
}
|
||||
return $out . $m[2];
|
||||
}
|
||||
}
|
||||
@@ -12,21 +12,15 @@
|
||||
*/
|
||||
class Minify_CSS_UriRewriter {
|
||||
|
||||
/**
|
||||
* Defines which class to call as part of callbacks, change this
|
||||
* if you extend Minify_CSS_UriRewriter
|
||||
* @var string
|
||||
*/
|
||||
protected static $className = 'Minify_CSS_UriRewriter';
|
||||
|
||||
/**
|
||||
* rewrite() and rewriteRelative() append debugging information here
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $debugText = '';
|
||||
|
||||
/**
|
||||
* Rewrite file relative URIs as root relative in CSS files
|
||||
* In CSS content, rewrite file relative URIs as root relative
|
||||
*
|
||||
* @param string $css
|
||||
*
|
||||
@@ -83,7 +77,7 @@ class Minify_CSS_UriRewriter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepend a path to relative URIs in CSS files
|
||||
* In CSS content, prepend a path to relative URIs
|
||||
*
|
||||
* @param string $css
|
||||
*
|
||||
@@ -107,73 +101,8 @@ class Minify_CSS_UriRewriter {
|
||||
return $css;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @var string directory of this stylesheet
|
||||
*/
|
||||
private static $_currentDir = '';
|
||||
|
||||
/**
|
||||
* @var string DOC_ROOT
|
||||
*/
|
||||
private static $_docRoot = '';
|
||||
|
||||
/**
|
||||
* @var array directory replacements to map symlink targets back to their
|
||||
* source (within the document root) E.g. '/var/www/symlink' => '/var/realpath'
|
||||
*/
|
||||
private static $_symlinks = array();
|
||||
|
||||
/**
|
||||
* @var string path to prepend
|
||||
*/
|
||||
private static $_prependPath = null;
|
||||
|
||||
private static function _trimUrls($css)
|
||||
{
|
||||
return preg_replace('/
|
||||
url\\( # url(
|
||||
\\s*
|
||||
([^\\)]+?) # 1 = URI (assuming does not contain ")")
|
||||
\\s*
|
||||
\\) # )
|
||||
/x', 'url($1)', $css);
|
||||
}
|
||||
|
||||
private static function _processUriCB($m)
|
||||
{
|
||||
// $m matched either '/@import\\s+([\'"])(.*?)[\'"]/' or '/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
|
||||
$isImport = ($m[0][0] === '@');
|
||||
// determine URI and the quote character (if any)
|
||||
if ($isImport) {
|
||||
$quoteChar = $m[1];
|
||||
$uri = $m[2];
|
||||
} else {
|
||||
// $m[1] is either quoted or not
|
||||
$quoteChar = ($m[1][0] === "'" || $m[1][0] === '"')
|
||||
? $m[1][0]
|
||||
: '';
|
||||
$uri = ($quoteChar === '')
|
||||
? $m[1]
|
||||
: substr($m[1], 1, strlen($m[1]) - 2);
|
||||
}
|
||||
// analyze URI
|
||||
if ('/' !== $uri[0] // root-relative
|
||||
&& false === strpos($uri, '//') // protocol (non-data)
|
||||
&& 0 !== strpos($uri, 'data:') // data protocol
|
||||
) {
|
||||
// URI is file-relative: rewrite depending on options
|
||||
$uri = (self::$_prependPath !== null)
|
||||
? (self::$_prependPath . $uri)
|
||||
: self::rewriteRelative($uri, self::$_currentDir, self::$_docRoot, self::$_symlinks);
|
||||
}
|
||||
return $isImport
|
||||
? "@import {$quoteChar}{$uri}{$quoteChar}"
|
||||
: "url({$quoteChar}{$uri}{$quoteChar})";
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrite a file relative URI as root relative
|
||||
* Get a root relative URI from a file relative URI
|
||||
*
|
||||
* <code>
|
||||
* Minify_CSS_UriRewriter::rewriteRelative(
|
||||
@@ -219,16 +148,16 @@ class Minify_CSS_UriRewriter {
|
||||
self::$debugText .= "file-relative URI : {$uri}\n"
|
||||
. "path prepended : {$path}\n";
|
||||
|
||||
// "unresolve" a symlink back to doc root
|
||||
foreach ($symlinks as $link => $target) {
|
||||
if (0 === strpos($path, $target)) {
|
||||
// replace $target with $link
|
||||
// "unresolve" a symlink back to doc root
|
||||
foreach ($symlinks as $link => $target) {
|
||||
if (0 === strpos($path, $target)) {
|
||||
// replace $target with $link
|
||||
$path = $link . substr($path, strlen($target));
|
||||
|
||||
self::$debugText .= "symlink unresolved : {$path}\n";
|
||||
self::$debugText .= "symlink unresolved : {$path}\n";
|
||||
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
// strip doc root
|
||||
$path = substr($path, strlen($realDocRoot));
|
||||
@@ -236,21 +165,39 @@ class Minify_CSS_UriRewriter {
|
||||
self::$debugText .= "docroot stripped : {$path}\n";
|
||||
|
||||
// fix to root-relative URI
|
||||
|
||||
$uri = strtr($path, '/\\', '//');
|
||||
|
||||
// remove /./ and /../ where possible
|
||||
$uri = str_replace('/./', '/', $uri);
|
||||
// inspired by patch from Oleg Cherniy
|
||||
do {
|
||||
$uri = preg_replace('@/[^/]+/\\.\\./@', '/', $uri, 1, $changed);
|
||||
} while ($changed);
|
||||
$uri = self::removeDots($uri);
|
||||
|
||||
self::$debugText .= "traversals removed : {$uri}\n\n";
|
||||
|
||||
return $uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove instances of "./" and "../" where possible from a root-relative URI
|
||||
*
|
||||
* @param string $uri
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function removeDots($uri)
|
||||
{
|
||||
$uri = str_replace('/./', '/', $uri);
|
||||
// inspired by patch from Oleg Cherniy
|
||||
do {
|
||||
$uri = preg_replace('@/[^/]+/\\.\\./@', '/', $uri, 1, $changed);
|
||||
} while ($changed);
|
||||
return $uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines which class to call as part of callbacks, change this
|
||||
* if you extend Minify_CSS_UriRewriter
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $className = 'Minify_CSS_UriRewriter';
|
||||
|
||||
/**
|
||||
* Get realpath with any trailing slash removed. If realpath() fails,
|
||||
* just remove the trailing slash.
|
||||
@@ -267,4 +214,97 @@ class Minify_CSS_UriRewriter {
|
||||
}
|
||||
return rtrim($path, '/\\');
|
||||
}
|
||||
|
||||
/**
|
||||
* Directory of this stylesheet
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private static $_currentDir = '';
|
||||
|
||||
/**
|
||||
* DOC_ROOT
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private static $_docRoot = '';
|
||||
|
||||
/**
|
||||
* directory replacements to map symlink targets back to their
|
||||
* source (within the document root) E.g. '/var/www/symlink' => '/var/realpath'
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $_symlinks = array();
|
||||
|
||||
/**
|
||||
* Path to prepend
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private static $_prependPath = null;
|
||||
|
||||
/**
|
||||
* @param string $css
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function _trimUrls($css)
|
||||
{
|
||||
return preg_replace('/
|
||||
url\\( # url(
|
||||
\\s*
|
||||
([^\\)]+?) # 1 = URI (assuming does not contain ")")
|
||||
\\s*
|
||||
\\) # )
|
||||
/x', 'url($1)', $css);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $m
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function _processUriCB($m)
|
||||
{
|
||||
// $m matched either '/@import\\s+([\'"])(.*?)[\'"]/' or '/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
|
||||
$isImport = ($m[0][0] === '@');
|
||||
// determine URI and the quote character (if any)
|
||||
if ($isImport) {
|
||||
$quoteChar = $m[1];
|
||||
$uri = $m[2];
|
||||
} else {
|
||||
// $m[1] is either quoted or not
|
||||
$quoteChar = ($m[1][0] === "'" || $m[1][0] === '"')
|
||||
? $m[1][0]
|
||||
: '';
|
||||
$uri = ($quoteChar === '')
|
||||
? $m[1]
|
||||
: substr($m[1], 1, strlen($m[1]) - 2);
|
||||
}
|
||||
// analyze URI
|
||||
if ('/' !== $uri[0] // root-relative
|
||||
&& false === strpos($uri, '//') // protocol (non-data)
|
||||
&& 0 !== strpos($uri, 'data:') // data protocol
|
||||
) {
|
||||
// URI is file-relative: rewrite depending on options
|
||||
if (self::$_prependPath === null) {
|
||||
$uri = self::rewriteRelative($uri, self::$_currentDir, self::$_docRoot, self::$_symlinks);
|
||||
} else {
|
||||
$uri = self::$_prependPath . $uri;
|
||||
if ($uri[0] === '/') {
|
||||
$root = '';
|
||||
$rootRelative = $uri;
|
||||
$uri = $root . self::removeDots($rootRelative);
|
||||
} elseif (preg_match('@^((https?\:)?//([^/]+))/@', $uri, $m) && (false !== strpos($m[3], '.'))) {
|
||||
$root = $m[1];
|
||||
$rootRelative = substr($uri, strlen($root));
|
||||
$uri = $root . self::removeDots($rootRelative);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $isImport
|
||||
? "@import {$quoteChar}{$uri}{$quoteChar}"
|
||||
: "url({$quoteChar}{$uri}{$quoteChar})";
|
||||
}
|
||||
}
|
||||
@@ -54,9 +54,12 @@ class Minify_Cache_APC {
|
||||
*/
|
||||
public function getSize($id)
|
||||
{
|
||||
return $this->_fetch($id)
|
||||
? strlen($this->_data)
|
||||
: false;
|
||||
if (! $this->_fetch($id)) {
|
||||
return false;
|
||||
}
|
||||
return (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2))
|
||||
? mb_strlen($this->_data, '8bit')
|
||||
: strlen($this->_data);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,13 +9,12 @@ class Minify_Cache_File {
|
||||
public function __construct($path = '', $fileLocking = false)
|
||||
{
|
||||
if (! $path) {
|
||||
require_once 'Solar/Dir.php';/*noLibHook*/
|
||||
$path = rtrim(Solar_Dir::tmp(), DIRECTORY_SEPARATOR);
|
||||
}
|
||||
$path = self::tmp();
|
||||
}
|
||||
$this->_locking = $fileLocking;
|
||||
$this->_path = $path;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Write data to cache.
|
||||
*
|
||||
@@ -27,20 +26,19 @@ class Minify_Cache_File {
|
||||
*/
|
||||
public function store($id, $data)
|
||||
{
|
||||
$flag = $this->_locking
|
||||
? LOCK_EX
|
||||
: null;
|
||||
if (is_file($this->_path . '/' . $id)) {
|
||||
@unlink($this->_path . '/' . $id);
|
||||
}
|
||||
if (! @file_put_contents($this->_path . '/' . $id, $data, $flag)) {
|
||||
return false;
|
||||
$flag = $this->_locking
|
||||
? LOCK_EX
|
||||
: null;
|
||||
$file = $this->_path . '/' . $id;
|
||||
if (! @file_put_contents($file, $data, $flag)) {
|
||||
$this->_log("Minify_Cache_File: Write failed to '$file'");
|
||||
}
|
||||
// write control
|
||||
if ($data !== $this->fetch($id)) {
|
||||
@unlink($file);
|
||||
$this->_log("Minify_Cache_File: Post-write read failed for '$file'");
|
||||
return false;
|
||||
}
|
||||
// write control
|
||||
if ($data !== $this->fetch($id)) {
|
||||
@unlink($file);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -78,15 +76,15 @@ class Minify_Cache_File {
|
||||
*/
|
||||
public function display($id)
|
||||
{
|
||||
if ($this->_locking) {
|
||||
$fp = fopen($this->_path . '/' . $id, 'rb');
|
||||
flock($fp, LOCK_SH);
|
||||
fpassthru($fp);
|
||||
flock($fp, LOCK_UN);
|
||||
fclose($fp);
|
||||
} else {
|
||||
readfile($this->_path . '/' . $id);
|
||||
}
|
||||
if ($this->_locking) {
|
||||
$fp = fopen($this->_path . '/' . $id, 'rb');
|
||||
flock($fp, LOCK_SH);
|
||||
fpassthru($fp);
|
||||
flock($fp, LOCK_UN);
|
||||
fclose($fp);
|
||||
} else {
|
||||
readfile($this->_path . '/' . $id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,15 +96,15 @@ class Minify_Cache_File {
|
||||
*/
|
||||
public function fetch($id)
|
||||
{
|
||||
if ($this->_locking) {
|
||||
$fp = fopen($this->_path . '/' . $id, 'rb');
|
||||
flock($fp, LOCK_SH);
|
||||
$ret = stream_get_contents($fp);
|
||||
flock($fp, LOCK_UN);
|
||||
fclose($fp);
|
||||
return $ret;
|
||||
} else {
|
||||
return file_get_contents($this->_path . '/' . $id);
|
||||
if ($this->_locking) {
|
||||
$fp = fopen($this->_path . '/' . $id, 'rb');
|
||||
flock($fp, LOCK_SH);
|
||||
$ret = stream_get_contents($fp);
|
||||
flock($fp, LOCK_UN);
|
||||
fclose($fp);
|
||||
return $ret;
|
||||
} else {
|
||||
return file_get_contents($this->_path . '/' . $id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +117,79 @@ class Minify_Cache_File {
|
||||
{
|
||||
return $this->_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a usable temp directory
|
||||
*
|
||||
* Adapted from Solar/Dir.php
|
||||
* @author Paul M. Jones <pmjones@solarphp.com>
|
||||
* @license http://opensource.org/licenses/bsd-license.php BSD
|
||||
* @link http://solarphp.com/trac/core/browser/trunk/Solar/Dir.php
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function tmp()
|
||||
{
|
||||
static $tmp = null;
|
||||
if (! $tmp) {
|
||||
$tmp = function_exists('sys_get_temp_dir')
|
||||
? sys_get_temp_dir()
|
||||
: self::_tmp();
|
||||
$tmp = rtrim($tmp, DIRECTORY_SEPARATOR);
|
||||
}
|
||||
return $tmp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the OS-specific directory for temporary files
|
||||
*
|
||||
* @author Paul M. Jones <pmjones@solarphp.com>
|
||||
* @license http://opensource.org/licenses/bsd-license.php BSD
|
||||
* @link http://solarphp.com/trac/core/browser/trunk/Solar/Dir.php
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function _tmp()
|
||||
{
|
||||
// non-Windows system?
|
||||
if (strtolower(substr(PHP_OS, 0, 3)) != 'win') {
|
||||
$tmp = empty($_ENV['TMPDIR']) ? getenv('TMPDIR') : $_ENV['TMPDIR'];
|
||||
if ($tmp) {
|
||||
return $tmp;
|
||||
} else {
|
||||
return '/tmp';
|
||||
}
|
||||
}
|
||||
// Windows 'TEMP'
|
||||
$tmp = empty($_ENV['TEMP']) ? getenv('TEMP') : $_ENV['TEMP'];
|
||||
if ($tmp) {
|
||||
return $tmp;
|
||||
}
|
||||
// Windows 'TMP'
|
||||
$tmp = empty($_ENV['TMP']) ? getenv('TMP') : $_ENV['TMP'];
|
||||
if ($tmp) {
|
||||
return $tmp;
|
||||
}
|
||||
// Windows 'windir'
|
||||
$tmp = empty($_ENV['windir']) ? getenv('windir') : $_ENV['windir'];
|
||||
if ($tmp) {
|
||||
return $tmp;
|
||||
}
|
||||
// final fallback for Windows
|
||||
return getenv('SystemRoot') . '\\temp';
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message to the Minify logger
|
||||
* @param string $msg
|
||||
* @return null
|
||||
*/
|
||||
protected function _log($msg)
|
||||
{
|
||||
require_once 'Minify/Logger.php';
|
||||
Minify_Logger::log($msg);
|
||||
}
|
||||
|
||||
private $_path = null;
|
||||
private $_locking = null;
|
||||
private $_locking = null;
|
||||
}
|
||||
@@ -60,9 +60,12 @@ class Minify_Cache_Memcache {
|
||||
*/
|
||||
public function getSize($id)
|
||||
{
|
||||
return $this->_fetch($id)
|
||||
? strlen($this->_data)
|
||||
: false;
|
||||
if (! $this->_fetch($id)) {
|
||||
return false;
|
||||
}
|
||||
return (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2))
|
||||
? mb_strlen($this->_data, '8bit')
|
||||
: strlen($this->_data);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,8 +30,7 @@ class Minify_CommentPreserver {
|
||||
* Process a string outside of C-style comments that begin with "/*!"
|
||||
*
|
||||
* On each non-empty string outside these comments, the given processor
|
||||
* function will be called. The first "!" will be removed from the
|
||||
* preserved comments, and the comments will be surrounded by
|
||||
* function will be called. The comments will be surrounded by
|
||||
* Minify_CommentPreserver::$preprend and Minify_CommentPreserver::$append.
|
||||
*
|
||||
* @param string $content
|
||||
@@ -65,7 +64,7 @@ class Minify_CommentPreserver {
|
||||
* @param string $in input
|
||||
*
|
||||
* @return array 3 elements are returned. If a YUI comment is found, the
|
||||
* 2nd element is the comment and the 1st and 2nd are the surrounding
|
||||
* 2nd element is the comment and the 1st and 3rd are the surrounding
|
||||
* strings. If no comment is found, the entire string is returned as the
|
||||
* 1st element and the other two are false.
|
||||
*/
|
||||
@@ -79,7 +78,7 @@ class Minify_CommentPreserver {
|
||||
}
|
||||
$ret = array(
|
||||
substr($in, 0, $start)
|
||||
,self::$prepend . '/*' . substr($in, $start + 3, $end - $start - 1) . self::$append
|
||||
,self::$prepend . '/*!' . substr($in, $start + 3, $end - $start - 1) . self::$append
|
||||
);
|
||||
$endChars = (strlen($in) - $end - 2);
|
||||
$ret[] = (0 === $endChars)
|
||||
|
||||
@@ -27,7 +27,7 @@ abstract class Minify_Controller_Base {
|
||||
*
|
||||
* @param array $options controller and Minify options
|
||||
*
|
||||
* return array $options Minify::serve options
|
||||
* @return array $options Minify::serve options
|
||||
*/
|
||||
abstract public function setupSources($options);
|
||||
|
||||
@@ -52,15 +52,16 @@ abstract class Minify_Controller_Base {
|
||||
,'quiet' => false // serve() will send headers and output
|
||||
,'debug' => false
|
||||
|
||||
// if you override this, the response code MUST be directly after
|
||||
// if you override these, the response codes MUST be directly after
|
||||
// the first space.
|
||||
,'badRequestHeader' => 'HTTP/1.0 400 Bad Request'
|
||||
,'errorHeader' => 'HTTP/1.0 500 Internal Server Error'
|
||||
|
||||
// callback function to see/modify content of all sources
|
||||
,'postprocessor' => null
|
||||
// file to require to load preprocessor
|
||||
,'postprocessorRequire' => null
|
||||
);/*noLibHook*/
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -100,7 +101,7 @@ abstract class Minify_Controller_Base {
|
||||
&& is_string($minifierCallback[0])
|
||||
&& !class_exists($minifierCallback[0], false)) {
|
||||
|
||||
require str_replace('_', '/', $minifierCallback[0]) . '.php';/*noLibHook*/
|
||||
require str_replace('_', '/', $minifierCallback[0]) . '.php';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,6 +118,8 @@ abstract class Minify_Controller_Base {
|
||||
* be in subdirectories of these directories.
|
||||
*
|
||||
* @return bool file is safe
|
||||
*
|
||||
* @deprecated use checkAllowDirs, checkNotHidden instead
|
||||
*/
|
||||
public static function _fileIsSafe($file, $safeDirs)
|
||||
{
|
||||
@@ -134,15 +137,58 @@ abstract class Minify_Controller_Base {
|
||||
list($revExt) = explode('.', strrev($base));
|
||||
return in_array(strrev($revExt), array('js', 'css', 'html', 'txt'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @var array instances of Minify_Source, which provide content and
|
||||
* any individual minification needs.
|
||||
* @param string $file
|
||||
* @param array $allowDirs
|
||||
* @param string $uri
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function checkAllowDirs($file, $allowDirs, $uri)
|
||||
{
|
||||
foreach ((array)$allowDirs as $allowDir) {
|
||||
if (strpos($file, $allowDir) === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
throw new Exception("File '$file' is outside \$allowDirs. If the path is"
|
||||
. " resolved via an alias/symlink, look into the \$min_symlinks option."
|
||||
. " E.g. \$min_symlinks['/" . dirname($uri) . "'] = '" . dirname($file) . "';");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function checkNotHidden($file)
|
||||
{
|
||||
$b = basename($file);
|
||||
if (0 === strpos($b, '.')) {
|
||||
throw new Exception("Filename '$b' starts with period (may be hidden)");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* instances of Minify_Source, which provide content and any individual minification needs.
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
* @see Minify_Source
|
||||
*/
|
||||
public $sources = array();
|
||||
|
||||
/**
|
||||
* Short name to place inside cache id
|
||||
*
|
||||
* The setupSources() method may choose to set this, making it easier to
|
||||
* recognize a particular set of sources/settings in the cache folder. It
|
||||
* will be filtered and truncated to make the final cache id <= 250 bytes.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $selectionId = '';
|
||||
|
||||
/**
|
||||
* Mix in default controller options with user-given options
|
||||
*
|
||||
@@ -192,11 +238,13 @@ abstract class Minify_Controller_Base {
|
||||
|
||||
/**
|
||||
* Send message to the Minify logger
|
||||
*
|
||||
* @param string $msg
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
protected function log($msg) {
|
||||
require_once 'Minify/Logger.php';/*noLibHook*/
|
||||
public function log($msg) {
|
||||
require_once 'Minify/Logger.php';
|
||||
Minify_Logger::log($msg);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
require_once 'Minify/Controller/Base.php';/*noLibHook*/
|
||||
require_once 'Minify/Controller/Base.php';
|
||||
|
||||
/**
|
||||
* Controller class for minifying a set of files
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
require_once 'Minify/Controller/Base.php';/*noLibHook*/
|
||||
require_once 'Minify/Controller/Base.php';
|
||||
|
||||
/**
|
||||
* Controller class for serving predetermined groups of minimized sets, selected
|
||||
@@ -34,12 +34,11 @@ class Minify_Controller_Groups extends Minify_Controller_Base {
|
||||
* Set up groups of files as sources
|
||||
*
|
||||
* @param array $options controller and Minify options
|
||||
* @return array Minify options
|
||||
*
|
||||
* Controller options:
|
||||
*
|
||||
*
|
||||
* 'groups': (required) array mapping PATH_INFO strings to arrays
|
||||
* of complete file paths. @see Minify_Controller_Groups
|
||||
* of complete file paths. @see Minify_Controller_Groups
|
||||
*
|
||||
* @return array Minify options
|
||||
*/
|
||||
public function setupSources($options) {
|
||||
// strip controller options
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
require_once 'Minify/Controller/Base.php';/*noLibHook*/
|
||||
require_once 'Minify/Controller/Base.php';
|
||||
|
||||
/**
|
||||
* Controller class for requests to /min/index.php
|
||||
@@ -18,8 +18,8 @@ class Minify_Controller_MinApp extends Minify_Controller_Base {
|
||||
* Set up groups of files as sources
|
||||
*
|
||||
* @param array $options controller and Minify options
|
||||
*
|
||||
* @return array Minify options
|
||||
*
|
||||
*/
|
||||
public function setupSources($options) {
|
||||
// filter controller options
|
||||
@@ -28,64 +28,94 @@ class Minify_Controller_MinApp extends Minify_Controller_Base {
|
||||
'allowDirs' => '//'
|
||||
,'groupsOnly' => false
|
||||
,'groups' => array()
|
||||
,'maxFiles' => 10
|
||||
,'noMinPattern' => '@[-\\.]min\\.(?:js|css)$@i' // matched against basename
|
||||
)
|
||||
,(isset($options['minApp']) ? $options['minApp'] : array())
|
||||
);
|
||||
unset($options['minApp']);
|
||||
$sources = array();
|
||||
$this->selectionId = '';
|
||||
$firstMissingResource = null;
|
||||
|
||||
if (isset($_GET['g'])) {
|
||||
// try groups
|
||||
if (! isset($cOptions['groups'][$_GET['g']])) {
|
||||
$this->log("A group configuration for \"{$_GET['g']}\" was not set");
|
||||
// add group(s)
|
||||
$this->selectionId .= 'g=' . $_GET['g'];
|
||||
$keys = explode(',', $_GET['g']);
|
||||
if ($keys != array_unique($keys)) {
|
||||
$this->log("Duplicate group key found.");
|
||||
return $options;
|
||||
}
|
||||
|
||||
$files = $cOptions['groups'][$_GET['g']];
|
||||
// if $files is a single object, casting will break it
|
||||
if (is_object($files)) {
|
||||
$files = array($files);
|
||||
} elseif (! is_array($files)) {
|
||||
$files = (array)$files;
|
||||
}
|
||||
foreach ($files as $file) {
|
||||
if ($file instanceof Minify_Source) {
|
||||
$sources[] = $file;
|
||||
continue;
|
||||
}
|
||||
if (0 === strpos($file, '//')) {
|
||||
$file = $_SERVER['DOCUMENT_ROOT'] . substr($file, 1);
|
||||
}
|
||||
$file = realpath($file);
|
||||
if (is_file($file)) {
|
||||
$sources[] = new Minify_Source(array(
|
||||
'filepath' => $file
|
||||
));
|
||||
} else {
|
||||
$this->log("The path \"{$file}\" could not be found (or was not a file)");
|
||||
$keys = explode(',', $_GET['g']);
|
||||
foreach ($keys as $key) {
|
||||
if (! isset($cOptions['groups'][$key])) {
|
||||
$this->log("A group configuration for \"{$key}\" was not found");
|
||||
return $options;
|
||||
}
|
||||
$files = $cOptions['groups'][$key];
|
||||
// if $files is a single object, casting will break it
|
||||
if (is_object($files)) {
|
||||
$files = array($files);
|
||||
} elseif (! is_array($files)) {
|
||||
$files = (array)$files;
|
||||
}
|
||||
foreach ($files as $file) {
|
||||
if ($file instanceof Minify_Source) {
|
||||
$sources[] = $file;
|
||||
continue;
|
||||
}
|
||||
if (0 === strpos($file, '//')) {
|
||||
$file = $_SERVER['DOCUMENT_ROOT'] . substr($file, 1);
|
||||
}
|
||||
$realpath = realpath($file);
|
||||
if ($realpath && is_file($realpath)) {
|
||||
$sources[] = $this->_getFileSource($realpath, $cOptions);
|
||||
} else {
|
||||
$this->log("The path \"{$file}\" (realpath \"{$realpath}\") could not be found (or was not a file)");
|
||||
if (null === $firstMissingResource) {
|
||||
$firstMissingResource = basename($file);
|
||||
continue;
|
||||
} else {
|
||||
$secondMissingResource = basename($file);
|
||||
$this->log("More than one file was missing: '$firstMissingResource', '$secondMissingResource'");
|
||||
return $options;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($sources) {
|
||||
try {
|
||||
$this->checkType($sources[0]);
|
||||
} catch (Exception $e) {
|
||||
$this->log($e->getMessage());
|
||||
return $options;
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif (! $cOptions['groupsOnly'] && isset($_GET['f'])) {
|
||||
}
|
||||
if (! $cOptions['groupsOnly'] && isset($_GET['f'])) {
|
||||
// try user files
|
||||
// The following restrictions are to limit the URLs that minify will
|
||||
// respond to. Ideally there should be only one way to reference a file.
|
||||
// respond to.
|
||||
if (// verify at least one file, files are single comma separated,
|
||||
// and are all same extension
|
||||
! preg_match('/^[^,]+\\.(css|js)(?:,[^,]+\\.\\1)*$/', $_GET['f'])
|
||||
! preg_match('/^[^,]+\\.(css|js)(?:,[^,]+\\.\\1)*$/', $_GET['f'], $m)
|
||||
// no "//"
|
||||
|| strpos($_GET['f'], '//') !== false
|
||||
// no "\"
|
||||
|| strpos($_GET['f'], '\\') !== false
|
||||
// no "./"
|
||||
|| preg_match('/(?:^|[^\\.])\\.\\//', $_GET['f'])
|
||||
) {
|
||||
$this->log("GET param 'f' invalid (see MinApp.php line 63)");
|
||||
$this->log("GET param 'f' was invalid");
|
||||
return $options;
|
||||
}
|
||||
$ext = ".{$m[1]}";
|
||||
try {
|
||||
$this->checkType($m[1]);
|
||||
} catch (Exception $e) {
|
||||
$this->log($e->getMessage());
|
||||
return $options;
|
||||
}
|
||||
$files = explode(',', $_GET['f']);
|
||||
if (count($files) > $cOptions['maxFiles'] || $files != array_unique($files)) {
|
||||
$this->log("Too many or duplicate files specified");
|
||||
if ($files != array_unique($files)) {
|
||||
$this->log("Duplicate files were specified");
|
||||
return $options;
|
||||
}
|
||||
if (isset($_GET['b'])) {
|
||||
@@ -96,7 +126,7 @@ class Minify_Controller_MinApp extends Minify_Controller_Base {
|
||||
// valid base
|
||||
$base = "/{$_GET['b']}/";
|
||||
} else {
|
||||
$this->log("GET param 'b' invalid (see MinApp.php line 84)");
|
||||
$this->log("GET param 'b' was invalid");
|
||||
return $options;
|
||||
}
|
||||
} else {
|
||||
@@ -106,27 +136,96 @@ class Minify_Controller_MinApp extends Minify_Controller_Base {
|
||||
foreach ((array)$cOptions['allowDirs'] as $allowDir) {
|
||||
$allowDirs[] = realpath(str_replace('//', $_SERVER['DOCUMENT_ROOT'] . '/', $allowDir));
|
||||
}
|
||||
$basenames = array(); // just for cache id
|
||||
foreach ($files as $file) {
|
||||
$path = $_SERVER['DOCUMENT_ROOT'] . $base . $file;
|
||||
$file = realpath($path);
|
||||
if (false === $file) {
|
||||
$this->log("Path \"{$path}\" failed realpath()");
|
||||
return $options;
|
||||
} elseif (! parent::_fileIsSafe($file, $allowDirs)) {
|
||||
$this->log("Path \"{$path}\" failed Minify_Controller_Base::_fileIsSafe()");
|
||||
return $options;
|
||||
} else {
|
||||
$sources[] = new Minify_Source(array(
|
||||
'filepath' => $file
|
||||
));
|
||||
$uri = $base . $file;
|
||||
$path = $_SERVER['DOCUMENT_ROOT'] . $uri;
|
||||
$realpath = realpath($path);
|
||||
if (false === $realpath || ! is_file($realpath)) {
|
||||
$this->log("The path \"{$path}\" (realpath \"{$realpath}\") could not be found (or was not a file)");
|
||||
if (null === $firstMissingResource) {
|
||||
$firstMissingResource = $uri;
|
||||
continue;
|
||||
} else {
|
||||
$secondMissingResource = $uri;
|
||||
$this->log("More than one file was missing: '$firstMissingResource', '$secondMissingResource`'");
|
||||
return $options;
|
||||
}
|
||||
}
|
||||
try {
|
||||
parent::checkNotHidden($realpath);
|
||||
parent::checkAllowDirs($realpath, $allowDirs, $uri);
|
||||
} catch (Exception $e) {
|
||||
$this->log($e->getMessage());
|
||||
return $options;
|
||||
}
|
||||
$sources[] = $this->_getFileSource($realpath, $cOptions);
|
||||
$basenames[] = basename($realpath, $ext);
|
||||
}
|
||||
if ($this->selectionId) {
|
||||
$this->selectionId .= '_f=';
|
||||
}
|
||||
$this->selectionId .= implode(',', $basenames) . $ext;
|
||||
}
|
||||
if ($sources) {
|
||||
if (null !== $firstMissingResource) {
|
||||
array_unshift($sources, new Minify_Source(array(
|
||||
'id' => 'missingFile'
|
||||
// should not cause cache invalidation
|
||||
,'lastModified' => 0
|
||||
// due to caching, filename is unreliable.
|
||||
,'content' => "/* Minify: at least one missing file. See " . Minify::URL_DEBUG . " */\n"
|
||||
,'minifier' => ''
|
||||
)));
|
||||
}
|
||||
$this->sources = $sources;
|
||||
} else {
|
||||
$this->log("No sources to serve");
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file
|
||||
*
|
||||
* @param array $cOptions
|
||||
*
|
||||
* @return Minify_Source
|
||||
*/
|
||||
protected function _getFileSource($file, $cOptions)
|
||||
{
|
||||
$spec['filepath'] = $file;
|
||||
if ($cOptions['noMinPattern']
|
||||
&& preg_match($cOptions['noMinPattern'], basename($file))) {
|
||||
$spec['minifier'] = '';
|
||||
}
|
||||
return new Minify_Source($spec);
|
||||
}
|
||||
|
||||
protected $_type = null;
|
||||
|
||||
/**
|
||||
* Make sure that only source files of a single type are registered
|
||||
*
|
||||
* @param string $sourceOrExt
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function checkType($sourceOrExt)
|
||||
{
|
||||
if ($sourceOrExt === 'js') {
|
||||
$type = Minify::TYPE_JS;
|
||||
} elseif ($sourceOrExt === 'css') {
|
||||
$type = Minify::TYPE_CSS;
|
||||
} elseif ($sourceOrExt->contentType !== null) {
|
||||
$type = $sourceOrExt->contentType;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
if ($this->_type === null) {
|
||||
$this->_type = $type;
|
||||
} elseif ($this->_type !== $type) {
|
||||
throw new Exception('Content-Type mismatch');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
require_once 'Minify/Controller/Base.php';/*noLibHook*/
|
||||
require_once 'Minify/Controller/Base.php';
|
||||
|
||||
/**
|
||||
* Controller class for serving a single HTML page
|
||||
@@ -40,14 +40,19 @@ class Minify_Controller_Page extends Minify_Controller_Base {
|
||||
$sourceSpec = array(
|
||||
'filepath' => $options['file']
|
||||
);
|
||||
$f = $options['file'];
|
||||
} else {
|
||||
// strip controller options
|
||||
$sourceSpec = array(
|
||||
'content' => $options['content']
|
||||
,'id' => $options['id']
|
||||
);
|
||||
$f = $options['id'];
|
||||
unset($options['content'], $options['id']);
|
||||
}
|
||||
// something like "builder,index.php" or "directory,file.html"
|
||||
$this->selectionId = strtr(substr($f, 1 + strlen(dirname(dirname($f)))), '/\\', ',,');
|
||||
|
||||
if (isset($options['minifyAll'])) {
|
||||
// this will be the 2nd argument passed to Minify_HTML::minify()
|
||||
$sourceSpec['minifyOptions'] = array(
|
||||
@@ -73,8 +78,8 @@ class Minify_Controller_Page extends Minify_Controller_Base {
|
||||
if ($this->_loadCssJsMinifiers) {
|
||||
// Minify will not call for these so we must manually load
|
||||
// them when Minify/HTML.php is called for.
|
||||
require_once 'Minify/CSS.php';/*noLibHook*/
|
||||
require_once 'JSMin.php';/*noLibHook*/
|
||||
require_once 'Minify/CSS.php';
|
||||
require_once 'JSMin.php';
|
||||
}
|
||||
parent::loadMinifier($minifierCallback); // load Minify/HTML.php
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
require_once 'Minify/Controller/Base.php';/*noLibHook*/
|
||||
require_once 'Minify/Controller/Base.php';
|
||||
|
||||
/**
|
||||
* Controller class for emulating version 1 of minify.php
|
||||
* Controller class for emulating version 1 of minify.php (mostly a proof-of-concept)
|
||||
*
|
||||
* <code>
|
||||
* Minify::serve('Version1');
|
||||
|
||||
@@ -1,245 +1,246 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Minify_HTML
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
/**
|
||||
* Compress HTML
|
||||
*
|
||||
* This is a heavy regex-based removal of whitespace, unnecessary comments and
|
||||
* tokens. IE conditional comments are preserved. There are also options to have
|
||||
* STYLE and SCRIPT blocks compressed by callback functions.
|
||||
*
|
||||
* A test suite is available.
|
||||
*
|
||||
* @package Minify
|
||||
* @author Stephen Clay <steve@mrclay.org>
|
||||
*/
|
||||
class Minify_HTML {
|
||||
|
||||
/**
|
||||
* "Minify" an HTML page
|
||||
*
|
||||
* @param string $html
|
||||
*
|
||||
* @param array $options
|
||||
*
|
||||
* 'cssMinifier' : (optional) callback function to process content of STYLE
|
||||
* elements.
|
||||
*
|
||||
* 'jsMinifier' : (optional) callback function to process content of SCRIPT
|
||||
* elements. Note: the type attribute is ignored.
|
||||
*
|
||||
* 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
|
||||
* unset, minify will sniff for an XHTML doctype.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function minify($html, $options = array()) {
|
||||
$min = new Minify_HTML($html, $options);
|
||||
return $min->process();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a minifier object
|
||||
*
|
||||
* @param string $html
|
||||
*
|
||||
* @param array $options
|
||||
*
|
||||
* 'cssMinifier' : (optional) callback function to process content of STYLE
|
||||
* elements.
|
||||
*
|
||||
* 'jsMinifier' : (optional) callback function to process content of SCRIPT
|
||||
* elements. Note: the type attribute is ignored.
|
||||
*
|
||||
* 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
|
||||
* unset, minify will sniff for an XHTML doctype.
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function __construct($html, $options = array())
|
||||
{
|
||||
$this->_html = str_replace("\r\n", "\n", trim($html));
|
||||
if (isset($options['xhtml'])) {
|
||||
$this->_isXhtml = (bool)$options['xhtml'];
|
||||
}
|
||||
if (isset($options['cssMinifier'])) {
|
||||
$this->_cssMinifier = $options['cssMinifier'];
|
||||
}
|
||||
if (isset($options['jsMinifier'])) {
|
||||
$this->_jsMinifier = $options['jsMinifier'];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Minify the markeup given in the constructor
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function process()
|
||||
{
|
||||
if ($this->_isXhtml === null) {
|
||||
$this->_isXhtml = (false !== strpos($this->_html, '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML'));
|
||||
}
|
||||
|
||||
$this->_replacementHash = 'MINIFYHTML' . md5($_SERVER['REQUEST_TIME']);
|
||||
$this->_placeholders = array();
|
||||
|
||||
// replace SCRIPTs (and minify) with placeholders
|
||||
$this->_html = preg_replace_callback(
|
||||
'/(\\s*)(<script\\b[^>]*?>)([\\s\\S]*?)<\\/script>(\\s*)/i'
|
||||
,array($this, '_removeScriptCB')
|
||||
,$this->_html);
|
||||
|
||||
// replace STYLEs (and minify) with placeholders
|
||||
$this->_html = preg_replace_callback(
|
||||
'/\\s*(<style\\b[^>]*?>)([\\s\\S]*?)<\\/style>\\s*/i'
|
||||
,array($this, '_removeStyleCB')
|
||||
,$this->_html);
|
||||
|
||||
// remove HTML comments (not containing IE conditional comments).
|
||||
$this->_html = preg_replace_callback(
|
||||
'/<!--([\\s\\S]*?)-->/'
|
||||
,array($this, '_commentCB')
|
||||
,$this->_html);
|
||||
|
||||
// replace PREs with placeholders
|
||||
$this->_html = preg_replace_callback('/\\s*(<pre\\b[^>]*?>[\\s\\S]*?<\\/pre>)\\s*/i'
|
||||
,array($this, '_removePreCB')
|
||||
,$this->_html);
|
||||
|
||||
// replace TEXTAREAs with placeholders
|
||||
$this->_html = preg_replace_callback(
|
||||
'/\\s*(<textarea\\b[^>]*?>[\\s\\S]*?<\\/textarea>)\\s*/i'
|
||||
,array($this, '_removeTextareaCB')
|
||||
,$this->_html);
|
||||
|
||||
// trim each line.
|
||||
// @todo take into account attribute values that span multiple lines.
|
||||
$this->_html = preg_replace('/^\\s+|\\s+$/m', '', $this->_html);
|
||||
|
||||
// remove ws around block/undisplayed elements
|
||||
$this->_html = preg_replace('/\\s+(<\\/?(?:area|base(?:font)?|blockquote|body'
|
||||
.'|caption|center|cite|col(?:group)?|dd|dir|div|dl|dt|fieldset|form'
|
||||
.'|frame(?:set)?|h[1-6]|head|hr|html|legend|li|link|map|menu|meta'
|
||||
.'|ol|opt(?:group|ion)|p|param|t(?:able|body|head|d|h||r|foot|itle)'
|
||||
.'|ul)\\b[^>]*>)/i', '$1', $this->_html);
|
||||
|
||||
// remove ws outside of all elements
|
||||
$this->_html = preg_replace_callback(
|
||||
'/>([^<]+)</'
|
||||
,array($this, '_outsideTagCB')
|
||||
,$this->_html);
|
||||
|
||||
// use newlines before 1st attribute in open tags (to limit line lengths)
|
||||
$this->_html = preg_replace('/(<[a-z\\-]+)\\s+([^>]+>)/i', "$1\n$2", $this->_html);
|
||||
|
||||
// fill placeholders
|
||||
$this->_html = str_replace(
|
||||
array_keys($this->_placeholders)
|
||||
,array_values($this->_placeholders)
|
||||
,$this->_html
|
||||
);
|
||||
return $this->_html;
|
||||
}
|
||||
|
||||
protected function _commentCB($m)
|
||||
{
|
||||
return (0 === strpos($m[1], '[') || false !== strpos($m[1], '<!['))
|
||||
? $m[0]
|
||||
: '';
|
||||
}
|
||||
|
||||
protected function _reservePlace($content)
|
||||
{
|
||||
$placeholder = '%' . $this->_replacementHash . count($this->_placeholders) . '%';
|
||||
$this->_placeholders[$placeholder] = $content;
|
||||
return $placeholder;
|
||||
}
|
||||
|
||||
protected $_isXhtml = null;
|
||||
protected $_replacementHash = null;
|
||||
protected $_placeholders = array();
|
||||
protected $_cssMinifier = null;
|
||||
protected $_jsMinifier = null;
|
||||
|
||||
protected function _outsideTagCB($m)
|
||||
{
|
||||
return '>' . preg_replace('/^\\s+|\\s+$/', ' ', $m[1]) . '<';
|
||||
}
|
||||
|
||||
protected function _removePreCB($m)
|
||||
{
|
||||
return $this->_reservePlace($m[1]);
|
||||
}
|
||||
|
||||
protected function _removeTextareaCB($m)
|
||||
{
|
||||
return $this->_reservePlace($m[1]);
|
||||
}
|
||||
|
||||
protected function _removeStyleCB($m)
|
||||
{
|
||||
$openStyle = $m[1];
|
||||
$css = $m[2];
|
||||
// remove HTML comments
|
||||
$css = preg_replace('/(?:^\\s*<!--|-->\\s*$)/', '', $css);
|
||||
|
||||
// remove CDATA section markers
|
||||
$css = $this->_removeCdata($css);
|
||||
|
||||
// minify
|
||||
$minifier = $this->_cssMinifier
|
||||
? $this->_cssMinifier
|
||||
: 'trim';
|
||||
$css = call_user_func($minifier, $css);
|
||||
|
||||
return $this->_reservePlace($this->_needsCdata($css)
|
||||
? "{$openStyle}/*<![CDATA[*/{$css}/*]]>*/</style>"
|
||||
: "{$openStyle}{$css}</style>"
|
||||
);
|
||||
}
|
||||
|
||||
protected function _removeScriptCB($m)
|
||||
{
|
||||
$openScript = $m[2];
|
||||
$js = $m[3];
|
||||
|
||||
// whitespace surrounding? preserve at least one space
|
||||
$ws1 = ($m[1] === '') ? '' : ' ';
|
||||
$ws2 = ($m[4] === '') ? '' : ' ';
|
||||
|
||||
// remove HTML comments (and ending "//" if present)
|
||||
$js = preg_replace('/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $js);
|
||||
|
||||
// remove CDATA section markers
|
||||
$js = $this->_removeCdata($js);
|
||||
|
||||
// minify
|
||||
$minifier = $this->_jsMinifier
|
||||
? $this->_jsMinifier
|
||||
: 'trim';
|
||||
$js = call_user_func($minifier, $js);
|
||||
|
||||
return $this->_reservePlace($this->_needsCdata($js)
|
||||
? "{$ws1}{$openScript}/*<![CDATA[*/{$js}/*]]>*/</script>{$ws2}"
|
||||
: "{$ws1}{$openScript}{$js}</script>{$ws2}"
|
||||
);
|
||||
}
|
||||
|
||||
protected function _removeCdata($str)
|
||||
{
|
||||
return (false !== strpos($str, '<![CDATA['))
|
||||
? str_replace(array('<![CDATA[', ']]>'), '', $str)
|
||||
: $str;
|
||||
}
|
||||
|
||||
protected function _needsCdata($str)
|
||||
{
|
||||
return ($this->_isXhtml && preg_match('/(?:[<&]|\\-\\-|\\]\\]>)/', $str));
|
||||
}
|
||||
}
|
||||
<?php
|
||||
/**
|
||||
* Class Minify_HTML
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
/**
|
||||
* Compress HTML
|
||||
*
|
||||
* This is a heavy regex-based removal of whitespace, unnecessary comments and
|
||||
* tokens. IE conditional comments are preserved. There are also options to have
|
||||
* STYLE and SCRIPT blocks compressed by callback functions.
|
||||
*
|
||||
* A test suite is available.
|
||||
*
|
||||
* @package Minify
|
||||
* @author Stephen Clay <steve@mrclay.org>
|
||||
*/
|
||||
class Minify_HTML {
|
||||
|
||||
/**
|
||||
* "Minify" an HTML page
|
||||
*
|
||||
* @param string $html
|
||||
*
|
||||
* @param array $options
|
||||
*
|
||||
* 'cssMinifier' : (optional) callback function to process content of STYLE
|
||||
* elements.
|
||||
*
|
||||
* 'jsMinifier' : (optional) callback function to process content of SCRIPT
|
||||
* elements. Note: the type attribute is ignored.
|
||||
*
|
||||
* 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
|
||||
* unset, minify will sniff for an XHTML doctype.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function minify($html, $options = array()) {
|
||||
$min = new Minify_HTML($html, $options);
|
||||
return $min->process();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a minifier object
|
||||
*
|
||||
* @param string $html
|
||||
*
|
||||
* @param array $options
|
||||
*
|
||||
* 'cssMinifier' : (optional) callback function to process content of STYLE
|
||||
* elements.
|
||||
*
|
||||
* 'jsMinifier' : (optional) callback function to process content of SCRIPT
|
||||
* elements. Note: the type attribute is ignored.
|
||||
*
|
||||
* 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
|
||||
* unset, minify will sniff for an XHTML doctype.
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function __construct($html, $options = array())
|
||||
{
|
||||
$this->_html = str_replace("\r\n", "\n", trim($html));
|
||||
if (isset($options['xhtml'])) {
|
||||
$this->_isXhtml = (bool)$options['xhtml'];
|
||||
}
|
||||
if (isset($options['cssMinifier'])) {
|
||||
$this->_cssMinifier = $options['cssMinifier'];
|
||||
}
|
||||
if (isset($options['jsMinifier'])) {
|
||||
$this->_jsMinifier = $options['jsMinifier'];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Minify the markeup given in the constructor
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function process()
|
||||
{
|
||||
if ($this->_isXhtml === null) {
|
||||
$this->_isXhtml = (false !== strpos($this->_html, '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML'));
|
||||
}
|
||||
|
||||
$this->_replacementHash = 'MINIFYHTML' . md5($_SERVER['REQUEST_TIME']);
|
||||
$this->_placeholders = array();
|
||||
|
||||
// replace SCRIPTs (and minify) with placeholders
|
||||
$this->_html = preg_replace_callback(
|
||||
'/(\\s*)<script(\\b[^>]*?>)([\\s\\S]*?)<\\/script>(\\s*)/i'
|
||||
,array($this, '_removeScriptCB')
|
||||
,$this->_html);
|
||||
|
||||
// replace STYLEs (and minify) with placeholders
|
||||
$this->_html = preg_replace_callback(
|
||||
'/\\s*<style(\\b[^>]*>)([\\s\\S]*?)<\\/style>\\s*/i'
|
||||
,array($this, '_removeStyleCB')
|
||||
,$this->_html);
|
||||
|
||||
// remove HTML comments (not containing IE conditional comments).
|
||||
$this->_html = preg_replace_callback(
|
||||
'/<!--([\\s\\S]*?)-->/'
|
||||
,array($this, '_commentCB')
|
||||
,$this->_html);
|
||||
|
||||
// replace PREs with placeholders
|
||||
$this->_html = preg_replace_callback('/\\s*<pre(\\b[^>]*?>[\\s\\S]*?<\\/pre>)\\s*/i'
|
||||
,array($this, '_removePreCB')
|
||||
,$this->_html);
|
||||
|
||||
// replace TEXTAREAs with placeholders
|
||||
$this->_html = preg_replace_callback(
|
||||
'/\\s*<textarea(\\b[^>]*?>[\\s\\S]*?<\\/textarea>)\\s*/i'
|
||||
,array($this, '_removeTextareaCB')
|
||||
,$this->_html);
|
||||
|
||||
// trim each line.
|
||||
// @todo take into account attribute values that span multiple lines.
|
||||
$this->_html = preg_replace('/^\\s+|\\s+$/m', '', $this->_html);
|
||||
|
||||
// remove ws around block/undisplayed elements
|
||||
$this->_html = preg_replace('/\\s+(<\\/?(?:area|base(?:font)?|blockquote|body'
|
||||
.'|caption|center|cite|col(?:group)?|dd|dir|div|dl|dt|fieldset|form'
|
||||
.'|frame(?:set)?|h[1-6]|head|hr|html|legend|li|link|map|menu|meta'
|
||||
.'|ol|opt(?:group|ion)|p|param|t(?:able|body|head|d|h||r|foot|itle)'
|
||||
.'|ul)\\b[^>]*>)/i', '$1', $this->_html);
|
||||
|
||||
// remove ws outside of all elements
|
||||
$this->_html = preg_replace(
|
||||
'/>(\\s(?:\\s*))?([^<]+)(\\s(?:\s*))?</'
|
||||
,'>$1$2$3<'
|
||||
,$this->_html);
|
||||
|
||||
// use newlines before 1st attribute in open tags (to limit line lengths)
|
||||
$this->_html = preg_replace('/(<[a-z\\-]+)\\s+([^>]+>)/i', "$1\n$2", $this->_html);
|
||||
|
||||
// fill placeholders
|
||||
$this->_html = str_replace(
|
||||
array_keys($this->_placeholders)
|
||||
,array_values($this->_placeholders)
|
||||
,$this->_html
|
||||
);
|
||||
// issue 229: multi-pass to catch scripts that didn't get replaced in textareas
|
||||
$this->_html = str_replace(
|
||||
array_keys($this->_placeholders)
|
||||
,array_values($this->_placeholders)
|
||||
,$this->_html
|
||||
);
|
||||
return $this->_html;
|
||||
}
|
||||
|
||||
protected function _commentCB($m)
|
||||
{
|
||||
return (0 === strpos($m[1], '[') || false !== strpos($m[1], '<!['))
|
||||
? $m[0]
|
||||
: '';
|
||||
}
|
||||
|
||||
protected function _reservePlace($content)
|
||||
{
|
||||
$placeholder = '%' . $this->_replacementHash . count($this->_placeholders) . '%';
|
||||
$this->_placeholders[$placeholder] = $content;
|
||||
return $placeholder;
|
||||
}
|
||||
|
||||
protected $_isXhtml = null;
|
||||
protected $_replacementHash = null;
|
||||
protected $_placeholders = array();
|
||||
protected $_cssMinifier = null;
|
||||
protected $_jsMinifier = null;
|
||||
|
||||
protected function _removePreCB($m)
|
||||
{
|
||||
return $this->_reservePlace("<pre{$m[1]}");
|
||||
}
|
||||
|
||||
protected function _removeTextareaCB($m)
|
||||
{
|
||||
return $this->_reservePlace("<textarea{$m[1]}");
|
||||
}
|
||||
|
||||
protected function _removeStyleCB($m)
|
||||
{
|
||||
$openStyle = "<style{$m[1]}";
|
||||
$css = $m[2];
|
||||
// remove HTML comments
|
||||
$css = preg_replace('/(?:^\\s*<!--|-->\\s*$)/', '', $css);
|
||||
|
||||
// remove CDATA section markers
|
||||
$css = $this->_removeCdata($css);
|
||||
|
||||
// minify
|
||||
$minifier = $this->_cssMinifier
|
||||
? $this->_cssMinifier
|
||||
: 'trim';
|
||||
$css = call_user_func($minifier, $css);
|
||||
|
||||
return $this->_reservePlace($this->_needsCdata($css)
|
||||
? "{$openStyle}/*<![CDATA[*/{$css}/*]]>*/</style>"
|
||||
: "{$openStyle}{$css}</style>"
|
||||
);
|
||||
}
|
||||
|
||||
protected function _removeScriptCB($m)
|
||||
{
|
||||
$openScript = "<script{$m[2]}";
|
||||
$js = $m[3];
|
||||
|
||||
// whitespace surrounding? preserve at least one space
|
||||
$ws1 = ($m[1] === '') ? '' : ' ';
|
||||
$ws2 = ($m[4] === '') ? '' : ' ';
|
||||
|
||||
// remove HTML comments (and ending "//" if present)
|
||||
$js = preg_replace('/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $js);
|
||||
|
||||
// remove CDATA section markers
|
||||
$js = $this->_removeCdata($js);
|
||||
|
||||
// minify
|
||||
$minifier = $this->_jsMinifier
|
||||
? $this->_jsMinifier
|
||||
: 'trim';
|
||||
$js = call_user_func($minifier, $js);
|
||||
|
||||
return $this->_reservePlace($this->_needsCdata($js)
|
||||
? "{$ws1}{$openScript}/*<![CDATA[*/{$js}/*]]>*/</script>{$ws2}"
|
||||
: "{$ws1}{$openScript}{$js}</script>{$ws2}"
|
||||
);
|
||||
}
|
||||
|
||||
protected function _removeCdata($str)
|
||||
{
|
||||
return (false !== strpos($str, '<![CDATA['))
|
||||
? str_replace(array('<![CDATA[', ']]>'), '', $str)
|
||||
: $str;
|
||||
}
|
||||
|
||||
protected function _needsCdata($str)
|
||||
{
|
||||
return ($this->_isXhtml && preg_match('/(?:[<&]|\\-\\-|\\]\\]>)/', $str));
|
||||
}
|
||||
}
|
||||
@@ -1,157 +1,216 @@
|
||||
<?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>
|
||||
*/
|
||||
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 _importCB to write the fetched content back to the obj
|
||||
private $_importedContent = '';
|
||||
|
||||
private static $_isCss = null;
|
||||
|
||||
private function __construct($currentDir)
|
||||
{
|
||||
$this->_currentDir = $currentDir;
|
||||
}
|
||||
|
||||
private function _getContent($file)
|
||||
{
|
||||
$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
|
||||
);
|
||||
|
||||
if (self::$_isCss) {
|
||||
// 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 */";/*noLibHook*/
|
||||
}
|
||||
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));
|
||||
$content = $obj->_getContent($file);
|
||||
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);
|
||||
// strip doc root
|
||||
$path = substr($path, strlen(realpath($_SERVER['DOCUMENT_ROOT'])));
|
||||
// fix to absolute URL
|
||||
$url = strtr($path, '/\\', '//');
|
||||
// remove /./ and /../ where possible
|
||||
$url = str_replace('/./', '/', $url);
|
||||
// inspired by patch from Oleg Cherniy
|
||||
do {
|
||||
$url = preg_replace('@/[^/]+/\\.\\./@', '/', $url, 1, $changed);
|
||||
} while ($changed);
|
||||
}
|
||||
}
|
||||
return "url({$quote}{$url}{$quote})";
|
||||
}
|
||||
}
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -26,9 +26,9 @@ class Minify_Lines {
|
||||
*
|
||||
* 'id': (optional) string to identify file. E.g. file name/path
|
||||
*
|
||||
* 'currentDir': (default null) if given, this is assumed to be the
|
||||
* directory of the current CSS file. Using this, minify will rewrite
|
||||
* all relative URIs in import/url declarations to correctly point to
|
||||
* 'currentDir': (default null) if given, this is assumed to be the
|
||||
* directory of the current CSS file. Using this, minify will rewrite
|
||||
* all relative URIs in import/url declarations to correctly point to
|
||||
* the desired files, and prepend a comment with debugging information about
|
||||
* this process.
|
||||
*
|
||||
@@ -40,10 +40,16 @@ class Minify_Lines {
|
||||
? $options['id']
|
||||
: '';
|
||||
$content = str_replace("\r\n", "\n", $content);
|
||||
|
||||
// Hackily rewrite strings with XPath expressions that are
|
||||
// likely to throw off our dumb parser (for Prototype 1.6.1).
|
||||
$content = str_replace('"/*"', '"/"+"*"', $content);
|
||||
$content = preg_replace('@([\'"])(\\.?//?)\\*@', '$1$2$1+$1*', $content);
|
||||
|
||||
$lines = explode("\n", $content);
|
||||
$numLines = count($lines);
|
||||
// determine left padding
|
||||
$padTo = strlen($numLines);
|
||||
$padTo = strlen((string) $numLines); // e.g. 103 lines = 3 digits
|
||||
$inComment = false;
|
||||
$i = 0;
|
||||
$newLines = array();
|
||||
@@ -59,12 +65,12 @@ class Minify_Lines {
|
||||
|
||||
// check for desired URI rewriting
|
||||
if (isset($options['currentDir'])) {
|
||||
require_once 'Minify/CSS/UriRewriter.php';/*noLibHook*/
|
||||
require_once 'Minify/CSS/UriRewriter.php';
|
||||
Minify_CSS_UriRewriter::$debugText = '';
|
||||
$content = Minify_CSS_UriRewriter::rewrite(
|
||||
$content
|
||||
,$options['currentDir']
|
||||
,isset($options['docRoot']) ? $options['docRoot'] : $_SERVER['DOCUMENT_ROOT']
|
||||
,isset($options['docRoot']) ? $options['docRoot'] : $_SERVER['DOCUMENT_ROOT']
|
||||
,isset($options['symlinks']) ? $options['symlinks'] : array()
|
||||
);
|
||||
$content = "/* Minify_CSS_UriRewriter::\$debugText\n\n"
|
||||
@@ -82,7 +88,7 @@ class Minify_Lines {
|
||||
*
|
||||
* @param bool $inComment was the parser in a comment at the
|
||||
* beginning of the line?
|
||||
*
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function _eolInComment($line, $inComment)
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
*
|
||||
* @package Minify
|
||||
* @author Stephen Clay <steve@mrclay.org>
|
||||
*
|
||||
* @todo lose this singleton! pass log object in Minify::serve and distribute to others
|
||||
*/
|
||||
class Minify_Logger {
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ if (false === (@include 'class.JavaScriptPacker.php')) {
|
||||
'The script "class.JavaScriptPacker.php" is required. Please see: http:'
|
||||
.'//code.google.com/p/minify/source/browse/trunk/min/lib/Minify/Packer.php'
|
||||
,E_USER_ERROR
|
||||
);/*noLibHook*/
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -90,8 +90,11 @@ class Minify_YUICompressor {
|
||||
throw new Exception('Minify_YUICompressor : could not create temp file.');
|
||||
}
|
||||
file_put_contents($tmpFile, $content);
|
||||
exec(self::_getCmd($options, $type, $tmpFile), $output);
|
||||
exec(self::_getCmd($options, $type, $tmpFile), $output, $result_code);
|
||||
unlink($tmpFile);
|
||||
if ($result_code != 0) {
|
||||
throw new Exception('Minify_YUICompressor : YUI compressor execution failed.');
|
||||
}
|
||||
return implode("\n", $output);
|
||||
}
|
||||
|
||||
@@ -110,7 +113,7 @@ class Minify_YUICompressor {
|
||||
);
|
||||
$cmd = self::$javaExecutable . ' -jar ' . escapeshellarg(self::$jarFile)
|
||||
. " --type {$type}"
|
||||
. (preg_match('/^[a-zA-Z\\-]+$/', $o['charset'])
|
||||
. (preg_match('/^[\\da-zA-Z0-9\\-]+$/', $o['charset'])
|
||||
? " --charset {$o['charset']}"
|
||||
: '')
|
||||
. (is_numeric($o['line-break']) && $o['line-break'] >= 0
|
||||
@@ -128,11 +131,17 @@ class Minify_YUICompressor {
|
||||
|
||||
private static function _prepare()
|
||||
{
|
||||
if (! is_file(self::$jarFile)
|
||||
|| ! is_dir(self::$tempDir)
|
||||
|| ! is_writable(self::$tempDir)
|
||||
) {
|
||||
throw new Exception('Minify_YUICompressor : $jarFile and $tempDir must be set.');
|
||||
if (! is_file(self::$jarFile)) {
|
||||
throw new Exception('Minify_YUICompressor : $jarFile('.self::$jarFile.') is not a valid file.');
|
||||
}
|
||||
if (! is_executable(self::$jarFile)) {
|
||||
throw new Exception('Minify_YUICompressor : $jarFile('.self::$jarFile.') is not executable.');
|
||||
}
|
||||
if (! is_dir(self::$tempDir)) {
|
||||
throw new Exception('Minify_YUICompressor : $tempDir('.self::$tempDir.') is not a valid direcotry.');
|
||||
}
|
||||
if (! is_writable(self::$tempDir)) {
|
||||
throw new Exception('Minify_YUICompressor : $tempDir('.self::$tempDir.') is not writable.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user