|
|
@@ -3,6 +3,7 @@
|
|
|
namespace MatthiasMullie\Minify;
|
|
|
|
|
|
use MatthiasMullie\Minify\Exceptions\FileImportException;
|
|
|
+use MatthiasMullie\PathConverter\ConverterInterface;
|
|
|
use MatthiasMullie\PathConverter\Converter;
|
|
|
|
|
|
/**
|
|
|
@@ -12,7 +13,7 @@ use MatthiasMullie\PathConverter\Converter;
|
|
|
*
|
|
|
* @author Matthias Mullie <minify@mullie.eu>
|
|
|
* @author Tijs Verkoyen <minify@verkoyen.eu>
|
|
|
- * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved.
|
|
|
+ * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
|
|
|
* @license MIT License
|
|
|
*/
|
|
|
class CSS extends Minify
|
|
|
@@ -83,7 +84,7 @@ class CSS extends Minify
|
|
|
|
|
|
// add to top
|
|
|
$content = implode('', $matches[0]).$content;
|
|
|
- };
|
|
|
+ }
|
|
|
|
|
|
return $content;
|
|
|
}
|
|
|
@@ -94,9 +95,9 @@ class CSS extends Minify
|
|
|
* @import's will be loaded and their content merged into the original file,
|
|
|
* to save HTTP requests.
|
|
|
*
|
|
|
- * @param string $source The file to combine imports for.
|
|
|
- * @param string $content The CSS content to combine imports for.
|
|
|
- * @param string[] $parents Parent paths, for circular reference checks.
|
|
|
+ * @param string $source The file to combine imports for
|
|
|
+ * @param string $content The CSS content to combine imports for
|
|
|
+ * @param string[] $parents Parent paths, for circular reference checks
|
|
|
*
|
|
|
* @return string
|
|
|
*
|
|
|
@@ -120,16 +121,7 @@ class CSS extends Minify
|
|
|
(?P<quotes>["\']?)
|
|
|
|
|
|
# fetch path
|
|
|
- (?P<path>
|
|
|
-
|
|
|
- # do not fetch data uris or external sources
|
|
|
- (?!(
|
|
|
- ["\']?
|
|
|
- (data|https?):
|
|
|
- ))
|
|
|
-
|
|
|
- .+?
|
|
|
- )
|
|
|
+ (?P<path>.+?)
|
|
|
|
|
|
# (optional) close path enclosure
|
|
|
(?P=quotes)
|
|
|
@@ -164,16 +156,7 @@ class CSS extends Minify
|
|
|
(?P<quotes>["\'])
|
|
|
|
|
|
# fetch path
|
|
|
- (?P<path>
|
|
|
-
|
|
|
- # do not fetch data uris or external sources
|
|
|
- (?!(
|
|
|
- ["\']?
|
|
|
- (data|https?):
|
|
|
- ))
|
|
|
-
|
|
|
- .+?
|
|
|
- )
|
|
|
+ (?P<path>.+?)
|
|
|
|
|
|
# close path enclosure
|
|
|
(?P=quotes)
|
|
|
@@ -211,33 +194,33 @@ class CSS extends Minify
|
|
|
|
|
|
// only replace the import with the content if we can grab the
|
|
|
// content of the file
|
|
|
- if ($this->canImportFile($importPath)) {
|
|
|
- // check if current file was not imported previously in the same
|
|
|
- // import chain.
|
|
|
- if (in_array($importPath, $parents)) {
|
|
|
- throw new FileImportException('Failed to import file "'.$importPath.'": circular reference detected.');
|
|
|
- }
|
|
|
+ if (!$this->canImportByPath($match['path']) || !$this->canImportFile($importPath)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
|
|
|
- // grab referenced file & minify it (which may include importing
|
|
|
- // yet other @import statements recursively)
|
|
|
- $minifier = new static($importPath);
|
|
|
- $importContent = $minifier->execute($source, $parents);
|
|
|
+ // check if current file was not imported previously in the same
|
|
|
+ // import chain.
|
|
|
+ if (in_array($importPath, $parents)) {
|
|
|
+ throw new FileImportException('Failed to import file "'.$importPath.'": circular reference detected.');
|
|
|
+ }
|
|
|
|
|
|
- // check if this is only valid for certain media
|
|
|
- if (!empty($match['media'])) {
|
|
|
- $importContent = '@media '.$match['media'].'{'.$importContent.'}';
|
|
|
- }
|
|
|
+ // grab referenced file & minify it (which may include importing
|
|
|
+ // yet other @import statements recursively)
|
|
|
+ $minifier = new static($importPath);
|
|
|
+ $importContent = $minifier->execute($source, $parents);
|
|
|
|
|
|
- // add to replacement array
|
|
|
- $search[] = $match[0];
|
|
|
- $replace[] = $importContent;
|
|
|
+ // check if this is only valid for certain media
|
|
|
+ if (!empty($match['media'])) {
|
|
|
+ $importContent = '@media '.$match['media'].'{'.$importContent.'}';
|
|
|
}
|
|
|
+
|
|
|
+ // add to replacement array
|
|
|
+ $search[] = $match[0];
|
|
|
+ $replace[] = $importContent;
|
|
|
}
|
|
|
|
|
|
// replace the import statements
|
|
|
- $content = str_replace($search, $replace, $content);
|
|
|
-
|
|
|
- return $content;
|
|
|
+ return str_replace($search, $replace, $content);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -246,25 +229,28 @@ class CSS extends Minify
|
|
|
* @url(image.jpg) images will be loaded and their content merged into the
|
|
|
* original file, to save HTTP requests.
|
|
|
*
|
|
|
- * @param string $source The file to import files for.
|
|
|
- * @param string $content The CSS content to import files for.
|
|
|
+ * @param string $source The file to import files for
|
|
|
+ * @param string $content The CSS content to import files for
|
|
|
*
|
|
|
* @return string
|
|
|
*/
|
|
|
protected function importFiles($source, $content)
|
|
|
{
|
|
|
- $extensions = array_keys($this->importExtensions);
|
|
|
- $regex = '/url\((["\']?)((?!["\']?data:).*?\.('.implode('|', $extensions).'))\\1\)/i';
|
|
|
- if ($extensions && preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) {
|
|
|
+ $regex = '/url\((["\']?)(.+?)\\1\)/i';
|
|
|
+ if ($this->importExtensions && preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) {
|
|
|
$search = array();
|
|
|
$replace = array();
|
|
|
|
|
|
// loop the matches
|
|
|
foreach ($matches as $match) {
|
|
|
+ $extension = substr(strrchr($match[2], '.'), 1);
|
|
|
+ if ($extension && !array_key_exists($extension, $this->importExtensions)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
// get the path for the file that will be imported
|
|
|
$path = $match[2];
|
|
|
$path = dirname($source).'/'.$path;
|
|
|
- $extension = $match[3];
|
|
|
|
|
|
// only replace the import with the content if we're able to get
|
|
|
// the content of the file, and it's relatively small
|
|
|
@@ -290,21 +276,21 @@ class CSS extends Minify
|
|
|
* Minify the data.
|
|
|
* Perform CSS optimizations.
|
|
|
*
|
|
|
- * @param string[optional] $path Path to write the data to.
|
|
|
- * @param string[] $parents Parent paths, for circular reference checks.
|
|
|
+ * @param string[optional] $path Path to write the data to
|
|
|
+ * @param string[] $parents Parent paths, for circular reference checks
|
|
|
*
|
|
|
- * @return string The minified data.
|
|
|
+ * @return string The minified data
|
|
|
*/
|
|
|
public function execute($path = null, $parents = array())
|
|
|
{
|
|
|
$content = '';
|
|
|
|
|
|
- // loop css data (raw data and files)
|
|
|
+ // loop CSS data (raw data and files)
|
|
|
foreach ($this->data as $source => $css) {
|
|
|
/*
|
|
|
- * Let's first take out strings & comments, since we can't just remove
|
|
|
- * whitespace anywhere. If whitespace occurs inside a string, we should
|
|
|
- * leave it alone. E.g.:
|
|
|
+ * Let's first take out strings & comments, since we can't just
|
|
|
+ * remove whitespace anywhere. If whitespace occurs inside a string,
|
|
|
+ * we should leave it alone. E.g.:
|
|
|
* p { content: "a test" }
|
|
|
*/
|
|
|
$this->extractStrings();
|
|
|
@@ -330,9 +316,9 @@ class CSS extends Minify
|
|
|
* to be relative no longer to the source file, but to the new path.
|
|
|
* If we don't write to a file, fall back to same path so no
|
|
|
* conversion happens (because we still want it to go through most
|
|
|
- * of the move code...)
|
|
|
+ * of the move code, which also addresses url() & @import syntax...)
|
|
|
*/
|
|
|
- $converter = new Converter($source, $path ?: $source);
|
|
|
+ $converter = $this->getPathConverter($source, $path ?: $source);
|
|
|
$css = $this->move($converter, $css);
|
|
|
|
|
|
// combine css
|
|
|
@@ -350,12 +336,12 @@ class CSS extends Minify
|
|
|
* will have to be updated when a file is being saved at another location
|
|
|
* (e.g. ../../images/image.gif, if the new CSS file is 1 folder deeper).
|
|
|
*
|
|
|
- * @param Converter $converter Relative path converter
|
|
|
- * @param string $content The CSS content to update relative urls for.
|
|
|
+ * @param ConverterInterface $converter Relative path converter
|
|
|
+ * @param string $content The CSS content to update relative urls for
|
|
|
*
|
|
|
* @return string
|
|
|
*/
|
|
|
- protected function move(Converter $converter, $content)
|
|
|
+ protected function move(ConverterInterface $converter, $content)
|
|
|
{
|
|
|
/*
|
|
|
* Relative path references will usually be enclosed by url(). @import
|
|
|
@@ -380,17 +366,7 @@ class CSS extends Minify
|
|
|
(?P<quotes>["\'])?
|
|
|
|
|
|
# fetch path
|
|
|
- (?P<path>
|
|
|
-
|
|
|
- # do not fetch data uris or external sources
|
|
|
- (?!(
|
|
|
- \s?
|
|
|
- ["\']?
|
|
|
- (data|https?):
|
|
|
- ))
|
|
|
-
|
|
|
- .+?
|
|
|
- )
|
|
|
+ (?P<path>.+?)
|
|
|
|
|
|
# close path enclosure
|
|
|
(?(quotes)(?P=quotes))
|
|
|
@@ -417,16 +393,7 @@ class CSS extends Minify
|
|
|
(?P<quotes>["\'])
|
|
|
|
|
|
# fetch path
|
|
|
- (?P<path>
|
|
|
-
|
|
|
- # do not fetch data uris or external sources
|
|
|
- (?!(
|
|
|
- ["\']?
|
|
|
- (data|https?):
|
|
|
- ))
|
|
|
-
|
|
|
- .+?
|
|
|
- )
|
|
|
+ (?P<path>.+?)
|
|
|
|
|
|
# close path enclosure
|
|
|
(?P=quotes)
|
|
|
@@ -450,42 +417,43 @@ class CSS extends Minify
|
|
|
// determine if it's a url() or an @import match
|
|
|
$type = (strpos($match[0], '@import') === 0 ? 'import' : 'url');
|
|
|
|
|
|
- // attempting to interpret GET-params makes no sense, so let's discard them for awhile
|
|
|
- $params = strrchr($match['path'], '?');
|
|
|
- $url = $params ? substr($match['path'], 0, -strlen($params)) : $match['path'];
|
|
|
+ $url = $match['path'];
|
|
|
+ if ($this->canImportByPath($url)) {
|
|
|
+ // attempting to interpret GET-params makes no sense, so let's discard them for awhile
|
|
|
+ $params = strrchr($url, '?');
|
|
|
+ $url = $params ? substr($url, 0, -strlen($params)) : $url;
|
|
|
|
|
|
- // fix relative url
|
|
|
- $url = $converter->convert($url);
|
|
|
+ // fix relative url
|
|
|
+ $url = $converter->convert($url);
|
|
|
|
|
|
- // now that the path has been converted, re-apply GET-params
|
|
|
- $url .= $params;
|
|
|
+ // now that the path has been converted, re-apply GET-params
|
|
|
+ $url .= $params;
|
|
|
+ }
|
|
|
|
|
|
// build replacement
|
|
|
$search[] = $match[0];
|
|
|
- if ($type == 'url') {
|
|
|
+ if ($type === 'url') {
|
|
|
$replace[] = 'url('.$url.')';
|
|
|
- } elseif ($type == 'import') {
|
|
|
+ } elseif ($type === 'import') {
|
|
|
$replace[] = '@import "'.$url.'"';
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// replace urls
|
|
|
- $content = str_replace($search, $replace, $content);
|
|
|
-
|
|
|
- return $content;
|
|
|
+ return str_replace($search, $replace, $content);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Shorthand hex color codes.
|
|
|
* #FF0000 -> #F00.
|
|
|
*
|
|
|
- * @param string $content The CSS content to shorten the hex color codes for.
|
|
|
+ * @param string $content The CSS content to shorten the hex color codes for
|
|
|
*
|
|
|
* @return string
|
|
|
*/
|
|
|
protected function shortenHex($content)
|
|
|
{
|
|
|
- $content = preg_replace('/(?<![\'"])#([0-9a-z])\\1([0-9a-z])\\2([0-9a-z])\\3(?![\'"])/i', '#$1$2$3', $content);
|
|
|
+ $content = preg_replace('/(?<=[: ])#([0-9a-z])\\1([0-9a-z])\\2([0-9a-z])\\3(?=[; }])/i', '#$1$2$3', $content);
|
|
|
|
|
|
// we can shorten some even more by replacing them with their color name
|
|
|
$colors = array(
|
|
|
@@ -518,13 +486,19 @@ class CSS extends Minify
|
|
|
'#F5DEB3' => 'wheat',
|
|
|
);
|
|
|
|
|
|
- return str_ireplace(array_keys($colors), $colors, $content);
|
|
|
+ return preg_replace_callback(
|
|
|
+ '/(?<=[: ])('.implode(array_keys($colors), '|').')(?=[; }])/i',
|
|
|
+ function ($match) use ($colors) {
|
|
|
+ return $colors[strtoupper($match[0])];
|
|
|
+ },
|
|
|
+ $content
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Shorten CSS font weights.
|
|
|
*
|
|
|
- * @param string $content The CSS content to shorten the font weights for.
|
|
|
+ * @param string $content The CSS content to shorten the font weights for
|
|
|
*
|
|
|
* @return string
|
|
|
*/
|
|
|
@@ -536,7 +510,7 @@ class CSS extends Minify
|
|
|
);
|
|
|
|
|
|
$callback = function ($match) use ($weights) {
|
|
|
- return $match[1] . $weights[$match[2]];
|
|
|
+ return $match[1].$weights[$match[2]];
|
|
|
};
|
|
|
|
|
|
return preg_replace_callback('/(font-weight\s*:\s*)('.implode('|', array_keys($weights)).')(?=[;}])/', $callback, $content);
|
|
|
@@ -545,7 +519,7 @@ class CSS extends Minify
|
|
|
/**
|
|
|
* Shorthand 0 values to plain 0, instead of e.g. -0em.
|
|
|
*
|
|
|
- * @param string $content The CSS content to shorten the zero values for.
|
|
|
+ * @param string $content The CSS content to shorten the zero values for
|
|
|
*
|
|
|
* @return string
|
|
|
*/
|
|
|
@@ -579,6 +553,30 @@ class CSS extends Minify
|
|
|
// strip negative zeroes (-0 -> 0) & truncate zeroes (00 -> 0)
|
|
|
$content = preg_replace('/'.$before.'-?0+'.$units.'?'.$after.'/', '0\\1', $content);
|
|
|
|
|
|
+ // remove zeroes where they make no sense in calc: e.g. calc(100px - 0)
|
|
|
+ // the 0 doesn't have any effect, and this isn't even valid without unit
|
|
|
+ // strip all `+ 0` or `- 0` occurrences: calc(10% + 0) -> calc(10%)
|
|
|
+ // looped because there may be multiple 0s inside 1 group of parentheses
|
|
|
+ do {
|
|
|
+ $previous = $content;
|
|
|
+ $content = preg_replace('/\(([^\(\)]+) [\+\-] 0( [^\(\)]+)?\)/', '(\\1\\2)', $content);
|
|
|
+ } while ($content !== $previous);
|
|
|
+ // strip all `0 +` occurrences: calc(0 + 10%) -> calc(10%)
|
|
|
+ $content = preg_replace('/\(0 \+ ([^\(\)]+)\)/', '(\\1)', $content);
|
|
|
+ // strip all `0 -` occurrences: calc(0 - 10%) -> calc(-10%)
|
|
|
+ $content = preg_replace('/\(0 \- ([^\(\)]+)\)/', '(-\\1)', $content);
|
|
|
+ // I'm not going to attempt to optimize away `x * 0` instances:
|
|
|
+ // it's dumb enough code already that it likely won't occur, and it's
|
|
|
+ // too complex to do right (order of operations would have to be
|
|
|
+ // respected etc)
|
|
|
+ // what I cared about most here was fixing incorrectly truncated units
|
|
|
+
|
|
|
+ // IE doesn't seem to understand a unitless flex-basis value, so let's
|
|
|
+ // add it in again (make it `%`, which is only 1 char: 0%, 0px, 0
|
|
|
+ // anything, it's all just the same)
|
|
|
+ $content = preg_replace('/flex:([^ ]+ [^ ]+ )0([;\}])/', 'flex:${1}0%${2}', $content);
|
|
|
+ $content = preg_replace('/flex-basis:0([;\}])/', 'flex-basis:0%${1}', $content);
|
|
|
+
|
|
|
return $content;
|
|
|
}
|
|
|
|
|
|
@@ -591,7 +589,7 @@ class CSS extends Minify
|
|
|
*/
|
|
|
protected function stripEmptyTags($content)
|
|
|
{
|
|
|
- return preg_replace('/(^|\})[^\{\}]+\{\s*\}/', '\\1', $content);
|
|
|
+ return preg_replace('/(^|\}|;)[^\{\};]+\{\s*\}/', '\\1', $content);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -605,7 +603,7 @@ class CSS extends Minify
|
|
|
/**
|
|
|
* Strip whitespace.
|
|
|
*
|
|
|
- * @param string $content The CSS content to strip the whitespace for.
|
|
|
+ * @param string $content The CSS content to strip the whitespace for
|
|
|
*
|
|
|
* @return string
|
|
|
*/
|
|
|
@@ -639,7 +637,7 @@ class CSS extends Minify
|
|
|
/**
|
|
|
* Check if file is small enough to be imported.
|
|
|
*
|
|
|
- * @param string $path The path to the file.
|
|
|
+ * @param string $path The path to the file
|
|
|
*
|
|
|
* @return bool
|
|
|
*/
|
|
|
@@ -647,4 +645,30 @@ class CSS extends Minify
|
|
|
{
|
|
|
return ($size = @filesize($path)) && $size <= $this->maxImportSize * 1024;
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Check if file a file can be imported, going by the path.
|
|
|
+ *
|
|
|
+ * @param string $path
|
|
|
+ *
|
|
|
+ * @return bool
|
|
|
+ */
|
|
|
+ protected function canImportByPath($path)
|
|
|
+ {
|
|
|
+ return preg_match('/^(data:|https?:|\\/)/', $path) === 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Return a converter to update relative paths to be relative to the new
|
|
|
+ * destination.
|
|
|
+ *
|
|
|
+ * @param string $source
|
|
|
+ * @param string $target
|
|
|
+ *
|
|
|
+ * @return ConverterInterface
|
|
|
+ */
|
|
|
+ protected function getPathConverter($source, $target)
|
|
|
+ {
|
|
|
+ return new Converter($source, $target);
|
|
|
+ }
|
|
|
}
|