MDL-32050 import Minify 2.1.5

This commit is contained in:
Petr Skoda
2012-03-16 20:03:03 +01:00
parent a2b30aa852
commit 37d729777c
44 changed files with 3711 additions and 2158 deletions
-132
View File
@@ -1,132 +0,0 @@
The files in this directory represent the default Minify setup designed to ease
integration with your site. This app will combine and minify your Javascript or
CSS files and serve them with HTTP compression and cache headers.
RECOMMENDED
It's recommended to edit config.php to set $min_cachePath to a writeable
(by PHP) directory on your system. This will improve performance.
GETTING STARTED
The quickest way to get started is to use the Minify URI Builder application
on your website: http://example.com/min/builder/
MINIFYING A SINGLE FILE
Let's say you want to serve this file:
http://example.com/wp-content/themes/default/default.css
Here's the "Minify URL" for this file:
http://example.com/min/?f=wp-content/themes/default/default.css
In other words, the "f" argument is set to the file path from root without the
initial "/". As CSS files may contain relative URIs, Minify will automatically
"fix" these by rewriting them as root relative.
COMBINING MULTIPLE FILES IN ONE DOWNLOAD
Separate the paths given to "f" with commas.
Let's say you have CSS files at these URLs:
http://example.com/scripts/jquery-1.2.6.js
http://example.com/scripts/site.js
You can combine these files through Minify by requesting this URL:
http://example.com/min/?f=scripts/jquery-1.2.6.js,scripts/site.js
SIMPLIFYING URLS WITH A BASE PATH
If you're combining files that share the same ancestor directory, you can use
the "b" argument to set the base directory for the "f" argument. Do not include
the leading or trailing "/" characters.
E.g., the following URLs will serve the exact same content:
http://example.com/min/?f=scripts/jquery-1.2.6.js,scripts/site.js,scripts/home.js
http://example.com/min/?b=scripts&f=jquery-1.2.6.js,site.js,home.js
MINIFY URLS IN HTML
In (X)HTML files, don't forget to replace any "&" characters with "&".
SPECIFYING ALLOWED DIRECTORIES
By default, Minify will serve any *.css/*.js files within the DOCUMENT_ROOT. If
you'd prefer to limit Minify's access to certain directories, set the
$min_serveOptions['minApp']['allowDirs'] array in config.php. E.g. to limit
to the /js and /themes/default directories, use:
$min_serveOptions['minApp']['allowDirs'] = array('//js', '//themes/default');
GROUPS: FASTER PERFORMANCE AND BETTER URLS
For the best performance, 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')
);
This pre-selects the following files to be combined under the key "js":
http://example.com/js/Class.js
http://example.com/js/email.js
You can now serve these files with this simple URL:
http://example.com/min/?g=js
GROUPS: SPECIFYING FILES OUTSIDE THE DOC_ROOT
In the groupsConfig.php array, the "//" in the file paths is a shortcut for
the DOCUMENT_ROOT, but you can also specify paths from the root of the filesystem
or relative to the DOC_ROOT:
return array(
'js' => array(
'//js/file.js' // file within DOC_ROOT
,'//../file.js' // file in parent directory of DOC_ROOT
,'C:/Users/Steve/file.js' // file anywhere on filesystem
)
);
FAR-FUTURE EXPIRES HEADERS
Minify can send far-future (one year) Expires headers. To enable this you must
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.:
<?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>";
DEBUG MODE
In debug mode, instead of compressing files, Minify sends combined files with
comments prepended to each line to show the line number in the original source
file. To enable this, set $min_allowDebugFlag to true in config.php and append
"&debug=1" to your URIs. E.g. /min/?f=script1.js,script2.js&debug=1
Known issue: files with comment-like strings/regexps can cause problems in this mode.
QUESTIONS?
http://groups.google.com/group/minify
-242
View File
@@ -1,242 +0,0 @@
var MUB = {
_uid : 0
,_minRoot : '/min/?'
,checkRewrite : function () {
var testUri = location.pathname.replace(/\/[^\/]*$/, '/rewriteTest.js').substr(1);
function fail() {
$('#minRewriteFailed')[0].className = 'topNote';
};
$.ajax({
url : '../f=' + testUri + '&' + (new Date()).getTime()
,success : function (data) {
if (data === '1') {
MUB._minRoot = '/min/';
$('span.minRoot').html('/min/');
} else
fail();
}
,error : fail
});
}
/**
* Get markup for new source LI element
*/
,newLi : function () {
return '<li id="li' + MUB._uid + '">http://' + location.host + '/<input type=text size=20>'
+ ' <button title="Remove">x</button> <button title="Include Earlier">&uarr;</button>'
+ ' <button title="Include Later">&darr;</button> <span></span></li>';
}
/**
* Add new empty source LI and attach handlers to buttons
*/
,addLi : function () {
$('#sources').append(MUB.newLi());
var li = $('#li' + MUB._uid)[0];
$('button[title=Remove]', li).click(function () {
$('#results').hide();
var hadValue = !!$('input', li)[0].value;
$(li).remove();
});
$('button[title$=Earlier]', li).click(function () {
$(li).prev('li').find('input').each(function () {
$('#results').hide();
// this = previous li input
var tmp = this.value;
this.value = $('input', li).val();
$('input', li).val(tmp);
MUB.updateAllTestLinks();
});
});
$('button[title$=Later]', li).click(function () {
$(li).next('li').find('input').each(function () {
$('#results').hide();
// this = next li input
var tmp = this.value;
this.value = $('input', li).val();
$('input', li).val(tmp);
MUB.updateAllTestLinks();
});
});
++MUB._uid;
}
/**
* In the context of a source LI element, this will analyze the URI in
* the INPUT and check the URL on the site.
*/
,liUpdateTestLink : function () { // call in context of li element
if (! $('input', this)[0].value)
return;
var li = this;
$('span', this).html('');
var url = 'http://' + location.host + '/'
+ $('input', this)[0].value.replace(/^\//, '');
$.ajax({
url : url
,complete : function (xhr, stat) {
if ('success' == stat)
$('span', li).html('&#x2713;');
else {
$('span', li).html('<button><b>404! </b> recheck</button>')
.find('button').click(function () {
MUB.liUpdateTestLink.call(li);
});
}
}
,dataType : 'text'
});
}
/**
* Check all source URLs
*/
,updateAllTestLinks : function () {
$('#sources li').each(MUB.liUpdateTestLink);
}
/**
* In a given array of strings, find the character they all have at
* a particular index
* @param Array arr array of strings
* @param Number pos index to check
* @return mixed a common char or '' if any do not match
*/
,getCommonCharAtPos : function (arr, pos) {
var i
,l = arr.length
,c = arr[0].charAt(pos);
if (c === '' || l === 1)
return c;
for (i = 1; i < l; ++i)
if (arr[i].charAt(pos) !== c)
return '';
return c;
}
/**
* Get the shortest URI to minify the set of source files
* @param Array sources URIs
*/
,getBestUri : function (sources) {
var pos = 0
,base = ''
,c;
while (true) {
c = MUB.getCommonCharAtPos(sources, pos);
if (c === '')
break;
else
base += c;
++pos;
}
base = base.replace(/[^\/]+$/, '');
var uri = MUB._minRoot + 'f=' + sources.join(',');
if (base.charAt(base.length - 1) === '/') {
// we have a base dir!
var basedSources = sources
,i
,l = sources.length;
for (i = 0; i < l; ++i) {
basedSources[i] = sources[i].substr(base.length);
}
base = base.substr(0, base.length - 1);
var bUri = MUB._minRoot + 'b=' + base + '&f=' + basedSources.join(',');
//window.console && console.log([uri, bUri]);
uri = uri.length < bUri.length
? uri
: bUri;
}
return uri;
}
/**
* Create the Minify URI for the sources
*/
,update : function () {
MUB.updateAllTestLinks();
var sources = []
,ext = false
,fail = false;
$('#sources input').each(function () {
var m, val;
if (! fail && this.value && (m = this.value.match(/\.(css|js)$/))) {
var thisExt = m[1];
if (ext === false)
ext = thisExt;
else if (thisExt !== ext) {
fail = true;
return alert('extensions must match!');
}
this.value = this.value.replace(/^\//, '');
if (-1 != $.inArray(this.value, sources)) {
fail = true;
return alert('duplicate file!');
}
sources.push(this.value);
}
});
if (fail || ! sources.length)
return;
$('#groupConfig').val(" 'keyName' => array('//" + sources.join("', '//") + "'),");
var uri = MUB.getBestUri(sources)
,uriH = uri.replace(/</, '&lt;').replace(/>/, '&gt;').replace(/&/, '&amp;');
$('#uriA').html(uriH)[0].href = uri;
$('#uriHtml').val(
ext === 'js'
? '<script type="text/javascript" src="' + uriH + '"></script>'
: '<link type="text/css" rel="stylesheet" href="' + uriH + '" />'
);
$('#results').show();
}
/**
* Handler for the "Add file +" button
*/
,addButtonClick : function () {
$('#results').hide();
MUB.addLi();
MUB.updateAllTestLinks();
$('#update').show().click(MUB.update);
$('#sources li:last input')[0].focus();
}
/**
* Runs on DOMready
*/
,init : function () {
$('#app').show();
$('#sources').html('');
$('#add button').click(MUB.addButtonClick);
// make easier to copy text out of
$('#uriHtml, #groupConfig').click(function () {
this.select();
}).focus(function () {
this.select();
});
$('a.ext').attr({target:'_blank'});
if (location.hash) {
// make links out of URIs from bookmarklet
$('#getBm').hide();
$('#bmUris').html('<p><strong>Found by bookmarklet:</strong> /<a href=#>'
+ location.hash.substr(1).split(',').join('</a> | /<a href=#>')
+ '</a></p>'
);
$('#bmUris a').click(function () {
MUB.addButtonClick();
$('#sources li:last input').val(this.innerHTML)
MUB.liUpdateTestLink.call($('#sources li:last')[0]);
$('#results').hide();
return false;
}).attr({title:'Add file +'});
} else {
// copy bookmarklet code into href
var bmUri = location.pathname.replace(/\/[^\/]*$/, '/bm.js').substr(1);
$.ajax({
url : '../?f=' + bmUri
,success : function (code) {
$('#bm')[0].href = code
.replace('%BUILDER_URL%', location.href)
.replace(/\n/g, ' ');
}
,dataType : 'text'
});
$.browser.msie && $('#getBm p:last').append(' Sorry, not supported in MSIE!');
MUB.addButtonClick();
}
MUB.checkRewrite();
}
};
window.onload = MUB.init;
-36
View File
@@ -1,36 +0,0 @@
javascript:(function() {
var d = document
,uris = []
,i = 0
,o
,home = (location + '').split('/').splice(0, 3).join('/') + '/';
function add(uri) {
return (0 === uri.indexOf(home))
&& (!/[\?&]/.test(uri))
&& uris.push(escape(uri.substr(home.length)));
};
function sheet(ss) {
// we must check the domain with add() before accessing ss.cssRules
// otherwise a security exception will be thrown
if (ss.href && add(ss.href) && ss.cssRules) {
var i = 0, r;
while (r = ss.cssRules[i++])
r.styleSheet && sheet(r.styleSheet);
}
};
while (o = d.getElementsByTagName('script')[i++])
o.src && !(o.type && /vbs/i.test(o.type)) && add(o.src);
i = 0;
while (o = d.styleSheets[i++])
/* http://www.w3.org/TR/DOM-Level-2-Style/stylesheets.html#StyleSheets-DocumentStyle-styleSheets
document.styleSheet is a list property where [0] accesses the 1st element and
[outOfRange] returns null. In IE, styleSheets is a function, and also throws an
exception when you check the out of bounds index. (sigh) */
sheet(o);
if (uris.length)
window.open('%BUILDER_URL%#' + uris.join(','));
else
alert('No js/css files found with URLs within "'
+ home.split('/')[2]
+ '".\n(This tool is limited to URLs with the same domain.)');
})();
-182
View File
@@ -1,182 +0,0 @@
<?php
if (phpversion() < 5) {
exit('Minify requires PHP5 or greater.');
}
// check for auto-encoding
$encodeOutput = (function_exists('gzdeflate')
&& !ini_get('zlib.output_compression'));
require dirname(__FILE__) . '/../config.php';
if (! $min_enableBuilder) {
header('Location: /');
exit();
}
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">
body {margin:1em 60px;}
h1, h2, h3 {margin-left:-25px; position:relative;}
h1 {margin-top:0;}
#sources {margin:0; padding:0;}
#sources li {margin:0 0 0 40px}
#sources li input {margin-left:2px}
#add {margin:5px 0 1em 40px}
.hide {display:none}
#uriTable {border-collapse:collapse;}
#uriTable td, #uriTable th {padding-top:10px;}
#uriTable th {padding-right:10px;}
#groupConfig {font-family:monospace;}
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>
<?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>
<?php endIf; ?>
<p id=minRewriteFailed class="hide"><strong>Note:</strong> Your webserver does not seem to
support mod_rewrite (used in /min/.htaccess). Your Minify URIs will contain "?", which
<a href="http://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/"
>may reduce the benefit of proxy cache servers</a>.</p>
<h1>Minify URI Builder</h1>
<noscript><p class="topNote">Javascript and a browser supported by jQuery 1.2.6 is required
for this application.</p></noscript>
<div id=app class=hide>
<p>Create a list of Javascript or CSS files (or 1 is fine) you'd like to combine
and click [Update].</p>
<ol id=sources><li></li></ol>
<div id=add><button>Add file +</button></div>
<div id=bmUris></div>
<p><button id=update class=hide>Update</button></p>
<div id=results class=hide>
<h2>Minify URI</h2>
<p>Place this URI in your HTML to serve the files above combined, minified, compressed and
with cache headers.</p>
<table id=uriTable>
<tr><th>URI</th><td><a id=uriA class=ext>/min</a> <small>(opens in new window)</small></td></tr>
<tr><th>HTML</th><td><input id=uriHtml type=text size=100 readonly></td></tr>
</table>
<h2>How to serve these files as a group</h2>
<p>For the best performance you can serve these files as a pre-defined group with a URI
like: <code><span class=minRoot>/min/?</span>g=keyName</code></p>
<p>To do this, add a line like this to /min/groupsConfig.php:</p>
<pre><code>return array(
<span style="color:#666">... your existing groups here ...</span>
<input id=groupConfig size=100 type=text readonly>
);</code></pre>
<p><em>Make sure to replace <code>keyName</code> with a unique key for this group.</em></p>
</div>
<div id=getBm>
<h3>Find URIs on a Page</h3>
<p>You can use the bookmarklet below to fetch all CSS &amp; Javascript URIs from a page
on your site. When you active it, this page will open in a new window with a list of
available URIs to add.</p>
<p><a id=bm>Create Minify URIs</a> <small>(right-click, add to bookmarks)</small></p>
</div>
<h3>Combining CSS files that contain <code>@import</code></h3>
<p>If your CSS files contain <code>@import</code> declarations, Minify will not
remove them. Therefore, you will want to remove those that point to files already
in your list, and move any others to the top of the first file in your list
(imports below any styles will be ignored by browsers as invalid).</p>
<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>
</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>
<script type="text/javascript"
src="http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js"></script>
<script type="text/javascript">
$(function () {
// detection of double output encoding
var msg = '<\p class=topWarning><\strong>Warning:<\/strong> ';
var url = 'ocCheck.php?' + (new Date()).getTime();
$.get(url, function (ocStatus) {
$.get(url + '&hello=1', function (ocHello) {
if (ocHello != 'World!') {
msg += 'It appears output is being automatically compressed, interfering '
+ ' with Minify\'s own compression. ';
if (ocStatus == '1')
msg += 'The option "zlib.output_compression" is enabled in your PHP configuration. '
+ 'Minify set this to "0", but it had no effect. This option must be disabled '
+ 'in php.ini or .htaccess.';
else
msg += 'The option "zlib.output_compression" is disabled in your PHP configuration '
+ 'so this behavior is likely due to a server option.';
$(document.body).prepend(msg + '<\/p>');
} else
if (ocStatus == '1')
$(document.body).prepend('<\p class=topNote><\strong>Note:</\strong> The option '
+ '"zlib.output_compression" is enabled in your PHP configuration, but has been '
+ 'successfully disabled via ini_set(). If you experience mangled output you '
+ 'may want to consider disabling this option in your PHP configuration.<\/p>'
);
});
});
});
</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>
<?php
$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';
if (0 === stripos(PHP_OS, 'win')) {
Minify::setDocRoot(); // we may be on IIS
}
Minify::setCache(isset($min_cachePath) ? $min_cachePath : null);
Minify::$uploaderHoursBehind = $min_uploaderHoursBehind;
Minify::serve('Page', $serveOpts);
-36
View File
@@ -1,36 +0,0 @@
<?php
/**
* AJAX checks for zlib.output_compression
*
* @package Minify
*/
$_oc = ini_get('zlib.output_compression');
// allow access only if builder is enabled
require dirname(__FILE__) . '/../config.php';
if (! $min_enableBuilder) {
header('Location: /');
exit();
}
if (isset($_GET['hello'])) {
// echo 'World!'
// try to prevent double encoding (may not have an effect)
ini_set('zlib.output_compression', '0');
require $min_libPath . '/HTTP/Encoder.php';
HTTP_Encoder::$encodeToIe6 = true; // just in case
$he = new HTTP_Encoder(array(
'content' => 'World!'
,'method' => 'deflate'
));
$he->encode();
$he->sendAll();
} else {
// echo status "0" or "1"
header('Content-Type: text/plain');
echo (int)$_oc;
}
-1
View File
@@ -1 +0,0 @@
1
+77 -25
View File
@@ -1,19 +1,40 @@
<?php
/**
* Configuration for default Minify application
* Configuration for "min", the default application built with the Minify
* library
*
* @package Minify
*/
defined('MOODLE_INTERNAL') || die();
defined('MOODLE_INTERNAL') || die(); // start of moodle modification
// NOTE: Copy all necessary settings here, do not modify the rest.
// Minifier can not be accessed directly, only use PHP api.
$min_enableBuilder = false;
$min_errorLogger = false;
$min_allowDebugFlag = debugging('', DEBUG_DEVELOPER);
$min_cachePath = $CFG->tempdir;
$min_documentRoot = $CFG->dirroot.'/lib/minify';
$min_cacheFileLocking = true;
$min_serveOptions['bubbleCssImports'] = false;
$min_serveOptions['maxAge'] = 1800;
$min_serveOptions['minApp']['groupsOnly'] = true;
$min_symlinks = array();
$min_uploaderHoursBehind = 0;
$min_libPath = dirname(__FILE__) . '/lib';
// do not change zlib compression or buffering here
// TODO: locking setting, caching setting
return; // end of moodle modification
/**
* 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 = ($CFG->debug);
* Allow use of the Minify URI Builder app. Only set this to true while you need it.
**/
$min_enableBuilder = true;
/**
@@ -23,24 +44,38 @@ $min_allowDebugFlag = ($CFG->debug);
*
* 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;
/**
* Allow use of the Minify URI Builder app. If you no longer need
* this, set to false.
**/
$min_enableBuilder = 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;
/**
* For best performance, specify your temp directory here. Otherwise Minify
* will have to load extra code to guess. Some examples below:
*/
$min_cachePath = $CFG->tempdir.'';
//$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();
/**
@@ -53,8 +88,8 @@ $min_cachePath = $CFG->tempdir.'';
* If /min/ is directly inside your document root, just uncomment the
* second line. The third line might work on some Apache servers.
*/
$min_documentRoot = $CFG->dirroot.'/lib/minify';
//$min_documentRoot = substr(__FILE__, 0, strlen(__FILE__) - 15);
$min_documentRoot = '';
//$min_documentRoot = substr(__FILE__, 0, -15);
//$min_documentRoot = $_SERVER['SUBDOMAIN_DOCUMENT_ROOT'];
@@ -77,9 +112,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
@@ -88,6 +123,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.
@@ -102,12 +149,17 @@ $min_serveOptions['maxAge'] = 1800;
* Set to true to disable the "f" GET parameter for specifying files.
* Only the "g" parameter will be considered.
*/
$min_serveOptions['minApp']['groupsOnly'] = true;
$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';
/**
@@ -148,7 +200,7 @@ $min_uploaderHoursBehind = 0;
* Path to Minify's lib folder. If you happen to move it, change
* this accordingly.
*/
$min_libPath = $CFG->libdir . '/minify/lib';
$min_libPath = dirname(__FILE__) . '/lib';
// try to disable output_compression (may not have an effect)
+3 -20
View File
@@ -7,28 +7,11 @@
/**
* You may wish to use the Minify URI Builder app to suggest
* changes. http://yourdomain/min/builder/
*
* See http://code.google.com/p/minify/wiki/CustomSource for other ideas
**/
return array(
// 'js' => array('//js/file1.js', '//js/file2.js'),
// 'css' => array('//css/file1.css', '//css/file2.css'),
// custom source example
/*'js2' => array(
dirname(__FILE__) . '/../min_unit_tests/_test_files/js/before.js',
// do NOT process this file
new Minify_Source(array(
'filepath' => dirname(__FILE__) . '/../min_unit_tests/_test_files/js/before.js',
'minifier' => create_function('$a', 'return $a;')
))
),//*/
/*'js3' => array(
dirname(__FILE__) . '/../min_unit_tests/_test_files/js/before.js',
// do NOT process this file
new Minify_Source(array(
'filepath' => dirname(__FILE__) . '/../min_unit_tests/_test_files/js/before.js',
'minifier' => array('Minify_Packer', 'minify')
))
),//*/
);
);
+1 -1
View File
@@ -1065,7 +1065,7 @@ class FirePHP {
* @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
* @author Christoph Dorn <[email protected]>
* @copyright 2005 Michal Migurski
* @version CVS: $Id$
* @version CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $
* @license http://www.opensource.org/licenses/bsd-license.php
* @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198
*/
+35 -17
View File
@@ -75,9 +75,8 @@ class HTTP_ConditionalGet {
/**
* @param array $spec options
*
* 'isPublic': (bool) if true, the Cache-Control header will contain
* "public", allowing proxies to cache the content. Otherwise "private" will
* be sent, allowing only browser caching. (default false)
* 'isPublic': (bool) if false, the Cache-Control header will contain
* "private", allowing only browser caching. (default false)
*
* 'lastModifiedTime': (int) if given, both ETag AND Last-Modified headers
* will be sent with content. This is recommended.
@@ -106,8 +105,6 @@ class HTTP_ConditionalGet {
* seconds, and also set the Expires header to the equivalent GMT date.
* After the max-age period has passed, the browser will again send a
* conditional GET to revalidate its cache.
*
* @return null
*/
public function __construct($spec)
{
@@ -150,7 +147,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 +209,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) {
@@ -230,8 +232,6 @@ class HTTP_ConditionalGet {
* "private" will be sent, allowing only browser caching.
*
* @param array $options (default empty) additional options for constructor
*
* @return null
*/
public static function check($lastModifiedTime = null, $isPublic = false, $options = array())
{
@@ -267,13 +267,21 @@ class HTTP_ConditionalGet {
protected $_lmTime = null;
protected $_etag = null;
protected $_stripEtag = false;
/**
* @param string $hash
*
* @param string $scope
*/
protected function _setEtag($hash, $scope)
{
$this->_etag = '"' . substr($scope, 0, 3) . $hash . '"';
$this->_headers['ETag'] = $this->_etag;
}
/**
* @param int $time
*/
protected function _setLastModified($time)
{
$this->_lmTime = (int)$time;
@@ -282,6 +290,8 @@ class HTTP_ConditionalGet {
/**
* Determine validity of client cache and queue 304 header if valid
*
* @return bool
*/
protected function _isCacheValid()
{
@@ -298,6 +308,9 @@ class HTTP_ConditionalGet {
return $isValid;
}
/**
* @return bool
*/
protected function resourceMatchedEtag()
{
if (!isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
@@ -319,7 +332,12 @@ class HTTP_ConditionalGet {
}
return false;
}
/**
* @param string $etag
*
* @return string
*/
protected function normalizeEtag($etag) {
$etag = trim($etag);
return $this->_stripEtag
@@ -327,17 +345,17 @@ class HTTP_ConditionalGet {
: $etag;
}
/**
* @return bool
*/
protected function resourceNotModified()
{
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);
+28 -19
View File
@@ -59,7 +59,7 @@ class HTTP_Encoder {
*
* @var bool
*/
public static $encodeToIe6 = false;
public static $encodeToIe6 = true;
/**
@@ -85,13 +85,16 @@ class HTTP_Encoder {
* method. If not set, the best method will be chosen by getAcceptedEncoding()
* The available methods are 'gzip', 'deflate', 'compress', and '' (no
* encoding)
*
* @return null
*/
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'];
}
@@ -109,7 +112,7 @@ class HTTP_Encoder {
*
* Call after encode() for encoded content.
*
* return string
* @return string
*/
public function getContent()
{
@@ -143,8 +146,6 @@ class HTTP_Encoder {
* not handled purposefully.
*
* @see getHeaders()
*
* @return null
*/
public function sendHeaders()
{
@@ -161,8 +162,6 @@ class HTTP_Encoder {
* You must call this before headers are sent and it probably cannot be
* used in conjunction with zlib output buffering / mod_gzip. Errors are
* not handled purposefully.
*
* @return null
*/
public function sendAll()
{
@@ -195,7 +194,7 @@ class HTTP_Encoder {
// @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
if (! isset($_SERVER['HTTP_ACCEPT_ENCODING'])
|| self::_isBuggyIe())
|| self::isBuggyIe())
{
return array('', '');
}
@@ -253,7 +252,9 @@ class HTTP_Encoder {
*/
public function encode($compressionLevel = null)
{
$this->_headers['Vary'] = 'Accept-Encoding';
if (! self::isBuggyIe()) {
$this->_headers['Vary'] = 'Accept-Encoding';
}
if (null === $compressionLevel) {
$compressionLevel = self::$compressionLevel;
}
@@ -273,7 +274,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;
@@ -301,16 +304,17 @@ class HTTP_Encoder {
$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()
{
if (empty($_SERVER['HTTP_USER_AGENT'])) {
return false;
}
$ua = $_SERVER['HTTP_USER_AGENT'];
// quick escape for non-IEs
if (0 !== strpos($ua, 'Mozilla/4.0 (compatible; MSIE ')
@@ -318,9 +322,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;
}
+103 -32
View File
@@ -1,16 +1,20 @@
<?php
/**
* jsmin.php - PHP implementation of Douglas Crockford's JSMin.
* JSMin.php - modified PHP implementation of Douglas Crockford's JSMin.
*
* 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.
* <code>
* $minifiedJs = JSMin::minify($js);
* </code>
*
* This is a modified port of jsmin.c. Improvements:
*
* 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.
* Does not choke on some regexp literals containing quote characters. E.g. /'/
*
* Spaces are preserved after some add/sub operators, so they are not mistakenly
* converted to post-inc/dec. E.g. a + ++b -> a+ ++b
*
* Preserves multi-line comments that begin with /*!
*
* PHP 5 or higher is required.
*
* Permission is hereby granted to use this version of the library under the
@@ -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,11 +68,13 @@ class JSMin {
protected $inputLength = 0;
protected $lookAhead = null;
protected $output = '';
protected $lastByteOut = '';
/**
* Minify Javascript
* Minify Javascript.
*
* @param string $js Javascript to be minified
*
* @return string
*/
public static function minify($js)
@@ -76,38 +82,55 @@ 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;
}
/**
* Perform minification, return result
*
* @return string
*/
public function min()
{
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
if ($this->a === ' ') {
if (! $this->isAlphaNum($this->b)) {
if (($this->lastByteOut === '+' || $this->lastByteOut === '-')
&& ($this->b === $this->lastByteOut)) {
// Don't delete this space. If we do, the addition/subtraction
// could be parsed as a post-increment
} elseif (! $this->isAlphaNum($this->b)) {
$command = self::ACTION_DELETE_A;
}
} 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)) {
@@ -120,19 +143,38 @@ 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.
* ACTION_DELETE_A_B = Get the next B.
*
* @param int $command
* @throws JSMin_UnterminatedRegExpException|JSMin_UnterminatedStringException
*/
protected function action($command)
{
if ($command === self::ACTION_DELETE_A_B
&& $this->b === ' '
&& ($this->a === '+' || $this->a === '-')) {
// Note: we're at an addition/substraction operator; the inputIndex
// will certainly be a valid index
if ($this->input[$this->inputIndex] === $this->a) {
// This is "+ +" or "- -". Don't delete the space.
$command = self::ACTION_KEEP_A;
}
}
switch ($command) {
case self::ACTION_KEEP_A:
$this->output .= $this->a;
$this->lastByteOut = $this->a;
// fallthrough
case self::ACTION_DELETE_A:
$this->a = $this->b;
@@ -140,17 +182,22 @@ class JSMin {
$str = $this->a; // in case needed for exception
while (true) {
$this->output .= $this->a;
$this->lastByteOut = $this->a;
$this->a = $this->get();
if ($this->a === $this->b) { // end quote
break;
}
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 === '\\') {
$this->output .= $this->a;
$this->lastByteOut = $this->a;
$this->a = $this->get();
$str .= $this->a;
}
@@ -173,16 +220,21 @@ 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;
$this->lastByteOut = $this->a;
}
$this->b = $this->next();
}
// end case ACTION_DELETE_A_B
}
}
/**
* @return bool
*/
protected function isRegexpLiteral()
{
if (false !== strpos("\n{;(,=:[!&|?", $this->a)) { // we aren't dividing
@@ -207,9 +259,11 @@ class JSMin {
}
return false;
}
/**
* Get next char. Convert ctrl char to space.
*
* @return string
*/
protected function get()
{
@@ -231,24 +285,33 @@ class JSMin {
}
return $c;
}
/**
* Get next char. If is ctrl character, translate to a space or newline.
*
* @return string
*/
protected function peek()
{
$this->lookAhead = $this->get();
return $this->lookAhead;
}
/**
* Is $c a letter, digit, underscore, dollar sign, escape, or non-ASCII?
*
* @param string $c
*
* @return bool
*/
protected function isAlphaNum($c)
{
return (preg_match('/^[0-9a-zA-Z_\\$\\\\]$/', $c) || ord($c) > 126);
}
/**
* @return string
*/
protected function singleLineComment()
{
$comment = '';
@@ -264,7 +327,11 @@ class JSMin {
}
}
}
/**
* @return string
* @throws JSMin_UnterminatedCommentException
*/
protected function multipleLineComment()
{
$this->get();
@@ -276,7 +343,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,15 +352,19 @@ 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.
*
* @return string
*/
protected function next()
{
+379 -165
View File
@@ -1,7 +1,7 @@
<?php
/**
* JSMinPlus version 1.1
* JSMinPlus version 1.4
*
* Minifies a javascript file using a javascript parser
*
@@ -15,6 +15,11 @@
* Usage: $minified = JSMinPlus::minify($script [, $filename])
*
* Versionlog (see also changelog.txt):
* 23-07-2011 - remove dynamic creation of OP_* and KEYWORD_* defines and declare them on top
* reduce memory footprint by minifying by block-scope
* some small byte-saving and performance improvements
* 12-05-2009 - fixed hook:colon precedence, fixed empty body in loop and if-constructs
* 18-04-2009 - fixed crashbug in PHP 5.2.9 and several other bugfixes
* 12-04-2009 - some small bugfixes and performance improvements
* 09-04-2009 - initial open sourced version 1.0
*
@@ -43,7 +48,7 @@
* the Initial Developer. All Rights Reserved.
*
* Contributor(s): Tino Zijdel <[email protected]>
* PHP port, modifications and minifier routine are (C) 2009
* PHP port, modifications and minifier routine are (C) 2009-2011
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@@ -65,7 +70,8 @@ define('TOKEN_IDENTIFIER', 3);
define('TOKEN_STRING', 4);
define('TOKEN_REGEXP', 5);
define('TOKEN_NEWLINE', 6);
define('TOKEN_CONDCOMMENT_MULTILINE', 7);
define('TOKEN_CONDCOMMENT_START', 7);
define('TOKEN_CONDCOMMENT_END', 8);
define('JS_SCRIPT', 100);
define('JS_BLOCK', 101);
@@ -82,10 +88,89 @@ define('JS_SETTER', 111);
define('JS_GROUP', 112);
define('JS_LIST', 113);
define('JS_MINIFIED', 999);
define('DECLARED_FORM', 0);
define('EXPRESSED_FORM', 1);
define('STATEMENT_FORM', 2);
/* Operators */
define('OP_SEMICOLON', ';');
define('OP_COMMA', ',');
define('OP_HOOK', '?');
define('OP_COLON', ':');
define('OP_OR', '||');
define('OP_AND', '&&');
define('OP_BITWISE_OR', '|');
define('OP_BITWISE_XOR', '^');
define('OP_BITWISE_AND', '&');
define('OP_STRICT_EQ', '===');
define('OP_EQ', '==');
define('OP_ASSIGN', '=');
define('OP_STRICT_NE', '!==');
define('OP_NE', '!=');
define('OP_LSH', '<<');
define('OP_LE', '<=');
define('OP_LT', '<');
define('OP_URSH', '>>>');
define('OP_RSH', '>>');
define('OP_GE', '>=');
define('OP_GT', '>');
define('OP_INCREMENT', '++');
define('OP_DECREMENT', '--');
define('OP_PLUS', '+');
define('OP_MINUS', '-');
define('OP_MUL', '*');
define('OP_DIV', '/');
define('OP_MOD', '%');
define('OP_NOT', '!');
define('OP_BITWISE_NOT', '~');
define('OP_DOT', '.');
define('OP_LEFT_BRACKET', '[');
define('OP_RIGHT_BRACKET', ']');
define('OP_LEFT_CURLY', '{');
define('OP_RIGHT_CURLY', '}');
define('OP_LEFT_PAREN', '(');
define('OP_RIGHT_PAREN', ')');
define('OP_CONDCOMMENT_END', '@*/');
define('OP_UNARY_PLUS', 'U+');
define('OP_UNARY_MINUS', 'U-');
/* Keywords */
define('KEYWORD_BREAK', 'break');
define('KEYWORD_CASE', 'case');
define('KEYWORD_CATCH', 'catch');
define('KEYWORD_CONST', 'const');
define('KEYWORD_CONTINUE', 'continue');
define('KEYWORD_DEBUGGER', 'debugger');
define('KEYWORD_DEFAULT', 'default');
define('KEYWORD_DELETE', 'delete');
define('KEYWORD_DO', 'do');
define('KEYWORD_ELSE', 'else');
define('KEYWORD_ENUM', 'enum');
define('KEYWORD_FALSE', 'false');
define('KEYWORD_FINALLY', 'finally');
define('KEYWORD_FOR', 'for');
define('KEYWORD_FUNCTION', 'function');
define('KEYWORD_IF', 'if');
define('KEYWORD_IN', 'in');
define('KEYWORD_INSTANCEOF', 'instanceof');
define('KEYWORD_NEW', 'new');
define('KEYWORD_NULL', 'null');
define('KEYWORD_RETURN', 'return');
define('KEYWORD_SWITCH', 'switch');
define('KEYWORD_THIS', 'this');
define('KEYWORD_THROW', 'throw');
define('KEYWORD_TRUE', 'true');
define('KEYWORD_TRY', 'try');
define('KEYWORD_TYPEOF', 'typeof');
define('KEYWORD_VAR', 'var');
define('KEYWORD_VOID', 'void');
define('KEYWORD_WHILE', 'while');
define('KEYWORD_WITH', 'with');
class JSMinPlus
{
private $parser;
@@ -107,7 +192,7 @@ class JSMinPlus
private function __construct()
{
$this->parser = new JSParser();
$this->parser = new JSParser($this);
}
public static function minify($js, $filename='')
@@ -136,42 +221,55 @@ class JSMinPlus
return false;
}
private function parseTree($n, $noBlockGrouping = false)
public function parseTree($n, $noBlockGrouping = false)
{
$s = '';
switch ($n->type)
{
case KEYWORD_FUNCTION:
$s .= 'function' . ($n->name ? ' ' . $n->name : '') . '(';
$params = $n->params;
for ($i = 0, $j = count($params); $i < $j; $i++)
$s .= ($i ? ',' : '') . $params[$i];
$s .= '){' . $this->parseTree($n->body, true) . '}';
case JS_MINIFIED:
$s = $n->value;
break;
case JS_SCRIPT:
// we do nothing with funDecls or varDecls
// we do nothing yet with funDecls or varDecls
$noBlockGrouping = true;
// fall through
// FALL THROUGH
case JS_BLOCK:
$childs = $n->treeNodes;
$lastType = 0;
for ($c = 0, $i = 0, $j = count($childs); $i < $j; $i++)
{
$type = $childs[$i]->type;
$t = $this->parseTree($childs[$i]);
if (strlen($t))
{
if ($c)
{
if ($childs[$i]->type == KEYWORD_FUNCTION && $childs[$i]->functionForm == DECLARED_FORM)
$s .= "\n"; // put declared functions on a new line
$s = rtrim($s, ';');
if ($type == KEYWORD_FUNCTION && $childs[$i]->functionForm == DECLARED_FORM)
{
// put declared functions on a new line
$s .= "\n";
}
elseif ($type == KEYWORD_VAR && $type == $lastType)
{
// mutiple var-statements can go into one
$t = ',' . substr($t, 4);
}
else
{
// add terminator
$s .= ';';
}
}
$s .= $t;
$c++;
$lastType = $type;
}
}
@@ -181,31 +279,41 @@ class JSMinPlus
}
break;
case KEYWORD_FUNCTION:
$s .= 'function' . ($n->name ? ' ' . $n->name : '') . '(';
$params = $n->params;
for ($i = 0, $j = count($params); $i < $j; $i++)
$s .= ($i ? ',' : '') . $params[$i];
$s .= '){' . $this->parseTree($n->body, true) . '}';
break;
case KEYWORD_IF:
$s = 'if(' . $this->parseTree($n->condition) . ')';
$thenPart = $this->parseTree($n->thenPart);
$elsePart = $n->elsePart ? $this->parseTree($n->elsePart) : null;
// quite a rancid hack to see if we should enclose the thenpart in brackets
if ($thenPart[0] != '{')
{
if (strpos($thenPart, 'if(') !== false)
$thenPart = '{' . $thenPart . '}';
elseif ($elsePart)
$thenPart .= ';';
}
$s .= $thenPart;
// empty if-statement
if ($thenPart == '')
$thenPart = ';';
if ($elsePart)
{
$s .= 'else';
// be carefull and always make a block out of the thenPart; could be more optimized but is a lot of trouble
if ($thenPart != ';' && $thenPart[0] != '{')
$thenPart = '{' . $thenPart . '}';
$s .= $thenPart . 'else';
// we could check for more, but that hardly ever applies so go for performance
if ($elsePart[0] != '{')
$s .= ' ';
$s .= $elsePart;
}
else
{
$s .= $thenPart;
}
break;
case KEYWORD_SWITCH:
@@ -219,26 +327,48 @@ class JSMinPlus
else
$s .= 'default:';
$statement = $this->parseTree($case->statements);
$statement = $this->parseTree($case->statements, true);
if ($statement)
$s .= $statement . ';';
{
$s .= $statement;
// no terminator for last statement
if ($i + 1 < $j)
$s .= ';';
}
}
$s = rtrim($s, ';') . '}';
$s .= '}';
break;
case KEYWORD_FOR:
$s = 'for(' . ($n->setup ? $this->parseTree($n->setup) : '')
. ';' . ($n->condition ? $this->parseTree($n->condition) : '')
. ';' . ($n->update ? $this->parseTree($n->update) : '') . ')'
. $this->parseTree($n->body);
. ';' . ($n->update ? $this->parseTree($n->update) : '') . ')';
$body = $this->parseTree($n->body);
if ($body == '')
$body = ';';
$s .= $body;
break;
case KEYWORD_WHILE:
$s = 'while(' . $this->parseTree($n->condition) . ')' . $this->parseTree($n->body);
$s = 'while(' . $this->parseTree($n->condition) . ')';
$body = $this->parseTree($n->body);
if ($body == '')
$body = ';';
$s .= $body;
break;
case JS_FOR_IN:
$s = 'for(' . ($n->varDecl ? $this->parseTree($n->varDecl) : $this->parseTree($n->iterator)) . ' in ' . $this->parseTree($n->object) . ')' . $this->parseTree($n->body);
$s = 'for(' . ($n->varDecl ? $this->parseTree($n->varDecl) : $this->parseTree($n->iterator)) . ' in ' . $this->parseTree($n->object) . ')';
$body = $this->parseTree($n->body);
if ($body == '')
$body = ';';
$s .= $body;
break;
case KEYWORD_DO:
@@ -263,11 +393,19 @@ class JSMinPlus
break;
case KEYWORD_THROW:
$s = 'throw ' . $this->parseTree($n->exception);
break;
case KEYWORD_RETURN:
$s = 'return' . ($n->value ? ' ' . $this->parseTree($n->value) : '');
$s = $n->type;
if ($n->value)
{
$t = $this->parseTree($n->value);
if (strlen($t))
{
if ($this->isWordChar($t[0]) || $t[0] == '\\')
$s .= ' ';
$s .= $t;
}
}
break;
case KEYWORD_WITH:
@@ -288,12 +426,47 @@ class JSMinPlus
}
break;
case KEYWORD_IN:
case KEYWORD_INSTANCEOF:
$left = $this->parseTree($n->treeNodes[0]);
$right = $this->parseTree($n->treeNodes[1]);
$s = $left;
if ($this->isWordChar(substr($left, -1)))
$s .= ' ';
$s .= $n->type;
if ($this->isWordChar($right[0]) || $right[0] == '\\')
$s .= ' ';
$s .= $right;
break;
case KEYWORD_DELETE:
case KEYWORD_TYPEOF:
$right = $this->parseTree($n->treeNodes[0]);
$s = $n->type;
if ($this->isWordChar($right[0]) || $right[0] == '\\')
$s .= ' ';
$s .= $right;
break;
case KEYWORD_VOID:
$s = 'void(' . $this->parseTree($n->treeNodes[0]) . ')';
break;
case KEYWORD_DEBUGGER:
throw new Exception('NOT IMPLEMENTED: DEBUGGER');
break;
case TOKEN_CONDCOMMENT_MULTILINE:
$s = $n->value . ' ';
case TOKEN_CONDCOMMENT_START:
case TOKEN_CONDCOMMENT_END:
$s = $n->value . ($n->type == TOKEN_CONDCOMMENT_START ? ' ' : '');
$childs = $n->treeNodes;
for ($i = 0, $j = count($childs); $i < $j; $i++)
$s .= $this->parseTree($childs[$i]);
@@ -333,34 +506,32 @@ class JSMinPlus
case OP_PLUS:
case OP_MINUS:
$s = $this->parseTree($n->treeNodes[0]) . $n->type;
$nextTokenType = $n->treeNodes[1]->type;
if ( $nextTokenType == OP_PLUS || $nextTokenType == OP_MINUS ||
$nextTokenType == OP_INCREMENT || $nextTokenType == OP_DECREMENT ||
$nextTokenType == OP_UNARY_PLUS || $nextTokenType == OP_UNARY_MINUS
)
$s .= ' ';
$s .= $this->parseTree($n->treeNodes[1]);
break;
$left = $this->parseTree($n->treeNodes[0]);
$right = $this->parseTree($n->treeNodes[1]);
case KEYWORD_IN:
$s = $this->parseTree($n->treeNodes[0]) . ' in ' . $this->parseTree($n->treeNodes[1]);
break;
switch ($n->treeNodes[1]->type)
{
case OP_PLUS:
case OP_MINUS:
case OP_INCREMENT:
case OP_DECREMENT:
case OP_UNARY_PLUS:
case OP_UNARY_MINUS:
$s = $left . $n->type . ' ' . $right;
break;
case KEYWORD_INSTANCEOF:
$s = $this->parseTree($n->treeNodes[0]) . ' instanceof ' . $this->parseTree($n->treeNodes[1]);
break;
case TOKEN_STRING:
//combine concatted strings with same quotestyle
if ($n->type == OP_PLUS && substr($left, -1) == $right[0])
{
$s = substr($left, 0, -1) . substr($right, 1);
break;
}
// FALL THROUGH
case KEYWORD_DELETE:
$s = 'delete ' . $this->parseTree($n->treeNodes[0]);
break;
case KEYWORD_VOID:
$s = 'void(' . $this->parseTree($n->treeNodes[0]) . ')';
break;
case KEYWORD_TYPEOF:
$s = 'typeof ' . $this->parseTree($n->treeNodes[0]);
default:
$s = $left . $n->type . $right;
}
break;
case OP_NOT:
@@ -452,13 +623,33 @@ class JSMinPlus
$s .= '}';
break;
case TOKEN_NUMBER:
$s = $n->value;
if (preg_match('/^([1-9]+)(0{3,})$/', $s, $m))
$s = $m[1] . 'e' . strlen($m[2]);
break;
case KEYWORD_NULL: case KEYWORD_THIS: case KEYWORD_TRUE: case KEYWORD_FALSE:
case TOKEN_IDENTIFIER: case TOKEN_NUMBER: case TOKEN_STRING: case TOKEN_REGEXP:
case TOKEN_IDENTIFIER: case TOKEN_STRING: case TOKEN_REGEXP:
$s = $n->value;
break;
case JS_GROUP:
$s = '(' . $this->parseTree($n->treeNodes[0]) . ')';
if (in_array(
$n->treeNodes[0]->type,
array(
JS_ARRAY_INIT, JS_OBJECT_INIT, JS_GROUP,
TOKEN_NUMBER, TOKEN_STRING, TOKEN_REGEXP, TOKEN_IDENTIFIER,
KEYWORD_NULL, KEYWORD_THIS, KEYWORD_TRUE, KEYWORD_FALSE
)
))
{
$s = $this->parseTree($n->treeNodes[0]);
}
else
{
$s = '(' . $this->parseTree($n->treeNodes[0]) . ')';
}
break;
default:
@@ -472,17 +663,23 @@ class JSMinPlus
{
return preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $string) && !in_array($string, $this->reserved);
}
private function isWordChar($char)
{
return $char == '_' || $char == '$' || ctype_alnum($char);
}
}
class JSParser
{
private $t;
private $minifier;
private $opPrecedence = array(
';' => 0,
',' => 1,
'=' => 2, '?' => 2, ':' => 2,
// The above all have to have the same precedence, see bug 330975.
// The above all have to have the same precedence, see bug 330975
'||' => 4,
'&&' => 5,
'|' => 6,
@@ -523,11 +720,12 @@ class JSParser
'.' => 2,
JS_NEW_WITH_ARGS => 2, JS_INDEX => 2, JS_CALL => 2,
JS_ARRAY_INIT => 1, JS_OBJECT_INIT => 1, JS_GROUP => 1,
TOKEN_CONDCOMMENT_MULTILINE => 1
TOKEN_CONDCOMMENT_START => 1, TOKEN_CONDCOMMENT_END => 1
);
public function __construct()
public function __construct($minifier=null)
{
$this->minifier = $minifier;
$this->t = new JSTokenizer();
}
@@ -551,6 +749,19 @@ class JSParser
$n->funDecls = $x->funDecls;
$n->varDecls = $x->varDecls;
// minify by scope
if ($this->minifier)
{
$n->value = $this->minifier->parseTree($n);
// clear tree from node to save memory
$n->treeNodes = null;
$n->funDecls = null;
$n->varDecls = null;
$n->type = JS_MINIFIED;
}
return $n;
}
@@ -809,7 +1020,7 @@ class JSParser
case KEYWORD_THROW:
$n = new JSNode($this->t);
$n->exception = $this->Expression($x);
$n->value = $this->Expression($x);
break;
case KEYWORD_RETURN:
@@ -835,7 +1046,8 @@ class JSParser
$n = $this->Variables($x);
break;
case TOKEN_CONDCOMMENT_MULTILINE:
case TOKEN_CONDCOMMENT_START:
case TOKEN_CONDCOMMENT_END:
$n = new JSNode($this->t);
return $n;
@@ -994,37 +1206,47 @@ class JSParser
// NB: cannot be empty, Statement handled that.
break 2;
case OP_ASSIGN:
case OP_HOOK:
case OP_COLON:
if ($this->t->scanOperand)
break 2;
// Use >, not >=, for right-associative ASSIGN and HOOK/COLON.
while ( !empty($operators) &&
( $this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt] ||
($tt == OP_COLON && end($operators)->type == OP_ASSIGN)
)
$this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt]
)
$this->reduce($operators, $operands);
if ($tt == OP_COLON)
{
$n = end($operators);
if ($n->type != OP_HOOK)
throw $this->t->newSyntaxError('Invalid label');
array_push($operators, new JSNode($this->t));
--$x->hookLevel;
}
else
{
array_push($operators, new JSNode($this->t));
if ($tt == OP_ASSIGN)
end($operands)->assignOp = $this->t->currentToken()->assignOp;
else
++$x->hookLevel;
}
++$x->hookLevel;
$this->t->scanOperand = true;
$n = $this->Expression($x);
if (!$this->t->match(OP_COLON))
break 2;
--$x->hookLevel;
array_push($operands, $n);
break;
case OP_COLON:
if ($x->hookLevel)
break 2;
throw $this->t->newSyntaxError('Invalid label');
break;
case OP_ASSIGN:
if ($this->t->scanOperand)
break 2;
// Use >, not >=, for right-associative ASSIGN
while ( !empty($operators) &&
$this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt]
)
$this->reduce($operators, $operands);
array_push($operators, new JSNode($this->t));
end($operands)->assignOp = $this->t->currentToken()->assignOp;
$this->t->scanOperand = true;
break;
@@ -1036,14 +1258,19 @@ class JSParser
!$x->bracketLevel && !$x->curlyLevel &&
!$x->parenLevel
)
{
break 2;
}
// FALL THROUGH
case OP_COMMA:
// Treat comma as left-associative so reduce can fold left-heavy
// COMMA trees into a single array.
// FALL THROUGH
// A comma operator should not be parsed if we're parsing the then part
// of a conditional expression unless it's parenthesized somehow.
if ($tt == OP_COMMA && $x->hookLevel &&
!$x->bracketLevel && !$x->curlyLevel &&
!$x->parenLevel
)
break 2;
// Treat comma as left-associative so reduce can fold left-heavy
// COMMA trees into a single array.
// FALL THROUGH
case OP_OR:
case OP_AND:
case OP_BITWISE_OR:
@@ -1127,7 +1354,8 @@ class JSParser
$this->t->scanOperand = false;
break;
case TOKEN_CONDCOMMENT_MULTILINE:
case TOKEN_CONDCOMMENT_START:
case TOKEN_CONDCOMMENT_END:
if ($this->t->scanOperand)
array_push($operators, new JSNode($this->t));
else
@@ -1312,7 +1540,7 @@ class JSParser
}
if ($x->hookLevel != $hl)
throw $this->t->newSyntaxError('Missing : after ?');
throw $this->t->newSyntaxError('Missing : in conditional expression');
if ($x->parenLevel != $pl)
throw $this->t->newSyntaxError('Missing ) in parenthetical');
@@ -1443,7 +1671,7 @@ class JSNode
if (($numargs = func_num_args()) > 2)
{
$args = func_get_args();;
$args = func_get_args();
for ($i = 2; $i < $numargs; $i++)
$this->addNode($args[$i]);
}
@@ -1465,6 +1693,14 @@ class JSNode
public function addNode($node)
{
if ($node !== null)
{
if ($node->start < $this->start)
$this->start = $node->start;
if ($this->end < $node->end)
$this->end = $node->end;
}
$this->treeNodes[] = $node;
}
}
@@ -1499,44 +1735,11 @@ class JSTokenizer
);
private $opTypeNames = array(
';' => 'SEMICOLON',
',' => 'COMMA',
'?' => 'HOOK',
':' => 'COLON',
'||' => 'OR',
'&&' => 'AND',
'|' => 'BITWISE_OR',
'^' => 'BITWISE_XOR',
'&' => 'BITWISE_AND',
'===' => 'STRICT_EQ',
'==' => 'EQ',
'=' => 'ASSIGN',
'!==' => 'STRICT_NE',
'!=' => 'NE',
'<<' => 'LSH',
'<=' => 'LE',
'<' => 'LT',
'>>>' => 'URSH',
'>>' => 'RSH',
'>=' => 'GE',
'>' => 'GT',
'++' => 'INCREMENT',
'--' => 'DECREMENT',
'+' => 'PLUS',
'-' => 'MINUS',
'*' => 'MUL',
'/' => 'DIV',
'%' => 'MOD',
'!' => 'NOT',
'~' => 'BITWISE_NOT',
'.' => 'DOT',
'[' => 'LEFT_BRACKET',
']' => 'RIGHT_BRACKET',
'{' => 'LEFT_CURLY',
'}' => 'RIGHT_CURLY',
'(' => 'LEFT_PAREN',
')' => 'RIGHT_PAREN',
'@*/' => 'CONDCOMMENT_END'
';', ',', '?', ':', '||', '&&', '|', '^',
'&', '===', '==', '=', '!==', '!=', '<<', '<=',
'<', '>>>', '>>', '>=', '>', '++', '--', '+',
'-', '*', '/', '%', '!', '~', '.', '[',
']', '{', '}', '(', ')', '@*/'
);
private $assignOps = array('|', '^', '&', '<<', '>>', '>>>', '+', '-', '*', '/', '%');
@@ -1544,17 +1747,7 @@ class JSTokenizer
public function __construct()
{
$this->opRegExp = '#^(' . implode('|', array_map('preg_quote', array_keys($this->opTypeNames))) . ')#';
// this is quite a hidden yet convenient place to create the defines for operators and keywords
foreach ($this->opTypeNames as $operand => $name)
define('OP_' . $name, $operand);
define('OP_UNARY_PLUS', 'U+');
define('OP_UNARY_MINUS', 'U-');
foreach ($this->keywords as $keyword)
define('KEYWORD_' . strtoupper($keyword), $keyword);
$this->opRegExp = '#^(' . implode('|', array_map('preg_quote', $this->opTypeNames)) . ')#';
}
public function init($source, $filename = '', $lineno = 1)
@@ -1668,7 +1861,7 @@ class JSTokenizer
}
// Comments
if (!preg_match('/^\/(?:\*(@(?:cc_on|if|elif|else|end))?(?:.|\n)*?\*\/|\/.*)/', $input, $match))
if (!preg_match('/^\/(?:\*(@(?:cc_on|if|elif|else|end))?.*?\*\/|\/[^\n]*)/s', $input, $match))
{
if (!$chunksize)
break;
@@ -1681,7 +1874,7 @@ class JSTokenizer
// check if this is a conditional (JScript) comment
if (!empty($match[1]))
{
//$match[0] = '/*' . $match[1];
$match[0] = '/*' . $match[1];
$conditional_comment = true;
break;
}
@@ -1699,28 +1892,44 @@ class JSTokenizer
}
elseif ($conditional_comment)
{
$tt = TOKEN_CONDCOMMENT_MULTILINE;
$tt = TOKEN_CONDCOMMENT_START;
}
else
{
switch ($input[0])
{
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
if (preg_match('/^\d+\.\d*(?:[eE][-+]?\d+)?|^\d+(?:\.\d*)?[eE][-+]?\d+/', $input, $match))
case '0':
// hexadecimal
if (($input[1] == 'x' || $input[1] == 'X') && preg_match('/^0x[0-9a-f]+/i', $input, $match))
{
$tt = TOKEN_NUMBER;
break;
}
elseif (preg_match('/^0[xX][\da-fA-F]+|^0[0-7]*|^\d+/', $input, $match))
// FALL THROUGH
case '1': case '2': case '3': case '4': case '5':
case '6': case '7': case '8': case '9':
// should always match
preg_match('/^\d+(?:\.\d*)?(?:[eE][-+]?\d+)?/', $input, $match);
$tt = TOKEN_NUMBER;
break;
case "'":
if (preg_match('/^\'(?:[^\\\\\'\r\n]++|\\\\(?:.|\r?\n))*\'/', $input, $match))
{
// this should always match because of \d+
$tt = TOKEN_NUMBER;
$tt = TOKEN_STRING;
}
else
{
if ($chunksize)
return $this->get(null); // retry with a full chunk fetch
throw $this->newSyntaxError('Unterminated string literal');
}
break;
case '"':
case "'":
if (preg_match('/^"(?:\\\\(?:.|\r?\n)|[^\\\\"\r\n])*"|^\'(?:\\\\(?:.|\r?\n)|[^\\\\\'\r\n])*\'/', $input, $match))
if (preg_match('/^"(?:[^\\\\"\r\n]++|\\\\(?:.|\r?\n))*"/', $input, $match))
{
$tt = TOKEN_STRING;
}
@@ -1739,7 +1948,7 @@ class JSTokenizer
$tt = TOKEN_REGEXP;
break;
}
// fall through
// FALL THROUGH
case '|':
case '^':
@@ -1780,7 +1989,7 @@ class JSTokenizer
$tt = TOKEN_NUMBER;
break;
}
// fall through
// FALL THROUGH
case ';':
case ',':
@@ -1799,7 +2008,14 @@ class JSTokenizer
break;
case '@':
throw $this->newSyntaxError('Illegal token');
// check end of conditional comment
if (substr($input, 0, 3) == '@*/')
{
$match = array('@*/');
$tt = TOKEN_CONDCOMMENT_END;
}
else
throw $this->newSyntaxError('Illegal token');
break;
case "\n":
@@ -1868,5 +2084,3 @@ class JSToken
public $lineno;
public $assignOp;
}
?>
+150 -65
View File
@@ -29,12 +29,13 @@ require_once 'Minify/Source.php';
*/
class Minify {
const VERSION = '2.1.3';
const VERSION = '2.1.5';
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?
@@ -58,7 +59,14 @@ class Minify {
* @var string $importWarning
*/
public static $importWarning = "/* See http://code.google.com/p/minify/wiki/CommonProblems#@imports_can_appear_in_invalid_locations_in_combined_CSS_files */\n";
/**
* Has the DOCUMENT_ROOT been set in user code?
*
* @var bool
*/
public static $isDocRootSet = false;
/**
* Specify a cache object (with identical interface as Minify_Cache_File) or
* a path to use with Minify_Cache_File.
@@ -148,8 +156,8 @@ class Minify {
*
* Any controller options are documented in that controller's setupSources() method.
*
* @param mixed instance of subclass of Minify_Controller_Base or string name of
* controller. E.g. 'Files'
* @param mixed $controller instance of subclass of Minify_Controller_Base or string
* name of controller. E.g. 'Files'
*
* @param array $options controller/serve options
*
@@ -159,6 +167,10 @@ class Minify {
*/
public static function serve($controller, $options = array())
{
if (! self::$isDocRootSet && 0 === stripos(PHP_OS, 'win')) {
self::setDocRoot();
}
if (is_string($controller)) {
// make $controller into object
$class = 'Minify_Controller_' . $controller;
@@ -167,6 +179,7 @@ class Minify {
. str_replace('_', '/', $controller) . ".php";
}
$controller = new $class();
/* @var Minify_Controller_Base $controller */
}
// set up controller sources and mix remaining options with
@@ -179,9 +192,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 +213,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 +224,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 +239,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) {
@@ -249,8 +264,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 +275,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 +290,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 +307,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 +325,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';
}
@@ -356,52 +389,69 @@ class Minify {
}
/**
* On IIS, create $_SERVER['DOCUMENT_ROOT']
* Set $_SERVER['DOCUMENT_ROOT']. On IIS, the value is created from SCRIPT_FILENAME and SCRIPT_NAME.
*
* @param bool $unsetPathInfo (default false) if true, $_SERVER['PATH_INFO']
* will be unset (it is inconsistent with Apache's setting)
*
* @return null
* @param string $docRoot value to use for DOCUMENT_ROOT
*/
public static function setDocRoot($unsetPathInfo = false)
public static function setDocRoot($docRoot = '')
{
if (isset($_SERVER['SERVER_SOFTWARE'])
&& 0 === strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS/')
) {
$_SERVER['DOCUMENT_ROOT'] = rtrim(substr(
$_SERVER['PATH_TRANSLATED']
self::$isDocRootSet = true;
if ($docRoot) {
$_SERVER['DOCUMENT_ROOT'] = $docRoot;
} elseif (isset($_SERVER['SERVER_SOFTWARE'])
&& 0 === strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS/')) {
$_SERVER['DOCUMENT_ROOT'] = substr(
$_SERVER['SCRIPT_FILENAME']
,0
,strlen($_SERVER['PATH_TRANSLATED']) - strlen($_SERVER['SCRIPT_NAME'])
), '\\');
if ($unsetPathInfo) {
unset($_SERVER['PATH_INFO']);
}
require_once 'Minify/Logger.php';
Minify_Logger::log("setDocRoot() set DOCUMENT_ROOT to \"{$_SERVER['DOCUMENT_ROOT']}\"");
,strlen($_SERVER['SCRIPT_FILENAME']) - strlen($_SERVER['SCRIPT_NAME']));
$_SERVER['DOCUMENT_ROOT'] = rtrim($_SERVER['DOCUMENT_ROOT'], '\\');
}
}
/**
* @var mixed Minify_Cache_* object or null (i.e. no server cache is used)
* Any Minify_Cache_* object or null (i.e. no server cache is used)
*
* @var Minify_Cache_File
*/
private static $_cache = null;
/**
* @var Minify_Controller active controller for current request
* Active controller for current request
*
* @var Minify_Controller_Base
*/
protected static $_controller = null;
/**
* @var array options for current request
* Options for current request
*
* @var array
*/
protected static $_options = null;
/**
* @param string $header
*
* @param string $url
*/
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
*
* @param array $sources Minify_Source instances
*
* @return null
*/
protected static function _setupDebug($sources)
{
@@ -439,21 +489,22 @@ class Minify {
$defaultMinifier = isset(self::$_options['minifiers'][$type])
? 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) {
$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) {
// process groups of sources with identical minifiers/options
$content = array();
$i = 0;
$l = count(self::$_controller->sources);
$groupToProcessTogether = array();
$lastMinifier = null;
$lastOptions = null;
do {
// get next source
$source = null;
if ($i < $l) {
$source = self::$_controller->sources[$i];
/* @var Minify_Source $source */
$sourceContent = $source->getContent();
// allow the source to override our minifier and options
$minifier = (null !== $source->minifier)
? $source->minifier
@@ -461,16 +512,40 @@ class Minify {
$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);
}
// do we need to process our group right now?
if ($i > 0 // yes, we have at least the first group populated
&& (
! $source // yes, we ran out of sources
|| $type === self::TYPE_CSS // yes, to process CSS individually (avoiding PCRE bugs/limits)
|| $minifier !== $lastMinifier // yes, minifier changed
|| $options !== $lastOptions) // yes, options changed
)
{
// minify previous sources with last settings
$imploded = implode($implodeSeparator, $groupToProcessTogether);
$groupToProcessTogether = array();
if ($lastMinifier) {
self::$_controller->loadMinifier($lastMinifier);
try {
$content[] = call_user_func($lastMinifier, $imploded, $lastOptions);
} catch (Exception $e) {
throw new Exception("Exception in minifier: " . $e->getMessage());
}
} else {
$pieces[] = $source->getContent();
$content[] = $imploded;
}
}
$content = implode($implodeSeparator, $pieces);
}
// add content to the group
if ($source) {
$groupToProcessTogether[] = $sourceContent;
$lastMinifier = $minifier;
$lastOptions = $options;
}
$i++;
} while ($source);
$content = implode($implodeSeparator, $content);
if ($type === self::TYPE_CSS && false !== strpos($content, '@import')) {
$content = self::_handleCssImports($content);
@@ -491,22 +566,32 @@ 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']
,self::VERSION
)));
return "{$prefix}_{$name}_{$md5}";
}
/**
* Bubble CSS @imports to the top or prepend a warning if an
* @import is detected not at the top.
* Bubble CSS @imports to the top or prepend a warning if an import is detected not at the top.
*
* @param string $css
*
* @return string
*/
protected static function _handleCssImports($css)
{
+99 -83
View File
@@ -1,83 +1,99 @@
<?php
/**
* Class Minify_CSS
* @package Minify
*/
/**
* Minify CSS
*
* This class uses Minify_CSS_Compressor and Minify_CSS_UriRewriter to
* minify CSS and rewrite relative URIs.
*
* @package Minify
* @author Stephen Clay <[email protected]>
* @author http://code.google.com/u/1stvamp/ (Issue 64 patch)
*/
class Minify_CSS {
/**
* Minify a CSS string
*
* @param string $css
*
* @param array $options available options:
*
* 'preserveComments': (default true) multi-line comments that begin
* with "/*!" will be preserved with newlines before and after to
* enhance readability.
*
* 'prependRelativePath': (default null) if given, this string will be
* prepended to all relative URIs in import/url declarations
*
* '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. 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
* </code>
*
* @return string
*/
public static function minify($css, $options = array())
{
require_once 'Minify/CSS/Compressor.php';
if (isset($options['preserveComments'])
&& !$options['preserveComments']) {
$css = Minify_CSS_Compressor::process($css, $options);
} else {
require_once 'Minify/CommentPreserver.php';
$css = Minify_CommentPreserver::process(
$css
,array('Minify_CSS_Compressor', 'process')
,array($options)
);
}
if (! isset($options['currentDir']) && ! isset($options['prependRelativePath'])) {
return $css;
}
require_once 'Minify/CSS/UriRewriter.php';
if (isset($options['currentDir'])) {
return Minify_CSS_UriRewriter::rewrite(
$css
,$options['currentDir']
,isset($options['docRoot']) ? $options['docRoot'] : $_SERVER['DOCUMENT_ROOT']
,isset($options['symlinks']) ? $options['symlinks'] : array()
);
} else {
return Minify_CSS_UriRewriter::prepend(
$css
,$options['prependRelativePath']
);
}
}
}
<?php
/**
* Class Minify_CSS
* @package Minify
*/
/**
* Minify CSS
*
* This class uses Minify_CSS_Compressor and Minify_CSS_UriRewriter to
* minify CSS and rewrite relative URIs.
*
* @package Minify
* @author Stephen Clay <[email protected]>
* @author http://code.google.com/u/1stvamp/ (Issue 64 patch)
*/
class Minify_CSS {
/**
* Minify a CSS string
*
* @param string $css
*
* @param array $options available options:
*
* '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
*
* '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. 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
* </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 (! $options['preserveComments']) {
$css = Minify_CSS_Compressor::process($css, $options);
} else {
require_once 'Minify/CommentPreserver.php';
$css = Minify_CommentPreserver::process(
$css
,array('Minify_CSS_Compressor', 'process')
,array($options)
);
}
if (! $options['currentDir'] && ! $options['prependRelativePath']) {
return $css;
}
require_once 'Minify/CSS/UriRewriter.php';
if ($options['currentDir']) {
return Minify_CSS_UriRewriter::rewrite(
$css
,$options['currentDir']
,$options['docRoot']
,$options['symlinks']
);
} else {
return Minify_CSS_UriRewriter::prepend(
$css
,$options['prependRelativePath']
);
}
}
}
+249 -251
View File
@@ -1,251 +1,249 @@
<?php
/**
* Class Minify_CSS_Compressor
* @package Minify
*/
/**
* Compress CSS
*
* This is a heavy regex-based removal of whitespace, unnecessary
* comments and tokens, and some CSS value minimization, where practical.
* Many steps have been taken to avoid breaking comment-based hacks,
* including the ie5/mac filter (and its inversion), but expect tricky
* hacks involving comment tokens in 'content' value strings to break
* minimization badly. A test suite is available.
*
* @package Minify
* @author Stephen Clay <[email protected]>
* @author http://code.google.com/u/1stvamp/ (Issue 64 patch)
*/
class Minify_CSS_Compressor {
/**
* Minify a CSS string
*
* @param string $css
*
* @param array $options (currently ignored)
*
* @return string
*/
public static function process($css, $options = array())
{
$obj = new Minify_CSS_Compressor($options);
return $obj->_process($css);
}
/**
* @var array options
*/
protected $_options = null;
/**
* @var bool Are we "in" a hack?
*
* I.e. are some browsers targetted until the next comment?
*/
protected $_inHack = false;
/**
* Constructor
*
* @param array $options (currently ignored)
*
* @return null
*/
private function __construct($options) {
$this->_options = $options;
}
/**
* Minify a CSS string
*
* @param string $css
*
* @return string
*/
protected function _process($css)
{
$css = str_replace("\r\n", "\n", $css);
// preserve empty comment after '>'
// http://www.webdevout.net/css-hacks#in_css-selectors
$css = preg_replace('@>/\\*\\s*\\*/@', '>/*keep*/', $css);
// preserve empty comment between property and value
// http://css-discuss.incutio.com/?page=BoxModelHack
$css = preg_replace('@/\\*\\s*\\*/\\s*:@', '/*keep*/:', $css);
$css = preg_replace('@:\\s*/\\*\\s*\\*/@', ':/*keep*/', $css);
// apply callback to all valid comments (and strip out surrounding ws
$css = preg_replace_callback('@\\s*/\\*([\\s\\S]*?)\\*/\\s*@'
,array($this, '_commentCB'), $css);
// remove ws around { } and last semicolon in declaration block
$css = preg_replace('/\\s*{\\s*/', '{', $css);
$css = preg_replace('/;?\\s*}\\s*/', '}', $css);
// remove ws surrounding semicolons
$css = preg_replace('/\\s*;\\s*/', ';', $css);
// remove ws around urls
$css = preg_replace('/
url\\( # url(
\\s*
([^\\)]+?) # 1 = the URL (really just a bunch of non right parenthesis)
\\s*
\\) # )
/x', 'url($1)', $css);
// remove ws between rules and colons
$css = preg_replace('/
\\s*
([{;]) # 1 = beginning of block or rule separator
\\s*
([\\*_]?[\\w\\-]+) # 2 = property (and maybe IE filter)
\\s*
:
\\s*
(\\b|[#\'"]) # 3 = first character of a value
/x', '$1$2:$3', $css);
// remove ws in selectors
$css = preg_replace_callback('/
(?: # non-capture
\\s*
[^~>+,\\s]+ # selector part
\\s*
[,>+~] # combinators
)+
\\s*
[^~>+,\\s]+ # selector part
{ # open declaration block
/x'
,array($this, '_selectorsCB'), $css);
// minimize hex colors
$css = preg_replace('/([^=])#([a-f\\d])\\2([a-f\\d])\\3([a-f\\d])\\4([\\s;\\}])/i'
, '$1#$2$3$4$5', $css);
// remove spaces between font families
$css = preg_replace_callback('/font-family:([^;}]+)([;}])/'
,array($this, '_fontFamilyCB'), $css);
$css = preg_replace('/@import\\s+url/', '@import url', $css);
// replace any ws involving newlines with a single newline
$css = preg_replace('/[ \\t]*\\n+\\s*/', "\n", $css);
// separate common descendent selectors w/ newlines (to limit line lengths)
$css = preg_replace('/([\\w#\\.\\*]+)\\s+([\\w#\\.\\*]+){/', "$1\n$2{", $css);
// Use newline after 1st numeric value (to limit line lengths).
$css = preg_replace('/
((?:padding|margin|border|outline):\\d+(?:px|em)?) # 1 = prop : 1st numeric value
\\s+
/x'
,"$1\n", $css);
// prevent triggering IE6 bug: http://www.crankygeek.com/ie6pebug/
$css = preg_replace('/:first-l(etter|ine)\\{/', ':first-l$1 {', $css);
return trim($css);
}
/**
* Replace what looks like a set of selectors
*
* @param array $m regex matches
*
* @return string
*/
protected function _selectorsCB($m)
{
// remove ws around the combinators
return preg_replace('/\\s*([,>+~])\\s*/', '$1', $m[0]);
}
/**
* Process a comment and return a replacement
*
* @param array $m regex matches
*
* @return string
*/
protected function _commentCB($m)
{
$hasSurroundingWs = (trim($m[0]) !== $m[1]);
$m = $m[1];
// $m is the comment content w/o the surrounding tokens,
// but the return value will replace the entire comment.
if ($m === 'keep') {
return '/**/';
}
if ($m === '" "') {
// component of http://tantek.com/CSS/Examples/midpass.html
return '/*" "*/';
}
if (preg_match('@";\\}\\s*\\}/\\*\\s+@', $m)) {
// component of http://tantek.com/CSS/Examples/midpass.html
return '/*";}}/* */';
}
if ($this->_inHack) {
// inversion: feeding only to one browser
if (preg_match('@
^/ # comment started like /*/
\\s*
(\\S[\\s\\S]+?) # has at least some non-ws content
\\s*
/\\* # ends like /*/ or /**/
@x', $m, $n)) {
// end hack mode after this comment, but preserve the hack and comment content
$this->_inHack = false;
return "/*/{$n[1]}/**/";
}
}
if (substr($m, -1) === '\\') { // comment ends like \*/
// begin hack mode and preserve hack
$this->_inHack = true;
return '/*\\*/';
}
if ($m !== '' && $m[0] === '/') { // comment looks like /*/ foo */
// begin hack mode and preserve hack
$this->_inHack = true;
return '/*/*/';
}
if ($this->_inHack) {
// a regular comment ends hack mode but should be preserved
$this->_inHack = false;
return '/**/';
}
// Issue 107: if there's any surrounding whitespace, it may be important, so
// replace the comment with a single space
return $hasSurroundingWs // remove all other comments
? ' '
: '';
}
/**
* Process a font-family listing and return a replacement
*
* @param array $m regex matches
*
* @return string
*/
protected function _fontFamilyCB($m)
{
// 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];
}
}
<?php
/**
* Class Minify_CSS_Compressor
* @package Minify
*/
/**
* Compress CSS
*
* This is a heavy regex-based removal of whitespace, unnecessary
* comments and tokens, and some CSS value minimization, where practical.
* Many steps have been taken to avoid breaking comment-based hacks,
* including the ie5/mac filter (and its inversion), but expect tricky
* hacks involving comment tokens in 'content' value strings to break
* minimization badly. A test suite is available.
*
* @package Minify
* @author Stephen Clay <[email protected]>
* @author http://code.google.com/u/1stvamp/ (Issue 64 patch)
*/
class Minify_CSS_Compressor {
/**
* Minify a CSS string
*
* @param string $css
*
* @param array $options (currently ignored)
*
* @return string
*/
public static function process($css, $options = array())
{
$obj = new Minify_CSS_Compressor($options);
return $obj->_process($css);
}
/**
* @var array
*/
protected $_options = null;
/**
* Are we "in" a hack? I.e. are some browsers targetted until the next comment?
*
* @var bool
*/
protected $_inHack = false;
/**
* Constructor
*
* @param array $options (currently ignored)
*/
private function __construct($options) {
$this->_options = $options;
}
/**
* Minify a CSS string
*
* @param string $css
*
* @return string
*/
protected function _process($css)
{
$css = str_replace("\r\n", "\n", $css);
// preserve empty comment after '>'
// http://www.webdevout.net/css-hacks#in_css-selectors
$css = preg_replace('@>/\\*\\s*\\*/@', '>/*keep*/', $css);
// preserve empty comment between property and value
// http://css-discuss.incutio.com/?page=BoxModelHack
$css = preg_replace('@/\\*\\s*\\*/\\s*:@', '/*keep*/:', $css);
$css = preg_replace('@:\\s*/\\*\\s*\\*/@', ':/*keep*/', $css);
// apply callback to all valid comments (and strip out surrounding ws
$css = preg_replace_callback('@\\s*/\\*([\\s\\S]*?)\\*/\\s*@'
,array($this, '_commentCB'), $css);
// remove ws around { } and last semicolon in declaration block
$css = preg_replace('/\\s*{\\s*/', '{', $css);
$css = preg_replace('/;?\\s*}\\s*/', '}', $css);
// remove ws surrounding semicolons
$css = preg_replace('/\\s*;\\s*/', ';', $css);
// remove ws around urls
$css = preg_replace('/
url\\( # url(
\\s*
([^\\)]+?) # 1 = the URL (really just a bunch of non right parenthesis)
\\s*
\\) # )
/x', 'url($1)', $css);
// remove ws between rules and colons
$css = preg_replace('/
\\s*
([{;]) # 1 = beginning of block or rule separator
\\s*
([\\*_]?[\\w\\-]+) # 2 = property (and maybe IE filter)
\\s*
:
\\s*
(\\b|[#\'"-]) # 3 = first character of a value
/x', '$1$2:$3', $css);
// remove ws in selectors
$css = preg_replace_callback('/
(?: # non-capture
\\s*
[^~>+,\\s]+ # selector part
\\s*
[,>+~] # combinators
)+
\\s*
[^~>+,\\s]+ # selector part
{ # open declaration block
/x'
,array($this, '_selectorsCB'), $css);
// minimize hex colors
$css = preg_replace('/([^=])#([a-f\\d])\\2([a-f\\d])\\3([a-f\\d])\\4([\\s;\\}])/i'
, '$1#$2$3$4$5', $css);
// remove spaces between font families
$css = preg_replace_callback('/font-family:([^;}]+)([;}])/'
,array($this, '_fontFamilyCB'), $css);
$css = preg_replace('/@import\\s+url/', '@import url', $css);
// replace any ws involving newlines with a single newline
$css = preg_replace('/[ \\t]*\\n+\\s*/', "\n", $css);
// separate common descendent selectors w/ newlines (to limit line lengths)
$css = preg_replace('/([\\w#\\.\\*]+)\\s+([\\w#\\.\\*]+){/', "$1\n$2{", $css);
// Use newline after 1st numeric value (to limit line lengths).
$css = preg_replace('/
((?:padding|margin|border|outline):\\d+(?:px|em)?) # 1 = prop : 1st numeric value
\\s+
/x'
,"$1\n", $css);
// prevent triggering IE6 bug: http://www.crankygeek.com/ie6pebug/
$css = preg_replace('/:first-l(etter|ine)\\{/', ':first-l$1 {', $css);
return trim($css);
}
/**
* Replace what looks like a set of selectors
*
* @param array $m regex matches
*
* @return string
*/
protected function _selectorsCB($m)
{
// remove ws around the combinators
return preg_replace('/\\s*([,>+~])\\s*/', '$1', $m[0]);
}
/**
* Process a comment and return a replacement
*
* @param array $m regex matches
*
* @return string
*/
protected function _commentCB($m)
{
$hasSurroundingWs = (trim($m[0]) !== $m[1]);
$m = $m[1];
// $m is the comment content w/o the surrounding tokens,
// but the return value will replace the entire comment.
if ($m === 'keep') {
return '/**/';
}
if ($m === '" "') {
// component of http://tantek.com/CSS/Examples/midpass.html
return '/*" "*/';
}
if (preg_match('@";\\}\\s*\\}/\\*\\s+@', $m)) {
// component of http://tantek.com/CSS/Examples/midpass.html
return '/*";}}/* */';
}
if ($this->_inHack) {
// inversion: feeding only to one browser
if (preg_match('@
^/ # comment started like /*/
\\s*
(\\S[\\s\\S]+?) # has at least some non-ws content
\\s*
/\\* # ends like /*/ or /**/
@x', $m, $n)) {
// end hack mode after this comment, but preserve the hack and comment content
$this->_inHack = false;
return "/*/{$n[1]}/**/";
}
}
if (substr($m, -1) === '\\') { // comment ends like \*/
// begin hack mode and preserve hack
$this->_inHack = true;
return '/*\\*/';
}
if ($m !== '' && $m[0] === '/') { // comment looks like /*/ foo */
// begin hack mode and preserve hack
$this->_inHack = true;
return '/*/*/';
}
if ($this->_inHack) {
// a regular comment ends hack mode but should be preserved
$this->_inHack = false;
return '/**/';
}
// Issue 107: if there's any surrounding whitespace, it may be important, so
// replace the comment with a single space
return $hasSurroundingWs // remove all other comments
? ' '
: '';
}
/**
* Process a font-family listing and return a replacement
*
* @param array $m regex matches
*
* @return string
*/
protected function _fontFamilyCB($m)
{
// 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];
}
}
+123 -83
View File
@@ -12,21 +12,15 @@
*/
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
*/
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 +77,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 +101,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(
@@ -236,21 +165,39 @@ class Minify_CSS_UriRewriter {
self::$debugText .= "docroot stripped : {$path}\n";
// fix to root-relative URI
$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 +214,97 @@ class Minify_CSS_UriRewriter {
}
return rtrim($path, '/\\');
}
/**
* Directory of this stylesheet
*
* @var string
*/
private static $_currentDir = '';
/**
* DOC_ROOT
*
* @var string
*/
private static $_docRoot = '';
/**
* directory replacements to map symlink targets back to their
* source (within the document root) E.g. '/var/www/symlink' => '/var/realpath'
*
* @var array
*/
private static $_symlinks = array();
/**
* Path to prepend
*
* @var string
*/
private static $_prependPath = null;
/**
* @param string $css
*
* @return string
*/
private static function _trimUrls($css)
{
return preg_replace('/
url\\( # url(
\\s*
([^\\)]+?) # 1 = URI (assuming does not contain ")")
\\s*
\\) # )
/x', 'url($1)', $css);
}
/**
* @param array $m
*
* @return string
*/
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})";
}
}
+6 -3
View File
@@ -54,9 +54,12 @@ class Minify_Cache_APC {
*/
public function getSize($id)
{
return $this->_fetch($id)
? strlen($this->_data)
: false;
if (! $this->_fetch($id)) {
return false;
}
return (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2))
? mb_strlen($this->_data, '8bit')
: strlen($this->_data);
}
/**
+78 -8
View File
@@ -9,13 +9,12 @@ class Minify_Cache_File {
public function __construct($path = '', $fileLocking = false)
{
if (! $path) {
require_once 'Solar/Dir.php';
$path = rtrim(Solar_Dir::tmp(), DIRECTORY_SEPARATOR);
$path = self::tmp();
}
$this->_locking = $fileLocking;
$this->_path = $path;
}
/**
* Write data to cache.
*
@@ -30,15 +29,14 @@ class Minify_Cache_File {
$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;
$file = $this->_path . '/' . $id;
if (! @file_put_contents($file, $data, $flag)) {
$this->_log("Minify_Cache_File: Write failed to '$file'");
}
// write control
if ($data !== $this->fetch($id)) {
@unlink($file);
$this->_log("Minify_Cache_File: Post-write read failed for '$file'");
return false;
}
return true;
@@ -119,6 +117,78 @@ class Minify_Cache_File {
{
return $this->_path;
}
/**
* Get a usable temp directory
*
* Adapted from Solar/Dir.php
* @author Paul M. Jones <[email protected]>
* @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 <[email protected]>
* @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;
+6 -3
View File
@@ -60,9 +60,12 @@ class Minify_Cache_Memcache {
*/
public function getSize($id)
{
return $this->_fetch($id)
? strlen($this->_data)
: false;
if (! $this->_fetch($id)) {
return false;
}
return (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2))
? mb_strlen($this->_data, '8bit')
: strlen($this->_data);
}
/**
@@ -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;
}
}
+3 -4
View File
@@ -30,8 +30,7 @@ class Minify_CommentPreserver {
* Process a string outside of C-style comments that begin with "/*!"
*
* On each non-empty string outside these comments, the given processor
* function will be called. The first "!" will be removed from the
* preserved comments, and the comments will be surrounded by
* function will be called. The comments will be surrounded by
* Minify_CommentPreserver::$preprend and Minify_CommentPreserver::$append.
*
* @param string $content
@@ -65,7 +64,7 @@ class Minify_CommentPreserver {
* @param string $in input
*
* @return array 3 elements are returned. If a YUI comment is found, the
* 2nd element is the comment and the 1st and 2nd are the surrounding
* 2nd element is the comment and the 1st and 3rd are the surrounding
* strings. If no comment is found, the entire string is returned as the
* 1st element and the other two are false.
*/
@@ -79,7 +78,7 @@ class Minify_CommentPreserver {
}
$ret = array(
substr($in, 0, $start)
,self::$prepend . '/*' . substr($in, $start + 3, $end - $start - 1) . self::$append
,self::$prepend . '/*!' . substr($in, $start + 3, $end - $start - 1) . self::$append
);
$endChars = (strlen($in) - $end - 2);
$ret[] = (0 === $endChars)
+54 -6
View File
@@ -27,7 +27,7 @@ abstract class Minify_Controller_Base {
*
* @param array $options controller and Minify options
*
* return array $options Minify::serve options
* @return array $options Minify::serve options
*/
abstract public function setupSources($options);
@@ -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,15 +137,58 @@ abstract class Minify_Controller_Base {
list($revExt) = explode('.', strrev($base));
return in_array(strrev($revExt), array('js', 'css', 'html', 'txt'));
}
/**
* @var array instances of Minify_Source, which provide content and
* any individual minification needs.
* @param string $file
* @param array $allowDirs
* @param string $uri
* @return bool
* @throws Exception
*/
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) . "';");
}
/**
* @param string $file
* @throws Exception
*/
public static function checkNotHidden($file)
{
$b = basename($file);
if (0 === strpos($b, '.')) {
throw new Exception("Filename '$b' starts with period (may be hidden)");
}
}
/**
* instances of Minify_Source, which provide content and any individual minification needs.
*
* @var array
*
* @see Minify_Source
*/
public $sources = array();
/**
* Short name to place inside cache id
*
* 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
*/
public $selectionId = '';
/**
* Mix in default controller options with user-given options
*
@@ -192,10 +238,12 @@ abstract class Minify_Controller_Base {
/**
* Send message to the Minify logger
*
* @param string $msg
*
* @return null
*/
protected function log($msg) {
public function log($msg) {
require_once 'Minify/Logger.php';
Minify_Logger::log($msg);
}
+4 -5
View File
@@ -34,12 +34,11 @@ class Minify_Controller_Groups extends Minify_Controller_Base {
* Set up groups of files as sources
*
* @param array $options controller and Minify options
* @return array Minify options
*
* Controller options:
*
*
* 'groups': (required) array mapping PATH_INFO strings to arrays
* of complete file paths. @see Minify_Controller_Groups
* of complete file paths. @see Minify_Controller_Groups
*
* @return array Minify options
*/
public function setupSources($options) {
// strip controller options
+148 -49
View File
@@ -18,8 +18,8 @@ class Minify_Controller_MinApp extends Minify_Controller_Base {
* Set up groups of files as sources
*
* @param array $options controller and Minify options
*
* @return array Minify options
*
*/
public function setupSources($options) {
// filter controller options
@@ -28,64 +28,94 @@ 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 = '';
$firstMissingResource = null;
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)");
$keys = explode(',', $_GET['g']);
foreach ($keys 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)");
if (null === $firstMissingResource) {
$firstMissingResource = basename($file);
continue;
} else {
$secondMissingResource = basename($file);
$this->log("More than one file was missing: '$firstMissingResource', '$secondMissingResource'");
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 +126,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 +136,96 @@ 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 (null === $firstMissingResource) {
$firstMissingResource = $uri;
continue;
} else {
$secondMissingResource = $uri;
$this->log("More than one file was missing: '$firstMissingResource', '$secondMissingResource`'");
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 (null !== $firstMissingResource) {
array_unshift($sources, new Minify_Source(array(
'id' => 'missingFile'
// should not cause cache invalidation
,'lastModified' => 0
// due to caching, filename is unreliable.
,'content' => "/* Minify: at least one missing file. See " . Minify::URL_DEBUG . " */\n"
,'minifier' => ''
)));
}
$this->sources = $sources;
} else {
$this->log("No sources to serve");
}
return $options;
}
/**
* @param string $file
*
* @param array $cOptions
*
* @return Minify_Source
*/
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
*
* @param string $sourceOrExt
*
* @throws Exception
*/
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');
+26
View File
@@ -0,0 +1,26 @@
<?php
/**
* Detect whether request should be debugged
*
* @package Minify
* @author Stephen Clay <[email protected]>
*/
class Minify_DebugDetector {
public static function shouldDebugRequest($cookie, $get, $requestUri)
{
if (isset($get['debug'])) {
return true;
}
if (! empty($cookie['minifyDebug'])) {
foreach (preg_split('/\\s+/', $cookie['minifyDebug']) as $debugUri) {
$pattern = '@' . preg_quote($debugUri, '@') . '@i';
$pattern = str_replace(array('\\*', '\\?'), array('.*', '.'), $pattern);
if (preg_match($pattern, $requestUri)) {
return true;
}
}
}
return false;
}
}
+246 -245
View File
@@ -1,245 +1,246 @@
<?php
/**
* Class Minify_HTML
* @package Minify
*/
/**
* Compress HTML
*
* This is a heavy regex-based removal of whitespace, unnecessary comments and
* tokens. IE conditional comments are preserved. There are also options to have
* STYLE and SCRIPT blocks compressed by callback functions.
*
* A test suite is available.
*
* @package Minify
* @author Stephen Clay <[email protected]>
*/
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 <[email protected]>
*/
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
View File
@@ -0,0 +1,193 @@
<?php
/**
* Class Minify_HTML_Helper
* @package Minify
*/
/**
* Helpers for writing Minfy URIs into HTML
*
* @package Minify
* @author Stephen Clay <[email protected]>
*/
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;
}
}
+216 -157
View File
@@ -1,157 +1,216 @@
<?php
/**
* Class Minify_ImportProcessor
* @package Minify
*/
/**
* Linearize a CSS/JS file by including content specified by CSS import
* declarations. In CSS files, relative URIs are fixed.
*
* @imports will be processed regardless of where they appear in the source
* files; i.e. @imports commented out or in string content will still be
* processed!
*
* This has a unit test but should be considered "experimental".
*
* @package Minify
* @author Stephen Clay <[email protected]>
*/
class Minify_ImportProcessor {
public static $filesIncluded = array();
public static function process($file)
{
self::$filesIncluded = array();
self::$_isCss = (strtolower(substr($file, -4)) === '.css');
$obj = new Minify_ImportProcessor(dirname($file));
return $obj->_getContent($file);
}
// allows callback funcs to know the current directory
private $_currentDir = null;
// allows _importCB to write the fetched content back to the obj
private $_importedContent = '';
private static $_isCss = null;
private function __construct($currentDir)
{
$this->_currentDir = $currentDir;
}
private function _getContent($file)
{
$file = realpath($file);
if (! $file
|| in_array($file, self::$filesIncluded)
|| false === ($content = @file_get_contents($file))
) {
// file missing, already included, or failed read
return '';
}
self::$filesIncluded[] = realpath($file);
$this->_currentDir = dirname($file);
// remove UTF-8 BOM if present
if (pack("CCC",0xef,0xbb,0xbf) === substr($content, 0, 3)) {
$content = substr($content, 3);
}
// ensure uniform EOLs
$content = str_replace("\r\n", "\n", $content);
// process @imports
$content = preg_replace_callback(
'/
@import\\s+
(?:url\\(\\s*)? # maybe url(
[\'"]? # maybe quote
(.*?) # 1 = URI
[\'"]? # maybe end quote
(?:\\s*\\))? # maybe )
([a-zA-Z,\\s]*)? # 2 = media list
; # end token
/x'
,array($this, '_importCB')
,$content
);
if (self::$_isCss) {
// rewrite remaining relative URIs
$content = preg_replace_callback(
'/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
,array($this, '_urlCB')
,$content
);
}
return $this->_importedContent . $content;
}
private function _importCB($m)
{
$url = $m[1];
$mediaList = preg_replace('/\\s+/', '', $m[2]);
if (strpos($url, '://') > 0) {
// protocol, leave in place for CSS, comment for JS
return self::$_isCss
? $m[0]
: "/* Minify_ImportProcessor will not include remote content */";
}
if ('/' === $url[0]) {
// protocol-relative or root path
$url = ltrim($url, '/');
$file = realpath($_SERVER['DOCUMENT_ROOT']) . DIRECTORY_SEPARATOR
. strtr($url, '/', DIRECTORY_SEPARATOR);
} else {
// relative to current path
$file = $this->_currentDir . DIRECTORY_SEPARATOR
. strtr($url, '/', DIRECTORY_SEPARATOR);
}
$obj = new Minify_ImportProcessor(dirname($file));
$content = $obj->_getContent($file);
if ('' === $content) {
// failed. leave in place for CSS, comment for JS
return self::$_isCss
? $m[0]
: "/* Minify_ImportProcessor could not fetch '{$file}' */";;
}
return (!self::$_isCss || preg_match('@(?:^$|\\ball\\b)@', $mediaList))
? $content
: "@media {$mediaList} {\n{$content}\n}\n";
}
private function _urlCB($m)
{
// $m[1] is either quoted or not
$quote = ($m[1][0] === "'" || $m[1][0] === '"')
? $m[1][0]
: '';
$url = ($quote === '')
? $m[1]
: substr($m[1], 1, strlen($m[1]) - 2);
if ('/' !== $url[0]) {
if (strpos($url, '//') > 0) {
// probably starts with protocol, do not alter
} else {
// prepend path with current dir separator (OS-independent)
$path = $this->_currentDir
. DIRECTORY_SEPARATOR . strtr($url, '/', DIRECTORY_SEPARATOR);
// strip doc root
$path = substr($path, strlen(realpath($_SERVER['DOCUMENT_ROOT'])));
// fix to absolute URL
$url = strtr($path, '/\\', '//');
// remove /./ and /../ where possible
$url = str_replace('/./', '/', $url);
// inspired by patch from Oleg Cherniy
do {
$url = preg_replace('@/[^/]+/\\.\\./@', '/', $url, 1, $changed);
} while ($changed);
}
}
return "url({$quote}{$url}{$quote})";
}
}
<?php
/**
* Class Minify_ImportProcessor
* @package Minify
*/
/**
* Linearize a CSS/JS file by including content specified by CSS import
* declarations. In CSS files, relative URIs are fixed.
*
* @imports will be processed regardless of where they appear in the source
* files; i.e. @imports commented out or in string content will still be
* processed!
*
* This has a unit test but should be considered "experimental".
*
* @package Minify
* @author Stephen Clay <[email protected]>
* @author Simon Schick <[email protected]>
*/
class Minify_ImportProcessor {
public static $filesIncluded = array();
public static function process($file)
{
self::$filesIncluded = array();
self::$_isCss = (strtolower(substr($file, -4)) === '.css');
$obj = new Minify_ImportProcessor(dirname($file));
return $obj->_getContent($file);
}
// allows callback funcs to know the current directory
private $_currentDir = null;
// allows callback funcs to know the directory of the file that inherits this one
private $_previewsDir = null;
// allows _importCB to write the fetched content back to the obj
private $_importedContent = '';
private static $_isCss = null;
/**
* @param String $currentDir
* @param String $previewsDir Is only used internally
*/
private function __construct($currentDir, $previewsDir = "")
{
$this->_currentDir = $currentDir;
$this->_previewsDir = $previewsDir;
}
private function _getContent($file, $is_imported = false)
{
$file = realpath($file);
if (! $file
|| in_array($file, self::$filesIncluded)
|| false === ($content = @file_get_contents($file))
) {
// file missing, already included, or failed read
return '';
}
self::$filesIncluded[] = realpath($file);
$this->_currentDir = dirname($file);
// remove UTF-8 BOM if present
if (pack("CCC",0xef,0xbb,0xbf) === substr($content, 0, 3)) {
$content = substr($content, 3);
}
// ensure uniform EOLs
$content = str_replace("\r\n", "\n", $content);
// process @imports
$content = preg_replace_callback(
'/
@import\\s+
(?:url\\(\\s*)? # maybe url(
[\'"]? # maybe quote
(.*?) # 1 = URI
[\'"]? # maybe end quote
(?:\\s*\\))? # maybe )
([a-zA-Z,\\s]*)? # 2 = media list
; # end token
/x'
,array($this, '_importCB')
,$content
);
// You only need to rework the import-path if the script is imported
if (self::$_isCss && $is_imported) {
// rewrite remaining relative URIs
$content = preg_replace_callback(
'/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
,array($this, '_urlCB')
,$content
);
}
return $this->_importedContent . $content;
}
private function _importCB($m)
{
$url = $m[1];
$mediaList = preg_replace('/\\s+/', '', $m[2]);
if (strpos($url, '://') > 0) {
// protocol, leave in place for CSS, comment for JS
return self::$_isCss
? $m[0]
: "/* Minify_ImportProcessor will not include remote content */";
}
if ('/' === $url[0]) {
// protocol-relative or root path
$url = ltrim($url, '/');
$file = realpath($_SERVER['DOCUMENT_ROOT']) . DIRECTORY_SEPARATOR
. strtr($url, '/', DIRECTORY_SEPARATOR);
} else {
// relative to current path
$file = $this->_currentDir . DIRECTORY_SEPARATOR
. strtr($url, '/', DIRECTORY_SEPARATOR);
}
$obj = new Minify_ImportProcessor(dirname($file), $this->_currentDir);
$content = $obj->_getContent($file, true);
if ('' === $content) {
// failed. leave in place for CSS, comment for JS
return self::$_isCss
? $m[0]
: "/* Minify_ImportProcessor could not fetch '{$file}' */";
}
return (!self::$_isCss || preg_match('@(?:^$|\\ball\\b)@', $mediaList))
? $content
: "@media {$mediaList} {\n{$content}\n}\n";
}
private function _urlCB($m)
{
// $m[1] is either quoted or not
$quote = ($m[1][0] === "'" || $m[1][0] === '"')
? $m[1][0]
: '';
$url = ($quote === '')
? $m[1]
: substr($m[1], 1, strlen($m[1]) - 2);
if ('/' !== $url[0]) {
if (strpos($url, '//') > 0) {
// probably starts with protocol, do not alter
} else {
// prepend path with current dir separator (OS-independent)
$path = $this->_currentDir
. DIRECTORY_SEPARATOR . strtr($url, '/', DIRECTORY_SEPARATOR);
// update the relative path by the directory of the file that imported this one
$url = self::getPathDiff(realpath($this->_previewsDir), $path);
}
}
return "url({$quote}{$url}{$quote})";
}
/**
* @param string $from
* @param string $to
* @param string $ps
* @return string
*/
private function getPathDiff($from, $to, $ps = DIRECTORY_SEPARATOR)
{
$realFrom = $this->truepath($from);
$realTo = $this->truepath($to);
$arFrom = explode($ps, rtrim($realFrom, $ps));
$arTo = explode($ps, rtrim($realTo, $ps));
while (count($arFrom) && count($arTo) && ($arFrom[0] == $arTo[0]))
{
array_shift($arFrom);
array_shift($arTo);
}
return str_pad("", count($arFrom) * 3, '..' . $ps) . implode($ps, $arTo);
}
/**
* This function is to replace PHP's extremely buggy realpath().
* @param string $path The original path, can be relative etc.
* @return string The resolved path, it might not exist.
* @see http://stackoverflow.com/questions/4049856/replace-phps-realpath
*/
function truepath($path)
{
// whether $path is unix or not
$unipath = strlen($path) == 0 || $path{0} != '/';
// attempts to detect if path is relative in which case, add cwd
if (strpos($path, ':') === false && $unipath)
$path = $this->_currentDir . DIRECTORY_SEPARATOR . $path;
// resolve path parts (single dot, double dot and double delimiters)
$path = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $path);
$parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen');
$absolutes = array();
foreach ($parts as $part) {
if ('.' == $part)
continue;
if ('..' == $part) {
array_pop($absolutes);
} else {
$absolutes[] = $part;
}
}
$path = implode(DIRECTORY_SEPARATOR, $absolutes);
// resolve any symlinks
if (file_exists($path) && linkinfo($path) > 0)
$path = readlink($path);
// put initial separator that could have been lost
$path = !$unipath ? '/' . $path : $path;
return $path;
}
}
@@ -0,0 +1,133 @@
<?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 <[email protected]>
*
* @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)
{
$allowUrlFopen = preg_match('/1|yes|on|true/i', ini_get('allow_url_fopen'));
if ($allowUrlFopen) {
$contents = file_get_contents(self::URL, false, stream_context_create(array(
'http' => array(
'method' => 'POST',
'header' => 'Content-type: application/x-www-form-urlencoded',
'content' => $postBody,
'max_redirects' => 0,
'timeout' => 15,
)
)));
} elseif (defined('CURLOPT_POST')) {
$ch = curl_init(self::URL);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-type: application/x-www-form-urlencoded'));
curl_setopt($ch, CURLOPT_POSTFIELDS, $postBody);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15);
$contents = curl_exec($ch);
curl_close($ch);
} else {
throw new Minify_JS_ClosureCompiler_Exception(
"Could not make HTTP request: allow_url_open is false and cURL not available"
);
}
if (false === $contents) {
throw new Minify_JS_ClosureCompiler_Exception(
"No HTTP response from server"
);
}
return trim($contents);
}
protected function _buildPostBody($js, $returnErrors = false)
{
return http_build_query(array(
'js_code' => $js,
'output_info' => ($returnErrors ? 'errors' : 'compiled_code'),
'output_format' => 'text',
'compilation_level' => 'SIMPLE_OPTIMIZATIONS'
), null, '&');
}
/**
* 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 {}
+8 -2
View File
@@ -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();
@@ -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)
+2
View File
@@ -9,6 +9,8 @@
*
* @package Minify
* @author Stephen Clay <[email protected]>
*
* @todo lose this singleton! pass log object in Minify::serve and distribute to others
*/
class Minify_Logger {
@@ -0,0 +1,382 @@
/*
* YUI Compressor
* http://developer.yahoo.com/yui/compressor/
* Author: Julien Lecomte - http://www.julienlecomte.net/
* Author: Isaac Schlueter - http://foohack.com/
* Author: Stoyan Stefanov - http://phpied.com/
* Copyright (c) 2011 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);
}
}
// Leave data urls alone to increase parse performance.
protected String extractDataUrls(String css, ArrayList preservedTokens) {
int maxIndex = css.length() - 1;
int appendIndex = 0;
StringBuffer sb = new StringBuffer();
Pattern p = Pattern.compile("url\\(\\s*([\"']?)data\\:");
Matcher m = p.matcher(css);
/*
* Since we need to account for non-base64 data urls, we need to handle
* ' and ) being part of the data string. Hence switching to indexOf,
* to determine whether or not we have matching string terminators and
* handling sb appends directly, instead of using matcher.append* methods.
*/
while (m.find()) {
int startIndex = m.start() + 4; // "url(".length()
String terminator = m.group(1); // ', " or empty (not quoted)
if (terminator.length() == 0) {
terminator = ")";
}
boolean foundTerminator = false;
int endIndex = m.end() - 1;
while(foundTerminator == false && endIndex+1 <= maxIndex) {
endIndex = css.indexOf(terminator, endIndex+1);
if ((endIndex > 0) && (css.charAt(endIndex-1) != '\\')) {
foundTerminator = true;
if (!")".equals(terminator)) {
endIndex = css.indexOf(")", endIndex);
}
}
}
// Enough searching, start moving stuff over to the buffer
sb.append(css.substring(appendIndex, m.start()));
if (foundTerminator) {
String token = css.substring(startIndex, endIndex);
token = token.replaceAll("\\s+", "");
preservedTokens.add(token);
String preserver = "url(___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___)";
sb.append(preserver);
appendIndex = endIndex + 1;
} else {
// No end terminator found, re-add the whole match. Should we throw/warn here?
sb.append(css.substring(m.start(), m.end()));
appendIndex = m.end();
}
}
sb.append(css.substring(appendIndex));
return sb.toString();
}
public void compress(Writer out, int linebreakpos)
throws IOException {
Pattern p;
Matcher m;
String css = srcsb.toString();
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;
css = this.extractDataUrls(css, preservedTokens);
StringBuffer sb = new StringBuffer(css);
// 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.
// We also want to make sure we're only compressing #AABBCC patterns inside { }, not id selectors ( #FAABAC {} )
// We also want to avoid compressing invalid values (e.g. #AABBCCD to #ABCD)
p = Pattern.compile("(\\=\\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])" + "(:?\\}|[^0-9a-fA-F{][^{]*?\\})");
m = p.matcher(css);
sb = new StringBuffer();
int index = 0;
while (m.find(index)) {
sb.append(css.substring(index, m.start()));
boolean isFilter = (m.group(1) != null && !"".equals(m.group(1)));
if (isFilter) {
// Restore, as is. Compression will break filters
sb.append(m.group(1) + "#" + m.group(2) + m.group(3) + m.group(4) + m.group(5) + m.group(6) + m.group(7));
} else {
if( m.group(2).equalsIgnoreCase(m.group(3)) &&
m.group(4).equalsIgnoreCase(m.group(5)) &&
m.group(6).equalsIgnoreCase(m.group(7))) {
// #AABBCC pattern
sb.append("#" + (m.group(3) + m.group(5) + m.group(7)).toLowerCase());
} else {
// Non-compressible color, restore, but lower case.
sb.append("#" + (m.group(2) + m.group(3) + m.group(4) + m.group(5) + m.group(6) + m.group(7)).toLowerCase());
}
}
index = m.end(7);
}
sb.append(css.substring(index));
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("[^\\}\\{/;]+\\{\\}", "");
// TODO: Should this be after we re-insert tokens. These could alter the break points. However then
// we'd need to make sure we don't break in the middle of a string etc.
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
View File
@@ -0,0 +1,171 @@
<?php
/**
* Class Minify_YUI_CssCompressor
* @package Minify
*
* YUI Compressor
* Author: Julien Lecomte - http://www.julienlecomte.net/
* Author: Isaac Schlueter - http://foohack.com/
* Author: Stoyan Stefanov - http://phpied.com/
* Author: Steve Clay - http://www.mrclay.org/ (PHP port)
* Copyright (c) 2009 Yahoo! Inc. All rights reserved.
* The copyrights embodied in the content of this file are licensed
* by Yahoo! Inc. under the BSD (revised) open source license.
*/
/**
* Compress CSS (incomplete DO NOT USE)
*
* @see https://github.com/yui/yuicompressor/blob/master/src/com/yahoo/platform/yui/compressor/CssCompressor.java
*
* @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];
}
}
}
+16 -7
View File
@@ -90,8 +90,11 @@ class Minify_YUICompressor {
throw new Exception('Minify_YUICompressor : could not create temp file.');
}
file_put_contents($tmpFile, $content);
exec(self::_getCmd($options, $type, $tmpFile), $output);
exec(self::_getCmd($options, $type, $tmpFile), $output, $result_code);
unlink($tmpFile);
if ($result_code != 0) {
throw new Exception('Minify_YUICompressor : YUI compressor execution failed.');
}
return implode("\n", $output);
}
@@ -110,7 +113,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 +131,17 @@ 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_executable(self::$jarFile)) {
throw new Exception('Minify_YUICompressor : $jarFile('.self::$jarFile.') is not executable.');
}
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.');
}
}
}
+381
View File
@@ -0,0 +1,381 @@
<?php
namespace MrClay;
/**
* Forms a front controller for a console app, handling and validating arguments (options)
*
* Instantiate, add arguments, then call validate(). Afterwards, the user's valid arguments
* and their values will be available in $cli->values.
*
* You may also specify that some arguments be used to provide input/output. By communicating
* solely through the file pointers provided by openInput()/openOutput(), you can make your
* app more flexible to end users.
*
* @author Steve Clay <[email protected]>
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
class Cli {
/**
* @var array validation errors
*/
public $errors = array();
/**
* @var array option values available after validation.
*
* E.g. array(
* 'a' => false // option was missing
* ,'b' => true // option was present
* ,'c' => "Hello" // option had value
* ,'f' => "/home/user/file" // file path from root
* ,'f.raw' => "~/file" // file path as given to option
* )
*/
public $values = array();
/**
* @var array
*/
public $moreArgs = array();
/**
* @var array
*/
public $debug = array();
/**
* @var bool The user wants help info
*/
public $isHelpRequest = false;
/**
* @var array of Cli\Arg
*/
protected $_args = array();
/**
* @var resource
*/
protected $_stdin = null;
/**
* @var resource
*/
protected $_stdout = null;
/**
* @param bool $exitIfNoStdin (default true) Exit() if STDIN is not defined
*/
public function __construct($exitIfNoStdin = true)
{
if ($exitIfNoStdin && ! defined('STDIN')) {
exit('This script is for command-line use only.');
}
if (isset($GLOBALS['argv'][1])
&& ($GLOBALS['argv'][1] === '-?' || $GLOBALS['argv'][1] === '--help')) {
$this->isHelpRequest = true;
}
}
/**
* @param Cli\Arg|string $letter
* @return Cli\Arg
*/
public function addOptionalArg($letter)
{
return $this->addArgument($letter, false);
}
/**
* @param Cli\Arg|string $letter
* @return Cli\Arg
*/
public function addRequiredArg($letter)
{
return $this->addArgument($letter, true);
}
/**
* @param string $letter
* @param bool $required
* @param Cli\Arg|null $arg
* @return Cli\Arg
* @throws \InvalidArgumentException
*/
public function addArgument($letter, $required, Cli\Arg $arg = null)
{
if (! preg_match('/^[a-zA-Z]$/', $letter)) {
throw new \InvalidArgumentException('$letter must be in [a-zA-z]');
}
if (! $arg) {
$arg = new Cli\Arg($required);
}
$this->_args[$letter] = $arg;
return $arg;
}
/**
* @param string $letter
* @return Cli\Arg|null
*/
public function getArgument($letter)
{
return isset($this->_args[$letter]) ? $this->_args[$letter] : null;
}
/*
* Read and validate options
*
* @return bool true if all options are valid
*/
public function validate()
{
$options = '';
$this->errors = array();
$this->values = array();
$this->_stdin = null;
if ($this->isHelpRequest) {
return false;
}
$lettersUsed = '';
foreach ($this->_args as $letter => $arg) {
/* @var Cli\Arg $arg */
$options .= $letter;
$lettersUsed .= $letter;
if ($arg->mayHaveValue || $arg->mustHaveValue) {
$options .= ($arg->mustHaveValue ? ':' : '::');
}
}
$this->debug['argv'] = $GLOBALS['argv'];
$argvCopy = array_slice($GLOBALS['argv'], 1);
$o = getopt($options);
$this->debug['getopt_options'] = $options;
$this->debug['getopt_return'] = $o;
foreach ($this->_args as $letter => $arg) {
/* @var Cli\Arg $arg */
$this->values[$letter] = false;
if (isset($o[$letter])) {
if (is_bool($o[$letter])) {
// remove from argv copy
$k = array_search("-$letter", $argvCopy);
if ($k !== false) {
array_splice($argvCopy, $k, 1);
}
if ($arg->mustHaveValue) {
$this->addError($letter, "Missing value");
} else {
$this->values[$letter] = true;
}
} else {
// string
$this->values[$letter] = $o[$letter];
$v =& $this->values[$letter];
// remove from argv copy
// first look for -ovalue or -o=value
$pattern = "/^-{$letter}=?" . preg_quote($v, '/') . "$/";
$foundInArgv = false;
foreach ($argvCopy as $k => $argV) {
if (preg_match($pattern, $argV)) {
array_splice($argvCopy, $k, 1);
$foundInArgv = true;
break;
}
}
if (! $foundInArgv) {
// space separated
$k = array_search("-$letter", $argvCopy);
if ($k !== false) {
array_splice($argvCopy, $k, 2);
}
}
// check that value isn't really another option
if (strlen($lettersUsed) > 1) {
$pattern = "/^-[" . str_replace($letter, '', $lettersUsed) . "]/i";
if (preg_match($pattern, $v)) {
$this->addError($letter, "Value was read as another option: %s", $v);
return false;
}
}
if ($arg->assertFile || $arg->assertDir) {
if ($v[0] !== '/' && $v[0] !== '~') {
$this->values["$letter.raw"] = $v;
$v = getcwd() . "/$v";
}
}
if ($arg->assertFile) {
if ($arg->useAsInfile) {
$this->_stdin = $v;
} elseif ($arg->useAsOutfile) {
$this->_stdout = $v;
}
if ($arg->assertReadable && ! is_readable($v)) {
$this->addError($letter, "File not readable: %s", $v);
continue;
}
if ($arg->assertWritable) {
if (is_file($v)) {
if (! is_writable($v)) {
$this->addError($letter, "File not writable: %s", $v);
}
} else {
if (! is_writable(dirname($v))) {
$this->addError($letter, "Directory not writable: %s", dirname($v));
}
}
}
} elseif ($arg->assertDir && $arg->assertWritable && ! is_writable($v)) {
$this->addError($letter, "Directory not readable: %s", $v);
}
}
} else {
if ($arg->isRequired()) {
$this->addError($letter, "Missing");
}
}
}
$this->moreArgs = $argvCopy;
reset($this->moreArgs);
return empty($this->errors);
}
/**
* Get the full paths of file(s) passed in as unspecified arguments
*
* @return array
*/
public function getPathArgs()
{
$r = $this->moreArgs;
foreach ($r as $k => $v) {
if ($v[0] !== '/' && $v[0] !== '~') {
$v = getcwd() . "/$v";
$v = str_replace('/./', '/', $v);
do {
$v = preg_replace('@/[^/]+/\\.\\./@', '/', $v, 1, $changed);
} while ($changed);
$r[$k] = $v;
}
}
return $r;
}
/**
* Get a short list of errors with options
*
* @return string
*/
public function getErrorReport()
{
if (empty($this->errors)) {
return '';
}
$r = "Some arguments did not pass validation:\n";
foreach ($this->errors as $letter => $arr) {
$r .= " $letter : " . implode(', ', $arr) . "\n";
}
$r .= "\n";
return $r;
}
/**
* @return string
*/
public function getArgumentsListing()
{
$r = "\n";
foreach ($this->_args as $letter => $arg) {
/* @var Cli\Arg $arg */
$desc = $arg->getDescription();
$flag = " -$letter ";
if ($arg->mayHaveValue) {
$flag .= "[VAL]";
} elseif ($arg->mustHaveValue) {
$flag .= "VAL";
}
if ($arg->assertFile) {
$flag = str_replace('VAL', 'FILE', $flag);
} elseif ($arg->assertDir) {
$flag = str_replace('VAL', 'DIR', $flag);
}
if ($arg->isRequired()) {
$desc = "(required) $desc";
}
$flag = str_pad($flag, 12, " ", STR_PAD_RIGHT);
$desc = wordwrap($desc, 70);
$r .= $flag . str_replace("\n", "\n ", $desc) . "\n\n";
}
return $r;
}
/**
* Get resource of open input stream. May be STDIN or a file pointer
* to the file specified by an option with 'STDIN'.
*
* @return resource
*/
public function openInput()
{
if (null === $this->_stdin) {
return STDIN;
} else {
$this->_stdin = fopen($this->_stdin, 'rb');
return $this->_stdin;
}
}
public function closeInput()
{
if (null !== $this->_stdin) {
fclose($this->_stdin);
}
}
/**
* Get resource of open output stream. May be STDOUT or a file pointer
* to the file specified by an option with 'STDOUT'. The file will be
* truncated to 0 bytes on opening.
*
* @return resource
*/
public function openOutput()
{
if (null === $this->_stdout) {
return STDOUT;
} else {
$this->_stdout = fopen($this->_stdout, 'wb');
return $this->_stdout;
}
}
public function closeOutput()
{
if (null !== $this->_stdout) {
fclose($this->_stdout);
}
}
/**
* @param string $letter
* @param string $msg
* @param string $value
*/
protected function addError($letter, $msg, $value = null)
{
if ($value !== null) {
$value = var_export($value, 1);
}
$this->errors[$letter][] = sprintf($msg, $value);
}
}
+181
View File
@@ -0,0 +1,181 @@
<?php
namespace MrClay\Cli;
/**
* An argument for a CLI app. This specifies the argument, what values it expects and
* how it's treated during validation.
*
* By default, the argument will be assumed to be an optional letter flag with no value following.
*
* If the argument may receive a value, call mayHaveValue(). If there's whitespace after the
* flag, the value will be returned as true instead of the string.
*
* If the argument MUST be accompanied by a value, call mustHaveValue(). In this case, whitespace
* is permitted between the flag and its value.
*
* Use assertFile() or assertDir() to indicate that the argument must return a string value
* specifying a file or directory. During validation, the value will be resolved to a
* full file/dir path (not necessarily existing!) and the original value will be accessible
* via a "*.raw" key. E.g. $cli->values['f.raw']
*
* Use assertReadable()/assertWritable() to cause the validator to test the file/dir for
* read/write permissions respectively.
*
* @method \MrClay\Cli\Arg mayHaveValue() Assert that the argument, if present, may receive a string value
* @method \MrClay\Cli\Arg mustHaveValue() Assert that the argument, if present, must receive a string value
* @method \MrClay\Cli\Arg assertFile() Assert that the argument's value must specify a file
* @method \MrClay\Cli\Arg assertDir() Assert that the argument's value must specify a directory
* @method \MrClay\Cli\Arg assertReadable() Assert that the specified file/dir must be readable
* @method \MrClay\Cli\Arg assertWritable() Assert that the specified file/dir must be writable
*
* @property-read bool mayHaveValue
* @property-read bool mustHaveValue
* @property-read bool assertFile
* @property-read bool assertDir
* @property-read bool assertReadable
* @property-read bool assertWritable
* @property-read bool useAsInfile
* @property-read bool useAsOutfile
*
* @author Steve Clay <[email protected]>
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
class Arg {
/**
* @return array
*/
public function getDefaultSpec()
{
return array(
'mayHaveValue' => false,
'mustHaveValue' => false,
'assertFile' => false,
'assertDir' => false,
'assertReadable' => false,
'assertWritable' => false,
'useAsInfile' => false,
'useAsOutfile' => false,
);
}
/**
* @var array
*/
protected $spec = array();
/**
* @var bool
*/
protected $required = false;
/**
* @var string
*/
protected $description = '';
/**
* @param bool $isRequired
*/
public function __construct($isRequired = false)
{
$this->spec = $this->getDefaultSpec();
$this->required = (bool) $isRequired;
if ($isRequired) {
$this->spec['mustHaveValue'] = true;
}
}
/**
* Assert that the argument's value points to a writable file. When
* Cli::openOutput() is called, a write pointer to this file will
* be provided.
* @return Arg
*/
public function useAsOutfile()
{
$this->spec['useAsOutfile'] = true;
return $this->assertFile()->assertWritable();
}
/**
* Assert that the argument's value points to a readable file. When
* Cli::openInput() is called, a read pointer to this file will
* be provided.
* @return Arg
*/
public function useAsInfile()
{
$this->spec['useAsInfile'] = true;
return $this->assertFile()->assertReadable();
}
/**
* @return array
*/
public function getSpec()
{
return $this->spec;
}
/**
* @param string $desc
* @return Arg
*/
public function setDescription($desc)
{
$this->description = $desc;
return $this;
}
/**
* @return string
*/
public function getDescription()
{
return $this->description;
}
/**
* @return bool
*/
public function isRequired()
{
return $this->required;
}
/**
* Note: magic methods declared in class PHPDOC
*
* @param string $name
* @param array $args
* @return Arg
* @throws \BadMethodCallException
*/
public function __call($name, array $args = array())
{
if (array_key_exists($name, $this->spec)) {
$this->spec[$name] = true;
if ($name === 'assertFile' || $name === 'assertDir') {
$this->spec['mustHaveValue'] = true;
}
} else {
throw new \BadMethodCallException('Method does not exist');
}
return $this;
}
/**
* Note: magic properties declared in class PHPDOC
*
* @param string $name
* @return bool|null
*/
public function __get($name)
{
if (array_key_exists($name, $this->spec)) {
return $this->spec[$name];
}
return null;
}
}
-199
View File
@@ -1,199 +0,0 @@
<?php
/**
*
* Utility class for static directory methods.
*
* @category Solar
*
* @package Solar
*
* @author Paul M. Jones <[email protected]>
*
* @license http://opensource.org/licenses/bsd-license.php BSD
*
* @version $Id$
*
*/
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';
}
}
+3 -6
View File
@@ -1,4 +1,4 @@
Description of Minify 2.1.3 import into Moodle
Description of Minify 2.1.5 import into Moodle
Notes:
* Uses are required to add minify/lib to the include path
@@ -12,9 +12,6 @@ Usage:
Changes:
* Removed index.php - Is an unused entry point program and could potentially
pose a security risk in the future.
* Removed /builder/* - Not needed
* Removed .htaccess - Not needed
* Changed config.php - Changed settings to Moodle specific settings incase this
ever gets accidentally used.
* Updated lib/Minify/CSS/Compressor.php - Applied an upstream fix for MDL-29864
to allow usage of unquoted font-familes with spaces in CSS.
http://code.google.com/p/minify/issues/detail?id=210
* Changed config.php - added moodle specific settings
+56 -72
View File
@@ -1,90 +1,74 @@
<?php
/**
* Utility functions for generating group URIs in HTML files
* Utility functions for generating URIs in HTML files
*
* Before including this file, /min/lib must be in your include_path.
*
* @package Minify
*/
require_once 'Minify/Build.php';
require_once dirname(__FILE__) . '/lib/Minify/HTML/Helper.php';
/**
* Get a timestamped URI to a minified resource using the default Minify install
/*
* Get an HTML-escaped Minify URI for a group or set of files. By default, URIs
* will contain timestamps to allow far-future Expires headers.
*
* <code>
* <link rel="stylesheet" type="text/css" href="<?php echo Minify_groupUri('css'); ?>" />
* <script type="text/javascript" src="<?php echo Minify_groupUri('js'); ?>"></script>
* <link rel="stylesheet" type="text/css" href="<?= Minify_getUri('css'); ?>" />
* <script src="<?= Minify_getUri('js'); ?>"></script>
* <script src="<?= Minify_getUri(array(
* '//scripts/file1.js'
* ,'//scripts/file2.js'
* )); ?>"></script>
* </code>
*
* If you do not want ampersands as HTML entities, set Minify_Build::$ampersand = "&"
* before using this function.
*
* @param string $group a key from groupsConfig.php
* @param boolean $forceAmpersand (default false) Set to true if the RewriteRule
* directives in .htaccess are functional. This will remove the "?" from URIs, making them
* more cacheable by proxies.
* @param mixed $keyOrFiles a group key or array of file paths/URIs
* @param array $opts options:
* 'farExpires' : (default true) append a modified timestamp for cache revving
* 'debug' : (default false) append debug flag
* 'charset' : (default 'UTF-8') for htmlspecialchars
* 'minAppUri' : (default '/min') URI of min directory
* 'rewriteWorks' : (default true) does mod_rewrite work in min app?
* 'groupsConfigFile' : specify if different
* @return string
*/
function Minify_groupUri($group, $forceAmpersand = false)
{
$path = $forceAmpersand
? "/g={$group}"
: "/?g={$group}";
return _Minify_getBuild($group)->uri(
'/' . basename(dirname(__FILE__)) . $path
,$forceAmpersand
);
}
/**
* Get the last modification time of the source js/css files used by Minify to
* build the page.
*
* If you're caching the output of Minify_groupUri(), you'll want to rebuild
* the cache if it's older than this timestamp.
*
* <code>
* // simplistic HTML cache system
* $file = '/path/to/cache/file';
* if (! file_exists($file) || filemtime($file) < Minify_groupsMtime(array('js', 'css'))) {
* // (re)build cache
* $page = buildPage(); // this calls Minify_groupUri() for js and css
* file_put_contents($file, $page);
* echo $page;
* exit();
* }
* readfile($file);
* </code>
*
* @param array $groups an array of keys from groupsConfig.php
* @return int Unix timestamp of the latest modification
*/
function Minify_groupsMtime($groups)
{
$max = 0;
foreach ((array)$groups as $group) {
$max = max($max, _Minify_getBuild($group)->lastModified);
}
return $max;
}
/**
* @param string $group a key from groupsConfig.php
* @return Minify_Build
* @private
*/
function _Minify_getBuild($group)
function Minify_getUri($keyOrFiles, $opts = array())
{
static $builds = array();
static $gc = false;
if (false === $gc) {
$gc = (require dirname(__FILE__) . '/groupsConfig.php');
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';
}
if (! isset($builds[$group])) {
$builds[$group] = new Minify_Build($gc[$group]);
$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 $builds[$group];
return Minify_HTML_Helper::getLastModified($sources);
}
+1 -1
View File
@@ -81,7 +81,7 @@
<location>minify</location>
<name>Minify</name>
<license>BSD</license>
<version>2.1.3</version>
<version>2.1.5</version>
<licenseversion></licenseversion>
</library>
<library>
+2
View File
@@ -270,6 +270,8 @@ function upgrade_stale_php_files_present() {
global $CFG;
$someexamplesofremovedfiles = array(
// removed in 2.3dev
'/lib/minify/builder/',
// removed in 2.2dev
'/lib/yui/3.4.1pr1/',
// removed in 2.2