1
0
mirror of https://github.com/mrclay/minify.git synced 2025-08-12 09:05:08 +02:00

Work on: Issue 120, Issue 170, Issue 152, Issue 125, Issue 134, Issue 143

Broke some tests
This commit is contained in:
Steve Clay
2010-05-10 07:44:40 +00:00
parent f4c688b338
commit 02dbf14c8e
17 changed files with 259 additions and 111 deletions

View File

@@ -16,12 +16,14 @@ See UPGRADING.txt for instructions.
INSTALLATION AND USAGE: INSTALLATION AND USAGE:
1. Place the /min/ directory as a child of your DOCUMENT_ROOT 1. Place the /min/ directory as a child of your DOCUMENT_ROOT
directory: i.e. you will have: /home/user/www/public_html/min directory: i.e. you will have: /home/user/www/min
2. Open http://yourdomain/min/ in a web browser. This will forward 2. Open http://yourdomain/min/ in a web browser. This will forward
you to the Minify URI Builder application, which will help you you to the Minify URI Builder application, which will help you
quickly start using Minify to serve content on your site. quickly start using Minify to serve content on your site.
See the User Guide: http://code.google.com/p/minify/wiki/UserGuide
UNIT TESTING: UNIT TESTING:
@@ -36,12 +38,6 @@ components with more verbose output.)
3. Remove /min_unit_tests/ from your DOCUMENT_ROOT when you are done. 3. Remove /min_unit_tests/ from your DOCUMENT_ROOT when you are done.
EXTRAS:
The min_extras folder contains files for benchmarking using Apache ab on Windows
and a couple single-use tools. DO NOT place this on your production server.
FILE ENCODINGS FILE ENCODINGS
Minify *should* work fine with files encoded in UTF-8 or other 8-bit Minify *should* work fine with files encoded in UTF-8 or other 8-bit

View File

@@ -1,4 +1,13 @@
<IfModule mod_rewrite.c> <IfModule mod_rewrite.c>
RewriteEngine on RewriteEngine on
RewriteRule ^([a-z]=.*) index.php?$1 [L,NE]
</IfModule> # You may need RewriteBase on some servers
#RewriteBase /min
# rewrite URLs like "/min/f=..." to "/min/?f=..."
RewriteRule ^([bfg]=.*) index.php?$1 [L,NE]
</IfModule>
<IfModule mod_env.c>
# In case AddOutputFilterByType has been added
SetEnv no-gzip
</IfModule>

View File

@@ -66,14 +66,14 @@ to the /js and /themes/default directories, use:
$min_serveOptions['minApp']['allowDirs'] = array('//js', '//themes/default'); $min_serveOptions['minApp']['allowDirs'] = array('//js', '//themes/default');
GROUPS: FASTER PERFORMANCE AND BETTER URLS GROUPS: NICER URLS
For the best performance, edit groupsConfig.php to pre-specify groups of files For nicer URLs, edit groupsConfig.php to pre-specify groups of files
to be combined under preset keys. E.g., here's an example configuration in to be combined under preset keys. E.g., here's an example configuration in
groupsConfig.php: groupsConfig.php:
return array( return array(
'js' => array('//js/Class.js', '//js/email.js') 'js' => array('//js/Class.js', '//js/email.js')
); );
This pre-selects the following files to be combined under the key "js": This pre-selects the following files to be combined under the key "js":

View File

