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:
16
HISTORY.txt
16
HISTORY.txt
@@ -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)
|
||||
|
18
README.txt
18
README.txt
@@ -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
|
||||
|
@@ -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>
|
||||
|
@@ -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
|
||||
|
@@ -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
15
min/builder/bm2.js
Normal 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;
|
||||
})();
|
@@ -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>&debug</code> to a Minify URL, or
|
||||
by sending the cookie <code>minDebug=<match></code>, where <code><match></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
43
min/builder/test.php
Normal 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>";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//*/
|
@@ -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';
|
||||
|
||||
|
||||
/**
|
||||
|
@@ -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')
|
||||
))
|
||||
),//*/
|
||||
);
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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);
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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.
|
||||
|
@@ -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
|
||||
|
@@ -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(
|
||||
|
@@ -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];
|
||||
}
|
||||
}
|
||||
|
@@ -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})";
|
||||
}
|
||||
}
|
||||
|
@@ -54,9 +54,12 @@ class Minify_Cache_APC {
|
||||
*/
|
||||
public function getSize($id)
|
||||
{
|
||||
return $this->_fetch($id)
|
||||
? strlen($this->_data)
|
||||
: false;
|
||||
if (! $this->_fetch($id)) {
|
||||
return false;
|
||||
}
|
||||
return (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2))
|
||||
? mb_strlen($this->_data, '8bit')
|
||||
: strlen($this->_data);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -9,13 +9,12 @@ class Minify_Cache_File {
|
||||
public function __construct($path = '', $fileLocking = false)
|
||||
{
|
||||
if (! $path) {
|
||||
require_once 'Solar/Dir.php';
|
||||
$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;
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
142
min/lib/Minify/Cache/ZendPlatform.php
Normal file
142
min/lib/Minify/Cache/ZendPlatform.php
Normal 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;
|
||||
}
|
||||
}
|
@@ -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)
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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(
|
||||
|
@@ -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');
|
||||
|
@@ -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));
|
||||
}
|
||||
}
|
||||
|
193
min/lib/Minify/HTML/Helper.php
Normal file
193
min/lib/Minify/HTML/Helper.php
Normal 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;
|
||||
}
|
||||
}
|
116
min/lib/Minify/JS/ClosureCompiler.php
Normal file
116
min/lib/Minify/JS/ClosureCompiler.php
Normal 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 {}
|
@@ -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)
|
||||
|
@@ -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 {
|
||||
|
||||
|
291
min/lib/Minify/YUI/CssCompressor.java
Normal file
291
min/lib/Minify/YUI/CssCompressor.java
Normal 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);
|
||||
}
|
||||
}
|
171
min/lib/Minify/YUI/CssCompressor.php
Normal file
171
min/lib/Minify/YUI/CssCompressor.php
Normal 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];
|
||||
}
|
||||
}
|
||||
}
|
81
min/lib/Minify/YUI/Java/Matcher.php
Normal file
81
min/lib/Minify/YUI/Java/Matcher.php
Normal 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));
|
||||
}
|
||||
}
|
64
min/lib/Minify/YUI/Java/String.php
Normal file
64
min/lib/Minify/YUI/Java/String.php
Normal 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;
|
||||
}
|
||||
}
|
@@ -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.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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';
|
||||
}
|
||||
}
|
132
min/utils.php
132
min/utils.php
@@ -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
4
min_unit_tests/.htaccess
Normal file
@@ -0,0 +1,4 @@
|
||||
<IfModule mod_env.c>
|
||||
# In case AddOutputFilterByType has been added
|
||||
SetEnv no-gzip
|
||||
</IfModule>
|
@@ -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();
|
@@ -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}
|
1
min_unit_tests/_test_files/css/issue210.css
Normal file
1
min_unit_tests/_test_files/css/issue210.css
Normal file
@@ -0,0 +1 @@
|
||||
.ui-widget { font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif; }
|
1
min_unit_tests/_test_files/css/issue210.min.css
vendored
Normal file
1
min_unit_tests/_test_files/css/issue210.min.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.ui-widget{font-family:Trebuchet MS,Tahoma,Verdana,Arial,sans-serif}
|
@@ -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;
|
||||
}
|
||||
|
@@ -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}}
|
@@ -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
|
||||
|
14
min_unit_tests/_test_files/css_uriRewriter/exp_prepend.css
Normal file
14
min_unit_tests/_test_files/css_uriRewriter/exp_prepend.css
Normal 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 */
|
14
min_unit_tests/_test_files/css_uriRewriter/exp_prepend2.css
Normal file
14
min_unit_tests/_test_files/css_uriRewriter/exp_prepend2.css
Normal 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 */
|
@@ -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>
|
@@ -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>
|
@@ -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
|
||||
|
8
min_unit_tests/_test_files/htmlHelper_groupsConfig.php
Normal file
8
min_unit_tests/_test_files/htmlHelper_groupsConfig.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
return array(
|
||||
'css' => array(
|
||||
'//_test_files/css/paths_prepend.css'
|
||||
,'//_test_files/css/styles.css'
|
||||
)
|
||||
);
|
@@ -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;
|
||||
}
|
||||
|
4
min_unit_tests/_test_files/js/before.min.js
vendored
4
min_unit_tests/_test_files/js/before.min.js
vendored
@@ -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;}
|
||||
|
7
min_unit_tests/_test_files/js/issue132.js
Normal file
7
min_unit_tests/_test_files/js/issue132.js
Normal file
@@ -0,0 +1,7 @@
|
||||
// from jQuery tablesorter
|
||||
ts.addParser({
|
||||
id: "currency",
|
||||
is: function(s) {
|
||||
return /^[£$€?.]/.test(s);
|
||||
},
|
||||
});
|
1
min_unit_tests/_test_files/js/issue132.min.js
vendored
Normal file
1
min_unit_tests/_test_files/js/issue132.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
ts.addParser({id:"currency",is:function(s){return /^[£$€?.]/.test(s);},});
|
3
min_unit_tests/_test_files/js/issue141.min.js
vendored
Normal file
3
min_unit_tests/_test_files/js/issue141.min.js
vendored
Normal 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
|
||||
|
2
min_unit_tests/_test_files/js/issue144.js
Normal file
2
min_unit_tests/_test_files/js/issue144.js
Normal file
@@ -0,0 +1,2 @@
|
||||
// JSMin should not alter this file
|
||||
if(!a.id)a.id="dp"+ ++this.uuid;
|
2
min_unit_tests/_test_files/js/issue144.min.js
vendored
Normal file
2
min_unit_tests/_test_files/js/issue144.min.js
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
// JSMin should not alter this file
|
||||
if(!a.id)a.id="dp"+ ++this.uuid;
|
@@ -1,4 +1,4 @@
|
||||
var MrClay = window.MrClay || {};
|
||||
var MrClay = window.MrClay || {};
|
||||
|
||||
/**
|
||||
* Simplified access to/manipulation of the query string
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// http://mrclay.org/
|
||||
// http://mrclay.org/
|
||||
(function(){
|
||||
var
|
||||
reMailto = /^mailto:my_name_is_(\S+)_and_the_domain_is_(\S+)$/,
|
||||
|
6
min_unit_tests/_test_files/minify/issue143.js
Normal file
6
min_unit_tests/_test_files/minify/issue143.js
Normal file
@@ -0,0 +1,6 @@
|
||||
/*
|
||||
* This file is to intentionally throw a JSMin exception
|
||||
*/
|
||||
function HelloWorld() {
|
||||
return /regexp;
|
||||
}
|
@@ -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);
|
@@ -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 */
|
||||
|
@@ -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}
|
@@ -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;}
|
||||
|
6
min_unit_tests/_test_files/yuic/README
Normal file
6
min_unit_tests/_test_files/yuic/README
Normal 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!
|
2
min_unit_tests/_test_files/yuic/background-position.css
Normal file
2
min_unit_tests/_test_files/yuic/background-position.css
Normal file
@@ -0,0 +1,2 @@
|
||||
a {background-position: 0 0 0 0;}
|
||||
b {BACKGROUND-POSITION: 0 0;}
|
@@ -0,0 +1 @@
|
||||
a{background-position:0 0}b{background-position:0 0}
|
5
min_unit_tests/_test_files/yuic/border-none.css
Normal file
5
min_unit_tests/_test_files/yuic/border-none.css
Normal file
@@ -0,0 +1,5 @@
|
||||
a {
|
||||
border: none;
|
||||
}
|
||||
b {BACKGROUND:none}
|
||||
s {border-top: none;}
|
1
min_unit_tests/_test_files/yuic/border-none.css.min
Normal file
1
min_unit_tests/_test_files/yuic/border-none.css.min
Normal file
@@ -0,0 +1 @@
|
||||
a{border:0}b{background:0}s{border-top:0}
|
9
min_unit_tests/_test_files/yuic/box-model-hack.css
Normal file
9
min_unit_tests/_test_files/yuic/box-model-hack.css
Normal file
@@ -0,0 +1,9 @@
|
||||
#elem {
|
||||
width: 100px;
|
||||
voice-family: "\"}\"";
|
||||
voice-family:inherit;
|
||||
width: 200px;
|
||||
}
|
||||
html>body #elem {
|
||||
width: 200px;
|
||||
}
|
1
min_unit_tests/_test_files/yuic/box-model-hack.css.min
Normal file
1
min_unit_tests/_test_files/yuic/box-model-hack.css.min
Normal file
@@ -0,0 +1 @@
|
||||
#elem{width:100px;voice-family:"\"}\"";voice-family:inherit;width:200px}html>body #elem{width:200px}
|
10
min_unit_tests/_test_files/yuic/bug2527974.css
Normal file
10
min_unit_tests/_test_files/yuic/bug2527974.css
Normal 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;}
|
1
min_unit_tests/_test_files/yuic/bug2527974.css.min
Normal file
1
min_unit_tests/_test_files/yuic/bug2527974.css.min
Normal 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}
|
19
min_unit_tests/_test_files/yuic/bug2527991.css
Normal file
19
min_unit_tests/_test_files/yuic/bug2527991.css
Normal 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;
|
||||
}
|
||||
}
|
1
min_unit_tests/_test_files/yuic/bug2527991.css.min
Normal file
1
min_unit_tests/_test_files/yuic/bug2527991.css.min
Normal 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}}
|
4
min_unit_tests/_test_files/yuic/bug2527998.css
Normal file
4
min_unit_tests/_test_files/yuic/bug2527998.css
Normal file
@@ -0,0 +1,4 @@
|
||||
/*! special */
|
||||
body {
|
||||
|
||||
}
|
1
min_unit_tests/_test_files/yuic/bug2527998.css.min
Normal file
1
min_unit_tests/_test_files/yuic/bug2527998.css.min
Normal file
@@ -0,0 +1 @@
|
||||
/*! special */
|
5
min_unit_tests/_test_files/yuic/bug2528034.css
Normal file
5
min_unit_tests/_test_files/yuic/bug2528034.css
Normal file
@@ -0,0 +1,5 @@
|
||||
a[href$="/test/"] span:first-child { b:1; }
|
||||
a[href$="/test/"] span:first-child { }
|
||||
|
||||
|
||||
|
1
min_unit_tests/_test_files/yuic/bug2528034.css.min
Normal file
1
min_unit_tests/_test_files/yuic/bug2528034.css.min
Normal file
@@ -0,0 +1 @@
|
||||
a[href$="/test/"] span:first-child{b:1}
|
9
min_unit_tests/_test_files/yuic/charset-media.css
Normal file
9
min_unit_tests/_test_files/yuic/charset-media.css
Normal file
@@ -0,0 +1,9 @@
|
||||
/* re: 2495387 */
|
||||
@charset 'utf-8';
|
||||
@media all {
|
||||
body {
|
||||
}
|
||||
body {
|
||||
background-color: gold;
|
||||
}
|
||||
}
|
1
min_unit_tests/_test_files/yuic/charset-media.css.min
Normal file
1
min_unit_tests/_test_files/yuic/charset-media.css.min
Normal file
@@ -0,0 +1 @@
|
||||
@charset 'utf-8';@media all{body{background-color:gold}}
|
7
min_unit_tests/_test_files/yuic/color.css
Normal file
7
min_unit_tests/_test_files/yuic/color.css
Normal 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);
|
||||
}
|
1
min_unit_tests/_test_files/yuic/color.css.min
Normal file
1
min_unit_tests/_test_files/yuic/color.css.min
Normal 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)}
|
3
min_unit_tests/_test_files/yuic/comment.css
Normal file
3
min_unit_tests/_test_files/yuic/comment.css
Normal file
@@ -0,0 +1,3 @@
|
||||
html >/**/ body p {
|
||||
color: blue;
|
||||
}
|
1
min_unit_tests/_test_files/yuic/comment.css.min
Normal file
1
min_unit_tests/_test_files/yuic/comment.css.min
Normal file
@@ -0,0 +1 @@
|
||||
html>/**/body p{color:blue}
|
15
min_unit_tests/_test_files/yuic/concat-charset.css
Normal file
15
min_unit_tests/_test_files/yuic/concat-charset.css
Normal 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;
|
||||
}
|
1
min_unit_tests/_test_files/yuic/concat-charset.css.min
Normal file
1
min_unit_tests/_test_files/yuic/concat-charset.css.min
Normal file
@@ -0,0 +1 @@
|
||||
@charset "utf-8";#foo{border-width:1px}#bar{border-width:10px}
|
3
min_unit_tests/_test_files/yuic/decimals.css
Normal file
3
min_unit_tests/_test_files/yuic/decimals.css
Normal file
@@ -0,0 +1,3 @@
|
||||
::selection {
|
||||
margin: 0.6px 0.333pt 1.2em 8.8cm;
|
||||
}
|
1
min_unit_tests/_test_files/yuic/decimals.css.min
Normal file
1
min_unit_tests/_test_files/yuic/decimals.css.min
Normal file
@@ -0,0 +1 @@
|
||||
::selection{margin:.6px .333pt 1.2em 8.8cm}
|
7
min_unit_tests/_test_files/yuic/dollar-header.css
Normal file
7
min_unit_tests/_test_files/yuic/dollar-header.css
Normal file
@@ -0,0 +1,7 @@
|
||||
/*!
|
||||
$Header: /temp/dirname/filename.css 3 2/02/08 3:37p JSmith $
|
||||
*/
|
||||
|
||||
foo {
|
||||
bar: baz
|
||||
}
|
3
min_unit_tests/_test_files/yuic/dollar-header.css.min
Normal file
3
min_unit_tests/_test_files/yuic/dollar-header.css.min
Normal file
@@ -0,0 +1,3 @@
|
||||
/*!
|
||||
$Header: /temp/dirname/filename.css 3 2/02/08 3:37p JSmith $
|
||||
*/foo{bar:baz}
|
6
min_unit_tests/_test_files/yuic/font-face.css
Normal file
6
min_unit_tests/_test_files/yuic/font-face.css
Normal file
@@ -0,0 +1,6 @@
|
||||
@font-face {
|
||||
font-family: 'gzipper';
|
||||
src: url(yanone.eot);
|
||||
src: local('gzipper'),
|
||||
url(yanone.ttf) format('truetype');
|
||||
}
|
1
min_unit_tests/_test_files/yuic/font-face.css.min
Normal file
1
min_unit_tests/_test_files/yuic/font-face.css.min
Normal file
@@ -0,0 +1 @@
|
||||
@font-face{font-family:'gzipper';src:url(yanone.eot);src:local('gzipper'),url(yanone.ttf) format('truetype')}
|
5
min_unit_tests/_test_files/yuic/ie5mac.css
Normal file
5
min_unit_tests/_test_files/yuic/ie5mac.css
Normal file
@@ -0,0 +1,5 @@
|
||||
/* Ignore the next rule in IE mac \*/
|
||||
.selector {
|
||||
color: khaki;
|
||||
}
|
||||
/* Stop ignoring in IE mac */
|
1
min_unit_tests/_test_files/yuic/ie5mac.css.min
Normal file
1
min_unit_tests/_test_files/yuic/ie5mac.css.min
Normal file
@@ -0,0 +1 @@
|
||||
/*\*/.selector{color:khaki}/**/
|
16
min_unit_tests/_test_files/yuic/media-empty-class.css
Normal file
16
min_unit_tests/_test_files/yuic/media-empty-class.css
Normal 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; }
|
||||
}
|
@@ -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
Reference in New Issue
Block a user