MDL-32050 import Minify 2.1.5
This commit is contained in:
@@ -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
|
||||
@@ -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">↑</button>'
|
||||
+ ' <button title="Include Later">↓</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('✓');
|
||||
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(/</, '<').replace(/>/, '>').replace(/&/, '&');
|
||||
$('#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;
|
||||
@@ -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.)');
|
||||
})();
|
||||
@@ -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 & 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);
|
||||
@@ -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 +0,0 @@
|
||||
1
|
||||
+77
-25
@@ -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)
|
||||
|
||||
@@ -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')
|
||||
))
|
||||
),//*/
|
||||
);
|
||||
);
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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']
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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})";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,9 +54,12 @@ class Minify_Cache_APC {
|
||||
*/
|
||||
public function getSize($id)
|
||||
{
|
||||
return $this->_fetch($id)
|
||||
? strlen($this->_data)
|
||||
: false;
|
||||
if (! $this->_fetch($id)) {
|
||||
return false;
|
||||
}
|
||||
return (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2))
|
||||
? mb_strlen($this->_data, '8bit')
|
||||
: strlen($this->_data);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,13 +9,12 @@ class Minify_Cache_File {
|
||||
public function __construct($path = '', $fileLocking = false)
|
||||
{
|
||||
if (! $path) {
|
||||
require_once 'Solar/Dir.php';
|
||||
$path = rtrim(Solar_Dir::tmp(), DIRECTORY_SEPARATOR);
|
||||
$path = self::tmp();
|
||||
}
|
||||
$this->_locking = $fileLocking;
|
||||
$this->_path = $path;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Write data to cache.
|
||||
*
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user