@@ -7,14 +7,14 @@ | |||
* </code> | |||
* | |||
* This is a modified port of jsmin.c. Improvements: | |||
* | |||
* | |||
* Does not choke on some regexp literals containing quote characters. E.g. /'/ | |||
* | |||
* Spaces are preserved after some add/sub operators, so they are not mistakenly | |||
* | |||
* Spaces are preserved after some add/sub operators, so they are not mistakenly | |||
* converted to post-inc/dec. E.g. a + ++b -> a+ ++b | |||
* | |||
* Preserves multi-line comments that begin with /*! | |||
* | |||
* | |||
* PHP 5 or higher is required. | |||
* | |||
* Permission is hereby granted to use this version of the library under the | |||
@@ -69,6 +69,7 @@ class JSMin { | |||
protected $lookAhead = null; | |||
protected $output = ''; | |||
protected $lastByteOut = ''; | |||
protected $keptComment = ''; | |||
/** | |||
* Minify Javascript. | |||
@@ -116,8 +117,8 @@ class JSMin { | |||
// determine next command | |||
$command = self::ACTION_KEEP_A; // default | |||
if ($this->a === ' ') { | |||
if (($this->lastByteOut === '+' || $this->lastByteOut === '-') | |||
&& ($this->b === $this->lastByteOut)) { | |||
if (($this->lastByteOut === '+' || $this->lastByteOut === '-') | |||
&& ($this->b === $this->lastByteOut)) { | |||
// Don't delete this space. If we do, the addition/subtraction | |||
// could be parsed as a post-increment | |||
} elseif (! $this->isAlphaNum($this->b)) { | |||
@@ -126,16 +127,17 @@ class JSMin { | |||
} elseif ($this->a === "\n") { | |||
if ($this->b === ' ') { | |||
$command = self::ACTION_DELETE_A_B; | |||
// in case of mbstring.func_overload & 2, must check for null b, | |||
// otherwise mb_strpos will give WARNING | |||
// in case of mbstring.func_overload & 2, must check for null b, | |||
// otherwise mb_strpos will give WARNING | |||
} elseif ($this->b === null | |||
|| (false === strpos('{[(+-', $this->b) | |||
|| (false === strpos('{[(+-!~', $this->b) | |||
&& ! $this->isAlphaNum($this->b))) { | |||
$command = self::ACTION_DELETE_A; | |||
} | |||
} elseif (! $this->isAlphaNum($this->a)) { | |||
if ($this->b === ' ' | |||
|| ($this->b === "\n" | |||
|| ($this->b === "\n" | |||
&& (false === strpos('}])+-"\'', $this->a)))) { | |||
$command = self::ACTION_DELETE_A_B; | |||
} | |||
@@ -160,7 +162,8 @@ class JSMin { | |||
*/ | |||
protected function action($command) | |||
{ | |||
if ($command === self::ACTION_DELETE_A_B | |||
// make sure we don't compress "a + ++b" to "a+++b", etc. | |||
if ($command === self::ACTION_DELETE_A_B | |||
&& $this->b === ' ' | |||
&& ($this->a === '+' || $this->a === '-')) { | |||
// Note: we're at an addition/substraction operator; the inputIndex | |||
@@ -170,58 +173,88 @@ class JSMin { | |||
$command = self::ACTION_KEEP_A; | |||
} | |||
} | |||
switch ($command) { | |||
case self::ACTION_KEEP_A: | |||
case self::ACTION_KEEP_A: // 1 | |||
$this->output .= $this->a; | |||
if ($this->keptComment) { | |||
$this->output = rtrim($this->output, "\n"); | |||
$this->output .= $this->keptComment; | |||
$this->keptComment = ''; | |||
} | |||
$this->lastByteOut = $this->a; | |||
// fallthrough | |||
case self::ACTION_DELETE_A: | |||
// fallthrough intentional | |||
case self::ACTION_DELETE_A: // 2 | |||
$this->a = $this->b; | |||
if ($this->a === "'" || $this->a === '"') { // string literal | |||
$str = $this->a; // in case needed for exception | |||
while (true) { | |||
for(;;) { | |||
$this->output .= $this->a; | |||
$this->lastByteOut = $this->a; | |||
$this->a = $this->get(); | |||
$this->a = $this->get(); | |||
if ($this->a === $this->b) { // end quote | |||
break; | |||
} | |||
if (ord($this->a) <= self::ORD_LF) { | |||
if ($this->isEOF($this->a)) { | |||
$byte = $this->inputIndex - 1; | |||
throw new JSMin_UnterminatedStringException( | |||
"JSMin: Unterminated String at byte " | |||
. $this->inputIndex . ": {$str}"); | |||
"JSMin: Unterminated String at byte {$byte}: {$str}"); | |||
} | |||
$str .= $this->a; | |||
if ($this->a === '\\') { | |||
$this->output .= $this->a; | |||
$this->lastByteOut = $this->a; | |||
$this->a = $this->get(); | |||
$str .= $this->a; | |||
} | |||
} | |||
} | |||
// fallthrough | |||
case self::ACTION_DELETE_A_B: | |||
// fallthrough intentional | |||
case self::ACTION_DELETE_A_B: // 3 | |||
$this->b = $this->next(); | |||
if ($this->b === '/' && $this->isRegexpLiteral()) { // RegExp literal | |||
if ($this->b === '/' && $this->isRegexpLiteral()) { | |||
$this->output .= $this->a . $this->b; | |||
$pattern = '/'; // in case needed for exception | |||
while (true) { | |||
$pattern = '/'; // keep entire pattern in case we need to report it in the exception | |||
for(;;) { | |||
$this->a = $this->get(); | |||
$pattern .= $this->a; | |||
if ($this->a === '[') { | |||
for(;;) { | |||
$this->output .= $this->a; | |||
$this->a = $this->get(); | |||
$pattern .= $this->a; | |||
if ($this->a === ']') { | |||
break; | |||
} | |||
if ($this->a === '\\') { | |||
$this->output .= $this->a; | |||
$this->a = $this->get(); | |||
$pattern .= $this->a; | |||
} | |||
if ($this->isEOF($this->a)) { | |||
throw new JSMin_UnterminatedRegExpException( | |||
"JSMin: Unterminated set in RegExp at byte " | |||
. $this->inputIndex .": {$pattern}"); | |||
} | |||
} | |||
} | |||
if ($this->a === '/') { // end pattern | |||
break; // while (true) | |||
} elseif ($this->a === '\\') { | |||
$this->output .= $this->a; | |||
$this->a = $this->get(); | |||
$pattern .= $this->a; | |||
} elseif (ord($this->a) <= self::ORD_LF) { | |||
$this->a = $this->get(); | |||
$pattern .= $this->a; | |||
} elseif ($this->isEOF($this->a)) { | |||
$byte = $this->inputIndex - 1; | |||
throw new JSMin_UnterminatedRegExpException( | |||
"JSMin: Unterminated RegExp at byte " | |||
. $this->inputIndex .": {$pattern}"); | |||
"JSMin: Unterminated RegExp at byte {$byte}: {$pattern}"); | |||
} | |||
$this->output .= $this->a; | |||
$this->lastByteOut = $this->a; | |||
@@ -237,31 +270,43 @@ class JSMin { | |||
*/ | |||
protected function isRegexpLiteral() | |||
{ | |||
if (false !== strpos("\n{;(,=:[!&|?", $this->a)) { // we aren't dividing | |||
if (false !== strpos("(,=:[!&|?+-~*{;", $this->a)) { | |||
// we obviously aren't dividing | |||
return true; | |||
} | |||
if (' ' === $this->a) { | |||
$length = strlen($this->output); | |||
if ($length < 2) { // weird edge case | |||
return true; | |||
// we have to check for a preceding keyword, and we don't need to pattern | |||
// match over the whole output. | |||
$recentOutput = substr($this->output, -10); | |||
// check if return/typeof directly precede a pattern without a space | |||
foreach (array('return', 'typeof') as $keyword) { | |||
if ($this->a !== substr($keyword, -1)) { | |||
// certainly wasn't keyword | |||
continue; | |||
} | |||
// you can't divide a keyword | |||
if (preg_match('/(?:case|else|in|return|typeof)$/', $this->output, $m)) { | |||
if ($this->output === $m[0]) { // odd but could happen | |||
return true; | |||
} | |||
// make sure it's a keyword, not end of an identifier | |||
$charBeforeKeyword = substr($this->output, $length - strlen($m[0]) - 1, 1); | |||
if (! $this->isAlphaNum($charBeforeKeyword)) { | |||
if (preg_match("~(^|[\\s\\S])" . substr($keyword, 0, -1) . "$~", $recentOutput, $m)) { | |||
if ($m[1] === '' || !$this->isAlphaNum($m[1])) { | |||
return true; | |||
} | |||
} | |||
} | |||
// check all keywords | |||
if ($this->a === ' ' || $this->a === "\n") { | |||
if (preg_match('~(^|[\\s\\S])(?:case|else|in|return|typeof)$~', $recentOutput, $m)) { | |||
if ($m[1] === '' || !$this->isAlphaNum($m[1])) { | |||
return true; | |||
} | |||
} | |||
} | |||
return false; | |||
} | |||
/** | |||
* Get next char. Convert ctrl char to space. | |||
* Return the next character from stdin. Watch out for lookahead. If the character is a control character, | |||
* translate it to a space or linefeed. | |||
* | |||
* @return string | |||
*/ | |||
@@ -270,24 +315,36 @@ class JSMin { | |||
$c = $this->lookAhead; | |||
$this->lookAhead = null; | |||
if ($c === null) { | |||
// getc(stdin) | |||
if ($this->inputIndex < $this->inputLength) { | |||
$c = $this->input[$this->inputIndex]; | |||
$this->inputIndex += 1; | |||
} else { | |||
return null; | |||
$c = null; | |||
} | |||
} | |||
if ($c === "\r" || $c === "\n") { | |||
return "\n"; | |||
if (ord($c) >= self::ORD_SPACE || $c === "\n" || $c === null) { | |||
return $c; | |||
} | |||
if (ord($c) < self::ORD_SPACE) { // control char | |||
return ' '; | |||
if ($c === "\r") { | |||
return "\n"; | |||
} | |||
return $c; | |||
return ' '; | |||
} | |||
/** | |||
* Does $a indicate end of input? | |||
* | |||
* @param string $a | |||
* @return bool | |||
*/ | |||
protected function isEOF($a) | |||
{ | |||
return ord($a) <= self::ORD_LF; | |||
} | |||
/** | |||
* Get next char. If is ctrl character, translate to a space or newline. | |||
* Get next char (without getting it). If is ctrl character, translate to a space or newline. | |||
* | |||
* @return string | |||
*/ | |||
@@ -298,7 +355,7 @@ class JSMin { | |||
} | |||
/** | |||
* Is $c a letter, digit, underscore, dollar sign, escape, or non-ASCII? | |||
* Return true if the character is a letter, digit, underscore, dollar sign, or non-ASCII character. | |||
* | |||
* @param string $c | |||
* | |||
@@ -306,77 +363,84 @@ class JSMin { | |||
*/ | |||
protected function isAlphaNum($c) | |||
{ | |||
return (preg_match('/^[0-9a-zA-Z_\\$\\\\]$/', $c) || ord($c) > 126); | |||
return (preg_match('/^[a-z0-9A-Z_\\$\\\\]$/', $c) || ord($c) > 126); | |||
} | |||
/** | |||
* @return string | |||
* Consume a single line comment from input (possibly retaining it) | |||
*/ | |||
protected function singleLineComment() | |||
protected function consumeSingleLineComment() | |||
{ | |||
$comment = ''; | |||
while (true) { | |||
$get = $this->get(); | |||
$comment .= $get; | |||
if (ord($get) <= self::ORD_LF) { // EOL reached | |||
if (ord($get) <= self::ORD_LF) { // end of line reached | |||
// if IE conditional comment | |||
if (preg_match('/^\\/@(?:cc_on|if|elif|else|end)\\b/', $comment)) { | |||
return "/{$comment}"; | |||
$this->keptComment .= "/{$comment}"; | |||
} | |||
return $get; | |||
return; | |||
} | |||
} | |||
} | |||
/** | |||
* @return string | |||
* Consume a multiple line comment from input (possibly retaining it) | |||
* | |||
* @throws JSMin_UnterminatedCommentException | |||
*/ | |||
protected function multipleLineComment() | |||
protected function consumeMultipleLineComment() | |||
{ | |||
$this->get(); | |||
$comment = ''; | |||
while (true) { | |||
for(;;) { | |||
$get = $this->get(); | |||
if ($get === '*') { | |||
if ($this->peek() === '/') { // end of comment reached | |||
$this->get(); | |||
// if comment preserved by YUI Compressor | |||
if (0 === strpos($comment, '!')) { | |||
return "\n/*!" . substr($comment, 1) . "*/\n"; | |||
} | |||
// if IE conditional comment | |||
if (preg_match('/^@(?:cc_on|if|elif|else|end)\\b/', $comment)) { | |||
return "/*{$comment}*/"; | |||
// preserved by YUI Compressor | |||
if (!$this->keptComment) { | |||
// don't prepend a newline if two comments right after one another | |||
$this->keptComment = "\n"; | |||
} | |||
$this->keptComment .= "/*!" . substr($comment, 1) . "*/\n"; | |||
} else if (preg_match('/^@(?:cc_on|if|elif|else|end)\\b/', $comment)) { | |||
// IE conditional | |||
$this->keptComment .= "/*{$comment}*/"; | |||
} | |||
return ' '; | |||
return; | |||
} | |||
} elseif ($get === null) { | |||
throw new JSMin_UnterminatedCommentException( | |||
"JSMin: Unterminated comment at byte " | |||
. $this->inputIndex . ": /*{$comment}"); | |||
"JSMin: Unterminated comment at byte {$this->inputIndex}: /*{$comment}"); | |||
} | |||
$comment .= $get; | |||
} | |||
} | |||
/** | |||
* Get the next character, skipping over comments. | |||
* Some comments may be preserved. | |||
* Get the next character, skipping over comments. Some comments may be preserved. | |||
* | |||
* @return string | |||
*/ | |||
protected function next() | |||
{ | |||
$get = $this->get(); | |||
if ($get !== '/') { | |||
return $get; | |||
} | |||
switch ($this->peek()) { | |||
case '/': return $this->singleLineComment(); | |||
case '*': return $this->multipleLineComment(); | |||
default: return $get; | |||
if ($get === '/') { | |||
switch ($this->peek()) { | |||
case '/': | |||
$this->consumeSingleLineComment(); | |||
$get = "\n"; | |||
break; | |||
case '*': | |||
$this->consumeMultipleLineComment(); | |||
$get = ' '; | |||
break; | |||
} | |||
} | |||
return $get; | |||
} | |||
} | |||
@@ -3,11 +3,6 @@ | |||
* Class Minify | |||
* @package Minify | |||
*/ | |||
/** | |||
* Minify_Source | |||
*/ | |||
require_once 'Minify/Source.php'; | |||
/** | |||
* Minify - Combines, minifies, and caches JavaScript and CSS files on demand. | |||
@@ -29,7 +24,7 @@ require_once 'Minify/Source.php'; | |||
*/ | |||
class Minify { | |||
const VERSION = '2.1.5'; | |||
const VERSION = '2.2.0'; | |||
const TYPE_CSS = 'text/css'; | |||
const TYPE_HTML = 'text/html'; | |||
// there is some debate over the ideal JS Content-Type, but this is the | |||
@@ -85,7 +80,6 @@ class Minify { | |||
public static function setCache($cache = '', $fileLocking = true) | |||
{ | |||
if (is_string($cache)) { | |||
require_once 'Minify/Cache/File.php'; | |||
self::$_cache = new Minify_Cache_File($cache, $fileLocking); | |||
} else { | |||
self::$_cache = $cache; | |||
@@ -161,9 +155,11 @@ class Minify { | |||
* | |||
* @param array $options controller/serve options | |||
* | |||
* @return mixed null, or, if the 'quiet' option is set to true, an array | |||
* @return null|array if the 'quiet' option is set to true, an array | |||
* with keys "success" (bool), "statusCode" (int), "content" (string), and | |||
* "headers" (array). | |||
* | |||
* @throws Exception | |||
*/ | |||
public static function serve($controller, $options = array()) | |||
{ | |||
@@ -174,10 +170,6 @@ class Minify { | |||
if (is_string($controller)) { | |||
// make $controller into object | |||
$class = 'Minify_Controller_' . $controller; | |||
if (! class_exists($class, false)) { | |||
require_once "Minify/Controller/" | |||
. str_replace('_', '/', $controller) . ".php"; | |||
} | |||
$controller = new $class(); | |||
/* @var Minify_Controller_Base $controller */ | |||
} | |||
@@ -219,8 +211,7 @@ class Minify { | |||
$contentEncoding = self::$_options['encodeMethod']; | |||
} else { | |||
// sniff request header | |||
require_once 'HTTP/Encoder.php'; | |||
// depending on what the client accepts, $contentEncoding may be | |||
// depending on what the client accepts, $contentEncoding may be | |||
// 'x-gzip' while our internal encodeMethod is 'gzip'. Calling | |||
// getAcceptedEncoding(false, false) leaves out compress and deflate as options. | |||
list(self::$_options['encodeMethod'], $contentEncoding) = HTTP_Encoder::getAcceptedEncoding(false, false); | |||
@@ -231,7 +222,6 @@ class Minify { | |||
} | |||
// check client cache | |||
require_once 'HTTP/ConditionalGet.php'; | |||
$cgOptions = array( | |||
'lastModifiedTime' => self::$_options['lastModifiedTime'] | |||
,'isPublic' => self::$_options['isPublic'] | |||
@@ -300,7 +290,7 @@ class Minify { | |||
throw $e; | |||
} | |||
self::$_cache->store($cacheId, $content); | |||
if (function_exists('gzencode')) { | |||
if (function_exists('gzencode') && self::$_options['encodeMethod']) { | |||
self::$_cache->store($cacheId . '.gz', gzencode($content, self::$_options['encodeLevel'])); | |||
} | |||
} | |||
@@ -451,7 +441,7 @@ class Minify { | |||
/** | |||
* Set up sources to use Minify_Lines | |||
* | |||
* @param array $sources Minify_Source instances | |||
* @param Minify_Source[] $sources Minify_Source instances | |||
*/ | |||
protected static function _setupDebug($sources) | |||
{ | |||
@@ -468,6 +458,8 @@ class Minify { | |||
* Combines sources and minifies the result. | |||
* | |||
* @return string | |||
* | |||
* @throws Exception | |||
*/ | |||
protected static function _combineMinify() | |||
{ | |||
@@ -526,7 +518,6 @@ class Minify { | |||
$imploded = implode($implodeSeparator, $groupToProcessTogether); | |||
$groupToProcessTogether = array(); | |||
if ($lastMinifier) { | |||
self::$_controller->loadMinifier($lastMinifier); | |||
try { | |||
$content[] = call_user_func($lastMinifier, $imploded, $lastOptions); | |||
} catch (Exception $e) { | |||
@@ -574,7 +565,7 @@ class Minify { | |||
{ | |||
$name = preg_replace('/[^a-zA-Z0-9\\.=_,]/', '', self::$_controller->selectionId); | |||
$name = preg_replace('/\\.+/', '.', $name); | |||
$name = substr($name, 0, 200 - 34 - strlen($prefix)); | |||
$name = substr($name, 0, 100 - 34 - strlen($prefix)); | |||
$md5 = md5(serialize(array( | |||
Minify_Source::getDigest(self::$_controller->sources) | |||
,self::$_options['minifiers'] | |||
@@ -4,8 +4,6 @@ | |||
* @package Minify | |||
*/ | |||
require_once 'Minify/Source.php'; | |||
/** | |||
* Maintain a single last modification time for a group of Minify sources to | |||
* allow use of far off Expires headers in Minify. | |||
@@ -56,6 +56,7 @@ class Minify_CSS { | |||
public static function minify($css, $options = array()) | |||
{ | |||
$options = array_merge(array( | |||
'compress' => true, | |||
'removeCharsets' => true, | |||
'preserveComments' => true, | |||
'currentDir' => null, | |||
@@ -67,21 +68,20 @@ class Minify_CSS { | |||
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'; | |||
$css = Minify_CommentPreserver::process( | |||
$css | |||
,array('Minify_CSS_Compressor', 'process') | |||
,array($options) | |||
); | |||
if ($options['compress']) { | |||
if (! $options['preserveComments']) { | |||
$css = Minify_CSS_Compressor::process($css, $options); | |||
} else { | |||
$css = Minify_CommentPreserver::process( | |||
$css | |||
,array('Minify_CSS_Compressor', 'process') | |||
,array($options) | |||
); | |||
} | |||
} | |||
if (! $options['currentDir'] && ! $options['prependRelativePath']) { | |||
return $css; | |||
} | |||
require_once 'Minify/CSS/UriRewriter.php'; | |||
if ($options['currentDir']) { | |||
return Minify_CSS_UriRewriter::rewrite( | |||
$css | |||
@@ -70,7 +70,7 @@ class Minify_CSS_UriRewriter { | |||
// rewrite | |||
$css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/' | |||
,array(self::$className, '_processUriCB'), $css); | |||
$css = preg_replace_callback('/url\\(\\s*([^\\)\\s]+)\\s*\\)/' | |||
$css = preg_replace_callback('/url\\(\\s*([\'"](.*?)[\'"]|[^\\)\\s]+)\\s*\\)/' | |||
,array(self::$className, '_processUriCB'), $css); | |||
return $css; | |||
@@ -94,7 +94,7 @@ class Minify_CSS_UriRewriter { | |||
// append | |||
$css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/' | |||
,array(self::$className, '_processUriCB'), $css); | |||
$css = preg_replace_callback('/url\\(\\s*([^\\)\\s]+)\\s*\\)/' | |||
$css = preg_replace_callback('/url\\(\\s*([\'"](.*?)[\'"]|[^\\)\\s]+)\\s*\\)/' | |||
,array(self::$className, '_processUriCB'), $css); | |||
self::$_prependPath = null; | |||
@@ -282,11 +282,8 @@ class Minify_CSS_UriRewriter { | |||
? $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 | |||
) { | |||
// if not root/scheme relative and not starts with scheme | |||
if (!preg_match('~^(/|[a-z]+\:)~', $uri)) { | |||
// URI is file-relative: rewrite depending on options | |||
if (self::$_prependPath === null) { | |||
$uri = self::rewriteRelative($uri, self::$_currentDir, self::$_docRoot, self::$_symlinks); | |||
@@ -98,6 +98,9 @@ class Minify_Cache_File { | |||
{ | |||
if ($this->_locking) { | |||
$fp = fopen($this->_path . '/' . $id, 'rb'); | |||
if (!$fp) { | |||
return false; | |||
} | |||
flock($fp, LOCK_SH); | |||
$ret = stream_get_contents($fp); | |||
flock($fp, LOCK_UN); | |||
@@ -186,7 +189,6 @@ class Minify_Cache_File { | |||
*/ | |||
protected function _log($msg) | |||
{ | |||
require_once 'Minify/Logger.php'; | |||
Minify_Logger::log($msg); | |||
} | |||
@@ -79,33 +79,6 @@ abstract class Minify_Controller_Base { | |||
} | |||
/** | |||
* Load any code necessary to execute the given minifier callback. | |||
* | |||
* The controller is responsible for loading minification code on demand | |||
* via this method. This built-in function will only load classes for | |||
* static method callbacks where the class isn't already defined. It uses | |||
* the PEAR convention, so, given array('Jimmy_Minifier', 'minCss'), this | |||
* function will include 'Jimmy/Minifier.php'. | |||
* | |||
* If you need code loaded on demand and this doesn't suit you, you'll need | |||
* to override this function in your subclass. | |||
* @see Minify_Controller_Page::loadMinifier() | |||
* | |||
* @param callback $minifierCallback callback of minifier function | |||
* | |||
* @return null | |||
*/ | |||
public function loadMinifier($minifierCallback) | |||
{ | |||
if (is_array($minifierCallback) | |||
&& is_string($minifierCallback[0]) | |||
&& !class_exists($minifierCallback[0], false)) { | |||
require str_replace('_', '/', $minifierCallback[0]) . '.php'; | |||
} | |||
} | |||
/** | |||
* Is a user-given file within an allowable directory, existing, | |||
* and having an extension js/css/html/txt ? | |||
* | |||
@@ -244,7 +217,6 @@ abstract class Minify_Controller_Base { | |||
* @return null | |||
*/ | |||
public function log($msg) { | |||
require_once 'Minify/Logger.php'; | |||
Minify_Logger::log($msg); | |||
} | |||
} |
@@ -4,8 +4,6 @@ | |||
* @package Minify | |||
*/ | |||
require_once 'Minify/Controller/Base.php'; | |||
/** | |||
* Controller class for minifying a set of files | |||
* | |||
@@ -4,8 +4,6 @@ | |||
* @package Minify | |||
*/ | |||
require_once 'Minify/Controller/Base.php'; | |||
/** | |||
* Controller class for serving predetermined groups of minimized sets, selected | |||
* by PATH_INFO | |||
@@ -4,8 +4,6 @@ | |||
* @package Minify | |||
*/ | |||
require_once 'Minify/Controller/Base.php'; | |||
/** | |||
* Controller class for requests to /min/index.php | |||
* | |||
@@ -22,6 +20,13 @@ class Minify_Controller_MinApp extends Minify_Controller_Base { | |||
* @return array Minify options | |||
*/ | |||
public function setupSources($options) { | |||
// PHP insecure by default: realpath() and other FS functions can't handle null bytes. | |||
foreach (array('g', 'b', 'f') as $key) { | |||
if (isset($_GET[$key])) { | |||
$_GET[$key] = str_replace("\x00", '', (string)$_GET[$key]); | |||
} | |||
} | |||
// filter controller options | |||
$cOptions = array_merge( | |||
array( | |||
@@ -36,7 +41,6 @@ class Minify_Controller_MinApp extends Minify_Controller_Base { | |||
$sources = array(); | |||
$this->selectionId = ''; | |||
$firstMissingResource = null; | |||
if (isset($_GET['g'])) { | |||
// add group(s) | |||
$this->selectionId .= 'g=' . $_GET['g']; | |||
@@ -195,9 +199,12 @@ class Minify_Controller_MinApp extends Minify_Controller_Base { | |||
protected function _getFileSource($file, $cOptions) | |||
{ | |||
$spec['filepath'] = $file; | |||
if ($cOptions['noMinPattern'] | |||
&& preg_match($cOptions['noMinPattern'], basename($file))) { | |||
$spec['minifier'] = ''; | |||
if ($cOptions['noMinPattern'] && preg_match($cOptions['noMinPattern'], basename($file))) { | |||
if (preg_match('~\.css$~i', $file)) { | |||
$spec['minifyOptions']['compress'] = false; | |||
} else { | |||
$spec['minifier'] = ''; | |||
} | |||
} | |||
return new Minify_Source($spec); | |||
} | |||
@@ -4,8 +4,6 @@ | |||
* @package Minify | |||
*/ | |||
require_once 'Minify/Controller/Base.php'; | |||
/** | |||
* Controller class for serving a single HTML page | |||
* | |||
@@ -59,7 +57,6 @@ class Minify_Controller_Page extends Minify_Controller_Base { | |||
'cssMinifier' => array('Minify_CSS', 'minify') | |||
,'jsMinifier' => array('JSMin', 'minify') | |||
); | |||
$this->_loadCssJsMinifiers = true; | |||
unset($options['minifyAll']); | |||
} | |||
$this->sources[] = new Minify_Source($sourceSpec); | |||
@@ -67,21 +64,5 @@ class Minify_Controller_Page extends Minify_Controller_Base { | |||
$options['contentType'] = Minify::TYPE_HTML; | |||
return $options; | |||
} | |||
protected $_loadCssJsMinifiers = false; | |||
/** | |||
* @see Minify_Controller_Base::loadMinifier() | |||
*/ | |||
public function loadMinifier($minifierCallback) | |||
{ | |||
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'; | |||
require_once 'JSMin.php'; | |||
} | |||
parent::loadMinifier($minifierCallback); // load Minify/HTML.php | |||
} | |||
} | |||
@@ -4,8 +4,6 @@ | |||
* @package Minify | |||
*/ | |||
require_once 'Minify/Controller/Base.php'; | |||
/** | |||
* Controller class for emulating version 1 of minify.php (mostly a proof-of-concept) | |||
* | |||
@@ -26,6 +24,11 @@ class Minify_Controller_Version1 extends Minify_Controller_Base { | |||
* | |||
*/ | |||
public function setupSources($options) { | |||
// PHP insecure by default: realpath() and other FS functions can't handle null bytes. | |||
if (isset($_GET['files'])) { | |||
$_GET['files'] = str_replace("\x00", '', (string)$_GET['files']); | |||
} | |||
self::_setupDefines(); | |||
if (MINIFY_USE_CACHE) { | |||
$cacheDir = defined('MINIFY_CACHE_DIR') | |||
@@ -51,8 +54,7 @@ class Minify_Controller_Version1 extends Minify_Controller_Base { | |||
) { | |||
return $options; | |||
} | |||
$extension = $m[1]; | |||
$files = explode(',', $_GET['files']); | |||
if (count($files) > MINIFY_MAX_FILES) { | |||
return $options; | |||
@@ -63,7 +65,6 @@ class Minify_Controller_Version1 extends Minify_Controller_Base { | |||
. DIRECTORY_SEPARATOR; | |||
$prependAbsPaths = $_SERVER['DOCUMENT_ROOT']; | |||
$sources = array(); | |||
$goodFiles = array(); | |||
$hasBadSource = false; | |||
@@ -1,22 +1,26 @@ | |||
<?php | |||
/** | |||
* Class Minify_HTML | |||
* Class Minify_HTML | |||
* @package Minify | |||
*/ | |||
/** | |||
* Compress HTML | |||
* | |||
* This is a heavy regex-based removal of whitespace, unnecessary comments and | |||
* 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. | |||
* | |||
* STYLE and SCRIPT blocks compressed by callback functions. | |||
* | |||
* A test suite is available. | |||
* | |||
* | |||
* @package Minify | |||
* @author Stephen Clay <steve@mrclay.org> | |||
*/ | |||
class Minify_HTML { | |||
/** | |||
* @var boolean | |||
*/ | |||
protected $_jsCleanComments = true; | |||
/** | |||
* "Minify" an HTML page | |||
@@ -27,21 +31,21 @@ class Minify_HTML { | |||
* | |||
* '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); | |||
$min = new self($html, $options); | |||
return $min->process(); | |||
} | |||
/** | |||
* Create a minifier object | |||
* | |||
@@ -51,14 +55,14 @@ class Minify_HTML { | |||
* | |||
* '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. | |||
* | |||
* | |||
* 'jsCleanComments' : (optional) whether to remove HTML comments beginning and end of script block | |||
* | |||
* '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()) | |||
{ | |||
@@ -72,9 +76,12 @@ class Minify_HTML { | |||
if (isset($options['jsMinifier'])) { | |||
$this->_jsMinifier = $options['jsMinifier']; | |||
} | |||
if (isset($options['jsCleanComments'])) { | |||
$this->_jsCleanComments = (bool)$options['jsCleanComments']; | |||
} | |||
} | |||
/** | |||
* Minify the markeup given in the constructor | |||
* | |||
@@ -124,7 +131,7 @@ class Minify_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' | |||
.'|caption|center|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); | |||
@@ -213,17 +220,19 @@ class Minify_HTML { | |||
// 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); | |||
if ($this->_jsCleanComments) { | |||
$js = preg_replace('/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $js); | |||
} | |||
// remove CDATA section markers | |||
$js = $this->_removeCdata($js); | |||
// minify | |||
$minifier = $this->_jsMinifier | |||
? $this->_jsMinifier | |||
: 'trim'; | |||
: 'trim'; | |||
$js = call_user_func($minifier, $js); | |||
return $this->_reservePlace($this->_needsCdata($js) | |||
@@ -15,10 +15,10 @@ class Minify_HTML_Helper { | |||
public $minAppUri = '/min'; | |||
public $groupsConfigFile = ''; | |||
/* | |||
/** | |||
* Get an HTML-escaped Minify URI for a group or set of files | |||
* | |||
* @param mixed $keyOrFiles a group key or array of filepaths/URIs | |||
* @param string|array $keyOrFiles a group key or array of filepaths/URIs | |||
* @param array $opts options: | |||
* 'farExpires' : (default true) append a modified timestamp for cache revving | |||
* 'debug' : (default false) append debug flag | |||
@@ -51,8 +51,12 @@ class Minify_HTML_Helper { | |||
return htmlspecialchars($uri, ENT_QUOTES, $opts['charset']); | |||
} | |||
/* | |||
/** | |||
* Get non-HTML-escaped URI to minify the specified files | |||
* | |||
* @param bool $farExpires | |||
* @param bool $debug | |||
* @return string | |||
*/ | |||
public function getRawUri($farExpires = true, $debug = false) | |||
{ | |||
@@ -74,6 +78,12 @@ class Minify_HTML_Helper { | |||
return $path; | |||
} | |||
/** | |||
* Set the files that will comprise the URI we're building | |||
* | |||
* @param array $files | |||
* @param bool $checkLastModified | |||
*/ | |||
public function setFiles($files, $checkLastModified = true) | |||
{ | |||
$this->_groupKey = null; | |||
@@ -94,6 +104,12 @@ class Minify_HTML_Helper { | |||
$this->_filePaths = $files; | |||
} | |||
/** | |||
* Set the group of files that will comprise the URI we're building | |||
* | |||
* @param string $key | |||
* @param bool $checkLastModified | |||
*/ | |||
public function setGroup($key, $checkLastModified = true) | |||
{ | |||
$this->_groupKey = $key; | |||
@@ -103,13 +119,23 @@ class Minify_HTML_Helper { | |||
} | |||
if (is_file($this->groupsConfigFile)) { | |||
$gc = (require $this->groupsConfigFile); | |||
if (isset($gc[$key])) { | |||
$this->_lastModified = self::getLastModified($gc[$key]); | |||
$keys = explode(',', $key); | |||
foreach ($keys as $key) { | |||
if (isset($gc[$key])) { | |||
$this->_lastModified = self::getLastModified($gc[$key], $this->_lastModified); | |||
} | |||
} | |||
} | |||
} | |||
} | |||
/** | |||
* Get the max(lastModified) of all files | |||
* | |||
* @param array|string $sources | |||
* @param int $lastModified | |||
* @return int | |||
*/ | |||
public static function getLastModified($sources, $lastModified = 0) | |||
{ | |||
$max = $lastModified; | |||
@@ -142,13 +168,19 @@ class Minify_HTML_Helper { | |||
* @return mixed a common char or '' if any do not match | |||
*/ | |||
protected static function _getCommonCharAtPos($arr, $pos) { | |||
$l = count($arr); | |||
if (!isset($arr[0][$pos])) { | |||
return ''; | |||
} | |||
$c = $arr[0][$pos]; | |||
if ($c === '' || $l === 1) | |||
$l = count($arr); | |||
if ($l === 1) { | |||
return $c; | |||
for ($i = 1; $i < $l; ++$i) | |||
if ($arr[$i][$pos] !== $c) | |||
} | |||
for ($i = 1; $i < $l; ++$i) { | |||
if ($arr[$i][$pos] !== $c) { | |||
return ''; | |||
} | |||
} | |||
return $c; | |||
} | |||
@@ -157,11 +189,11 @@ class Minify_HTML_Helper { | |||
* | |||
* @param array $paths root-relative URIs of files | |||
* @param string $minRoot root-relative URI of the "min" application | |||
* @return string | |||
*/ | |||
protected static function _getShortestUri($paths, $minRoot = '/min/') { | |||
$pos = 0; | |||
$base = ''; | |||
$c; | |||
while (true) { | |||
$c = self::_getCommonCharAtPos($paths, $pos); | |||
if ($c === '') { | |||
@@ -14,13 +14,68 @@ | |||
* @todo can use a stream wrapper to unit test this? | |||
*/ | |||
class Minify_JS_ClosureCompiler { | |||
const URL = 'http://closure-compiler.appspot.com/compile'; | |||
/** | |||
* Minify Javascript code via HTTP request to the Closure Compiler API | |||
* @var string The option key for the maximum POST byte size | |||
*/ | |||
const OPTION_MAX_BYTES = 'maxBytes'; | |||
/** | |||
* @var string The option key for additional params. @see __construct | |||
*/ | |||
const OPTION_ADDITIONAL_OPTIONS = 'additionalParams'; | |||
/** | |||
* @var string The option key for the fallback Minifier | |||
*/ | |||
const OPTION_FALLBACK_FUNCTION = 'fallbackFunc'; | |||
/** | |||
* @var string The option key for the service URL | |||
*/ | |||
const OPTION_COMPILER_URL = 'compilerUrl'; | |||
/** | |||
* @var int The default maximum POST byte size according to https://developers.google.com/closure/compiler/docs/api-ref | |||
*/ | |||
const DEFAULT_MAX_BYTES = 200000; | |||
/** | |||
* @var string[] $DEFAULT_OPTIONS The default options to pass to the compiler service | |||
* | |||
* @note This would be a constant if PHP allowed it | |||
*/ | |||
private static $DEFAULT_OPTIONS = array( | |||
'output_format' => 'text', | |||
'compilation_level' => 'SIMPLE_OPTIMIZATIONS' | |||
); | |||
/** | |||
* @var string $url URL of compiler server. defaults to Google's | |||
*/ | |||
protected $serviceUrl = 'http://closure-compiler.appspot.com/compile'; | |||
/** | |||
* @var int $maxBytes The maximum JS size that can be sent to the compiler server in bytes | |||
*/ | |||
protected $maxBytes = self::DEFAULT_MAX_BYTES; | |||
/** | |||
* @var string[] $additionalOptions Additional options to pass to the compiler service | |||
*/ | |||
protected $additionalOptions = array(); | |||
/** | |||
* @var callable Function to minify JS if service fails. Default is JSMin | |||
*/ | |||
protected $fallbackMinifier = array('JSMin', 'minify'); | |||
/** | |||
* Minify JavaScript code via HTTP request to a Closure Compiler API | |||
* | |||
* @param string $js input code | |||
* @param array $options unused at this point | |||
* @param array $options Options passed to __construct(). @see __construct | |||
* | |||
* @return string | |||
*/ | |||
public static function minify($js, array $options = array()) | |||
@@ -30,63 +85,101 @@ class Minify_JS_ClosureCompiler { | |||
} | |||
/** | |||
* @param array $options Options with keys available below: | |||
* | |||
* fallbackFunc : (callable) function to minify if service unavailable. Default is JSMin. | |||
* | |||
* @param array $options | |||
* compilerUrl : (string) URL to closure compiler server | |||
* | |||
* fallbackFunc : default array($this, 'fallback'); | |||
* maxBytes : (int) The maximum amount of bytes to be sent as js_code in the POST request. | |||
* Defaults to 200000. | |||
* | |||
* additionalParams : (string[]) Additional parameters to pass to the compiler server. Can be anything named | |||
* in https://developers.google.com/closure/compiler/docs/api-ref except for js_code and | |||
* output_info | |||
*/ | |||
public function __construct(array $options = array()) | |||
{ | |||
$this->_fallbackFunc = isset($options['fallbackMinifier']) | |||
? $options['fallbackMinifier'] | |||
: array($this, '_fallback'); | |||
if (isset($options[self::OPTION_FALLBACK_FUNCTION])) { | |||
$this->fallbackMinifier = $options[self::OPTION_FALLBACK_FUNCTION]; | |||
} | |||
if (isset($options[self::OPTION_COMPILER_URL])) { | |||
$this->serviceUrl = $options[self::OPTION_COMPILER_URL]; | |||
} | |||
if (isset($options[self::OPTION_ADDITIONAL_OPTIONS]) && is_array($options[self::OPTION_ADDITIONAL_OPTIONS])) { | |||
$this->additionalOptions = $options[self::OPTION_ADDITIONAL_OPTIONS]; | |||
} | |||
if (isset($options[self::OPTION_MAX_BYTES])) { | |||
$this->maxBytes = (int) $options[self::OPTION_MAX_BYTES]; | |||
} | |||
} | |||
/** | |||
* Call the service to perform the minification | |||
* | |||
* @param string $js JavaScript code | |||
* @return string | |||
* @throws Minify_JS_ClosureCompiler_Exception | |||
*/ | |||
public function min($js) | |||
{ | |||
$postBody = $this->_buildPostBody($js); | |||
$bytes = (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) | |||
? mb_strlen($postBody, '8bit') | |||
: strlen($postBody); | |||
if ($bytes > 200000) { | |||
throw new Minify_JS_ClosureCompiler_Exception( | |||
'POST content larger than 200000 bytes' | |||
); | |||
$postBody = $this->buildPostBody($js); | |||
if ($this->maxBytes > 0) { | |||
$bytes = (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) | |||
? mb_strlen($postBody, '8bit') | |||
: strlen($postBody); | |||
if ($bytes > $this->maxBytes) { | |||
throw new Minify_JS_ClosureCompiler_Exception( | |||
'POST content larger than ' . $this->maxBytes . ' bytes' | |||
); | |||
} | |||
} | |||
$response = $this->_getResponse($postBody); | |||
$response = $this->getResponse($postBody); | |||
if (preg_match('/^Error\(\d\d?\):/', $response)) { | |||
if (is_callable($this->_fallbackFunc)) { | |||
if (is_callable($this->fallbackMinifier)) { | |||
// use fallback | |||
$response = "/* Received errors from Closure Compiler API:\n$response" | |||
. "\n(Using fallback minifier)\n*/\n"; | |||
$response .= call_user_func($this->_fallbackFunc, $js); | |||
$response .= call_user_func($this->fallbackMinifier, $js); | |||
} else { | |||
throw new Minify_JS_ClosureCompiler_Exception($response); | |||
} | |||
} | |||
if ($response === '') { | |||
$errors = $this->_getResponse($this->_buildPostBody($js, true)); | |||
$errors = $this->getResponse($this->buildPostBody($js, true)); | |||
throw new Minify_JS_ClosureCompiler_Exception($errors); | |||
} | |||
return $response; | |||
} | |||
protected $_fallbackFunc = null; | |||
protected function _getResponse($postBody) | |||
/** | |||
* Get the response for a given POST body | |||
* | |||
* @param string $postBody | |||
* @return string | |||
* @throws Minify_JS_ClosureCompiler_Exception | |||
*/ | |||
protected function getResponse($postBody) | |||
{ | |||
$allowUrlFopen = preg_match('/1|yes|on|true/i', ini_get('allow_url_fopen')); | |||
if ($allowUrlFopen) { | |||
$contents = file_get_contents(self::URL, false, stream_context_create(array( | |||
$contents = file_get_contents($this->serviceUrl, false, stream_context_create(array( | |||
'http' => array( | |||
'method' => 'POST', | |||
'header' => 'Content-type: application/x-www-form-urlencoded', | |||
'header' => "Content-type: application/x-www-form-urlencoded\r\nConnection: close\r\n", | |||
'content' => $postBody, | |||
'max_redirects' => 0, | |||
'timeout' => 15, | |||
) | |||
))); | |||
} elseif (defined('CURLOPT_POST')) { | |||
$ch = curl_init(self::URL); | |||
$ch = curl_init($this->serviceUrl); | |||
curl_setopt($ch, CURLOPT_POST, true); | |||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); | |||
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-type: application/x-www-form-urlencoded')); | |||
@@ -100,33 +193,37 @@ class Minify_JS_ClosureCompiler { | |||
"Could not make HTTP request: allow_url_open is false and cURL not available" | |||
); | |||
} | |||
if (false === $contents) { | |||
throw new Minify_JS_ClosureCompiler_Exception( | |||
"No HTTP response from server" | |||
); | |||
} | |||
return trim($contents); | |||
} | |||
protected function _buildPostBody($js, $returnErrors = false) | |||
{ | |||
return http_build_query(array( | |||
'js_code' => $js, | |||
'output_info' => ($returnErrors ? 'errors' : 'compiled_code'), | |||
'output_format' => 'text', | |||
'compilation_level' => 'SIMPLE_OPTIMIZATIONS' | |||
), null, '&'); | |||
return trim($contents); | |||
} | |||
/** | |||
* Default fallback function if CC API fails | |||
* @param string $js | |||
* Build a POST request body | |||
* | |||
* @param string $js JavaScript code | |||
* @param bool $returnErrors | |||
* @return string | |||
*/ | |||
protected function _fallback($js) | |||
protected function buildPostBody($js, $returnErrors = false) | |||
{ | |||
require_once 'JSMin.php'; | |||
return JSMin::minify($js); | |||
return http_build_query( | |||
array_merge( | |||
self::$DEFAULT_OPTIONS, | |||
$this->additionalOptions, | |||
array( | |||
'js_code' => $js, | |||
'output_info' => ($returnErrors ? 'errors' : 'compiled_code') | |||
) | |||
), | |||
null, | |||
'&' | |||
); | |||
} | |||
} | |||
@@ -55,7 +55,11 @@ class Minify_Lines { | |||
$newLines = array(); | |||
while (null !== ($line = array_shift($lines))) { | |||
if (('' !== $id) && (0 == $i % 50)) { | |||
array_push($newLines, '', "/* {$id} */", ''); | |||
if ($inComment) { | |||
array_push($newLines, '', "/* {$id} *|", ''); | |||
} else { | |||
array_push($newLines, '', "/* {$id} */", ''); | |||
} | |||
} | |||
++$i; | |||
$newLines[] = self::_addNote($line, $i, $inComment, $padTo); | |||
@@ -65,7 +69,6 @@ class Minify_Lines { | |||
// check for desired URI rewriting | |||
if (isset($options['currentDir'])) { | |||
require_once 'Minify/CSS/UriRewriter.php'; | |||
Minify_CSS_UriRewriter::$debugText = ''; | |||
$content = Minify_CSS_UriRewriter::rewrite( | |||
$content | |||
@@ -93,6 +96,9 @@ class Minify_Lines { | |||
*/ | |||
private static function _eolInComment($line, $inComment) | |||
{ | |||
// crude way to avoid things like // */ | |||
$line = preg_replace('~//.*?(\\*/|/\\*).*~', '', $line); | |||
while (strlen($line)) { | |||
$search = $inComment | |||
? '*/' | |||
@@ -13,14 +13,17 @@ | |||
* Java environment. | |||
* | |||
* <code> | |||
* Minify_YUICompressor::$jarFile = '/path/to/yuicompressor-2.3.5.jar'; | |||
* Minify_YUICompressor::$jarFile = '/path/to/yuicompressor-2.4.6.jar'; | |||
* Minify_YUICompressor::$tempDir = '/tmp'; | |||
* $code = Minify_YUICompressor::minifyJs( | |||
* $code | |||
* ,array('nomunge' => true, 'line-break' => 1000) | |||
* ); | |||
* </code> | |||
* | |||
* | |||
* Note: In case you run out stack (default is 512k), you may increase stack size in $options: | |||
* array('stack-size' => '2048k') | |||
* | |||
* @todo unit tests, $options docs | |||
* | |||
* @package Minify | |||
@@ -87,7 +90,7 @@ class Minify_YUICompressor { | |||
{ | |||
self::_prepare(); | |||
if (! ($tmpFile = tempnam(self::$tempDir, 'yuic_'))) { | |||
throw new Exception('Minify_YUICompressor : could not create temp file.'); | |||
throw new Exception('Minify_YUICompressor : could not create temp file in "'.self::$tempDir.'".'); | |||
} | |||
file_put_contents($tmpFile, $content); | |||
exec(self::_getCmd($options, $type, $tmpFile), $output, $result_code); | |||
@@ -108,10 +111,15 @@ class Minify_YUICompressor { | |||
,'nomunge' => false | |||
,'preserve-semi' => false | |||
,'disable-optimizations' => false | |||
,'stack-size' => '' | |||
) | |||
,$userOptions | |||
); | |||
$cmd = self::$javaExecutable . ' -jar ' . escapeshellarg(self::$jarFile) | |||
$cmd = self::$javaExecutable | |||
. (!empty($o['stack-size']) | |||
? ' -Xss' . $o['stack-size'] | |||
: '') | |||
. ' -jar ' . escapeshellarg(self::$jarFile) | |||
. " --type {$type}" | |||
. (preg_match('/^[\\da-zA-Z0-9\\-]+$/', $o['charset']) | |||
? " --charset {$o['charset']}" | |||
@@ -134,8 +142,8 @@ class Minify_YUICompressor { | |||
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_readable(self::$jarFile)) { | |||
throw new Exception('Minify_YUICompressor : $jarFile('.self::$jarFile.') is not readable.'); | |||
} | |||
if (! is_dir(self::$tempDir)) { | |||
throw new Exception('Minify_YUICompressor : $tempDir('.self::$tempDir.') is not a valid direcotry.'); | |||
@@ -2,6 +2,9 @@ | |||
namespace MrClay; | |||
use MrClay\Cli\Arg; | |||
use InvalidArgumentException; | |||
/** | |||
* Forms a front controller for a console app, handling and validating arguments (options) | |||
* | |||
@@ -51,7 +54,7 @@ class Cli { | |||
public $isHelpRequest = false; | |||
/** | |||
* @var array of Cli\Arg | |||
* @var Arg[] | |||
*/ | |||
protected $_args = array(); | |||
@@ -80,8 +83,8 @@ class Cli { | |||
} | |||
/** | |||
* @param Cli\Arg|string $letter | |||
* @return Cli\Arg | |||
* @param Arg|string $letter | |||
* @return Arg | |||
*/ | |||
public function addOptionalArg($letter) | |||
{ | |||
@@ -89,8 +92,8 @@ class Cli { | |||
} | |||
/** | |||
* @param Cli\Arg|string $letter | |||
* @return Cli\Arg | |||
* @param Arg|string $letter | |||
* @return Arg | |||
*/ | |||
public function addRequiredArg($letter) | |||
{ | |||
@@ -100,17 +103,17 @@ class Cli { | |||
/** | |||
* @param string $letter | |||
* @param bool $required | |||
* @param Cli\Arg|null $arg | |||
* @return Cli\Arg | |||
* @throws \InvalidArgumentException | |||
* @param Arg|null $arg | |||
* @return Arg | |||
* @throws InvalidArgumentException | |||
*/ | |||
public function addArgument($letter, $required, Cli\Arg $arg = null) | |||
public function addArgument($letter, $required, Arg $arg = null) | |||
{ | |||
if (! preg_match('/^[a-zA-Z]$/', $letter)) { | |||
throw new \InvalidArgumentException('$letter must be in [a-zA-z]'); | |||
throw new InvalidArgumentException('$letter must be in [a-zA-Z]'); | |||
} | |||
if (! $arg) { | |||
$arg = new Cli\Arg($required); | |||
$arg = new Arg($required); | |||
} | |||
$this->_args[$letter] = $arg; | |||
return $arg; | |||
@@ -118,7 +121,7 @@ class Cli { | |||
/** | |||
* @param string $letter | |||
* @return Cli\Arg|null | |||
* @return Arg|null | |||
*/ | |||
public function getArgument($letter) | |||
{ | |||
@@ -143,7 +146,7 @@ class Cli { | |||
$lettersUsed = ''; | |||
foreach ($this->_args as $letter => $arg) { | |||
/* @var Cli\Arg $arg */ | |||
/* @var Arg $arg */ | |||
$options .= $letter; | |||
$lettersUsed .= $letter; | |||
@@ -159,7 +162,7 @@ class Cli { | |||
$this->debug['getopt_return'] = $o; | |||
foreach ($this->_args as $letter => $arg) { | |||
/* @var Cli\Arg $arg */ | |||
/* @var Arg $arg */ | |||
$this->values[$letter] = false; | |||
if (isset($o[$letter])) { | |||
if (is_bool($o[$letter])) { | |||
@@ -295,7 +298,7 @@ class Cli { | |||
{ | |||
$r = "\n"; | |||
foreach ($this->_args as $letter => $arg) { | |||
/* @var Cli\Arg $arg */ | |||
/* @var Arg $arg */ | |||
$desc = $arg->getDescription(); | |||
$flag = " -$letter "; | |||
if ($arg->mayHaveValue) { | |||
@@ -2,6 +2,8 @@ | |||
namespace MrClay\Cli; | |||
use BadMethodCallException; | |||
/** | |||
* An argument for a CLI app. This specifies the argument, what values it expects and | |||
* how it's treated during validation. | |||
@@ -150,7 +152,7 @@ class Arg { | |||
* @param string $name | |||
* @param array $args | |||
* @return Arg | |||
* @throws \BadMethodCallException | |||
* @throws BadMethodCallException | |||
*/ | |||
public function __call($name, array $args = array()) | |||
{ | |||
@@ -160,7 +162,7 @@ class Arg { | |||
$this->spec['mustHaveValue'] = true; | |||
} | |||
} else { | |||
throw new \BadMethodCallException('Method does not exist'); | |||
throw new BadMethodCallException('Method does not exist'); | |||
} | |||
return $this; | |||
} | |||