@@ -202,7 +202,7 @@ var MUB = {
$('#sources').html(''); $('#sources').html('');
$('#add button').click(MUB.addButtonClick); $('#add button').click(MUB.addButtonClick);
// make easier to copy text out of // make easier to copy text out of
$('#uriHtml, #groupConfig').click(function () { $('#uriHtml, #groupConfig, #symlinkOpt').click(function () {
this.select(); this.select();
}).focus(function () { }).focus(function () {
this.select(); this.select();
@@ -223,10 +223,9 @@ var MUB = {
return false; return false;
}).attr({title:'Add file +'}); }).attr({title:'Add file +'});
} else { } else {
// copy bookmarklet code into href // setup bookmarklet 1
var bmUri = location.pathname.replace(/\/[^\/]*$/, '/bm.js').substr(1);
$.ajax({ $.ajax({
url : '../?f=' + bmUri url : '../?f=' + location.pathname.replace(/\/[^\/]*$/, '/bm.js').substr(1)
,success : function (code) { ,success : function (code) {
$('#bm')[0].href = code $('#bm')[0].href = code
.replace('%BUILDER_URL%', location.href) .replace('%BUILDER_URL%', location.href)
@@ -237,6 +236,14 @@ var MUB = {
$.browser.msie && $('#getBm p:last').append(' Sorry, not supported in MSIE!'); $.browser.msie && $('#getBm p:last').append(' Sorry, not supported in MSIE!');
MUB.addButtonClick(); MUB.addButtonClick();
} }
// setup bookmarklet 2
$.ajax({
url : '../?f=' + location.pathname.replace(/\/[^\/]*$/, '/bm2.js').substr(1)
,success : function (code) {
$('#bm2')[0].href = code.replace(/\n/g, ' ');
}
,dataType : 'text'
});
MUB.checkRewrite(); MUB.checkRewrite();
} }
}; };

14
min/builder/bm2.js Normal file
View File

@@ -0,0 +1,14 @@
javascript:(function(){
var d = document
,c = d.cookie
,m = c.match(/\bminDebug=([^; ]+)/)
,v = m ? decodeURIComponent(m[1]) : ''
,p = prompt('Debug Minify URIs on "' + location.hostname + '" \ncontaining: (empty to disable)', v)
;
if (p === null) return;
p = p.replace(/\s+/g, '');
v = (p === '')
? 'minDebug=; expires=Fri, 27 Jul 2001 02:47:11 UTC; path=/'
: 'minDebug=' + encodeURIComponent(p) + '; path=/';
d.cookie = v;
})();

View File

@@ -8,6 +8,20 @@ if (phpversion() < 5) {
$encodeOutput = (function_exists('gzdeflate') $encodeOutput = (function_exists('gzdeflate')
&& !ini_get('zlib.output_compression')); && !ini_get('zlib.output_compression'));
// recommend $min_symlinks setting for Apache UserDir
$symlinkOption = '';
if (0 === strpos($_SERVER["SERVER_SOFTWARE"], 'Apache/')
&& preg_match('@^/\\~(\\w+)/@', $_SERVER['REQUEST_URI'], $m)
) {
$userDir = DIRECTORY_SEPARATOR . $m[1] . DIRECTORY_SEPARATOR;
if (false !== strpos(__FILE__, $userDir)) {
$sm = array();
$sm["//~{$m[1]}"] = dirname(dirname(__FILE__));
$array = str_replace('array (', 'array(', var_export($sm, 1));
$symlinkOption = "\$min_symlinks = $array;";
}
}
require dirname(__FILE__) . '/../config.php'; require dirname(__FILE__) . '/../config.php';
if (! $min_enableBuilder) { if (! $min_enableBuilder) {
@@ -18,8 +32,8 @@ if (! $min_enableBuilder) {
ob_start(); ob_start();
?> ?>
<!DOCTYPE HTML> <!DOCTYPE HTML>
<meta name="ROBOTS" content="NOINDEX, NOFOLLOW">
<title>Minify URI Builder</title> <title>Minify URI Builder</title>
<meta name="ROBOTS" content="NOINDEX, NOFOLLOW">
<style type="text/css"> <style type="text/css">
body {margin:1em 60px;} body {margin:1em 60px;}
h1, h2, h3 {margin-left:-25px; position:relative;} h1, h2, h3 {margin-left:-25px; position:relative;}
@@ -39,14 +53,24 @@ b {color:#c00}
.topWarning a {color:#fff;} .topWarning a {color:#fff;}
</style> </style>
<body> <body>
<?php if ($symlinkOption): ?>
<div class=topNote><strong>Note:</strong> It looks like you're running Minify in a user
directory. You may need the following option in /min/config.php to have URIs
correctly rewritten in CSS output:
<br><textarea id=symlinkOpt rows=3 cols=80 readonly><?php echo htmlspecialchars($symlinkOption); ?></textarea>
</div>
<?php endif; ?>
<p class=topWarning id=jsDidntLoad><strong>Uh Oh.</strong> Minify was unable to <p class=topWarning id=jsDidntLoad><strong>Uh Oh.</strong> Minify was unable to
serve the Javascript for this app. <a href="http://code.google.com/p/minify/wiki/Debugging">Enable serve the Javascript for this app. To troubleshoot this,
FirePHP debugging</a> and request the <a id=builderScriptSrc href=#>Minify URL</a> directly. <a href="http://code.google.com/p/minify/wiki/Debugging">enable FirePHP debugging</a>
and request the <a id=builderScriptSrc href=#>Minify URL</a> directly. Hopefully the
FirePHP console will report the cause of the error.
</p> </p>
<?php if (! isset($min_cachePath)): ?> <?php if (! isset($min_cachePath)): ?>
<p class=topNote><strong>Note:</strong> Please set <code>$min_cachePath</code> <p class=topNote><strong>Note:</strong> You can set <code>$min_cachePath</code>
in /min/config.php to improve performance.</p> in /min/config.php to slightly improve performance.</p>
<?php endIf; ?> <?php endIf; ?>
<p id=minRewriteFailed class="hide"><strong>Note:</strong> Your webserver does not seem to <p id=minRewriteFailed class="hide"><strong>Note:</strong> Your webserver does not seem to
@@ -111,14 +135,21 @@ in your list, and move any others to the top of the first file in your list
<p>If you desire, you can use Minify URIs in imports and they will not be touched <p>If you desire, you can use Minify URIs in imports and they will not be touched
by Minify. E.g. <code>@import "<span class=minRoot>/min/?</span>g=css2";</code></p> by Minify. E.g. <code>@import "<span class=minRoot>/min/?</span>g=css2";</code></p>
<h3>Debug Mode</h3>
<p>When /min/config.php has <code>$min_allowDebugFlag = <strong>true</strong>;</code>
you can get debug output by appending <code>&amp;debug</code> to a Minify URL, or
by sending the cookie <code>minDebug=&lt;match&gt;</code>, where <code>minDebug=&lt;match&gt;</code>
should match the Minify URIs you'd like to debug. This bookmarklet will allow you to
set this cookie.</p>
<p><a id=bm2>Minify Debug</a> <small>(right-click, add to bookmarks)</small></p>
</div><!-- #app --> </div><!-- #app -->
<hr> <hr>
<p>Need help? Search or post to the <a class=ext <p>Need help? Check the <a href="http://code.google.com/p/minify/w/list?can=3">wiki</a>,
href="http://groups.google.com/group/minify">Minify discussion list</a>.</p> or post to the <a class=ext href="http://groups.google.com/group/minify">discussion
<p><small>This app is minified :) <a class=ext list</a>.</p>
href="http://code.google.com/p/minify/source/browse/trunk/min/builder/index.php">view <p><small>This app is minified :)</small></p>
source</a></small></p>
<script type="text/javascript" <script type="text/javascript"
src="http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js"></script> src="http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js"></script>
@@ -163,9 +194,22 @@ $(function () {
</script> </script>
</body> </body>
<?php <?php
$content = ob_get_clean();
$serveOpts = array( // setup Minify
'content' => ob_get_contents() set_include_path(dirname(__FILE__) . '/../lib' . PATH_SEPARATOR . get_include_path());
require 'Minify.php';
if (0 === stripos(PHP_OS, 'win')) {
Minify::setDocRoot(); // we may be on IIS
}
Minify::setCache(
isset($min_cachePath) ? $min_cachePath : ''
,$min_cacheFileLocking
);
Minify::$uploaderHoursBehind = $min_uploaderHoursBehind;
Minify::serve('Page', array(
'content' => $content
,'id' => __FILE__ ,'id' => __FILE__
,'lastModifiedTime' => max( ,'lastModifiedTime' => max(
// regenerate cache if either of these change // regenerate cache if either of these change
@@ -174,17 +218,4 @@ $serveOpts = array(
) )
,'minifyAll' => true ,'minifyAll' => true
,'encodeOutput' => $encodeOutput ,'encodeOutput' => $encodeOutput
); ));
ob_end_clean();
set_include_path(dirname(__FILE__) . '/../lib' . PATH_SEPARATOR . get_include_path());
require 'Minify.php';
if (0 === stripos(PHP_OS, 'win')) {
Minify::setDocRoot(); // we may be on IIS
}
Minify::setCache(isset($min_cachePath) ? $min_cachePath : null);
Minify::$uploaderHoursBehind = $min_uploaderHoursBehind;
Minify::serve('Page', $serveOpts);

View File

@@ -7,22 +7,12 @@
*/ */
/**
* In 'debug' mode, Minify can combine files with no minification and
* add comments to indicate line #s of the original files.
*
* To allow debugging, set this option to true and add "&debug=1" to
* a URI. E.g. /min/?f=script1.js,script2.js&debug=1
*/
$min_allowDebugFlag = false;
/** /**
* Set to true to log messages to FirePHP (Firefox Firebug addon). * Set to true to log messages to FirePHP (Firefox Firebug addon).
* Set to false for no error logging (Minify may be slightly faster). * Set to false for no error logging (Minify may be slightly faster).
* @link http://www.firephp.org/ * @link http://www.firephp.org/
* *
* If you want to use a custom error logger, set this to your logger * If you want to use a custom error logger, set this to your logger
* instance. Your object should have a method log(string $message). * instance. Your object should have a method log(string $message).
* *
* @todo cache system does not have error logging yet. * @todo cache system does not have error logging yet.
@@ -30,6 +20,21 @@ $min_allowDebugFlag = false;
$min_errorLogger = false; $min_errorLogger = false;
/**
* To allow debugging, you must set this option to true.
*
* Once true, you can send the cookie minDebug to request debug mode output. The
* cookie value should match the URIs you'd like to debug. E.g. to debug
* /min/f=file1.js send the cookie minDebug=file1.js
* You can manually enable debugging by appending "&debug" to a URI.
* E.g. /min/?f=script1.js,script2.js&debug
*
* In 'debug' mode, Minify combines files with no minification and adds comments
* to indicate line #s of the original files.
*/
$min_allowDebugFlag = false;
/** /**
* Allow use of the Minify URI Builder app. If you no longer need * Allow use of the Minify URI Builder app. If you no longer need
* this, set to false. * this, set to false.

View File

@@ -35,8 +35,15 @@ foreach ($min_symlinks as $uri => $target) {
$min_serveOptions['minApp']['allowDirs'][] = $target; $min_serveOptions['minApp']['allowDirs'][] = $target;
} }
if ($min_allowDebugFlag && isset($_GET['debug'])) { if ($min_allowDebugFlag) {
$min_serveOptions['debug'] = true; if (! empty($_COOKIE['minDebug'])
&& false !== strpos($_SERVER['REQUEST_URI'], $_COOKIE['minDebug'])) {
$min_serveOptions['debug'] = true;
}
// allow GET to override
if (isset($_GET['debug'])) {
$min_serveOptions['debug'] = true;
}
} }
if ($min_errorLogger) { if ($min_errorLogger) {

View File

@@ -75,9 +75,8 @@ class HTTP_ConditionalGet {
/** /**
* @param array $spec options * @param array $spec options
* *
* 'isPublic': (bool) if true, the Cache-Control header will contain * 'isPublic': (bool) if false, the Cache-Control header will contain
* "public", allowing proxies to cache the content. Otherwise "private" will * "private", allowing only browser caching. (default false)
* be sent, allowing only browser caching. (default false)
* *
* 'lastModifiedTime': (int) if given, both ETag AND Last-Modified headers * 'lastModifiedTime': (int) if given, both ETag AND Last-Modified headers
* will be sent with content. This is recommended. * will be sent with content. This is recommended.
@@ -150,7 +149,10 @@ class HTTP_ConditionalGet {
} elseif (isset($spec['contentHash'])) { // Use the hash as the ETag } elseif (isset($spec['contentHash'])) { // Use the hash as the ETag
$this->_setEtag($spec['contentHash'] . $etagAppend, $scope); $this->_setEtag($spec['contentHash'] . $etagAppend, $scope);
} }
$this->_headers['Cache-Control'] = "max-age={$maxAge}, {$scope}"; $privacy = ($scope === 'private')
? ', private'
: '';
$this->_headers['Cache-Control'] = "max-age={$maxAge}{$privacy}";
// invalidate cache if disabled, otherwise check // invalidate cache if disabled, otherwise check
$this->cacheIsValid = (isset($spec['invalidate']) && $spec['invalidate']) $this->cacheIsValid = (isset($spec['invalidate']) && $spec['invalidate'])
? false ? false
@@ -332,11 +334,8 @@ class HTTP_ConditionalGet {
if (!isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { if (!isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
return false; return false;
} }
$ifModifiedSince = $_SERVER['HTTP_IF_MODIFIED_SINCE']; // strip off IE's extra data (semicolon)
if (false !== ($semicolon = strrpos($ifModifiedSince, ';'))) { list($ifModifiedSince) = explode(';', $_SERVER['HTTP_IF_MODIFIED_SINCE'], 2);
// IE has tacked on extra data to this header, strip it
$ifModifiedSince = substr($ifModifiedSince, 0, $semicolon);
}
if (strtotime($ifModifiedSince) >= $this->_lmTime) { if (strtotime($ifModifiedSince) >= $this->_lmTime) {
// Apache 2.2's behavior. If there was no ETag match, send the // Apache 2.2's behavior. If there was no ETag match, send the
// non-encoded version of the ETag value. // non-encoded version of the ETag value.

View File

@@ -171,7 +171,8 @@ class JSMin {
} }
if (ord($this->a) <= self::ORD_LF) { if (ord($this->a) <= self::ORD_LF) {
throw new JSMin_UnterminatedStringException( throw new JSMin_UnterminatedStringException(
"Unterminated String: {$str}"); "JSMin: Unterminated String at byte "
. $this->inputIndex . ": {$str}");
} }
$str .= $this->a; $str .= $this->a;
if ($this->a === '\\') { if ($this->a === '\\') {
@@ -198,7 +199,8 @@ class JSMin {
$pattern .= $this->a; $pattern .= $this->a;
} elseif (ord($this->a) <= self::ORD_LF) { } elseif (ord($this->a) <= self::ORD_LF) {
throw new JSMin_UnterminatedRegExpException( throw new JSMin_UnterminatedRegExpException(
"Unterminated RegExp: {$pattern}"); "JSMin: Unterminated RegExp at byte "
. $this->inputIndex .": {$pattern}");
} }
$this->output .= $this->a; $this->output .= $this->a;
} }
@@ -310,7 +312,9 @@ class JSMin {
return ' '; return ' ';
} }
} elseif ($get === null) { } elseif ($get === null) {
throw new JSMin_UnterminatedCommentException("Unterminated Comment: /*{$comment}"); throw new JSMin_UnterminatedCommentException(
"JSMin: Unterminated comment at byte "
. $this->inputIndex . ": /*{$comment}");
} }
$comment .= $get; $comment .= $get;
} }

View File

@@ -227,6 +227,8 @@ class Minify {
); );
if (self::$_options['maxAge'] > 0) { if (self::$_options['maxAge'] > 0) {
$cgOptions['maxAge'] = self::$_options['maxAge']; $cgOptions['maxAge'] = self::$_options['maxAge'];
} elseif (self::$_options['debug']) {
$cgOptions['invalidate'] = true;
} }
$cg = new HTTP_ConditionalGet($cgOptions); $cg = new HTTP_ConditionalGet($cgOptions);
if ($cg->cacheIsValid) { if ($cg->cacheIsValid) {
@@ -267,7 +269,7 @@ class Minify {
// the goal is to use only the cache methods to sniff the length and // the goal is to use only the cache methods to sniff the length and
// output the content, as they do not require ever loading the file into // output the content, as they do not require ever loading the file into
// memory. // memory.
$cacheId = 'minify_' . self::_getCacheId(); $cacheId = self::_getCacheId();
$fullCacheId = (self::$_options['encodeMethod']) $fullCacheId = (self::$_options['encodeMethod'])
? $cacheId . '.gz' ? $cacheId . '.gz'
: $cacheId; : $cacheId;
@@ -489,7 +491,12 @@ class Minify {
if ($minifier) { if ($minifier) {
self::$_controller->loadMinifier($minifier); self::$_controller->loadMinifier($minifier);
// get source content and minify it // get source content and minify it
$pieces[] = call_user_func($minifier, $source->getContent(), $options); try {
$pieces[] = call_user_func($minifier, $source->getContent(), $options);
} catch (Exception $e) {
throw new Exception("Exception in " . $source->getId() .
": " . $e->getMessage());
}
} else { } else {
$pieces[] = $source->getContent(); $pieces[] = $source->getContent();
} }
@@ -515,17 +522,23 @@ class Minify {
* *
* Any settings that could affect output are taken into consideration * Any settings that could affect output are taken into consideration
* *
* @param string $prefix
*
* @return string * @return string
*/ */
protected static function _getCacheId() protected static function _getCacheId($prefix = 'minify')
{ {
return md5(serialize(array( $name = preg_replace('/[^a-zA-Z0-9\\.=_,]/', '', self::$_controller->selectionId);
$name = preg_replace('/\\.+/', '.', $name);
$name = substr($name, 0, 250 - 34 - strlen($prefix));
$md5 = md5(serialize(array(
Minify_Source::getDigest(self::$_controller->sources) Minify_Source::getDigest(self::$_controller->sources)
,self::$_options['minifiers'] ,self::$_options['minifiers']
,self::$_options['minifierOptions'] ,self::$_options['minifierOptions']
,self::$_options['postprocessor'] ,self::$_options['postprocessor']
,self::$_options['bubbleCssImports'] ,self::$_options['bubbleCssImports']
))); )));
return "{$prefix}_{$name}_{$md5}";
} }
/** /**

View File

@@ -144,6 +144,15 @@ abstract class Minify_Controller_Base {
*/ */
public $sources = array(); public $sources = array();
/**
* 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 short name to place inside cache id
*/
public $selectionId = '';
/** /**
* Mix in default controller options with user-given options * Mix in default controller options with user-given options
* *

View File

@@ -40,7 +40,8 @@ class Minify_Controller_MinApp extends Minify_Controller_Base {
$this->log("A group configuration for \"{$_GET['g']}\" was not set"); $this->log("A group configuration for \"{$_GET['g']}\" was not set");
return $options; return $options;
} }
$this->selectionId = "g=" . $_GET['g'];
$files = $cOptions['groups'][$_GET['g']]; $files = $cOptions['groups'][$_GET['g']];
// if $files is a single object, casting will break it // if $files is a single object, casting will break it
if (is_object($files)) { if (is_object($files)) {
@@ -70,7 +71,7 @@ class Minify_Controller_MinApp extends Minify_Controller_Base {
// respond to. Ideally there should be only one way to reference a file. // respond to. Ideally there should be only one way to reference a file.
if (// verify at least one file, files are single comma separated, if (// verify at least one file, files are single comma separated,
// and are all same extension // and are all same extension
! preg_match('/^[^,]+\\.(css|js)(?:,[^,]+\\.\\1)*$/', $_GET['f']) ! preg_match('/^[^,]+\\.(css|js)(?:,[^,]+\\.\\1)*$/', $_GET['f'], $m)
// no "//" // no "//"
|| strpos($_GET['f'], '//') !== false || strpos($_GET['f'], '//') !== false
// no "\" // no "\"
@@ -81,11 +82,13 @@ class Minify_Controller_MinApp extends Minify_Controller_Base {
$this->log("GET param 'f' invalid (see MinApp.php line 63)"); $this->log("GET param 'f' invalid (see MinApp.php line 63)");
return $options; return $options;
} }
$ext = ".{$m[1]}";
$files = explode(',', $_GET['f']); $files = explode(',', $_GET['f']);
if ($files != array_unique($files)) { if ($files != array_unique($files)) {
$this->log("Duplicate files specified"); $this->log("Duplicate files specified");
return $options; return $options;
} }
if (isset($_GET['b'])) { if (isset($_GET['b'])) {
// check for validity // check for validity
if (preg_match('@^[^/]+(?:/[^/]+)*$@', $_GET['b']) if (preg_match('@^[^/]+(?:/[^/]+)*$@', $_GET['b'])
@@ -104,6 +107,7 @@ class Minify_Controller_MinApp extends Minify_Controller_Base {
foreach ((array)$cOptions['allowDirs'] as $allowDir) { foreach ((array)$cOptions['allowDirs'] as $allowDir) {
$allowDirs[] = realpath(str_replace('//', $_SERVER['DOCUMENT_ROOT'] . '/', $allowDir)); $allowDirs[] = realpath(str_replace('//', $_SERVER['DOCUMENT_ROOT'] . '/', $allowDir));
} }
$basenames = array(); // just for cache id
foreach ($files as $file) { foreach ($files as $file) {
$path = $_SERVER['DOCUMENT_ROOT'] . $base . $file; $path = $_SERVER['DOCUMENT_ROOT'] . $base . $file;
$file = realpath($path); $file = realpath($path);
@@ -115,8 +119,10 @@ class Minify_Controller_MinApp extends Minify_Controller_Base {
return $options; return $options;
} else { } else {
$sources[] = $this->_getFileSource($file, $cOptions); $sources[] = $this->_getFileSource($file, $cOptions);
$basenames[] = basename($file, $ext);
} }
} }
$this->selectionId = implode(',', $basenames) . $ext;
} }
if ($sources) { if ($sources) {
$this->sources = $sources; $this->sources = $sources;

View File

@@ -40,14 +40,19 @@ class Minify_Controller_Page extends Minify_Controller_Base {
$sourceSpec = array( $sourceSpec = array(
'filepath' => $options['file'] 'filepath' => $options['file']
); );
$f = $options['file'];
} else { } else {
// strip controller options // strip controller options
$sourceSpec = array( $sourceSpec = array(
'content' => $options['content'] 'content' => $options['content']
,'id' => $options['id'] ,'id' => $options['id']
); );
$f = $options['id'];
unset($options['content'], $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'])) { if (isset($options['minifyAll'])) {
// this will be the 2nd argument passed to Minify_HTML::minify() // this will be the 2nd argument passed to Minify_HTML::minify()
$sourceSpec['minifyOptions'] = array( $sourceSpec['minifyOptions'] = array(

4
min_unit_tests/.htaccess Normal file
View File

@@ -0,0 +1,4 @@
<IfModule mod_env.c>
# In case AddOutputFilterByType has been added
SetEnv no-gzip
</IfModule>

View File

@@ -29,7 +29,7 @@ function test_Minify()
'Vary' => 'Accept-Encoding', 'Vary' => 'Accept-Encoding',
'Last-Modified' => gmdate('D, d M Y H:i:s \G\M\T', $lastModified), 'Last-Modified' => gmdate('D, d M Y H:i:s \G\M\T', $lastModified),
'ETag' => "\"pub{$lastModified}\"", 'ETag' => "\"pub{$lastModified}\"",
'Cache-Control' => 'max-age=1800, public', 'Cache-Control' => 'max-age=1800',
'_responseCode' => 'HTTP/1.0 304 Not Modified', '_responseCode' => 'HTTP/1.0 304 Not Modified',
) )
); );
@@ -49,7 +49,7 @@ function test_Minify()
assertTrue( assertTrue(
! class_exists('Minify_CSS', false) ! class_exists('Minify_CSS', false)
&& ! class_exists('Minify_Cache', false) && ! class_exists('Minify_Cache_File', false)
,'Minify : cache, and minifier classes aren\'t loaded for 304s' ,'Minify : cache, and minifier classes aren\'t loaded for 304s'
); );
@@ -70,11 +70,13 @@ function test_Minify()
'Vary' => 'Accept-Encoding', 'Vary' => 'Accept-Encoding',
'Last-Modified' => gmdate('D, d M Y H:i:s \G\M\T', $lastModified), 'Last-Modified' => gmdate('D, d M Y H:i:s \G\M\T', $lastModified),
'ETag' => "\"pub{$lastModified}\"", 'ETag' => "\"pub{$lastModified}\"",
'Cache-Control' => 'max-age=86400, public', 'Cache-Control' => 'max-age=86400',
'Content-Length' => strlen($content), 'Content-Length' => strlen($content),
'Content-Type' => 'application/x-javascript; charset=utf-8', 'Content-Type' => 'application/x-javascript; charset=utf-8',
) )
); );
unset($_SERVER['HTTP_IF_NONE_MATCH']);
unset($_SERVER['HTTP_IF_MODIFIED_SINCE']);
$output = Minify::serve('Files', array( $output = Minify::serve('Files', array(
'files' => array( 'files' => array(
$minifyTestPath . '/email.js' $minifyTestPath . '/email.js'
@@ -185,7 +187,7 @@ function test_Minify()
'Vary' => 'Accept-Encoding', 'Vary' => 'Accept-Encoding',
'Last-Modified' => gmdate('D, d M Y H:i:s \G\M\T', $lastModified), 'Last-Modified' => gmdate('D, d M Y H:i:s \G\M\T', $lastModified),
'ETag' => "\"pub{$lastModified}\"", 'ETag' => "\"pub{$lastModified}\"",
'Cache-Control' => 'max-age=0, public', 'Cache-Control' => 'max-age=0',
'Content-Length' => strlen($expectedContent), 'Content-Length' => strlen($expectedContent),
'Content-Type' => 'text/css; charset=utf-8', 'Content-Type' => 'text/css; charset=utf-8',
) )

View File

@@ -1,5 +1,7 @@
<?php <?php
//phpinfo(); exit();
if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) { if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) {
// called directly // called directly
if (isset($_GET['getOutputCompression'])) { if (isset($_GET['getOutputCompression'])) {
@@ -9,6 +11,10 @@ if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) {
if (isset($_GET['hello'])) { if (isset($_GET['hello'])) {
// try to disable (may not work) // try to disable (may not work)
ini_set('zlib.output_compression', '0'); ini_set('zlib.output_compression', '0');
$type = ($_GET['hello'] == 'js')
? 'application/x-javascript'
: "text/{$_GET['hello']}";
header("Content-Type: {$type}");
echo 'World!'; echo 'World!';
exit(); exit();
} }
@@ -60,42 +66,73 @@ function test_environment()
echo "!WARN: environment : zlib.output_compression is enabled in php.ini" echo "!WARN: environment : zlib.output_compression is enabled in php.ini"
. " or .htaccess.\n"; . " or .htaccess.\n";
} }
$fp = fopen($thisUrl . '?hello=1', 'r', false, stream_context_create(array( $testJs = _test_environment_getHello($thisUrl . '?hello=js');
'http' => array( $passed = assertTrue(
'method' => "GET", $testJs['length'] == 6
'header' => "Accept-Encoding: deflate, gzip\r\n" ,'environment : PHP/server should not auto-encode application/x-javascript output'
)
)));
$meta = stream_get_meta_data($fp);
$passed = true;
foreach ($meta['wrapper_data'] as $i => $header) {
if ((preg_match('@^Content-Length: (\\d+)$@i', $header, $m) && $m[1] !== '6')
|| preg_match('@^Content-Encoding:@i', $header, $m)
) {
$passed = false;
break;
}
}
$streamContents = stream_get_contents($fp);
if ($passed && $streamContents !== 'World!') {
$passed = false;
}
assertTrue(
$passed
,'environment : PHP/server does not auto-HTTP-encode content'
); );
fclose($fp);
$testCss = _test_environment_getHello($thisUrl . '?hello=css');
if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) { $passed = $passed && assertTrue(
if (! $passed) { $testCss['length'] == 6
echo "\nReturned content should be 6 bytes and not HTTP encoded.\n" ,'environment : PHP/server should not auto-encode text/css output'
. "Headers returned by: {$thisUrl}?hello=1\n\n"; );
var_export($meta['wrapper_data']);
$testHtml = _test_environment_getHello($thisUrl . '?hello=html');
$passed = $passed && assertTrue(
$testHtml['length'] == 6
,'environment : PHP/server should not auto-encode text/html output'
);
if (! $passed) {
$testFake = _test_environment_getHello($thisUrl . '?hello=faketype');
if ($testFake['length'] == 6) {
echo "!NOTE: environment : Server does not auto-encode arbitrary types. This\n"
. " may indicate that the auto-encoding is caused by Apache's \n"
. " AddOutputFilterByType.";
} }
} }
} }
function _test_environment_getHello($url)
{
$fp = fopen($url, 'r', false, stream_context_create(array(
'http' => array(
'method' => "GET",
'timeout' => '10',
'header' => "Accept-Encoding: deflate, gzip\r\n",
)
)));
$meta = stream_get_meta_data($fp);
$encoding = '';
$length = 0;
foreach ($meta['wrapper_data'] as $i => $header) {
if (preg_match('@^Content-Length:\\s*(\\d+)$@i', $header, $m)) {
$length = $m[1];
} elseif (preg_match('@^Content-Encoding:\\s*(\\S+)$@i', $header, $m)) {
if ($m[1] !== 'identity') {
$encoding = $m[1];
}
}
}
$streamContents = stream_get_contents($fp);
fclose($fp);
if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) {
if ($length != 6) {
echo "\nReturned content should be 6 bytes and not HTTP encoded.\n"
. "Headers returned by: {$url}\n\n";
var_export($meta['wrapper_data']);
echo "\n\n";
}
}
return array(
'length' => $length
,'encoding' => $encoding
,'bytes' => $streamContents
);
}
test_environment(); test_environment();