MDL-53695 minify: switch to MatthiasMullie\Minify

The previously used minification libraries were:
a) Unmaintained
b) Had some silly licensing issues (MDL-36457)

The new library is smaller, maintained, active and GPL-compatible.
This commit is contained in:
Dan Poltawski
2016-10-10 13:17:04 +01:00
parent d9520bc04e
commit 4cb24aa9d7
68 changed files with 2113 additions and 11458 deletions
+2 -1
View File
@@ -20,7 +20,8 @@ lib/phpexcel/
lib/google/
lib/htmlpurifier/
lib/jabber/
lib/minify/
lib/minify/matthiasmullie-minify/
lib/minify/matthiasmullie-pathconverter/
lib/flowplayer/
lib/pear/Auth/RADIUS.php
lib/pear/Crypt/CHAP.php
+2 -1
View File
@@ -19,7 +19,8 @@ lib/phpexcel/
lib/google/
lib/htmlpurifier/
lib/jabber/
lib/minify/
lib/minify/matthiasmullie-minify/
lib/minify/matthiasmullie-pathconverter/
lib/flowplayer/
lib/pear/Auth/RADIUS.php
lib/pear/Crypt/CHAP.php
+3 -1
View File
@@ -79,7 +79,9 @@ class core_component {
'Sabberworm\\CSS' => 'lib/php-css-parser',
'MoodleHQ\\RTLCSS' => 'lib/rtlcss',
'Leafo\\ScssPhp' => 'lib/scssphp',
'Box\\Spout' => 'lib/spout/src/Spout'
'Box\\Spout' => 'lib/spout/src/Spout',
'MatthiasMullie\\Minify' => 'lib/minify/matthiasmullie-minify/src/',
'MatthiasMullie\\PathConverter' => 'lib/minify/matthiasmullie-pathconverter/src/',
);
/**
+4 -19
View File
@@ -35,18 +35,9 @@ class core_minify {
* @return string minified JS code
*/
public static function js($content) {
global $CFG;
require_once("$CFG->libdir/minify/lib/JSMinPlus.php");
try {
ob_start(); // JSMinPlus just echos errors, weird...
$compressed = JSMinPlus::minify($content);
if ($compressed !== false) {
ob_end_clean();
return $compressed;
}
$error = ob_get_clean();
$minifier = new MatthiasMullie\Minify\JS($content);
return $minifier->minify();
} catch (Exception $e) {
ob_end_clean();
$error = $e->getMessage();
@@ -97,16 +88,10 @@ EOD;
* @return string minified CSS
*/
public static function css($content) {
global $CFG;
require_once("$CFG->libdir/minify/lib/Minify/CSS/Compressor.php");
$error = 'unknown';
try {
$compressed = Minify_CSS_Compressor::process($content);
if ($compressed !== false) {
return $compressed;
}
$minifier = new MatthiasMullie\Minify\CSS($content);
return $minifier->minify();
} catch (Exception $e) {
$error = $e->getMessage();
}
-26
View File
@@ -1,26 +0,0 @@
Copyright (c) 2008 Ryan Grove <[email protected]>
Copyright (c) 2008 Steve Clay <[email protected]>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of this project nor the names of its contributors may be
used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-206
View File
@@ -1,206 +0,0 @@
<?php
/**
* Configuration for "min", the default application built with the Minify
* library
*
* @package Minify
*/
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 = $CFG->debugdeveloper;
$min_cachePath = $CFG->tempdir;
$min_documentRoot = $CFG->dirroot.'/lib/minify';
$min_cacheFileLocking = empty($CFG->preventfilelocking);
$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
return; // end of moodle modification
/**
* Allow use of the Minify URI Builder app. Only set this to true while you need it.
*/
$min_enableBuilder = false;
/**
* If non-empty, the Builder will be protected with HTTP Digest auth.
* The username is "admin".
*/
$min_builderPassword = 'admin';
/**
* Set to true to log messages to FirePHP (Firefox Firebug addon).
* Set to false for no error logging (Minify may be slightly faster).
* @link http://www.firephp.org/
*
* If you want to use a custom error logger, set this to your logger
* instance. Your object should have a method log(string $message).
*/
$min_errorLogger = false;
/**
* To allow debug mode output, you must set this option to true.
*
* Once true, you can send the cookie minDebug to request debug mode output. The
* cookie value should match the URIs you'd like to debug. E.g. to debug
* /min/f=file1.js send the cookie minDebug=file1.js
* You can manually enable debugging by appending "&debug" to a URI.
* E.g. /min/?f=script1.js,script2.js&debug
*
* In 'debug' mode, Minify combines files with no minification and adds comments
* to indicate line #s of the original files.
*/
$min_allowDebugFlag = false;
/**
* For best performance, specify your temp directory here. Otherwise Minify
* will have to load extra code to guess. Some examples below:
*/
//$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();
/**
* Leave an empty string to use PHP's $_SERVER['DOCUMENT_ROOT'].
*
* On some servers, this value may be misconfigured or missing. If so, set this
* to your full document root path with no trailing slash.
* E.g. '/home/accountname/public_html' or 'c:\\xampp\\htdocs'
*
* If /min/ is directly inside your document root, just uncomment the
* second line. The third line might work on some Apache servers.
*/
$min_documentRoot = '';
//$min_documentRoot = substr(__FILE__, 0, -15);
//$min_documentRoot = $_SERVER['SUBDOMAIN_DOCUMENT_ROOT'];
/**
* Cache file locking. Set to false if filesystem is NFS. On at least one
* NFS system flock-ing attempts stalled PHP for 30 seconds!
*/
$min_cacheFileLocking = true;
/**
* Combining multiple CSS files can place @import declarations after rules, which
* is invalid. Minify will attempt to detect when this happens and place a
* warning comment at the top of the CSS output. To resolve this you can either
* move the @imports within your CSS files, or enable this option, which will
* move all @imports to the top of the output. Note that moving @imports could
* affect CSS values (which is why this option is disabled by default).
*/
$min_serveOptions['bubbleCssImports'] = false;
/**
* 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
* querystring, maxAge will be set to one year. E.g. /min/f=hello.css&123456
*/
$min_serveOptions['maxAge'] = 1800;
/**
* To use Google's Closure Compiler API to minify Javascript (falling back to JSMin
* on failure), uncomment the following line:
*/
//$min_serveOptions['minifiers']['application/x-javascript'] = array('Minify_JS_ClosureCompiler', 'minify');
/**
* If you'd like to restrict the "f" option to files within/below
* particular directories below DOCUMENT_ROOT, set this here.
* You will still need to include the directory in the
* f or b GET parameters.
*
* // = shortcut for DOCUMENT_ROOT
*/
//$min_serveOptions['minApp']['allowDirs'] = array('//js', '//css');
/**
* Set to true to disable the "f" GET parameter for specifying files.
* Only the "g" parameter will be considered.
*/
$min_serveOptions['minApp']['groupsOnly'] = false;
/**
* 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']['noMinPattern'] = '@[-\\.]min\\.(?:js|css)$@i';
/**
* If you minify CSS files stored in symlink-ed directories, the URI rewriting
* algorithm can fail. To prevent this, 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>
*/
$min_symlinks = array();
/**
* If you upload files from Windows to a non-Windows server, Windows may report
* incorrect mtimes for the files. This may cause Minify to keep serving stale
* cache files when source file changes are made too frequently (e.g. more than
* once an hour).
*
* Immediately after modifying and uploading a file, use the touch command to
* update the mtime on the server. If the mtime jumps ahead by a number of hours,
* set this variable to that number. If the mtime moves back, this should not be
* needed.
*
* In the Windows SFTP client WinSCP, there's an option that may fix this
* issue without changing the variable below. Under login > environment,
* select the option "Adjust remote timestamp with DST".
* @link http://winscp.net/eng/docs/ui_login_environment#daylight_saving_time
*/
$min_uploaderHoursBehind = 0;
/**
* Path to Minify's lib folder. If you happen to move it, change
* this accordingly.
*/
$min_libPath = dirname(__FILE__) . '/lib';
// try to disable output_compression (may not have an effect)
ini_set('zlib.output_compression', '0');
-17
View File
@@ -1,17 +0,0 @@
<?php
/**
* Groups configuration for default Minify implementation
* @package Minify
*/
/**
* 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'),
);
-777
View File
@@ -1,777 +0,0 @@
<?php
/*!
* cssmin.php v2.4.8-4
* Author: Tubal Martin - http://tubalmartin.me/
* Repo: https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port
*
* This is a PHP port of the CSS minification tool distributed with YUICompressor,
* itself a port of the cssmin utility by Isaac Schlueter - http://foohack.com/
* Permission is hereby granted to use the PHP version under the same
* conditions as the YUICompressor.
*/
/*!
* YUI Compressor
* http://developer.yahoo.com/yui/compressor/
* Author: Julien Lecomte - http://www.julienlecomte.net/
* Copyright (c) 2013 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.
*/
class CSSmin
{
const NL = '___YUICSSMIN_PRESERVED_NL___';
const TOKEN = '___YUICSSMIN_PRESERVED_TOKEN_';
const COMMENT = '___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_';
const CLASSCOLON = '___YUICSSMIN_PSEUDOCLASSCOLON___';
const QUERY_FRACTION = '___YUICSSMIN_QUERY_FRACTION___';
private $comments;
private $preserved_tokens;
private $memory_limit;
private $max_execution_time;
private $pcre_backtrack_limit;
private $pcre_recursion_limit;
private $raise_php_limits;
/**
* @param bool|int $raise_php_limits
* If true, PHP settings will be raised if needed
*/
public function __construct($raise_php_limits = TRUE)
{
// Set suggested PHP limits
$this->memory_limit = 128 * 1048576; // 128MB in bytes
$this->max_execution_time = 60; // 1 min
$this->pcre_backtrack_limit = 1000 * 1000;
$this->pcre_recursion_limit = 500 * 1000;
$this->raise_php_limits = (bool) $raise_php_limits;
}
/**
* Minify a string of CSS
* @param string $css
* @param int|bool $linebreak_pos
* @return string
*/
public function run($css = '', $linebreak_pos = FALSE)
{
if (empty($css)) {
return '';
}
if ($this->raise_php_limits) {
$this->do_raise_php_limits();
}
$this->comments = array();
$this->preserved_tokens = array();
$start_index = 0;
$length = strlen($css);
$css = $this->extract_data_urls($css);
// collect all comment blocks...
while (($start_index = $this->index_of($css, '/*', $start_index)) >= 0) {
$end_index = $this->index_of($css, '*/', $start_index + 2);
if ($end_index < 0) {
$end_index = $length;
}
$comment_found = $this->str_slice($css, $start_index + 2, $end_index);
$this->comments[] = $comment_found;
$comment_preserve_string = self::COMMENT . (count($this->comments) - 1) . '___';
$css = $this->str_slice($css, 0, $start_index + 2) . $comment_preserve_string . $this->str_slice($css, $end_index);
// Set correct start_index: Fixes issue #2528130
$start_index = $end_index + 2 + strlen($comment_preserve_string) - strlen($comment_found);
}
// preserve strings so their content doesn't get accidentally minified
$css = preg_replace_callback('/(?:"(?:[^\\\\"]|\\\\.|\\\\)*")|'."(?:'(?:[^\\\\']|\\\\.|\\\\)*')/S", array($this, 'replace_string'), $css);
// Let's divide css code in chunks of 5.000 chars aprox.
// Reason: PHP's PCRE functions like preg_replace have a "backtrack limit"
// of 100.000 chars by default (php < 5.3.7) so if we're dealing with really
// long strings and a (sub)pattern matches a number of chars greater than
// the backtrack limit number (i.e. /(.*)/s) PCRE functions may fail silently
// returning NULL and $css would be empty.
$charset = '';
$charset_regexp = '/(@charset)( [^;]+;)/i';
$css_chunks = array();
$css_chunk_length = 5000; // aprox size, not exact
$start_index = 0;
$i = $css_chunk_length; // save initial iterations
$l = strlen($css);
// if the number of characters is 5000 or less, do not chunk
if ($l <= $css_chunk_length) {
$css_chunks[] = $css;
} else {
// chunk css code securely
while ($i < $l) {
$i += 50; // save iterations
if ($l - $start_index <= $css_chunk_length || $i >= $l) {
$css_chunks[] = $this->str_slice($css, $start_index);
break;
}
if ($css[$i - 1] === '}' && $i - $start_index > $css_chunk_length) {
// If there are two ending curly braces }} separated or not by spaces,
// join them in the same chunk (i.e. @media blocks)
$next_chunk = substr($css, $i);
if (preg_match('/^\s*\}/', $next_chunk)) {
$i = $i + $this->index_of($next_chunk, '}') + 1;
}
$css_chunks[] = $this->str_slice($css, $start_index, $i);
$start_index = $i;
}
}
}
// Minify each chunk
for ($i = 0, $n = count($css_chunks); $i < $n; $i++) {
$css_chunks[$i] = $this->minify($css_chunks[$i], $linebreak_pos);
// Keep the first @charset at-rule found
if (empty($charset) && preg_match($charset_regexp, $css_chunks[$i], $matches)) {
$charset = strtolower($matches[1]) . $matches[2];
}
// Delete all @charset at-rules
$css_chunks[$i] = preg_replace($charset_regexp, '', $css_chunks[$i]);
}
// Update the first chunk and push the charset to the top of the file.
$css_chunks[0] = $charset . $css_chunks[0];
return implode('', $css_chunks);
}
/**
* Sets the memory limit for this script
* @param int|string $limit
*/
public function set_memory_limit($limit)
{
$this->memory_limit = $this->normalize_int($limit);
}
/**
* Sets the maximum execution time for this script
* @param int|string $seconds
*/
public function set_max_execution_time($seconds)
{
$this->max_execution_time = (int) $seconds;
}
/**
* Sets the PCRE backtrack limit for this script
* @param int $limit
*/
public function set_pcre_backtrack_limit($limit)
{
$this->pcre_backtrack_limit = (int) $limit;
}
/**
* Sets the PCRE recursion limit for this script
* @param int $limit
*/
public function set_pcre_recursion_limit($limit)
{
$this->pcre_recursion_limit = (int) $limit;
}
/**
* Try to configure PHP to use at least the suggested minimum settings
*/
private function do_raise_php_limits()
{
$php_limits = array(
'memory_limit' => $this->memory_limit,
'max_execution_time' => $this->max_execution_time,
'pcre.backtrack_limit' => $this->pcre_backtrack_limit,
'pcre.recursion_limit' => $this->pcre_recursion_limit
);
// If current settings are higher respect them.
foreach ($php_limits as $name => $suggested) {
$current = $this->normalize_int(ini_get($name));
// memory_limit exception: allow -1 for "no memory limit".
if ($current > -1 && ($suggested == -1 || $current < $suggested)) {
ini_set($name, $suggested);
}
}
}
/**
* Does bulk of the minification
* @param string $css
* @param int|bool $linebreak_pos
* @return string
*/
private function minify($css, $linebreak_pos)
{
// strings are safe, now wrestle the comments
for ($i = 0, $max = count($this->comments); $i < $max; $i++) {
$token = $this->comments[$i];
$placeholder = '/' . self::COMMENT . $i . '___/';
// ! in the first position of the comment means preserve
// so push to the preserved tokens keeping the !
if (substr($token, 0, 1) === '!') {
$this->preserved_tokens[] = $token;
$token_tring = self::TOKEN . (count($this->preserved_tokens) - 1) . '___';
$css = preg_replace($placeholder, $token_tring, $css, 1);
// Preserve new lines for /*! important comments
$css = preg_replace('/\s*[\n\r\f]+\s*(\/\*'. $token_tring .')/S', self::NL.'$1', $css);
$css = preg_replace('/('. $token_tring .'\*\/)\s*[\n\r\f]+\s*/', '$1'.self::NL, $css);
continue;
}
// \ in the last position looks like hack for Mac/IE5
// shorten that to /*\*/ and the next one to /**/
if (substr($token, (strlen($token) - 1), 1) === '\\') {
$this->preserved_tokens[] = '\\';
$css = preg_replace($placeholder, self::TOKEN . (count($this->preserved_tokens) - 1) . '___', $css, 1);
$i = $i + 1; // attn: advancing the loop
$this->preserved_tokens[] = '';
$css = preg_replace('/' . self::COMMENT . $i . '___/', self::TOKEN . (count($this->preserved_tokens) - 1) . '___', $css, 1);
continue;
}
// keep empty comments after child selectors (IE7 hack)
// e.g. html >/**/ body
if (strlen($token) === 0) {
$start_index = $this->index_of($css, $this->str_slice($placeholder, 1, -1));
if ($start_index > 2) {
if (substr($css, $start_index - 3, 1) === '>') {
$this->preserved_tokens[] = '';
$css = preg_replace($placeholder, self::TOKEN . (count($this->preserved_tokens) - 1) . '___', $css, 1);
}
}
}
// in all other cases kill the comment
$css = preg_replace('/\/\*' . $this->str_slice($placeholder, 1, -1) . '\*\//', '', $css, 1);
}
// Normalize all whitespace strings to single spaces. Easier to work with that way.
$css = preg_replace('/\s+/', ' ', $css);
// Fix IE7 issue on matrix filters which browser accept whitespaces between Matrix parameters
$css = preg_replace_callback('/\s*filter\:\s*progid:DXImageTransform\.Microsoft\.Matrix\(([^\)]+)\)/', array($this, 'preserve_old_IE_specific_matrix_definition'), $css);
// Shorten & preserve calculations calc(...) since spaces are important
$css = preg_replace_callback('/calc(\(((?:[^\(\)]+|(?1))*)\))/i', array($this, 'replace_calc'), $css);
// Replace positive sign from numbers preceded by : or a white-space before the leading space is removed
// +1.2em to 1.2em, +.8px to .8px, +2% to 2%
$css = preg_replace('/((?<!\\\\)\:|\s)\+(\.?\d+)/S', '$1$2', $css);
// Remove leading zeros from integer and float numbers preceded by : or a white-space
// 000.6 to .6, -0.8 to -.8, 0050 to 50, -01.05 to -1.05
$css = preg_replace('/((?<!\\\\)\:|\s)(\-?)0+(\.?\d+)/S', '$1$2$3', $css);
// Remove trailing zeros from float numbers preceded by : or a white-space
// -6.0100em to -6.01em, .0100 to .01, 1.200px to 1.2px
$css = preg_replace('/((?<!\\\\)\:|\s)(\-?)(\d?\.\d+?)0+([^\d])/S', '$1$2$3$4', $css);
// Remove trailing .0 -> -9.0 to -9
$css = preg_replace('/((?<!\\\\)\:|\s)(\-?\d+)\.0([^\d])/S', '$1$2$3', $css);
// Replace 0 length numbers with 0
$css = preg_replace('/((?<!\\\\)\:|\s)\-?\.?0+([^\d])/S', '${1}0$2', $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('/(?:^|\})[^\{]*\s+\:/', array($this, 'replace_colon'), $css);
// Remove spaces before the things that should not have spaces before them.
$css = preg_replace('/\s+([\!\{\}\;\:\>\+\(\)\]\~\=,])/', '$1', $css);
// Restore spaces for !important
$css = preg_replace('/\!important/i', ' !important', $css);
// bring back the colon
$css = preg_replace('/' . self::CLASSCOLON . '/', ':', $css);
// retain space for special IE6 cases
$css = preg_replace_callback('/\:first\-(line|letter)(\{|,)/i', array($this, 'lowercase_pseudo_first'), $css);
// no space after the end of a preserved comment
$css = preg_replace('/\*\/ /', '*/', $css);
// lowercase some popular @directives
$css = preg_replace_callback('/@(font-face|import|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?keyframe|media|page|namespace)/i', array($this, 'lowercase_directives'), $css);
// lowercase some more common pseudo-elements
$css = preg_replace_callback('/:(active|after|before|checked|disabled|empty|enabled|first-(?:child|of-type)|focus|hover|last-(?:child|of-type)|link|only-(?:child|of-type)|root|:selection|target|visited)/i', array($this, 'lowercase_pseudo_elements'), $css);
// lowercase some more common functions
$css = preg_replace_callback('/:(lang|not|nth-child|nth-last-child|nth-last-of-type|nth-of-type|(?:-(?:moz|webkit)-)?any)\(/i', array($this, 'lowercase_common_functions'), $css);
// lower case some common function that can be values
// NOTE: rgb() isn't useful as we replace with #hex later, as well as and() is already done for us
$css = preg_replace_callback('/([:,\( ]\s*)(attr|color-stop|from|rgba|to|url|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?(?:calc|max|min|(?:repeating-)?(?:linear|radial)-gradient)|-webkit-gradient)/iS', array($this, 'lowercase_common_functions_values'), $css);
// Put the space back in some cases, to support stuff like
// @media screen and (-webkit-min-device-pixel-ratio:0){
$css = preg_replace('/\band\(/i', 'and (', $css);
// Remove the spaces after the things that should not have spaces after them.
$css = preg_replace('/([\!\{\}\:;\>\+\(\[\~\=,])\s+/S', '$1', $css);
// remove unnecessary semicolons
$css = preg_replace('/;+\}/', '}', $css);
// Fix for issue: #2528146
// Restore semicolon if the last property is prefixed with a `*` (lte IE7 hack)
// to avoid issues on Symbian S60 3.x browsers.
$css = preg_replace('/(\*[a-z0-9\-]+\s*\:[^;\}]+)(\})/', '$1;$2', $css);
// Replace 0 <length> and 0 <percentage> values with 0.
// <length> data type: https://developer.mozilla.org/en-US/docs/Web/CSS/length
// <percentage> data type: https://developer.mozilla.org/en-US/docs/Web/CSS/percentage
$css = preg_replace('/([^\\\\]\:|\s)0(?:em|ex|ch|rem|vw|vh|vm|vmin|cm|mm|in|px|pt|pc|%)/iS', '${1}0', $css);
// 0% step in a keyframe? restore the % unit
$css = preg_replace_callback('/(@[a-z\-]*?keyframes[^\{]+\{)(.*?)(\}\})/iS', array($this, 'replace_keyframe_zero'), $css);
// Replace 0 0; or 0 0 0; or 0 0 0 0; with 0.
$css = preg_replace('/\:0(?: 0){1,3}(;|\}| \!)/', ':0$1', $css);
// Fix for issue: #2528142
// Replace text-shadow:0; with text-shadow:0 0 0;
$css = preg_replace('/(text-shadow\:0)(;|\}| \!)/i', '$1 0 0$2', $css);
// Replace background-position:0; with background-position:0 0;
// same for transform-origin
// Changing -webkit-mask-position: 0 0 to just a single 0 will result in the second parameter defaulting to 50% (center)
$css = preg_replace('/(background\-position|webkit-mask-position|(?:webkit|moz|o|ms|)\-?transform\-origin)\:0(;|\}| \!)/iS', '$1:0 0$2', $css);
// Shorten colors from rgb(51,102,153) to #336699, rgb(100%,0%,0%) to #ff0000 (sRGB color space)
// Shorten colors from hsl(0, 100%, 50%) to #ff0000 (sRGB color space)
// 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*\)(.{1})/i', array($this, 'rgb_to_hex'), $css);
$css = preg_replace_callback('/hsl\s*\(\s*([0-9,\s\-\.\%]+)\s*\)(.{1})/i', array($this, 'hsl_to_hex'), $css);
// Shorten colors from #AABBCC to #ABC or short color name.
$css = $this->compress_hex_colors($css);
// border: none to border:0, outline: none to outline:0
$css = preg_replace('/(border\-?(?:top|right|bottom|left|)|outline)\:none(;|\}| \!)/iS', '$1:0$2', $css);
// shorter opacity IE filter
$css = preg_replace('/progid\:DXImageTransform\.Microsoft\.Alpha\(Opacity\=/i', 'alpha(opacity=', $css);
// Find a fraction that is used for Opera's -o-device-pixel-ratio query
// Add token to add the "\" back in later
$css = preg_replace('/\(([a-z\-]+):([0-9]+)\/([0-9]+)\)/i', '($1:$2'. self::QUERY_FRACTION .'$3)', $css);
// Remove empty rules.
$css = preg_replace('/[^\};\{\/]+\{\}/S', '', $css);
// Add "/" back to fix Opera -o-device-pixel-ratio query
$css = preg_replace('/'. self::QUERY_FRACTION .'/', '/', $css);
// Replace multiple semi-colons in a row by a single one
// See SF bug #1980989
$css = preg_replace('/;;+/', ';', $css);
// Restore new lines for /*! important comments
$css = preg_replace('/'. self::NL .'/', "\n", $css);
// Lowercase all uppercase properties
$css = preg_replace_callback('/(\{|\;)([A-Z\-]+)(\:)/', array($this, 'lowercase_properties'), $css);
// 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.
if ($linebreak_pos !== FALSE && (int) $linebreak_pos >= 0) {
$linebreak_pos = (int) $linebreak_pos;
$start_index = $i = 0;
while ($i < strlen($css)) {
$i++;
if ($css[$i - 1] === '}' && $i - $start_index > $linebreak_pos) {
$css = $this->str_slice($css, 0, $i) . "\n" . $this->str_slice($css, $i);
$start_index = $i;
}
}
}
// restore preserved comments and strings in reverse order
for ($i = count($this->preserved_tokens) - 1; $i >= 0; $i--) {
$css = preg_replace('/' . self::TOKEN . $i . '___/', $this->preserved_tokens[$i], $css, 1);
}
// Trim the final string (for any leading or trailing white spaces)
return trim($css);
}
/**
* Utility method to replace all data urls with tokens before we start
* compressing, to avoid performance issues running some of the subsequent
* regexes against large strings chunks.
*
* @param string $css
* @return string
*/
private function extract_data_urls($css)
{
// Leave data urls alone to increase parse performance.
$max_index = strlen($css) - 1;
$append_index = $index = $last_index = $offset = 0;
$sb = array();
$pattern = '/url\(\s*(["\']?)data\:/i';
// 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 (preg_match($pattern, $css, $m, 0, $offset)) {
$index = $this->index_of($css, $m[0], $offset);
$last_index = $index + strlen($m[0]);
$start_index = $index + 4; // "url(".length()
$end_index = $last_index - 1;
$terminator = $m[1]; // ', " or empty (not quoted)
$found_terminator = FALSE;
if (strlen($terminator) === 0) {
$terminator = ')';
}
while ($found_terminator === FALSE && $end_index+1 <= $max_index) {
$end_index = $this->index_of($css, $terminator, $end_index + 1);
// endIndex == 0 doesn't really apply here
if ($end_index > 0 && substr($css, $end_index - 1, 1) !== '\\') {
$found_terminator = TRUE;
if (')' != $terminator) {
$end_index = $this->index_of($css, ')', $end_index);
}
}
}
// Enough searching, start moving stuff over to the buffer
$sb[] = $this->str_slice($css, $append_index, $index);
if ($found_terminator) {
$token = $this->str_slice($css, $start_index, $end_index);
$token = preg_replace('/\s+/', '', $token);
$this->preserved_tokens[] = $token;
$preserver = 'url(' . self::TOKEN . (count($this->preserved_tokens) - 1) . '___)';
$sb[] = $preserver;
$append_index = $end_index + 1;
} else {
// No end terminator found, re-add the whole match. Should we throw/warn here?
$sb[] = $this->str_slice($css, $index, $last_index);
$append_index = $last_index;
}
$offset = $last_index;
}
$sb[] = $this->str_slice($css, $append_index);
return implode('', $sb);
}
/**
* Utility method to compress hex color values of the form #AABBCC to #ABC or short color name.
*
* DOES NOT compress CSS ID selectors which match the above pattern (which would break things).
* e.g. #AddressForm { ... }
*
* DOES NOT compress IE filters, which have hex color values (which would break things).
* e.g. filter: chroma(color="#FFFFFF");
*
* DOES NOT compress invalid hex values.
* e.g. background-color: #aabbccdd
*
* @param string $css
* @return string
*/
private function compress_hex_colors($css)
{
// Look for hex colors inside { ... } (to avoid IDs) and which don't have a =, or a " in front of them (to avoid filters)
$pattern = '/(\=\s*?["\']?)?#([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])(\}|[^0-9a-f{][^{]*?\})/iS';
$_index = $index = $last_index = $offset = 0;
$sb = array();
// See: http://ajaxmin.codeplex.com/wikipage?title=CSS%20Colors
$short_safe = array(
'#808080' => 'gray',
'#008000' => 'green',
'#800000' => 'maroon',
'#000080' => 'navy',
'#808000' => 'olive',
'#ffa500' => 'orange',
'#800080' => 'purple',
'#c0c0c0' => 'silver',
'#008080' => 'teal',
'#f00' => 'red'
);
while (preg_match($pattern, $css, $m, 0, $offset)) {
$index = $this->index_of($css, $m[0], $offset);
$last_index = $index + strlen($m[0]);
$is_filter = $m[1] !== null && $m[1] !== '';
$sb[] = $this->str_slice($css, $_index, $index);
if ($is_filter) {
// Restore, maintain case, otherwise filter will break
$sb[] = $m[1] . '#' . $m[2] . $m[3] . $m[4] . $m[5] . $m[6] . $m[7];
} else {
if (strtolower($m[2]) == strtolower($m[3]) &&
strtolower($m[4]) == strtolower($m[5]) &&
strtolower($m[6]) == strtolower($m[7])) {
// Compress.
$hex = '#' . strtolower($m[3] . $m[5] . $m[7]);
} else {
// Non compressible color, restore but lower case.
$hex = '#' . strtolower($m[2] . $m[3] . $m[4] . $m[5] . $m[6] . $m[7]);
}
// replace Hex colors to short safe color names
$sb[] = array_key_exists($hex, $short_safe) ? $short_safe[$hex] : $hex;
}
$_index = $offset = $last_index - strlen($m[8]);
}
$sb[] = $this->str_slice($css, $_index);
return implode('', $sb);
}
/* CALLBACKS
* ---------------------------------------------------------------------------------------------
*/
private function replace_string($matches)
{
$match = $matches[0];
$quote = substr($match, 0, 1);
// Must use addcslashes in PHP to avoid parsing of backslashes
$match = addcslashes($this->str_slice($match, 1, -1), '\\');
// maybe the string contains a comment-like substring?
// one, maybe more? put'em back then
if (($pos = $this->index_of($match, self::COMMENT)) >= 0) {
for ($i = 0, $max = count($this->comments); $i < $max; $i++) {
$match = preg_replace('/' . self::COMMENT . $i . '___/', $this->comments[$i], $match, 1);
}
}
// minify alpha opacity in filter strings
$match = preg_replace('/progid\:DXImageTransform\.Microsoft\.Alpha\(Opacity\=/i', 'alpha(opacity=', $match);
$this->preserved_tokens[] = $match;
return $quote . self::TOKEN . (count($this->preserved_tokens) - 1) . '___' . $quote;
}
private function replace_colon($matches)
{
return preg_replace('/\:/', self::CLASSCOLON, $matches[0]);
}
private function replace_calc($matches)
{
$this->preserved_tokens[] = trim(preg_replace('/\s*([\*\/\(\),])\s*/', '$1', $matches[2]));
return 'calc('. self::TOKEN . (count($this->preserved_tokens) - 1) . '___' . ')';
}
private function preserve_old_IE_specific_matrix_definition($matches)
{
$this->preserved_tokens[] = $matches[1];
return 'filter:progid:DXImageTransform.Microsoft.Matrix(' . self::TOKEN . (count($this->preserved_tokens) - 1) . '___' . ')';
}
private function replace_keyframe_zero($matches)
{
return $matches[1] . preg_replace('/0(\{|,[^\)\{]+\{)/', '0%$1', $matches[2]) . $matches[3];
}
private function rgb_to_hex($matches)
{
// Support for percentage values rgb(100%, 0%, 45%);
if ($this->index_of($matches[1], '%') >= 0){
$rgbcolors = explode(',', str_replace('%', '', $matches[1]));
for ($i = 0; $i < count($rgbcolors); $i++) {
$rgbcolors[$i] = $this->round_number(floatval($rgbcolors[$i]) * 2.55);
}
} else {
$rgbcolors = explode(',', $matches[1]);
}
// Values outside the sRGB color space should be clipped (0-255)
for ($i = 0; $i < count($rgbcolors); $i++) {
$rgbcolors[$i] = $this->clamp_number(intval($rgbcolors[$i], 10), 0, 255);
$rgbcolors[$i] = sprintf("%02x", $rgbcolors[$i]);
}
// Fix for issue #2528093
if (!preg_match('/[\s\,\);\}]/', $matches[2])){
$matches[2] = ' ' . $matches[2];
}
return '#' . implode('', $rgbcolors) . $matches[2];
}
private function hsl_to_hex($matches)
{
$values = explode(',', str_replace('%', '', $matches[1]));
$h = floatval($values[0]);
$s = floatval($values[1]);
$l = floatval($values[2]);
// Wrap and clamp, then fraction!
$h = ((($h % 360) + 360) % 360) / 360;
$s = $this->clamp_number($s, 0, 100) / 100;
$l = $this->clamp_number($l, 0, 100) / 100;
if ($s == 0) {
$r = $g = $b = $this->round_number(255 * $l);
} else {
$v2 = $l < 0.5 ? $l * (1 + $s) : ($l + $s) - ($s * $l);
$v1 = (2 * $l) - $v2;
$r = $this->round_number(255 * $this->hue_to_rgb($v1, $v2, $h + (1/3)));
$g = $this->round_number(255 * $this->hue_to_rgb($v1, $v2, $h));
$b = $this->round_number(255 * $this->hue_to_rgb($v1, $v2, $h - (1/3)));
}
return $this->rgb_to_hex(array('', $r.','.$g.','.$b, $matches[2]));
}
private function lowercase_pseudo_first($matches)
{
return ':first-'. strtolower($matches[1]) .' '. $matches[2];
}
private function lowercase_directives($matches)
{
return '@'. strtolower($matches[1]);
}
private function lowercase_pseudo_elements($matches)
{
return ':'. strtolower($matches[1]);
}
private function lowercase_common_functions($matches)
{
return ':'. strtolower($matches[1]) .'(';
}
private function lowercase_common_functions_values($matches)
{
return $matches[1] . strtolower($matches[2]);
}
private function lowercase_properties($matches)
{
return $matches[1].strtolower($matches[2]).$matches[3];
}
/* HELPERS
* ---------------------------------------------------------------------------------------------
*/
private function hue_to_rgb($v1, $v2, $vh)
{
$vh = $vh < 0 ? $vh + 1 : ($vh > 1 ? $vh - 1 : $vh);
if ($vh * 6 < 1) return $v1 + ($v2 - $v1) * 6 * $vh;
if ($vh * 2 < 1) return $v2;
if ($vh * 3 < 2) return $v1 + ($v2 - $v1) * ((2/3) - $vh) * 6;
return $v1;
}
private function round_number($n)
{
return intval(floor(floatval($n) + 0.5), 10);
}
private function clamp_number($n, $min, $max)
{
return min(max($n, $min), $max);
}
/**
* PHP port of Javascript's "indexOf" function for strings only
* Author: Tubal Martin http://blog.margenn.com
*
* @param string $haystack
* @param string $needle
* @param int $offset index (optional)
* @return int
*/
private function index_of($haystack, $needle, $offset = 0)
{
$index = strpos($haystack, $needle, $offset);
return ($index !== FALSE) ? $index : -1;
}
/**
* PHP port of Javascript's "slice" function for strings only
* Author: Tubal Martin http://blog.margenn.com
* Tests: http://margenn.com/tubal/str_slice/
*
* @param string $str
* @param int $start index
* @param int|bool $end index (optional)
* @return string
*/
private function str_slice($str, $start = 0, $end = FALSE)
{
if ($end !== FALSE && ($start < 0 || $end <= 0)) {
$max = strlen($str);
if ($start < 0) {
if (($start = $max + $start) < 0) {
return '';
}
}
if ($end < 0) {
if (($end = $max + $end) < 0) {
return '';
}
}
if ($end <= $start) {
return '';
}
}
$slice = ($end === FALSE) ? substr($str, $start) : substr($str, $start, $end - $start);
return ($slice === FALSE) ? '' : $slice;
}
/**
* Convert strings like "64M" or "30" to int values
* @param mixed $size
* @return int
*/
private function normalize_int($size)
{
if (is_string($size)) {
switch (substr($size, -1)) {
case 'M': case 'm': return $size * 1048576;
case 'K': case 'k': return $size * 1024;
case 'G': case 'g': return $size * 1073741824;
}
}
return (int) $size;
}
}
-121
View File
@@ -1,121 +0,0 @@
<?php
/**
* DooDigestAuth class file.
*
* @author Leng Sheng Hong <[email protected]>
* @link http://www.doophp.com/
* @copyright Copyright &copy; 2009 Leng Sheng Hong
* @license http://www.doophp.com/license
*/
/**
* Handles HTTP digest authentication
*
* <p>HTTP digest authentication can be used with the URI router.
* HTTP digest is much more recommended over the use of HTTP Basic auth which doesn't provide any encryption.
* If you are running PHP on Apache in CGI/FastCGI mode, you would need to
* add the following line to your .htaccess for digest auth to work correctly.</p>
* <code>RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]</code>
*
* <p>This class is tested under Apache 2.2 and Cherokee web server. It should work in both mod_php and cgi mode.</p>
*
* @author Leng Sheng Hong <[email protected]>
* @version $Id: DooDigestAuth.php 1000 2009-07-7 18:27:22
* @package doo.auth
* @since 1.0
*/
class DooDigestAuth{
/**
* Authenticate against a list of username and passwords.
*
* <p>HTTP Digest Authentication doesn't work with PHP in CGI mode,
* you have to add this into your .htaccess <code>RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]</code></p>
*
* @param string $realm Name of the authentication session
* @param array $users An assoc array of username and password: array('uname1'=>'pwd1', 'uname2'=>'pwd2')
* @param string $fail_msg Message to be displayed if the User cancel the login
* @param string $fail_url URL to be redirect if the User cancel the login
* @return string The username if login success.
*/
public static function http_auth($realm, $users, $fail_msg=NULL, $fail_url=NULL){
$realm = "Restricted area - $realm";
//user => password
//$users = array('admin' => '1234', 'guest' => 'guest');
if(!empty($_SERVER['REDIRECT_HTTP_AUTHORIZATION']) && strpos($_SERVER['REDIRECT_HTTP_AUTHORIZATION'], 'Digest')===0){
$_SERVER['PHP_AUTH_DIGEST'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION'];
}
if (empty($_SERVER['PHP_AUTH_DIGEST'])) {
header('WWW-Authenticate: Digest realm="'.$realm.
'",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"');
header('HTTP/1.1 401 Unauthorized');
if($fail_msg!=NULL)
die($fail_msg);
if($fail_url!=NULL)
die("<script>window.location.href = '$fail_url'</script>");
exit;
}
// analyze the PHP_AUTH_DIGEST variable
if (!($data = self::http_digest_parse($_SERVER['PHP_AUTH_DIGEST'])) || !isset($users[$data['username']])){
header('WWW-Authenticate: Digest realm="'.$realm.
'",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"');
header('HTTP/1.1 401 Unauthorized');
if($fail_msg!=NULL)
die($fail_msg);
if($fail_url!=NULL)
die("<script>window.location.href = '$fail_url'</script>");
exit;
}
// generate the valid response
$A1 = md5($data['username'] . ':' . $realm . ':' . $users[$data['username']]);
$A2 = md5($_SERVER['REQUEST_METHOD'].':'.$data['uri']);
$valid_response = md5($A1.':'.$data['nonce'].':'.$data['nc'].':'.$data['cnonce'].':'.$data['qop'].':'.$A2);
if ($data['response'] != $valid_response){
header('HTTP/1.1 401 Unauthorized');
header('WWW-Authenticate: Digest realm="'.$realm.
'",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"');
if($fail_msg!=NULL)
die($fail_msg);
if($fail_url!=NULL)
die("<script>window.location.href = '$fail_url'</script>");
exit;
}
// ok, valid username & password
return $data['username'];
}
/**
* Method to parse the http auth header, works with IE.
*
* Internet Explorer returns a qop="xxxxxxxxxxx" in the header instead of qop=xxxxxxxxxxx as most browsers do.
*
* @param string $txt header string to parse
* @return array An assoc array of the digest auth session
*/
private static function http_digest_parse($txt)
{
$res = preg_match("/username=\"([^\"]+)\"/i", $txt, $match);
$data['username'] = (isset($match[1]))?$match[1]:null;
$res = preg_match('/nonce=\"([^\"]+)\"/i', $txt, $match);
$data['nonce'] = $match[1];
$res = preg_match('/nc=([0-9]+)/i', $txt, $match);
$data['nc'] = $match[1];
$res = preg_match('/cnonce=\"([^\"]+)\"/i', $txt, $match);
$data['cnonce'] = $match[1];
$res = preg_match('/qop=([^,]+)/i', $txt, $match);
$data['qop'] = str_replace('"','',$match[1]);
$res = preg_match('/uri=\"([^\"]+)\"/i', $txt, $match);
$data['uri'] = $match[1];
$res = preg_match('/response=\"([^\"]+)\"/i', $txt, $match);
$data['response'] = $match[1];
return $data;
}
}
File diff suppressed because it is too large Load Diff
-366
View File
@@ -1,366 +0,0 @@
<?php
/**
* Class HTTP_ConditionalGet
* @package Minify
* @subpackage HTTP
*/
/**
* Implement conditional GET via a timestamp or hash of content
*
* E.g. Content from DB with update time:
* <code>
* list($updateTime, $content) = getDbUpdateAndContent();
* $cg = new HTTP_ConditionalGet(array(
* 'lastModifiedTime' => $updateTime
* ,'isPublic' => true
* ));
* $cg->sendHeaders();
* if ($cg->cacheIsValid) {
* exit();
* }
* echo $content;
* </code>
*
* E.g. Shortcut for the above
* <code>
* HTTP_ConditionalGet::check($updateTime, true); // exits if client has cache
* echo $content;
* </code>
*
* E.g. Content from DB with no update time:
* <code>
* $content = getContentFromDB();
* $cg = new HTTP_ConditionalGet(array(
* 'contentHash' => md5($content)
* ));
* $cg->sendHeaders();
* if ($cg->cacheIsValid) {
* exit();
* }
* echo $content;
* </code>
*
* E.g. Static content with some static includes:
* <code>
* // before content
* $cg = new HTTP_ConditionalGet(array(
* 'lastUpdateTime' => max(
* filemtime(__FILE__)
* ,filemtime('/path/to/header.inc')
* ,filemtime('/path/to/footer.inc')
* )
* ));
* $cg->sendHeaders();
* if ($cg->cacheIsValid) {
* exit();
* }
* </code>
* @package Minify
* @subpackage HTTP
* @author Stephen Clay <[email protected]>
*/
class HTTP_ConditionalGet {
/**
* Does the client have a valid copy of the requested resource?
*
* You'll want to check this after instantiating the object. If true, do
* not send content, just call sendHeaders() if you haven't already.
*
* @var bool
*/
public $cacheIsValid = null;
/**
* @param array $spec options
*
* '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.
*
* 'encoding': (string) if set, the header "Vary: Accept-Encoding" will
* always be sent and a truncated version of the encoding will be appended
* to the ETag. E.g. "pub123456;gz". This will also trigger a more lenient
* checking of the client's If-None-Match header, as the encoding portion of
* the ETag will be stripped before comparison.
*
* 'contentHash': (string) if given, only the ETag header can be sent with
* content (only HTTP1.1 clients can conditionally GET). The given string
* should be short with no quote characters and always change when the
* resource changes (recommend md5()). This is not needed/used if
* lastModifiedTime is given.
*
* 'eTag': (string) if given, this will be used as the ETag header rather
* than values based on lastModifiedTime or contentHash. Also the encoding
* string will not be appended to the given value as described above.
*
* 'invalidate': (bool) if true, the client cache will be considered invalid
* without testing. Effectively this disables conditional GET.
* (default false)
*
* 'maxAge': (int) if given, this will set the Cache-Control max-age in
* 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.
*/
public function __construct($spec)
{
$scope = (isset($spec['isPublic']) && $spec['isPublic'])
? 'public'
: 'private';
$maxAge = 0;
// backwards compatibility (can be removed later)
if (isset($spec['setExpires'])
&& is_numeric($spec['setExpires'])
&& ! isset($spec['maxAge'])) {
$spec['maxAge'] = $spec['setExpires'] - $_SERVER['REQUEST_TIME'];
}
if (isset($spec['maxAge'])) {
$maxAge = $spec['maxAge'];
$this->_headers['Expires'] = self::gmtDate(
$_SERVER['REQUEST_TIME'] + $spec['maxAge']
);
}
$etagAppend = '';
if (isset($spec['encoding'])) {
$this->_stripEtag = true;
$this->_headers['Vary'] = 'Accept-Encoding';
if ('' !== $spec['encoding']) {
if (0 === strpos($spec['encoding'], 'x-')) {
$spec['encoding'] = substr($spec['encoding'], 2);
}
$etagAppend = ';' . substr($spec['encoding'], 0, 2);
}
}
if (isset($spec['lastModifiedTime'])) {
$this->_setLastModified($spec['lastModifiedTime']);
if (isset($spec['eTag'])) { // Use it
$this->_setEtag($spec['eTag'], $scope);
} else { // base both headers on time
$this->_setEtag($spec['lastModifiedTime'] . $etagAppend, $scope);
}
} elseif (isset($spec['eTag'])) { // Use it
$this->_setEtag($spec['eTag'], $scope);
} elseif (isset($spec['contentHash'])) { // Use the hash as the ETag
$this->_setEtag($spec['contentHash'] . $etagAppend, $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
: $this->_isCacheValid();
}
/**
* Get array of output headers to be sent
*
* In the case of 304 responses, this array will only contain the response
* code header: array('_responseCode' => 'HTTP/1.0 304 Not Modified')
*
* Otherwise something like:
* <code>
* array(
* 'Cache-Control' => 'max-age=0, public'
* ,'ETag' => '"foobar"'
* )
* </code>
*
* @return array
*/
public function getHeaders()
{
return $this->_headers;
}
/**
* Set the Content-Length header in bytes
*
* With most PHP configs, as long as you don't flush() output, this method
* is not needed and PHP will buffer all output and set Content-Length for
* you. Otherwise you'll want to call this to let the client know up front.
*
* @param int $bytes
*
* @return int copy of input $bytes
*/
public function setContentLength($bytes)
{
return $this->_headers['Content-Length'] = $bytes;
}
/**
* Send headers
*
* @see getHeaders()
*
* Note this doesn't "clear" the headers. Calling sendHeaders() will
* call header() again (but probably have not effect) and getHeaders() will
* still return the headers.
*
* @return null
*/
public function sendHeaders()
{
$headers = $this->_headers;
if (array_key_exists('_responseCode', $headers)) {
// 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) {
header($name . ': ' . $val);
}
}
/**
* Exit if the client's cache is valid for this resource
*
* This is a convenience method for common use of the class
*
* @param int $lastModifiedTime if given, both ETag AND Last-Modified headers
* will be sent with content. This is recommended.
*
* @param bool $isPublic (default false) if true, the Cache-Control header
* will contain "public", allowing proxies to cache the content. Otherwise
* "private" will be sent, allowing only browser caching.
*
* @param array $options (default empty) additional options for constructor
*/
public static function check($lastModifiedTime = null, $isPublic = false, $options = array())
{
if (null !== $lastModifiedTime) {
$options['lastModifiedTime'] = (int)$lastModifiedTime;
}
$options['isPublic'] = (bool)$isPublic;
$cg = new HTTP_ConditionalGet($options);
$cg->sendHeaders();
if ($cg->cacheIsValid) {
exit();
}
}
/**
* Get a GMT formatted date for use in HTTP headers
*
* <code>
* header('Expires: ' . HTTP_ConditionalGet::gmtdate($time));
* </code>
*
* @param int $time unix timestamp
*
* @return string
*/
public static function gmtDate($time)
{
return gmdate('D, d M Y H:i:s \G\M\T', $time);
}
protected $_headers = array();
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;
$this->_headers['Last-Modified'] = self::gmtDate($time);
}
/**
* Determine validity of client cache and queue 304 header if valid
*
* @return bool
*/
protected function _isCacheValid()
{
if (null === $this->_etag) {
// lmTime is copied to ETag, so this condition implies that the
// server sent neither ETag nor Last-Modified, so the client can't
// possibly has a valid cache.
return false;
}
$isValid = ($this->resourceMatchedEtag() || $this->resourceNotModified());
if ($isValid) {
$this->_headers['_responseCode'] = 'HTTP/1.0 304 Not Modified';
}
return $isValid;
}
/**
* @return bool
*/
protected function resourceMatchedEtag()
{
if (!isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
return false;
}
$clientEtagList = get_magic_quotes_gpc()
? stripslashes($_SERVER['HTTP_IF_NONE_MATCH'])
: $_SERVER['HTTP_IF_NONE_MATCH'];
$clientEtags = explode(',', $clientEtagList);
$compareTo = $this->normalizeEtag($this->_etag);
foreach ($clientEtags as $clientEtag) {
if ($this->normalizeEtag($clientEtag) === $compareTo) {
// respond with the client's matched ETag, even if it's not what
// we would've sent by default
$this->_headers['ETag'] = trim($clientEtag);
return true;
}
}
return false;
}
/**
* @param string $etag
*
* @return string
*/
protected function normalizeEtag($etag) {
$etag = trim($etag);
return $this->_stripEtag
? preg_replace('/;\\w\\w"$/', '"', $etag)
: $etag;
}
/**
* @return bool
*/
protected function resourceNotModified()
{
if (!isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
return false;
}
// 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);
return true;
}
return false;
}
}
-335
View File
@@ -1,335 +0,0 @@
<?php
/**
* Class HTTP_Encoder
* @package Minify
* @subpackage HTTP
*/
/**
* Encode and send gzipped/deflated content
*
* The "Vary: Accept-Encoding" header is sent. If the client allows encoding,
* Content-Encoding and Content-Length are added.
*
* <code>
* // Send a CSS file, compressed if possible
* $he = new HTTP_Encoder(array(
* 'content' => file_get_contents($cssFile)
* ,'type' => 'text/css'
* ));
* $he->encode();
* $he->sendAll();
* </code>
*
* <code>
* // Shortcut to encoding output
* header('Content-Type: text/css'); // needed if not HTML
* HTTP_Encoder::output($css);
* </code>
*
* <code>
* // Just sniff for the accepted encoding
* $encoding = HTTP_Encoder::getAcceptedEncoding();
* </code>
*
* For more control over headers, use getHeaders() and getData() and send your
* own output.
*
* Note: If you don't need header mgmt, use PHP's native gzencode, gzdeflate,
* and gzcompress functions for gzip, deflate, and compress-encoding
* respectively.
*
* @package Minify
* @subpackage HTTP
* @author Stephen Clay <[email protected]>
*/
class HTTP_Encoder {
/**
* Should the encoder allow HTTP encoding to IE6?
*
* If you have many IE6 users and the bandwidth savings is worth troubling
* some of them, set this to true.
*
* By default, encoding is only offered to IE7+. When this is true,
* getAcceptedEncoding() will return an encoding for IE6 if its user agent
* string contains "SV1". This has been documented in many places as "safe",
* but there seem to be remaining, intermittent encoding bugs in patched
* IE6 on the wild web.
*
* @var bool
*/
public static $encodeToIe6 = true;
/**
* Default compression level for zlib operations
*
* This level is used if encode() is not given a $compressionLevel
*
* @var int
*/
public static $compressionLevel = 6;
/**
* Get an HTTP Encoder object
*
* @param array $spec options
*
* 'content': (string required) content to be encoded
*
* 'type': (string) if set, the Content-Type header will have this value.
*
* 'method: (string) only set this if you are forcing a particular encoding
* method. If not set, the best method will be chosen by getAcceptedEncoding()
* The available methods are 'gzip', 'deflate', 'compress', and '' (no
* encoding)
*/
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'] = $this->_useMbStrlen
? (string)mb_strlen($this->_content, '8bit')
: (string)strlen($this->_content);
if (isset($spec['type'])) {
$this->_headers['Content-Type'] = $spec['type'];
}
if (isset($spec['method'])
&& in_array($spec['method'], array('gzip', 'deflate', 'compress', '')))
{
$this->_encodeMethod = array($spec['method'], $spec['method']);
} else {
$this->_encodeMethod = self::getAcceptedEncoding();
}
}
/**
* Get content in current form
*
* Call after encode() for encoded content.
*
* @return string
*/
public function getContent()
{
return $this->_content;
}
/**
* Get array of output headers to be sent
*
* E.g.
* <code>
* array(
* 'Content-Length' => '615'
* ,'Content-Encoding' => 'x-gzip'
* ,'Vary' => 'Accept-Encoding'
* )
* </code>
*
* @return array
*/
public function getHeaders()
{
return $this->_headers;
}
/**
* Send output headers
*
* 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.
*
* @see getHeaders()
*/
public function sendHeaders()
{
foreach ($this->_headers as $name => $val) {
header($name . ': ' . $val);
}
}
/**
* Send output headers and content
*
* A shortcut for sendHeaders() and echo getContent()
*
* 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.
*/
public function sendAll()
{
$this->sendHeaders();
echo $this->_content;
}
/**
* Determine the client's best encoding method from the HTTP Accept-Encoding
* header.
*
* If no Accept-Encoding header is set, or the browser is IE before v6 SP2,
* this will return ('', ''), the "identity" encoding.
*
* A syntax-aware scan is done of the Accept-Encoding, so the method must
* be non 0. The methods are favored in order of gzip, deflate, then
* compress. Deflate is always smallest and generally faster, but is
* rarely sent by servers, so client support could be buggier.
*
* @param bool $allowCompress allow the older compress encoding
*
* @param bool $allowDeflate allow the more recent deflate encoding
*
* @return array two values, 1st is the actual encoding method, 2nd is the
* alias of that method to use in the Content-Encoding header (some browsers
* call gzip "x-gzip" etc.)
*/
public static function getAcceptedEncoding($allowCompress = true, $allowDeflate = true)
{
// @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
if (! isset($_SERVER['HTTP_ACCEPT_ENCODING'])
|| self::isBuggyIe())
{
return array('', '');
}
$ae = $_SERVER['HTTP_ACCEPT_ENCODING'];
// gzip checks (quick)
if (0 === strpos($ae, 'gzip,') // most browsers
|| 0 === strpos($ae, 'deflate, gzip,') // opera
) {
return array('gzip', 'gzip');
}
// gzip checks (slow)
if (preg_match(
'@(?:^|,)\\s*((?:x-)?gzip)\\s*(?:$|,|;\\s*q=(?:0\\.|1))@'
,$ae
,$m)) {
return array('gzip', $m[1]);
}
if ($allowDeflate) {
// deflate checks
$aeRev = strrev($ae);
if (0 === strpos($aeRev, 'etalfed ,') // ie, webkit
|| 0 === strpos($aeRev, 'etalfed,') // gecko
|| 0 === strpos($ae, 'deflate,') // opera
// slow parsing
|| preg_match(
'@(?:^|,)\\s*deflate\\s*(?:$|,|;\\s*q=(?:0\\.|1))@', $ae)) {
return array('deflate', 'deflate');
}
}
if ($allowCompress && preg_match(
'@(?:^|,)\\s*((?:x-)?compress)\\s*(?:$|,|;\\s*q=(?:0\\.|1))@'
,$ae
,$m)) {
return array('compress', $m[1]);
}
return array('', '');
}
/**
* Encode (compress) the content
*
* If the encode method is '' (none) or compression level is 0, or the 'zlib'
* extension isn't loaded, we return false.
*
* Then the appropriate gz_* function is called to compress the content. If
* this fails, false is returned.
*
* The header "Vary: Accept-Encoding" is added. If encoding is successful,
* the Content-Length header is updated, and Content-Encoding is also added.
*
* @param int $compressionLevel given to zlib functions. If not given, the
* class default will be used.
*
* @return bool success true if the content was actually compressed
*/
public function encode($compressionLevel = null)
{
if (! self::isBuggyIe()) {
$this->_headers['Vary'] = 'Accept-Encoding';
}
if (null === $compressionLevel) {
$compressionLevel = self::$compressionLevel;
}
if ('' === $this->_encodeMethod[0]
|| ($compressionLevel == 0)
|| !extension_loaded('zlib'))
{
return false;
}
if ($this->_encodeMethod[0] === 'deflate') {
$encoded = gzdeflate($this->_content, $compressionLevel);
} elseif ($this->_encodeMethod[0] === 'gzip') {
$encoded = gzencode($this->_content, $compressionLevel);
} else {
$encoded = gzcompress($this->_content, $compressionLevel);
}
if (false === $encoded) {
return false;
}
$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;
}
/**
* Encode and send appropriate headers and content
*
* This is a convenience method for common use of the class
*
* @param string $content
*
* @param int $compressionLevel given to zlib functions. If not given, the
* class default will be used.
*
* @return bool success true if the content was actually compressed
*/
public static function output($content, $compressionLevel = null)
{
if (null === $compressionLevel) {
$compressionLevel = self::$compressionLevel;
}
$he = new HTTP_Encoder(array('content' => $content));
$ret = $he->encode($compressionLevel);
$he->sendAll();
return $ret;
}
/**
* Is the browser an IE version earlier than 6 SP2?
*
* @return bool
*/
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 ')
|| false !== strpos($ua, 'Opera')) {
return false;
}
// no regex = faaast
$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;
}
File diff suppressed because it is too large Load Diff
-608
View File
@@ -1,608 +0,0 @@
<?php
/**
* Class Minify
* @package Minify
*/
/**
* Minify - Combines, minifies, and caches JavaScript and CSS files on demand.
*
* See README for usage instructions (for now).
*
* This library was inspired by {@link mailto:[email protected] jscsscomp by Maxim Martynyuk}
* and by the article {@link http://www.hunlock.com/blogs/Supercharged_Javascript "Supercharged JavaScript" by Patrick Hunlock}.
*
* Requires PHP 5.1.0.
* Tested on PHP 5.1.6.
*
* @package Minify
* @author Ryan Grove <[email protected]>
* @author Stephen Clay <[email protected]>
* @copyright 2008 Ryan Grove, Stephen Clay. All rights reserved.
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @link http://code.google.com/p/minify/
*/
class Minify {
const VERSION = '2.2.0';
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?
*
* If you upload files from Windows to a non-Windows server, Windows may report
* incorrect mtimes for the files. Immediately after modifying and uploading a
* file, use the touch command to update the mtime on the server. If the mtime
* jumps ahead by a number of hours, set this variable to that number. If the mtime
* moves back, this should not be needed.
*
* @var int $uploaderHoursBehind
*/
public static $uploaderHoursBehind = 0;
/**
* If this string is not empty AND the serve() option 'bubbleCssImports' is
* NOT set, then serve() will check CSS files for @import declarations that
* appear too late in the combined stylesheet. If found, serve() will prepend
* the output with this warning.
*
* @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.
*
* If not called, Minify will not use a cache and, for each 200 response, will
* need to recombine files, minify and encode the output.
*
* @param mixed $cache object with identical interface as Minify_Cache_File or
* a directory path, or null to disable caching. (default = '')
*
* @param bool $fileLocking (default = true) This only applies if the first
* parameter is a string.
*
* @return null
*/
public static function setCache($cache = '', $fileLocking = true)
{
if (is_string($cache)) {
self::$_cache = new Minify_Cache_File($cache, $fileLocking);
} else {
self::$_cache = $cache;
}
}
/**
* Serve a request for a minified file.
*
* Here are the available options and defaults in the base controller:
*
* 'isPublic' : send "public" instead of "private" in Cache-Control
* headers, allowing shared caches to cache the output. (default true)
*
* 'quiet' : set to true to have serve() return an array rather than sending
* any headers/output (default false)
*
* 'encodeOutput' : set to false to disable content encoding, and not send
* the Vary header (default true)
*
* 'encodeMethod' : generally you should let this be determined by
* HTTP_Encoder (leave null), but you can force a particular encoding
* to be returned, by setting this to 'gzip' or '' (no encoding)
*
* 'encodeLevel' : level of encoding compression (0 to 9, default 9)
*
* 'contentTypeCharset' : appended to the Content-Type header sent. Set to a falsey
* value to remove. (default 'utf-8')
*
* 'maxAge' : set this to the number of seconds the client should use its cache
* before revalidating with the server. This sets Cache-Control: max-age and the
* Expires header. Unlike the old 'setExpires' setting, this setting will NOT
* prevent conditional GETs. Note this has nothing to do with server-side caching.
*
* 'rewriteCssUris' : If true, serve() will automatically set the 'currentDir'
* minifier option to enable URI rewriting in CSS files (default true)
*
* 'bubbleCssImports' : If true, all @import declarations in combined CSS
* files will be move to the top. Note this may alter effective CSS values
* due to a change in order. (default false)
*
* 'debug' : set to true to minify all sources with the 'Lines' controller, which
* eases the debugging of combined files. This also prevents 304 responses.
* @see Minify_Lines::minify()
*
* 'minifiers' : to override Minify's default choice of minifier function for
* a particular content-type, specify your callback under the key of the
* content-type:
* <code>
* // call customCssMinifier($css) for all CSS minification
* $options['minifiers'][Minify::TYPE_CSS] = 'customCssMinifier';
*
* // don't minify Javascript at all
* $options['minifiers'][Minify::TYPE_JS] = '';
* </code>
*
* 'minifierOptions' : to send options to the minifier function, specify your options
* under the key of the content-type. E.g. To send the CSS minifier an option:
* <code>
* // give CSS minifier array('optionName' => 'optionValue') as 2nd argument
* $options['minifierOptions'][Minify::TYPE_CSS]['optionName'] = 'optionValue';
* </code>
*
* 'contentType' : (optional) this is only needed if your file extension is not
* js/css/html. The given content-type will be sent regardless of source file
* extension, so this should not be used in a Groups config with other
* Javascript/CSS files.
*
* Any controller options are documented in that controller's setupSources() method.
*
* @param mixed $controller instance of subclass of Minify_Controller_Base or string
* name of controller. E.g. 'Files'
*
* @param array $options controller/serve options
*
* @return null|array if the 'quiet' option is set to true, an array
* with keys "success" (bool), "statusCode" (int), "content" (string), and
* "headers" (array).
*
* @throws Exception
*/
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;
$controller = new $class();
/* @var Minify_Controller_Base $controller */
}
// set up controller sources and mix remaining options with
// controller defaults
$options = $controller->setupSources($options);
$options = $controller->analyzeSources($options);
self::$_options = $controller->mixInDefaultOptions($options);
// check request validity
if (! $controller->sources) {
// invalid request!
if (! self::$_options['quiet']) {
self::_errorExit(self::$_options['badRequestHeader'], self::URL_DEBUG);
} else {
list(,$statusCode) = explode(' ', self::$_options['badRequestHeader']);
return array(
'success' => false
,'statusCode' => (int)$statusCode
,'content' => ''
,'headers' => array()
);
}
}
self::$_controller = $controller;
if (self::$_options['debug']) {
self::_setupDebug($controller->sources);
self::$_options['maxAge'] = 0;
}
// determine encoding
if (self::$_options['encodeOutput']) {
$sendVary = true;
if (self::$_options['encodeMethod'] !== null) {
// controller specifically requested this
$contentEncoding = self::$_options['encodeMethod'];
} else {
// sniff request header
// depending on what the client accepts, $contentEncoding may be
// '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)
}
// check client cache
$cgOptions = array(
'lastModifiedTime' => self::$_options['lastModifiedTime']
,'isPublic' => self::$_options['isPublic']
,'encoding' => self::$_options['encodeMethod']
);
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) {
// client's cache is valid
if (! self::$_options['quiet']) {
$cg->sendHeaders();
return;
} else {
return array(
'success' => true
,'statusCode' => 304
,'content' => ''
,'headers' => $cg->getHeaders()
);
}
} else {
// client will need output
$headers = $cg->getHeaders();
unset($cg);
}
if (self::$_options['contentType'] === self::TYPE_CSS
&& self::$_options['rewriteCssUris']) {
foreach($controller->sources as $key => $source) {
if ($source->filepath
&& !isset($source->minifyOptions['currentDir'])
&& !isset($source->minifyOptions['prependRelativePath'])
) {
$source->minifyOptions['currentDir'] = dirname($source->filepath);
}
}
}
// check server 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 = self::_getCacheId();
$fullCacheId = (self::$_options['encodeMethod'])
? $cacheId . '.gz'
: $cacheId;
// check cache for valid entry
$cacheIsReady = self::$_cache->isValid($fullCacheId, self::$_options['lastModifiedTime']);
if ($cacheIsReady) {
$cacheContentLength = self::$_cache->getSize($fullCacheId);
} else {
// generate & cache content
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::$_options['encodeMethod']) {
self::$_cache->store($cacheId . '.gz', gzencode($content, self::$_options['encodeLevel']));
}
}
} else {
// no cache
$cacheIsReady = false;
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
$content = gzencode($content, self::$_options['encodeLevel']);
}
// add headers
$headers['Content-Length'] = $cacheIsReady
? $cacheContentLength
: ((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'] && $sendVary) {
$headers['Vary'] = 'Accept-Encoding';
}
if (! self::$_options['quiet']) {
// output headers & content
foreach ($headers as $name => $val) {
header($name . ': ' . $val);
}
if ($cacheIsReady) {
self::$_cache->display($fullCacheId);
} else {
echo $content;
}
} else {
return array(
'success' => true
,'statusCode' => 200
,'content' => $cacheIsReady
? self::$_cache->fetch($fullCacheId)
: $content
,'headers' => $headers
);
}
}
/**
* Return combined minified content for a set of sources
*
* No internal caching will be used and the content will not be HTTP encoded.
*
* @param array $sources array of filepaths and/or Minify_Source objects
*
* @param array $options (optional) array of options for serve. By default
* these are already set: quiet = true, encodeMethod = '', lastModifiedTime = 0.
*
* @return string
*/
public static function combine($sources, $options = array())
{
$cache = self::$_cache;
self::$_cache = null;
$options = array_merge(array(
'files' => (array)$sources
,'quiet' => true
,'encodeMethod' => ''
,'lastModifiedTime' => 0
), $options);
$out = self::serve('Files', $options);
self::$_cache = $cache;
return $out['content'];
}
/**
* Set $_SERVER['DOCUMENT_ROOT']. On IIS, the value is created from SCRIPT_FILENAME and SCRIPT_NAME.
*
* @param string $docRoot value to use for DOCUMENT_ROOT
*/
public static function setDocRoot($docRoot = '')
{
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['SCRIPT_FILENAME']) - strlen($_SERVER['SCRIPT_NAME']));
$_SERVER['DOCUMENT_ROOT'] = rtrim($_SERVER['DOCUMENT_ROOT'], '\\');
}
}
/**
* Any Minify_Cache_* object or null (i.e. no server cache is used)
*
* @var Minify_Cache_File
*/
private static $_cache = null;
/**
* Active controller for current request
*
* @var Minify_Controller_Base
*/
protected static $_controller = null;
/**
* 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 Minify_Source[] $sources Minify_Source instances
*/
protected static function _setupDebug($sources)
{
foreach ($sources as $source) {
$source->minifier = array('Minify_Lines', 'minify');
$id = $source->getId();
$source->minifyOptions = array(
'id' => (is_file($id) ? basename($id) : $id)
);
}
}
/**
* Combines sources and minifies the result.
*
* @return string
*
* @throws Exception
*/
protected static function _combineMinify()
{
$type = self::$_options['contentType']; // ease readability
// when combining scripts, make sure all statements separated and
// trailing single line comment is terminated
$implodeSeparator = ($type === self::TYPE_JS)
? "\n;"
: '';
// allow the user to pass a particular array of options to each
// minifier (designated by type). source objects may still override
// these
$defaultOptions = isset(self::$_options['minifierOptions'][$type])
? self::$_options['minifierOptions'][$type]
: array();
// if minifier not set, default is no minification. source objects
// may still override this
$defaultMinifier = isset(self::$_options['minifiers'][$type])
? self::$_options['minifiers'][$type]
: false;
// 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
: $defaultMinifier;
$options = (null !== $source->minifyOptions)
? array_merge($defaultOptions, $source->minifyOptions)
: $defaultOptions;
}
// 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) {
try {
$content[] = call_user_func($lastMinifier, $imploded, $lastOptions);
} catch (Exception $e) {
throw new Exception("Exception in minifier: " . $e->getMessage());
}
} else {
$content[] = $imploded;
}
}
// 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);
}
// do any post-processing (esp. for editing build URIs)
if (self::$_options['postprocessorRequire']) {
require_once self::$_options['postprocessorRequire'];
}
if (self::$_options['postprocessor']) {
$content = call_user_func(self::$_options['postprocessor'], $content, $type);
}
return $content;
}
/**
* Make a unique cache id for for this request.
*
* Any settings that could affect output are taken into consideration
*
* @param string $prefix
*
* @return string
*/
protected static function _getCacheId($prefix = 'minify')
{
$name = preg_replace('/[^a-zA-Z0-9\\.=_,]/', '', self::$_controller->selectionId);
$name = preg_replace('/\\.+/', '.', $name);
$name = substr($name, 0, 100 - 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.
*
* @param string $css
*
* @return string
*/
protected static function _handleCssImports($css)
{
if (self::$_options['bubbleCssImports']) {
// bubble CSS imports
preg_match_all('/@import.*?;/', $css, $imports);
$css = implode('', $imports[0]) . preg_replace('/@import.*?;/', '', $css);
} else if ('' !== self::$importWarning) {
// remove comments so we don't mistake { in a comment as a block
$noCommentCss = preg_replace('@/\\*[\\s\\S]*?\\*/@', '', $css);
$lastImportPos = strrpos($noCommentCss, '@import');
$firstBlockPos = strpos($noCommentCss, '{');
if (false !== $lastImportPos
&& false !== $firstBlockPos
&& $firstBlockPos < $lastImportPos
) {
// { appears before @import : prepend warning
$css = self::$importWarning . $css;
}
}
return $css;
}
}
-101
View File
@@ -1,101 +0,0 @@
<?php
/**
* Class Minify_Build
* @package Minify
*/
/**
* Maintain a single last modification time for a group of Minify sources to
* allow use of far off Expires headers in Minify.
*
* <code>
* // in config file
* $groupSources = array(
* 'js' => array('file1.js', 'file2.js')
* ,'css' => array('file1.css', 'file2.css', 'file3.css')
* )
*
* // during HTML generation
* $jsBuild = new Minify_Build($groupSources['js']);
* $cssBuild = new Minify_Build($groupSources['css']);
*
* $script = "<script type='text/javascript' src='"
* . $jsBuild->uri('/min.php/js') . "'></script>";
* $link = "<link rel='stylesheet' type='text/css' href='"
* . $cssBuild->uri('/min.php/css') . "'>";
*
* // in min.php
* Minify::serve('Groups', array(
* 'groups' => $groupSources
* ,'setExpires' => (time() + 86400 * 365)
* ));
* </code>
*
* @package Minify
* @author Stephen Clay <[email protected]>
*/
class Minify_Build {
/**
* Last modification time of all files in the build
*
* @var int
*/
public $lastModified = 0;
/**
* String to use as ampersand in uri(). Set this to '&' if
* you are not HTML-escaping URIs.
*
* @var string
*/
public static $ampersand = '&amp;';
/**
* Get a time-stamped URI
*
* <code>
* echo $b->uri('/site.js');
* // outputs "/site.js?1678242"
*
* echo $b->uri('/scriptaculous.js?load=effects');
* // outputs "/scriptaculous.js?load=effects&amp1678242"
* </code>
*
* @param string $uri
* @param boolean $forceAmpersand (default = false) Force the use of ampersand to
* append the timestamp to the URI.
* @return string
*/
public function uri($uri, $forceAmpersand = false) {
$sep = ($forceAmpersand || strpos($uri, '?') !== false)
? self::$ampersand
: '?';
return "{$uri}{$sep}{$this->lastModified}";
}
/**
* Create a build object
*
* @param array $sources array of Minify_Source objects and/or file paths
*
* @return null
*/
public function __construct($sources)
{
$max = 0;
foreach ((array)$sources as $source) {
if ($source instanceof Minify_Source) {
$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));
}
}
}
$this->lastModified = $max;
}
}
-99
View File
@@ -1,99 +0,0 @@
<?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(
'compress' => true,
'removeCharsets' => true,
'preserveComments' => true,
'currentDir' => null,
'docRoot' => $_SERVER['DOCUMENT_ROOT'],
'prependRelativePath' => null,
'symlinks' => array(),
), $options);
if ($options['removeCharsets']) {
$css = preg_replace('/@charset[^;]+;\\s*/', '', $css);
}
if ($options['compress']) {
if (! $options['preserveComments']) {
$css = Minify_CSS_Compressor::process($css, $options);
} else {
$css = Minify_CommentPreserver::process(
$css
,array('Minify_CSS_Compressor', 'process')
,array($options)
);
}
}
if (! $options['currentDir'] && ! $options['prependRelativePath']) {
return $css;
}
if ($options['currentDir']) {
return Minify_CSS_UriRewriter::rewrite(
$css
,$options['currentDir']
,$options['docRoot']
,$options['symlinks']
);
} else {
return Minify_CSS_UriRewriter::prepend(
$css
,$options['prependRelativePath']
);
}
}
}
-249
View File
@@ -1,249 +0,0 @@
<?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];
}
}
-307
View File
@@ -1,307 +0,0 @@
<?php
/**
* Class Minify_CSS_UriRewriter
* @package Minify
*/
/**
* Rewrite file-relative URIs as root-relative in CSS files
*
* @package Minify
* @author Stephen Clay <[email protected]>
*/
class Minify_CSS_UriRewriter {
/**
* rewrite() and rewriteRelative() append debugging information here
*
* @var string
*/
public static $debugText = '';
/**
* In CSS content, rewrite file relative URIs as root relative
*
* @param string $css
*
* @param string $currentDir The directory of the current CSS file.
*
* @param string $docRoot The document root of the web site in which
* the CSS file resides (default = $_SERVER['DOCUMENT_ROOT']).
*
* @param array $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 rewrite($css, $currentDir, $docRoot = null, $symlinks = array())
{
self::$_docRoot = self::_realpath(
$docRoot ? $docRoot : $_SERVER['DOCUMENT_ROOT']
);
self::$_currentDir = self::_realpath($currentDir);
self::$_symlinks = array();
// normalize symlinks
foreach ($symlinks as $link => $target) {
$link = ($link === '//')
? self::$_docRoot
: str_replace('//', self::$_docRoot . '/', $link);
$link = strtr($link, '/', DIRECTORY_SEPARATOR);
self::$_symlinks[$link] = self::_realpath($target);
}
self::$debugText .= "docRoot : " . self::$_docRoot . "\n"
. "currentDir : " . self::$_currentDir . "\n";
if (self::$_symlinks) {
self::$debugText .= "symlinks : " . var_export(self::$_symlinks, 1) . "\n";
}
self::$debugText .= "\n";
$css = self::_trimUrls($css);
// rewrite
$css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/'
,array(self::$className, '_processUriCB'), $css);
$css = preg_replace_callback('/url\\(\\s*([\'"](.*?)[\'"]|[^\\)\\s]+)\\s*\\)/'
,array(self::$className, '_processUriCB'), $css);
return $css;
}
/**
* In CSS content, prepend a path to relative URIs
*
* @param string $css
*
* @param string $path The path to prepend.
*
* @return string
*/
public static function prepend($css, $path)
{
self::$_prependPath = $path;
$css = self::_trimUrls($css);
// append
$css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/'
,array(self::$className, '_processUriCB'), $css);
$css = preg_replace_callback('/url\\(\\s*([\'"](.*?)[\'"]|[^\\)\\s]+)\\s*\\)/'
,array(self::$className, '_processUriCB'), $css);
self::$_prependPath = null;
return $css;
}
/**
* Get a root relative URI from a file relative URI
*
* <code>
* Minify_CSS_UriRewriter::rewriteRelative(
* '../img/hello.gif'
* , '/home/user/www/css' // path of CSS file
* , '/home/user/www' // doc root
* );
* // returns '/img/hello.gif'
*
* // example where static files are stored in a symlinked directory
* Minify_CSS_UriRewriter::rewriteRelative(
* 'hello.gif'
* , '/var/staticFiles/theme'
* , '/home/user/www'
* , array('/home/user/www/static' => '/var/staticFiles')
* );
* // returns '/static/theme/hello.gif'
* </code>
*
* @param string $uri file relative URI
*
* @param string $realCurrentDir realpath of the current file's directory.
*
* @param string $realDocRoot realpath of the site document root.
*
* @param array $symlinks (default = array()) If the file is stored in
* a symlink-ed directory, provide an array of link paths to
* real target paths, where the link paths "appear" to be within the document
* root. E.g.:
* <code>
* array('/home/foo/www/not/real/path' => '/real/target/path') // unix
* array('C:\\htdocs\\not\\real' => 'D:\\real\\target\\path') // Windows
* </code>
*
* @return string
*/
public static function rewriteRelative($uri, $realCurrentDir, $realDocRoot, $symlinks = array())
{
// prepend path with current dir separator (OS-independent)
$path = strtr($realCurrentDir, '/', DIRECTORY_SEPARATOR)
. DIRECTORY_SEPARATOR . strtr($uri, '/', DIRECTORY_SEPARATOR);
self::$debugText .= "file-relative URI : {$uri}\n"
. "path prepended : {$path}\n";
// "unresolve" a symlink back to doc root
foreach ($symlinks as $link => $target) {
if (0 === strpos($path, $target)) {
// replace $target with $link
$path = $link . substr($path, strlen($target));
self::$debugText .= "symlink unresolved : {$path}\n";
break;
}
}
// strip doc root
$path = substr($path, strlen($realDocRoot));
self::$debugText .= "docroot stripped : {$path}\n";
// fix to root-relative URI
$uri = strtr($path, '/\\', '//');
$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.
*
* @param string $path
*
* @return mixed path with no trailing slash
*/
protected static function _realpath($path)
{
$realPath = realpath($path);
if ($realPath !== false) {
$path = $realPath;
}
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);
}
// if not root/scheme relative and not starts with scheme
if (!preg_match('~^(/|[a-z]+\:)~', $uri)) {
// 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})";
}
}
-85
View File
@@ -1,85 +0,0 @@
<?php
/**
* Class Minify_CSSmin
* @package Minify
*/
/**
* Wrapper for CSSmin
*
* This class uses CSSmin and Minify_CSS_UriRewriter to minify CSS and rewrite relative URIs.
*
* @package Minify
* @author Stephen Clay <[email protected]>
*/
class Minify_CSSmin {
/**
* Minify a CSS string
*
* @param string $css
*
* @param array $options available options:
*
* '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(
'compress' => true,
'removeCharsets' => true,
'currentDir' => null,
'docRoot' => $_SERVER['DOCUMENT_ROOT'],
'prependRelativePath' => null,
'symlinks' => array(),
), $options);
if ($options['removeCharsets']) {
$css = preg_replace('/@charset[^;]+;\\s*/', '', $css);
}
if ($options['compress']) {
$obj = new CSSmin();
$css = $obj->run($css);
}
if (! $options['currentDir'] && ! $options['prependRelativePath']) {
return $css;
}
if ($options['currentDir']) {
return Minify_CSS_UriRewriter::rewrite(
$css
,$options['currentDir']
,$options['docRoot']
,$options['symlinks']
);
} else {
return Minify_CSS_UriRewriter::prepend(
$css
,$options['prependRelativePath']
);
}
}
}
-133
View File
@@ -1,133 +0,0 @@
<?php
/**
* Class Minify_Cache_APC
* @package Minify
*/
/**
* APC-based cache class for Minify
*
* <code>
* Minify::setCache(new Minify_Cache_APC());
* </code>
*
* @package Minify
* @author Chris Edwards
**/
class Minify_Cache_APC {
/**
* Create a Minify_Cache_APC 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 apc_store($id, "{$_SERVER['REQUEST_TIME']}|{$data}", $this->_exp);
}
/**
* Get the size of a cache entry
*
* @param string $id cache id
*
* @return int size in bytes
*/
public function getSize($id)
{
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);
}
/**
* 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)
{
return ($this->_fetch($id) && ($this->_lm >= $srcMtime));
}
/**
* 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 apc, store in instance
*
* @param string $id
*
* @return bool success
*/
private function _fetch($id)
{
if ($this->_id === $id) {
return true;
}
$ret = apc_fetch($id);
if (false === $ret) {
$this->_id = null;
return false;
}
list($this->_lm, $this->_data) = explode('|', $ret, 2);
$this->_id = $id;
return true;
}
}
-197
View File
@@ -1,197 +0,0 @@
<?php
/**
* Class Minify_Cache_File
* @package Minify
*/
class Minify_Cache_File {
public function __construct($path = '', $fileLocking = false)
{
if (! $path) {
$path = self::tmp();
}
$this->_locking = $fileLocking;
$this->_path = $path;
}
/**
* Write data to cache.
*
* @param string $id cache id (e.g. a filename)
*
* @param string $data
*
* @return bool success
*/
public function store($id, $data)
{
$flag = $this->_locking
? LOCK_EX
: null;
$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;
}
/**
* Get the size of a cache entry
*
* @param string $id cache id (e.g. a filename)
*
* @return int size in bytes
*/
public function getSize($id)
{
return filesize($this->_path . '/' . $id);
}
/**
* Does a valid cache entry exist?
*
* @param string $id cache id (e.g. a filename)
*
* @param int $srcMtime mtime of the original source file(s)
*
* @return bool exists
*/
public function isValid($id, $srcMtime)
{
$file = $this->_path . '/' . $id;
return (is_file($file) && (filemtime($file) >= $srcMtime));
}
/**
* Send the cached content to output
*
* @param string $id cache id (e.g. a filename)
*/
public function display($id)
{
if ($this->_locking) {
$fp = fopen($this->_path . '/' . $id, 'rb');
flock($fp, LOCK_SH);
fpassthru($fp);
flock($fp, LOCK_UN);
fclose($fp);
} else {
readfile($this->_path . '/' . $id);
}
}
/**
* Fetch the cached content
*
* @param string $id cache id (e.g. a filename)
*
* @return string
*/
public function fetch($id)
{
if ($this->_locking) {
$fp = fopen($this->_path . '/' . $id, 'rb');
if (!$fp) {
return false;
}
flock($fp, LOCK_SH);
$ret = stream_get_contents($fp);
flock($fp, LOCK_UN);
fclose($fp);
return $ret;
} else {
return file_get_contents($this->_path . '/' . $id);
}
}
/**
* Fetch the cache path used
*
* @return string
*/
public function getPath()
{
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)
{
Minify_Logger::log($msg);
}
private $_path = null;
private $_locking = null;
}
-140
View File
@@ -1,140 +0,0 @@
<?php
/**
* Class Minify_Cache_Memcache
* @package Minify
*/
/**
* Memcache-based cache class for Minify
*
* <code>
* // fall back to disk caching if memcache can't connect
* $memcache = new Memcache;
* if ($memcache->connect('localhost', 11211)) {
* Minify::setCache(new Minify_Cache_Memcache($memcache));
* } else {
* Minify::setCache();
* }
* </code>
**/
class Minify_Cache_Memcache {
/**
* Create a Minify_Cache_Memcache object, to be passed to
* Minify::setCache().
*
* @param Memcache $memcache already-connected instance
*
* @param int $expire seconds until expiration (default = 0
* meaning the item will not get an expiration date)
*
* @return null
*/
public function __construct($memcache, $expire = 0)
{
$this->_mc = $memcache;
$this->_exp = $expire;
}
/**
* Write data to cache.
*
* @param string $id cache id
*
* @param string $data
*
* @return bool success
*/
public function store($id, $data)
{
return $this->_mc->set($id, "{$_SERVER['REQUEST_TIME']}|{$data}", 0, $this->_exp);
}
/**
* Get the size of a cache entry
*
* @param string $id cache id
*
* @return int size in bytes
*/
public function getSize($id)
{
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);
}
/**
* 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)
{
return ($this->_fetch($id) && ($this->_lm >= $srcMtime));
}
/**
* 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 $_mc = null;
private $_exp = null;
// cache of most recently fetched id
private $_lm = null;
private $_data = null;
private $_id = null;
/**
* Fetch data and timestamp from memcache, store in instance
*
* @param string $id
*
* @return bool success
*/
private function _fetch($id)
{
if ($this->_id === $id) {
return true;
}
$ret = $this->_mc->get($id);
if (false === $ret) {
$this->_id = null;
return false;
}
list($this->_lm, $this->_data) = explode('|', $ret, 2);
$this->_id = $id;
return true;
}
}
-130
View File
@@ -1,130 +0,0 @@
<?php
/**
* Class Minify_Cache_Wincache
* @package Minify
*/
/**
* WinCache-based cache class for Minify
*
* <code>
* Minify::setCache(new Minify_Cache_WinCache());
* </code>
*
* @package Minify
* @author Matthias Fax
**/
class Minify_Cache_WinCache
{
/**
* Create a Minify_Cache_Wincache object, to be passed to
* Minify::setCache().
*
*
* @param int $expire seconds until expiration (default = 0
* meaning the item will not get an expiration date)
*/
public function __construct($expire = 0)
{
if (!function_exists('wincache_ucache_info')) {
throw new Exception("WinCache for PHP is not installed to be able to use Minify_Cache_WinCache!");
}
$this->_exp = $expire;
}
/**
* Write data to cache.
*
* @param string $id cache id
*
* @param string $data
*
* @return bool success
*/
public function store($id, $data)
{
return wincache_ucache_set($id, "{$_SERVER['REQUEST_TIME']}|{$data}", $this->_exp);
}
/**
* Get the size of a cache entry
*
* @param string $id cache id
*
* @return int size in bytes
*/
public function getSize($id)
{
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);
}
/**
* 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)
{
return ($this->_fetch($id) && ($this->_lm >= $srcMtime));
}
/**
* 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 WinCache, store in instance
*
* @param string $id
*
* @return bool success
*/
private function _fetch($id)
{
if ($this->_id === $id) {
return true;
}
$suc = false;
$ret = wincache_ucache_get($id, $suc);
if (!$suc) {
$this->_id = NULL;
return false;
}
list($this->_lm, $this->_data) = explode('|', $ret, 2);
$this->_id = $id;
return true;
}
}
-126
View File
@@ -1,126 +0,0 @@
<?php
/**
* Class Minify_Cache_XCache
*
* @link http://xcache.lighttpd.net/
* @package Minify
*/
/**
* XCache-based cache class for Minify
* {@see http://xcache.lighttpd.net/wiki/XcacheApi XCache API}
*
* <code>
* Minify::setCache(new Minify_Cache_XCache());
* </code>
*
* @package Minify
* @author Elan Ruusamäe <[email protected]>
**/
class Minify_Cache_XCache {
/**
* Create a Minify_Cache_XCache object, to be passed to
* Minify::setCache().
*
* @param int $expire seconds until expiration (default = 0
* meaning the item will not get an expiration date)
*/
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 xcache_set($id, "{$_SERVER['REQUEST_TIME']}|{$data}", $this->_exp);
}
/**
* Get the size of a cache entry
*
* @param string $id cache id
* @return int size in bytes
*/
public function getSize($id)
{
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);
}
/**
* 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)
{
return ($this->_fetch($id) && ($this->_lm >= $srcMtime));
}
/**
* 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 xcache, store in instance
*
* @param string $id
* @return bool success
*/
private function _fetch($id)
{
if ($this->_id === $id) {
return true;
}
$ret = xcache_get($id);
if (false === $ret) {
$this->_id = null;
return false;
}
list($this->_lm, $this->_data) = explode('|', $ret, 2);
$this->_id = $id;
return true;
}
}
@@ -1,142 +0,0 @@
<?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;
}
}
-139
View File
@@ -1,139 +0,0 @@
<?php
/**
* Class Minify_ClosureCompiler
* @package Minify
*/
/**
* Compress Javascript using the Closure Compiler
*
* You must set $jarFile and $tempDir before calling the minify functions.
* Also, depending on your shell's environment, you may need to specify
* the full path to java in $javaExecutable or use putenv() to setup the
* Java environment.
*
* <code>
* Minify_ClosureCompiler::$jarFile = '/path/to/closure-compiler-20120123.jar';
* Minify_ClosureCompiler::$tempDir = '/tmp';
* $code = Minify_ClosureCompiler::minify(
* $code,
* array('compilation_level' => 'SIMPLE_OPTIMIZATIONS')
* );
*
* --compilation_level WHITESPACE_ONLY, SIMPLE_OPTIMIZATIONS, ADVANCED_OPTIMIZATIONS
*
* </code>
*
* @todo unit tests, $options docs
* @todo more options support (or should just passthru them all?)
*
* @package Minify
* @author Stephen Clay <[email protected]>
* @author Elan Ruusamäe <[email protected]>
*/
class Minify_ClosureCompiler {
const OPTION_CHARSET = 'charset';
const OPTION_COMPILATION_LEVEL = 'compilation_level';
public static $isDebug = false;
/**
* Filepath of the Closure Compiler jar file. This must be set before
* calling minifyJs().
*
* @var string
*/
public static $jarFile = null;
/**
* Writable temp directory. This must be set before calling minifyJs().
*
* @var string
*/
public static $tempDir = null;
/**
* Filepath of "java" executable (may be needed if not in shell's PATH)
*
* @var string
*/
public static $javaExecutable = 'java';
/**
* Minify a Javascript string
*
* @param string $js
*
* @param array $options (verbose is ignored)
*
* @see https://code.google.com/p/closure-compiler/source/browse/trunk/README
*
* @return string
*
* @throws Minify_ClosureCompiler_Exception
*/
public static function minify($js, $options = array())
{
self::_prepare();
if (! ($tmpFile = tempnam(self::$tempDir, 'cc_'))) {
throw new Minify_ClosureCompiler_Exception('Minify_ClosureCompiler : could not create temp file in "'.self::$tempDir.'".');
}
file_put_contents($tmpFile, $js);
$cmd = self::_getCmd($options, $tmpFile);
exec($cmd, $output, $result_code);
unlink($tmpFile);
if ($result_code != 0) {
$message = 'Minify_ClosureCompiler : Closure Compiler execution failed.';
if (self::$isDebug) {
exec($cmd . ' 2>&1', $error);
if ($error) {
$message .= "\nReason:\n" . join("\n", $error);
}
}
throw new Minify_ClosureCompiler_Exception($message);
}
return implode("\n", $output);
}
private static function _getCmd($userOptions, $tmpFile)
{
$o = array_merge(
array(
self::OPTION_CHARSET => 'utf-8',
self::OPTION_COMPILATION_LEVEL => 'SIMPLE_OPTIMIZATIONS',
),
$userOptions
);
$charsetOption = $o[self::OPTION_CHARSET];
$cmd = self::$javaExecutable . ' -jar ' . escapeshellarg(self::$jarFile)
. (preg_match('/^[\\da-zA-Z0-9\\-]+$/', $charsetOption)
? " --charset {$charsetOption}"
: '');
foreach (array(self::OPTION_COMPILATION_LEVEL) as $opt) {
if ($o[$opt]) {
$cmd .= " --{$opt} ". escapeshellarg($o[$opt]);
}
}
return $cmd . ' ' . escapeshellarg($tmpFile);
}
private static function _prepare()
{
if (! is_file(self::$jarFile)) {
throw new Minify_ClosureCompiler_Exception('Minify_ClosureCompiler : $jarFile('.self::$jarFile.') is not a valid file.');
}
if (! is_readable(self::$jarFile)) {
throw new Minify_ClosureCompiler_Exception('Minify_ClosureCompiler : $jarFile('.self::$jarFile.') is not readable.');
}
if (! is_dir(self::$tempDir)) {
throw new Minify_ClosureCompiler_Exception('Minify_ClosureCompiler : $tempDir('.self::$tempDir.') is not a valid direcotry.');
}
if (! is_writable(self::$tempDir)) {
throw new Minify_ClosureCompiler_Exception('Minify_ClosureCompiler : $tempDir('.self::$tempDir.') is not writable.');
}
}
}
class Minify_ClosureCompiler_Exception extends Exception {}
@@ -1,89 +0,0 @@
<?php
/**
* Class Minify_CommentPreserver
* @package Minify
*/
/**
* Process a string in pieces preserving C-style comments that begin with "/*!"
*
* @package Minify
* @author Stephen Clay <[email protected]>
*/
class Minify_CommentPreserver {
/**
* String to be prepended to each preserved comment
*
* @var string
*/
public static $prepend = "\n";
/**
* String to be appended to each preserved comment
*
* @var string
*/
public static $append = "\n";
/**
* 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 comments will be surrounded by
* Minify_CommentPreserver::$preprend and Minify_CommentPreserver::$append.
*
* @param string $content
* @param callback $processor function
* @param array $args array of extra arguments to pass to the processor
* function (default = array())
* @return string
*/
public static function process($content, $processor, $args = array())
{
$ret = '';
while (true) {
list($beforeComment, $comment, $afterComment) = self::_nextComment($content);
if ('' !== $beforeComment) {
$callArgs = $args;
array_unshift($callArgs, $beforeComment);
$ret .= call_user_func_array($processor, $callArgs);
}
if (false === $comment) {
break;
}
$ret .= $comment;
$content = $afterComment;
}
return $ret;
}
/**
* Extract comments that YUI Compressor preserves.
*
* @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 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.
*/
private static function _nextComment($in)
{
if (
false === ($start = strpos($in, '/*!'))
|| false === ($end = strpos($in, '*/', $start + 3))
) {
return array($in, false, false);
}
$ret = array(
substr($in, 0, $start)
,self::$prepend . '/*!' . substr($in, $start + 3, $end - $start - 1) . self::$append
);
$endChars = (strlen($in) - $end - 2);
$ret[] = (0 === $endChars)
? ''
: substr($in, -$endChars);
return $ret;
}
}
-222
View File
@@ -1,222 +0,0 @@
<?php
/**
* Class Minify_Controller_Base
* @package Minify
*/
/**
* Base class for Minify controller
*
* The controller class validates a request and uses it to create sources
* for minification and set options like contentType. It's also responsible
* for loading minifier code upon request.
*
* @package Minify
* @author Stephen Clay <[email protected]>
*/
abstract class Minify_Controller_Base {
/**
* Setup controller sources and set an needed options for Minify::source
*
* You must override this method in your subclass controller to set
* $this->sources. If the request is NOT valid, make sure $this->sources
* is left an empty array. Then strip any controller-specific options from
* $options and return it. To serve files, $this->sources must be an array of
* Minify_Source objects.
*
* @param array $options controller and Minify options
*
* @return array $options Minify::serve options
*/
abstract public function setupSources($options);
/**
* Get default Minify options for this controller.
*
* Override in subclass to change defaults
*
* @return array options for Minify
*/
public function getDefaultMinifyOptions() {
return array(
'isPublic' => true
,'encodeOutput' => function_exists('gzdeflate')
,'encodeMethod' => null // determine later
,'encodeLevel' => 9
,'minifierOptions' => array() // no minifier options
,'contentTypeCharset' => 'utf-8'
,'maxAge' => 1800 // 30 minutes
,'rewriteCssUris' => true
,'bubbleCssImports' => false
,'quiet' => false // serve() will send headers and output
,'debug' => false
// 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
// file to require to load preprocessor
,'postprocessorRequire' => null
);
}
/**
* Get default minifiers for this controller.
*
* Override in subclass to change defaults
*
* @return array minifier callbacks for common types
*/
public function getDefaultMinifers() {
$ret[Minify::TYPE_JS] = array('JSMin', 'minify');
$ret[Minify::TYPE_CSS] = array('Minify_CSS', 'minify');
$ret[Minify::TYPE_HTML] = array('Minify_HTML', 'minify');
return $ret;
}
/**
* Is a user-given file within an allowable directory, existing,
* and having an extension js/css/html/txt ?
*
* This is a convenience function for controllers that have to accept
* user-given paths
*
* @param string $file full file path (already processed by realpath())
*
* @param array $safeDirs directories where files are safe to serve. Files can also
* be in subdirectories of these directories.
*
* @return bool file is safe
*
* @deprecated use checkAllowDirs, checkNotHidden instead
*/
public static function _fileIsSafe($file, $safeDirs)
{
$pathOk = false;
foreach ((array)$safeDirs as $safeDir) {
if (strpos($file, $safeDir) === 0) {
$pathOk = true;
break;
}
}
$base = basename($file);
if (! $pathOk || ! is_file($file) || $base[0] === '.') {
return false;
}
list($revExt) = explode('.', strrev($base));
return in_array(strrev($revExt), array('js', 'css', 'html', 'txt'));
}
/**
* @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
*
* @param array $options user options
*
* @return array mixed options
*/
public final function mixInDefaultOptions($options)
{
$ret = array_merge(
$this->getDefaultMinifyOptions(), $options
);
if (! isset($options['minifiers'])) {
$options['minifiers'] = array();
}
$ret['minifiers'] = array_merge(
$this->getDefaultMinifers(), $options['minifiers']
);
return $ret;
}
/**
* Analyze sources (if there are any) and set $options 'contentType'
* and 'lastModifiedTime' if they already aren't.
*
* @param array $options options for Minify
*
* @return array options for Minify
*/
public final function analyzeSources($options = array())
{
if ($this->sources) {
if (! isset($options['contentType'])) {
$options['contentType'] = Minify_Source::getContentType($this->sources);
}
// last modified is needed for caching, even if setExpires is set
if (! isset($options['lastModifiedTime'])) {
$max = 0;
foreach ($this->sources as $source) {
$max = max($source->lastModified, $max);
}
$options['lastModifiedTime'] = $max;
}
}
return $options;
}
/**
* Send message to the Minify logger
*
* @param string $msg
*
* @return null
*/
public function log($msg) {
Minify_Logger::log($msg);
}
}
@@ -1,76 +0,0 @@
<?php
/**
* Class Minify_Controller_Files
* @package Minify
*/
/**
* Controller class for minifying a set of files
*
* E.g. the following would serve the minified Javascript for a site
* <code>
* Minify::serve('Files', array(
* 'files' => array(
* '//js/jquery.js'
* ,'//js/plugins.js'
* ,'/home/username/file.js'
* )
* ));
* </code>
*
* As a shortcut, the controller will replace "//" at the beginning
* of a filename with $_SERVER['DOCUMENT_ROOT'] . '/'.
*
* @package Minify
* @author Stephen Clay <[email protected]>
*/
class Minify_Controller_Files extends Minify_Controller_Base {
/**
* Set up file sources
*
* @param array $options controller and Minify options
* @return array Minify options
*
* Controller options:
*
* 'files': (required) array of complete file paths, or a single path
*/
public function setupSources($options) {
// strip controller options
$files = $options['files'];
// if $files is a single object, casting will break it
if (is_object($files)) {
$files = array($files);
} elseif (! is_array($files)) {
$files = (array)$files;
}
unset($options['files']);
$sources = array();
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 (is_file($realPath)) {
$sources[] = new Minify_Source(array(
'filepath' => $realPath
));
} else {
$this->log("The path \"{$file}\" could not be found (or was not a file)");
return $options;
}
}
if ($sources) {
$this->sources = $sources;
}
return $options;
}
}
@@ -1,91 +0,0 @@
<?php
/**
* Class Minify_Controller_Groups
* @package Minify
*/
/**
* Controller class for serving predetermined groups of minimized sets, selected
* by PATH_INFO
*
* <code>
* Minify::serve('Groups', array(
* 'groups' => array(
* 'css' => array('//css/type.css', '//css/layout.css')
* ,'js' => array('//js/jquery.js', '//js/site.js')
* )
* ));
* </code>
*
* If the above code were placed in /serve.php, it would enable the URLs
* /serve.php/js and /serve.php/css
*
* As a shortcut, the controller will replace "//" at the beginning
* of a filename with $_SERVER['DOCUMENT_ROOT'] . '/'.
*
* @package Minify
* @author Stephen Clay <[email protected]>
*/
class Minify_Controller_Groups extends Minify_Controller_Base {
/**
* Set up groups of files as sources
*
* @param array $options controller and Minify options
*
* 'groups': (required) array mapping PATH_INFO strings to arrays
* of complete file paths. @see Minify_Controller_Groups
*
* @return array Minify options
*/
public function setupSources($options) {
// strip controller options
$groups = $options['groups'];
unset($options['groups']);
// mod_fcgid places PATH_INFO in ORIG_PATH_INFO
$pi = isset($_SERVER['ORIG_PATH_INFO'])
? substr($_SERVER['ORIG_PATH_INFO'], 1)
: (isset($_SERVER['PATH_INFO'])
? substr($_SERVER['PATH_INFO'], 1)
: false
);
if (false === $pi || ! isset($groups[$pi])) {
// no PATH_INFO or not a valid group
$this->log("Missing PATH_INFO or no group set for \"$pi\"");
return $options;
}
$sources = array();
$files = $groups[$pi];
// 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 (is_file($realPath)) {
$sources[] = new Minify_Source(array(
'filepath' => $realPath
));
} else {
$this->log("The path \"{$file}\" could not be found (or was not a file)");
return $options;
}
}
if ($sources) {
$this->sources = $sources;
}
return $options;
}
}
-237
View File
@@ -1,237 +0,0 @@
<?php
/**
* Class Minify_Controller_MinApp
* @package Minify
*/
/**
* Controller class for requests to /min/index.php
*
* @package Minify
* @author Stephen Clay <[email protected]>
*/
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) {
// PHP insecure by default: realpath() and other FS functions can't handle null bytes.
foreach (array('g', 'b', 'f') as $key) {
if (isset($_GET[$key])) {
$_GET[$key] = str_replace("\x00", '', (string)$_GET[$key]);
}
}
// filter controller options
$cOptions = array_merge(
array(
'allowDirs' => '//'
,'groupsOnly' => false
,'groups' => array()
,'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'])) {
// 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;
}
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;
}
}
}
}
if (! $cOptions['groupsOnly'] && isset($_GET['f'])) {
// try user files
// The following restrictions are to limit the URLs that minify will
// 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'], $m)
// no "//"
|| strpos($_GET['f'], '//') !== false
// no "\"
|| strpos($_GET['f'], '\\') !== false
) {
$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 ($files != array_unique($files)) {
$this->log("Duplicate files were specified");
return $options;
}
if (isset($_GET['b'])) {
// check for validity
if (preg_match('@^[^/]+(?:/[^/]+)*$@', $_GET['b'])
&& false === strpos($_GET['b'], '..')
&& $_GET['b'] !== '.') {
// valid base
$base = "/{$_GET['b']}/";
} else {
$this->log("GET param 'b' was invalid");
return $options;
}
} else {
$base = '/';
}
$allowDirs = array();
foreach ((array)$cOptions['allowDirs'] as $allowDir) {
$allowDirs[] = realpath(str_replace('//', $_SERVER['DOCUMENT_ROOT'] . '/', $allowDir));
}
$basenames = array(); // just for cache id
foreach ($files as $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))) {
if (preg_match('~\.css$~i', $file)) {
$spec['minifyOptions']['compress'] = false;
} else {
$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');
}
}
}
-68
View File
@@ -1,68 +0,0 @@
<?php
/**
* Class Minify_Controller_Page
* @package Minify
*/
/**
* Controller class for serving a single HTML page
*
* @link http://code.google.com/p/minify/source/browse/trunk/web/examples/1/index.php#59
* @package Minify
* @author Stephen Clay <[email protected]>
*/
class Minify_Controller_Page extends Minify_Controller_Base {
/**
* Set up source of HTML content
*
* @param array $options controller and Minify options
* @return array Minify options
*
* Controller options:
*
* 'content': (required) HTML markup
*
* 'id': (required) id of page (string for use in server-side caching)
*
* 'lastModifiedTime': timestamp of when this content changed. This
* is recommended to allow both server and client-side caching.
*
* 'minifyAll': should all CSS and Javascript blocks be individually
* minified? (default false)
*
* @todo Add 'file' option to read HTML file.
*/
public function setupSources($options) {
if (isset($options['file'])) {
$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(
'cssMinifier' => array('Minify_CSS', 'minify')
,'jsMinifier' => array('JSMin', 'minify')
);
unset($options['minifyAll']);
}
$this->sources[] = new Minify_Source($sourceSpec);
$options['contentType'] = Minify::TYPE_HTML;
return $options;
}
}
@@ -1,119 +0,0 @@
<?php
/**
* Class Minify_Controller_Version1
* @package Minify
*/
/**
* Controller class for emulating version 1 of minify.php (mostly a proof-of-concept)
*
* <code>
* Minify::serve('Version1');
* </code>
*
* @package Minify
* @author Stephen Clay <[email protected]>
*/
class Minify_Controller_Version1 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) {
// PHP insecure by default: realpath() and other FS functions can't handle null bytes.
if (isset($_GET['files'])) {
$_GET['files'] = str_replace("\x00", '', (string)$_GET['files']);
}
self::_setupDefines();
if (MINIFY_USE_CACHE) {
$cacheDir = defined('MINIFY_CACHE_DIR')
? MINIFY_CACHE_DIR
: '';
Minify::setCache($cacheDir);
}
$options['badRequestHeader'] = 'HTTP/1.0 404 Not Found';
$options['contentTypeCharset'] = MINIFY_ENCODING;
// The following restrictions are to limit the URLs that minify will
// respond to. Ideally there should be only one way to reference a file.
if (! isset($_GET['files'])
// verify at least one file, files are single comma separated,
// and are all same extension
|| ! preg_match('/^[^,]+\\.(css|js)(,[^,]+\\.\\1)*$/', $_GET['files'], $m)
// no "//" (makes URL rewriting easier)
|| strpos($_GET['files'], '//') !== false
// no "\"
|| strpos($_GET['files'], '\\') !== false
// no "./"
|| preg_match('/(?:^|[^\\.])\\.\\//', $_GET['files'])
) {
return $options;
}
$files = explode(',', $_GET['files']);
if (count($files) > MINIFY_MAX_FILES) {
return $options;
}
// strings for prepending to relative/absolute paths
$prependRelPaths = dirname($_SERVER['SCRIPT_FILENAME'])
. DIRECTORY_SEPARATOR;
$prependAbsPaths = $_SERVER['DOCUMENT_ROOT'];
$goodFiles = array();
$hasBadSource = false;
$allowDirs = isset($options['allowDirs'])
? $options['allowDirs']
: MINIFY_BASE_DIR;
foreach ($files as $file) {
// prepend appropriate string for abs/rel paths
$file = ($file[0] === '/' ? $prependAbsPaths : $prependRelPaths) . $file;
// make sure a real file!
$file = realpath($file);
// don't allow unsafe or duplicate files
if (parent::_fileIsSafe($file, $allowDirs)
&& !in_array($file, $goodFiles))
{
$goodFiles[] = $file;
$srcOptions = array(
'filepath' => $file
);
$this->sources[] = new Minify_Source($srcOptions);
} else {
$hasBadSource = true;
break;
}
}
if ($hasBadSource) {
$this->sources = array();
}
if (! MINIFY_REWRITE_CSS_URLS) {
$options['rewriteCssUris'] = false;
}
return $options;
}
private static function _setupDefines()
{
$defaults = array(
'MINIFY_BASE_DIR' => realpath($_SERVER['DOCUMENT_ROOT'])
,'MINIFY_ENCODING' => 'utf-8'
,'MINIFY_MAX_FILES' => 16
,'MINIFY_REWRITE_CSS_URLS' => true
,'MINIFY_USE_CACHE' => true
);
foreach ($defaults as $const => $val) {
if (! defined($const)) {
define($const, $val);
}
}
}
}
-26
View File
@@ -1,26 +0,0 @@
<?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;
}
}
-255
View File
@@ -1,255 +0,0 @@
<?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 {
/**
* @var boolean
*/
protected $_jsCleanComments = true;
/**
* "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 self($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.
*
* 'jsCleanComments' : (optional) whether to remove HTML comments beginning and end of script block
*
* 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
* unset, minify will sniff for an XHTML doctype.
*/
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'];
}
if (isset($options['jsCleanComments'])) {
$this->_jsCleanComments = (bool)$options['jsCleanComments'];
}
}
/**
* 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|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)
if ($this->_jsCleanComments) {
$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));
}
}
-225
View File
@@ -1,225 +0,0 @@
<?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 string|array $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
*
* @param bool $farExpires
* @param bool $debug
* @return string
*/
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;
}
/**
* Set the files that will comprise the URI we're building
*
* @param array $files
* @param bool $checkLastModified
*/
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;
}
/**
* Set the group of files that will comprise the URI we're building
*
* @param string $key
* @param bool $checkLastModified
*/
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);
$keys = explode(',', $key);
foreach ($keys as $key) {
if (isset($gc[$key])) {
$this->_lastModified = self::getLastModified($gc[$key], $this->_lastModified);
}
}
}
}
}
/**
* Get the max(lastModified) of all files
*
* @param array|string $sources
* @param int $lastModified
* @return int
*/
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) {
if (!isset($arr[0][$pos])) {
return '';
}
$c = $arr[0][$pos];
$l = count($arr);
if ($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
* @return string
*/
protected static function _getShortestUri($paths, $minRoot = '/min/') {
$pos = 0;
$base = '';
while (true) {
$c = self::_getCommonCharAtPos($paths, $pos);
if ($c === '') {
break;
} else {
$base .= $c;
}
++$pos;
}
$base = preg_replace('@[^/]+$@', '', $base);
$uri = $minRoot . 'f=' . implode(',', $paths);
if (substr($base, -1) === '/') {
// we have a base dir!
$basedPaths = $paths;
$l = count($paths);
for ($i = 0; $i < $l; ++$i) {
$basedPaths[$i] = substr($paths[$i], strlen($base));
}
$base = substr($base, 0, strlen($base) - 1);
$bUri = $minRoot . 'b=' . $base . '&f=' . implode(',', $basedPaths);
$uri = strlen($uri) < strlen($bUri)
? $uri
: $bUri;
}
return $uri;
}
}
-216
View File
@@ -1,216 +0,0 @@
<?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;
}
}
@@ -1,230 +0,0 @@
<?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 {
/**
* @var string The option key for the maximum POST byte size
*/
const OPTION_MAX_BYTES = 'maxBytes';
/**
* @var string The option key for additional params. @see __construct
*/
const OPTION_ADDITIONAL_OPTIONS = 'additionalParams';
/**
* @var string The option key for the fallback Minifier
*/
const OPTION_FALLBACK_FUNCTION = 'fallbackFunc';
/**
* @var string The option key for the service URL
*/
const OPTION_COMPILER_URL = 'compilerUrl';
/**
* @var int The default maximum POST byte size according to https://developers.google.com/closure/compiler/docs/api-ref
*/
const DEFAULT_MAX_BYTES = 200000;
/**
* @var string[] $DEFAULT_OPTIONS The default options to pass to the compiler service
*
* @note This would be a constant if PHP allowed it
*/
private static $DEFAULT_OPTIONS = array(
'output_format' => 'text',
'compilation_level' => 'SIMPLE_OPTIMIZATIONS'
);
/**
* @var string $url URL of compiler server. defaults to Google's
*/
protected $serviceUrl = 'http://closure-compiler.appspot.com/compile';
/**
* @var int $maxBytes The maximum JS size that can be sent to the compiler server in bytes
*/
protected $maxBytes = self::DEFAULT_MAX_BYTES;
/**
* @var string[] $additionalOptions Additional options to pass to the compiler service
*/
protected $additionalOptions = array();
/**
* @var callable Function to minify JS if service fails. Default is JSMin
*/
protected $fallbackMinifier = array('JSMin', 'minify');
/**
* Minify JavaScript code via HTTP request to a Closure Compiler API
*
* @param string $js input code
* @param array $options Options passed to __construct(). @see __construct
*
* @return string
*/
public static function minify($js, array $options = array())
{
$obj = new self($options);
return $obj->min($js);
}
/**
* @param array $options Options with keys available below:
*
* fallbackFunc : (callable) function to minify if service unavailable. Default is JSMin.
*
* compilerUrl : (string) URL to closure compiler server
*
* maxBytes : (int) The maximum amount of bytes to be sent as js_code in the POST request.
* Defaults to 200000.
*
* additionalParams : (string[]) Additional parameters to pass to the compiler server. Can be anything named
* in https://developers.google.com/closure/compiler/docs/api-ref except for js_code and
* output_info
*/
public function __construct(array $options = array())
{
if (isset($options[self::OPTION_FALLBACK_FUNCTION])) {
$this->fallbackMinifier = $options[self::OPTION_FALLBACK_FUNCTION];
}
if (isset($options[self::OPTION_COMPILER_URL])) {
$this->serviceUrl = $options[self::OPTION_COMPILER_URL];
}
if (isset($options[self::OPTION_ADDITIONAL_OPTIONS]) && is_array($options[self::OPTION_ADDITIONAL_OPTIONS])) {
$this->additionalOptions = $options[self::OPTION_ADDITIONAL_OPTIONS];
}
if (isset($options[self::OPTION_MAX_BYTES])) {
$this->maxBytes = (int) $options[self::OPTION_MAX_BYTES];
}
}
/**
* Call the service to perform the minification
*
* @param string $js JavaScript code
* @return string
* @throws Minify_JS_ClosureCompiler_Exception
*/
public function min($js)
{
$postBody = $this->buildPostBody($js);
if ($this->maxBytes > 0) {
$bytes = (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2))
? mb_strlen($postBody, '8bit')
: strlen($postBody);
if ($bytes > $this->maxBytes) {
throw new Minify_JS_ClosureCompiler_Exception(
'POST content larger than ' . $this->maxBytes . ' bytes'
);
}
}
$response = $this->getResponse($postBody);
if (preg_match('/^Error\(\d\d?\):/', $response)) {
if (is_callable($this->fallbackMinifier)) {
// use fallback
$response = "/* Received errors from Closure Compiler API:\n$response"
. "\n(Using fallback minifier)\n*/\n";
$response .= call_user_func($this->fallbackMinifier, $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;
}
/**
* Get the response for a given POST body
*
* @param string $postBody
* @return string
* @throws Minify_JS_ClosureCompiler_Exception
*/
protected function getResponse($postBody)
{
$allowUrlFopen = preg_match('/1|yes|on|true/i', ini_get('allow_url_fopen'));
if ($allowUrlFopen) {
$contents = file_get_contents($this->serviceUrl, false, stream_context_create(array(
'http' => array(
'method' => 'POST',
'header' => "Content-type: application/x-www-form-urlencoded\r\nConnection: close\r\n",
'content' => $postBody,
'max_redirects' => 0,
'timeout' => 15,
)
)));
} elseif (defined('CURLOPT_POST')) {
$ch = curl_init($this->serviceUrl);
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);
}
/**
* Build a POST request body
*
* @param string $js JavaScript code
* @param bool $returnErrors
* @return string
*/
protected function buildPostBody($js, $returnErrors = false)
{
return http_build_query(
array_merge(
self::$DEFAULT_OPTIONS,
$this->additionalOptions,
array(
'js_code' => $js,
'output_info' => ($returnErrors ? 'errors' : 'compiled_code')
)
),
null,
'&'
);
}
}
class Minify_JS_ClosureCompiler_Exception extends Exception {}
-143
View File
@@ -1,143 +0,0 @@
<?php
/**
* Class Minify_Lines
* @package Minify
*/
/**
* Add line numbers in C-style comments for easier debugging of combined content
*
* @package Minify
* @author Stephen Clay <[email protected]>
* @author Adam Pedersen (Issue 55 fix)
*/
class Minify_Lines {
/**
* Add line numbers in C-style comments
*
* This uses a very basic parser easily fooled by comment tokens inside
* strings or regexes, but, otherwise, generally clean code will not be
* mangled. URI rewriting can also be performed.
*
* @param string $content
*
* @param array $options available options:
*
* 'id': (optional) string to identify file. E.g. file name/path
*
* 'currentDir': (default null) if given, this is assumed to be the
* directory of the current CSS file. Using this, minify will rewrite
* all relative URIs in import/url declarations to correctly point to
* the desired files, and prepend a comment with debugging information about
* this process.
*
* @return string
*/
public static function minify($content, $options = array())
{
$id = (isset($options['id']) && $options['id'])
? $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((string) $numLines); // e.g. 103 lines = 3 digits
$inComment = false;
$i = 0;
$newLines = array();
while (null !== ($line = array_shift($lines))) {
if (('' !== $id) && (0 == $i % 50)) {
if ($inComment) {
array_push($newLines, '', "/* {$id} *|", '');
} else {
array_push($newLines, '', "/* {$id} */", '');
}
}
++$i;
$newLines[] = self::_addNote($line, $i, $inComment, $padTo);
$inComment = self::_eolInComment($line, $inComment);
}
$content = implode("\n", $newLines) . "\n";
// check for desired URI rewriting
if (isset($options['currentDir'])) {
Minify_CSS_UriRewriter::$debugText = '';
$content = Minify_CSS_UriRewriter::rewrite(
$content
,$options['currentDir']
,isset($options['docRoot']) ? $options['docRoot'] : $_SERVER['DOCUMENT_ROOT']
,isset($options['symlinks']) ? $options['symlinks'] : array()
);
$content = "/* Minify_CSS_UriRewriter::\$debugText\n\n"
. Minify_CSS_UriRewriter::$debugText . "*/\n"
. $content;
}
return $content;
}
/**
* Is the parser within a C-style comment at the end of this line?
*
* @param string $line current line of code
*
* @param bool $inComment was the parser in a comment at the
* beginning of the line?
*
* @return bool
*/
private static function _eolInComment($line, $inComment)
{
// crude way to avoid things like // */
$line = preg_replace('~//.*?(\\*/|/\\*).*~', '', $line);
while (strlen($line)) {
$search = $inComment
? '*/'
: '/*';
$pos = strpos($line, $search);
if (false === $pos) {
return $inComment;
} else {
if ($pos == 0
|| ($inComment
? substr($line, $pos, 3)
: substr($line, $pos-1, 3)) != '*/*')
{
$inComment = ! $inComment;
}
$line = substr($line, $pos + 2);
}
}
return $inComment;
}
/**
* Prepend a comment (or note) to the given line
*
* @param string $line current line of code
*
* @param string $note content of note/comment
*
* @param bool $inComment was the parser in a comment at the
* beginning of the line?
*
* @param int $padTo minimum width of comment
*
* @return string
*/
private static function _addNote($line, $note, $inComment, $padTo)
{
return $inComment
? '/* ' . str_pad($note, $padTo, ' ', STR_PAD_RIGHT) . ' *| ' . $line
: '/* ' . str_pad($note, $padTo, ' ', STR_PAD_RIGHT) . ' */ ' . $line;
}
}
-28
View File
@@ -1,28 +0,0 @@
<?php
/**
* Class Minify_Loader
* @package Minify
*/
/**
* Class autoloader
*
* @package Minify
* @author Stephen Clay <[email protected]>
*/
class Minify_Loader {
public function loadClass($class)
{
$file = dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR;
$file .= strtr($class, "\\_", DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR) . '.php';
if (is_readable($file)) {
require $file;
}
}
static public function register()
{
$inst = new self();
spl_autoload_register(array($inst, 'loadClass'));
}
}
-47
View File
@@ -1,47 +0,0 @@
<?php
/**
* Class Minify_Logger
* @package Minify
*/
/**
* Message logging class
*
* @package Minify
* @author Stephen Clay <[email protected]>
*
* @todo lose this singleton! pass log object in Minify::serve and distribute to others
*/
class Minify_Logger {
/**
* Set logger object.
*
* The object should have a method "log" that accepts a value as 1st argument and
* an optional string label as the 2nd.
*
* @param mixed $obj or a "falsey" value to disable
* @return null
*/
public static function setLogger($obj = null) {
self::$_logger = $obj
? $obj
: null;
}
/**
* Pass a message to the logger (if set)
*
* @param string $msg message to log
* @return null
*/
public static function log($msg, $label = 'Minify') {
if (! self::$_logger) return;
self::$_logger->log($msg, $label);
}
/**
* @var mixed logger object (like FirePHP) or null (i.e. no logger available)
*/
private static $_logger = null;
}
-37
View File
@@ -1,37 +0,0 @@
<?php
/**
* Class Minify_Packer
*
* To use this class you must first download the PHP port of Packer
* and place the file "class.JavaScriptPacker.php" in /lib (or your
* include_path).
* @link http://joliclic.free.fr/php/javascript-packer/en/
*
* Be aware that, as long as HTTP encoding is used, scripts minified with JSMin
* will provide better client-side performance, as they need not be unpacked in
* client-side code.
*
* @package Minify
*/
if (false === (@include 'class.JavaScriptPacker.php')) {
trigger_error(
'The script "class.JavaScriptPacker.php" is required. Please see: http:'
.'//code.google.com/p/minify/source/browse/trunk/min/lib/Minify/Packer.php'
,E_USER_ERROR
);
}
/**
* Minify Javascript using Dean Edward's Packer
*
* @package Minify
*/
class Minify_Packer {
public static function minify($code, $options = array())
{
// @todo: set encoding options based on $options :)
$packer = new JavascriptPacker($code, 'Normal', true, false);
return trim($packer->pack());
}
}
-187
View File
@@ -1,187 +0,0 @@
<?php
/**
* Class Minify_Source
* @package Minify
*/
/**
* A content source to be minified by Minify.
*
* This allows per-source minification options and the mixing of files with
* content from other sources.
*
* @package Minify
* @author Stephen Clay <[email protected]>
*/
class Minify_Source {
/**
* @var int time of last modification
*/
public $lastModified = null;
/**
* @var callback minifier function specifically for this source.
*/
public $minifier = null;
/**
* @var array minification options specific to this source.
*/
public $minifyOptions = null;
/**
* @var string full path of file
*/
public $filepath = null;
/**
* @var string HTTP Content Type (Minify requires one of the constants Minify::TYPE_*)
*/
public $contentType = null;
/**
* Create a Minify_Source
*
* In the $spec array(), you can either provide a 'filepath' to an existing
* file (existence will not be checked!) or give 'id' (unique string for
* the content), 'content' (the string content) and 'lastModified'
* (unixtime of last update).
*
* As a shortcut, the controller will replace "//" at the beginning
* of a filepath with $_SERVER['DOCUMENT_ROOT'] . '/'.
*
* @param array $spec options
*/
public function __construct($spec)
{
if (isset($spec['filepath'])) {
if (0 === strpos($spec['filepath'], '//')) {
$spec['filepath'] = $_SERVER['DOCUMENT_ROOT'] . substr($spec['filepath'], 1);
}
$segments = explode('.', $spec['filepath']);
$ext = strtolower(array_pop($segments));
switch ($ext) {
case 'js' : $this->contentType = 'application/x-javascript';
break;
case 'css' : $this->contentType = 'text/css';
break;
case 'htm' : // fallthrough
case 'html' : $this->contentType = 'text/html';
break;
}
$this->filepath = $spec['filepath'];
$this->_id = $spec['filepath'];
$this->lastModified = filemtime($spec['filepath'])
// offset for Windows uploaders with out of sync clocks
+ round(Minify::$uploaderHoursBehind * 3600);
} elseif (isset($spec['id'])) {
$this->_id = 'id::' . $spec['id'];
if (isset($spec['content'])) {
$this->_content = $spec['content'];
} else {
$this->_getContentFunc = $spec['getContentFunc'];
}
$this->lastModified = isset($spec['lastModified'])
? $spec['lastModified']
: time();
}
if (isset($spec['contentType'])) {
$this->contentType = $spec['contentType'];
}
if (isset($spec['minifier'])) {
$this->minifier = $spec['minifier'];
}
if (isset($spec['minifyOptions'])) {
$this->minifyOptions = $spec['minifyOptions'];
}
}
/**
* Get content
*
* @return string
*/
public function getContent()
{
$content = (null !== $this->filepath)
? file_get_contents($this->filepath)
: ((null !== $this->_content)
? $this->_content
: call_user_func($this->_getContentFunc, $this->_id)
);
// remove UTF-8 BOM if present
return (pack("CCC",0xef,0xbb,0xbf) === substr($content, 0, 3))
? substr($content, 3)
: $content;
}
/**
* Get id
*
* @return string
*/
public function getId()
{
return $this->_id;
}
/**
* Verifies a single minification call can handle all sources
*
* @param array $sources Minify_Source instances
*
* @return bool true iff there no sources with specific minifier preferences.
*/
public static function haveNoMinifyPrefs($sources)
{
foreach ($sources as $source) {
if (null !== $source->minifier
|| null !== $source->minifyOptions) {
return false;
}
}
return true;
}
/**
* Get unique string for a set of sources
*
* @param array $sources Minify_Source instances
*
* @return string
*/
public static function getDigest($sources)
{
foreach ($sources as $source) {
$info[] = array(
$source->_id, $source->minifier, $source->minifyOptions
);
}
return md5(serialize($info));
}
/**
* Get content type from a group of sources
*
* This is called if the user doesn't pass in a 'contentType' options
*
* @param array $sources Minify_Source instances
*
* @return string content type. e.g. 'text/css'
*/
public static function getContentType($sources)
{
foreach ($sources as $source) {
if ($source->contentType !== null) {
return $source->contentType;
}
}
return 'text/plain';
}
protected $_content = null;
protected $_getContentFunc = null;
protected $_id = null;
}
@@ -1,382 +0,0 @@
/*
* YUI Compressor
* http://developer.yahoo.com/yui/compressor/
* Author: Julien Lecomte - http://www.julienlecomte.net/
* Author: Isaac Schlueter - http://foohack.com/
* Author: Stoyan Stefanov - http://phpied.com/
* Copyright (c) 2011 Yahoo! Inc. All rights reserved.
* The copyrights embodied in the content of this file are licensed
* by Yahoo! Inc. under the BSD (revised) open source license.
*/
package com.yahoo.platform.yui.compressor;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.ArrayList;
public class CssCompressor {
private StringBuffer srcsb = new StringBuffer();
public CssCompressor(Reader in) throws IOException {
// Read the stream...
int c;
while ((c = in.read()) != -1) {
srcsb.append((char) c);
}
}
// Leave data urls alone to increase parse performance.
protected String extractDataUrls(String css, ArrayList preservedTokens) {
int maxIndex = css.length() - 1;
int appendIndex = 0;
StringBuffer sb = new StringBuffer();
Pattern p = Pattern.compile("url\\(\\s*([\"']?)data\\:");
Matcher m = p.matcher(css);
/*
* Since we need to account for non-base64 data urls, we need to handle
* ' and ) being part of the data string. Hence switching to indexOf,
* to determine whether or not we have matching string terminators and
* handling sb appends directly, instead of using matcher.append* methods.
*/
while (m.find()) {
int startIndex = m.start() + 4; // "url(".length()
String terminator = m.group(1); // ', " or empty (not quoted)
if (terminator.length() == 0) {
terminator = ")";
}
boolean foundTerminator = false;
int endIndex = m.end() - 1;
while(foundTerminator == false && endIndex+1 <= maxIndex) {
endIndex = css.indexOf(terminator, endIndex+1);
if ((endIndex > 0) && (css.charAt(endIndex-1) != '\\')) {
foundTerminator = true;
if (!")".equals(terminator)) {
endIndex = css.indexOf(")", endIndex);
}
}
}
// Enough searching, start moving stuff over to the buffer
sb.append(css.substring(appendIndex, m.start()));
if (foundTerminator) {
String token = css.substring(startIndex, endIndex);
token = token.replaceAll("\\s+", "");
preservedTokens.add(token);
String preserver = "url(___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___)";
sb.append(preserver);
appendIndex = endIndex + 1;
} else {
// No end terminator found, re-add the whole match. Should we throw/warn here?
sb.append(css.substring(m.start(), m.end()));
appendIndex = m.end();
}
}
sb.append(css.substring(appendIndex));
return sb.toString();
}
public void compress(Writer out, int linebreakpos)
throws IOException {
Pattern p;
Matcher m;
String css = srcsb.toString();
int startIndex = 0;
int endIndex = 0;
int i = 0;
int max = 0;
ArrayList preservedTokens = new ArrayList(0);
ArrayList comments = new ArrayList(0);
String token;
int totallen = css.length();
String placeholder;
css = this.extractDataUrls(css, preservedTokens);
StringBuffer sb = new StringBuffer(css);
// collect all comment blocks...
while ((startIndex = sb.indexOf("/*", startIndex)) >= 0) {
endIndex = sb.indexOf("*/", startIndex + 2);
if (endIndex < 0) {
endIndex = totallen;
}
token = sb.substring(startIndex + 2, endIndex);
comments.add(token);
sb.replace(startIndex + 2, endIndex, "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + (comments.size() - 1) + "___");
startIndex += 2;
}
css = sb.toString();
// preserve strings so their content doesn't get accidentally minified
sb = new StringBuffer();
p = Pattern.compile("(\"([^\\\\\"]|\\\\.|\\\\)*\")|(\'([^\\\\\']|\\\\.|\\\\)*\')");
m = p.matcher(css);
while (m.find()) {
token = m.group();
char quote = token.charAt(0);
token = token.substring(1, token.length() - 1);
// maybe the string contains a comment-like substring?
// one, maybe more? put'em back then
if (token.indexOf("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_") >= 0) {
for (i = 0, max = comments.size(); i < max; i += 1) {
token = token.replace("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___", comments.get(i).toString());
}
}
// minify alpha opacity in filter strings
token = token.replaceAll("(?i)progid:DXImageTransform.Microsoft.Alpha\\(Opacity=", "alpha(opacity=");
preservedTokens.add(token);
String preserver = quote + "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___" + quote;
m.appendReplacement(sb, preserver);
}
m.appendTail(sb);
css = sb.toString();
// strings are safe, now wrestle the comments
for (i = 0, max = comments.size(); i < max; i += 1) {
token = comments.get(i).toString();
placeholder = "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___";
// ! in the first position of the comment means preserve
// so push to the preserved tokens while stripping the !
if (token.startsWith("!")) {
preservedTokens.add(token);
css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___");
continue;
}
// \ in the last position looks like hack for Mac/IE5
// shorten that to /*\*/ and the next one to /**/
if (token.endsWith("\\")) {
preservedTokens.add("\\");
css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___");
i = i + 1; // attn: advancing the loop
preservedTokens.add("");
css = css.replace("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___", "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___");
continue;
}
// keep empty comments after child selectors (IE7 hack)
// e.g. html >/**/ body
if (token.length() == 0) {
startIndex = css.indexOf(placeholder);
if (startIndex > 2) {
if (css.charAt(startIndex - 3) == '>') {
preservedTokens.add("");
css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___");
}
}
}
// in all other cases kill the comment
css = css.replace("/*" + placeholder + "*/", "");
}
// Normalize all whitespace strings to single spaces. Easier to work with that way.
css = css.replaceAll("\\s+", " ");
// Remove the spaces before the things that should not have spaces before them.
// But, be careful not to turn "p :link {...}" into "p:link{...}"
// Swap out any pseudo-class colons with the token, and then swap back.
sb = new StringBuffer();
p = Pattern.compile("(^|\\})(([^\\{:])+:)+([^\\{]*\\{)");
m = p.matcher(css);
while (m.find()) {
String s = m.group();
s = s.replaceAll(":", "___YUICSSMIN_PSEUDOCLASSCOLON___");
s = s.replaceAll( "\\\\", "\\\\\\\\" ).replaceAll( "\\$", "\\\\\\$" );
m.appendReplacement(sb, s);
}
m.appendTail(sb);
css = sb.toString();
// Remove spaces before the things that should not have spaces before them.
css = css.replaceAll("\\s+([!{};:>+\\(\\)\\],])", "$1");
// bring back the colon
css = css.replaceAll("___YUICSSMIN_PSEUDOCLASSCOLON___", ":");
// retain space for special IE6 cases
css = css.replaceAll(":first\\-(line|letter)(\\{|,)", ":first-$1 $2");
// no space after the end of a preserved comment
css = css.replaceAll("\\*/ ", "*/");
// If there is a @charset, then only allow one, and push to the top of the file.
css = css.replaceAll("^(.*)(@charset \"[^\"]*\";)", "$2$1");
css = css.replaceAll("^(\\s*@charset [^;]+;\\s*)+", "$1");
// Put the space back in some cases, to support stuff like
// @media screen and (-webkit-min-device-pixel-ratio:0){
css = css.replaceAll("\\band\\(", "and (");
// Remove the spaces after the things that should not have spaces after them.
css = css.replaceAll("([!{}:;>+\\(\\[,])\\s+", "$1");
// remove unnecessary semicolons
css = css.replaceAll(";+}", "}");
// Replace 0(px,em,%) with 0.
css = css.replaceAll("([\\s:])(0)(px|em|%|in|cm|mm|pc|pt|ex)", "$1$2");
// Replace 0 0 0 0; with 0.
css = css.replaceAll(":0 0 0 0(;|})", ":0$1");
css = css.replaceAll(":0 0 0(;|})", ":0$1");
css = css.replaceAll(":0 0(;|})", ":0$1");
// Replace background-position:0; with background-position:0 0;
// same for transform-origin
sb = new StringBuffer();
p = Pattern.compile("(?i)(background-position|transform-origin|webkit-transform-origin|moz-transform-origin|o-transform-origin|ms-transform-origin):0(;|})");
m = p.matcher(css);
while (m.find()) {
m.appendReplacement(sb, m.group(1).toLowerCase() + ":0 0" + m.group(2));
}
m.appendTail(sb);
css = sb.toString();
// Replace 0.6 to .6, but only when preceded by : or a white-space
css = css.replaceAll("(:|\\s)0+\\.(\\d+)", "$1.$2");
// Shorten colors from rgb(51,102,153) to #336699
// This makes it more likely that it'll get further compressed in the next step.
p = Pattern.compile("rgb\\s*\\(\\s*([0-9,\\s]+)\\s*\\)");
m = p.matcher(css);
sb = new StringBuffer();
while (m.find()) {
String[] rgbcolors = m.group(1).split(",");
StringBuffer hexcolor = new StringBuffer("#");
for (i = 0; i < rgbcolors.length; i++) {
int val = Integer.parseInt(rgbcolors[i]);
if (val < 16) {
hexcolor.append("0");
}
hexcolor.append(Integer.toHexString(val));
}
m.appendReplacement(sb, hexcolor.toString());
}
m.appendTail(sb);
css = sb.toString();
// Shorten colors from #AABBCC to #ABC. Note that we want to make sure
// the color is not preceded by either ", " or =. Indeed, the property
// filter: chroma(color="#FFFFFF");
// would become
// filter: chroma(color="#FFF");
// which makes the filter break in IE.
// We also want to make sure we're only compressing #AABBCC patterns inside { }, not id selectors ( #FAABAC {} )
// We also want to avoid compressing invalid values (e.g. #AABBCCD to #ABCD)
p = Pattern.compile("(\\=\\s*?[\"']?)?" + "#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])" + "(:?\\}|[^0-9a-fA-F{][^{]*?\\})");
m = p.matcher(css);
sb = new StringBuffer();
int index = 0;
while (m.find(index)) {
sb.append(css.substring(index, m.start()));
boolean isFilter = (m.group(1) != null && !"".equals(m.group(1)));
if (isFilter) {
// Restore, as is. Compression will break filters
sb.append(m.group(1) + "#" + m.group(2) + m.group(3) + m.group(4) + m.group(5) + m.group(6) + m.group(7));
} else {
if( m.group(2).equalsIgnoreCase(m.group(3)) &&
m.group(4).equalsIgnoreCase(m.group(5)) &&
m.group(6).equalsIgnoreCase(m.group(7))) {
// #AABBCC pattern
sb.append("#" + (m.group(3) + m.group(5) + m.group(7)).toLowerCase());
} else {
// Non-compressible color, restore, but lower case.
sb.append("#" + (m.group(2) + m.group(3) + m.group(4) + m.group(5) + m.group(6) + m.group(7)).toLowerCase());
}
}
index = m.end(7);
}
sb.append(css.substring(index));
css = sb.toString();
// border: none -> border:0
sb = new StringBuffer();
p = Pattern.compile("(?i)(border|border-top|border-right|border-bottom|border-right|outline|background):none(;|})");
m = p.matcher(css);
while (m.find()) {
m.appendReplacement(sb, m.group(1).toLowerCase() + ":0" + m.group(2));
}
m.appendTail(sb);
css = sb.toString();
// shorter opacity IE filter
css = css.replaceAll("(?i)progid:DXImageTransform.Microsoft.Alpha\\(Opacity=", "alpha(opacity=");
// Remove empty rules.
css = css.replaceAll("[^\\}\\{/;]+\\{\\}", "");
// TODO: Should this be after we re-insert tokens. These could alter the break points. However then
// we'd need to make sure we don't break in the middle of a string etc.
if (linebreakpos >= 0) {
// Some source control tools don't like it when files containing lines longer
// than, say 8000 characters, are checked in. The linebreak option is used in
// that case to split long lines after a specific column.
i = 0;
int linestartpos = 0;
sb = new StringBuffer(css);
while (i < sb.length()) {
char c = sb.charAt(i++);
if (c == '}' && i - linestartpos > linebreakpos) {
sb.insert(i, '\n');
linestartpos = i;
}
}
css = sb.toString();
}
// Replace multiple semi-colons in a row by a single one
// See SF bug #1980989
css = css.replaceAll(";;+", ";");
// restore preserved comments and strings
for(i = 0, max = preservedTokens.size(); i < max; i++) {
css = css.replace("___YUICSSMIN_PRESERVED_TOKEN_" + i + "___", preservedTokens.get(i).toString());
}
// Trim the final string (for any leading or trailing white spaces)
css = css.trim();
// Write the output...
out.write(css);
}
}
-171
View File
@@ -1,171 +0,0 @@
<?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];
}
}
}
-156
View File
@@ -1,156 +0,0 @@
<?php
/**
* Class Minify_YUICompressor
* @package Minify
*/
/**
* Compress Javascript/CSS using the YUI Compressor
*
* You must set $jarFile and $tempDir before calling the minify functions.
* Also, depending on your shell's environment, you may need to specify
* the full path to java in $javaExecutable or use putenv() to setup the
* Java environment.
*
* <code>
* Minify_YUICompressor::$jarFile = '/path/to/yuicompressor-2.4.6.jar';
* Minify_YUICompressor::$tempDir = '/tmp';
* $code = Minify_YUICompressor::minifyJs(
* $code
* ,array('nomunge' => true, 'line-break' => 1000)
* );
* </code>
*
* Note: In case you run out stack (default is 512k), you may increase stack size in $options:
* array('stack-size' => '2048k')
*
* @todo unit tests, $options docs
*
* @package Minify
* @author Stephen Clay <[email protected]>
*/
class Minify_YUICompressor {
/**
* Filepath of the YUI Compressor jar file. This must be set before
* calling minifyJs() or minifyCss().
*
* @var string
*/
public static $jarFile = null;
/**
* Writable temp directory. This must be set before calling minifyJs()
* or minifyCss().
*
* @var string
*/
public static $tempDir = null;
/**
* Filepath of "java" executable (may be needed if not in shell's PATH)
*
* @var string
*/
public static $javaExecutable = 'java';
/**
* Minify a Javascript string
*
* @param string $js
*
* @param array $options (verbose is ignored)
*
* @see http://www.julienlecomte.net/yuicompressor/README
*
* @return string
*/
public static function minifyJs($js, $options = array())
{
return self::_minify('js', $js, $options);
}
/**
* Minify a CSS string
*
* @param string $css
*
* @param array $options (verbose is ignored)
*
* @see http://www.julienlecomte.net/yuicompressor/README
*
* @return string
*/
public static function minifyCss($css, $options = array())
{
return self::_minify('css', $css, $options);
}
private static function _minify($type, $content, $options)
{
self::_prepare();
if (! ($tmpFile = tempnam(self::$tempDir, 'yuic_'))) {
throw new Exception('Minify_YUICompressor : could not create temp file in "'.self::$tempDir.'".');
}
file_put_contents($tmpFile, $content);
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);
}
private static function _getCmd($userOptions, $type, $tmpFile)
{
$o = array_merge(
array(
'charset' => ''
,'line-break' => 5000
,'type' => $type
,'nomunge' => false
,'preserve-semi' => false
,'disable-optimizations' => false
,'stack-size' => ''
)
,$userOptions
);
$cmd = self::$javaExecutable
. (!empty($o['stack-size'])
? ' -Xss' . $o['stack-size']
: '')
. ' -jar ' . escapeshellarg(self::$jarFile)
. " --type {$type}"
. (preg_match('/^[\\da-zA-Z0-9\\-]+$/', $o['charset'])
? " --charset {$o['charset']}"
: '')
. (is_numeric($o['line-break']) && $o['line-break'] >= 0
? ' --line-break ' . (int)$o['line-break']
: '');
if ($type === 'js') {
foreach (array('nomunge', 'preserve-semi', 'disable-optimizations') as $opt) {
$cmd .= $o[$opt]
? " --{$opt}"
: '';
}
}
return $cmd . ' ' . escapeshellarg($tmpFile);
}
private static function _prepare()
{
if (! is_file(self::$jarFile)) {
throw new Exception('Minify_YUICompressor : $jarFile('.self::$jarFile.') is not a valid file.');
}
if (! is_readable(self::$jarFile)) {
throw new Exception('Minify_YUICompressor : $jarFile('.self::$jarFile.') is not readable.');
}
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.');
}
}
}
-384
View File
@@ -1,384 +0,0 @@
<?php
namespace MrClay;
use MrClay\Cli\Arg;
use InvalidArgumentException;
/**
* 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 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 Arg|string $letter
* @return Arg
*/
public function addOptionalArg($letter)
{
return $this->addArgument($letter, false);
}
/**
* @param Arg|string $letter
* @return Arg
*/
public function addRequiredArg($letter)
{
return $this->addArgument($letter, true);
}
/**
* @param string $letter
* @param bool $required
* @param Arg|null $arg
* @return Arg
* @throws InvalidArgumentException
*/
public function addArgument($letter, $required, Arg $arg = null)
{
if (! preg_match('/^[a-zA-Z]$/', $letter)) {
throw new InvalidArgumentException('$letter must be in [a-zA-Z]');
}
if (! $arg) {
$arg = new Arg($required);
}
$this->_args[$letter] = $arg;
return $arg;
}
/**
* @param string $letter
* @return 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 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 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 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);
}
}
-183
View File
@@ -1,183 +0,0 @@
<?php
namespace MrClay\Cli;
use BadMethodCallException;
/**
* 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;
}
}
@@ -0,0 +1,7 @@
in
public
extends
private
protected
implements
instanceof
@@ -0,0 +1,27 @@
do
in
let
new
var
case
else
enum
void
with
class
const
yield
delete
export
import
public
static
typeof
extends
package
private
continue
function
protected
implements
instanceof
@@ -0,0 +1,63 @@
do
if
in
for
let
new
try
var
case
else
enum
eval
null
this
true
void
with
break
catch
class
const
false
super
throw
while
yield
delete
export
import
public
return
static
switch
typeof
default
extends
finally
package
private
continue
debugger
function
arguments
interface
protected
implements
instanceof
abstract
boolean
byte
char
double
final
float
goto
int
long
native
short
synchronized
throws
transient
volatile
@@ -0,0 +1,46 @@
+
-
*
/
%
=
+=
-=
*=
/=
%=
<<=
>>=
>>>=
&=
^=
|=
&
|
^
~
<<
>>
>>>
==
===
!=
!==
>
<
>=
<=
&&
||
!
.
[
]
?
:
,
;
(
)
{
}
@@ -0,0 +1,44 @@
+
-
*
/
%
=
+=
-=
*=
/=
%=
<<=
>>=
>>>=
&=
^=
|=
&
|
^
~
<<
>>
>>>
==
===
!=
!==
>
<
>=
<=
&&
||
.
[
]
?
:
,
;
(
)
}
@@ -0,0 +1,43 @@
+
-
*
/
%
=
+=
-=
*=
/=
%=
<<=
>>=
>>>=
&=
^=
|=
&
|
^
~
<<
>>
>>>
==
===
!=
!==
>
<
>=
<=
&&
||
!
.
[
?
:
,
;
(
{
@@ -0,0 +1,668 @@
<?php
namespace MatthiasMullie\Minify;
use MatthiasMullie\Minify\Exceptions\FileImportException;
use MatthiasMullie\PathConverter\Converter;
/**
* CSS minifier.
*
* Please report bugs on https://github.com/matthiasmullie/minify/issues
*
* @author Matthias Mullie <[email protected]>
* @author Tijs Verkoyen <[email protected]>
* @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved.
* @license MIT License
*/
class CSS extends Minify
{
/**
* @var int
*/
protected $maxImportSize = 5;
/**
* @var string[]
*/
protected $importExtensions = array(
'gif' => 'data:image/gif',
'png' => 'data:image/png',
'jpe' => 'data:image/jpeg',
'jpg' => 'data:image/jpeg',
'jpeg' => 'data:image/jpeg',
'svg' => 'data:image/svg+xml',
'woff' => 'data:application/x-font-woff',
'tif' => 'image/tiff',
'tiff' => 'image/tiff',
'xbm' => 'image/x-xbitmap',
);
/**
* Set the maximum size if files to be imported.
*
* Files larger than this size (in kB) will not be imported into the CSS.
* Importing files into the CSS as data-uri will save you some connections,
* but we should only import relatively small decorative images so that our
* CSS file doesn't get too bulky.
*
* @param int $size Size in kB
*/
public function setMaxImportSize($size)
{
$this->maxImportSize = $size;
}
/**
* Set the type of extensions to be imported into the CSS (to save network
* connections).
* Keys of the array should be the file extensions & respective values
* should be the data type.
*
* @param string[] $extensions Array of file extensions
*/
public function setImportExtensions(array $extensions)
{
$this->importExtensions = $extensions;
}
/**
* Move any import statements to the top.
*
* @param string $content Nearly finished CSS content
*
* @return string
*/
protected function moveImportsToTop($content)
{
if (preg_match_all('/@import[^;]+;/', $content, $matches)) {
// remove from content
foreach ($matches[0] as $import) {
$content = str_replace($import, '', $content);
}
// add to top
$content = implode('', $matches[0]).$content;
};
return $content;
}
/**
* Combine CSS from import statements.
*
* @import's will be loaded and their content merged into the original file,
* to save HTTP requests.
*
* @param string $source The file to combine imports for.
* @param string $content The CSS content to combine imports for.
* @param string[] $parents Parent paths, for circular reference checks.
*
* @return string
*
* @throws FileImportException
*/
protected function combineImports($source, $content, $parents)
{
$importRegexes = array(
// @import url(xxx)
'/
# import statement
@import
# whitespace
\s+
# open url()
url\(
# (optional) open path enclosure
(?P<quotes>["\']?)
# fetch path
(?P<path>
# do not fetch data uris or external sources
(?!(
["\']?
(data|https?):
))
.+?
)
# (optional) close path enclosure
(?P=quotes)
# close url()
\)
# (optional) trailing whitespace
\s*
# (optional) media statement(s)
(?P<media>[^;]*)
# (optional) trailing whitespace
\s*
# (optional) closing semi-colon
;?
/ix',
// @import 'xxx'
'/
# import statement
@import
# whitespace
\s+
# open path enclosure
(?P<quotes>["\'])
# fetch path
(?P<path>
# do not fetch data uris or external sources
(?!(
["\']?
(data|https?):
))
.+?
)
# close path enclosure
(?P=quotes)
# (optional) trailing whitespace
\s*
# (optional) media statement(s)
(?P<media>[^;]*)
# (optional) trailing whitespace
\s*
# (optional) closing semi-colon
;?
/ix',
);
// find all relative imports in css
$matches = array();
foreach ($importRegexes as $importRegex) {
if (preg_match_all($importRegex, $content, $regexMatches, PREG_SET_ORDER)) {
$matches = array_merge($matches, $regexMatches);
}
}
$search = array();
$replace = array();
// loop the matches
foreach ($matches as $match) {
// get the path for the file that will be imported
$importPath = dirname($source).'/'.$match['path'];
// only replace the import with the content if we can grab the
// content of the file
if ($this->canImportFile($importPath)) {
// check if current file was not imported previously in the same
// import chain.
if (in_array($importPath, $parents)) {
throw new FileImportException('Failed to import file "'.$importPath.'": circular reference detected.');
}
// grab referenced file & minify it (which may include importing
// yet other @import statements recursively)
$minifier = new static($importPath);
$importContent = $minifier->execute($source, $parents);
// check if this is only valid for certain media
if (!empty($match['media'])) {
$importContent = '@media '.$match['media'].'{'.$importContent.'}';
}
// add to replacement array
$search[] = $match[0];
$replace[] = $importContent;
}
}
// replace the import statements
$content = str_replace($search, $replace, $content);
return $content;
}
/**
* Import files into the CSS, base64-ized.
*
* @url(image.jpg) images will be loaded and their content merged into the
* original file, to save HTTP requests.
*
* @param string $source The file to import files for.
* @param string $content The CSS content to import files for.
*
* @return string
*/
protected function importFiles($source, $content)
{
$extensions = array_keys($this->importExtensions);
$regex = '/url\((["\']?)((?!["\']?data:).*?\.('.implode('|', $extensions).'))\\1\)/i';
if ($extensions && preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) {
$search = array();
$replace = array();
// loop the matches
foreach ($matches as $match) {
// get the path for the file that will be imported
$path = $match[2];
$path = dirname($source).'/'.$path;
$extension = $match[3];
// only replace the import with the content if we're able to get
// the content of the file, and it's relatively small
if ($this->canImportFile($path) && $this->canImportBySize($path)) {
// grab content && base64-ize
$importContent = $this->load($path);
$importContent = base64_encode($importContent);
// build replacement
$search[] = $match[0];
$replace[] = 'url('.$this->importExtensions[$extension].';base64,'.$importContent.')';
}
}
// replace the import statements
$content = str_replace($search, $replace, $content);
}
return $content;
}
/**
* Minify the data.
* Perform CSS optimizations.
*
* @param string[optional] $path Path to write the data to.
* @param string[] $parents Parent paths, for circular reference checks.
*
* @return string The minified data.
*/
public function execute($path = null, $parents = array())
{
$content = '';
// loop css data (raw data and files)
foreach ($this->data as $source => $css) {
/*
* Let's first take out strings & comments, since we can't just remove
* whitespace anywhere. If whitespace occurs inside a string, we should
* leave it alone. E.g.:
* p { content: "a test" }
*/
$this->extractStrings();
$this->stripComments();
$css = $this->replace($css);
$css = $this->stripWhitespace($css);
$css = $this->shortenHex($css);
$css = $this->shortenZeroes($css);
$css = $this->shortenFontWeights($css);
$css = $this->stripEmptyTags($css);
// restore the string we've extracted earlier
$css = $this->restoreExtractedData($css);
$source = is_int($source) ? '' : $source;
$parents = $source ? array_merge($parents, array($source)) : $parents;
$css = $this->combineImports($source, $css, $parents);
$css = $this->importFiles($source, $css);
/*
* If we'll save to a new path, we'll have to fix the relative paths
* to be relative no longer to the source file, but to the new path.
* If we don't write to a file, fall back to same path so no
* conversion happens (because we still want it to go through most
* of the move code...)
*/
$converter = new Converter($source, $path ?: $source);
$css = $this->move($converter, $css);
// combine css
$content .= $css;
}
$content = $this->moveImportsToTop($content);
return $content;
}
/**
* Moving a css file should update all relative urls.
* Relative references (e.g. ../images/image.gif) in a certain css file,
* will have to be updated when a file is being saved at another location
* (e.g. ../../images/image.gif, if the new CSS file is 1 folder deeper).
*
* @param Converter $converter Relative path converter
* @param string $content The CSS content to update relative urls for.
*
* @return string
*/
protected function move(Converter $converter, $content)
{
/*
* Relative path references will usually be enclosed by url(). @import
* is an exception, where url() is not necessary around the path (but is
* allowed).
* This *could* be 1 regular expression, where both regular expressions
* in this array are on different sides of a |. But we're using named
* patterns in both regexes, the same name on both regexes. This is only
* possible with a (?J) modifier, but that only works after a fairly
* recent PCRE version. That's why I'm doing 2 separate regular
* expressions & combining the matches after executing of both.
*/
$relativeRegexes = array(
// url(xxx)
'/
# open url()
url\(
\s*
# open path enclosure
(?P<quotes>["\'])?
# fetch path
(?P<path>
# do not fetch data uris or external sources
(?!(
\s?
["\']?
(data|https?):
))
.+?
)
# close path enclosure
(?(quotes)(?P=quotes))
\s*
# close url()
\)
/ix',
// @import "xxx"
'/
# import statement
@import
# whitespace
\s+
# we don\'t have to check for @import url(), because the
# condition above will already catch these
# open path enclosure
(?P<quotes>["\'])
# fetch path
(?P<path>
# do not fetch data uris or external sources
(?!(
["\']?
(data|https?):
))
.+?
)
# close path enclosure
(?P=quotes)
/ix',
);
// find all relative urls in css
$matches = array();
foreach ($relativeRegexes as $relativeRegex) {
if (preg_match_all($relativeRegex, $content, $regexMatches, PREG_SET_ORDER)) {
$matches = array_merge($matches, $regexMatches);
}
}
$search = array();
$replace = array();
// loop all urls
foreach ($matches as $match) {
// determine if it's a url() or an @import match
$type = (strpos($match[0], '@import') === 0 ? 'import' : 'url');
// attempting to interpret GET-params makes no sense, so let's discard them for awhile
$params = strrchr($match['path'], '?');
$url = $params ? substr($match['path'], 0, -strlen($params)) : $match['path'];
// fix relative url
$url = $converter->convert($url);
// now that the path has been converted, re-apply GET-params
$url .= $params;
// build replacement
$search[] = $match[0];
if ($type == 'url') {
$replace[] = 'url('.$url.')';
} elseif ($type == 'import') {
$replace[] = '@import "'.$url.'"';
}
}
// replace urls
$content = str_replace($search, $replace, $content);
return $content;
}
/**
* Shorthand hex color codes.
* #FF0000 -> #F00.
*
* @param string $content The CSS content to shorten the hex color codes for.
*
* @return string
*/
protected function shortenHex($content)
{
$content = preg_replace('/(?<![\'"])#([0-9a-z])\\1([0-9a-z])\\2([0-9a-z])\\3(?![\'"])/i', '#$1$2$3', $content);
// we can shorten some even more by replacing them with their color name
$colors = array(
'#F0FFFF' => 'azure',
'#F5F5DC' => 'beige',
'#A52A2A' => 'brown',
'#FF7F50' => 'coral',
'#FFD700' => 'gold',
'#808080' => 'gray',
'#008000' => 'green',
'#4B0082' => 'indigo',
'#FFFFF0' => 'ivory',
'#F0E68C' => 'khaki',
'#FAF0E6' => 'linen',
'#800000' => 'maroon',
'#000080' => 'navy',
'#808000' => 'olive',
'#CD853F' => 'peru',
'#FFC0CB' => 'pink',
'#DDA0DD' => 'plum',
'#800080' => 'purple',
'#F00' => 'red',
'#FA8072' => 'salmon',
'#A0522D' => 'sienna',
'#C0C0C0' => 'silver',
'#FFFAFA' => 'snow',
'#D2B48C' => 'tan',
'#FF6347' => 'tomato',
'#EE82EE' => 'violet',
'#F5DEB3' => 'wheat',
);
return str_ireplace(array_keys($colors), $colors, $content);
}
/**
* Shorten CSS font weights.
*
* @param string $content The CSS content to shorten the font weights for.
*
* @return string
*/
protected function shortenFontWeights($content)
{
$weights = array(
'normal' => 400,
'bold' => 700,
);
$callback = function ($match) use ($weights) {
return $match[1] . $weights[$match[2]];
};
return preg_replace_callback('/(font-weight\s*:\s*)('.implode('|', array_keys($weights)).')(?=[;}])/', $callback, $content);
}
/**
* Shorthand 0 values to plain 0, instead of e.g. -0em.
*
* @param string $content The CSS content to shorten the zero values for.
*
* @return string
*/
protected function shortenZeroes($content)
{
// reusable bits of code throughout these regexes:
// before & after are used to make sure we don't match lose unintended
// 0-like values (e.g. in #000, or in http://url/1.0)
// units can be stripped from 0 values, or used to recognize non 0
// values (where wa may be able to strip a .0 suffix)
$before = '(?<=[:(, ])';
$after = '(?=[ ,);}])';
$units = '(em|ex|%|px|cm|mm|in|pt|pc|ch|rem|vh|vw|vmin|vmax|vm)';
// strip units after zeroes (0px -> 0)
// NOTE: it should be safe to remove all units for a 0 value, but in
// practice, Webkit (especially Safari) seems to stumble over at least
// 0%, potentially other units as well. Only stripping 'px' for now.
// @see https://github.com/matthiasmullie/minify/issues/60
$content = preg_replace('/'.$before.'(-?0*(\.0+)?)(?<=0)px'.$after.'/', '\\1', $content);
// strip 0-digits (.0 -> 0)
$content = preg_replace('/'.$before.'\.0+'.$units.'?'.$after.'/', '0\\1', $content);
// strip trailing 0: 50.10 -> 50.1, 50.10px -> 50.1px
$content = preg_replace('/'.$before.'(-?[0-9]+\.[0-9]+)0+'.$units.'?'.$after.'/', '\\1\\2', $content);
// strip trailing 0: 50.00 -> 50, 50.00px -> 50px
$content = preg_replace('/'.$before.'(-?[0-9]+)\.0+'.$units.'?'.$after.'/', '\\1\\2', $content);
// strip leading 0: 0.1 -> .1, 01.1 -> 1.1
$content = preg_replace('/'.$before.'(-?)0+([0-9]*\.[0-9]+)'.$units.'?'.$after.'/', '\\1\\2\\3', $content);
// strip negative zeroes (-0 -> 0) & truncate zeroes (00 -> 0)
$content = preg_replace('/'.$before.'-?0+'.$units.'?'.$after.'/', '0\\1', $content);
// remove zeroes where they make no sense in calc: e.g. calc(100px - 0)
// the 0 doesn't have any effect, and this isn't even valid without unit
// strip all `+ 0` or `- 0` occurrences: calc(10% + 0) -> calc(10%)
// looped because there may be multiple 0s inside 1 group of parentheses
do {
$previous = $content;
$content = preg_replace('/\(([^\(\)]+)\s+[\+\-]\s+0(\s+[^\(\)]+)?\)/', '(\\1\\2)', $content);
} while ( $content !== $previous );
// strip all `0 +` occurrences: calc(0 + 10%) -> calc(10%)
$content = preg_replace('/\(\s*0\s+\+\s+([^\(\)]+)\)/', '(\\1)', $content);
// strip all `0 -` occurrences: calc(0 - 10%) -> calc(-10%)
$content = preg_replace('/\(\s*0\s+\-\s+([^\(\)]+)\)/', '(-\\1)', $content);
// I'm not going to attempt to optimize away `x * 0` instances:
// it's dumb enough code already that it likely won't occur, and it's
// too complex to do right (order of operations would have to be
// respected etc)
// what I cared about most here was fixing incorrectly truncated units
return $content;
}
/**
* Strip comments from source code.
*
* @param string $content
*
* @return string
*/
protected function stripEmptyTags($content)
{
return preg_replace('/(^|\})[^\{\}]+\{\s*\}/', '\\1', $content);
}
/**
* Strip comments from source code.
*/
protected function stripComments()
{
$this->registerPattern('/\/\*.*?\*\//s', '');
}
/**
* Strip whitespace.
*
* @param string $content The CSS content to strip the whitespace for.
*
* @return string
*/
protected function stripWhitespace($content)
{
// remove leading & trailing whitespace
$content = preg_replace('/^\s*/m', '', $content);
$content = preg_replace('/\s*$/m', '', $content);
// replace newlines with a single space
$content = preg_replace('/\s+/', ' ', $content);
// remove whitespace around meta characters
// inspired by stackoverflow.com/questions/15195750/minify-compress-css-with-regex
$content = preg_replace('/\s*([\*$~^|]?+=|[{};,>~]|!important\b)\s*/', '$1', $content);
$content = preg_replace('/([\[(:])\s+/', '$1', $content);
$content = preg_replace('/\s+([\]\)])/', '$1', $content);
$content = preg_replace('/\s+(:)(?![^\}]*\{)/', '$1', $content);
// whitespace around + and - can only be stripped in selectors, like
// :nth-child(3+2n), not in things like calc(3px + 2px) or shorthands
// like 3px -2px
$content = preg_replace('/\s*([+-])\s*(?=[^}]*{)/', '$1', $content);
// remove semicolon/whitespace followed by closing bracket
$content = str_replace(';}', '}', $content);
return trim($content);
}
/**
* Check if file is small enough to be imported.
*
* @param string $path The path to the file.
*
* @return bool
*/
protected function canImportBySize($path)
{
return ($size = @filesize($path)) && $size <= $this->maxImportSize * 1024;
}
}
@@ -0,0 +1,12 @@
<?php
namespace MatthiasMullie\Minify;
/**
* @deprecated Use Exceptions\BasicException instead
*
* @author Matthias Mullie <[email protected]>
*/
abstract class Exception extends \Exception
{
}
@@ -0,0 +1,12 @@
<?php
namespace MatthiasMullie\Minify\Exceptions;
use MatthiasMullie\Minify\Exception;
/**
* @author Matthias Mullie <[email protected]>
*/
abstract class BasicException extends Exception
{
}
@@ -0,0 +1,10 @@
<?php
namespace MatthiasMullie\Minify\Exceptions;
/**
* @author Matthias Mullie <[email protected]>
*/
class FileImportException extends BasicException
{
}
@@ -0,0 +1,10 @@
<?php
namespace MatthiasMullie\Minify\Exceptions;
/**
* @author Matthias Mullie <[email protected]>
*/
class IOException extends BasicException
{
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,421 @@
<?php
namespace MatthiasMullie\Minify;
use MatthiasMullie\Minify\Exceptions\IOException;
use Psr\Cache\CacheItemInterface;
/**
* Abstract minifier class.
*
* Please report bugs on https://github.com/matthiasmullie/minify/issues
*
* @author Matthias Mullie <[email protected]>
* @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved.
* @license MIT License
*/
abstract class Minify
{
/**
* The data to be minified.
*
* @var string[]
*/
protected $data = array();
/**
* Array of patterns to match.
*
* @var string[]
*/
protected $patterns = array();
/**
* This array will hold content of strings and regular expressions that have
* been extracted from the JS source code, so we can reliably match "code",
* without having to worry about potential "code-like" characters inside.
*
* @var string[]
*/
public $extracted = array();
/**
* Init the minify class - optionally, code may be passed along already.
*/
public function __construct(/* $data = null, ... */)
{
// it's possible to add the source through the constructor as well ;)
if (func_num_args()) {
call_user_func_array(array($this, 'add'), func_get_args());
}
}
/**
* Add a file or straight-up code to be minified.
*
* @param string $data
*/
public function add($data /* $data = null, ... */)
{
// bogus "usage" of parameter $data: scrutinizer warns this variable is
// not used (we're using func_get_args instead to support overloading),
// but it still needs to be defined because it makes no sense to have
// this function without argument :)
$args = array($data) + func_get_args();
// this method can be overloaded
foreach ($args as $data) {
// redefine var
$data = (string) $data;
// load data
$value = $this->load($data);
$key = ($data != $value) ? $data : count($this->data);
// store data
$this->data[$key] = $value;
}
}
/**
* Minify the data & (optionally) saves it to a file.
*
* @param string[optional] $path Path to write the data to.
*
* @return string The minified data.
*/
public function minify($path = null)
{
$content = $this->execute($path);
// save to path
if ($path !== null) {
$this->save($content, $path);
}
return $content;
}
/**
* Minify & gzip the data & (optionally) saves it to a file.
*
* @param string[optional] $path Path to write the data to.
* @param int[optional] $level Compression level, from 0 to 9.
*
* @return string The minified & gzipped data.
*/
public function gzip($path = null, $level = 9)
{
$content = $this->execute($path);
$content = gzencode($content, $level, FORCE_GZIP);
// save to path
if ($path !== null) {
$this->save($content, $path);
}
return $content;
}
/**
* Minify the data & write it to a CacheItemInterface object.
*
* @param CacheItemInterface $item Cache item to write the data to.
*
* @return CacheItemInterface Cache item with the minifier data.
*/
public function cache(CacheItemInterface $item)
{
$content = $this->execute();
$item->set($content);
return $item;
}
/**
* Minify the data.
*
* @param string[optional] $path Path to write the data to.
*
* @return string The minified data.
*/
abstract public function execute($path = null);
/**
* Load data.
*
* @param string $data Either a path to a file or the content itself.
*
* @return string
*/
protected function load($data)
{
// check if the data is a file
if ($this->canImportFile($data)) {
$data = file_get_contents($data);
// strip BOM, if any
if (substr($data, 0, 3) == "\xef\xbb\xbf") {
$data = substr($data, 3);
}
}
return $data;
}
/**
* Save to file.
*
* @param string $content The minified data.
* @param string $path The path to save the minified data to.
*
* @throws IOException
*/
protected function save($content, $path)
{
$handler = $this->openFileForWriting($path);
$this->writeToFile($handler, $content);
@fclose($handler);
}
/**
* Register a pattern to execute against the source content.
*
* @param string $pattern PCRE pattern.
* @param string|callable $replacement Replacement value for matched pattern.
*/
protected function registerPattern($pattern, $replacement = '')
{
// study the pattern, we'll execute it more than once
$pattern .= 'S';
$this->patterns[] = array($pattern, $replacement);
}
/**
* We can't "just" run some regular expressions against JavaScript: it's a
* complex language. E.g. having an occurrence of // xyz would be a comment,
* unless it's used within a string. Of you could have something that looks
* like a 'string', but inside a comment.
* The only way to accurately replace these pieces is to traverse the JS one
* character at a time and try to find whatever starts first.
*
* @param string $content The content to replace patterns in.
*
* @return string The (manipulated) content.
*/
protected function replace($content)
{
$processed = '';
$positions = array_fill(0, count($this->patterns), -1);
$matches = array();
while ($content) {
// find first match for all patterns
foreach ($this->patterns as $i => $pattern) {
list($pattern, $replacement) = $pattern;
// no need to re-run matches that are still in the part of the
// content that hasn't been processed
if ($positions[$i] >= 0) {
continue;
}
$match = null;
if (preg_match($pattern, $content, $match)) {
$matches[$i] = $match;
// we'll store the match position as well; that way, we
// don't have to redo all preg_matches after changing only
// the first (we'll still know where those others are)
$positions[$i] = strpos($content, $match[0]);
} else {
// if the pattern couldn't be matched, there's no point in
// executing it again in later runs on this same content;
// ignore this one until we reach end of content
unset($matches[$i]);
$positions[$i] = strlen($content);
}
}
// no more matches to find: everything's been processed, break out
if (!$matches) {
$processed .= $content;
break;
}
// see which of the patterns actually found the first thing (we'll
// only want to execute that one, since we're unsure if what the
// other found was not inside what the first found)
$discardLength = min($positions);
$firstPattern = array_search($discardLength, $positions);
$match = $matches[$firstPattern][0];
// execute the pattern that matches earliest in the content string
list($pattern, $replacement) = $this->patterns[$firstPattern];
$replacement = $this->replacePattern($pattern, $replacement, $content);
// figure out which part of the string was unmatched; that's the
// part we'll execute the patterns on again next
$content = substr($content, $discardLength);
$unmatched = (string) substr($content, strpos($content, $match) + strlen($match));
// move the replaced part to $processed and prepare $content to
// again match batch of patterns against
$processed .= substr($replacement, 0, strlen($replacement) - strlen($unmatched));
$content = $unmatched;
// first match has been replaced & that content is to be left alone,
// the next matches will start after this replacement, so we should
// fix their offsets
foreach ($positions as $i => $position) {
$positions[$i] -= $discardLength + strlen($match);
}
}
return $processed;
}
/**
* This is where a pattern is matched against $content and the matches
* are replaced by their respective value.
* This function will be called plenty of times, where $content will always
* move up 1 character.
*
* @param string $pattern Pattern to match.
* @param string|callable $replacement Replacement value.
* @param string $content Content to match pattern against.
*
* @return string
*/
protected function replacePattern($pattern, $replacement, $content)
{
if (is_callable($replacement)) {
return preg_replace_callback($pattern, $replacement, $content, 1, $count);
} else {
return preg_replace($pattern, $replacement, $content, 1, $count);
}
}
/**
* Strings are a pattern we need to match, in order to ignore potential
* code-like content inside them, but we just want all of the string
* content to remain untouched.
*
* This method will replace all string content with simple STRING#
* placeholder text, so we've rid all strings from characters that may be
* misinterpreted. Original string content will be saved in $this->extracted
* and after doing all other minifying, we can restore the original content
* via restoreStrings().
*
* @param string[optional] $chars
*/
protected function extractStrings($chars = '\'"')
{
// PHP only supports $this inside anonymous functions since 5.4
$minifier = $this;
$callback = function ($match) use ($minifier) {
// check the second index here, because the first always contains a quote
if ($match[2] === '') {
/*
* Empty strings need no placeholder; they can't be confused for
* anything else anyway.
* But we still needed to match them, for the extraction routine
* to skip over this particular string.
*/
return $match[0];
}
$count = count($minifier->extracted);
$placeholder = $match[1].$count.$match[1];
$minifier->extracted[$placeholder] = $match[1].$match[2].$match[1];
return $placeholder;
};
/*
* The \\ messiness explained:
* * Don't count ' or " as end-of-string if it's escaped (has backslash
* in front of it)
* * Unless... that backslash itself is escaped (another leading slash),
* in which case it's no longer escaping the ' or "
* * So there can be either no backslash, or an even number
* * multiply all of that times 4, to account for the escaping that has
* to be done to pass the backslash into the PHP string without it being
* considered as escape-char (times 2) and to get it in the regex,
* escaped (times 2)
*/
$this->registerPattern('/(['.$chars.'])(.*?(?<!\\\\)(\\\\\\\\)*+)\\1/s', $callback);
}
/**
* This method will restore all extracted data (strings, regexes) that were
* replaced with placeholder text in extract*(). The original content was
* saved in $this->extracted.
*
* @param string $content
*
* @return string
*/
protected function restoreExtractedData($content)
{
if (!$this->extracted) {
// nothing was extracted, nothing to restore
return $content;
}
$content = strtr($content, $this->extracted);
$this->extracted = array();
return $content;
}
/**
* Check if the path is a regular file and can be read.
*
* @param string $path
*
* @return bool
*/
protected function canImportFile($path)
{
return strlen($path) < PHP_MAXPATHLEN && @is_file($path) && is_readable($path);
}
/**
* Attempts to open file specified by $path for writing.
*
* @param string $path The path to the file.
*
* @return resource Specifier for the target file.
*
* @throws IOException
*/
protected function openFileForWriting($path)
{
if (($handler = @fopen($path, 'w')) === false) {
throw new IOException('The file "'.$path.'" could not be opened for writing. Check if PHP has enough permissions.');
}
return $handler;
}
/**
* Attempts to write $content to the file specified by $handler. $path is used for printing exceptions.
*
* @param resource $handler The resource to write to.
* @param string $content The content to write.
* @param string $path The path to the file (for exception printing only).
*
* @throws IOException
*/
protected function writeToFile($handler, $content, $path = '')
{
if (($result = @fwrite($handler, $content)) === false || ($result < strlen($content))) {
throw new IOException('The file "'.$path.'" could not be written to. Check your disk space and file permissions.');
}
}
}
@@ -0,0 +1,195 @@
<?php
namespace MatthiasMullie\PathConverter;
/**
* Convert paths relative from 1 file to another.
*
* E.g.
* ../../images/icon.jpg relative to /css/imports/icons.css
* becomes
* ../images/icon.jpg relative to /css/minified.css
*
* Please report bugs on https://github.com/matthiasmullie/path-converter/issues
*
* @author Matthias Mullie <[email protected]>
* @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved.
* @license MIT License
*/
class Converter
{
/**
* @var string
*/
protected $from;
/**
* @var string
*/
protected $to;
/**
* @param string $from The original base path (directory, not file!)
* @param string $to The new base path (directory, not file!)
*/
public function __construct($from, $to)
{
$shared = $this->shared($from, $to);
if ($shared === '') {
// when both paths have nothing in common, one of them is probably
// absolute while the other is relative
$cwd = getcwd();
$from = strpos($from, $cwd) === 0 ? $from : $cwd.'/'.$from;
$to = strpos($to, $cwd) === 0 ? $to : $cwd.'/'.$to;
// or traveling the tree via `..`
// attempt to resolve path, or assume it's fine if it doesn't exist
$from = realpath($from) ?: $from;
$to = realpath($to) ?: $to;
}
$from = $this->dirname($from);
$to = $this->dirname($to);
$from = $this->normalize($from);
$to = $this->normalize($to);
$this->from = $from;
$this->to = $to;
}
/**
* Normalize path.
*
* @param string $path
*
* @return string
*/
protected function normalize($path)
{
// deal with different operating systems' directory structure
$path = rtrim(str_replace(DIRECTORY_SEPARATOR, '/', $path), '/');
/*
* Example:
* /home/forkcms/frontend/cache/compiled_templates/../../core/layout/css/../images/img.gif
* to
* /home/forkcms/frontend/core/layout/images/img.gif
*/
do {
$path = preg_replace('/[^\/]+(?<!\.\.)\/\.\.\//', '', $path, -1, $count);
} while ($count);
return $path;
}
/**
* Figure out the shared path of 2 locations.
*
* Example:
* /home/forkcms/frontend/core/layout/images/img.gif
* and
* /home/forkcms/frontend/cache/minified_css
* share
* /home/forkcms/frontend
*
* @param string $path1
* @param string $path2
*
* @return string
*/
protected function shared($path1, $path2)
{
// $path could theoretically be empty (e.g. no path is given), in which
// case it shouldn't expand to array(''), which would compare to one's
// root /
$path1 = $path1 ? explode('/', $path1) : array();
$path2 = $path2 ? explode('/', $path2) : array();
$shared = array();
// compare paths & strip identical ancestors
foreach ($path1 as $i => $chunk) {
if (isset($path2[$i]) && $path1[$i] == $path2[$i]) {
$shared[] = $chunk;
} else {
break;
}
}
return implode('/', $shared);
}
/**
* Convert paths relative from 1 file to another.
*
* E.g.
* ../images/img.gif relative to /home/forkcms/frontend/core/layout/css
* should become:
* ../../core/layout/images/img.gif relative to
* /home/forkcms/frontend/cache/minified_css
*
* @param string $path The relative path that needs to be converted.
*
* @return string The new relative path.
*/
public function convert($path)
{
// quit early if conversion makes no sense
if ($this->from === $this->to) {
return $path;
}
$path = $this->normalize($path);
// if we're not dealing with a relative path, just return absolute
if (strpos($path, '/') === 0) {
return $path;
}
// normalize paths
$path = $this->normalize($this->from.'/'.$path);
// strip shared ancestor paths
$shared = $this->shared($path, $this->to);
$path = mb_substr($path, mb_strlen($shared));
$to = mb_substr($this->to, mb_strlen($shared));
// add .. for every directory that needs to be traversed to new path
$to = str_repeat('../', mb_substr_count($to, '/'));
return $to.ltrim($path, '/');
}
/**
* Attempt to get the directory name from a path.
*
* @param string $path
*
* @return string
*/
public function dirname($path)
{
if (is_file($path)) {
return dirname($path);
}
if (is_dir($path)) {
return rtrim($path, '/');
}
// no known file/dir, start making assumptions
// ends in / = dir
if (mb_substr($path, -1) === '/') {
return rtrim($path, '/');
}
// has a dot in the name, likely a file
if (preg_match('/.*\..*$/', basename($path)) !== 0) {
return dirname($path);
}
// you're on your own here!
return $path;
}
}
+8 -12
View File
@@ -1,14 +1,10 @@
Description of Minify 2.1.7 import into Moodle
Description of MatthiasMullie\Minify import into Moodle
Notes:
* Do not use anything from /lib/minify/ directly, always use core_minify::*() methods.
* In 2.7dev we will import only the minimal number of files required by new core_minify class
and delete deprecated js_minify() and css_minify_css().
1) Download https://github.com/matthiasmullie/minify/archive/1.3.37.zip
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 - added moodle specific settings
* Removed lib/JSMin.php which is not GNU GPL compatible.
mv minify-1.3.37/src /path/to/moodle/lib/minify/matthiasmullie-minify/
mv minify-1.3.37/data /path/to/moodle/lib/lib/minify/matthiasmullie-minify/
2) Download https://github.com/matthiasmullie/path-converter/archive/1.0.8.zip
mv path-converter-1.0.8/src/ /path/to/moodle/lib/minify/matthiasmullie-pathconverter/
-81
View File
@@ -1,81 +0,0 @@
<?php
/**
* Utility functions for generating URIs in HTML files
*
* @warning These functions execute min/groupsConfig.php, sometimes multiple times.
* You must make sure that functions are not redefined, and if your use custom sources,
* you must require_once dirname(__FILE__) . '/lib/Minify/Source.php' so that
* class is available.
*
* @package Minify
*/
if (! class_exists('Minify_Loader', false)) {
require dirname(__FILE__) . '/lib/Minify/Loader.php';
Minify_Loader::register();
}
/*
* 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="<?= Minify_getUri('css'); ?>" />
* <script src="<?= Minify_getUri('js'); ?>"></script>
* <script src="<?= Minify_getUri(array(
* '//scripts/file1.js'
* ,'//scripts/file2.js'
* )); ?>"></script>
* </code>
*
* @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_getUri($keyOrFiles, $opts = array())
{
return Minify_HTML_Helper::getUri($keyOrFiles, $opts);
}
/**
* Get the last modification time of several source js/css files. If you're
* caching the output of Minify_getUri(), you might want to know if one of the
* dependent source files has changed so you can update the HTML.
*
* Since this makes a bunch of stat() calls, you might not want to check this
* on every request.
*
* @param array $keysAndFiles group keys and/or file paths/URIs.
* @return int latest modification time of all given keys/files
*/
function Minify_mtime($keysAndFiles, $groupsConfigFile = null)
{
$gc = null;
if (! $groupsConfigFile) {
$groupsConfigFile = dirname(__FILE__) . '/groupsConfig.php';
}
$sources = array();
foreach ($keysAndFiles as $keyOrFile) {
if (is_object($keyOrFile)
|| 0 === strpos($keyOrFile, '/')
|| 1 === strpos($keyOrFile, ':\\')) {
// a file/source obj
$sources[] = $keyOrFile;
} else {
if (! $gc) {
$gc = (require $groupsConfigFile);
}
foreach ($gc[$keyOrFile] as $source) {
$sources[] = $source;
}
}
}
return Minify_HTML_Helper::getLastModified($sources);
}
+7 -6
View File
@@ -64,12 +64,13 @@ color: #281f18;
$files = array($testfile1, $testfile2);
$this->assertSame("body{background:#fff;margin:0;padding:0;color:#281f18}\nbody{}", core_minify::css_files($files));
$this->assertSame("body{background:#fff;margin:0;padding:0;color:#281f18}\n", core_minify::css_files($files));
$files = array($testfile1, $testfile2, $testfile3);
$this->assertStringStartsWith("body{background:#fff;margin:0;padding:0;color:#281f18}\nbody{}\n\n\n/* Cannot read CSS file ", @core_minify::css_files($files));
$this->assertStringStartsWith("body{background:#fff;margin:0;padding:0;color:#281f18}\n\n\n\n/* Cannot read CSS file ",
@core_minify::css_files($files));
unlink($testfile1);
unlink($testfile2);
@@ -82,11 +83,10 @@ function hm()
}
";
$this->assertSame("function hm(){}", core_minify::js($js));
$this->assertSame("function hm()\n{}", core_minify::js($js));
$js = "function hm{}";
$result = core_minify::js($js);
$this->assertStringStartsWith("\ntry {console.log('Error: Minimisation of JavaScript failed!');} catch (e) {}", $result);
$this->assertContains($js, $result);
}
@@ -110,11 +110,12 @@ function hm()
$files = array($testfile1, $testfile2);
$this->assertSame("function hm(){};\nfunction oh(){}", core_minify::js_files($files));
$this->assertSame("function hm()\n{};\nfunction oh(){}", core_minify::js_files($files));
$files = array($testfile1, $testfile2, $testfile3);
$this->assertStringStartsWith("function hm(){};\nfunction oh(){};\n\n\n// Cannot read JS file ", @core_minify::js_files($files));
$this->assertStringStartsWith("function hm()\n{};\nfunction oh(){};\n\n\n// Cannot read JS file ",
@core_minify::js_files($files));
unlink($testfile1);
unlink($testfile2);
+11 -4
View File
@@ -57,10 +57,17 @@
<licenseversion>2.0+</licenseversion>
</library>
<library>
<location>minify</location>
<name>Minify</name>
<license>BSD</license>
<version>2.2.1</version>
<location>minify/matthiasmullie-minify</location>
<name>MatthiasMullie\Minify</name>
<license>MIT</license>
<version>1.3.37</version>
<licenseversion></licenseversion>
</library>
<library>
<location>minify/matthiasmullie-pathconverter</location>
<name>MatthiasMullie\PathConverter</name>
<license>MIT</license>
<version>1.0.8</version>
<licenseversion></licenseversion>
</library>
<library>
+2
View File
@@ -99,6 +99,8 @@ information provided here is intended especially for developers.
custom fields (and other checks) should be evaluated to determine if the user has been completely setup.
* profile_field_base class has new methods: get_field_config_for_external() and get_field_properties().
This two new methods should be implemented by profile field plugins to make them compatible with Web Services.
* The minifier library used by core_minify has been switched to https://github.com/matthiasmullie/minify - there are minor differences
in minifier output.
=== 3.1 ===
+1 -1
View File
@@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
$version = 2016100700.00; // YYYYMMDD = weekly release date of this DEV branch.
$version = 2016101000.00; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.