1
0
mirror of https://github.com/mrclay/minify.git synced 2025-08-11 08:34:19 +02:00

Merge remote-tracking branch 'origin/2.1.4'

This commit is contained in:
Steve Clay
2011-06-25 01:35:02 -04:00
142 changed files with 3201 additions and 1175 deletions

View File

@@ -1,5 +1,21 @@
Minify Release History
Version 2.1.4
* Option to minify JS with Closure Compiler API w/ JSMin failover
* Cookie/bookmarklet-based debug mode. No HTML editing!
* Allows 1 file to be missing w/o complete failure
* Combine multiple groups and files in single URI
* More useful HTML helpers for writing versioned URIs
* More detailed error logging, including minifier exceptions
* Builder offers more helpful messages/PHP environment warnings
* Bypass minification based on filename pattern. e.g. foo.min.js / foo-min.css
* JSMin won't choke on common Closure compiler syntaxes (i+ ++j)
* Better caching in IE6
* Cache ids are influenced by group/file names
* Debug mode for Javascript doesn't break on common XPath strings (Prototype 1.6)
* Removed annoying maxFiles limit
* mbstring.func_overload usage is safer
Version 2.1.3
* HTTP fixes
* ETag generation now valid (different when gzipped)

View File

@@ -8,6 +8,14 @@ and tell clients to cache the file for a period of time.
More info: http://code.google.com/p/minify/
WORDPRESS USER?
These WP plugins integrate Minify into WordPress's style and script hooks to
get you set up faster.
http://wordpress.org/extend/plugins/wp-minify/
http://wordpress.org/extend/plugins/w3-total-cache/
UPGRADING
See UPGRADING.txt for instructions.
@@ -16,12 +24,14 @@ See UPGRADING.txt for instructions.
INSTALLATION AND USAGE:
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
you to the Minify URI Builder application, which will help you
quickly start using Minify to serve content on your site.
See the User Guide: http://code.google.com/p/minify/wiki/UserGuide
UNIT TESTING:
@@ -36,12 +46,6 @@ components with more verbose output.)
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
Minify *should* work fine with files encoded in UTF-8 or other 8-bit

View File

