';
31 | },
32 | /**
33 | * Add new empty source LI and attach handlers to buttons
34 | */
35 | addLi : function () {
36 | $('#sources').append(MUB.newLi());
37 | var li = $('#li' + MUB._uid)[0];
38 | $('button[title=Remove]', li).click(function () {
39 | $('#results').hide();
40 | var hadValue = !!$('input', li)[0].value;
41 | $(li).remove();
42 | });
43 | $('button[title$=Earlier]', li).click(function () {
44 | $(li).prev('li').find('input').each(function () {
45 | $('#results').hide();
46 | // this = previous li input
47 | var tmp = this.value;
48 | this.value = $('input', li).val();
49 | $('input', li).val(tmp);
50 | MUB.updateAllTestLinks();
51 | });
52 | });
53 | $('button[title$=Later]', li).click(function () {
54 | $(li).next('li').find('input').each(function () {
55 | $('#results').hide();
56 | // this = next li input
57 | var tmp = this.value;
58 | this.value = $('input', li).val();
59 | $('input', li).val(tmp);
60 | MUB.updateAllTestLinks();
61 | });
62 | });
63 | ++MUB._uid;
64 | },
65 | /**
66 | * In the context of a source LI element, this will analyze the URI in
67 | * the INPUT and check the URL on the site.
68 | */
69 | liUpdateTestLink : function () { // call in context of li element
70 | if (! $('input', this)[0].value)
71 | return;
72 | var li = this;
73 | $('span', this).html('');
74 | var url = location.protocol + '//' + location.host + '/' +
75 | $('input', this)[0].value.replace(/^\//, '');
76 | $.ajax({
77 | url : url,
78 | complete : function (xhr, stat) {
79 | if ('success' === stat)
80 | $('span', li).html('✓');
81 | else {
82 | $('span', li).html('')
83 | .find('button').click(function () {
84 | MUB.liUpdateTestLink.call(li);
85 | });
86 | }
87 | },
88 | dataType : 'text'
89 | });
90 | },
91 | /**
92 | * Check all source URLs
93 | */
94 | updateAllTestLinks : function () {
95 | $('#sources li').each(MUB.liUpdateTestLink);
96 | },
97 | /**
98 | * In a given array of strings, find the character they all have at
99 | * a particular index
100 | * @param Array arr array of strings
101 | * @param Number pos index to check
102 | * @return mixed a common char or '' if any do not match
103 | */
104 | getCommonCharAtPos : function (arr, pos) {
105 | var i,
106 | l = arr.length,
107 | c = arr[0].charAt(pos);
108 | if (c === '' || l === 1)
109 | return c;
110 | for (i = 1; i < l; ++i)
111 | if (arr[i].charAt(pos) !== c)
112 | return '';
113 | return c;
114 | },
115 | /**
116 | * Get the shortest URI to minify the set of source files
117 | * @param Array sources URIs
118 | */
119 | getBestUri : function (sources) {
120 | var pos = 0,
121 | base = '',
122 | c;
123 | while (true) {
124 | c = MUB.getCommonCharAtPos(sources, pos);
125 | if (c === '')
126 | break;
127 | else
128 | base += c;
129 | ++pos;
130 | }
131 | base = base.replace(/[^\/]+$/, '');
132 | var uri = MUB._minRoot + 'f=' + sources.join(',');
133 | if (base.charAt(base.length - 1) === '/') {
134 | // we have a base dir!
135 | var basedSources = sources,
136 | i,
137 | l = sources.length;
138 | for (i = 0; i < l; ++i) {
139 | basedSources[i] = sources[i].substr(base.length);
140 | }
141 | base = base.substr(0, base.length - 1);
142 | var bUri = MUB._minRoot + 'b=' + base + '&f=' + basedSources.join(',');
143 | //window.console && console.log([uri, bUri]);
144 | uri = uri.length < bUri.length ? uri : bUri;
145 | }
146 | return uri;
147 | },
148 | /**
149 | * Create the Minify URI for the sources
150 | */
151 | update : function () {
152 | MUB.updateAllTestLinks();
153 | var sources = [],
154 | ext = false,
155 | fail = false,
156 | markup;
157 | $('#sources input').each(function () {
158 | var m, val;
159 | if (! fail && this.value && (m = this.value.match(/\.(css|js)$/))) {
160 | var thisExt = m[1];
161 | if (ext === false)
162 | ext = thisExt;
163 | else if (thisExt !== ext) {
164 | fail = true;
165 | return alert('extensions must match!');
166 | }
167 | this.value = this.value.replace(/^\//, '');
168 | if (-1 !== $.inArray(this.value, sources)) {
169 | fail = true;
170 | return alert('duplicate file!');
171 | }
172 | sources.push(this.value);
173 | }
174 | });
175 | if (fail || ! sources.length)
176 | return;
177 | $('#groupConfig').val(" 'keyName' => array('//" + sources.join("', '//") + "'),");
178 | var uri = MUB.getBestUri(sources),
179 | uriH = uri.replace(/, '<').replace(/>/, '>').replace(/&/, '&');
180 | $('#uriA').html(uriH)[0].href = uri;
181 | if (ext === 'js') {
182 | markup = '';
183 | } else {
184 | markup = '';
185 | }
186 | $('#uriHtml').val(markup);
187 | $('#results').show();
188 | },
189 | /**
190 | * Handler for the "Add file +" button
191 | */
192 | addButtonClick : function () {
193 | $('#results').hide();
194 | MUB.addLi();
195 | MUB.updateAllTestLinks();
196 | $('#update').show().click(MUB.update);
197 | $('#sources li:last input')[0].focus();
198 | },
199 | /**
200 | * Runs on DOMready
201 | */
202 | init : function () {
203 | $('#jsDidntLoad').remove();
204 | $('#app').show();
205 | $('#sources').html('');
206 | $('#add button').click(MUB.addButtonClick);
207 | // make easier to copy text out of
208 | $('#uriHtml, #groupConfig, #symlinkOpt').click(function () {
209 | this.select();
210 | }).focus(function () {
211 | this.select();
212 | });
213 | $('a.ext').attr({target:'_blank'});
214 | if (location.hash) {
215 | // make links out of URIs from bookmarklet
216 | $('#getBm').hide();
217 | var i = 0, found = location.hash.substr(1).split(','), l = found.length;
218 | $('#bmUris').html('
Found by bookmarklet: /
');
219 | var $p = $('#bmUris p');
220 | for (; i < l; i++) {
221 | $p.append($('').text(found[i])[0]);
222 | if (i < (l - 1)) {
223 | $p.append(', /');
224 | }
225 | }
226 | $('#bmUris a').click(function () {
227 | MUB.addButtonClick();
228 | $('#sources li:last input').val(this.innerHTML);
229 | MUB.liUpdateTestLink.call($('#sources li:last')[0]);
230 | $('#results').hide();
231 | return false;
232 | }).attr({title:'Add file +'});
233 | } else {
234 | // setup bookmarklet 1
235 | $.ajax({
236 | url : '../?f=' + location.pathname.replace(/\/[^\/]*$/, '/bm.js').substr(1),
237 | success : function (code) {
238 | $('#bm')[0].href = code
239 | .replace('%BUILDER_URL%', location.href)
240 | .replace(/\n/g, ' ');
241 | },
242 | dataType : 'text'
243 | });
244 | if ($.browser.msie) {
245 | $('#getBm p:last').append(' Sorry, not supported in MSIE!');
246 | }
247 | MUB.addButtonClick();
248 | }
249 | // setup bookmarklet 2
250 | $.ajax({
251 | url : '../?f=' + location.pathname.replace(/\/[^\/]*$/, '/bm2.js').substr(1),
252 | success : function (code) {
253 | $('#bm2')[0].href = code.replace(/\n/g, ' ');
254 | },
255 | dataType : 'text'
256 | });
257 | MUB.checkRewrite();
258 | }
259 | };
260 | $(MUB.init);
261 |
--------------------------------------------------------------------------------
/min/builder/bm.js:
--------------------------------------------------------------------------------
1 | javascript:(function() {
2 | var d = document
3 | ,uris = []
4 | ,i = 0
5 | ,o
6 | ,home = (location + '').split('/').splice(0, 3).join('/') + '/';
7 | function add(uri) {
8 | return (0 === uri.indexOf(home))
9 | && (!/[\?&]/.test(uri))
10 | && uris.push(escape(uri.substr(home.length)));
11 | };
12 | function sheet(ss) {
13 | // we must check the domain with add() before accessing ss.cssRules
14 | // otherwise a security exception will be thrown
15 | if (ss.href && add(ss.href) && ss.cssRules) {
16 | var i = 0, r;
17 | while (r = ss.cssRules[i++])
18 | r.styleSheet && sheet(r.styleSheet);
19 | }
20 | };
21 | while (o = d.getElementsByTagName('script')[i++])
22 | o.src && !(o.type && /vbs/i.test(o.type)) && add(o.src);
23 | i = 0;
24 | while (o = d.styleSheets[i++])
25 | /* http://www.w3.org/TR/DOM-Level-2-Style/stylesheets.html#StyleSheets-DocumentStyle-styleSheets
26 | document.styleSheet is a list property where [0] accesses the 1st element and
27 | [outOfRange] returns null. In IE, styleSheets is a function, and also throws an
28 | exception when you check the out of bounds index. (sigh) */
29 | sheet(o);
30 | if (uris.length)
31 | window.open('%BUILDER_URL%#' + uris.join(','));
32 | else
33 | alert('No js/css files found with URLs within "'
34 | + home.split('/')[2]
35 | + '".\n(This tool is limited to URLs with the same domain.)');
36 | })();
--------------------------------------------------------------------------------
/min/builder/bm2.js:
--------------------------------------------------------------------------------
1 | javascript:(function(){
2 | var d = document
3 | ,c = d.cookie
4 | ,m = c.match(/\bminifyDebug=([^; ]+)/)
5 | ,v = m ? decodeURIComponent(m[1]) : ''
6 | ,p = prompt('Debug Minify URIs on ' + location.hostname + ' which contain:'
7 | + '\n(empty for none, space = OR, * = any string, ? = any char)', v)
8 | ;
9 | if (p === null) return;
10 | p = p.replace(/^\s+|\s+$/, '');
11 | v = (p === '')
12 | ? 'minifyDebug=; expires=Fri, 27 Jul 2001 02:47:11 UTC; path=/'
13 | : 'minifyDebug=' + encodeURIComponent(p) + '; path=/';
14 | d.cookie = v;
15 | })();
--------------------------------------------------------------------------------
/min/builder/index.php:
--------------------------------------------------------------------------------
1 | $min_builderPassword));
39 | }
40 |
41 | $cachePathCode = '';
42 | if (! isset($min_cachePath) && ! function_exists('sys_get_temp_dir')) {
43 | $detectedTmp = Minify_Cache_File::tmp();
44 | $cachePathCode = "\$min_cachePath = " . var_export($detectedTmp, 1) . ';';
45 | }
46 |
47 | ob_start();
48 | ?>
49 |
50 | Minify URI Builder
51 |
52 |
71 |
72 |
73 |
Note: It looks like you're running Minify in a user
74 | directory. You may need the following option in /min/config.php to have URIs
75 | correctly rewritten in CSS output:
76 |
77 |
78 |
79 |
80 |
Uh Oh. Minify was unable to
81 | serve Javascript for this app. To troubleshoot this,
82 | enable FirePHP debugging
83 | and request the Minify URL directly. Hopefully the
84 | FirePHP console will report the cause of the error.
85 |
86 |
87 |
88 |
Note: was discovered as a usable temp directory. To
90 | slightly improve performance you can hardcode this in /min/config.php:
91 |
92 |
93 |
94 |
Note: Your webserver does not seem to
95 | support mod_rewrite (used in /min/.htaccess). Your Minify URIs will contain "?", which
96 | may reduce the benefit of proxy cache servers.
98 |
99 |
Minify URI Builder
100 |
101 |
103 |
104 |
105 |
106 |
Create a list of Javascript or CSS files (or 1 is fine) you'd like to combine
107 | and click [Update].
For the best performance you can serve these files as a pre-defined group with a URI
128 | like: /min/?g=keyName
129 |
To do this, add a line like this to /min/groupsConfig.php:
130 |
131 |
return array(
132 | ... your existing groups here ...
133 |
134 | );
135 |
136 |
Make sure to replace keyName with a unique key for this group.
137 |
138 |
139 |
140 |
Find URIs on a Page
141 |
You can use the bookmarklet below to fetch all CSS & Javascript URIs from a page
142 | on your site. When you active it, this page will open in a new window with a list of
143 | available URIs to add.
If your CSS files contain @import declarations, Minify will not
150 | remove them. Therefore, you will want to remove those that point to files already
151 | in your list, and move any others to the top of the first file in your list
152 | (imports below any styles will be ignored by browsers as invalid).
153 |
If you desire, you can use Minify URIs in imports and they will not be touched
154 | by Minify. E.g. @import "/min/?g=css2";
155 |
156 |
Debug Mode
157 |
When /min/config.php has $min_allowDebugFlag = true;
158 | you can get debug output by appending &debug to a Minify URL, or
159 | by sending the cookie minDebug=<match>, where <match>
160 | should be a string in the Minify URIs you'd like to debug. This bookmarklet will allow you to
161 | set this cookie.
HTTP digest authentication can be used with the URI router.
15 | * HTTP digest is much more recommended over the use of HTTP Basic auth which doesn't provide any encryption.
16 | * If you are running PHP on Apache in CGI/FastCGI mode, you would need to
17 | * add the following line to your .htaccess for digest auth to work correctly.
This class is tested under Apache 2.2 and Cherokee web server. It should work in both mod_php and cgi mode.
21 | *
22 | * @author Leng Sheng Hong
23 | * @version $Id: DooDigestAuth.php 1000 2009-07-7 18:27:22
24 | * @package doo.auth
25 | * @since 1.0
26 | */
27 | class DooDigestAuth{
28 |
29 | /**
30 | * Authenticate against a list of username and passwords.
31 | *
32 | *
HTTP Digest Authentication doesn't work with PHP in CGI mode,
33 | * you have to add this into your .htaccess RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]
34 | *
35 | * @param string $realm Name of the authentication session
36 | * @param array $users An assoc array of username and password: array('uname1'=>'pwd1', 'uname2'=>'pwd2')
37 | * @param string $fail_msg Message to be displayed if the User cancel the login
38 | * @param string $fail_url URL to be redirect if the User cancel the login
39 | * @return string The username if login success.
40 | */
41 | public static function http_auth($realm, $users, $fail_msg=NULL, $fail_url=NULL){
42 | $realm = "Restricted area - $realm";
43 |
44 | //user => password
45 | //$users = array('admin' => '1234', 'guest' => 'guest');
46 | if(!empty($_SERVER['REDIRECT_HTTP_AUTHORIZATION']) && strpos($_SERVER['REDIRECT_HTTP_AUTHORIZATION'], 'Digest')===0){
47 | $_SERVER['PHP_AUTH_DIGEST'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION'];
48 | }
49 |
50 | if (empty($_SERVER['PHP_AUTH_DIGEST'])) {
51 | header('WWW-Authenticate: Digest realm="'.$realm.
52 | '",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"');
53 | header('HTTP/1.1 401 Unauthorized');
54 | if($fail_msg!=NULL)
55 | die($fail_msg);
56 | if($fail_url!=NULL)
57 | die("");
58 | exit;
59 | }
60 |
61 | // analyze the PHP_AUTH_DIGEST variable
62 | if (!($data = self::http_digest_parse($_SERVER['PHP_AUTH_DIGEST'])) || !isset($users[$data['username']])){
63 | header('WWW-Authenticate: Digest realm="'.$realm.
64 | '",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"');
65 | header('HTTP/1.1 401 Unauthorized');
66 | if($fail_msg!=NULL)
67 | die($fail_msg);
68 | if($fail_url!=NULL)
69 | die("");
70 | exit;
71 | }
72 |
73 | // generate the valid response
74 | $A1 = md5($data['username'] . ':' . $realm . ':' . $users[$data['username']]);
75 | $A2 = md5($_SERVER['REQUEST_METHOD'].':'.$data['uri']);
76 | $valid_response = md5($A1.':'.$data['nonce'].':'.$data['nc'].':'.$data['cnonce'].':'.$data['qop'].':'.$A2);
77 |
78 | if ($data['response'] != $valid_response){
79 | header('HTTP/1.1 401 Unauthorized');
80 | header('WWW-Authenticate: Digest realm="'.$realm.
81 | '",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"');
82 | if($fail_msg!=NULL)
83 | die($fail_msg);
84 | if($fail_url!=NULL)
85 | die("");
86 | exit;
87 | }
88 |
89 | // ok, valid username & password
90 | return $data['username'];
91 | }
92 |
93 | /**
94 | * Method to parse the http auth header, works with IE.
95 | *
96 | * Internet Explorer returns a qop="xxxxxxxxxxx" in the header instead of qop=xxxxxxxxxxx as most browsers do.
97 | *
98 | * @param string $txt header string to parse
99 | * @return array An assoc array of the digest auth session
100 | */
101 | private static function http_digest_parse($txt)
102 | {
103 | $res = preg_match("/username=\"([^\"]+)\"/i", $txt, $match);
104 | $data['username'] = (isset($match[1]))?$match[1]:null;
105 | $res = preg_match('/nonce=\"([^\"]+)\"/i', $txt, $match);
106 | $data['nonce'] = $match[1];
107 | $res = preg_match('/nc=([0-9]+)/i', $txt, $match);
108 | $data['nc'] = $match[1];
109 | $res = preg_match('/cnonce=\"([^\"]+)\"/i', $txt, $match);
110 | $data['cnonce'] = $match[1];
111 | $res = preg_match('/qop=([^,]+)/i', $txt, $match);
112 | $data['qop'] = str_replace('"','',$match[1]);
113 | $res = preg_match('/uri=\"([^\"]+)\"/i', $txt, $match);
114 | $data['uri'] = $match[1];
115 | $res = preg_match('/response=\"([^\"]+)\"/i', $txt, $match);
116 | $data['response'] = $match[1];
117 | return $data;
118 | }
119 |
120 |
121 | }
122 |
--------------------------------------------------------------------------------
/min/lib/Minify/Build.php:
--------------------------------------------------------------------------------
1 |
12 | * // in config file
13 | * $groupSources = array(
14 | * 'js' => array('file1.js', 'file2.js')
15 | * ,'css' => array('file1.css', 'file2.css', 'file3.css')
16 | * )
17 | *
18 | * // during HTML generation
19 | * $jsBuild = new Minify_Build($groupSources['js']);
20 | * $cssBuild = new Minify_Build($groupSources['css']);
21 | *
22 | * $script = "";
24 | * $link = "";
26 | *
27 | * // in min.php
28 | * Minify::serve('Groups', array(
29 | * 'groups' => $groupSources
30 | * ,'setExpires' => (time() + 86400 * 365)
31 | * ));
32 | *
33 | *
34 | * @package Minify
35 | * @author Stephen Clay
36 | */
37 | class Minify_Build {
38 |
39 | /**
40 | * Last modification time of all files in the build
41 | *
42 | * @var int
43 | */
44 | public $lastModified = 0;
45 |
46 | /**
47 | * String to use as ampersand in uri(). Set this to '&' if
48 | * you are not HTML-escaping URIs.
49 | *
50 | * @var string
51 | */
52 | public static $ampersand = '&';
53 |
54 | /**
55 | * Get a time-stamped URI
56 | *
57 | *
58 | * echo $b->uri('/site.js');
59 | * // outputs "/site.js?1678242"
60 | *
61 | * echo $b->uri('/scriptaculous.js?load=effects');
62 | * // outputs "/scriptaculous.js?load=effects&1678242"
63 | *
64 | *
65 | * @param string $uri
66 | * @param boolean $forceAmpersand (default = false) Force the use of ampersand to
67 | * append the timestamp to the URI.
68 | * @return string
69 | */
70 | public function uri($uri, $forceAmpersand = false) {
71 | $sep = ($forceAmpersand || strpos($uri, '?') !== false)
72 | ? self::$ampersand
73 | : '?';
74 | return "{$uri}{$sep}{$this->lastModified}";
75 | }
76 |
77 | /**
78 | * Create a build object
79 | *
80 | * @param array $sources array of Minify_Source objects and/or file paths
81 | *
82 | * @return null
83 | */
84 | public function __construct($sources)
85 | {
86 | $max = 0;
87 | foreach ((array)$sources as $source) {
88 | if ($source instanceof Minify_Source) {
89 | $max = max($max, $source->lastModified);
90 | } elseif (is_string($source)) {
91 | if (0 === strpos($source, '//')) {
92 | $source = $_SERVER['DOCUMENT_ROOT'] . substr($source, 1);
93 | }
94 | if (is_file($source)) {
95 | $max = max($max, filemtime($source));
96 | }
97 | }
98 | }
99 | $this->lastModified = $max;
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/min/lib/Minify/CSS.php:
--------------------------------------------------------------------------------
1 |
15 | * @author http://code.google.com/u/1stvamp/ (Issue 64 patch)
16 | */
17 | class Minify_CSS {
18 |
19 | /**
20 | * Minify a CSS string
21 | *
22 | * @param string $css
23 | *
24 | * @param array $options available options:
25 | *
26 | * 'preserveComments': (default true) multi-line comments that begin
27 | * with "/*!" will be preserved with newlines before and after to
28 | * enhance readability.
29 | *
30 | * 'removeCharsets': (default true) remove all @charset at-rules
31 | *
32 | * 'prependRelativePath': (default null) if given, this string will be
33 | * prepended to all relative URIs in import/url declarations
34 | *
35 | * 'currentDir': (default null) if given, this is assumed to be the
36 | * directory of the current CSS file. Using this, minify will rewrite
37 | * all relative URIs in import/url declarations to correctly point to
38 | * the desired files. For this to work, the files *must* exist and be
39 | * visible by the PHP process.
40 | *
41 | * 'symlinks': (default = array()) If the CSS file is stored in
42 | * a symlink-ed directory, provide an array of link paths to
43 | * target paths, where the link paths are within the document root. Because
44 | * paths need to be normalized for this to work, use "//" to substitute
45 | * the doc root in the link paths (the array keys). E.g.:
46 | *
47 | * array('//symlink' => '/real/target/path') // unix
48 | * array('//static' => 'D:\\staticStorage') // Windows
49 | *
50 | *
51 | * 'docRoot': (default = $_SERVER['DOCUMENT_ROOT'])
52 | * see Minify_CSS_UriRewriter::rewrite
53 | *
54 | * @return string
55 | */
56 | public static function minify($css, $options = array())
57 | {
58 | $options = array_merge(array(
59 | 'compress' => true,
60 | 'removeCharsets' => true,
61 | 'preserveComments' => true,
62 | 'currentDir' => null,
63 | 'docRoot' => $_SERVER['DOCUMENT_ROOT'],
64 | 'prependRelativePath' => null,
65 | 'symlinks' => array(),
66 | ), $options);
67 |
68 | if ($options['removeCharsets']) {
69 | $css = preg_replace('/@charset[^;]+;\\s*/', '', $css);
70 | }
71 | if ($options['compress']) {
72 | if (! $options['preserveComments']) {
73 | $css = Minify_CSS_Compressor::process($css, $options);
74 | } else {
75 | $css = Minify_CommentPreserver::process(
76 | $css
77 | ,array('Minify_CSS_Compressor', 'process')
78 | ,array($options)
79 | );
80 | }
81 | }
82 | if (! $options['currentDir'] && ! $options['prependRelativePath']) {
83 | return $css;
84 | }
85 | if ($options['currentDir']) {
86 | return Minify_CSS_UriRewriter::rewrite(
87 | $css
88 | ,$options['currentDir']
89 | ,$options['docRoot']
90 | ,$options['symlinks']
91 | );
92 | } else {
93 | return Minify_CSS_UriRewriter::prepend(
94 | $css
95 | ,$options['prependRelativePath']
96 | );
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/min/lib/Minify/CSS/Compressor.php:
--------------------------------------------------------------------------------
1 |
19 | * @author http://code.google.com/u/1stvamp/ (Issue 64 patch)
20 | */
21 | class Minify_CSS_Compressor {
22 |
23 | /**
24 | * Minify a CSS string
25 | *
26 | * @param string $css
27 | *
28 | * @param array $options (currently ignored)
29 | *
30 | * @return string
31 | */
32 | public static function process($css, $options = array())
33 | {
34 | $obj = new Minify_CSS_Compressor($options);
35 | return $obj->_process($css);
36 | }
37 |
38 | /**
39 | * @var array
40 | */
41 | protected $_options = null;
42 |
43 | /**
44 | * Are we "in" a hack? I.e. are some browsers targetted until the next comment?
45 | *
46 | * @var bool
47 | */
48 | protected $_inHack = false;
49 |
50 |
51 | /**
52 | * Constructor
53 | *
54 | * @param array $options (currently ignored)
55 | */
56 | private function __construct($options) {
57 | $this->_options = $options;
58 | }
59 |
60 | /**
61 | * Minify a CSS string
62 | *
63 | * @param string $css
64 | *
65 | * @return string
66 | */
67 | protected function _process($css)
68 | {
69 | $css = str_replace("\r\n", "\n", $css);
70 |
71 | // preserve empty comment after '>'
72 | // http://www.webdevout.net/css-hacks#in_css-selectors
73 | $css = preg_replace('@>/\\*\\s*\\*/@', '>/*keep*/', $css);
74 |
75 | // preserve empty comment between property and value
76 | // http://css-discuss.incutio.com/?page=BoxModelHack
77 | $css = preg_replace('@/\\*\\s*\\*/\\s*:@', '/*keep*/:', $css);
78 | $css = preg_replace('@:\\s*/\\*\\s*\\*/@', ':/*keep*/', $css);
79 |
80 | // apply callback to all valid comments (and strip out surrounding ws
81 | $css = preg_replace_callback('@\\s*/\\*([\\s\\S]*?)\\*/\\s*@'
82 | ,array($this, '_commentCB'), $css);
83 |
84 | // remove ws around { } and last semicolon in declaration block
85 | $css = preg_replace('/\\s*{\\s*/', '{', $css);
86 | $css = preg_replace('/;?\\s*}\\s*/', '}', $css);
87 |
88 | // remove ws surrounding semicolons
89 | $css = preg_replace('/\\s*;\\s*/', ';', $css);
90 |
91 | // remove ws around urls
92 | $css = preg_replace('/
93 | url\\( # url(
94 | \\s*
95 | ([^\\)]+?) # 1 = the URL (really just a bunch of non right parenthesis)
96 | \\s*
97 | \\) # )
98 | /x', 'url($1)', $css);
99 |
100 | // remove ws between rules and colons
101 | $css = preg_replace('/
102 | \\s*
103 | ([{;]) # 1 = beginning of block or rule separator
104 | \\s*
105 | ([\\*_]?[\\w\\-]+) # 2 = property (and maybe IE filter)
106 | \\s*
107 | :
108 | \\s*
109 | (\\b|[#\'"-]) # 3 = first character of a value
110 | /x', '$1$2:$3', $css);
111 |
112 | // remove ws in selectors
113 | $css = preg_replace_callback('/
114 | (?: # non-capture
115 | \\s*
116 | [^~>+,\\s]+ # selector part
117 | \\s*
118 | [,>+~] # combinators
119 | )+
120 | \\s*
121 | [^~>+,\\s]+ # selector part
122 | { # open declaration block
123 | /x'
124 | ,array($this, '_selectorsCB'), $css);
125 |
126 | // minimize hex colors
127 | $css = preg_replace('/([^=])#([a-f\\d])\\2([a-f\\d])\\3([a-f\\d])\\4([\\s;\\}])/i'
128 | , '$1#$2$3$4$5', $css);
129 |
130 | // remove spaces between font families
131 | $css = preg_replace_callback('/font-family:([^;}]+)([;}])/'
132 | ,array($this, '_fontFamilyCB'), $css);
133 |
134 | $css = preg_replace('/@import\\s+url/', '@import url', $css);
135 |
136 | // replace any ws involving newlines with a single newline
137 | $css = preg_replace('/[ \\t]*\\n+\\s*/', "\n", $css);
138 |
139 | // separate common descendent selectors w/ newlines (to limit line lengths)
140 | $css = preg_replace('/([\\w#\\.\\*]+)\\s+([\\w#\\.\\*]+){/', "$1\n$2{", $css);
141 |
142 | // Use newline after 1st numeric value (to limit line lengths).
143 | $css = preg_replace('/
144 | ((?:padding|margin|border|outline):\\d+(?:px|em)?) # 1 = prop : 1st numeric value
145 | \\s+
146 | /x'
147 | ,"$1\n", $css);
148 |
149 | // prevent triggering IE6 bug: http://www.crankygeek.com/ie6pebug/
150 | $css = preg_replace('/:first-l(etter|ine)\\{/', ':first-l$1 {', $css);
151 |
152 | return trim($css);
153 | }
154 |
155 | /**
156 | * Replace what looks like a set of selectors
157 | *
158 | * @param array $m regex matches
159 | *
160 | * @return string
161 | */
162 | protected function _selectorsCB($m)
163 | {
164 | // remove ws around the combinators
165 | return preg_replace('/\\s*([,>+~])\\s*/', '$1', $m[0]);
166 | }
167 |
168 | /**
169 | * Process a comment and return a replacement
170 | *
171 | * @param array $m regex matches
172 | *
173 | * @return string
174 | */
175 | protected function _commentCB($m)
176 | {
177 | $hasSurroundingWs = (trim($m[0]) !== $m[1]);
178 | $m = $m[1];
179 | // $m is the comment content w/o the surrounding tokens,
180 | // but the return value will replace the entire comment.
181 | if ($m === 'keep') {
182 | return '/**/';
183 | }
184 | if ($m === '" "') {
185 | // component of http://tantek.com/CSS/Examples/midpass.html
186 | return '/*" "*/';
187 | }
188 | if (preg_match('@";\\}\\s*\\}/\\*\\s+@', $m)) {
189 | // component of http://tantek.com/CSS/Examples/midpass.html
190 | return '/*";}}/* */';
191 | }
192 | if ($this->_inHack) {
193 | // inversion: feeding only to one browser
194 | if (preg_match('@
195 | ^/ # comment started like /*/
196 | \\s*
197 | (\\S[\\s\\S]+?) # has at least some non-ws content
198 | \\s*
199 | /\\* # ends like /*/ or /**/
200 | @x', $m, $n)) {
201 | // end hack mode after this comment, but preserve the hack and comment content
202 | $this->_inHack = false;
203 | return "/*/{$n[1]}/**/";
204 | }
205 | }
206 | if (substr($m, -1) === '\\') { // comment ends like \*/
207 | // begin hack mode and preserve hack
208 | $this->_inHack = true;
209 | return '/*\\*/';
210 | }
211 | if ($m !== '' && $m[0] === '/') { // comment looks like /*/ foo */
212 | // begin hack mode and preserve hack
213 | $this->_inHack = true;
214 | return '/*/*/';
215 | }
216 | if ($this->_inHack) {
217 | // a regular comment ends hack mode but should be preserved
218 | $this->_inHack = false;
219 | return '/**/';
220 | }
221 | // Issue 107: if there's any surrounding whitespace, it may be important, so
222 | // replace the comment with a single space
223 | return $hasSurroundingWs // remove all other comments
224 | ? ' '
225 | : '';
226 | }
227 |
228 | /**
229 | * Process a font-family listing and return a replacement
230 | *
231 | * @param array $m regex matches
232 | *
233 | * @return string
234 | */
235 | protected function _fontFamilyCB($m)
236 | {
237 | // Issue 210: must not eliminate WS between words in unquoted families
238 | $pieces = preg_split('/(\'[^\']+\'|"[^"]+")/', $m[1], null, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
239 | $out = 'font-family:';
240 | while (null !== ($piece = array_shift($pieces))) {
241 | if ($piece[0] !== '"' && $piece[0] !== "'") {
242 | $piece = preg_replace('/\\s+/', ' ', $piece);
243 | $piece = preg_replace('/\\s?,\\s?/', ',', $piece);
244 | }
245 | $out .= $piece;
246 | }
247 | return $out . $m[2];
248 | }
249 | }
250 |
--------------------------------------------------------------------------------
/min/lib/Minify/CSS/UriRewriter.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | class Minify_CSS_UriRewriter {
14 |
15 | /**
16 | * rewrite() and rewriteRelative() append debugging information here
17 | *
18 | * @var string
19 | */
20 | public static $debugText = '';
21 |
22 | /**
23 | * In CSS content, rewrite file relative URIs as root relative
24 | *
25 | * @param string $css
26 | *
27 | * @param string $currentDir The directory of the current CSS file.
28 | *
29 | * @param string $docRoot The document root of the web site in which
30 | * the CSS file resides (default = $_SERVER['DOCUMENT_ROOT']).
31 | *
32 | * @param array $symlinks (default = array()) If the CSS file is stored in
33 | * a symlink-ed directory, provide an array of link paths to
34 | * target paths, where the link paths are within the document root. Because
35 | * paths need to be normalized for this to work, use "//" to substitute
36 | * the doc root in the link paths (the array keys). E.g.:
37 | *
38 | * array('//symlink' => '/real/target/path') // unix
39 | * array('//static' => 'D:\\staticStorage') // Windows
40 | *
41 | *
42 | * @return string
43 | */
44 | public static function rewrite($css, $currentDir, $docRoot = null, $symlinks = array())
45 | {
46 | self::$_docRoot = self::_realpath(
47 | $docRoot ? $docRoot : $_SERVER['DOCUMENT_ROOT']
48 | );
49 | self::$_currentDir = self::_realpath($currentDir);
50 | self::$_symlinks = array();
51 |
52 | // normalize symlinks
53 | foreach ($symlinks as $link => $target) {
54 | $link = ($link === '//')
55 | ? self::$_docRoot
56 | : str_replace('//', self::$_docRoot . '/', $link);
57 | $link = strtr($link, '/', DIRECTORY_SEPARATOR);
58 | self::$_symlinks[$link] = self::_realpath($target);
59 | }
60 |
61 | self::$debugText .= "docRoot : " . self::$_docRoot . "\n"
62 | . "currentDir : " . self::$_currentDir . "\n";
63 | if (self::$_symlinks) {
64 | self::$debugText .= "symlinks : " . var_export(self::$_symlinks, 1) . "\n";
65 | }
66 | self::$debugText .= "\n";
67 |
68 | $css = self::_trimUrls($css);
69 |
70 | // rewrite
71 | $css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/'
72 | ,array(self::$className, '_processUriCB'), $css);
73 | $css = preg_replace_callback('/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
74 | ,array(self::$className, '_processUriCB'), $css);
75 |
76 | return $css;
77 | }
78 |
79 | /**
80 | * In CSS content, prepend a path to relative URIs
81 | *
82 | * @param string $css
83 | *
84 | * @param string $path The path to prepend.
85 | *
86 | * @return string
87 | */
88 | public static function prepend($css, $path)
89 | {
90 | self::$_prependPath = $path;
91 |
92 | $css = self::_trimUrls($css);
93 |
94 | // append
95 | $css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/'
96 | ,array(self::$className, '_processUriCB'), $css);
97 | $css = preg_replace_callback('/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
98 | ,array(self::$className, '_processUriCB'), $css);
99 |
100 | self::$_prependPath = null;
101 | return $css;
102 | }
103 |
104 | /**
105 | * Get a root relative URI from a file relative URI
106 | *
107 | *
108 | * Minify_CSS_UriRewriter::rewriteRelative(
109 | * '../img/hello.gif'
110 | * , '/home/user/www/css' // path of CSS file
111 | * , '/home/user/www' // doc root
112 | * );
113 | * // returns '/img/hello.gif'
114 | *
115 | * // example where static files are stored in a symlinked directory
116 | * Minify_CSS_UriRewriter::rewriteRelative(
117 | * 'hello.gif'
118 | * , '/var/staticFiles/theme'
119 | * , '/home/user/www'
120 | * , array('/home/user/www/static' => '/var/staticFiles')
121 | * );
122 | * // returns '/static/theme/hello.gif'
123 | *
124 | *
125 | * @param string $uri file relative URI
126 | *
127 | * @param string $realCurrentDir realpath of the current file's directory.
128 | *
129 | * @param string $realDocRoot realpath of the site document root.
130 | *
131 | * @param array $symlinks (default = array()) If the file is stored in
132 | * a symlink-ed directory, provide an array of link paths to
133 | * real target paths, where the link paths "appear" to be within the document
134 | * root. E.g.:
135 | *
136 | * array('/home/foo/www/not/real/path' => '/real/target/path') // unix
137 | * array('C:\\htdocs\\not\\real' => 'D:\\real\\target\\path') // Windows
138 | *
139 | *
140 | * @return string
141 | */
142 | public static function rewriteRelative($uri, $realCurrentDir, $realDocRoot, $symlinks = array())
143 | {
144 | // prepend path with current dir separator (OS-independent)
145 | $path = strtr($realCurrentDir, '/', DIRECTORY_SEPARATOR)
146 | . DIRECTORY_SEPARATOR . strtr($uri, '/', DIRECTORY_SEPARATOR);
147 |
148 | self::$debugText .= "file-relative URI : {$uri}\n"
149 | . "path prepended : {$path}\n";
150 |
151 | // "unresolve" a symlink back to doc root
152 | foreach ($symlinks as $link => $target) {
153 | if (0 === strpos($path, $target)) {
154 | // replace $target with $link
155 | $path = $link . substr($path, strlen($target));
156 |
157 | self::$debugText .= "symlink unresolved : {$path}\n";
158 |
159 | break;
160 | }
161 | }
162 | // strip doc root
163 | $path = substr($path, strlen($realDocRoot));
164 |
165 | self::$debugText .= "docroot stripped : {$path}\n";
166 |
167 | // fix to root-relative URI
168 | $uri = strtr($path, '/\\', '//');
169 | $uri = self::removeDots($uri);
170 |
171 | self::$debugText .= "traversals removed : {$uri}\n\n";
172 |
173 | return $uri;
174 | }
175 |
176 | /**
177 | * Remove instances of "./" and "../" where possible from a root-relative URI
178 | *
179 | * @param string $uri
180 | *
181 | * @return string
182 | */
183 | public static function removeDots($uri)
184 | {
185 | $uri = str_replace('/./', '/', $uri);
186 | // inspired by patch from Oleg Cherniy
187 | do {
188 | $uri = preg_replace('@/[^/]+/\\.\\./@', '/', $uri, 1, $changed);
189 | } while ($changed);
190 | return $uri;
191 | }
192 |
193 | /**
194 | * Defines which class to call as part of callbacks, change this
195 | * if you extend Minify_CSS_UriRewriter
196 | *
197 | * @var string
198 | */
199 | protected static $className = 'Minify_CSS_UriRewriter';
200 |
201 | /**
202 | * Get realpath with any trailing slash removed. If realpath() fails,
203 | * just remove the trailing slash.
204 | *
205 | * @param string $path
206 | *
207 | * @return mixed path with no trailing slash
208 | */
209 | protected static function _realpath($path)
210 | {
211 | $realPath = realpath($path);
212 | if ($realPath !== false) {
213 | $path = $realPath;
214 | }
215 | return rtrim($path, '/\\');
216 | }
217 |
218 | /**
219 | * Directory of this stylesheet
220 | *
221 | * @var string
222 | */
223 | private static $_currentDir = '';
224 |
225 | /**
226 | * DOC_ROOT
227 | *
228 | * @var string
229 | */
230 | private static $_docRoot = '';
231 |
232 | /**
233 | * directory replacements to map symlink targets back to their
234 | * source (within the document root) E.g. '/var/www/symlink' => '/var/realpath'
235 | *
236 | * @var array
237 | */
238 | private static $_symlinks = array();
239 |
240 | /**
241 | * Path to prepend
242 | *
243 | * @var string
244 | */
245 | private static $_prependPath = null;
246 |
247 | /**
248 | * @param string $css
249 | *
250 | * @return string
251 | */
252 | private static function _trimUrls($css)
253 | {
254 | return preg_replace('/
255 | url\\( # url(
256 | \\s*
257 | ([^\\)]+?) # 1 = URI (assuming does not contain ")")
258 | \\s*
259 | \\) # )
260 | /x', 'url($1)', $css);
261 | }
262 |
263 | /**
264 | * @param array $m
265 | *
266 | * @return string
267 | */
268 | private static function _processUriCB($m)
269 | {
270 | // $m matched either '/@import\\s+([\'"])(.*?)[\'"]/' or '/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
271 | $isImport = ($m[0][0] === '@');
272 | // determine URI and the quote character (if any)
273 | if ($isImport) {
274 | $quoteChar = $m[1];
275 | $uri = $m[2];
276 | } else {
277 | // $m[1] is either quoted or not
278 | $quoteChar = ($m[1][0] === "'" || $m[1][0] === '"')
279 | ? $m[1][0]
280 | : '';
281 | $uri = ($quoteChar === '')
282 | ? $m[1]
283 | : substr($m[1], 1, strlen($m[1]) - 2);
284 | }
285 | // analyze URI
286 | if ('/' !== $uri[0] // root-relative
287 | && false === strpos($uri, '//') // protocol (non-data)
288 | && 0 !== strpos($uri, 'data:') // data protocol
289 | ) {
290 | // URI is file-relative: rewrite depending on options
291 | if (self::$_prependPath === null) {
292 | $uri = self::rewriteRelative($uri, self::$_currentDir, self::$_docRoot, self::$_symlinks);
293 | } else {
294 | $uri = self::$_prependPath . $uri;
295 | if ($uri[0] === '/') {
296 | $root = '';
297 | $rootRelative = $uri;
298 | $uri = $root . self::removeDots($rootRelative);
299 | } elseif (preg_match('@^((https?\:)?//([^/]+))/@', $uri, $m) && (false !== strpos($m[3], '.'))) {
300 | $root = $m[1];
301 | $rootRelative = substr($uri, strlen($root));
302 | $uri = $root . self::removeDots($rootRelative);
303 | }
304 | }
305 | }
306 | return $isImport
307 | ? "@import {$quoteChar}{$uri}{$quoteChar}"
308 | : "url({$quoteChar}{$uri}{$quoteChar})";
309 | }
310 | }
311 |
--------------------------------------------------------------------------------
/min/lib/Minify/Cache/APC.php:
--------------------------------------------------------------------------------
1 |
11 | * Minify::setCache(new Minify_Cache_APC());
12 | *
13 | *
14 | * @package Minify
15 | * @author Chris Edwards
16 | **/
17 | class Minify_Cache_APC {
18 |
19 | /**
20 | * Create a Minify_Cache_APC object, to be passed to
21 | * Minify::setCache().
22 | *
23 | *
24 | * @param int $expire seconds until expiration (default = 0
25 | * meaning the item will not get an expiration date)
26 | *
27 | * @return null
28 | */
29 | public function __construct($expire = 0)
30 | {
31 | $this->_exp = $expire;
32 | }
33 |
34 | /**
35 | * Write data to cache.
36 | *
37 | * @param string $id cache id
38 | *
39 | * @param string $data
40 | *
41 | * @return bool success
42 | */
43 | public function store($id, $data)
44 | {
45 | return apc_store($id, "{$_SERVER['REQUEST_TIME']}|{$data}", $this->_exp);
46 | }
47 |
48 | /**
49 | * Get the size of a cache entry
50 | *
51 | * @param string $id cache id
52 | *
53 | * @return int size in bytes
54 | */
55 | public function getSize($id)
56 | {
57 | if (! $this->_fetch($id)) {
58 | return false;
59 | }
60 | return (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2))
61 | ? mb_strlen($this->_data, '8bit')
62 | : strlen($this->_data);
63 | }
64 |
65 | /**
66 | * Does a valid cache entry exist?
67 | *
68 | * @param string $id cache id
69 | *
70 | * @param int $srcMtime mtime of the original source file(s)
71 | *
72 | * @return bool exists
73 | */
74 | public function isValid($id, $srcMtime)
75 | {
76 | return ($this->_fetch($id) && ($this->_lm >= $srcMtime));
77 | }
78 |
79 | /**
80 | * Send the cached content to output
81 | *
82 | * @param string $id cache id
83 | */
84 | public function display($id)
85 | {
86 | echo $this->_fetch($id)
87 | ? $this->_data
88 | : '';
89 | }
90 |
91 | /**
92 | * Fetch the cached content
93 | *
94 | * @param string $id cache id
95 | *
96 | * @return string
97 | */
98 | public function fetch($id)
99 | {
100 | return $this->_fetch($id)
101 | ? $this->_data
102 | : '';
103 | }
104 |
105 | private $_exp = null;
106 |
107 | // cache of most recently fetched id
108 | private $_lm = null;
109 | private $_data = null;
110 | private $_id = null;
111 |
112 | /**
113 | * Fetch data and timestamp from apc, store in instance
114 | *
115 | * @param string $id
116 | *
117 | * @return bool success
118 | */
119 | private function _fetch($id)
120 | {
121 | if ($this->_id === $id) {
122 | return true;
123 | }
124 | $ret = apc_fetch($id);
125 | if (false === $ret) {
126 | $this->_id = null;
127 | return false;
128 | }
129 | list($this->_lm, $this->_data) = explode('|', $ret, 2);
130 | $this->_id = $id;
131 | return true;
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/min/lib/Minify/Cache/File.php:
--------------------------------------------------------------------------------
1 | _locking = $fileLocking;
15 | $this->_path = $path;
16 | }
17 |
18 | /**
19 | * Write data to cache.
20 | *
21 | * @param string $id cache id (e.g. a filename)
22 | *
23 | * @param string $data
24 | *
25 | * @return bool success
26 | */
27 | public function store($id, $data)
28 | {
29 | $flag = $this->_locking
30 | ? LOCK_EX
31 | : null;
32 | $file = $this->_path . '/' . $id;
33 | if (! @file_put_contents($file, $data, $flag)) {
34 | $this->_log("Minify_Cache_File: Write failed to '$file'");
35 | }
36 | // write control
37 | if ($data !== $this->fetch($id)) {
38 | @unlink($file);
39 | $this->_log("Minify_Cache_File: Post-write read failed for '$file'");
40 | return false;
41 | }
42 | return true;
43 | }
44 |
45 | /**
46 | * Get the size of a cache entry
47 | *
48 | * @param string $id cache id (e.g. a filename)
49 | *
50 | * @return int size in bytes
51 | */
52 | public function getSize($id)
53 | {
54 | return filesize($this->_path . '/' . $id);
55 | }
56 |
57 | /**
58 | * Does a valid cache entry exist?
59 | *
60 | * @param string $id cache id (e.g. a filename)
61 | *
62 | * @param int $srcMtime mtime of the original source file(s)
63 | *
64 | * @return bool exists
65 | */
66 | public function isValid($id, $srcMtime)
67 | {
68 | $file = $this->_path . '/' . $id;
69 | return (is_file($file) && (filemtime($file) >= $srcMtime));
70 | }
71 |
72 | /**
73 | * Send the cached content to output
74 | *
75 | * @param string $id cache id (e.g. a filename)
76 | */
77 | public function display($id)
78 | {
79 | if ($this->_locking) {
80 | $fp = fopen($this->_path . '/' . $id, 'rb');
81 | flock($fp, LOCK_SH);
82 | fpassthru($fp);
83 | flock($fp, LOCK_UN);
84 | fclose($fp);
85 | } else {
86 | readfile($this->_path . '/' . $id);
87 | }
88 | }
89 |
90 | /**
91 | * Fetch the cached content
92 | *
93 | * @param string $id cache id (e.g. a filename)
94 | *
95 | * @return string
96 | */
97 | public function fetch($id)
98 | {
99 | if ($this->_locking) {
100 | $fp = fopen($this->_path . '/' . $id, 'rb');
101 | flock($fp, LOCK_SH);
102 | $ret = stream_get_contents($fp);
103 | flock($fp, LOCK_UN);
104 | fclose($fp);
105 | return $ret;
106 | } else {
107 | return file_get_contents($this->_path . '/' . $id);
108 | }
109 | }
110 |
111 | /**
112 | * Fetch the cache path used
113 | *
114 | * @return string
115 | */
116 | public function getPath()
117 | {
118 | return $this->_path;
119 | }
120 |
121 | /**
122 | * Get a usable temp directory
123 | *
124 | * Adapted from Solar/Dir.php
125 | * @author Paul M. Jones
126 | * @license http://opensource.org/licenses/bsd-license.php BSD
127 | * @link http://solarphp.com/trac/core/browser/trunk/Solar/Dir.php
128 | *
129 | * @return string
130 | */
131 | public static function tmp()
132 | {
133 | static $tmp = null;
134 | if (! $tmp) {
135 | $tmp = function_exists('sys_get_temp_dir')
136 | ? sys_get_temp_dir()
137 | : self::_tmp();
138 | $tmp = rtrim($tmp, DIRECTORY_SEPARATOR);
139 | }
140 | return $tmp;
141 | }
142 |
143 | /**
144 | * Returns the OS-specific directory for temporary files
145 | *
146 | * @author Paul M. Jones
147 | * @license http://opensource.org/licenses/bsd-license.php BSD
148 | * @link http://solarphp.com/trac/core/browser/trunk/Solar/Dir.php
149 | *
150 | * @return string
151 | */
152 | protected static function _tmp()
153 | {
154 | // non-Windows system?
155 | if (strtolower(substr(PHP_OS, 0, 3)) != 'win') {
156 | $tmp = empty($_ENV['TMPDIR']) ? getenv('TMPDIR') : $_ENV['TMPDIR'];
157 | if ($tmp) {
158 | return $tmp;
159 | } else {
160 | return '/tmp';
161 | }
162 | }
163 | // Windows 'TEMP'
164 | $tmp = empty($_ENV['TEMP']) ? getenv('TEMP') : $_ENV['TEMP'];
165 | if ($tmp) {
166 | return $tmp;
167 | }
168 | // Windows 'TMP'
169 | $tmp = empty($_ENV['TMP']) ? getenv('TMP') : $_ENV['TMP'];
170 | if ($tmp) {
171 | return $tmp;
172 | }
173 | // Windows 'windir'
174 | $tmp = empty($_ENV['windir']) ? getenv('windir') : $_ENV['windir'];
175 | if ($tmp) {
176 | return $tmp;
177 | }
178 | // final fallback for Windows
179 | return getenv('SystemRoot') . '\\temp';
180 | }
181 |
182 | /**
183 | * Send message to the Minify logger
184 | * @param string $msg
185 | * @return null
186 | */
187 | protected function _log($msg)
188 | {
189 | Minify_Logger::log($msg);
190 | }
191 |
192 | private $_path = null;
193 | private $_locking = null;
194 | }
195 |
--------------------------------------------------------------------------------
/min/lib/Minify/Cache/Memcache.php:
--------------------------------------------------------------------------------
1 |
11 | * // fall back to disk caching if memcache can't connect
12 | * $memcache = new Memcache;
13 | * if ($memcache->connect('localhost', 11211)) {
14 | * Minify::setCache(new Minify_Cache_Memcache($memcache));
15 | * } else {
16 | * Minify::setCache();
17 | * }
18 | *
19 | **/
20 | class Minify_Cache_Memcache {
21 |
22 | /**
23 | * Create a Minify_Cache_Memcache object, to be passed to
24 | * Minify::setCache().
25 | *
26 | * @param Memcache $memcache already-connected instance
27 | *
28 | * @param int $expire seconds until expiration (default = 0
29 | * meaning the item will not get an expiration date)
30 | *
31 | * @return null
32 | */
33 | public function __construct($memcache, $expire = 0)
34 | {
35 | $this->_mc = $memcache;
36 | $this->_exp = $expire;
37 | }
38 |
39 | /**
40 | * Write data to cache.
41 | *
42 | * @param string $id cache id
43 | *
44 | * @param string $data
45 | *
46 | * @return bool success
47 | */
48 | public function store($id, $data)
49 | {
50 | return $this->_mc->set($id, "{$_SERVER['REQUEST_TIME']}|{$data}", 0, $this->_exp);
51 | }
52 |
53 |
54 | /**
55 | * Get the size of a cache entry
56 | *
57 | * @param string $id cache id
58 | *
59 | * @return int size in bytes
60 | */
61 | public function getSize($id)
62 | {
63 | if (! $this->_fetch($id)) {
64 | return false;
65 | }
66 | return (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2))
67 | ? mb_strlen($this->_data, '8bit')
68 | : strlen($this->_data);
69 | }
70 |
71 | /**
72 | * Does a valid cache entry exist?
73 | *
74 | * @param string $id cache id
75 | *
76 | * @param int $srcMtime mtime of the original source file(s)
77 | *
78 | * @return bool exists
79 | */
80 | public function isValid($id, $srcMtime)
81 | {
82 | return ($this->_fetch($id) && ($this->_lm >= $srcMtime));
83 | }
84 |
85 | /**
86 | * Send the cached content to output
87 | *
88 | * @param string $id cache id
89 | */
90 | public function display($id)
91 | {
92 | echo $this->_fetch($id)
93 | ? $this->_data
94 | : '';
95 | }
96 |
97 | /**
98 | * Fetch the cached content
99 | *
100 | * @param string $id cache id
101 | *
102 | * @return string
103 | */
104 | public function fetch($id)
105 | {
106 | return $this->_fetch($id)
107 | ? $this->_data
108 | : '';
109 | }
110 |
111 | private $_mc = null;
112 | private $_exp = null;
113 |
114 | // cache of most recently fetched id
115 | private $_lm = null;
116 | private $_data = null;
117 | private $_id = null;
118 |
119 | /**
120 | * Fetch data and timestamp from memcache, store in instance
121 | *
122 | * @param string $id
123 | *
124 | * @return bool success
125 | */
126 | private function _fetch($id)
127 | {
128 | if ($this->_id === $id) {
129 | return true;
130 | }
131 | $ret = $this->_mc->get($id);
132 | if (false === $ret) {
133 | $this->_id = null;
134 | return false;
135 | }
136 | list($this->_lm, $this->_data) = explode('|', $ret, 2);
137 | $this->_id = $id;
138 | return true;
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/min/lib/Minify/Cache/XCache.php:
--------------------------------------------------------------------------------
1 |
14 | * Minify::setCache(new Minify_Cache_XCache());
15 | *
16 | *
17 | * @package Minify
18 | * @author Elan Ruusamäe
19 | **/
20 | class Minify_Cache_XCache {
21 |
22 | /**
23 | * Create a Minify_Cache_XCache object, to be passed to
24 | * Minify::setCache().
25 | *
26 | * @param int $expire seconds until expiration (default = 0
27 | * meaning the item will not get an expiration date)
28 | */
29 | public function __construct($expire = 0)
30 | {
31 | $this->_exp = $expire;
32 | }
33 |
34 | /**
35 | * Write data to cache.
36 | *
37 | * @param string $id cache id
38 | * @param string $data
39 | * @return bool success
40 | */
41 | public function store($id, $data)
42 | {
43 | return xcache_set($id, "{$_SERVER['REQUEST_TIME']}|{$data}", $this->_exp);
44 | }
45 |
46 | /**
47 | * Get the size of a cache entry
48 | *
49 | * @param string $id cache id
50 | * @return int size in bytes
51 | */
52 | public function getSize($id)
53 | {
54 | if (! $this->_fetch($id)) {
55 | return false;
56 | }
57 | return (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2))
58 | ? mb_strlen($this->_data, '8bit')
59 | : strlen($this->_data);
60 | }
61 |
62 | /**
63 | * Does a valid cache entry exist?
64 | *
65 | * @param string $id cache id
66 | * @param int $srcMtime mtime of the original source file(s)
67 | * @return bool exists
68 | */
69 | public function isValid($id, $srcMtime)
70 | {
71 | return ($this->_fetch($id) && ($this->_lm >= $srcMtime));
72 | }
73 |
74 | /**
75 | * Send the cached content to output
76 | *
77 | * @param string $id cache id
78 | */
79 | public function display($id)
80 | {
81 | echo $this->_fetch($id)
82 | ? $this->_data
83 | : '';
84 | }
85 |
86 | /**
87 | * Fetch the cached content
88 | *
89 | * @param string $id cache id
90 | * @return string
91 | */
92 | public function fetch($id)
93 | {
94 | return $this->_fetch($id)
95 | ? $this->_data
96 | : '';
97 | }
98 |
99 | private $_exp = null;
100 |
101 | // cache of most recently fetched id
102 | private $_lm = null;
103 | private $_data = null;
104 | private $_id = null;
105 |
106 | /**
107 | * Fetch data and timestamp from xcache, store in instance
108 | *
109 | * @param string $id
110 | * @return bool success
111 | */
112 | private function _fetch($id)
113 | {
114 | if ($this->_id === $id) {
115 | return true;
116 | }
117 | $ret = xcache_get($id);
118 | if (false === $ret) {
119 | $this->_id = null;
120 | return false;
121 | }
122 | list($this->_lm, $this->_data) = explode('|', $ret, 2);
123 | $this->_id = $id;
124 | return true;
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/min/lib/Minify/Cache/ZendPlatform.php:
--------------------------------------------------------------------------------
1 |
14 | * Minify::setCache(new Minify_Cache_ZendPlatform());
15 | *
16 | *
17 | * @package Minify
18 | * @author Patrick van Dissel
19 | */
20 | class Minify_Cache_ZendPlatform {
21 |
22 |
23 | /**
24 | * Create a Minify_Cache_ZendPlatform object, to be passed to
25 | * Minify::setCache().
26 | *
27 | * @param int $expire seconds until expiration (default = 0
28 | * meaning the item will not get an expiration date)
29 | *
30 | * @return null
31 | */
32 | public function __construct($expire = 0)
33 | {
34 | $this->_exp = $expire;
35 | }
36 |
37 |
38 | /**
39 | * Write data to cache.
40 | *
41 | * @param string $id cache id
42 | *
43 | * @param string $data
44 | *
45 | * @return bool success
46 | */
47 | public function store($id, $data)
48 | {
49 | return output_cache_put($id, "{$_SERVER['REQUEST_TIME']}|{$data}");
50 | }
51 |
52 |
53 | /**
54 | * Get the size of a cache entry
55 | *
56 | * @param string $id cache id
57 | *
58 | * @return int size in bytes
59 | */
60 | public function getSize($id)
61 | {
62 | return $this->_fetch($id)
63 | ? strlen($this->_data)
64 | : false;
65 | }
66 |
67 |
68 | /**
69 | * Does a valid cache entry exist?
70 | *
71 | * @param string $id cache id
72 | *
73 | * @param int $srcMtime mtime of the original source file(s)
74 | *
75 | * @return bool exists
76 | */
77 | public function isValid($id, $srcMtime)
78 | {
79 | $ret = ($this->_fetch($id) && ($this->_lm >= $srcMtime));
80 | return $ret;
81 | }
82 |
83 |
84 | /**
85 | * Send the cached content to output
86 | *
87 | * @param string $id cache id
88 | */
89 | public function display($id)
90 | {
91 | echo $this->_fetch($id)
92 | ? $this->_data
93 | : '';
94 | }
95 |
96 |
97 | /**
98 | * Fetch the cached content
99 | *
100 | * @param string $id cache id
101 | *
102 | * @return string
103 | */
104 | public function fetch($id)
105 | {
106 | return $this->_fetch($id)
107 | ? $this->_data
108 | : '';
109 | }
110 |
111 |
112 | private $_exp = null;
113 |
114 |
115 | // cache of most recently fetched id
116 | private $_lm = null;
117 | private $_data = null;
118 | private $_id = null;
119 |
120 |
121 | /**
122 | * Fetch data and timestamp from ZendPlatform, store in instance
123 | *
124 | * @param string $id
125 | *
126 | * @return bool success
127 | */
128 | private function _fetch($id)
129 | {
130 | if ($this->_id === $id) {
131 | return true;
132 | }
133 | $ret = output_cache_get($id, $this->_exp);
134 | if (false === $ret) {
135 | $this->_id = null;
136 | return false;
137 | }
138 | list($this->_lm, $this->_data) = explode('|', $ret, 2);
139 | $this->_id = $id;
140 | return true;
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/min/lib/Minify/ClosureCompiler.php:
--------------------------------------------------------------------------------
1 |
16 | * Minify_ClosureCompiler::$jarFile = '/path/to/closure-compiler-20120123.jar';
17 | * Minify_ClosureCompiler::$tempDir = '/tmp';
18 | * $code = Minify_ClosureCompiler::minify(
19 | * $code,
20 | * array('compilation_level' => 'SIMPLE_OPTIMIZATIONS')
21 | * );
22 | *
23 | * --compilation_level WHITESPACE_ONLY, SIMPLE_OPTIMIZATIONS, ADVANCED_OPTIMIZATIONS
24 | *
25 | *
26 | *
27 | * @todo unit tests, $options docs
28 | * @todo more options support (or should just passthru them all?)
29 | *
30 | * @package Minify
31 | * @author Stephen Clay
32 | * @author Elan Ruusamäe
33 | */
34 | class Minify_ClosureCompiler {
35 |
36 | /**
37 | * Filepath of the Closure Compiler jar file. This must be set before
38 | * calling minifyJs().
39 | *
40 | * @var string
41 | */
42 | public static $jarFile = null;
43 |
44 | /**
45 | * Writable temp directory. This must be set before calling minifyJs().
46 | *
47 | * @var string
48 | */
49 | public static $tempDir = null;
50 |
51 | /**
52 | * Filepath of "java" executable (may be needed if not in shell's PATH)
53 | *
54 | * @var string
55 | */
56 | public static $javaExecutable = 'java';
57 |
58 | /**
59 | * Minify a Javascript string
60 | *
61 | * @param string $js
62 | *
63 | * @param array $options (verbose is ignored)
64 | *
65 | * @see https://code.google.com/p/closure-compiler/source/browse/trunk/README
66 | *
67 | * @return string
68 | */
69 | public static function minify($js, $options = array())
70 | {
71 | self::_prepare();
72 | if (! ($tmpFile = tempnam(self::$tempDir, 'cc_'))) {
73 | throw new Exception('Minify_ClosureCompiler : could not create temp file.');
74 | }
75 | file_put_contents($tmpFile, $js);
76 | exec(self::_getCmd($options, $tmpFile), $output, $result_code);
77 | unlink($tmpFile);
78 | if ($result_code != 0) {
79 | throw new Exception('Minify_ClosureCompiler : Closure Compiler execution failed.');
80 | }
81 | return implode("\n", $output);
82 | }
83 |
84 | private static function _getCmd($userOptions, $tmpFile)
85 | {
86 | $o = array_merge(
87 | array(
88 | 'charset' => 'utf-8',
89 | 'compilation_level' => 'SIMPLE_OPTIMIZATIONS',
90 | ),
91 | $userOptions
92 | );
93 | $cmd = self::$javaExecutable . ' -jar ' . escapeshellarg(self::$jarFile)
94 | . (preg_match('/^[\\da-zA-Z0-9\\-]+$/', $o['charset'])
95 | ? " --charset {$o['charset']}"
96 | : '');
97 |
98 | foreach (array('compilation_level') as $opt) {
99 | if ($o[$opt]) {
100 | $cmd .= " --{$opt} ". escapeshellarg($o[$opt]);
101 | }
102 | }
103 | return $cmd . ' ' . escapeshellarg($tmpFile);
104 | }
105 |
106 | private static function _prepare()
107 | {
108 | if (! is_file(self::$jarFile)) {
109 | throw new Exception('Minify_ClosureCompiler : $jarFile('.self::$jarFile.') is not a valid file.');
110 | }
111 | if (! is_readable(self::$jarFile)) {
112 | throw new Exception('Minify_ClosureCompiler : $jarFile('.self::$jarFile.') is not readable.');
113 | }
114 | if (! is_dir(self::$tempDir)) {
115 | throw new Exception('Minify_ClosureCompiler : $tempDir('.self::$tempDir.') is not a valid direcotry.');
116 | }
117 | if (! is_writable(self::$tempDir)) {
118 | throw new Exception('Minify_ClosureCompiler : $tempDir('.self::$tempDir.') is not writable.');
119 | }
120 | }
121 | }
122 |
123 | /* vim:ts=4:sw=4:et */
124 |
--------------------------------------------------------------------------------
/min/lib/Minify/CommentPreserver.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | class Minify_CommentPreserver {
14 |
15 | /**
16 | * String to be prepended to each preserved comment
17 | *
18 | * @var string
19 | */
20 | public static $prepend = "\n";
21 |
22 | /**
23 | * String to be appended to each preserved comment
24 | *
25 | * @var string
26 | */
27 | public static $append = "\n";
28 |
29 | /**
30 | * Process a string outside of C-style comments that begin with "/*!"
31 | *
32 | * On each non-empty string outside these comments, the given processor
33 | * function will be called. The comments will be surrounded by
34 | * Minify_CommentPreserver::$preprend and Minify_CommentPreserver::$append.
35 | *
36 | * @param string $content
37 | * @param callback $processor function
38 | * @param array $args array of extra arguments to pass to the processor
39 | * function (default = array())
40 | * @return string
41 | */
42 | public static function process($content, $processor, $args = array())
43 | {
44 | $ret = '';
45 | while (true) {
46 | list($beforeComment, $comment, $afterComment) = self::_nextComment($content);
47 | if ('' !== $beforeComment) {
48 | $callArgs = $args;
49 | array_unshift($callArgs, $beforeComment);
50 | $ret .= call_user_func_array($processor, $callArgs);
51 | }
52 | if (false === $comment) {
53 | break;
54 | }
55 | $ret .= $comment;
56 | $content = $afterComment;
57 | }
58 | return $ret;
59 | }
60 |
61 | /**
62 | * Extract comments that YUI Compressor preserves.
63 | *
64 | * @param string $in input
65 | *
66 | * @return array 3 elements are returned. If a YUI comment is found, the
67 | * 2nd element is the comment and the 1st and 3rd are the surrounding
68 | * strings. If no comment is found, the entire string is returned as the
69 | * 1st element and the other two are false.
70 | */
71 | private static function _nextComment($in)
72 | {
73 | if (
74 | false === ($start = strpos($in, '/*!'))
75 | || false === ($end = strpos($in, '*/', $start + 3))
76 | ) {
77 | return array($in, false, false);
78 | }
79 | $ret = array(
80 | substr($in, 0, $start)
81 | ,self::$prepend . '/*!' . substr($in, $start + 3, $end - $start - 1) . self::$append
82 | );
83 | $endChars = (strlen($in) - $end - 2);
84 | $ret[] = (0 === $endChars)
85 | ? ''
86 | : substr($in, -$endChars);
87 | return $ret;
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/min/lib/Minify/Controller/Base.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | abstract class Minify_Controller_Base {
18 |
19 | /**
20 | * Setup controller sources and set an needed options for Minify::source
21 | *
22 | * You must override this method in your subclass controller to set
23 | * $this->sources. If the request is NOT valid, make sure $this->sources
24 | * is left an empty array. Then strip any controller-specific options from
25 | * $options and return it. To serve files, $this->sources must be an array of
26 | * Minify_Source objects.
27 | *
28 | * @param array $options controller and Minify options
29 | *
30 | * @return array $options Minify::serve options
31 | */
32 | abstract public function setupSources($options);
33 |
34 | /**
35 | * Get default Minify options for this controller.
36 | *
37 | * Override in subclass to change defaults
38 | *
39 | * @return array options for Minify
40 | */
41 | public function getDefaultMinifyOptions() {
42 | return array(
43 | 'isPublic' => true
44 | ,'encodeOutput' => function_exists('gzdeflate')
45 | ,'encodeMethod' => null // determine later
46 | ,'encodeLevel' => 9
47 | ,'minifierOptions' => array() // no minifier options
48 | ,'contentTypeCharset' => 'utf-8'
49 | ,'maxAge' => 1800 // 30 minutes
50 | ,'rewriteCssUris' => true
51 | ,'bubbleCssImports' => false
52 | ,'quiet' => false // serve() will send headers and output
53 | ,'debug' => false
54 |
55 | // if you override these, the response codes MUST be directly after
56 | // the first space.
57 | ,'badRequestHeader' => 'HTTP/1.0 400 Bad Request'
58 | ,'errorHeader' => 'HTTP/1.0 500 Internal Server Error'
59 |
60 | // callback function to see/modify content of all sources
61 | ,'postprocessor' => null
62 | // file to require to load preprocessor
63 | ,'postprocessorRequire' => null
64 | );
65 | }
66 |
67 | /**
68 | * Get default minifiers for this controller.
69 | *
70 | * Override in subclass to change defaults
71 | *
72 | * @return array minifier callbacks for common types
73 | */
74 | public function getDefaultMinifers() {
75 | $ret[Minify::TYPE_JS] = array('JSMin', 'minify');
76 | $ret[Minify::TYPE_CSS] = array('Minify_CSS', 'minify');
77 | $ret[Minify::TYPE_HTML] = array('Minify_HTML', 'minify');
78 | return $ret;
79 | }
80 |
81 | /**
82 | * Is a user-given file within an allowable directory, existing,
83 | * and having an extension js/css/html/txt ?
84 | *
85 | * This is a convenience function for controllers that have to accept
86 | * user-given paths
87 | *
88 | * @param string $file full file path (already processed by realpath())
89 | *
90 | * @param array $safeDirs directories where files are safe to serve. Files can also
91 | * be in subdirectories of these directories.
92 | *
93 | * @return bool file is safe
94 | *
95 | * @deprecated use checkAllowDirs, checkNotHidden instead
96 | */
97 | public static function _fileIsSafe($file, $safeDirs)
98 | {
99 | $pathOk = false;
100 | foreach ((array)$safeDirs as $safeDir) {
101 | if (strpos($file, $safeDir) === 0) {
102 | $pathOk = true;
103 | break;
104 | }
105 | }
106 | $base = basename($file);
107 | if (! $pathOk || ! is_file($file) || $base[0] === '.') {
108 | return false;
109 | }
110 | list($revExt) = explode('.', strrev($base));
111 | return in_array(strrev($revExt), array('js', 'css', 'html', 'txt'));
112 | }
113 |
114 | /**
115 | * @param string $file
116 | * @param array $allowDirs
117 | * @param string $uri
118 | * @return bool
119 | * @throws Exception
120 | */
121 | public static function checkAllowDirs($file, $allowDirs, $uri)
122 | {
123 | foreach ((array)$allowDirs as $allowDir) {
124 | if (strpos($file, $allowDir) === 0) {
125 | return true;
126 | }
127 | }
128 | throw new Exception("File '$file' is outside \$allowDirs. If the path is"
129 | . " resolved via an alias/symlink, look into the \$min_symlinks option."
130 | . " E.g. \$min_symlinks['/" . dirname($uri) . "'] = '" . dirname($file) . "';");
131 | }
132 |
133 | /**
134 | * @param string $file
135 | * @throws Exception
136 | */
137 | public static function checkNotHidden($file)
138 | {
139 | $b = basename($file);
140 | if (0 === strpos($b, '.')) {
141 | throw new Exception("Filename '$b' starts with period (may be hidden)");
142 | }
143 | }
144 |
145 | /**
146 | * instances of Minify_Source, which provide content and any individual minification needs.
147 | *
148 | * @var array
149 | *
150 | * @see Minify_Source
151 | */
152 | public $sources = array();
153 |
154 | /**
155 | * Short name to place inside cache id
156 | *
157 | * The setupSources() method may choose to set this, making it easier to
158 | * recognize a particular set of sources/settings in the cache folder. It
159 | * will be filtered and truncated to make the final cache id <= 250 bytes.
160 | *
161 | * @var string
162 | */
163 | public $selectionId = '';
164 |
165 | /**
166 | * Mix in default controller options with user-given options
167 | *
168 | * @param array $options user options
169 | *
170 | * @return array mixed options
171 | */
172 | public final function mixInDefaultOptions($options)
173 | {
174 | $ret = array_merge(
175 | $this->getDefaultMinifyOptions(), $options
176 | );
177 | if (! isset($options['minifiers'])) {
178 | $options['minifiers'] = array();
179 | }
180 | $ret['minifiers'] = array_merge(
181 | $this->getDefaultMinifers(), $options['minifiers']
182 | );
183 | return $ret;
184 | }
185 |
186 | /**
187 | * Analyze sources (if there are any) and set $options 'contentType'
188 | * and 'lastModifiedTime' if they already aren't.
189 | *
190 | * @param array $options options for Minify
191 | *
192 | * @return array options for Minify
193 | */
194 | public final function analyzeSources($options = array())
195 | {
196 | if ($this->sources) {
197 | if (! isset($options['contentType'])) {
198 | $options['contentType'] = Minify_Source::getContentType($this->sources);
199 | }
200 | // last modified is needed for caching, even if setExpires is set
201 | if (! isset($options['lastModifiedTime'])) {
202 | $max = 0;
203 | foreach ($this->sources as $source) {
204 | $max = max($source->lastModified, $max);
205 | }
206 | $options['lastModifiedTime'] = $max;
207 | }
208 | }
209 | return $options;
210 | }
211 |
212 | /**
213 | * Send message to the Minify logger
214 | *
215 | * @param string $msg
216 | *
217 | * @return null
218 | */
219 | public function log($msg) {
220 | Minify_Logger::log($msg);
221 | }
222 | }
223 |
--------------------------------------------------------------------------------
/min/lib/Minify/Controller/Files.php:
--------------------------------------------------------------------------------
1 |
12 | * Minify::serve('Files', array(
13 | * 'files' => array(
14 | * '//js/jquery.js'
15 | * ,'//js/plugins.js'
16 | * ,'/home/username/file.js'
17 | * )
18 | * ));
19 | *
20 | *
21 | * As a shortcut, the controller will replace "//" at the beginning
22 | * of a filename with $_SERVER['DOCUMENT_ROOT'] . '/'.
23 | *
24 | * @package Minify
25 | * @author Stephen Clay
26 | */
27 | class Minify_Controller_Files extends Minify_Controller_Base {
28 |
29 | /**
30 | * Set up file sources
31 | *
32 | * @param array $options controller and Minify options
33 | * @return array Minify options
34 | *
35 | * Controller options:
36 | *
37 | * 'files': (required) array of complete file paths, or a single path
38 | */
39 | public function setupSources($options) {
40 | // strip controller options
41 |
42 | $files = $options['files'];
43 | // if $files is a single object, casting will break it
44 | if (is_object($files)) {
45 | $files = array($files);
46 | } elseif (! is_array($files)) {
47 | $files = (array)$files;
48 | }
49 | unset($options['files']);
50 |
51 | $sources = array();
52 | foreach ($files as $file) {
53 | if ($file instanceof Minify_Source) {
54 | $sources[] = $file;
55 | continue;
56 | }
57 | if (0 === strpos($file, '//')) {
58 | $file = $_SERVER['DOCUMENT_ROOT'] . substr($file, 1);
59 | }
60 | $realPath = realpath($file);
61 | if (is_file($realPath)) {
62 | $sources[] = new Minify_Source(array(
63 | 'filepath' => $realPath
64 | ));
65 | } else {
66 | $this->log("The path \"{$file}\" could not be found (or was not a file)");
67 | return $options;
68 | }
69 | }
70 | if ($sources) {
71 | $this->sources = $sources;
72 | }
73 | return $options;
74 | }
75 | }
76 |
77 |
--------------------------------------------------------------------------------
/min/lib/Minify/Controller/Groups.php:
--------------------------------------------------------------------------------
1 |
12 | * Minify::serve('Groups', array(
13 | * 'groups' => array(
14 | * 'css' => array('//css/type.css', '//css/layout.css')
15 | * ,'js' => array('//js/jquery.js', '//js/site.js')
16 | * )
17 | * ));
18 | *
19 | *
20 | * If the above code were placed in /serve.php, it would enable the URLs
21 | * /serve.php/js and /serve.php/css
22 | *
23 | * As a shortcut, the controller will replace "//" at the beginning
24 | * of a filename with $_SERVER['DOCUMENT_ROOT'] . '/'.
25 | *
26 | * @package Minify
27 | * @author Stephen Clay
28 | */
29 | class Minify_Controller_Groups extends Minify_Controller_Base {
30 |
31 | /**
32 | * Set up groups of files as sources
33 | *
34 | * @param array $options controller and Minify options
35 | *
36 | * 'groups': (required) array mapping PATH_INFO strings to arrays
37 | * of complete file paths. @see Minify_Controller_Groups
38 | *
39 | * @return array Minify options
40 | */
41 | public function setupSources($options) {
42 | // strip controller options
43 | $groups = $options['groups'];
44 | unset($options['groups']);
45 |
46 | // mod_fcgid places PATH_INFO in ORIG_PATH_INFO
47 | $pi = isset($_SERVER['ORIG_PATH_INFO'])
48 | ? substr($_SERVER['ORIG_PATH_INFO'], 1)
49 | : (isset($_SERVER['PATH_INFO'])
50 | ? substr($_SERVER['PATH_INFO'], 1)
51 | : false
52 | );
53 | if (false === $pi || ! isset($groups[$pi])) {
54 | // no PATH_INFO or not a valid group
55 | $this->log("Missing PATH_INFO or no group set for \"$pi\"");
56 | return $options;
57 | }
58 | $sources = array();
59 |
60 | $files = $groups[$pi];
61 | // if $files is a single object, casting will break it
62 | if (is_object($files)) {
63 | $files = array($files);
64 | } elseif (! is_array($files)) {
65 | $files = (array)$files;
66 | }
67 | foreach ($files as $file) {
68 | if ($file instanceof Minify_Source) {
69 | $sources[] = $file;
70 | continue;
71 | }
72 | if (0 === strpos($file, '//')) {
73 | $file = $_SERVER['DOCUMENT_ROOT'] . substr($file, 1);
74 | }
75 | $realPath = realpath($file);
76 | if (is_file($realPath)) {
77 | $sources[] = new Minify_Source(array(
78 | 'filepath' => $realPath
79 | ));
80 | } else {
81 | $this->log("The path \"{$file}\" could not be found (or was not a file)");
82 | return $options;
83 | }
84 | }
85 | if ($sources) {
86 | $this->sources = $sources;
87 | }
88 | return $options;
89 | }
90 | }
91 |
92 |
--------------------------------------------------------------------------------
/min/lib/Minify/Controller/MinApp.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | class Minify_Controller_MinApp extends Minify_Controller_Base {
14 |
15 | /**
16 | * Set up groups of files as sources
17 | *
18 | * @param array $options controller and Minify options
19 | *
20 | * @return array Minify options
21 | */
22 | public function setupSources($options) {
23 | // PHP insecure by default: realpath() and other FS functions can't handle null bytes.
24 | foreach (array('g', 'b', 'f') as $key) {
25 | if (isset($_GET[$key])) {
26 | $_GET[$key] = str_replace("\x00", '', (string)$_GET[$key]);
27 | }
28 | }
29 |
30 | // filter controller options
31 | $cOptions = array_merge(
32 | array(
33 | 'allowDirs' => '//'
34 | ,'groupsOnly' => false
35 | ,'groups' => array()
36 | ,'noMinPattern' => '@[-\\.]min\\.(?:js|css)$@i' // matched against basename
37 | )
38 | ,(isset($options['minApp']) ? $options['minApp'] : array())
39 | );
40 | unset($options['minApp']);
41 | $sources = array();
42 | $this->selectionId = '';
43 | $firstMissingResource = null;
44 | if (isset($_GET['g'])) {
45 | // add group(s)
46 | $this->selectionId .= 'g=' . $_GET['g'];
47 | $keys = explode(',', $_GET['g']);
48 | if ($keys != array_unique($keys)) {
49 | $this->log("Duplicate group key found.");
50 | return $options;
51 | }
52 | $keys = explode(',', $_GET['g']);
53 | foreach ($keys as $key) {
54 | if (! isset($cOptions['groups'][$key])) {
55 | $this->log("A group configuration for \"{$key}\" was not found");
56 | return $options;
57 | }
58 | $files = $cOptions['groups'][$key];
59 | // if $files is a single object, casting will break it
60 | if (is_object($files)) {
61 | $files = array($files);
62 | } elseif (! is_array($files)) {
63 | $files = (array)$files;
64 | }
65 | foreach ($files as $file) {
66 | if ($file instanceof Minify_Source) {
67 | $sources[] = $file;
68 | continue;
69 | }
70 | if (0 === strpos($file, '//')) {
71 | $file = $_SERVER['DOCUMENT_ROOT'] . substr($file, 1);
72 | }
73 | $realpath = realpath($file);
74 | if ($realpath && is_file($realpath)) {
75 | $sources[] = $this->_getFileSource($realpath, $cOptions);
76 | } else {
77 | $this->log("The path \"{$file}\" (realpath \"{$realpath}\") could not be found (or was not a file)");
78 | if (null === $firstMissingResource) {
79 | $firstMissingResource = basename($file);
80 | continue;
81 | } else {
82 | $secondMissingResource = basename($file);
83 | $this->log("More than one file was missing: '$firstMissingResource', '$secondMissingResource'");
84 | return $options;
85 | }
86 | }
87 | }
88 | if ($sources) {
89 | try {
90 | $this->checkType($sources[0]);
91 | } catch (Exception $e) {
92 | $this->log($e->getMessage());
93 | return $options;
94 | }
95 | }
96 | }
97 | }
98 | if (! $cOptions['groupsOnly'] && isset($_GET['f'])) {
99 | // try user files
100 | // The following restrictions are to limit the URLs that minify will
101 | // respond to.
102 | if (// verify at least one file, files are single comma separated,
103 | // and are all same extension
104 | ! preg_match('/^[^,]+\\.(css|js)(?:,[^,]+\\.\\1)*$/', $_GET['f'], $m)
105 | // no "//"
106 | || strpos($_GET['f'], '//') !== false
107 | // no "\"
108 | || strpos($_GET['f'], '\\') !== false
109 | ) {
110 | $this->log("GET param 'f' was invalid");
111 | return $options;
112 | }
113 | $ext = ".{$m[1]}";
114 | try {
115 | $this->checkType($m[1]);
116 | } catch (Exception $e) {
117 | $this->log($e->getMessage());
118 | return $options;
119 | }
120 | $files = explode(',', $_GET['f']);
121 | if ($files != array_unique($files)) {
122 | $this->log("Duplicate files were specified");
123 | return $options;
124 | }
125 | if (isset($_GET['b'])) {
126 | // check for validity
127 | if (preg_match('@^[^/]+(?:/[^/]+)*$@', $_GET['b'])
128 | && false === strpos($_GET['b'], '..')
129 | && $_GET['b'] !== '.') {
130 | // valid base
131 | $base = "/{$_GET['b']}/";
132 | } else {
133 | $this->log("GET param 'b' was invalid");
134 | return $options;
135 | }
136 | } else {
137 | $base = '/';
138 | }
139 | $allowDirs = array();
140 | foreach ((array)$cOptions['allowDirs'] as $allowDir) {
141 | $allowDirs[] = realpath(str_replace('//', $_SERVER['DOCUMENT_ROOT'] . '/', $allowDir));
142 | }
143 | $basenames = array(); // just for cache id
144 | foreach ($files as $file) {
145 | $uri = $base . $file;
146 | $path = $_SERVER['DOCUMENT_ROOT'] . $uri;
147 | $realpath = realpath($path);
148 | if (false === $realpath || ! is_file($realpath)) {
149 | $this->log("The path \"{$path}\" (realpath \"{$realpath}\") could not be found (or was not a file)");
150 | if (null === $firstMissingResource) {
151 | $firstMissingResource = $uri;
152 | continue;
153 | } else {
154 | $secondMissingResource = $uri;
155 | $this->log("More than one file was missing: '$firstMissingResource', '$secondMissingResource`'");
156 | return $options;
157 | }
158 | }
159 | try {
160 | parent::checkNotHidden($realpath);
161 | parent::checkAllowDirs($realpath, $allowDirs, $uri);
162 | } catch (Exception $e) {
163 | $this->log($e->getMessage());
164 | return $options;
165 | }
166 | $sources[] = $this->_getFileSource($realpath, $cOptions);
167 | $basenames[] = basename($realpath, $ext);
168 | }
169 | if ($this->selectionId) {
170 | $this->selectionId .= '_f=';
171 | }
172 | $this->selectionId .= implode(',', $basenames) . $ext;
173 | }
174 | if ($sources) {
175 | if (null !== $firstMissingResource) {
176 | array_unshift($sources, new Minify_Source(array(
177 | 'id' => 'missingFile'
178 | // should not cause cache invalidation
179 | ,'lastModified' => 0
180 | // due to caching, filename is unreliable.
181 | ,'content' => "/* Minify: at least one missing file. See " . Minify::URL_DEBUG . " */\n"
182 | ,'minifier' => ''
183 | )));
184 | }
185 | $this->sources = $sources;
186 | } else {
187 | $this->log("No sources to serve");
188 | }
189 | return $options;
190 | }
191 |
192 | /**
193 | * @param string $file
194 | *
195 | * @param array $cOptions
196 | *
197 | * @return Minify_Source
198 | */
199 | protected function _getFileSource($file, $cOptions)
200 | {
201 | $spec['filepath'] = $file;
202 | if ($cOptions['noMinPattern'] && preg_match($cOptions['noMinPattern'], basename($file))) {
203 | if (preg_match('~\.css$~i', $file)) {
204 | $spec['minifyOptions']['compress'] = false;
205 | } else {
206 | $spec['minifier'] = '';
207 | }
208 | }
209 | return new Minify_Source($spec);
210 | }
211 |
212 | protected $_type = null;
213 |
214 | /**
215 | * Make sure that only source files of a single type are registered
216 | *
217 | * @param string $sourceOrExt
218 | *
219 | * @throws Exception
220 | */
221 | public function checkType($sourceOrExt)
222 | {
223 | if ($sourceOrExt === 'js') {
224 | $type = Minify::TYPE_JS;
225 | } elseif ($sourceOrExt === 'css') {
226 | $type = Minify::TYPE_CSS;
227 | } elseif ($sourceOrExt->contentType !== null) {
228 | $type = $sourceOrExt->contentType;
229 | } else {
230 | return;
231 | }
232 | if ($this->_type === null) {
233 | $this->_type = $type;
234 | } elseif ($this->_type !== $type) {
235 | throw new Exception('Content-Type mismatch');
236 | }
237 | }
238 | }
239 |
--------------------------------------------------------------------------------
/min/lib/Minify/Controller/Page.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | class Minify_Controller_Page extends Minify_Controller_Base {
15 |
16 | /**
17 | * Set up source of HTML content
18 | *
19 | * @param array $options controller and Minify options
20 | * @return array Minify options
21 | *
22 | * Controller options:
23 | *
24 | * 'content': (required) HTML markup
25 | *
26 | * 'id': (required) id of page (string for use in server-side caching)
27 | *
28 | * 'lastModifiedTime': timestamp of when this content changed. This
29 | * is recommended to allow both server and client-side caching.
30 | *
31 | * 'minifyAll': should all CSS and Javascript blocks be individually
32 | * minified? (default false)
33 | *
34 | * @todo Add 'file' option to read HTML file.
35 | */
36 | public function setupSources($options) {
37 | if (isset($options['file'])) {
38 | $sourceSpec = array(
39 | 'filepath' => $options['file']
40 | );
41 | $f = $options['file'];
42 | } else {
43 | // strip controller options
44 | $sourceSpec = array(
45 | 'content' => $options['content']
46 | ,'id' => $options['id']
47 | );
48 | $f = $options['id'];
49 | unset($options['content'], $options['id']);
50 | }
51 | // something like "builder,index.php" or "directory,file.html"
52 | $this->selectionId = strtr(substr($f, 1 + strlen(dirname(dirname($f)))), '/\\', ',,');
53 |
54 | if (isset($options['minifyAll'])) {
55 | // this will be the 2nd argument passed to Minify_HTML::minify()
56 | $sourceSpec['minifyOptions'] = array(
57 | 'cssMinifier' => array('Minify_CSS', 'minify')
58 | ,'jsMinifier' => array('JSMin', 'minify')
59 | );
60 | unset($options['minifyAll']);
61 | }
62 | $this->sources[] = new Minify_Source($sourceSpec);
63 |
64 | $options['contentType'] = Minify::TYPE_HTML;
65 | return $options;
66 | }
67 | }
68 |
69 |
--------------------------------------------------------------------------------
/min/lib/Minify/Controller/Version1.php:
--------------------------------------------------------------------------------
1 |
11 | * Minify::serve('Version1');
12 | *
13 | *
14 | * @package Minify
15 | * @author Stephen Clay
16 | */
17 | class Minify_Controller_Version1 extends Minify_Controller_Base {
18 |
19 | /**
20 | * Set up groups of files as sources
21 | *
22 | * @param array $options controller and Minify options
23 | * @return array Minify options
24 | *
25 | */
26 | public function setupSources($options) {
27 | // PHP insecure by default: realpath() and other FS functions can't handle null bytes.
28 | if (isset($_GET['files'])) {
29 | $_GET['files'] = str_replace("\x00", '', (string)$_GET['files']);
30 | }
31 |
32 | self::_setupDefines();
33 | if (MINIFY_USE_CACHE) {
34 | $cacheDir = defined('MINIFY_CACHE_DIR')
35 | ? MINIFY_CACHE_DIR
36 | : '';
37 | Minify::setCache($cacheDir);
38 | }
39 | $options['badRequestHeader'] = 'HTTP/1.0 404 Not Found';
40 | $options['contentTypeCharset'] = MINIFY_ENCODING;
41 |
42 | // The following restrictions are to limit the URLs that minify will
43 | // respond to. Ideally there should be only one way to reference a file.
44 | if (! isset($_GET['files'])
45 | // verify at least one file, files are single comma separated,
46 | // and are all same extension
47 | || ! preg_match('/^[^,]+\\.(css|js)(,[^,]+\\.\\1)*$/', $_GET['files'], $m)
48 | // no "//" (makes URL rewriting easier)
49 | || strpos($_GET['files'], '//') !== false
50 | // no "\"
51 | || strpos($_GET['files'], '\\') !== false
52 | // no "./"
53 | || preg_match('/(?:^|[^\\.])\\.\\//', $_GET['files'])
54 | ) {
55 | return $options;
56 | }
57 |
58 | $files = explode(',', $_GET['files']);
59 | if (count($files) > MINIFY_MAX_FILES) {
60 | return $options;
61 | }
62 |
63 | // strings for prepending to relative/absolute paths
64 | $prependRelPaths = dirname($_SERVER['SCRIPT_FILENAME'])
65 | . DIRECTORY_SEPARATOR;
66 | $prependAbsPaths = $_SERVER['DOCUMENT_ROOT'];
67 |
68 | $goodFiles = array();
69 | $hasBadSource = false;
70 |
71 | $allowDirs = isset($options['allowDirs'])
72 | ? $options['allowDirs']
73 | : MINIFY_BASE_DIR;
74 |
75 | foreach ($files as $file) {
76 | // prepend appropriate string for abs/rel paths
77 | $file = ($file[0] === '/' ? $prependAbsPaths : $prependRelPaths) . $file;
78 | // make sure a real file!
79 | $file = realpath($file);
80 | // don't allow unsafe or duplicate files
81 | if (parent::_fileIsSafe($file, $allowDirs)
82 | && !in_array($file, $goodFiles))
83 | {
84 | $goodFiles[] = $file;
85 | $srcOptions = array(
86 | 'filepath' => $file
87 | );
88 | $this->sources[] = new Minify_Source($srcOptions);
89 | } else {
90 | $hasBadSource = true;
91 | break;
92 | }
93 | }
94 | if ($hasBadSource) {
95 | $this->sources = array();
96 | }
97 | if (! MINIFY_REWRITE_CSS_URLS) {
98 | $options['rewriteCssUris'] = false;
99 | }
100 | return $options;
101 | }
102 |
103 | private static function _setupDefines()
104 | {
105 | $defaults = array(
106 | 'MINIFY_BASE_DIR' => realpath($_SERVER['DOCUMENT_ROOT'])
107 | ,'MINIFY_ENCODING' => 'utf-8'
108 | ,'MINIFY_MAX_FILES' => 16
109 | ,'MINIFY_REWRITE_CSS_URLS' => true
110 | ,'MINIFY_USE_CACHE' => true
111 | );
112 | foreach ($defaults as $const => $val) {
113 | if (! defined($const)) {
114 | define($const, $val);
115 | }
116 | }
117 | }
118 | }
119 |
120 |
--------------------------------------------------------------------------------
/min/lib/Minify/DebugDetector.php:
--------------------------------------------------------------------------------
1 |
8 | */
9 | class Minify_DebugDetector {
10 | public static function shouldDebugRequest($cookie, $get, $requestUri)
11 | {
12 | if (isset($get['debug'])) {
13 | return true;
14 | }
15 | if (! empty($cookie['minifyDebug'])) {
16 | foreach (preg_split('/\\s+/', $cookie['minifyDebug']) as $debugUri) {
17 | $pattern = '@' . preg_quote($debugUri, '@') . '@i';
18 | $pattern = str_replace(array('\\*', '\\?'), array('.*', '.'), $pattern);
19 | if (preg_match($pattern, $requestUri)) {
20 | return true;
21 | }
22 | }
23 | }
24 | return false;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/min/lib/Minify/HTML.php:
--------------------------------------------------------------------------------
1 |
18 | */
19 | class Minify_HTML {
20 | /**
21 | * @var boolean
22 | */
23 | protected $_jsCleanComments = true;
24 |
25 | /**
26 | * "Minify" an HTML page
27 | *
28 | * @param string $html
29 | *
30 | * @param array $options
31 | *
32 | * 'cssMinifier' : (optional) callback function to process content of STYLE
33 | * elements.
34 | *
35 | * 'jsMinifier' : (optional) callback function to process content of SCRIPT
36 | * elements. Note: the type attribute is ignored.
37 | *
38 | * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
39 | * unset, minify will sniff for an XHTML doctype.
40 | *
41 | * @return string
42 | */
43 | public static function minify($html, $options = array()) {
44 | $min = new self($html, $options);
45 | return $min->process();
46 | }
47 |
48 |
49 | /**
50 | * Create a minifier object
51 | *
52 | * @param string $html
53 | *
54 | * @param array $options
55 | *
56 | * 'cssMinifier' : (optional) callback function to process content of STYLE
57 | * elements.
58 | *
59 | * 'jsMinifier' : (optional) callback function to process content of SCRIPT
60 | * elements. Note: the type attribute is ignored.
61 | *
62 | * 'jsCleanComments' : (optional) whether to remove HTML comments beginning and end of script block
63 | *
64 | * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
65 | * unset, minify will sniff for an XHTML doctype.
66 | *
67 | * @return null
68 | */
69 | public function __construct($html, $options = array())
70 | {
71 | $this->_html = str_replace("\r\n", "\n", trim($html));
72 | if (isset($options['xhtml'])) {
73 | $this->_isXhtml = (bool)$options['xhtml'];
74 | }
75 | if (isset($options['cssMinifier'])) {
76 | $this->_cssMinifier = $options['cssMinifier'];
77 | }
78 | if (isset($options['jsMinifier'])) {
79 | $this->_jsMinifier = $options['jsMinifier'];
80 | }
81 | if (isset($options['jsCleanComments'])) {
82 | $this->_jsCleanComments = (bool)$options['jsCleanComments'];
83 | }
84 | }
85 |
86 |
87 | /**
88 | * Minify the markeup given in the constructor
89 | *
90 | * @return string
91 | */
92 | public function process()
93 | {
94 | if ($this->_isXhtml === null) {
95 | $this->_isXhtml = (false !== strpos($this->_html, '_html);
149 |
150 | // fill placeholders
151 | $this->_html = str_replace(
152 | array_keys($this->_placeholders)
153 | ,array_values($this->_placeholders)
154 | ,$this->_html
155 | );
156 | // issue 229: multi-pass to catch scripts that didn't get replaced in textareas
157 | $this->_html = str_replace(
158 | array_keys($this->_placeholders)
159 | ,array_values($this->_placeholders)
160 | ,$this->_html
161 | );
162 | return $this->_html;
163 | }
164 |
165 | protected function _commentCB($m)
166 | {
167 | return (0 === strpos($m[1], '[') || false !== strpos($m[1], '_replacementHash . count($this->_placeholders) . '%';
175 | $this->_placeholders[$placeholder] = $content;
176 | return $placeholder;
177 | }
178 |
179 | protected $_isXhtml = null;
180 | protected $_replacementHash = null;
181 | protected $_placeholders = array();
182 | protected $_cssMinifier = null;
183 | protected $_jsMinifier = null;
184 |
185 | protected function _removePreCB($m)
186 | {
187 | return $this->_reservePlace("