@@ -1,4 +1,13 @@
<IfModule mod_rewrite.c>
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');
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
groupsConfig.php:
return array(
'js' => array('//js/Class.js', '//js/email.js')
return array(
'js' => array('//js/Class.js', '//js/email.js')
);
This pre-selects the following files to be combined under the key "js":
@@ -82,7 +82,7 @@ This pre-selects the following files to be combined under the key "js":
You can now serve these files with this simple URL:
http://example.com/min/?g=js
GROUPS: SPECIFYING FILES OUTSIDE THE DOC_ROOT
@@ -99,6 +99,14 @@ return array(
);
COMBINE MULTIPLE GROUPS AND FILES IN ONE URL
E.g.: http://example.com/min/?g=js&f=more/scripts.js
Separate group keys with commas:
http://example.com/min/?g=baseCss,css1&f=moreStyles.css
FAR-FUTURE EXPIRES HEADERS
Minify can send far-future (one year) Expires headers. To enable this you must
@@ -106,15 +114,20 @@ add a number to the querystring (e.g. /min/?g=js&1234 or /min/f=file.js&1234)
and alter it whenever a source file is changed. If you have a build process you
can use a build/source control revision number.
If you serve files as a group, you can use the utility function Minify_groupUri()
to get a "versioned" Minify URI for use in your HTML. E.g.:
You can alternately use the utility function Minify_getUri() to get a "versioned"
Minify URI for use in your HTML. E.g.:
<?php
// add /min/lib to your include_path first!
require $_SERVER['DOCUMENT_ROOT'] . '/min/utils.php';
$jsUri = Minify_groupUri('js');
echo "<script type='text/javascript' src='{$jsUri}'></script>";
$jsUri = Minify_getUri('js'); // a key in groupsConfig.php
echo "<script src='{$jsUri}'></script>";
$cssUri = Minify_getUri(array(
'//css/styles1.css'
,'//css/styles2.css'
)); // a list of files
echo "<link rel=stylesheet href='{$cssUri}'>";
DEBUG MODE

View File

@@ -1,3 +1,6 @@
/*!
* Minify URI Builder
*/
var MUB = {
_uid : 0
,_minRoot : '/min/?'
@@ -197,11 +200,12 @@ var MUB = {
* Runs on DOMready
*/
,init : function () {
$('#jsDidntLoad').remove();
$('#app').show();
$('#sources').html('');
$('#add button').click(MUB.addButtonClick);
// make easier to copy text out of
$('#uriHtml, #groupConfig').click(function () {
$('#uriHtml, #groupConfig, #symlinkOpt').click(function () {
this.select();
}).focus(function () {
this.select();
@@ -222,10 +226,9 @@ var MUB = {
return false;
}).attr({title:'Add file +'});
} else {
// copy bookmarklet code into href
var bmUri = location.pathname.replace(/\/[^\/]*$/, '/bm.js').substr(1);
// setup bookmarklet 1
$.ajax({
url : '../?f=' + bmUri
url : '../?f=' + location.pathname.replace(/\/[^\/]*$/, '/bm.js').substr(1)
,success : function (code) {
$('#bm')[0].href = code
.replace('%BUILDER_URL%', location.href)
@@ -236,7 +239,15 @@ var MUB = {
$.browser.msie && $('#getBm p:last').append(' Sorry, not supported in MSIE!');
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();
}
};
window.onload = MUB.init;
$(MUB.init);

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

@@ -0,0 +1,15 @@
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 + ' which contain:'
+ '\n(empty for none, space = OR)', v)
;
if (p === null) return;
p = p.replace(/^\s+|\s+$/, '');
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')
&& !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';
if (! $min_enableBuilder) {
@@ -15,13 +29,34 @@ if (! $min_enableBuilder) {
exit();
}
$setIncludeSuccess = set_include_path(dirname(__FILE__) . '/../lib' . PATH_SEPARATOR . get_include_path());
// we do it this way because we want the builder to work after the user corrects
// include_path. (set_include_path returning FALSE is OK).
try {
require_once 'Minify/Cache/File.php';
} catch (Exception $e) {
if (! $setIncludeSuccess) {
echo "Minify: set_include_path() failed. You may need to set your include_path "
."outside of PHP code, e.g., in php.ini.";
} else {
echo $e->getMessage();
}
exit();
}
require 'Minify.php';
$cachePathCode = '';
if (! isset($min_cachePath) && ! function_exists('sys_get_temp_dir')) {
$detectedTmp = Minify_Cache_File::tmp();
$cachePathCode = "\$min_cachePath = " . var_export($detectedTmp, 1) . ';';
}
ob_start();
?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<head>
<meta name="ROBOTS" content="NOINDEX, NOFOLLOW">
<title>Minify URI Builder</title>
<style type="text/css">
<!DOCTYPE HTML>
<title>Minify URI Builder</title>
<meta name="ROBOTS" content="NOINDEX, NOFOLLOW">
<style>
body {margin:1em 60px;}
h1, h2, h3 {margin-left:-25px; position:relative;}
h1 {margin-top:0;}
@@ -37,12 +72,30 @@ h1 {margin-top:0;}
b {color:#c00}
.topNote {background: #ff9; display:inline-block; padding:.5em .6em; margin:0 0 1em;}
.topWarning {background:#c00; color:#fff; padding:.5em .6em; margin:0 0 1em;}
</style>
</head>
.topWarning a {color:#fff;}
#jsDidntLoad {display:none;}
</style>
<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; ?>
<?php if (! isset($min_cachePath)): ?>
<p class=topNote><strong>Note:</strong> Please set <code>$min_cachePath</code>
in /min/config.php to improve performance.</p>
<p class=topWarning id=jsDidntLoad><strong>Uh Oh.</strong> Minify was unable to
serve Javascript for this app. To troubleshoot this,
<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>
<?php if ($cachePathCode): ?>
<p class=topNote><strong>Note:</strong> <code><?php echo
htmlspecialchars($detectedTmp); ?></code> was discovered as a usable temp directory.<br>To
slightly improve performance you can hardcode this in /min/config.php:
<code><?php echo htmlspecialchars($cachePathCode); ?></code></p>
<?php endIf; ?>
<p id=minRewriteFailed class="hide"><strong>Note:</strong> Your webserver does not seem to
@@ -107,20 +160,33 @@ 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
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>&lt;match&gt;</code>
should be a string in 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 -->
<hr>
<p>Need help? Search or post to the <a class=ext
href="http://groups.google.com/group/minify">Minify discussion list</a>.</p>
<p><small>This app is minified :) <a class=ext
href="http://code.google.com/p/minify/source/browse/trunk/min/builder/index.php">view
source</a></small></p>
<p>Need help? Check the <a href="http://code.google.com/p/minify/w/list?can=3">wiki</a>,
or post to the <a class=ext href="http://groups.google.com/group/minify">discussion
list</a>.</p>
<p><small>Powered by Minify <?php echo Minify::VERSION; ?></small></p>
<script type="text/javascript"
src="http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js"></script>
<script type="text/javascript">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js"></script>
<script>
$(function () {
// give Minify a few seconds to serve _index.js before showing scary red warning
setTimeout(function () {
if (! window.MUB) {
// Minify didn't load
$('#jsDidntLoad').css({display:'block'});
}
}, 3000);
// detection of double output encoding
var msg = '<\p class=topWarning><\strong>Warning:<\/strong> ';
var url = 'ocCheck.php?' + (new Date()).getTime();
@@ -148,35 +214,38 @@ $(function () {
});
});
</script>
<script type="text/javascript">
// workaround required to test when /min isn't child of web root
var src = location.pathname.replace(/\/[^\/]*$/, '/_index.js').substr(1);
document.write('<\script type="text/javascript" src="../?f=' + src + '"><\/script>');
<script>
// workaround required to test when /min isn't child of web root
var src = location.pathname.replace(/\/[^\/]*$/, '/_index.js').substr(1);
src = "../?f=" + src;
document.write('<\script type="text/javascript" src="' + src + '"><\/script>');
$(function () {
$('#builderScriptSrc')[0].href = src;
});
</script>
</body>
<?php
$content = ob_get_clean();
$serveOpts = array(
'content' => ob_get_contents()
,'id' => __FILE__
,'lastModifiedTime' => max(
// regenerate cache if either of these change
filemtime(__FILE__)
,filemtime(dirname(__FILE__) . '/../config.php')
)
,'minifyAll' => true
,'encodeOutput' => $encodeOutput
);
ob_end_clean();
set_include_path(dirname(__FILE__) . '/../lib' . PATH_SEPARATOR . get_include_path());
require 'Minify.php';
// setup Minify
if (0 === stripos(PHP_OS, 'win')) {
Minify::setDocRoot(); // we may be on IIS
}
Minify::setCache(isset($min_cachePath) ? $min_cachePath : null);
Minify::setCache(
isset($min_cachePath) ? $min_cachePath : ''
,$min_cacheFileLocking
);
Minify::$uploaderHoursBehind = $min_uploaderHoursBehind;
Minify::serve('Page', $serveOpts);
Minify::serve('Page', array(
'content' => $content
,'id' => __FILE__
,'lastModifiedTime' => max(
// regenerate cache if any of these change
filemtime(__FILE__)
,filemtime(dirname(__FILE__) . '/../config.php')
,filemtime(dirname(__FILE__) . '/../lib/Minify.php')
)
,'minifyAll' => true
,'encodeOutput' => $encodeOutput
));

43
min/builder/test.php Normal file
View File

@@ -0,0 +1,43 @@
<?php
exit();
/* currently unused.
// capture PHP's default setting (may get overridden in config
$_oc = ini_get('zlib.output_compression');
// allow access only if builder is enabled
require dirname(__FILE__) . '/../config.php';
if (! $min_enableBuilder) {
exit();
}
if (isset($_GET['oc'])) {
header('Content-Type: text/plain');
echo (int)$_oc;
} elseif (isset($_GET['text']) && in_array($_GET['text'], array('js', 'css', 'fake'))) {
ini_set('zlib.output_compression', '0');
$type = ($_GET['text'] == 'js')
? 'application/x-javascript'
: "text/{$_GET['text']}";
header("Content-Type: {$type}");
echo 'Hello';
} elseif (isset($_GET['docroot'])) {
if (false === realpath($_SERVER['DOCUMENT_ROOT'])) {
echo "<p class=topWarning><strong>realpath(DOCUMENT_ROOT) failed.</strong> You may need "
. "to set \$min_documentRoot manually (hopefully realpath() is not "
. "broken in your environment).</p>";
}
if (0 !== strpos(realpath(__FILE__), realpath($_SERVER['DOCUMENT_ROOT']))) {
echo "<p class=topWarning><strong>DOCUMENT_ROOT doesn't contain this file.</strong> You may "
. " need to set \$min_documentRoot manually</p>";
}
if (isset($_SERVER['SUBDOMAIN_DOCUMENT_ROOT'])) {
echo "<p class=topNote><strong>\$_SERVER['SUBDOMAIN_DOCUMENT_ROOT'] is set.</strong> "
. "You may need to set \$min_documentRoot to this in config.php</p>";
}
}
//*/

View File

@@ -1,33 +1,38 @@
<?php
/**
* Configuration for default Minify application
* Configuration for "min", the default application built with the Minify
* library
*
* @package Minify
*/
/**
* 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 false for no error logging (Minify may be slightly faster).
* @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).
*
* @todo cache system does not have error logging yet.
*/
$min_errorLogger = false;
/**
* To allow debug mode output, 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
* this, set to false.
@@ -42,6 +47,12 @@ $min_enableBuilder = true;
//$min_cachePath = 'c:\\WINDOWS\\Temp';
//$min_cachePath = '/tmp';
//$min_cachePath = preg_replace('/^\\d+;/', '', session_save_path());
/**
* To use APC/Memcache/ZendPlatform for cache storage, require the class and
* set $min_cachePath to an instance. Example below:
*/
//require dirname(__FILE__) . '/lib/Minify/Cache/APC.php';
//$min_cachePath = new Minify_Cache_APC();
/**
@@ -55,7 +66,7 @@ $min_enableBuilder = true;
* second line. The third line might work on some Apache servers.
*/
$min_documentRoot = '';
//$min_documentRoot = substr(__FILE__, 0, strlen(__FILE__) - 15);
//$min_documentRoot = substr(__FILE__, 0, -15);
//$min_documentRoot = $_SERVER['SUBDOMAIN_DOCUMENT_ROOT'];
@@ -78,9 +89,9 @@ $min_serveOptions['bubbleCssImports'] = false;
/**
* Maximum age of browser cache in seconds. After this period, the browser
* will send another conditional GET. Use a longer period for lower traffic
* but you may want to shorten this before making changes if it's crucial
* Cache-Control: max-age value sent to browser (in seconds). After this period,
* the browser will send another conditional GET. Use a longer period for lower
* traffic but you may want to shorten this before making changes if it's crucial
* those changes are seen immediately.
*
* Note: Despite this setting, if you include a number at the end of the
@@ -89,6 +100,18 @@ $min_serveOptions['bubbleCssImports'] = false;
$min_serveOptions['maxAge'] = 1800;
/**
* To use Google's Closure Compiler API (falling back to JSMin on failure),
* uncomment the following lines:
*/
/*function closureCompiler($js) {
require_once 'Minify/JS/ClosureCompiler.php';
return Minify_JS_ClosureCompiler::minify($js);
}
$min_serveOptions['minifiers']['application/x-javascript'] = 'closureCompiler';
//*/
/**
* If you'd like to restrict the "f" option to files within/below
* particular directories below DOCUMENT_ROOT, set this here.
@@ -105,10 +128,15 @@ $min_serveOptions['maxAge'] = 1800;
*/
$min_serveOptions['minApp']['groupsOnly'] = false;
/**
* Maximum # of files that can be specified in the "f" GET parameter
* By default, Minify will not minify files with names containing .min or -min
* before the extension. E.g. myFile.min.js will not be processed by JSMin
*
* To minify all files, set this option to null. You could also specify your
* own pattern that is matched against the filename.
*/
$min_serveOptions['minApp']['maxFiles'] = 10;
//$min_serveOptions['minApp']['noMinPattern'] = '@[-\\.]min\\.(?:js|css)$@i';
/**

View File

@@ -7,28 +7,11 @@
/**
* You may wish to use the Minify URI Builder app to suggest
* changes. http://yourdomain/min/builder/
*
* See http://code.google.com/p/minify/wiki/CustomSource for other ideas
**/
return array(
// 'js' => array('//js/file1.js', '//js/file2.js'),
// 'css' => array('//css/file1.css', '//css/file2.css'),
// custom source example
/*'js2' => array(
dirname(__FILE__) . '/../min_unit_tests/_test_files/js/before.js',
// do NOT process this file
new Minify_Source(array(
'filepath' => dirname(__FILE__) . '/../min_unit_tests/_test_files/js/before.js',
'minifier' => create_function('$a', 'return $a;')
))
),//*/
/*'js3' => array(
dirname(__FILE__) . '/../min_unit_tests/_test_files/js/before.js',
// do NOT process this file
new Minify_Source(array(
'filepath' => dirname(__FILE__) . '/../min_unit_tests/_test_files/js/before.js',
'minifier' => array('Minify_Packer', 'minify')
))
),//*/
);

View File

@@ -30,19 +30,33 @@ if ($min_documentRoot) {
}
$min_serveOptions['minifierOptions']['text/css']['symlinks'] = $min_symlinks;
// auto-add targets to allowDirs
foreach ($min_symlinks as $uri => $target) {
$min_serveOptions['minApp']['allowDirs'][] = $target;
}
if ($min_allowDebugFlag && isset($_GET['debug'])) {
$min_serveOptions['debug'] = true;
if ($min_allowDebugFlag) {
if (! empty($_COOKIE['minDebug'])) {
foreach (preg_split('/\\s+/', $_COOKIE['minDebug']) as $debugUri) {
if (false !== strpos($_SERVER['REQUEST_URI'], $debugUri)) {
$min_serveOptions['debug'] = true;
break;
}
}
}
// allow GET to override
if (isset($_GET['debug'])) {
$min_serveOptions['debug'] = true;
}
}
if ($min_errorLogger) {
require_once 'Minify/Logger.php';
if (true === $min_errorLogger) {
require_once 'FirePHP.php';
Minify_Logger::setLogger(FirePHP::getInstance(true));
} else {
Minify_Logger::setLogger($min_errorLogger);
$min_errorLogger = FirePHP::getInstance(true);
}
Minify_Logger::setLogger($min_errorLogger);
}
// check for URI versioning
@@ -55,7 +69,12 @@ if (isset($_GET['g'])) {
}
if (isset($_GET['f']) || isset($_GET['g'])) {
// serve!
Minify::serve('MinApp', $min_serveOptions);
if (! isset($min_serveController)) {
require 'Minify/Controller/MinApp.php';
$min_serveController = new Minify_Controller_MinApp();
}
Minify::serve($min_serveController, $min_serveOptions);
} elseif ($min_enableBuilder) {
header('Location: builder/');
@@ -63,4 +82,4 @@ if (isset($_GET['f']) || isset($_GET['g'])) {
} else {
header("Location: /");
exit();
}
}

View File

@@ -75,9 +75,8 @@ class HTTP_ConditionalGet {
/**
* @param array $spec options
*
* 'isPublic': (bool) if true, the Cache-Control header will contain
* "public", allowing proxies to cache the content. Otherwise "private" will
* be sent, allowing only browser caching. (default false)
* 'isPublic': (bool) if false, the Cache-Control header will contain
* "private", allowing only browser caching. (default false)
*
* 'lastModifiedTime': (int) if given, both ETag AND Last-Modified headers
* 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
$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
$this->cacheIsValid = (isset($spec['invalidate']) && $spec['invalidate'])
? false
@@ -209,7 +211,9 @@ class HTTP_ConditionalGet {
{
$headers = $this->_headers;
if (array_key_exists('_responseCode', $headers)) {
header($headers['_responseCode']);
// FastCGI environments require 3rd arg to header() to be set
list(, $code) = explode(' ', $headers['_responseCode'], 3);
header($headers['_responseCode'], true, $code);
unset($headers['_responseCode']);
}
foreach ($headers as $name => $val) {
@@ -332,12 +336,9 @@ class HTTP_ConditionalGet {
if (!isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
return false;
}
$ifModifiedSince = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
if (false !== ($semicolon = strrpos($ifModifiedSince, ';'))) {
// IE has tacked on extra data to this header, strip it
$ifModifiedSince = substr($ifModifiedSince, 0, $semicolon);
}
if ($ifModifiedSince == self::gmtDate($this->_lmTime)) {
// strip off IE's extra data (semicolon)
list($ifModifiedSince) = explode(';', $_SERVER['HTTP_IF_MODIFIED_SINCE'], 2);
if (strtotime($ifModifiedSince) >= $this->_lmTime) {
// Apache 2.2's behavior. If there was no ETag match, send the
// non-encoded version of the ETag value.
$this->_headers['ETag'] = $this->normalizeEtag($this->_etag);

View File

@@ -33,11 +33,11 @@
* </code>
*
* For more control over headers, use getHeaders() and getData() and send your
* own output.
*
* Note: If you don't need header mgmt, use PHP's native gzencode, gzdeflate,
* and gzcompress functions for gzip, deflate, and compress-encoding
* respectively.
* own output.
*
* Note: If you don't need header mgmt, use PHP's native gzencode, gzdeflate,
* and gzcompress functions for gzip, deflate, and compress-encoding
* respectively.
*
* @package Minify
* @subpackage HTTP
@@ -59,7 +59,7 @@ class HTTP_Encoder {
*
* @var bool
*/
public static $encodeToIe6 = false;
public static $encodeToIe6 = true;
/**
@@ -88,10 +88,15 @@ class HTTP_Encoder {
*
* @return null
*/
public function __construct($spec)
public function __construct($spec)
{
$this->_useMbStrlen = (function_exists('mb_strlen')
&& (ini_get('mbstring.func_overload') !== '')
&& ((int)ini_get('mbstring.func_overload') & 2));
$this->_content = $spec['content'];
$this->_headers['Content-Length'] = (string)strlen($this->_content);
$this->_headers['Content-Length'] = $this->_useMbStrlen
? (string)mb_strlen($this->_content, '8bit')
: (string)strlen($this->_content);
if (isset($spec['type'])) {
$this->_headers['Content-Type'] = $spec['type'];
}
@@ -111,7 +116,7 @@ class HTTP_Encoder {
*
* return string
*/
public function getContent()
public function getContent()
{
return $this->_content;
}
@@ -130,7 +135,7 @@ class HTTP_Encoder {
*
* @return array
*/
public function getHeaders()
public function getHeaders()
{
return $this->_headers;
}
@@ -146,7 +151,7 @@ class HTTP_Encoder {
*
* @return null
*/
public function sendHeaders()
public function sendHeaders()
{
foreach ($this->_headers as $name => $val) {
header($name . ': ' . $val);
@@ -164,7 +169,7 @@ class HTTP_Encoder {
*
* @return null
*/
public function sendAll()
public function sendAll()
{
$this->sendHeaders();
echo $this->_content;
@@ -181,21 +186,21 @@ class HTTP_Encoder {
* be non 0. The methods are favored in order of gzip, deflate, then
* compress. Deflate is always smallest and generally faster, but is
* rarely sent by servers, so client support could be buggier.
*
*
* @param bool $allowCompress allow the older compress encoding
*
* @param bool $allowDeflate allow the more recent deflate encoding
* @param bool $allowDeflate allow the more recent deflate encoding
*
* @return array two values, 1st is the actual encoding method, 2nd is the
* alias of that method to use in the Content-Encoding header (some browsers
* call gzip "x-gzip" etc.)
*/
public static function getAcceptedEncoding($allowCompress = true, $allowDeflate = true)
public static function getAcceptedEncoding($allowCompress = true, $allowDeflate = true)
{
// @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
if (! isset($_SERVER['HTTP_ACCEPT_ENCODING'])
|| self::_isBuggyIe())
|| self::isBuggyIe())
{
return array('', '');
}
@@ -244,16 +249,18 @@ class HTTP_Encoder {
* this fails, false is returned.
*
* The header "Vary: Accept-Encoding" is added. If encoding is successful,
* the Content-Length header is updated, and Content-Encoding is also added.
*
* the Content-Length header is updated, and Content-Encoding is also added.
*
* @param int $compressionLevel given to zlib functions. If not given, the
* class default will be used.
*
* @return bool success true if the content was actually compressed
*/
public function encode($compressionLevel = null)
public function encode($compressionLevel = null)
{
$this->_headers['Vary'] = 'Accept-Encoding';
if (! self::isBuggyIe()) {
$this->_headers['Vary'] = 'Accept-Encoding';
}
if (null === $compressionLevel) {
$compressionLevel = self::$compressionLevel;
}
@@ -262,9 +269,9 @@ class HTTP_Encoder {
|| !extension_loaded('zlib'))
{
return false;
}
if ($this->_encodeMethod[0] === 'deflate') {
$encoded = gzdeflate($this->_content, $compressionLevel);
}
if ($this->_encodeMethod[0] === 'deflate') {
$encoded = gzdeflate($this->_content, $compressionLevel);
} elseif ($this->_encodeMethod[0] === 'gzip') {
$encoded = gzencode($this->_content, $compressionLevel);
} else {
@@ -273,7 +280,9 @@ class HTTP_Encoder {
if (false === $encoded) {
return false;
}
$this->_headers['Content-Length'] = strlen($encoded);
$this->_headers['Content-Length'] = $this->_useMbStrlen
? (string)mb_strlen($encoded, '8bit')
: (string)strlen($encoded);
$this->_headers['Content-Encoding'] = $this->_encodeMethod[1];
$this->_content = $encoded;
return true;
@@ -285,7 +294,7 @@ class HTTP_Encoder {
* This is a convenience method for common use of the class
*
* @param string $content
*
*
* @param int $compressionLevel given to zlib functions. If not given, the
* class default will be used.
*
@@ -296,20 +305,18 @@ class HTTP_Encoder {
if (null === $compressionLevel) {
$compressionLevel = self::$compressionLevel;
}
$he = new HTTP_Encoder(array('content' => $content));
$ret = $he->encode($compressionLevel);
$he = new HTTP_Encoder(array('content' => $content));
$ret = $he->encode($compressionLevel);
$he->sendAll();
return $ret;
}
protected $_content = '';
protected $_headers = array();
protected $_encodeMethod = array('', '');
}
/**
* Is the browser an IE version earlier than 6 SP2?
* Is the browser an IE version earlier than 6 SP2?
*
* @return bool
*/
protected static function _isBuggyIe()
public static function isBuggyIe()
{
$ua = $_SERVER['HTTP_USER_AGENT'];
// quick escape for non-IEs
@@ -318,9 +325,14 @@ class HTTP_Encoder {
return false;
}
// no regex = faaast
$version = (float)substr($ua, 30);
$version = (float)substr($ua, 30);
return self::$encodeToIe6
? ($version < 6 || ($version == 6 && false === strpos($ua, 'SV1')))
: ($version < 7);
}
protected $_content = '';
protected $_headers = array();
protected $_encodeMethod = array('', '');
protected $_useMbStrlen = false;
}

View File

@@ -1,12 +1,16 @@
<?php
/**
* jsmin.php - PHP implementation of Douglas Crockford's JSMin.
* jsmin.php - extended PHP implementation of Douglas Crockford's JSMin.
*
* <code>
* $minifiedJs = JSMin::minify($js);
* </code>
*
* This is a direct port of jsmin.c to PHP with a few PHP performance tweaks and
* modifications to preserve some comments (see below). Also, rather than using
* stdin/stdout, JSMin::minify() accepts a string as input and returns another
* string as output.
*
*
* Comments containing IE conditional compilation are preserved, as are multi-line
* comments that begin with "/*!" (for documentation purposes). In the latter case
* newlines are inserted around the comment to enhance readability.
@@ -56,7 +60,7 @@ class JSMin {
const ACTION_KEEP_A = 1;
const ACTION_DELETE_A = 2;
const ACTION_DELETE_A_B = 3;
protected $a = "\n";
protected $b = '';
protected $input = '';
@@ -64,9 +68,9 @@ class JSMin {
protected $inputLength = 0;
protected $lookAhead = null;
protected $output = '';
/**
* Minify Javascript
* Minify Javascript.
*
* @param string $js Javascript to be minified
* @return string
@@ -76,16 +80,22 @@ class JSMin {
$jsmin = new JSMin($js);
return $jsmin->min();
}
/**
* Setup process
* @param string $input
*/
public function __construct($input)
{
$this->input = str_replace("\r\n", "\n", $input);
$this->inputLength = strlen($this->input);
$this->input = $input;
// look out for syntax like "++ +" and "- ++"
$p = '\\+';
$m = '\\-';
if (preg_match("/([$p$m])(?:\\1 [$p$m]| (?:$p$p|$m$m))/", $input)) {
// likely pre-minified and would be broken by JSMin
$this->output = $input;
}
}
/**
* Perform minification, return result
*/
@@ -94,8 +104,17 @@ class JSMin {
if ($this->output !== '') { // min already run
return $this->output;
}
$mbIntEnc = null;
if (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) {
$mbIntEnc = mb_internal_encoding();
mb_internal_encoding('8bit');
}
$this->input = str_replace("\r\n", "\n", $this->input);
$this->inputLength = strlen($this->input);
$this->action(self::ACTION_DELETE_A_B);
while ($this->a !== null) {
// determine next command
$command = self::ACTION_KEEP_A; // default
@@ -106,13 +125,16 @@ class JSMin {
} elseif ($this->a === "\n") {
if ($this->b === ' ') {
$command = self::ACTION_DELETE_A_B;
} elseif (false === strpos('{[(+-', $this->b)
&& ! $this->isAlphaNum($this->b)) {
// 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)
&& ! $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;
}
@@ -120,9 +142,13 @@ class JSMin {
$this->action($command);
}
$this->output = trim($this->output);
if ($mbIntEnc !== null) {
mb_internal_encoding($mbIntEnc);
}
return $this->output;
}
/**
* ACTION_KEEP_A = Output A. Copy B to A. Get the next B.
* ACTION_DELETE_A = Copy B to A. Get the next B.
@@ -146,7 +172,8 @@ class JSMin {
}
if (ord($this->a) <= self::ORD_LF) {
throw new JSMin_UnterminatedStringException(
'Unterminated String: ' . var_export($str, true));
"JSMin: Unterminated String at byte "
. $this->inputIndex . ": {$str}");
}
$str .= $this->a;
if ($this->a === '\\') {
@@ -173,7 +200,8 @@ class JSMin {
$pattern .= $this->a;
} elseif (ord($this->a) <= self::ORD_LF) {
throw new JSMin_UnterminatedRegExpException(
'Unterminated RegExp: '. var_export($pattern, true));
"JSMin: Unterminated RegExp at byte "
. $this->inputIndex .": {$pattern}");
}
$this->output .= $this->a;
}
@@ -182,7 +210,7 @@ class JSMin {
// end case ACTION_DELETE_A_B
}
}
protected function isRegexpLiteral()
{
if (false !== strpos("\n{;(,=:[!&|?", $this->a)) { // we aren't dividing
@@ -207,7 +235,7 @@ class JSMin {
}
return false;
}
/**
* Get next char. Convert ctrl char to space.
*/
@@ -231,7 +259,7 @@ class JSMin {
}
return $c;
}
/**
* Get next char. If is ctrl character, translate to a space or newline.
*/
@@ -240,7 +268,7 @@ class JSMin {
$this->lookAhead = $this->get();
return $this->lookAhead;
}
/**
* Is $c a letter, digit, underscore, dollar sign, escape, or non-ASCII?
*/
@@ -248,7 +276,7 @@ class JSMin {
{
return (preg_match('/^[0-9a-zA-Z_\\$\\\\]$/', $c) || ord($c) > 126);
}
protected function singleLineComment()
{
$comment = '';
@@ -264,7 +292,7 @@ class JSMin {
}
}
}
protected function multipleLineComment()
{
$this->get();
@@ -276,7 +304,7 @@ class JSMin {
$this->get();
// if comment preserved by YUI Compressor
if (0 === strpos($comment, '!')) {
return "\n/*" . substr($comment, 1) . "*/\n";
return "\n/*!" . substr($comment, 1) . "*/\n";
}
// if IE conditional comment
if (preg_match('/^@(?:cc_on|if|elif|else|end)\\b/', $comment)) {
@@ -285,12 +313,14 @@ class JSMin {
return ' ';
}
} elseif ($get === null) {
throw new JSMin_UnterminatedCommentException('Unterminated Comment: ' . var_export('/*' . $comment, true));
throw new JSMin_UnterminatedCommentException(
"JSMin: Unterminated comment at byte "
. $this->inputIndex . ": /*{$comment}");
}
$comment .= $get;
}
}
/**
* Get the next character, skipping over comments.
* Some comments may be preserved.

View File

@@ -29,12 +29,13 @@ require_once 'Minify/Source.php';
*/
class Minify {
const VERSION = '2.1.3';
const VERSION = '2.1.4';
const TYPE_CSS = 'text/css';
const TYPE_HTML = 'text/html';
// there is some debate over the ideal JS Content-Type, but this is the
// Apache default and what Yahoo! uses..
const TYPE_JS = 'application/x-javascript';
const URL_DEBUG = 'http://code.google.com/p/minify/wiki/Debugging';
/**
* How many hours behind are the file modification times of uploaded files?
@@ -179,9 +180,7 @@ class Minify {
if (! $controller->sources) {
// invalid request!
if (! self::$_options['quiet']) {
header(self::$_options['badRequestHeader']);
echo self::$_options['badRequestHeader'];
return;
self::_errorExit(self::$_options['badRequestHeader'], self::URL_DEBUG);
} else {
list(,$statusCode) = explode(' ', self::$_options['badRequestHeader']);
return array(
@@ -202,6 +201,7 @@ class Minify {
// determine encoding
if (self::$_options['encodeOutput']) {
$sendVary = true;
if (self::$_options['encodeMethod'] !== null) {
// controller specifically requested this
$contentEncoding = self::$_options['encodeMethod'];
@@ -212,6 +212,7 @@ class Minify {
// '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);
$sendVary = ! HTTP_Encoder::isBuggyIe();
}
} else {
self::$_options['encodeMethod'] = ''; // identity (no encoding)
@@ -226,6 +227,8 @@ class Minify {
);
if (self::$_options['maxAge'] > 0) {
$cgOptions['maxAge'] = self::$_options['maxAge'];
} elseif (self::$_options['debug']) {
$cgOptions['invalidate'] = true;
}
$cg = new HTTP_ConditionalGet($cgOptions);
if ($cg->cacheIsValid) {
@@ -250,7 +253,7 @@ class Minify {
if (self::$_options['contentType'] === self::TYPE_CSS
&& self::$_options['rewriteCssUris']) {
reset($controller->sources);
while (list($key, $source) = each($controller->sources)) {
foreach($controller->sources as $key => $source) {
if ($source->filepath
&& !isset($source->minifyOptions['currentDir'])
&& !isset($source->minifyOptions['prependRelativePath'])
@@ -261,12 +264,12 @@ class Minify {
}
// check server cache
if (null !== self::$_cache) {
if (null !== self::$_cache && ! self::$_options['debug']) {
// using cache
// 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
// memory.
$cacheId = 'minify_' . self::_getCacheId();
$cacheId = self::_getCacheId();
$fullCacheId = (self::$_options['encodeMethod'])
? $cacheId . '.gz'
: $cacheId;
@@ -276,7 +279,15 @@ class Minify {
$cacheContentLength = self::$_cache->getSize($fullCacheId);
} else {
// generate & cache content
$content = self::_combineMinify();
try {
$content = self::_combineMinify();
} catch (Exception $e) {
self::$_controller->log($e->getMessage());
if (! self::$_options['quiet']) {
self::_errorExit(self::$_options['errorHeader'], self::URL_DEBUG);
}
throw $e;
}
self::$_cache->store($cacheId, $content);
if (function_exists('gzencode')) {
self::$_cache->store($cacheId . '.gz', gzencode($content, self::$_options['encodeLevel']));
@@ -285,7 +296,15 @@ class Minify {
} else {
// no cache
$cacheIsReady = false;
$content = self::_combineMinify();
try {
$content = self::_combineMinify();
} catch (Exception $e) {
self::$_controller->log($e->getMessage());
if (! self::$_options['quiet']) {
self::_errorExit(self::$_options['errorHeader'], self::URL_DEBUG);
}
throw $e;
}
}
if (! $cacheIsReady && self::$_options['encodeMethod']) {
// still need to encode
@@ -295,14 +314,17 @@ class Minify {
// add headers
$headers['Content-Length'] = $cacheIsReady
? $cacheContentLength
: strlen($content);
: ((function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2))
? mb_strlen($content, '8bit')
: strlen($content)
);
$headers['Content-Type'] = self::$_options['contentTypeCharset']
? self::$_options['contentType'] . '; charset=' . self::$_options['contentTypeCharset']
: self::$_options['contentType'];
if (self::$_options['encodeMethod'] !== '') {
$headers['Content-Encoding'] = $contentEncoding;
}
if (self::$_options['encodeOutput']) {
if (self::$_options['encodeOutput'] && $sendVary) {
$headers['Vary'] = 'Accept-Encoding';
}
@@ -369,9 +391,9 @@ class Minify {
&& 0 === strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS/')
) {
$_SERVER['DOCUMENT_ROOT'] = rtrim(substr(
$_SERVER['PATH_TRANSLATED']
$_SERVER['SCRIPT_FILENAME']
,0
,strlen($_SERVER['PATH_TRANSLATED']) - strlen($_SERVER['SCRIPT_NAME'])
,strlen($_SERVER['SCRIPT_FILENAME']) - strlen($_SERVER['SCRIPT_NAME'])
), '\\');
if ($unsetPathInfo) {
unset($_SERVER['PATH_INFO']);
@@ -396,6 +418,20 @@ class Minify {
*/
protected static $_options = null;
protected static function _errorExit($header, $url)
{
$url = htmlspecialchars($url, ENT_QUOTES);
list(,$h1) = explode(' ', $header, 2);
$h1 = htmlspecialchars($h1);
// FastCGI environments require 3rd arg to header() to be set
list(, $code) = explode(' ', $header, 3);
header($header, true, $code);
header('Content-Type: text/html; charset=utf-8');
echo "<h1>$h1</h1>";
echo "<p>Please see <a href='$url'>$url</a>.</p>";
exit();
}
/**
* Set up sources to use Minify_Lines
*
@@ -440,37 +476,31 @@ class Minify {
? self::$_options['minifiers'][$type]
: false;
if (Minify_Source::haveNoMinifyPrefs(self::$_controller->sources)) {
// all source have same options/minifier, better performance
// to combine, then minify once
foreach (self::$_controller->sources as $source) {
// minify each source with its own options and minifier, then combine.
// Here we used to combine all first but this was probably
// bad for PCRE performance, esp. in CSS.
foreach (self::$_controller->sources as $source) {
// allow the source to override our minifier and options
$minifier = (null !== $source->minifier)
? $source->minifier
: $defaultMinifier;
$options = (null !== $source->minifyOptions)
? array_merge($defaultOptions, $source->minifyOptions)
: $defaultOptions;
if ($minifier) {
self::$_controller->loadMinifier($minifier);
// get source content and minify it
try {
$pieces[] = call_user_func($minifier, $source->getContent(), $options);
} catch (Exception $e) {
throw new Exception("Exception in " . $source->getId() .
": " . $e->getMessage());
}
} else {
$pieces[] = $source->getContent();
}
$content = implode($implodeSeparator, $pieces);
if ($defaultMinifier) {
self::$_controller->loadMinifier($defaultMinifier);
$content = call_user_func($defaultMinifier, $content, $defaultOptions);
}
} else {
// minify each source with its own options and minifier, then combine
foreach (self::$_controller->sources as $source) {
// allow the source to override our minifier and options
$minifier = (null !== $source->minifier)
? $source->minifier
: $defaultMinifier;
$options = (null !== $source->minifyOptions)
? array_merge($defaultOptions, $source->minifyOptions)
: $defaultOptions;
if ($minifier) {
self::$_controller->loadMinifier($minifier);
// get source content and minify it
$pieces[] = call_user_func($minifier, $source->getContent(), $options);
} else {
$pieces[] = $source->getContent();
}
}
$content = implode($implodeSeparator, $pieces);
}
$content = implode($implodeSeparator, $pieces);
if ($type === self::TYPE_CSS && false !== strpos($content, '@import')) {
$content = self::_handleCssImports($content);
@@ -491,17 +521,23 @@ class Minify {
*
* Any settings that could affect output are taken into consideration
*
* @param string $prefix
*
* @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, 200 - 34 - strlen($prefix));
$md5 = md5(serialize(array(
Minify_Source::getDigest(self::$_controller->sources)
,self::$_options['minifiers']
,self::$_options['minifierOptions']
,self::$_options['postprocessor']
,self::$_options['bubbleCssImports']
)));
return "{$prefix}_{$name}_{$md5}";
}
/**
@@ -512,7 +548,7 @@ class Minify {
{
if (self::$_options['bubbleCssImports']) {
// bubble CSS imports
preg_match_all('/@import.*?;/', $css, $imports);
preg_match_all('/@import.*?;/', $css, $imports);
$css = implode('', $imports[0]) . preg_replace('/@import.*?;/', '', $css);
} else if ('' !== self::$importWarning) {
// remove comments so we don't mistake { in a comment as a block

View File

@@ -26,6 +26,8 @@ class Minify_CSS {
* 'preserveComments': (default true) multi-line comments that begin
* with "/*!" will be preserved with newlines before and after to
* enhance readability.
*
* 'removeCharsets': (default true) remove all @charset at-rules
*
* 'prependRelativePath': (default null) if given, this string will be
* prepended to all relative URIs in import/url declarations
@@ -36,23 +38,37 @@ class Minify_CSS {
* the desired files. For this to work, the files *must* exist and be
* visible by the PHP process.
*
* 'symlinks': (default = array()) If the CSS file is stored in
* a symlink-ed directory, provide an array of link paths to
* target paths, where the link paths are within the document root. Because
* paths need to be normalized for this to work, use "//" to substitute
* the doc root in the link paths (the array keys). E.g.:
* <code>
* array('//symlink' => '/real/target/path') // unix
* array('//static' => 'D:\\staticStorage') // Windows
* 'symlinks': (default = array()) If the CSS file is stored in
* a symlink-ed directory, provide an array of link paths to
* target paths, where the link paths are within the document root. Because
* paths need to be normalized for this to work, use "//" to substitute
* the doc root in the link paths (the array keys). E.g.:
* <code>
* array('//symlink' => '/real/target/path') // unix
* array('//static' => 'D:\\staticStorage') // Windows
* </code>
*
* 'docRoot': (default = $_SERVER['DOCUMENT_ROOT'])
* see Minify_CSS_UriRewriter::rewrite
*
* @return string
*/
public static function minify($css, $options = array())
{
$options = array_merge(array(
'removeCharsets' => true,
'preserveComments' => true,
'currentDir' => null,
'docRoot' => $_SERVER['DOCUMENT_ROOT'],
'prependRelativePath' => null,
'symlinks' => array(),
), $options);
if ($options['removeCharsets']) {
$css = preg_replace('/@charset[^;]+;\\s*/', '', $css);
}
require_once 'Minify/CSS/Compressor.php';
if (isset($options['preserveComments'])
&& !$options['preserveComments']) {
if (! $options['preserveComments']) {
$css = Minify_CSS_Compressor::process($css, $options);
} else {
require_once 'Minify/CommentPreserver.php';
@@ -62,16 +78,16 @@ class Minify_CSS {
,array($options)
);
}
if (! isset($options['currentDir']) && ! isset($options['prependRelativePath'])) {
if (! $options['currentDir'] && ! $options['prependRelativePath']) {
return $css;
}
require_once 'Minify/CSS/UriRewriter.php';
if (isset($options['currentDir'])) {
if ($options['currentDir']) {
return Minify_CSS_UriRewriter::rewrite(
$css
,$options['currentDir']
,isset($options['docRoot']) ? $options['docRoot'] : $_SERVER['DOCUMENT_ROOT']
,isset($options['symlinks']) ? $options['symlinks'] : array()
,$options['docRoot']
,$options['symlinks']
);
} else {
return Minify_CSS_UriRewriter::prepend(

View File

@@ -108,7 +108,7 @@ class Minify_CSS_Compressor {
\\s*
:
\\s*
(\\b|[#\'"]) # 3 = first character of a value
(\\b|[#\'"-]) # 3 = first character of a value
/x', '$1$2:$3', $css);
// remove ws in selectors
@@ -236,15 +236,16 @@ class Minify_CSS_Compressor {
*/
protected function _fontFamilyCB($m)
{
$m[1] = preg_replace('/
\\s*
(
"[^"]+" # 1 = family in double qutoes
|\'[^\']+\' # or 1 = family in single quotes
|[\\w\\-]+ # or 1 = unquoted family
)
\\s*
/x', '$1', $m[1]);
return 'font-family:' . $m[1] . $m[2];
// Issue 210: must not eliminate WS between words in unquoted families
$pieces = preg_split('/(\'[^\']+\'|"[^"]+")/', $m[1], null, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
$out = 'font-family:';
while (null !== ($piece = array_shift($pieces))) {
if ($piece[0] !== '"' && $piece[0] !== "'") {
$piece = preg_replace('/\\s+/', ' ', $piece);
$piece = preg_replace('/\\s?,\\s?/', ',', $piece);
}
$out .= $piece;
}
return $out . $m[2];
}
}

View File

@@ -12,13 +12,6 @@
*/
class Minify_CSS_UriRewriter {
/**
* Defines which class to call as part of callbacks, change this
* if you extend Minify_CSS_UriRewriter
* @var string
*/
protected static $className = 'Minify_CSS_UriRewriter';
/**
* rewrite() and rewriteRelative() append debugging information here
* @var string
@@ -26,7 +19,7 @@ class Minify_CSS_UriRewriter {
public static $debugText = '';
/**
* Rewrite file relative URIs as root relative in CSS files
* In CSS content, rewrite file relative URIs as root relative
*
* @param string $css
*
@@ -83,7 +76,7 @@ class Minify_CSS_UriRewriter {
}
/**
* Prepend a path to relative URIs in CSS files
* In CSS content, prepend a path to relative URIs
*
* @param string $css
*
@@ -107,73 +100,8 @@ class Minify_CSS_UriRewriter {
return $css;
}
/**
* @var string directory of this stylesheet
*/
private static $_currentDir = '';
/**
* @var string DOC_ROOT
*/
private static $_docRoot = '';
/**
* @var array directory replacements to map symlink targets back to their
* source (within the document root) E.g. '/var/www/symlink' => '/var/realpath'
*/
private static $_symlinks = array();
/**
* @var string path to prepend
*/
private static $_prependPath = null;
private static function _trimUrls($css)
{
return preg_replace('/
url\\( # url(
\\s*
([^\\)]+?) # 1 = URI (assuming does not contain ")")
\\s*
\\) # )
/x', 'url($1)', $css);
}
private static function _processUriCB($m)
{
// $m matched either '/@import\\s+([\'"])(.*?)[\'"]/' or '/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
$isImport = ($m[0][0] === '@');
// determine URI and the quote character (if any)
if ($isImport) {
$quoteChar = $m[1];
$uri = $m[2];
} else {
// $m[1] is either quoted or not
$quoteChar = ($m[1][0] === "'" || $m[1][0] === '"')
? $m[1][0]
: '';
$uri = ($quoteChar === '')
? $m[1]
: substr($m[1], 1, strlen($m[1]) - 2);
}
// analyze URI
if ('/' !== $uri[0] // root-relative
&& false === strpos($uri, '//') // protocol (non-data)
&& 0 !== strpos($uri, 'data:') // data protocol
) {
// URI is file-relative: rewrite depending on options
$uri = (self::$_prependPath !== null)
? (self::$_prependPath . $uri)
: self::rewriteRelative($uri, self::$_currentDir, self::$_docRoot, self::$_symlinks);
}
return $isImport
? "@import {$quoteChar}{$uri}{$quoteChar}"
: "url({$quoteChar}{$uri}{$quoteChar})";
}
/**
* Rewrite a file relative URI as root relative
* Get a root relative URI from a file relative URI
*
* <code>
* Minify_CSS_UriRewriter::rewriteRelative(
@@ -219,16 +147,16 @@ class Minify_CSS_UriRewriter {
self::$debugText .= "file-relative URI : {$uri}\n"
. "path prepended : {$path}\n";
// "unresolve" a symlink back to doc root
foreach ($symlinks as $link => $target) {
if (0 === strpos($path, $target)) {
// replace $target with $link
// "unresolve" a symlink back to doc root
foreach ($symlinks as $link => $target) {
if (0 === strpos($path, $target)) {
// replace $target with $link
$path = $link . substr($path, strlen($target));
self::$debugText .= "symlink unresolved : {$path}\n";
self::$debugText .= "symlink unresolved : {$path}\n";
break;
}
break;
}
}
// strip doc root
$path = substr($path, strlen($realDocRoot));
@@ -239,18 +167,35 @@ class Minify_CSS_UriRewriter {
$uri = strtr($path, '/\\', '//');
// remove /./ and /../ where possible
$uri = str_replace('/./', '/', $uri);
// inspired by patch from Oleg Cherniy
do {
$uri = preg_replace('@/[^/]+/\\.\\./@', '/', $uri, 1, $changed);
} while ($changed);
$uri = self::removeDots($uri);
self::$debugText .= "traversals removed : {$uri}\n\n";
return $uri;
}
/**
* Remove instances of "./" and "../" where possible from a root-relative URI
* @param string $uri
* @return string
*/
public static function removeDots($uri)
{
$uri = str_replace('/./', '/', $uri);
// inspired by patch from Oleg Cherniy
do {
$uri = preg_replace('@/[^/]+/\\.\\./@', '/', $uri, 1, $changed);
} while ($changed);
return $uri;
}
/**
* Defines which class to call as part of callbacks, change this
* if you extend Minify_CSS_UriRewriter
* @var string
*/
protected static $className = 'Minify_CSS_UriRewriter';
/**
* Get realpath with any trailing slash removed. If realpath() fails,
* just remove the trailing slash.
@@ -267,4 +212,79 @@ class Minify_CSS_UriRewriter {
}
return rtrim($path, '/\\');
}
/**
* @var string directory of this stylesheet
*/
private static $_currentDir = '';
/**
* @var string DOC_ROOT
*/
private static $_docRoot = '';
/**
* @var array directory replacements to map symlink targets back to their
* source (within the document root) E.g. '/var/www/symlink' => '/var/realpath'
*/
private static $_symlinks = array();
/**
* @var string path to prepend
*/
private static $_prependPath = null;
private static function _trimUrls($css)
{
return preg_replace('/
url\\( # url(
\\s*
([^\\)]+?) # 1 = URI (assuming does not contain ")")
\\s*
\\) # )
/x', 'url($1)', $css);
}
private static function _processUriCB($m)
{
// $m matched either '/@import\\s+([\'"])(.*?)[\'"]/' or '/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
$isImport = ($m[0][0] === '@');
// determine URI and the quote character (if any)
if ($isImport) {
$quoteChar = $m[1];
$uri = $m[2];
} else {
// $m[1] is either quoted or not
$quoteChar = ($m[1][0] === "'" || $m[1][0] === '"')
? $m[1][0]
: '';
$uri = ($quoteChar === '')
? $m[1]
: substr($m[1], 1, strlen($m[1]) - 2);
}
// analyze URI
if ('/' !== $uri[0] // root-relative
&& false === strpos($uri, '//') // protocol (non-data)
&& 0 !== strpos($uri, 'data:') // data protocol
) {
// URI is file-relative: rewrite depending on options
if (self::$_prependPath === null) {
$uri = self::rewriteRelative($uri, self::$_currentDir, self::$_docRoot, self::$_symlinks);
} else {
$uri = self::$_prependPath . $uri;
if ($uri[0] === '/') {
$root = '';
$rootRelative = $uri;
$uri = $root . self::removeDots($rootRelative);
} elseif (preg_match('@^((https?\:)?//([^/]+))/@', $uri, $m) && (false !== strpos($m[3], '.'))) {
$root = $m[1];
$rootRelative = substr($uri, strlen($root));
$uri = $root . self::removeDots($rootRelative);
}
}
}
return $isImport
? "@import {$quoteChar}{$uri}{$quoteChar}"
: "url({$quoteChar}{$uri}{$quoteChar})";
}
}

View File

@@ -54,9 +54,12 @@ class Minify_Cache_APC {
*/
public function getSize($id)
{
return $this->_fetch($id)
? strlen($this->_data)
: false;
if (! $this->_fetch($id)) {
return false;
}
return (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2))
? mb_strlen($this->_data, '8bit')
: strlen($this->_data);
}
/**

View File

@@ -9,13 +9,12 @@ class Minify_Cache_File {
public function __construct($path = '', $fileLocking = false)
{
if (! $path) {
require_once 'Solar/Dir.php';
$path = rtrim(Solar_Dir::tmp(), DIRECTORY_SEPARATOR);
}
$path = self::tmp();
}
$this->_locking = $fileLocking;
$this->_path = $path;
}
/**
* Write data to cache.
*
@@ -27,20 +26,23 @@ class Minify_Cache_File {
*/
public function store($id, $data)
{
$flag = $this->_locking
? LOCK_EX
: null;
if (is_file($this->_path . '/' . $id)) {
@unlink($this->_path . '/' . $id);
}
if (! @file_put_contents($this->_path . '/' . $id, $data, $flag)) {
return false;
$flag = $this->_locking
? LOCK_EX
: null;
$file = $this->_path . '/' . $id;
if (is_file($file)) {
@unlink($file);
}
if (! @file_put_contents($file, $data, $flag)) {
$this->_log("Minify_Cache_File: Write failed to '$file'");
return false;
}
// write control
if ($data !== $this->fetch($id)) {
@unlink($file);
$this->_log("Minify_Cache_File: Post-write read failed for '$file'");
return false;
}
// write control
if ($data !== $this->fetch($id)) {
@unlink($file);
return false;
}
return true;
}
@@ -78,15 +80,15 @@ class Minify_Cache_File {
*/
public function display($id)
{
if ($this->_locking) {
$fp = fopen($this->_path . '/' . $id, 'rb');
flock($fp, LOCK_SH);
fpassthru($fp);
flock($fp, LOCK_UN);
fclose($fp);
} else {
readfile($this->_path . '/' . $id);
}
if ($this->_locking) {
$fp = fopen($this->_path . '/' . $id, 'rb');
flock($fp, LOCK_SH);
fpassthru($fp);
flock($fp, LOCK_UN);
fclose($fp);
} else {
readfile($this->_path . '/' . $id);
}
}
/**
@@ -98,15 +100,15 @@ class Minify_Cache_File {
*/
public function fetch($id)
{
if ($this->_locking) {
$fp = fopen($this->_path . '/' . $id, 'rb');
flock($fp, LOCK_SH);
$ret = stream_get_contents($fp);
flock($fp, LOCK_UN);
fclose($fp);
return $ret;
} else {
return file_get_contents($this->_path . '/' . $id);
if ($this->_locking) {
$fp = fopen($this->_path . '/' . $id, 'rb');
flock($fp, LOCK_SH);
$ret = stream_get_contents($fp);
flock($fp, LOCK_UN);
fclose($fp);
return $ret;
} else {
return file_get_contents($this->_path . '/' . $id);
}
}
@@ -119,7 +121,79 @@ class Minify_Cache_File {
{
return $this->_path;
}
/**
* Get a usable temp directory
*
* Adapted from Solar/Dir.php
* @author Paul M. Jones <pmjones@solarphp.com>
* @license http://opensource.org/licenses/bsd-license.php BSD
* @link http://solarphp.com/trac/core/browser/trunk/Solar/Dir.php
*
* @return string
*/
public static function tmp()
{
static $tmp = null;
if (! $tmp) {
$tmp = function_exists('sys_get_temp_dir')
? sys_get_temp_dir()
: self::_tmp();
$tmp = rtrim($tmp, DIRECTORY_SEPARATOR);
}
return $tmp;
}
/**
* Returns the OS-specific directory for temporary files
*
* @author Paul M. Jones <pmjones@solarphp.com>
* @license http://opensource.org/licenses/bsd-license.php BSD
* @link http://solarphp.com/trac/core/browser/trunk/Solar/Dir.php
*
* @return string
*/
protected static function _tmp()
{
// non-Windows system?
if (strtolower(substr(PHP_OS, 0, 3)) != 'win') {
$tmp = empty($_ENV['TMPDIR']) ? getenv('TMPDIR') : $_ENV['TMPDIR'];
if ($tmp) {
return $tmp;
} else {
return '/tmp';
}
}
// Windows 'TEMP'
$tmp = empty($_ENV['TEMP']) ? getenv('TEMP') : $_ENV['TEMP'];
if ($tmp) {
return $tmp;
}
// Windows 'TMP'
$tmp = empty($_ENV['TMP']) ? getenv('TMP') : $_ENV['TMP'];
if ($tmp) {
return $tmp;
}
// Windows 'windir'
$tmp = empty($_ENV['windir']) ? getenv('windir') : $_ENV['windir'];
if ($tmp) {
return $tmp;
}
// final fallback for Windows
return getenv('SystemRoot') . '\\temp';
}
/**
* Send message to the Minify logger
* @param string $msg
* @return null
*/
protected function _log($msg)
{
require_once 'Minify/Logger.php';
Minify_Logger::log($msg);
}
private $_path = null;
private $_locking = null;
private $_locking = null;
}

View File

@@ -60,9 +60,12 @@ class Minify_Cache_Memcache {
*/
public function getSize($id)
{
return $this->_fetch($id)
? strlen($this->_data)
: false;
if (! $this->_fetch($id)) {
return false;
}
return (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2))
? mb_strlen($this->_data, '8bit')
: strlen($this->_data);
}
/**

View File

@@ -0,0 +1,142 @@
<?php
/**
* Class Minify_Cache_ZendPlatform
* @package Minify
*/
/**
* ZendPlatform-based cache class for Minify
*
* Based on Minify_Cache_APC, uses output_cache_get/put (currently deprecated)
*
* <code>
* Minify::setCache(new Minify_Cache_ZendPlatform());
* </code>
*
* @package Minify
* @author Patrick van Dissel
*/
class Minify_Cache_ZendPlatform {
/**
* Create a Minify_Cache_ZendPlatform object, to be passed to
* Minify::setCache().
*
* @param int $expire seconds until expiration (default = 0
* meaning the item will not get an expiration date)
*
* @return null
*/
public function __construct($expire = 0)
{
$this->_exp = $expire;
}
/**
* Write data to cache.
*
* @param string $id cache id
*
* @param string $data
*
* @return bool success
*/
public function store($id, $data)
{
return output_cache_put($id, "{$_SERVER['REQUEST_TIME']}|{$data}");
}
/**
* Get the size of a cache entry
*
* @param string $id cache id
*
* @return int size in bytes
*/
public function getSize($id)
{
return $this->_fetch($id)
? strlen($this->_data)
: false;
}
/**
* Does a valid cache entry exist?
*
* @param string $id cache id
*
* @param int $srcMtime mtime of the original source file(s)
*
* @return bool exists
*/
public function isValid($id, $srcMtime)
{
$ret = ($this->_fetch($id) && ($this->_lm >= $srcMtime));
return $ret;
}
/**
* Send the cached content to output
*
* @param string $id cache id
*/
public function display($id)
{
echo $this->_fetch($id)
? $this->_data
: '';
}
/**
* Fetch the cached content
*
* @param string $id cache id
*
* @return string
*/
public function fetch($id)
{
return $this->_fetch($id)
? $this->_data
: '';
}
private $_exp = null;
// cache of most recently fetched id
private $_lm = null;
private $_data = null;
private $_id = null;
/**
* Fetch data and timestamp from ZendPlatform, store in instance
*
* @param string $id
*
* @return bool success
*/
private function _fetch($id)
{
if ($this->_id === $id) {
return true;
}
$ret = output_cache_get($id, $this->_exp);
if (false === $ret) {
$this->_id = null;
return false;
}
list($this->_lm, $this->_data) = explode('|', $ret, 2);
$this->_id = $id;
return true;
}
}

View File

@@ -30,8 +30,7 @@ class Minify_CommentPreserver {
* Process a string outside of C-style comments that begin with "/*!"
*
* On each non-empty string outside these comments, the given processor
* function will be called. The first "!" will be removed from the
* preserved comments, and the comments will be surrounded by
* function will be called. The comments will be surrounded by
* Minify_CommentPreserver::$preprend and Minify_CommentPreserver::$append.
*
* @param string $content
@@ -65,7 +64,7 @@ class Minify_CommentPreserver {
* @param string $in input
*
* @return array 3 elements are returned. If a YUI comment is found, the
* 2nd element is the comment and the 1st and 2nd are the surrounding
* 2nd element is the comment and the 1st and 3rd are the surrounding
* strings. If no comment is found, the entire string is returned as the
* 1st element and the other two are false.
*/
@@ -79,7 +78,7 @@ class Minify_CommentPreserver {
}
$ret = array(
substr($in, 0, $start)
,self::$prepend . '/*' . substr($in, $start + 3, $end - $start - 1) . self::$append
,self::$prepend . '/*!' . substr($in, $start + 3, $end - $start - 1) . self::$append
);
$endChars = (strlen($in) - $end - 2);
$ret[] = (0 === $endChars)

View File

@@ -52,9 +52,10 @@ abstract class Minify_Controller_Base {
,'quiet' => false // serve() will send headers and output
,'debug' => false
// if you override this, the response code MUST be directly after
// if you override these, the response codes MUST be directly after
// the first space.
,'badRequestHeader' => 'HTTP/1.0 400 Bad Request'
,'errorHeader' => 'HTTP/1.0 500 Internal Server Error'
// callback function to see/modify content of all sources
,'postprocessor' => null
@@ -117,6 +118,8 @@ abstract class Minify_Controller_Base {
* be in subdirectories of these directories.
*
* @return bool file is safe
*
* @deprecated use checkAllowDirs, checkNotHidden instead
*/
public static function _fileIsSafe($file, $safeDirs)
{
@@ -134,7 +137,28 @@ abstract class Minify_Controller_Base {
list($revExt) = explode('.', strrev($base));
return in_array(strrev($revExt), array('js', 'css', 'html', 'txt'));
}
public static function checkAllowDirs($file, $allowDirs, $uri)
{
foreach ((array)$allowDirs as $allowDir) {
if (strpos($file, $allowDir) === 0) {
return true;
}
}
throw new Exception("File '$file' is outside \$allowDirs. If the path is"
. " resolved via an alias/symlink, look into the \$min_symlinks option."
. " E.g. \$min_symlinks['/" . dirname($uri) . "'] = '" . dirname($file) . "';");
}
public static function checkNotHidden($file)
{
$b = basename($file);
if (0 === strpos($b, '.')) {
throw new Exception("Filename '$b' starts with period (may be hidden)");
}
}
/**
* @var array instances of Minify_Source, which provide content and
* any individual minification needs.
@@ -143,6 +167,15 @@ abstract class Minify_Controller_Base {
*/
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
*
@@ -195,7 +228,7 @@ abstract class Minify_Controller_Base {
* @param string $msg
* @return null
*/
protected function log($msg) {
public function log($msg) {
require_once 'Minify/Logger.php';
Minify_Logger::log($msg);
}

View File

@@ -28,64 +28,86 @@ class Minify_Controller_MinApp extends Minify_Controller_Base {
'allowDirs' => '//'
,'groupsOnly' => false
,'groups' => array()
,'maxFiles' => 10
,'noMinPattern' => '@[-\\.]min\\.(?:js|css)$@i' // matched against basename
)
,(isset($options['minApp']) ? $options['minApp'] : array())
);
unset($options['minApp']);
$sources = array();
$this->selectionId = '';
$missingUri = '';
if (isset($_GET['g'])) {
// try groups
if (! isset($cOptions['groups'][$_GET['g']])) {
$this->log("A group configuration for \"{$_GET['g']}\" was not set");
// add group(s)
$this->selectionId .= 'g=' . $_GET['g'];
$keys = explode(',', $_GET['g']);
if ($keys != array_unique($keys)) {
$this->log("Duplicate group key found.");
return $options;
}
$files = $cOptions['groups'][$_GET['g']];
// if $files is a single object, casting will break it
if (is_object($files)) {
$files = array($files);
} elseif (! is_array($files)) {
$files = (array)$files;
}
foreach ($files as $file) {
if ($file instanceof Minify_Source) {
$sources[] = $file;
continue;
}
if (0 === strpos($file, '//')) {
$file = $_SERVER['DOCUMENT_ROOT'] . substr($file, 1);
}
$file = realpath($file);
if (is_file($file)) {
$sources[] = new Minify_Source(array(
'filepath' => $file
));
} else {
$this->log("The path \"{$file}\" could not be found (or was not a file)");
foreach (explode(',', $_GET['g']) as $key) {
if (! isset($cOptions['groups'][$key])) {
$this->log("A group configuration for \"{$key}\" was not found");
return $options;
}
$files = $cOptions['groups'][$key];
// if $files is a single object, casting will break it
if (is_object($files)) {
$files = array($files);
} elseif (! is_array($files)) {
$files = (array)$files;
}
foreach ($files as $file) {
if ($file instanceof Minify_Source) {
$sources[] = $file;
continue;
}
if (0 === strpos($file, '//')) {
$file = $_SERVER['DOCUMENT_ROOT'] . substr($file, 1);
}
$realpath = realpath($file);
if ($realpath && is_file($realpath)) {
$sources[] = $this->_getFileSource($realpath, $cOptions);
} else {
$this->log("The path \"{$file}\" (realpath \"{$realpath}\") could not be found (or was not a file)");
return $options;
}
}
if ($sources) {
try {
$this->checkType($sources[0]);
} catch (Exception $e) {
$this->log($e->getMessage());
return $options;
}
}
}
} elseif (! $cOptions['groupsOnly'] && isset($_GET['f'])) {
}
if (! $cOptions['groupsOnly'] && isset($_GET['f'])) {
// try user files
// The following restrictions are to limit the URLs that minify will
// respond to. Ideally there should be only one way to reference a file.
// respond to.
if (// verify at least one file, files are single comma separated,
// and are all same extension
! preg_match('/^[^,]+\\.(css|js)(?:,[^,]+\\.\\1)*$/', $_GET['f'])
! preg_match('/^[^,]+\\.(css|js)(?:,[^,]+\\.\\1)*$/', $_GET['f'], $m)
// no "//"
|| strpos($_GET['f'], '//') !== false
// no "\"
|| strpos($_GET['f'], '\\') !== false
// no "./"
|| preg_match('/(?:^|[^\\.])\\.\\//', $_GET['f'])
) {
$this->log("GET param 'f' invalid (see MinApp.php line 63)");
$this->log("GET param 'f' was invalid");
return $options;
}
$ext = ".{$m[1]}";
try {
$this->checkType($m[1]);
} catch (Exception $e) {
$this->log($e->getMessage());
return $options;
}
$files = explode(',', $_GET['f']);
if (count($files) > $cOptions['maxFiles'] || $files != array_unique($files)) {
$this->log("Too many or duplicate files specified");
if ($files != array_unique($files)) {
$this->log("Duplicate files were specified");
return $options;
}
if (isset($_GET['b'])) {
@@ -96,7 +118,7 @@ class Minify_Controller_MinApp extends Minify_Controller_Base {
// valid base
$base = "/{$_GET['b']}/";
} else {
$this->log("GET param 'b' invalid (see MinApp.php line 84)");
$this->log("GET param 'b' was invalid");
return $options;
}
} else {
@@ -106,27 +128,82 @@ class Minify_Controller_MinApp extends Minify_Controller_Base {
foreach ((array)$cOptions['allowDirs'] as $allowDir) {
$allowDirs[] = realpath(str_replace('//', $_SERVER['DOCUMENT_ROOT'] . '/', $allowDir));
}
$basenames = array(); // just for cache id
foreach ($files as $file) {
$path = $_SERVER['DOCUMENT_ROOT'] . $base . $file;
$file = realpath($path);
if (false === $file) {
$this->log("Path \"{$path}\" failed realpath()");
return $options;
} elseif (! parent::_fileIsSafe($file, $allowDirs)) {
$this->log("Path \"{$path}\" failed Minify_Controller_Base::_fileIsSafe()");
return $options;
} else {
$sources[] = new Minify_Source(array(
'filepath' => $file
));
$uri = $base . $file;
$path = $_SERVER['DOCUMENT_ROOT'] . $uri;
$realpath = realpath($path);
if (false === $realpath || ! is_file($realpath)) {
$this->log("The path \"{$path}\" (realpath \"{$realpath}\") could not be found (or was not a file)");
if (! $missingUri) {
$missingUri = $uri;
continue;
} else {
$this->log("More than one file was missing: '$missingUri', '$uri'");
return $options;
}
}
try {
parent::checkNotHidden($realpath);
parent::checkAllowDirs($realpath, $allowDirs, $uri);
} catch (Exception $e) {
$this->log($e->getMessage());
return $options;
}
$sources[] = $this->_getFileSource($realpath, $cOptions);
$basenames[] = basename($realpath, $ext);
}
if ($this->selectionId) {
$this->selectionId .= '_f=';
}
$this->selectionId .= implode(',', $basenames) . $ext;
}
if ($sources) {
if ($missingUri) {
array_unshift($sources, new Minify_Source(array(
'id' => 'missingFile'
,'lastModified' => 0
,'content' => "/* Minify: missing file '" . ltrim($missingUri, '/') . "' */\n"
,'minifier' => ''
)));
}
$this->sources = $sources;
} else {
$this->log("No sources to serve");
}
return $options;
}
protected function _getFileSource($file, $cOptions)
{
$spec['filepath'] = $file;
if ($cOptions['noMinPattern']
&& preg_match($cOptions['noMinPattern'], basename($file))) {
$spec['minifier'] = '';
}
return new Minify_Source($spec);
}
protected $_type = null;
/*
* Make sure that only source files of a single type are registered
*/
public function checkType($sourceOrExt)
{
if ($sourceOrExt === 'js') {
$type = Minify::TYPE_JS;
} elseif ($sourceOrExt === 'css') {
$type = Minify::TYPE_CSS;
} elseif ($sourceOrExt->contentType !== null) {
$type = $sourceOrExt->contentType;
} else {
return;
}
if ($this->_type === null) {
$this->_type = $type;
} elseif ($this->_type !== $type) {
throw new Exception('Content-Type mismatch');
}
}
}

View File

@@ -40,14 +40,19 @@ class Minify_Controller_Page extends Minify_Controller_Base {
$sourceSpec = array(
'filepath' => $options['file']
);
$f = $options['file'];
} else {
// strip controller options
$sourceSpec = array(
'content' => $options['content']
,'id' => $options['id']
);
$f = $options['id'];
unset($options['content'], $options['id']);
}
// something like "builder,index.php" or "directory,file.html"
$this->selectionId = strtr(substr($f, 1 + strlen(dirname(dirname($f)))), '/\\', ',,');
if (isset($options['minifyAll'])) {
// this will be the 2nd argument passed to Minify_HTML::minify()
$sourceSpec['minifyOptions'] = array(

View File

@@ -7,7 +7,7 @@
require_once 'Minify/Controller/Base.php';
/**
* Controller class for emulating version 1 of minify.php
* Controller class for emulating version 1 of minify.php (mostly a proof-of-concept)
*
* <code>
* Minify::serve('Version1');

View File

@@ -1,245 +1,246 @@
<?php
/**
* Class Minify_HTML
* @package Minify
*/
/**
* Compress HTML
*
* This is a heavy regex-based removal of whitespace, unnecessary comments and
* tokens. IE conditional comments are preserved. There are also options to have
* STYLE and SCRIPT blocks compressed by callback functions.
*
* A test suite is available.
*
* @package Minify
* @author Stephen Clay <steve@mrclay.org>
*/
class Minify_HTML {
/**
* "Minify" an HTML page
*
* @param string $html
*
* @param array $options
*
* 'cssMinifier' : (optional) callback function to process content of STYLE
* elements.
*
* 'jsMinifier' : (optional) callback function to process content of SCRIPT
* elements. Note: the type attribute is ignored.
*
* 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
* unset, minify will sniff for an XHTML doctype.
*
* @return string
*/
public static function minify($html, $options = array()) {
$min = new Minify_HTML($html, $options);
return $min->process();
}
/**
* Create a minifier object
*
* @param string $html
*
* @param array $options
*
* 'cssMinifier' : (optional) callback function to process content of STYLE
* elements.
*
* 'jsMinifier' : (optional) callback function to process content of SCRIPT
* elements. Note: the type attribute is ignored.
*
* 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
* unset, minify will sniff for an XHTML doctype.
*
* @return null
*/
public function __construct($html, $options = array())
{
$this->_html = str_replace("\r\n", "\n", trim($html));
if (isset($options['xhtml'])) {
$this->_isXhtml = (bool)$options['xhtml'];
}
if (isset($options['cssMinifier'])) {
$this->_cssMinifier = $options['cssMinifier'];
}
if (isset($options['jsMinifier'])) {
$this->_jsMinifier = $options['jsMinifier'];
}
}
/**
* Minify the markeup given in the constructor
*
* @return string
*/
public function process()
{
if ($this->_isXhtml === null) {
$this->_isXhtml = (false !== strpos($this->_html, '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML'));
}
$this->_replacementHash = 'MINIFYHTML' . md5($_SERVER['REQUEST_TIME']);
$this->_placeholders = array();
// replace SCRIPTs (and minify) with placeholders
$this->_html = preg_replace_callback(
'/(\\s*)(<script\\b[^>]*?>)([\\s\\S]*?)<\\/script>(\\s*)/i'
,array($this, '_removeScriptCB')
,$this->_html);
// replace STYLEs (and minify) with placeholders
$this->_html = preg_replace_callback(
'/\\s*(<style\\b[^>]*?>)([\\s\\S]*?)<\\/style>\\s*/i'
,array($this, '_removeStyleCB')
,$this->_html);
// remove HTML comments (not containing IE conditional comments).
$this->_html = preg_replace_callback(
'/<!--([\\s\\S]*?)-->/'
,array($this, '_commentCB')
,$this->_html);
// replace PREs with placeholders
$this->_html = preg_replace_callback('/\\s*(<pre\\b[^>]*?>[\\s\\S]*?<\\/pre>)\\s*/i'
,array($this, '_removePreCB')
,$this->_html);
// replace TEXTAREAs with placeholders
$this->_html = preg_replace_callback(
'/\\s*(<textarea\\b[^>]*?>[\\s\\S]*?<\\/textarea>)\\s*/i'
,array($this, '_removeTextareaCB')
,$this->_html);
// trim each line.
// @todo take into account attribute values that span multiple lines.
$this->_html = preg_replace('/^\\s+|\\s+$/m', '', $this->_html);
// remove ws around block/undisplayed elements
$this->_html = preg_replace('/\\s+(<\\/?(?:area|base(?:font)?|blockquote|body'
.'|caption|center|cite|col(?:group)?|dd|dir|div|dl|dt|fieldset|form'
.'|frame(?:set)?|h[1-6]|head|hr|html|legend|li|link|map|menu|meta'
.'|ol|opt(?:group|ion)|p|param|t(?:able|body|head|d|h||r|foot|itle)'
.'|ul)\\b[^>]*>)/i', '$1', $this->_html);
// remove ws outside of all elements
$this->_html = preg_replace_callback(
'/>([^<]+)</'
,array($this, '_outsideTagCB')
,$this->_html);
// use newlines before 1st attribute in open tags (to limit line lengths)
$this->_html = preg_replace('/(<[a-z\\-]+)\\s+([^>]+>)/i', "$1\n$2", $this->_html);
// fill placeholders
$this->_html = str_replace(
array_keys($this->_placeholders)
,array_values($this->_placeholders)
,$this->_html
);
return $this->_html;
}
protected function _commentCB($m)
{
return (0 === strpos($m[1], '[') || false !== strpos($m[1], '<!['))
? $m[0]
: '';
}
protected function _reservePlace($content)
{
$placeholder = '%' . $this->_replacementHash . count($this->_placeholders) . '%';
$this->_placeholders[$placeholder] = $content;
return $placeholder;
}
protected $_isXhtml = null;
protected $_replacementHash = null;
protected $_placeholders = array();
protected $_cssMinifier = null;
protected $_jsMinifier = null;
protected function _outsideTagCB($m)
{
return '>' . preg_replace('/^\\s+|\\s+$/', ' ', $m[1]) . '<';
}
protected function _removePreCB($m)
{
return $this->_reservePlace($m[1]);
}
protected function _removeTextareaCB($m)
{
return $this->_reservePlace($m[1]);
}
protected function _removeStyleCB($m)
{
$openStyle = $m[1];
$css = $m[2];
// remove HTML comments
$css = preg_replace('/(?:^\\s*<!--|-->\\s*$)/', '', $css);
// remove CDATA section markers
$css = $this->_removeCdata($css);
// minify
$minifier = $this->_cssMinifier
? $this->_cssMinifier
: 'trim';
$css = call_user_func($minifier, $css);
return $this->_reservePlace($this->_needsCdata($css)
? "{$openStyle}/*<![CDATA[*/{$css}/*]]>*/</style>"
: "{$openStyle}{$css}</style>"
);
}
protected function _removeScriptCB($m)
{
$openScript = $m[2];
$js = $m[3];
// whitespace surrounding? preserve at least one space
$ws1 = ($m[1] === '') ? '' : ' ';
$ws2 = ($m[4] === '') ? '' : ' ';
// remove HTML comments (and ending "//" if present)
$js = preg_replace('/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $js);
// remove CDATA section markers
$js = $this->_removeCdata($js);
// minify
$minifier = $this->_jsMinifier
? $this->_jsMinifier
: 'trim';
$js = call_user_func($minifier, $js);
return $this->_reservePlace($this->_needsCdata($js)
? "{$ws1}{$openScript}/*<![CDATA[*/{$js}/*]]>*/</script>{$ws2}"
: "{$ws1}{$openScript}{$js}</script>{$ws2}"
);
}
protected function _removeCdata($str)
{
return (false !== strpos($str, '<![CDATA['))
? str_replace(array('<![CDATA[', ']]>'), '', $str)
: $str;
}
protected function _needsCdata($str)
{
return ($this->_isXhtml && preg_match('/(?:[<&]|\\-\\-|\\]\\]>)/', $str));
}
}
<?php
/**
* Class Minify_HTML
* @package Minify
*/
/**
* Compress HTML
*
* This is a heavy regex-based removal of whitespace, unnecessary comments and
* tokens. IE conditional comments are preserved. There are also options to have
* STYLE and SCRIPT blocks compressed by callback functions.
*
* A test suite is available.
*
* @package Minify
* @author Stephen Clay <steve@mrclay.org>
*/
class Minify_HTML {
/**
* "Minify" an HTML page
*
* @param string $html
*
* @param array $options
*
* 'cssMinifier' : (optional) callback function to process content of STYLE
* elements.
*
* 'jsMinifier' : (optional) callback function to process content of SCRIPT
* elements. Note: the type attribute is ignored.
*
* 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
* unset, minify will sniff for an XHTML doctype.
*
* @return string
*/
public static function minify($html, $options = array()) {
$min = new Minify_HTML($html, $options);
return $min->process();
}
/**
* Create a minifier object
*
* @param string $html
*
* @param array $options
*
* 'cssMinifier' : (optional) callback function to process content of STYLE
* elements.
*
* 'jsMinifier' : (optional) callback function to process content of SCRIPT
* elements. Note: the type attribute is ignored.
*
* 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
* unset, minify will sniff for an XHTML doctype.
*
* @return null
*/
public function __construct($html, $options = array())
{
$this->_html = str_replace("\r\n", "\n", trim($html));
if (isset($options['xhtml'])) {
$this->_isXhtml = (bool)$options['xhtml'];
}
if (isset($options['cssMinifier'])) {
$this->_cssMinifier = $options['cssMinifier'];
}
if (isset($options['jsMinifier'])) {
$this->_jsMinifier = $options['jsMinifier'];
}
}
/**
* Minify the markeup given in the constructor
*
* @return string
*/
public function process()
{
if ($this->_isXhtml === null) {
$this->_isXhtml = (false !== strpos($this->_html, '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML'));
}
$this->_replacementHash = 'MINIFYHTML' . md5($_SERVER['REQUEST_TIME']);
$this->_placeholders = array();
// replace SCRIPTs (and minify) with placeholders
$this->_html = preg_replace_callback(
'/(\\s*)<script(\\b[^>]*?>)([\\s\\S]*?)<\\/script>(\\s*)/i'
,array($this, '_removeScriptCB')
,$this->_html);
// replace STYLEs (and minify) with placeholders
$this->_html = preg_replace_callback(
'/\\s*<style(\\b[^>]*>)([\\s\\S]*?)<\\/style>\\s*/i'
,array($this, '_removeStyleCB')
,$this->_html);
// remove HTML comments (not containing IE conditional comments).
$this->_html = preg_replace_callback(
'/<!--([\\s\\S]*?)-->/'
,array($this, '_commentCB')
,$this->_html);
// replace PREs with placeholders
$this->_html = preg_replace_callback('/\\s*<pre(\\b[^>]*?>[\\s\\S]*?<\\/pre>)\\s*/i'
,array($this, '_removePreCB')
,$this->_html);
// replace TEXTAREAs with placeholders
$this->_html = preg_replace_callback(
'/\\s*<textarea(\\b[^>]*?>[\\s\\S]*?<\\/textarea>)\\s*/i'
,array($this, '_removeTextareaCB')
,$this->_html);
// trim each line.
// @todo take into account attribute values that span multiple lines.
$this->_html = preg_replace('/^\\s+|\\s+$/m', '', $this->_html);
// remove ws around block/undisplayed elements
$this->_html = preg_replace('/\\s+(<\\/?(?:area|base(?:font)?|blockquote|body'
.'|caption|center|cite|col(?:group)?|dd|dir|div|dl|dt|fieldset|form'
.'|frame(?:set)?|h[1-6]|head|hr|html|legend|li|link|map|menu|meta'
.'|ol|opt(?:group|ion)|p|param|t(?:able|body|head|d|h||r|foot|itle)'
.'|ul)\\b[^>]*>)/i', '$1', $this->_html);
// remove ws outside of all elements
$this->_html = preg_replace(
'/>(\\s(?:\\s*))?([^<]+)(\\s(?:\s*))?</'
,'>$1$2$3<'
,$this->_html);
// use newlines before 1st attribute in open tags (to limit line lengths)
$this->_html = preg_replace('/(<[a-z\\-]+)\\s+([^>]+>)/i', "$1\n$2", $this->_html);
// fill placeholders
$this->_html = str_replace(
array_keys($this->_placeholders)
,array_values($this->_placeholders)
,$this->_html
);
// issue 229: multi-pass to catch scripts that didn't get replaced in textareas
$this->_html = str_replace(
array_keys($this->_placeholders)
,array_values($this->_placeholders)
,$this->_html
);
return $this->_html;
}
protected function _commentCB($m)
{
return (0 === strpos($m[1], '[') || false !== strpos($m[1], '<!['))
? $m[0]
: '';
}
protected function _reservePlace($content)
{
$placeholder = '%' . $this->_replacementHash . count($this->_placeholders) . '%';
$this->_placeholders[$placeholder] = $content;
return $placeholder;
}
protected $_isXhtml = null;
protected $_replacementHash = null;
protected $_placeholders = array();
protected $_cssMinifier = null;
protected $_jsMinifier = null;
protected function _removePreCB($m)
{
return $this->_reservePlace("<pre{$m[1]}");
}
protected function _removeTextareaCB($m)
{
return $this->_reservePlace("<textarea{$m[1]}");
}
protected function _removeStyleCB($m)
{
$openStyle = "<style{$m[1]}";
$css = $m[2];
// remove HTML comments
$css = preg_replace('/(?:^\\s*<!--|-->\\s*$)/', '', $css);
// remove CDATA section markers
$css = $this->_removeCdata($css);
// minify
$minifier = $this->_cssMinifier
? $this->_cssMinifier
: 'trim';
$css = call_user_func($minifier, $css);
return $this->_reservePlace($this->_needsCdata($css)
? "{$openStyle}/*<![CDATA[*/{$css}/*]]>*/</style>"
: "{$openStyle}{$css}</style>"
);
}
protected function _removeScriptCB($m)
{
$openScript = "<script{$m[2]}";
$js = $m[3];
// whitespace surrounding? preserve at least one space
$ws1 = ($m[1] === '') ? '' : ' ';
$ws2 = ($m[4] === '') ? '' : ' ';
// remove HTML comments (and ending "//" if present)
$js = preg_replace('/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $js);
// remove CDATA section markers
$js = $this->_removeCdata($js);
// minify
$minifier = $this->_jsMinifier
? $this->_jsMinifier
: 'trim';
$js = call_user_func($minifier, $js);
return $this->_reservePlace($this->_needsCdata($js)
? "{$ws1}{$openScript}/*<![CDATA[*/{$js}/*]]>*/</script>{$ws2}"
: "{$ws1}{$openScript}{$js}</script>{$ws2}"
);
}
protected function _removeCdata($str)
{
return (false !== strpos($str, '<![CDATA['))
? str_replace(array('<![CDATA[', ']]>'), '', $str)
: $str;
}
protected function _needsCdata($str)
{
return ($this->_isXhtml && preg_match('/(?:[<&]|\\-\\-|\\]\\]>)/', $str));
}
}

View File

@@ -0,0 +1,193 @@
<?php
/**
* Class Minify_HTML_Helper
* @package Minify
*/
/**
* Helpers for writing Minfy URIs into HTML
*
* @package Minify
* @author Stephen Clay <steve@mrclay.org>
*/
class Minify_HTML_Helper {
public $rewriteWorks = true;
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 array $opts options:
* 'farExpires' : (default true) append a modified timestamp for cache revving
* 'debug' : (default false) append debug flag
* 'charset' : (default 'UTF-8') for htmlspecialchars
* 'minAppUri' : (default '/min') URI of min directory
* 'rewriteWorks' : (default true) does mod_rewrite work in min app?
* 'groupsConfigFile' : specify if different
* @return string
*/
public static function getUri($keyOrFiles, $opts = array())
{
$opts = array_merge(array( // default options
'farExpires' => true
,'debug' => false
,'charset' => 'UTF-8'
,'minAppUri' => '/min'
,'rewriteWorks' => true
,'groupsConfigFile' => ''
), $opts);
$h = new self;
$h->minAppUri = $opts['minAppUri'];
$h->rewriteWorks = $opts['rewriteWorks'];
$h->groupsConfigFile = $opts['groupsConfigFile'];
if (is_array($keyOrFiles)) {
$h->setFiles($keyOrFiles, $opts['farExpires']);
} else {
$h->setGroup($keyOrFiles, $opts['farExpires']);
}
$uri = $h->getRawUri($opts['farExpires'], $opts['debug']);
return htmlspecialchars($uri, ENT_QUOTES, $opts['charset']);
}
/*
* Get non-HTML-escaped URI to minify the specified files
*/
public function getRawUri($farExpires = true, $debug = false)
{
$path = rtrim($this->minAppUri, '/') . '/';
if (! $this->rewriteWorks) {
$path .= '?';
}
if (null === $this->_groupKey) {
// @todo: implement shortest uri
$path = self::_getShortestUri($this->_filePaths, $path);
} else {
$path .= "g=" . $this->_groupKey;
}
if ($debug) {
$path .= "&debug";
} elseif ($farExpires && $this->_lastModified) {
$path .= "&" . $this->_lastModified;
}
return $path;
}
public function setFiles($files, $checkLastModified = true)
{
$this->_groupKey = null;
if ($checkLastModified) {
$this->_lastModified = self::getLastModified($files);
}
// normalize paths like in /min/f=<paths>
foreach ($files as $k => $file) {
if (0 === strpos($file, '//')) {
$file = substr($file, 2);
} elseif (0 === strpos($file, '/')
|| 1 === strpos($file, ':\\')) {
$file = substr($file, strlen($_SERVER['DOCUMENT_ROOT']) + 1);
}
$file = strtr($file, '\\', '/');
$files[$k] = $file;
}
$this->_filePaths = $files;
}
public function setGroup($key, $checkLastModified = true)
{
$this->_groupKey = $key;
if ($checkLastModified) {
if (! $this->groupsConfigFile) {
$this->groupsConfigFile = dirname(dirname(dirname(dirname(__FILE__)))) . '/groupsConfig.php';
}
if (is_file($this->groupsConfigFile)) {
$gc = (require $this->groupsConfigFile);
if (isset($gc[$key])) {
$this->_lastModified = self::getLastModified($gc[$key]);
}
}
}
}
public static function getLastModified($sources, $lastModified = 0)
{
$max = $lastModified;
foreach ((array)$sources as $source) {
if (is_object($source) && isset($source->lastModified)) {
$max = max($max, $source->lastModified);
} elseif (is_string($source)) {
if (0 === strpos($source, '//')) {
$source = $_SERVER['DOCUMENT_ROOT'] . substr($source, 1);
}
if (is_file($source)) {
$max = max($max, filemtime($source));
}
}
}
return $max;
}
protected $_groupKey = null; // if present, URI will be like g=...
protected $_filePaths = array();
protected $_lastModified = null;
/**
* In a given array of strings, find the character they all have at
* a particular index
*
* @param array $arr array of strings
* @param int $pos index to check
* @return mixed a common char or '' if any do not match
*/
protected static function _getCommonCharAtPos($arr, $pos) {
$l = count($arr);
$c = $arr[0][$pos];
if ($c === '' || $l === 1)
return $c;
for ($i = 1; $i < $l; ++$i)
if ($arr[$i][$pos] !== $c)
return '';
return $c;
}
/**
* Get the shortest URI to minify the set of source files
*
* @param array $paths root-relative URIs of files
* @param string $minRoot root-relative URI of the "min" application
*/
protected static function _getShortestUri($paths, $minRoot = '/min/') {
$pos = 0;
$base = '';
$c;
while (true) {
$c = self::_getCommonCharAtPos($paths, $pos);
if ($c === '') {
break;
} else {
$base .= $c;
}
++$pos;
}
$base = preg_replace('@[^/]+$@', '', $base);
$uri = $minRoot . 'f=' . implode(',', $paths);
if (substr($base, -1) === '/') {
// we have a base dir!
$basedPaths = $paths;
$l = count($paths);
for ($i = 0; $i < $l; ++$i) {
$basedPaths[$i] = substr($paths[$i], strlen($base));
}
$base = substr($base, 0, strlen($base) - 1);
$bUri = $minRoot . 'b=' . $base . '&f=' . implode(',', $basedPaths);
$uri = strlen($uri) < strlen($bUri)
? $uri
: $bUri;
}
return $uri;
}
}

View File

@@ -0,0 +1,116 @@
<?php
/**
* Class Minify_JS_ClosureCompiler
* @package Minify
*/
/**
* Minify Javascript using Google's Closure Compiler API
*
* @link http://code.google.com/closure/compiler/
* @package Minify
* @author Stephen Clay <steve@mrclay.org>
*
* @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
*
* @param string $js input code
* @param array $options unused at this point
* @return string
*/
public static function minify($js, array $options = array())
{
$obj = new self($options);
return $obj->min($js);
}
/**
*
* @param array $options
*
* fallbackFunc : default array($this, 'fallback');
*/
public function __construct(array $options = array())
{
$this->_fallbackFunc = isset($options['fallbackMinifier'])
? $options['fallbackMinifier']
: array($this, '_fallback');
}
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'
);
}
$response = $this->_getResponse($postBody);
if (preg_match('/^Error\(\d\d?\):/', $response)) {
if (is_callable($this->_fallbackFunc)) {
$response = "/* Received errors from Closure Compiler API:\n$response"
. "\n(Using fallback minifier)\n*/\n";
$response .= call_user_func($this->_fallbackFunc, $js);
} else {
throw new Minify_JS_ClosureCompiler_Exception($response);
}
}
if ($response === '') {
$errors = $this->_getResponse($this->_buildPostBody($js, true));
throw new Minify_JS_ClosureCompiler_Exception($errors);
}
return $response;
}
protected $_fallbackFunc = null;
protected function _getResponse($postBody)
{
$contents = file_get_contents(self::URL, false, stream_context_create(array(
'http' => array(
'method' => 'POST',
'header' => 'Content-type: application/x-www-form-urlencoded',
'content' => $postBody,
'max_redirects' => 0,
'timeout' => 15,
)
)));
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, '&');
}
/**
* Default fallback function if CC API fails
* @param string $js
* @return string
*/
protected function _fallback($js)
{
require_once 'JSMin.php';
return JSMin::minify($js);
}
}
class Minify_JS_ClosureCompiler_Exception extends Exception {}

View File

@@ -26,9 +26,9 @@ class Minify_Lines {
*
* 'id': (optional) string to identify file. E.g. file name/path
*
* 'currentDir': (default null) if given, this is assumed to be the
* directory of the current CSS file. Using this, minify will rewrite
* all relative URIs in import/url declarations to correctly point to
* 'currentDir': (default null) if given, this is assumed to be the
* directory of the current CSS file. Using this, minify will rewrite
* all relative URIs in import/url declarations to correctly point to
* the desired files, and prepend a comment with debugging information about
* this process.
*
@@ -40,10 +40,16 @@ class Minify_Lines {
? $options['id']
: '';
$content = str_replace("\r\n", "\n", $content);
// Hackily rewrite strings with XPath expressions that are
// likely to throw off our dumb parser (for Prototype 1.6.1).
$content = str_replace('"/*"', '"/"+"*"', $content);
$content = preg_replace('@([\'"])(\\.?//?)\\*@', '$1$2$1+$1*', $content);
$lines = explode("\n", $content);
$numLines = count($lines);
// determine left padding
$padTo = strlen($numLines);
$padTo = strlen((string) $numLines); // e.g. 103 lines = 3 digits
$inComment = false;
$i = 0;
$newLines = array();
@@ -64,7 +70,7 @@ class Minify_Lines {
$content = Minify_CSS_UriRewriter::rewrite(
$content
,$options['currentDir']
,isset($options['docRoot']) ? $options['docRoot'] : $_SERVER['DOCUMENT_ROOT']
,isset($options['docRoot']) ? $options['docRoot'] : $_SERVER['DOCUMENT_ROOT']
,isset($options['symlinks']) ? $options['symlinks'] : array()
);
$content = "/* Minify_CSS_UriRewriter::\$debugText\n\n"
@@ -82,7 +88,7 @@ class Minify_Lines {
*
* @param bool $inComment was the parser in a comment at the
* beginning of the line?
*
*
* @return bool
*/
private static function _eolInComment($line, $inComment)

View File

@@ -9,6 +9,8 @@
*
* @package Minify
* @author Stephen Clay <steve@mrclay.org>
*
* @todo lose this singleton! pass log object in Minify::serve and distribute to others
*/
class Minify_Logger {

View File

@@ -0,0 +1,291 @@
/*
* YUI Compressor
* Author: Julien Lecomte - http://www.julienlecomte.net/
* Author: Isaac Schlueter - http://foohack.com/
* Author: Stoyan Stefanov - http://phpied.com/
* Copyright (c) 2009 Yahoo! Inc. All rights reserved.
* The copyrights embodied in the content of this file are licensed
* by Yahoo! Inc. under the BSD (revised) open source license.
*/
package com.yahoo.platform.yui.compressor;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.ArrayList;
public class CssCompressor {
private StringBuffer srcsb = new StringBuffer();
public CssCompressor(Reader in) throws IOException {
// Read the stream...
int c;
while ((c = in.read()) != -1) {
srcsb.append((char) c);
}
}
public void compress(Writer out, int linebreakpos)
throws IOException {
Pattern p;
Matcher m;
String css = srcsb.toString();
StringBuffer sb = new StringBuffer(css);
int startIndex = 0;
int endIndex = 0;
int i = 0;
int max = 0;
ArrayList preservedTokens = new ArrayList(0);
ArrayList comments = new ArrayList(0);
String token;
int totallen = css.length();
String placeholder;
// collect all comment blocks...
while ((startIndex = sb.indexOf("/*", startIndex)) >= 0) {
endIndex = sb.indexOf("*/", startIndex + 2);
if (endIndex < 0) {
endIndex = totallen;
}
token = sb.substring(startIndex + 2, endIndex);
comments.add(token);
sb.replace(startIndex + 2, endIndex, "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + (comments.size() - 1) + "___");
startIndex += 2;
}
css = sb.toString();
// preserve strings so their content doesn't get accidentally minified
sb = new StringBuffer();
p = Pattern.compile("(\"([^\\\\\"]|\\\\.|\\\\)*\")|(\'([^\\\\\']|\\\\.|\\\\)*\')");
m = p.matcher(css);
while (m.find()) {
token = m.group();
char quote = token.charAt(0);
token = token.substring(1, token.length() - 1);
// maybe the string contains a comment-like substring?
// one, maybe more? put'em back then
if (token.indexOf("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_") >= 0) {
for (i = 0, max = comments.size(); i < max; i += 1) {
token = token.replace("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___", comments.get(i).toString());
}
}
// minify alpha opacity in filter strings
token = token.replaceAll("(?i)progid:DXImageTransform.Microsoft.Alpha\\(Opacity=", "alpha(opacity=");
preservedTokens.add(token);
String preserver = quote + "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___" + quote;
m.appendReplacement(sb, preserver);
}
m.appendTail(sb);
css = sb.toString();
// strings are safe, now wrestle the comments
for (i = 0, max = comments.size(); i < max; i += 1) {
token = comments.get(i).toString();
placeholder = "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___";
// ! in the first position of the comment means preserve
// so push to the preserved tokens while stripping the !
if (token.startsWith("!")) {
preservedTokens.add(token);
css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___");
continue;
}
// \ in the last position looks like hack for Mac/IE5
// shorten that to /*\*/ and the next one to /**/
if (token.endsWith("\\")) {
preservedTokens.add("\\");
css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___");
i = i + 1; // attn: advancing the loop
preservedTokens.add("");
css = css.replace("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___", "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___");
continue;
}
// keep empty comments after child selectors (IE7 hack)
// e.g. html >/**/ body
if (token.length() == 0) {
startIndex = css.indexOf(placeholder);
if (startIndex > 2) {
if (css.charAt(startIndex - 3) == '>') {
preservedTokens.add("");
css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___");
}
}
}
// in all other cases kill the comment
css = css.replace("/*" + placeholder + "*/", "");
}
// Normalize all whitespace strings to single spaces. Easier to work with that way.
css = css.replaceAll("\\s+", " ");
// Remove the spaces before the things that should not have spaces before them.
// But, be careful not to turn "p :link {...}" into "p:link{...}"
// Swap out any pseudo-class colons with the token, and then swap back.
sb = new StringBuffer();
p = Pattern.compile("(^|\\})(([^\\{:])+:)+([^\\{]*\\{)");
m = p.matcher(css);
while (m.find()) {
String s = m.group();
s = s.replaceAll(":", "___YUICSSMIN_PSEUDOCLASSCOLON___");
s = s.replaceAll( "\\\\", "\\\\\\\\" ).replaceAll( "\\$", "\\\\\\$" );
m.appendReplacement(sb, s);
}
m.appendTail(sb);
css = sb.toString();
// Remove spaces before the things that should not have spaces before them.
css = css.replaceAll("\\s+([!{};:>+\\(\\)\\],])", "$1");
// bring back the colon
css = css.replaceAll("___YUICSSMIN_PSEUDOCLASSCOLON___", ":");
// retain space for special IE6 cases
css = css.replaceAll(":first\\-(line|letter)(\\{|,)", ":first-$1 $2");
// no space after the end of a preserved comment
css = css.replaceAll("\\*/ ", "*/");
// If there is a @charset, then only allow one, and push to the top of the file.
css = css.replaceAll("^(.*)(@charset \"[^\"]*\";)", "$2$1");
css = css.replaceAll("^(\\s*@charset [^;]+;\\s*)+", "$1");
// Put the space back in some cases, to support stuff like
// @media screen and (-webkit-min-device-pixel-ratio:0){
css = css.replaceAll("\\band\\(", "and (");
// Remove the spaces after the things that should not have spaces after them.
css = css.replaceAll("([!{}:;>+\\(\\[,])\\s+", "$1");
// remove unnecessary semicolons
css = css.replaceAll(";+}", "}");
// Replace 0(px,em,%) with 0.
css = css.replaceAll("([\\s:])(0)(px|em|%|in|cm|mm|pc|pt|ex)", "$1$2");
// Replace 0 0 0 0; with 0.
css = css.replaceAll(":0 0 0 0(;|})", ":0$1");
css = css.replaceAll(":0 0 0(;|})", ":0$1");
css = css.replaceAll(":0 0(;|})", ":0$1");
// Replace background-position:0; with background-position:0 0;
// same for transform-origin
sb = new StringBuffer();
p = Pattern.compile("(?i)(background-position|transform-origin|webkit-transform-origin|moz-transform-origin|o-transform-origin|ms-transform-origin):0(;|})");
m = p.matcher(css);
while (m.find()) {
m.appendReplacement(sb, m.group(1).toLowerCase() + ":0 0" + m.group(2));
}
m.appendTail(sb);
css = sb.toString();
// Replace 0.6 to .6, but only when preceded by : or a white-space
css = css.replaceAll("(:|\\s)0+\\.(\\d+)", "$1.$2");
// Shorten colors from rgb(51,102,153) to #336699
// This makes it more likely that it'll get further compressed in the next step.
p = Pattern.compile("rgb\\s*\\(\\s*([0-9,\\s]+)\\s*\\)");
m = p.matcher(css);
sb = new StringBuffer();
while (m.find()) {
String[] rgbcolors = m.group(1).split(",");
StringBuffer hexcolor = new StringBuffer("#");
for (i = 0; i < rgbcolors.length; i++) {
int val = Integer.parseInt(rgbcolors[i]);
if (val < 16) {
hexcolor.append("0");
}
hexcolor.append(Integer.toHexString(val));
}
m.appendReplacement(sb, hexcolor.toString());
}
m.appendTail(sb);
css = sb.toString();
// Shorten colors from #AABBCC to #ABC. Note that we want to make sure
// the color is not preceded by either ", " or =. Indeed, the property
// filter: chroma(color="#FFFFFF");
// would become
// filter: chroma(color="#FFF");
// which makes the filter break in IE.
p = Pattern.compile("([^\"'=\\s])(\\s*)#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])");
m = p.matcher(css);
sb = new StringBuffer();
while (m.find()) {
// Test for AABBCC pattern
if (m.group(3).equalsIgnoreCase(m.group(4)) &&
m.group(5).equalsIgnoreCase(m.group(6)) &&
m.group(7).equalsIgnoreCase(m.group(8))) {
m.appendReplacement(sb, (m.group(1) + m.group(2) + "#" + m.group(3) + m.group(5) + m.group(7)).toLowerCase());
} else {
m.appendReplacement(sb, m.group().toLowerCase());
}
}
m.appendTail(sb);
css = sb.toString();
// border: none -> border:0
sb = new StringBuffer();
p = Pattern.compile("(?i)(border|border-top|border-right|border-bottom|border-right|outline|background):none(;|})");
m = p.matcher(css);
while (m.find()) {
m.appendReplacement(sb, m.group(1).toLowerCase() + ":0" + m.group(2));
}
m.appendTail(sb);
css = sb.toString();
// shorter opacity IE filter
css = css.replaceAll("(?i)progid:DXImageTransform.Microsoft.Alpha\\(Opacity=", "alpha(opacity=");
// Remove empty rules.
css = css.replaceAll("[^\\}\\{/;]+\\{\\}", "");
if (linebreakpos >= 0) {
// Some source control tools don't like it when files containing lines longer
// than, say 8000 characters, are checked in. The linebreak option is used in
// that case to split long lines after a specific column.
i = 0;
int linestartpos = 0;
sb = new StringBuffer(css);
while (i < sb.length()) {
char c = sb.charAt(i++);
if (c == '}' && i - linestartpos > linebreakpos) {
sb.insert(i, '\n');
linestartpos = i;
}
}
css = sb.toString();
}
// Replace multiple semi-colons in a row by a single one
// See SF bug #1980989
css = css.replaceAll(";;+", ";");
// restore preserved comments and strings
for(i = 0, max = preservedTokens.size(); i < max; i++) {
css = css.replace("___YUICSSMIN_PRESERVED_TOKEN_" + i + "___", preservedTokens.get(i).toString());
}
// Trim the final string (for any leading or trailing white spaces)
css = css.trim();
// Write the output...
out.write(css);
}
}

View File

@@ -0,0 +1,171 @@
<?php
/**
* Class Minify_YUI_CssCompressor
* @package Minify
*
* YUI Compressor
* Author: Julien Lecomte - http://www.julienlecomte.net/
* Author: Isaac Schlueter - http://foohack.com/
* Author: Stoyan Stefanov - http://phpied.com/
* Author: Steve Clay - http://www.mrclay.org/ (PHP port)
* Copyright (c) 2009 Yahoo! Inc. All rights reserved.
* The copyrights embodied in the content of this file are licensed
* by Yahoo! Inc. under the BSD (revised) open source license.
*/
require_once dirname(__FILE__) . '/Java/String.php';
/**
* Compress CSS (incomplete DO NOT USE)
*
* @package Minify
*/
class Minify_YUI_CssCompressor {
/**
* Minify a CSS string
*
* @param string $css
*
* @return string
*/
public function compress($css, $linebreakpos = 0)
{
$css = str_replace("\r\n", "\n", $css);
/**
* @todo comment removal
* @todo re-port from newer Java version
*/
// Normalize all whitespace strings to single spaces. Easier to work with that way.
$css = preg_replace('@\s+@', ' ', $css);
// Make a pseudo class for the Box Model Hack
$css = preg_replace("@\"\\\\\"}\\\\\"\"@", "___PSEUDOCLASSBMH___", $css);
// Remove the spaces before the things that should not have spaces before them.
// But, be careful not to turn "p :link {...}" into "p:link{...}"
// Swap out any pseudo-class colons with the token, and then swap back.
$css = preg_replace_callback("@(^|\\})(([^\\{:])+:)+([^\\{]*\\{)@", array($this, '_removeSpacesCB'), $css);
$css = preg_replace("@\\s+([!{};:>+\\(\\)\\],])@", "$1", $css);
$css = str_replace("___PSEUDOCLASSCOLON___", ":", $css);
// Remove the spaces after the things that should not have spaces after them.
$css = preg_replace("@([!{}:;>+\\(\\[,])\\s+@", "$1", $css);
// Add the semicolon where it's missing.
$css = preg_replace("@([^;\\}])}@", "$1;}", $css);
// Replace 0(px,em,%) with 0.
$css = preg_replace("@([\\s:])(0)(px|em|%|in|cm|mm|pc|pt|ex)@", "$1$2", $css);
// Replace 0 0 0 0; with 0.
$css = str_replace(":0 0 0 0;", ":0;", $css);
$css = str_replace(":0 0 0;", ":0;", $css);
$css = str_replace(":0 0;", ":0;", $css);
// Replace background-position:0; with background-position:0 0;
$css = str_replace("background-position:0;", "background-position:0 0;", $css);
// Replace 0.6 to .6, but only when preceded by : or a white-space
$css = preg_replace("@(:|\\s)0+\\.(\\d+)@", "$1.$2", $css);
// Shorten colors from rgb(51,102,153) to #336699
// This makes it more likely that it'll get further compressed in the next step.
$css = preg_replace_callback("@rgb\\s*\\(\\s*([0-9,\\s]+)\\s*\\)@", array($this, '_shortenRgbCB'), $css);
// Shorten colors from #AABBCC to #ABC. Note that we want to make sure
// the color is not preceded by either ", " or =. Indeed, the property
// filter: chroma(color="#FFFFFF");
// would become
// filter: chroma(color="#FFF");
// which makes the filter break in IE.
$css = preg_replace_callback("@([^\"'=\\s])(\\s*)#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])@", array($this, '_shortenHexCB'), $css);
// Remove empty rules.
$css = preg_replace("@[^\\}]+\\{;\\}@", "", $css);
$linebreakpos = isset($this->_options['linebreakpos'])
? $this->_options['linebreakpos']
: 0;
if ($linebreakpos > 0) {
// Some source control tools don't like it when files containing lines longer
// than, say 8000 characters, are checked in. The linebreak option is used in
// that case to split long lines after a specific column.
$i = 0;
$linestartpos = 0;
$sb = $css;
// make sure strlen returns byte count
$mbIntEnc = null;
if (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) {
$mbIntEnc = mb_internal_encoding();
mb_internal_encoding('8bit');
}
$sbLength = strlen($css);
while ($i < $sbLength) {
$c = $sb[$i++];
if ($c === '}' && $i - $linestartpos > $linebreakpos) {
$sb = substr_replace($sb, "\n", $i, 0);
$sbLength++;
$linestartpos = $i;
}
}
$css = $sb;
// undo potential mb_encoding change
if ($mbIntEnc !== null) {
mb_internal_encoding($mbIntEnc);
}
}
// Replace the pseudo class for the Box Model Hack
$css = str_replace("___PSEUDOCLASSBMH___", "\"\\\\\"}\\\\\"\"", $css);
// Replace multiple semi-colons in a row by a single one
// See SF bug #1980989
$css = preg_replace("@;;+@", ";", $css);
// prevent triggering IE6 bug: http://www.crankygeek.com/ie6pebug/
$css = preg_replace('/:first-l(etter|ine)\\{/', ':first-l$1 {', $css);
// Trim the final string (for any leading or trailing white spaces)
$css = trim($css);
return $css;
}
protected function _removeSpacesCB($m)
{
return str_replace(':', '___PSEUDOCLASSCOLON___', $m[0]);
}
protected function _shortenRgbCB($m)
{
$rgbcolors = explode(',', $m[1]);
$hexcolor = '#';
for ($i = 0; $i < count($rgbcolors); $i++) {
$val = round($rgbcolors[$i]);
if ($val < 16) {
$hexcolor .= '0';
}
$hexcolor .= dechex($val);
}
return $hexcolor;
}
protected function _shortenHexCB($m)
{
// Test for AABBCC pattern
if ((strtolower($m[3])===strtolower($m[4])) &&
(strtolower($m[5])===strtolower($m[6])) &&
(strtolower($m[7])===strtolower($m[8]))) {
return $m[1] . $m[2] . "#" . $m[3] . $m[5] . $m[7];
} else {
return $m[0];
}
}
}

View File

@@ -0,0 +1,81 @@
<?php
require_once dirname(__FILE__) . '/String.php';
class Minify_YUI_Java_Matcher {
protected $_matches;
protected $_match;
protected $_subject;
protected $_appendPosition = 0;
public function __construct($pattern, $subject)
{
$this->_subject = $subject;
preg_match_all($pattern, $subject, $this->_matches, PREG_OFFSET_CAPTURE);
}
/**
*
* @return bool
*/
public function find()
{
$this->_match = current($this->_matches);
if ($this->_match) {
next($this->_matches);
return true;
}
return false;
}
public function group($group = 0)
{
return $this->_match[0][$group];
}
public function start()
{
return $this->_match[1];
}
public function end()
{
return $this->_match[1] + strlen($this->_match[0][0]);
}
public function appendReplacement(Minify_YUI_Java_String $string, $replacement)
{
$length = $this->start() - $this->_appendPosition;
$string->append(substr($this->_subject, $this->_appendPosition, $length));
$i = 0;
$newReplacement = '';
$next = '';
$length = strlen($replacement);
while ($i < $length) {
$curr = $replacement[$i];
$next = ($i === ($length - 1)) ? '' : $replacement[$i + 1];
if ($curr === '\\' && $next === '$') {
$newReplacement .= '$';
$i += 2;
continue;
}
if ($curr === '$' && is_numeric($next) && isset($this->_match[0][(int) $next])) {
$newReplacement .= $this->_match[0][(int) $next];
$i += 2;
continue;
}
$newReplacement .= $curr;
$i++;
}
$string->append($newReplacement);
$this->_appendPosition = $this->end();
}
public function appendTail(Minify_YUI_Java_String $string)
{
$string->append(substr($this->_subject, $this->_appendPosition));
}
}

View File

@@ -0,0 +1,64 @@
<?php
/**
* Class Minify_YUI_Java_String
* @package Minify
*/
if (function_exists('mb_strlen')) {
mb_internal_encoding('8bit'); // no multibyte strong functions, please
}
/**
* Allow PHP syntax of YUI's CssCompressor port to more closely match Java version
*/
class Minify_YUI_Java_String {
public $content;
public function __construct($content = '')
{
$this->content = $content;
}
public function replace($target, $replacement)
{
return new self(str_replace($target, $replacement, $this->content));
}
public function replaceAll($regex, $replacement)
{
$pattern = '/' . str_replace('/', '\/', $regex) . '/';
return new self(preg_replace($pattern, $replacement, $this->content));
}
/**
* Return position (in bytes) of string found or -1 (not FALSE!)
* @param string $needle
* @param int $offset
* @return int
*/
public function indexOf($needle, $offset = 0) {
$pos = strpos($this->content, $needle, $offset);
return ($pos === false)
? -1
: $pos;
}
/**
* Get number of bytes (not characters) in string
* @return int
*/
public function length()
{
return strlen($this->content);
}
public function toString()
{
return $this->content;
}
public function append($str)
{
$this->content .= $str;
}
}

View File

@@ -110,7 +110,7 @@ class Minify_YUICompressor {
);
$cmd = self::$javaExecutable . ' -jar ' . escapeshellarg(self::$jarFile)
. " --type {$type}"
. (preg_match('/^[a-zA-Z\\-]+$/', $o['charset'])
. (preg_match('/^[\\da-zA-Z0-9\\-]+$/', $o['charset'])
? " --charset {$o['charset']}"
: '')
. (is_numeric($o['line-break']) && $o['line-break'] >= 0
@@ -128,11 +128,14 @@ class Minify_YUICompressor {
private static function _prepare()
{
if (! is_file(self::$jarFile)
|| ! is_dir(self::$tempDir)
|| ! is_writable(self::$tempDir)
) {
throw new Exception('Minify_YUICompressor : $jarFile and $tempDir must be set.');
if (! is_file(self::$jarFile)) {
throw new Exception('Minify_YUICompressor : $jarFile('.self::$jarFile.') is not a valid file.');
}
if (! is_dir(self::$tempDir)) {
throw new Exception('Minify_YUICompressor : $tempDir('.self::$tempDir.') is not a valid direcotry.');
}
if (! is_writable(self::$tempDir)) {
throw new Exception('Minify_YUICompressor : $tempDir('.self::$tempDir.') is not writable.');
}
}
}

View File

@@ -1,199 +0,0 @@
<?php
/**
*
* Utility class for static directory methods.
*
* @category Solar
*
* @package Solar
*
* @author Paul M. Jones <pmjones@solarphp.com>
*
* @license http://opensource.org/licenses/bsd-license.php BSD
*
* @version $Id: Dir.php 2926 2007-11-09 16:25:44Z pmjones $
*
*/
class Solar_Dir {
/**
*
* The OS-specific temporary directory location.
*
* @var string
*
*/
protected static $_tmp;
/**
*
* Hack for [[php::is_dir() | ]] that checks the include_path.
*
* Use this to see if a directory exists anywhere in the include_path.
*
* {{code: php
* $dir = Solar_Dir::exists('path/to/dir')
* if ($dir) {
* $files = scandir($dir);
* } else {
* echo "Not found in the include-path.";
* }
* }}
*
* @param string $dir Check for this directory in the include_path.
*
* @return mixed If the directory exists in the include_path, returns the
* absolute path; if not, returns boolean false.
*
*/
public static function exists($dir)
{
// no file requested?
$dir = trim($dir);
if (! $dir) {
return false;
}
// using an absolute path for the file?
// dual check for Unix '/' and Windows '\',
// or Windows drive letter and a ':'.
$abs = ($dir[0] == '/' || $dir[0] == '\\' || $dir[1] == ':');
if ($abs && is_dir($dir)) {
return $dir;
}
// using a relative path on the file
$path = explode(PATH_SEPARATOR, ini_get('include_path'));
foreach ($path as $base) {
// strip Unix '/' and Windows '\'
$target = rtrim($base, '\\/') . DIRECTORY_SEPARATOR . $dir;
if (is_dir($target)) {
return $target;
}
}
// never found it
return false;
}
/**
*
* "Fixes" a directory string for the operating system.
*
* Use slashes anywhere you need a directory separator. Then run the
* string through fixdir() and the slashes will be converted to the
* proper separator (for example '\' on Windows).
*
* Always adds a final trailing separator.
*
* @param string $dir The directory string to 'fix'.
*
* @return string The "fixed" directory string.
*
*/
public static function fix($dir)
{
$dir = str_replace('/', DIRECTORY_SEPARATOR, $dir);
return rtrim($dir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
}
/**
*
* Convenience method for dirname() and higher-level directories.
*
* @param string $file Get the dirname() of this file.
*
* @param int $up Move up in the directory structure this many
* times, default 0.
*
* @return string The dirname() of the file.
*
*/
public static function name($file, $up = 0)
{
$dir = dirname($file);
while ($up --) {
$dir = dirname($dir);
}
return $dir;
}
/**
*
* Returns the OS-specific directory for temporary files.
*
* @param string $sub Add this subdirectory to the returned temporary
* directory name.
*
* @return string The temporary directory path.
*
*/
public static function tmp($sub = '')
{
// find the tmp dir if needed
if (! Solar_Dir::$_tmp) {
// use the system if we can
if (function_exists('sys_get_temp_dir')) {
$tmp = sys_get_temp_dir();
} else {
$tmp = Solar_Dir::_tmp();
}
// remove trailing separator and save
Solar_Dir::$_tmp = rtrim($tmp, DIRECTORY_SEPARATOR);
}
// do we have a subdirectory request?
$sub = trim($sub);
if ($sub) {
// remove leading and trailing separators, and force exactly
// one trailing separator
$sub = trim($sub, DIRECTORY_SEPARATOR)
. DIRECTORY_SEPARATOR;
}
return Solar_Dir::$_tmp . DIRECTORY_SEPARATOR . $sub;
}
/**
*
* Returns the OS-specific temporary directory location.
*
* @return string The temp directory path.
*
*/
protected static function _tmp()
{
// non-Windows system?
if (strtolower(substr(PHP_OS, 0, 3)) != 'win') {
$tmp = empty($_ENV['TMPDIR']) ? getenv('TMPDIR') : $_ENV['TMPDIR'];
if ($tmp) {
return $tmp;
} else {
return '/tmp';
}
}
// Windows 'TEMP'
$tmp = empty($_ENV['TEMP']) ? getenv('TEMP') : $_ENV['TEMP'];
if ($tmp) {
return $tmp;
}
// Windows 'TMP'
$tmp = empty($_ENV['TMP']) ? getenv('TMP') : $_ENV['TMP'];
if ($tmp) {
return $tmp;
}
// Windows 'windir'
$tmp = empty($_ENV['windir']) ? getenv('windir') : $_ENV['windir'];
if ($tmp) {
return $tmp;
}
// final fallback for Windows
return getenv('SystemRoot') . '\\temp';
}
}

View File

@@ -1,90 +1,74 @@
<?php
/**
* Utility functions for generating group URIs in HTML files
* Utility functions for generating URIs in HTML files
*
* Before including this file, /min/lib must be in your include_path.
*
* @package Minify
*/
require_once 'Minify/Build.php';
require_once dirname(__FILE__) . '/lib/Minify/HTML/Helper.php';
/**
* Get a timestamped URI to a minified resource using the default Minify install
/*
* Get an HTML-escaped Minify URI for a group or set of files. By default, URIs
* will contain timestamps to allow far-future Expires headers.
*
* <code>
* <link rel="stylesheet" type="text/css" href="<?php echo Minify_groupUri('css'); ?>" />
* <script type="text/javascript" src="<?php echo Minify_groupUri('js'); ?>"></script>
* <link rel="stylesheet" type="text/css" href="<?= Minify_getUri('css'); ?>" />
* <script src="<?= Minify_getUri('js'); ?>"></script>
* <script src="<?= Minify_getUri(array(
* '//scripts/file1.js'
* ,'//scripts/file2.js'
* )); ?>"></script>
* </code>
*
* If you do not want ampersands as HTML entities, set Minify_Build::$ampersand = "&"
* before using this function.
*
* @param string $group a key from groupsConfig.php
* @param boolean $forceAmpersand (default false) Set to true if the RewriteRule
* directives in .htaccess are functional. This will remove the "?" from URIs, making them
* more cacheable by proxies.
* @param mixed $keyOrFiles a group key or array of file paths/URIs
* @param array $opts options:
* 'farExpires' : (default true) append a modified timestamp for cache revving
* 'debug' : (default false) append debug flag
* 'charset' : (default 'UTF-8') for htmlspecialchars
* 'minAppUri' : (default '/min') URI of min directory
* 'rewriteWorks' : (default true) does mod_rewrite work in min app?
* 'groupsConfigFile' : specify if different
* @return string
*/
function Minify_groupUri($group, $forceAmpersand = false)
{
$path = $forceAmpersand
? "/g={$group}"
: "/?g={$group}";
return _Minify_getBuild($group)->uri(
'/' . basename(dirname(__FILE__)) . $path
,$forceAmpersand
);
}
/**
* Get the last modification time of the source js/css files used by Minify to
* build the page.
*
* If you're caching the output of Minify_groupUri(), you'll want to rebuild
* the cache if it's older than this timestamp.
*
* <code>
* // simplistic HTML cache system
* $file = '/path/to/cache/file';
* if (! file_exists($file) || filemtime($file) < Minify_groupsMtime(array('js', 'css'))) {
* // (re)build cache
* $page = buildPage(); // this calls Minify_groupUri() for js and css
* file_put_contents($file, $page);
* echo $page;
* exit();
* }
* readfile($file);
* </code>
*
* @param array $groups an array of keys from groupsConfig.php
* @return int Unix timestamp of the latest modification
*/
function Minify_groupsMtime($groups)
{
$max = 0;
foreach ((array)$groups as $group) {
$max = max($max, _Minify_getBuild($group)->lastModified);
}
return $max;
}
/**
* @param string $group a key from groupsConfig.php
* @return Minify_Build
* @private
*/
function _Minify_getBuild($group)
function Minify_getUri($keyOrFiles, $opts = array())
{
static $builds = array();
static $gc = false;
if (false === $gc) {
$gc = (require dirname(__FILE__) . '/groupsConfig.php');
}
if (! isset($builds[$group])) {
$builds[$group] = new Minify_Build($gc[$group]);
}
return $builds[$group];
return Minify_HTML_Helper::getUri($keyOrFiles, $opts);
}
/**
* Get the last modification time of several source js/css files. If you're
* caching the output of Minify_getUri(), you might want to know if one of the
* dependent source files has changed so you can update the HTML.
*
* Since this makes a bunch of stat() calls, you might not want to check this
* on every request.
*
* @param array $keysAndFiles group keys and/or file paths/URIs.
* @return int latest modification time of all given keys/files
*/
function Minify_mtime($keysAndFiles, $groupsConfigFile = null)
{
$gc = null;
if (! $groupsConfigFile) {
$groupsConfigFile = dirname(__FILE__) . '/groupsConfig.php';
}
$sources = array();
foreach ($keysAndFiles as $keyOrFile) {
if (is_object($keyOrFile)
|| 0 === strpos($keyOrFile, '/')
|| 1 === strpos($keyOrFile, ':\\')) {
// a file/source obj
$sources[] = $keyOrFile;
} else {
if (! $gc) {
$gc = (require $groupsConfigFile);
}
foreach ($gc[$keyOrFile] as $source) {
$sources[] = $source;
}
}
}
return Minify_HTML_Helper::getLastModified($sources);
}

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

@@ -24,7 +24,7 @@ if ($min_errorLogger && true !== $min_errorLogger) { // custom logger
error_reporting(E_ALL | E_STRICT);
ini_set('display_errors', 1);
header('Content-Type: text/plain');
header('Content-Type: text/plain;charset=utf-8');
$thisDir = dirname(__FILE__);
@@ -47,4 +47,17 @@ function assertTrue($test, $message)
return (bool)$test;
}
/**
* Get number of bytes in a string regardless of mbstring.func_overload
*
* @param string $str
* @return int
*/
function countBytes($str)
{
return (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2))
? mb_strlen($str, '8bit')
: strlen($str);
}
ob_start();

View File

@@ -1,3 +1,3 @@
/* YUI Compressor style comments are preserved */
/*! YUI Compressor style comments are preserved */
body{background:#fff url(/path/to/image.gif) repeat-y}

View File

@@ -0,0 +1 @@
.ui-widget { font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif; }

View File

@@ -0,0 +1 @@
.ui-widget{font-family:Trebuchet MS,Tahoma,Verdana,Arial,sans-serif}

View File

@@ -1,9 +1,11 @@
@charset "utf-8";
/* some CSS to try to exercise things in general */
@import url( /more.css );
body, td, th {
font-family: Verdana , "Bitstream Vera Sans" , sans-serif ;
font-family: Verdana , "Bitstream Vera Sans" , Arial Narrow, sans-serif ;
font-size : 12px;
}

View File

@@ -1,3 +1,3 @@
@import url(/more.css);body,td,th{font-family:Verdana,"Bitstream Vera Sans",sans-serif;font-size:12px}.nav{margin-left:20%}#main-nav{background-color:red;border:1px
@import url(/more.css);body,td,th{font-family:Verdana,"Bitstream Vera Sans",Arial Narrow,sans-serif;font-size:12px}.nav{margin-left:20%}#main-nav{background-color:red;border:1px
solid #0f7}div#content
h1+p{padding-top:0;margin-top:0}@media all and (min-width: 640px){#media-queries-1{background-color:#0f0}}@media screen and (max-width: 2000px){#media-queries-2{background-color:#0f0}}

View File

@@ -25,7 +25,7 @@ p{padding:0px;margin:0px;padding-bottom:5px}#post_nav{text-align:left;padding-bo
a{text-decoration:none;font-family:Verdana;font-size:12px;color:#108eed}#post_nav a:hover, .post_nav_2 p a:hover{text-decoration:underline;color:#FF8000}#rightcol
#about{padding-bottom:10px}#rightcol #r_news
input{color:#333;font-size:12px}#header{height:200px;width:100%;background-image:url('images/header_bg.jpg');background-repeat:x-repeat}#header
img{float:right;margin-right: -3px;z-index:100}.tags{text-transform:lowercase;color:#333;font-family:arial;font-size:12px;border-top:2px dotted #EEE;width:300px;padding-top:20px;padding-bottom:0px;margin-top:0px;padding-left:20px;padding-right:20px}.tags
img{float:right;margin-right:-3px;z-index:100}.tags{text-transform:lowercase;color:#333;font-family:arial;font-size:12px;border-top:2px dotted #EEE;width:300px;padding-top:20px;padding-bottom:0px;margin-top:0px;padding-left:20px;padding-right:20px}.tags
a{color:#108eed}.tags
p{text-align:left;margin:0px;padding:0px}blockquote
strong{font-family:verdana;display:block;margin-top:10px;color:#F00;font-style:italic;text-align:right}blockquote{margin:0px;background-color:#eee;border:2px

View File

@@ -0,0 +1,14 @@
@import "http://cnd.com/A/B/foo.css";
@import 'http://cnd.com/A/B/bar/foo.css' print;
@import 'http://cnd.com/A/bar/foo.css' print;
@import 'http://cnd.com/foo.css' print;
@import '/css/foo.css'; /* abs, should not alter */
@import 'http://foo.com/css/foo.css'; /* abs, should not alter */
@import url(http://cnd.com/A/foo.css) tv, projection;
@import url("/css/foo.css"); /* abs, should not alter */
@import url(/css2/foo.css); /* abs, should not alter */
@import url(data:image/gif;base64,AAAA); /* data, should not alter */
foo {background:url('http://cnd.com/A/B/bar/foo.png')}
foo {background:url('http://foo.com/css/foo.css');} /* abs, should not alter */
foo {background:url("//foo.com/css/foo.css");} /* protocol relative, should not alter */
foo {background:url(data:image/gif;base64,AAAA);} /* data, should not alter */

View File

@@ -0,0 +1,14 @@
@import "//cnd.com/A/B/foo.css";
@import '//cnd.com/A/B/bar/foo.css' print;
@import '//cnd.com/A/bar/foo.css' print;
@import '//cnd.com/foo.css' print;
@import '/css/foo.css'; /* abs, should not alter */
@import 'http://foo.com/css/foo.css'; /* abs, should not alter */
@import url(//cnd.com/A/foo.css) tv, projection;
@import url("/css/foo.css"); /* abs, should not alter */
@import url(/css2/foo.css); /* abs, should not alter */
@import url(data:image/gif;base64,AAAA); /* data, should not alter */
foo {background:url('//cnd.com/A/B/bar/foo.png')}
foo {background:url('http://foo.com/css/foo.css');} /* abs, should not alter */
foo {background:url("//foo.com/css/foo.css");} /* protocol relative, should not alter */
foo {background:url(data:image/gif;base64,AAAA);} /* data, should not alter */

View File

@@ -78,7 +78,7 @@ css hack {
<div id="container">
<div id="pageHeader">
<h1><span>css Zen Garden</span></h1>
<h2><span>The Beauty of <acronym title="Cascading Style Sheets">CSS</acronym>
<h2><span>The Beauty of <acronym title="Cascading Style Sheets">CSS</acronym>
Design</span></h2>
</div>
<pre>
@@ -90,7 +90,7 @@ Design</span></h2>
</div>
<textarea name="comment" id="comment" rows="6" class="maxwidth" cols="80">66666
1234567890</textarea>
1234567890<script> var Hello = 'world';</script></textarea>
</div>
</body>
</html>

View File

@@ -8,7 +8,7 @@ name="robots" content="all" /><title>css Zen Garden: The Beauty in CSS Design</t
is.mac=is.ua.indexOf('mac')>=0;if(is.ua.indexOf('opera')>=0){is.ie=is.ns=false;is.opera=true;}
if(is.ua.indexOf('gecko')>=0){is.ie=is.ns=false;is.gecko=true;}</script> <script type="text/javascript">/*<![CDATA[*/var i=0;while(++i<10)
{}/*]]>*/</script> <script type="text/javascript">i=1;</script> <script type="text/javascript">/*<![CDATA[*/(i<1);/*]]>*/</script> <!--[if IE 6]><style type="text/css">/*<![CDATA[*/
/* copyright: you'll need CDATA for this < & */
/*! copyright: you'll need CDATA for this < & */
body{background:white}/*]]>*/</style><![endif]--><style type="text/css" title="currentStyle" media="screen">@import "/001/001.css";/*\*/css
hack{}/**/ /*/*/css
hack{}/**/css
@@ -20,10 +20,12 @@ rel="alternate"
type="application/rss+xml"
title="RSS"
href="http://www.csszengarden.com/zengarden.xml" /></head><body
id="css-zen-garden"> <!--[if !IE]>--><p>Browser != IE</p><!--<![endif]--><div
id="css-zen-garden">
<!--[if !IE]>--><p>Browser != IE</p><!--<![endif]--><div
id="container"><div
id="pageHeader"><h1><span>css Zen Garden</span></h1><h2><span>The Beauty of <acronym
title="Cascading Style Sheets">CSS</acronym> Design</span></h2></div><pre>
title="Cascading Style Sheets">CSS</acronym>
Design</span></h2></div><pre>
White space is important here!
</pre><div
id="quickSummary"><p
@@ -33,4 +35,4 @@ class="p2"><span>Download the sample <a
href="/zengarden-sample.html" title="This page's source HTML code, not to be modified.">html file</a> and <a
href="/zengarden-sample.css" title="This page's sample CSS, the file you may modify.">css file</a></span></p></div><textarea name="comment" id="comment" rows="6" class="maxwidth" cols="80">66666
1234567890</textarea></div></body></html>
1234567890<script>var Hello='world';</script></textarea></div></body></html>

View File

@@ -8,7 +8,7 @@ name="robots" content="all"><title>css Zen Garden: The Beauty in CSS Design</tit
is.mac=is.ua.indexOf('mac')>=0;if(is.ua.indexOf('opera')>=0){is.ie=is.ns=false;is.opera=true;}
if(is.ua.indexOf('gecko')>=0){is.ie=is.ns=false;is.gecko=true;}</script> <script type="text/javascript">var i=0;while(++i<10)
{}</script> <script type="text/javascript">i=1;</script> <script type="text/javascript">(i<1);</script> <!--[if IE 6]><style type="text/css">
/* copyright: you'll need CDATA for this < & */
/*! copyright: you'll need CDATA for this < & */
body{background:white}</style><![endif]--><style type="text/css" title="currentStyle" media="screen">@import "/001/001.css";/*\*/css
hack{}/**/ /*/*/css
hack{}/**/css
@@ -20,10 +20,12 @@ rel="alternate"
type="application/rss+xml"
title="RSS"
href="http://www.csszengarden.com/zengarden.xml"></head><body
id="css-zen-garden"> <!--[if !IE]>--><p>Browser != IE</p><!--<![endif]--><div
id="css-zen-garden">
<!--[if !IE]>--><p>Browser != IE</p><!--<![endif]--><div
id="container"><div
id="pageHeader"><h1><span>css Zen Garden</span></h1><h2><span>The Beauty of <acronym
title="Cascading Style Sheets">CSS</acronym> Design</span></h2></div><pre>
title="Cascading Style Sheets">CSS</acronym>
Design</span></h2></div><pre>
White space is important here!
</pre><div
id="quickSummary"><p

View File

@@ -0,0 +1,8 @@
<?php
return array(
'css' => array(
'//_test_files/css/paths_prepend.css'
,'//_test_files/css/styles.css'
)
);

View File

@@ -1,10 +1,12 @@
@media screen {
@charset "utf-8";
/* some CSS to try to exercise things in general */
@import url(/more.css);
body, td, th {
font-family: Verdana , "Bitstream Vera Sans" , sans-serif ;
font-family: Verdana , "Bitstream Vera Sans" , Arial Narrow, sans-serif ;
font-size : 12px;
}

View File

@@ -1,10 +1,10 @@
/* is.js
/*! is.js
(c) 2001 Douglas Crockford
2001 June 3
*/
var is={ie:navigator.appName=='Microsoft Internet Explorer',java:navigator.javaEnabled(),ns:navigator.appName=='Netscape',ua:navigator.userAgent.toLowerCase(),version:parseFloat(navigator.appVersion.substr(21))||parseFloat(navigator.appVersion),win:navigator.platform=='Win32'}
/**
/*!*
* preserve this comment, too
*/
is.mac=is.ua.indexOf('mac')>=0;if(is.ua.indexOf('opera')>=0){is.ie=is.ns=false;is.opera=true;}

View File

@@ -0,0 +1,7 @@
// from jQuery tablesorter
ts.addParser({
id: "currency",
is: function(s) {
return /^[£$€?.]/.test(s);
},
});

View File

@@ -0,0 +1 @@
ts.addParser({id:"currency",is:function(s){return /^[£$€?.]/.test(s);},});

View File

@@ -0,0 +1,3 @@
// The MinApp controller should cause this file to not be minified
// since the basename of the filepath matches the default noMinPattern

View File

@@ -0,0 +1,2 @@
// JSMin should not alter this file
if(!a.id)a.id="dp"+ ++this.uuid;

View File

@@ -0,0 +1,2 @@
// JSMin should not alter this file
if(!a.id)a.id="dp"+ ++this.uuid;

View File

@@ -1,4 +1,4 @@
var MrClay = window.MrClay || {};
var MrClay = window.MrClay || {};
/**
* Simplified access to/manipulation of the query string

View File

@@ -1,4 +1,4 @@
// http://mrclay.org/
// http://mrclay.org/
(function(){
var
reMailto = /^mailto:my_name_is_(\S+)_and_the_domain_is_(\S+)$/,

View File

@@ -0,0 +1,6 @@
/*
* This file is to intentionally throw a JSMin exception
*/
function HelloWorld() {
return /regexp;
}

View File

@@ -1,2 +1,10 @@
var triggerBug = {_default: "*/*"};
var essentialFunctionality = true;
// sections from Prototype 1.6.1
var xpath = ".//*[local-name()='ul' or local-name()='UL']" +
"//*[local-name()='li' or local-name()='LI']";
this.matcher = ['.//*'];
xpath = {
descendant: "//*",
child: "/*",
f: 0
};
document._getElementsByXPath('.//*' + cond, element);

View File

@@ -29,9 +29,16 @@
;
/* lines_bugs.js */
/* 1 */ var triggerBug = {_default: "*/*"};
/* 2 */ var essentialFunctionality = true;
/* 3 */
/* 1 */ // sections from Prototype 1.6.1
/* 2 */ var xpath = ".//"+"*[local-name()='ul' or local-name()='UL']" +
/* 3 */ "//"+"*[local-name()='li' or local-name()='LI']";
/* 4 */ this.matcher = ['.//'+'*'];
/* 5 */ xpath = {
/* 6 */ descendant: "//"+"*",
/* 7 */ child: "/"+"*",
/* 8 */ f: 0
/* 9 */ };
/* 10 */ document._getElementsByXPath('.//'+'*' + cond, element);
;
/* QueryString.js */

View File

@@ -1,5 +1,5 @@
@import url(/more.css);body,td,th{font-family:Verdana,"Bitstream Vera Sans",sans-serif;font-size:12px}.nav{margin-left:20%}#main-nav{background-color:red;border:1px
@import url(/more.css);body,td,th{font-family:Verdana,"Bitstream Vera Sans",Arial Narrow,sans-serif;font-size:12px}.nav{margin-left:20%}#main-nav{background-color:red;border:1px
solid #0f7}div#content
h1+p{padding-top:0;margin-top:0}@media all and (min-width: 640px){#media-queries-1{background-color:#0f0}}@media screen and (max-width: 2000px){#media-queries-2{background-color:#0f0}}
/* YUI Compressor style comments are preserved */
/*! YUI Compressor style comments are preserved */
body{background:#fff url(/path/to/image.gif) repeat-y}

View File

@@ -1,5 +1,6 @@
(function(){var
reMailto=/^mailto:my_name_is_(\S+)_and_the_domain_is_(\S+)$/,reRemoveTitleIf=/^my name is/,oo=window.onload,fixHrefs=function(){var i=0,l,m;while(l=document.links[i++]){if(m=l.href.match(reMailto)){l.href='mailto:'+m[1]+'@'+m[2];if(reRemoveTitleIf.test(l.title)){l.title='';}}}};window.onload=function(){oo&&oo();fixHrefs();};})();;var MrClay=window.MrClay||{};MrClay.QueryString=function(){var parse=function(str){var assignments=str.split('&'),obj={},propValue;for(var i=0,l=assignments.length;i<l;++i){propValue=assignments[i].split('=');if(propValue.length>2||-1!=propValue[0].indexOf('+')||propValue[0]==''){continue;}
reMailto=/^mailto:my_name_is_(\S+)_and_the_domain_is_(\S+)$/,reRemoveTitleIf=/^my name is/,oo=window.onload,fixHrefs=function(){var i=0,l,m;while(l=document.links[i++]){if(m=l.href.match(reMailto)){l.href='mailto:'+m[1]+'@'+m[2];if(reRemoveTitleIf.test(l.title)){l.title='';}}}};window.onload=function(){oo&&oo();fixHrefs();};})();
;var MrClay=window.MrClay||{};MrClay.QueryString=function(){var parse=function(str){var assignments=str.split('&'),obj={},propValue;for(var i=0,l=assignments.length;i<l;++i){propValue=assignments[i].split('=');if(propValue.length>2||-1!=propValue[0].indexOf('+')||propValue[0]==''){continue;}
if(propValue.length==1){propValue[1]=propValue[0];}
obj[unescape(propValue[0])]=unescape(propValue[1].replace(/\+/g,' '));}
return obj;};function construct_(spec){spec=spec||window;if(typeof spec=='object'){this.window=spec;spec=spec.location.search.substr(1);}else{this.window=window;}

View File

@@ -0,0 +1,6 @@
To add a test:
1. Create a "blah.css" or "blah.js" file.
2. Create a "blah.css.min" or "blah.js.min" file, containing the expected minified output.
That's all!

View File

@@ -0,0 +1,2 @@
a {background-position: 0 0 0 0;}
b {BACKGROUND-POSITION: 0 0;}

View File

@@ -0,0 +1 @@
a{background-position:0 0}b{background-position:0 0}

View File

@@ -0,0 +1,5 @@
a {
border: none;
}
b {BACKGROUND:none}
s {border-top: none;}

View File

@@ -0,0 +1 @@
a{border:0}b{background:0}s{border-top:0}

View File

@@ -0,0 +1,9 @@
#elem {
width: 100px;
voice-family: "\"}\"";
voice-family:inherit;
width: 200px;
}
html>body #elem {
width: 200px;
}

View File

@@ -0,0 +1 @@
#elem{width:100px;voice-family:"\"}\"";voice-family:inherit;width:200px}html>body #elem{width:200px}

View File

@@ -0,0 +1,10 @@
/* this file contains no css, it exists purely to put the revision number into the
combined css before uploading it to SiteManager. The exclaimation at the start
of the comment informs yuicompressor not to strip the comment out */
/*! $LastChangedRevision: 81 $ $LastChangedDate: 2009-05-27 17:41:02 +0100 (Wed, 27 May 2009) $ */
body {
yo: cats;
}
ul[id$=foo] label:hover {yo: yo;}

View File

@@ -0,0 +1 @@
/*! $LastChangedRevision: 81 $ $LastChangedDate: 2009-05-27 17:41:02 +0100 (Wed, 27 May 2009) $ */body{yo:cats}ul[id$=foo] label:hover{yo:yo}

View File

@@ -0,0 +1,19 @@
@media screen and/*!YUI-Compresser */(-webkit-min-device-pixel-ratio:0) {
a{
b: 1;
}
}
@media screen and/*! */ /*! */(-webkit-min-device-pixel-ratio:0) {
a{
b: 1;
}
}
@media -webkit-min-device-pixel-ratio:0 {
a{
b: 1;
}
}

View File

@@ -0,0 +1 @@
@media screen and/*!YUI-Compresser */(-webkit-min-device-pixel-ratio:0){a{b:1}}@media screen and/*! *//*! */(-webkit-min-device-pixel-ratio:0){a{b:1}}@media -webkit-min-device-pixel-ratio:0{a{b:1}}

View File

@@ -0,0 +1,4 @@
/*! special */
body {
}

View File

@@ -0,0 +1 @@
/*! special */

View File

@@ -0,0 +1,5 @@
a[href$="/test/"] span:first-child { b:1; }
a[href$="/test/"] span:first-child { }

View File

@@ -0,0 +1 @@
a[href$="/test/"] span:first-child{b:1}

View File

@@ -0,0 +1,9 @@
/* re: 2495387 */
@charset 'utf-8';
@media all {
body {
}
body {
background-color: gold;
}
}

View File

@@ -0,0 +1 @@
@charset 'utf-8';@media all{body{background-color:gold}}

View File

@@ -0,0 +1,7 @@
.color {
me: rgb(123, 123, 123);
impressed: #ffeedd;
filter: chroma(color="#FFFFFF");
background: none repeat scroll 0 0 rgb(255, 0,0);
alpha: rgba(1, 2, 3, 4);
}

View File

@@ -0,0 +1 @@
.color{me:#7b7b7b;impressed:#fed;filter:chroma(color="#FFFFFF");background:none repeat scroll 0 0 #f00;alpha:rgba(1,2,3,4)}

View File

@@ -0,0 +1,3 @@
html >/**/ body p {
color: blue;
}

View File

@@ -0,0 +1 @@
html>/**/body p{color:blue}

View File

@@ -0,0 +1,15 @@
/* This is invalid CSS, but frequently happens as a result of concatenation. */
@charset "utf-8";
#foo {
border-width:1px;
}
/*
Note that this is erroneous!
The actual CSS file can only have a single charset.
However, this is the job of the author/application.
The compressor should not get involved.
*/
@charset "another one";
#bar {
border-width:10px;
}

View File

@@ -0,0 +1 @@
@charset "utf-8";#foo{border-width:1px}#bar{border-width:10px}

View File

@@ -0,0 +1,3 @@
::selection {
margin: 0.6px 0.333pt 1.2em 8.8cm;
}

View File

@@ -0,0 +1 @@
::selection{margin:.6px .333pt 1.2em 8.8cm}

View File

@@ -0,0 +1,7 @@
/*!
$Header: /temp/dirname/filename.css 3 2/02/08 3:37p JSmith $
*/
foo {
bar: baz
}

View File

@@ -0,0 +1,3 @@
/*!
$Header: /temp/dirname/filename.css 3 2/02/08 3:37p JSmith $
*/foo{bar:baz}

View File

@@ -0,0 +1,6 @@
@font-face {
font-family: 'gzipper';
src: url(yanone.eot);
src: local('gzipper'),
url(yanone.ttf) format('truetype');
}

View File

@@ -0,0 +1 @@
@font-face{font-family:'gzipper';src:url(yanone.eot);src:local('gzipper'),url(yanone.ttf) format('truetype')}

View File

@@ -0,0 +1,5 @@
/* Ignore the next rule in IE mac \*/
.selector {
color: khaki;
}
/* Stop ignoring in IE mac */

View File

@@ -0,0 +1 @@
/*\*/.selector{color:khaki}/**/

View File

@@ -0,0 +1,16 @@
/*! preserved */
emptiness {}
@import "another.css";
/* I'm empty - delete me */
empty { ;}
@media print {
.noprint { display: none; }
}
@media screen {
/* this rule should be removed, not simply minified.*/
.breakme {}
.printonly { display: none; }
}

View File

@@ -0,0 +1 @@
/*! preserved */@import "another.css";@media print{.noprint{display:none}}@media screen{.printonly{display:none}}

Some files were not shown because too many files have changed in this diff Show More