1
0
mirror of https://github.com/mrclay/minify.git synced 2025-08-16 19:14:01 +02:00

All PHP/doc files have Unix newlines w/ no BOM

This commit is contained in:
Steve Clay
2008-08-29 22:56:34 +00:00
parent 5a09b4943a
commit 79a4e17fb6
48 changed files with 3999 additions and 3998 deletions

50
HISTORY
View File

@@ -1,26 +1,26 @@
Minify Release History Minify Release History
Version 2.0.2 beta (2008-06-24) Version 2.0.2 beta (2008-06-24)
* Fast new cache system. Cached files served almost 3x as fast. * Fast new cache system. Cached files served almost 3x as fast.
* Dropped support of compress encoding (though HTTP_Encoder still supports it) * Dropped support of compress encoding (though HTTP_Encoder still supports it)
Version 2.0.1 (2008-05-31) Version 2.0.1 (2008-05-31)
* E_STRICT compliance (Cache_Lite_File). * E_STRICT compliance (Cache_Lite_File).
Version 2.0.0 (2008-05-22) Version 2.0.0 (2008-05-22)
* Complete code overhaul. Minify is now a PEAR-style class and toolkit * Complete code overhaul. Minify is now a PEAR-style class and toolkit
for building customized minifying file servers. for building customized minifying file servers.
* Content-Encoding: deflate/gzip/compress, based on request headers * Content-Encoding: deflate/gzip/compress, based on request headers
* Expanded CSS and HTML minifiers with test cases * Expanded CSS and HTML minifiers with test cases
* Easily plug-in 3rd-party minifiers (like Packer) * Easily plug-in 3rd-party minifiers (like Packer)
* Plug-able front end controller allows changing the way files are chosen * Plug-able front end controller allows changing the way files are chosen
* Compression & encoding modules lazy-loaded as needed (304 responses use minimal code) * Compression & encoding modules lazy-loaded as needed (304 responses use minimal code)
* Separate utility classes for HTTP encoding and cache control * Separate utility classes for HTTP encoding and cache control
Version 1.0.1 (2007-05-05) Version 1.0.1 (2007-05-05)
* Fixed various problems resolving pathnames when hosted on an NFS mount. * Fixed various problems resolving pathnames when hosted on an NFS mount.
* Fixed 'undefined constant' notice. * Fixed 'undefined constant' notice.
* Replaced old JSMin library with a much faster custom implementation. * Replaced old JSMin library with a much faster custom implementation.
Version 1.0.0 (2007-05-02) Version 1.0.0 (2007-05-02)
* First release. * First release.

50
LICENSE
View File

@@ -1,25 +1,25 @@
Copyright (c) 2007 Ryan Grove <ryan@wonko.com> Copyright (c) 2007 Ryan Grove <ryan@wonko.com>
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, * Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer. this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, * Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution. and/or other materials provided with the distribution.
* Neither the name of this project nor the names of its contributors may be * 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 used to endorse or promote products derived from this software without
specific prior written permission. specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 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 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 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 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

92
README
View File

@@ -1,46 +1,46 @@
WELCOME TO MINIFY 2.0! WELCOME TO MINIFY 2.0!
Minify is an HTTP content server. It compresses sources of content Minify is an HTTP content server. It compresses sources of content
(usually files), combines the result and serves it with appropriate (usually files), combines the result and serves it with appropriate
HTTP headers. These headers can allow clients to perform conditional HTTP headers. These headers can allow clients to perform conditional
GETs (serving content only when clients do not have a valid cache) GETs (serving content only when clients do not have a valid cache)
or tell clients to cache the file until a given date/time. or tell clients to cache the file until a given date/time.
More info: http://code.google.com/p/minify/ More info: http://code.google.com/p/minify/
INSTALLATION AND USAGE: http://code.google.com/p/minify/wiki/UserGuide INSTALLATION AND USAGE: http://code.google.com/p/minify/wiki/UserGuide
UNIT TESTING & EXAMPLES: http://code.google.com/p/minify/wiki/TestingMinify UNIT TESTING & EXAMPLES: http://code.google.com/p/minify/wiki/TestingMinify
MINIFY OVERVIEW MINIFY OVERVIEW
Minify works with Minify_Source objects. These usually represent a file Minify works with Minify_Source objects. These usually represent a file
on disk, but a source can also be a string in memory or from a database. on disk, but a source can also be a string in memory or from a database.
Sources with known "last modified" timestamps allow Minify to implement Sources with known "last modified" timestamps allow Minify to implement
server-side caching and conditional GETs. server-side caching and conditional GETs.
You configure Minify via a Minify_Controller object. The controller You configure Minify via a Minify_Controller object. The controller
supplies the sources and default options to serve a request, supplies the sources and default options to serve a request,
determining how it's to be responded to. Several controllers are determining how it's to be responded to. Several controllers are
supplied with Minify, but it's also fairly easy to write your own. See supplied with Minify, but it's also fairly easy to write your own. See
the files in /lib/Minify/Controller for examples. the files in /lib/Minify/Controller for examples.
To use an existing controller, you call Minify::serve(), passing it: To use an existing controller, you call Minify::serve(), passing it:
1. the name of your controller of choice (without the "Minify_Controller" 1. the name of your controller of choice (without the "Minify_Controller"
prefix) or a custom controller object subclassed from Minify_Controller_Base. prefix) or a custom controller object subclassed from Minify_Controller_Base.
2. a combined array of controller and Minify options. Eg.: 2. a combined array of controller and Minify options. Eg.:
// serve a minified javascript file and tell clients to cache it for a // serve a minified javascript file and tell clients to cache it for a
// year // year
Minify::serve('Files', array( Minify::serve('Files', array(
'files' => array('/path/to/file1.js', '/path/to/file2.js') 'files' => array('/path/to/file1.js', '/path/to/file2.js')
,'setExpires' => (time() + 86400 * 365) ,'setExpires' => (time() + 86400 * 365)
)); ));
The above creates an instance of Minify_Controller_Files, which creates The above creates an instance of Minify_Controller_Files, which creates
source objects from the 'files' option, and supplies other default options. source objects from the 'files' option, and supplies other default options.
FILE ENCODINGS FILE ENCODINGS
Minify *should* work fine with files encoded in UTF-8 or other 8-bit Minify *should* work fine with files encoded in UTF-8 or other 8-bit
encodings like ISO 8859/Windows-1252. Leading UTF-8 BOMs are stripped from encodings like ISO 8859/Windows-1252. Leading UTF-8 BOMs are stripped from
all sources to prevent duplication in output files. all sources to prevent duplication in output files.

View File

@@ -1,16 +1,16 @@
<?php <?php
/** /**
* Configuration for default Minify implementation * Configuration for default Minify implementation
* @package Minify * @package Minify
*/ */
/** /**
* For best performance, specify your temp directory here. * For best performance, specify your temp directory here.
* Otherwise Minify will have to load extra code to guess. * Otherwise Minify will have to load extra code to guess.
*/ */
//$min_cachePath = 'c:\\WINDOWS\Temp'; //$min_cachePath = 'c:\\WINDOWS\Temp';
//$min_cachePath = '/tmp'; //$min_cachePath = '/tmp';
/** /**
@@ -20,29 +20,29 @@
* those changes are seen immediately. * those changes are seen immediately.
*/ */
$min_serveOptions['maxAge'] = 1800; $min_serveOptions['maxAge'] = 1800;
/** /**
* If you'd like to restrict the "f" option to files within/below * If you'd like to restrict the "f" option to files within/below
* particular directories below DOCUMENT_ROOT, set this here. * particular directories below DOCUMENT_ROOT, set this here.
* You will still need to include the directory in the * You will still need to include the directory in the
* f or b GET parameters. * f or b GET parameters.
* *
* // = DOCUMENT_ROOT * // = DOCUMENT_ROOT
*/ */
//$min_allowDirs = array('//js', '//css'); //$min_allowDirs = array('//js', '//css');
/** /**
* Manually set the path to Minify's lib folder * Manually set the path to Minify's lib folder
*/ */
//$min_libPath = 'lib'; //$min_libPath = 'lib';
/** /**
* Set to true to disable the "f" GET parameter for specifying files. * Set to true to disable the "f" GET parameter for specifying files.
* Only the "g" parameter will be considered. * Only the "g" parameter will be considered.
*/ */
$min_groupsOnly = false; $min_groupsOnly = false;
@@ -50,7 +50,7 @@ $min_groupsOnly = false;
* Uncomment to enable debug mode. Files will be combined with no * Uncomment to enable debug mode. Files will be combined with no
* minification, and comments will be added to indicate the line #s * minification, and comments will be added to indicate the line #s
* of the original files. * of the original files.
*/ */
//$min_serveOptions['debug'] = true; //$min_serveOptions['debug'] = true;

View File

@@ -1,11 +1,11 @@
<?php <?php
/** /**
* Groups configuration for default Minify implementation * Groups configuration for default Minify implementation
* @package Minify * @package Minify
*/ */
return array( return array(
// 'js' => array('//js/file1.js', '//js/file2.js'), // 'js' => array('//js/file1.js', '//js/file2.js'),
// 'css' => array('//css/file1.css', '//css/file2.css'), // 'css' => array('//css/file1.css', '//css/file2.css'),
'js' => array('//js/Class.js', '//js/email.js') 'js' => array('//js/Class.js', '//js/email.js')
); );

View File

@@ -1,55 +1,55 @@
<?php <?php
/** /**
* Front controller for default Minify implementation * Front controller for default Minify implementation
* *
* DO NOT EDIT! Configure this utility via config.php and groupsConfig.php * DO NOT EDIT! Configure this utility via config.php and groupsConfig.php
* *
* @package Minify * @package Minify
*/ */
define('MINIFY_MIN_DIR', dirname(__FILE__)); define('MINIFY_MIN_DIR', dirname(__FILE__));
// load config // load config
require MINIFY_MIN_DIR . '/config.php'; require MINIFY_MIN_DIR . '/config.php';
// setup include path // setup include path
if (!isset($min_libPath)) { if (!isset($min_libPath)) {
// default lib path is inside this directory // default lib path is inside this directory
$min_libPath = MINIFY_MIN_DIR . '/lib'; $min_libPath = MINIFY_MIN_DIR . '/lib';
} }
set_include_path($min_libPath . PATH_SEPARATOR . get_include_path()); set_include_path($min_libPath . PATH_SEPARATOR . get_include_path());
// friendly error if lib wasn't in the include_path // friendly error if lib wasn't in the include_path
if (! (include 'Minify.php')) { if (! (include 'Minify.php')) {
trigger_error( trigger_error(
'Minify: You must add Minify/lib to the include_path or set $min_libPath in config.php' 'Minify: You must add Minify/lib to the include_path or set $min_libPath in config.php'
,E_USER_ERROR ,E_USER_ERROR
); );
} }
Minify::$uploaderHoursBehind = $min_uploaderHoursBehind; Minify::$uploaderHoursBehind = $min_uploaderHoursBehind;
if (isset($_GET['g'])) { if (isset($_GET['g'])) {
Minify::setCache(isset($min_cachePath) ? $min_cachePath : null); Minify::setCache(isset($min_cachePath) ? $min_cachePath : null);
// Groups expects the group key as PATH_INFO // Groups expects the group key as PATH_INFO
// we want to allow ?g=groupKey // we want to allow ?g=groupKey
$_SERVER['PATH_INFO'] = '/' . $_GET['g']; $_SERVER['PATH_INFO'] = '/' . $_GET['g'];
$min_serveOptions['groups'] = (require MINIFY_MIN_DIR . '/groupsConfig.php'); $min_serveOptions['groups'] = (require MINIFY_MIN_DIR . '/groupsConfig.php');
if (preg_match('/&\\d/', $_SERVER['QUERY_STRING'])) { if (preg_match('/&\\d/', $_SERVER['QUERY_STRING'])) {
$min_serveOptions['maxAge'] = 31536000; $min_serveOptions['maxAge'] = 31536000;
} }
Minify::serve('Groups', $min_serveOptions); Minify::serve('Groups', $min_serveOptions);
} elseif (!$min_groupsOnly && isset($_GET['f'])) { } elseif (!$min_groupsOnly && isset($_GET['f'])) {
/** /**
* crude initial implementation hacked onto on Version1 controller * crude initial implementation hacked onto on Version1 controller
* @todo encapsulate this in a new controller * @todo encapsulate this in a new controller
*/ */
if (isset($min_cachePath)) { if (isset($min_cachePath)) {
define('MINIFY_CACHE_DIR', $min_cachePath); define('MINIFY_CACHE_DIR', $min_cachePath);
} }
if (isset($min_allowDirs)) { if (isset($min_allowDirs)) {
foreach ((array)$min_allowDirs as $_allowDir) { foreach ((array)$min_allowDirs as $_allowDir) {
@@ -57,16 +57,16 @@ if (isset($_GET['g'])) {
$_SERVER['DOCUMENT_ROOT'] . substr($_allowDir, 1) $_SERVER['DOCUMENT_ROOT'] . substr($_allowDir, 1)
); );
} }
} }
// Version1 already does validation. All we want is to prepend "b" // Version1 already does validation. All we want is to prepend "b"
// to each file if it looks right. // to each file if it looks right.
$min_base = (isset($_GET['b']) && preg_match('@^[^/.]+(?:/[^/.]+)*$@', $_GET['b'])) $min_base = (isset($_GET['b']) && preg_match('@^[^/.]+(?:/[^/.]+)*$@', $_GET['b']))
? '/' . $_GET['b'] . '/' ? '/' . $_GET['b'] . '/'
: '/'; : '/';
// Version1 expects ?files=/js/file1.js,/js/file2.js,/js/file3.js // Version1 expects ?files=/js/file1.js,/js/file2.js,/js/file3.js
// we want to allow ?f=js/file1.js,js/file2.js,js/file3.js // we want to allow ?f=js/file1.js,js/file2.js,js/file3.js
// or ?b=js&f=file1.js,file2.js,file3.js // or ?b=js&f=file1.js,file2.js,file3.js
$_GET['files'] = $min_base . str_replace(',', ',' . $min_base, $_GET['f']); $_GET['files'] = $min_base . str_replace(',', ',' . $min_base, $_GET['f']);
Minify::serve('Version1', $min_serveOptions); Minify::serve('Version1', $min_serveOptions);
} }

View File

@@ -1,262 +1,262 @@
<?php <?php
/** /**
* Class HTTP_ConditionalGet * Class HTTP_ConditionalGet
* @package Minify * @package Minify
* @subpackage HTTP * @subpackage HTTP
*/ */
/** /**
* Implement conditional GET via a timestamp or hash of content * Implement conditional GET via a timestamp or hash of content
* *
* E.g. Content from DB with update time: * E.g. Content from DB with update time:
* <code> * <code>
* list($updateTime, $content) = getDbUpdateAndContent(); * list($updateTime, $content) = getDbUpdateAndContent();
* $cg = new HTTP_ConditionalGet(array( * $cg = new HTTP_ConditionalGet(array(
* 'lastModifiedTime' => $updateTime * 'lastModifiedTime' => $updateTime
* )); * ));
* $cg->sendHeaders(); * $cg->sendHeaders();
* if ($cg->cacheIsValid) { * if ($cg->cacheIsValid) {
* exit(); * exit();
* } * }
* echo $content; * echo $content;
* </code> * </code>
* *
* E.g. Content from DB with no update time: * E.g. Content from DB with no update time:
* <code> * <code>
* $content = getContentFromDB(); * $content = getContentFromDB();
* $cg = new HTTP_ConditionalGet(array( * $cg = new HTTP_ConditionalGet(array(
* 'contentHash' => md5($content) * 'contentHash' => md5($content)
* )); * ));
* $cg->sendHeaders(); * $cg->sendHeaders();
* if ($cg->cacheIsValid) { * if ($cg->cacheIsValid) {
* exit(); * exit();
* } * }
* echo $content; * echo $content;
* </code> * </code>
* *
* E.g. Static content with some static includes: * E.g. Static content with some static includes:
* <code> * <code>
* // before content * // before content
* $cg = new HTTP_ConditionalGet(array( * $cg = new HTTP_ConditionalGet(array(
* 'lastUpdateTime' => max( * 'lastUpdateTime' => max(
* filemtime(__FILE__) * filemtime(__FILE__)
* ,filemtime('/path/to/header.inc') * ,filemtime('/path/to/header.inc')
* ,filemtime('/path/to/footer.inc') * ,filemtime('/path/to/footer.inc')
* ) * )
* )); * ));
* $cg->sendHeaders(); * $cg->sendHeaders();
* if ($cg->cacheIsValid) { * if ($cg->cacheIsValid) {
* exit(); * exit();
* } * }
* </code> * </code>
* @package Minify * @package Minify
* @subpackage HTTP * @subpackage HTTP
* @author Stephen Clay <steve@mrclay.org> * @author Stephen Clay <steve@mrclay.org>
*/ */
class HTTP_ConditionalGet { class HTTP_ConditionalGet {
/** /**
* Does the client have a valid copy of the requested resource? * Does the client have a valid copy of the requested resource?
* *
* You'll want to check this after instantiating the object. If true, do * You'll want to check this after instantiating the object. If true, do
* not send content, just call sendHeaders() if you haven't already. * not send content, just call sendHeaders() if you haven't already.
* *
* @var bool * @var bool
*/ */
public $cacheIsValid = null; public $cacheIsValid = null;
/** /**
* @param array $spec options * @param array $spec options
* *
* 'isPublic': (bool) if true, the Cache-Control header will contain * 'isPublic': (bool) if true, the Cache-Control header will contain
* "public", allowing proxy caches to cache the content. Otherwise * "public", allowing proxy caches to cache the content. Otherwise
* "private" will be sent, allowing only browsers to cache. (default false) * "private" will be sent, allowing only browsers to cache. (default false)
* *
* 'lastModifiedTime': (int) if given, both ETag AND Last-Modified headers * 'lastModifiedTime': (int) if given, both ETag AND Last-Modified headers
* will be sent with content. This is recommended. * will be sent with content. This is recommended.
* *
* 'contentHash': (string) if given, only the ETag header can be sent with * 'contentHash': (string) if given, only the ETag header can be sent with
* content (only HTTP1.1 clients can conditionally GET). The given string * content (only HTTP1.1 clients can conditionally GET). The given string
* should be short with no quote characters and always change when the * should be short with no quote characters and always change when the
* resource changes (recommend md5()). This is not needed/used if * resource changes (recommend md5()). This is not needed/used if
* lastModifiedTime is given. * lastModifiedTime is given.
* *
* 'invalidate': (bool) if true, the client cache will be considered invalid * 'invalidate': (bool) if true, the client cache will be considered invalid
* without testing. Effectively this disables conditional GET. * without testing. Effectively this disables conditional GET.
* (default false) * (default false)
* *
* 'maxAge': (int) if given, this will set the Cache-Control max-age in * '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. * 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 * After the max-age period has passed, the browser will again send a
* conditional GET to revalidate its cache. * conditional GET to revalidate its cache.
* *
* @return null * @return null
*/ */
public function __construct($spec) { public function __construct($spec) {
$scope = (isset($spec['isPublic']) && $spec['isPublic']) $scope = (isset($spec['isPublic']) && $spec['isPublic'])
? 'public' ? 'public'
: 'private'; : 'private';
$maxAge = 0; $maxAge = 0;
// backwards compatibility (can be removed later) // backwards compatibility (can be removed later)
if (isset($spec['setExpires']) if (isset($spec['setExpires'])
&& is_numeric($spec['setExpires']) && is_numeric($spec['setExpires'])
&& ! isset($spec['maxAge'])) { && ! isset($spec['maxAge'])) {
$spec['maxAge'] = $spec['setExpires'] - $_SERVER['REQUEST_TIME']; $spec['maxAge'] = $spec['setExpires'] - $_SERVER['REQUEST_TIME'];
} }
if (isset($spec['maxAge'])) { if (isset($spec['maxAge'])) {
$maxAge = $spec['maxAge']; $maxAge = $spec['maxAge'];
$this->_headers['Expires'] = self::gmtDate( $this->_headers['Expires'] = self::gmtDate(
$_SERVER['REQUEST_TIME'] + $spec['maxAge'] $_SERVER['REQUEST_TIME'] + $spec['maxAge']
); );
} }
if (isset($spec['lastModifiedTime'])) { if (isset($spec['lastModifiedTime'])) {
// base both headers on time // base both headers on time
$this->_setLastModified($spec['lastModifiedTime']); $this->_setLastModified($spec['lastModifiedTime']);
$this->_setEtag($spec['lastModifiedTime'], $scope); $this->_setEtag($spec['lastModifiedTime'], $scope);
} else { } else {
// hope to use ETag // hope to use ETag
if (isset($spec['contentHash'])) { if (isset($spec['contentHash'])) {
$this->_setEtag($spec['contentHash'], $scope); $this->_setEtag($spec['contentHash'], $scope);
} }
} }
$this->_headers['Cache-Control'] = "max-age={$maxAge}, {$scope}, must-revalidate"; $this->_headers['Cache-Control'] = "max-age={$maxAge}, {$scope}, must-revalidate";
// invalidate cache if disabled, otherwise check // invalidate cache if disabled, otherwise check
$this->cacheIsValid = (isset($spec['invalidate']) && $spec['invalidate']) $this->cacheIsValid = (isset($spec['invalidate']) && $spec['invalidate'])
? false ? false
: $this->_isCacheValid(); : $this->_isCacheValid();
} }
/** /**
* Get array of output headers to be sent * Get array of output headers to be sent
* *
* In the case of 304 responses, this array will only contain the response * In the case of 304 responses, this array will only contain the response
* code header: array('_responseCode' => 'HTTP/1.0 304 Not Modified') * code header: array('_responseCode' => 'HTTP/1.0 304 Not Modified')
* *
* Otherwise something like: * Otherwise something like:
* <code> * <code>
* array( * array(
* 'Cache-Control' => 'max-age=0, public, must-revalidate' * 'Cache-Control' => 'max-age=0, public, must-revalidate'
* ,'ETag' => '"foobar"' * ,'ETag' => '"foobar"'
* ) * )
* </code> * </code>
* *
* @return array * @return array
*/ */
public function getHeaders() { public function getHeaders() {
return $this->_headers; return $this->_headers;
} }
/** /**
* Set the Content-Length header in bytes * Set the Content-Length header in bytes
* *
* With most PHP configs, as long as you don't flush() output, this method * 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 * 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. * you. Otherwise you'll want to call this to let the client know up front.
* *
* @param int $bytes * @param int $bytes
* *
* @return int copy of input $bytes * @return int copy of input $bytes
*/ */
public function setContentLength($bytes) { public function setContentLength($bytes) {
return $this->_headers['Content-Length'] = $bytes; return $this->_headers['Content-Length'] = $bytes;
} }
/** /**
* Send headers * Send headers
* *
* @see getHeaders() * @see getHeaders()
* *
* Note this doesn't "clear" the headers. Calling sendHeaders() will * Note this doesn't "clear" the headers. Calling sendHeaders() will
* call header() again (but probably have not effect) and getHeaders() will * call header() again (but probably have not effect) and getHeaders() will
* still return the headers. * still return the headers.
* *
* @return null * @return null
*/ */
public function sendHeaders() { public function sendHeaders() {
$headers = $this->_headers; $headers = $this->_headers;
if (array_key_exists('_responseCode', $headers)) { if (array_key_exists('_responseCode', $headers)) {
header($headers['_responseCode']); header($headers['_responseCode']);
unset($headers['_responseCode']); unset($headers['_responseCode']);
} }
foreach ($headers as $name => $val) { foreach ($headers as $name => $val) {
header($name . ': ' . $val); header($name . ': ' . $val);
} }
} }
/** /**
* Get a GMT formatted date for use in HTTP headers * Get a GMT formatted date for use in HTTP headers
* *
* <code> * <code>
* header('Expires: ' . HTTP_ConditionalGet::gmtdate($time)); * header('Expires: ' . HTTP_ConditionalGet::gmtdate($time));
* </code> * </code>
* *
* @param int $time unix timestamp * @param int $time unix timestamp
* *
* @return string * @return string
*/ */
public static function gmtDate($time) { public static function gmtDate($time) {
return gmdate('D, d M Y H:i:s \G\M\T', $time); return gmdate('D, d M Y H:i:s \G\M\T', $time);
} }
protected $_headers = array(); protected $_headers = array();
protected $_lmTime = null; protected $_lmTime = null;
protected $_etag = null; protected $_etag = null;
protected function _setEtag($hash, $scope) { protected function _setEtag($hash, $scope) {
$this->_etag = '"' . $hash $this->_etag = '"' . $hash
. substr($scope, 0, 3) . substr($scope, 0, 3)
. '"'; . '"';
$this->_headers['ETag'] = $this->_etag; $this->_headers['ETag'] = $this->_etag;
} }
protected function _setLastModified($time) { protected function _setLastModified($time) {
$this->_lmTime = (int)$time; $this->_lmTime = (int)$time;
$this->_headers['Last-Modified'] = self::gmtDate($time); $this->_headers['Last-Modified'] = self::gmtDate($time);
} }
/** /**
* Determine validity of client cache and queue 304 header if valid * Determine validity of client cache and queue 304 header if valid
*/ */
protected function _isCacheValid() protected function _isCacheValid()
{ {
if (null === $this->_etag) { if (null === $this->_etag) {
// lmTime is copied to ETag, so this condition implies that the // lmTime is copied to ETag, so this condition implies that the
// client received neither ETag nor Last-Modified, so can't // client received neither ETag nor Last-Modified, so can't
// possibly has a valid cache. // possibly has a valid cache.
return false; return false;
} }
$isValid = ($this->resourceMatchedEtag() || $this->resourceNotModified()); $isValid = ($this->resourceMatchedEtag() || $this->resourceNotModified());
if ($isValid) { if ($isValid) {
$this->_headers['_responseCode'] = 'HTTP/1.0 304 Not Modified'; $this->_headers['_responseCode'] = 'HTTP/1.0 304 Not Modified';
} }
return $isValid; return $isValid;
} }
protected function resourceMatchedEtag() { protected function resourceMatchedEtag() {
if (!isset($_SERVER['HTTP_IF_NONE_MATCH'])) { if (!isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
return false; return false;
} }
$cachedEtagList = get_magic_quotes_gpc() $cachedEtagList = get_magic_quotes_gpc()
? stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH'])
: $_SERVER['HTTP_IF_NONE_MATCH']; : $_SERVER['HTTP_IF_NONE_MATCH'];
$cachedEtags = split(',', $cachedEtagList); $cachedEtags = split(',', $cachedEtagList);
foreach ($cachedEtags as $cachedEtag) { foreach ($cachedEtags as $cachedEtag) {
if (trim($cachedEtag) == $this->_etag) { if (trim($cachedEtag) == $this->_etag) {
return true; return true;
} }
} }
return false; return false;
} }
protected function resourceNotModified() { protected function resourceNotModified() {
if (!isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { if (!isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
return false; return false;
} }
$ifModifiedSince = $_SERVER['HTTP_IF_MODIFIED_SINCE']; $ifModifiedSince = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
if (false !== ($semicolon = strrpos($ifModifiedSince, ';'))) { if (false !== ($semicolon = strrpos($ifModifiedSince, ';'))) {
// IE has tacked on extra data to this header, strip it // IE has tacked on extra data to this header, strip it
$ifModifiedSince = substr($ifModifiedSince, 0, $semicolon); $ifModifiedSince = substr($ifModifiedSince, 0, $semicolon);
} }
return ($ifModifiedSince == self::gmtDate($this->_lmTime)); return ($ifModifiedSince == self::gmtDate($this->_lmTime));
} }
} }

View File

@@ -1,291 +1,291 @@
<?php <?php
/** /**
* jsmin.php - PHP implementation of Douglas Crockford's JSMin. * jsmin.php - PHP implementation of Douglas Crockford's JSMin.
* *
* This is pretty much a direct port of jsmin.c to PHP with just a few * This is pretty much a direct port of jsmin.c to PHP with just a few
* PHP-specific performance tweaks. Also, whereas jsmin.c reads from stdin and * PHP-specific performance tweaks. Also, whereas jsmin.c reads from stdin and
* outputs to stdout, this library accepts a string as input and returns another * outputs to stdout, this library accepts a string as input and returns another
* string as output. * string as output.
* *
* PHP 5 or higher is required. * PHP 5 or higher is required.
* *
* Permission is hereby granted to use this version of the library under the * Permission is hereby granted to use this version of the library under the
* same terms as jsmin.c, which has the following license: * same terms as jsmin.c, which has the following license:
* *
* -- * --
* Copyright (c) 2002 Douglas Crockford (www.crockford.com) * Copyright (c) 2002 Douglas Crockford (www.crockford.com)
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of * Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in * this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to * the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do * of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions: * so, subject to the following conditions:
* *
* The above copyright notice and this permission notice shall be included in all * The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software. * copies or substantial portions of the Software.
* *
* The Software shall be used for Good, not Evil. * The Software shall be used for Good, not Evil.
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. * SOFTWARE.
* -- * --
* *
* @package JSMin * @package JSMin
* @author Ryan Grove <ryan@wonko.com> * @author Ryan Grove <ryan@wonko.com>
* @copyright 2002 Douglas Crockford <douglas@crockford.com> (jsmin.c) * @copyright 2002 Douglas Crockford <douglas@crockford.com> (jsmin.c)
* @copyright 2008 Ryan Grove <ryan@wonko.com> (PHP port) * @copyright 2008 Ryan Grove <ryan@wonko.com> (PHP port)
* @license http://opensource.org/licenses/mit-license.php MIT License * @license http://opensource.org/licenses/mit-license.php MIT License
* @version 1.1.1 (2008-03-02) * @version 1.1.1 (2008-03-02)
* @link http://code.google.com/p/jsmin-php/ * @link http://code.google.com/p/jsmin-php/
*/ */
class JSMin { class JSMin {
const ORD_LF = 10; const ORD_LF = 10;
const ORD_SPACE = 32; const ORD_SPACE = 32;
protected $a = ''; protected $a = '';
protected $b = ''; protected $b = '';
protected $input = ''; protected $input = '';
protected $inputIndex = 0; protected $inputIndex = 0;
protected $inputLength = 0; protected $inputLength = 0;
protected $lookAhead = null; protected $lookAhead = null;
protected $output = ''; protected $output = '';
// -- Public Static Methods -------------------------------------------------- // -- Public Static Methods --------------------------------------------------
public static function minify($js) { public static function minify($js) {
$jsmin = new JSMin($js); $jsmin = new JSMin($js);
return $jsmin->min(); return $jsmin->min();
} }
// -- Public Instance Methods ------------------------------------------------ // -- Public Instance Methods ------------------------------------------------
public function __construct($input) { public function __construct($input) {
$this->input = str_replace("\r\n", "\n", $input); $this->input = str_replace("\r\n", "\n", $input);
$this->inputLength = strlen($this->input); $this->inputLength = strlen($this->input);
} }
// -- Protected Instance Methods --------------------------------------------- // -- Protected Instance Methods ---------------------------------------------
protected function action($d) { protected function action($d) {
switch($d) { switch($d) {
case 1: case 1:
$this->output .= $this->a; $this->output .= $this->a;
case 2: case 2:
$this->a = $this->b; $this->a = $this->b;
if ($this->a === "'" || $this->a === '"') { if ($this->a === "'" || $this->a === '"') {
for (;;) { for (;;) {
$this->output .= $this->a; $this->output .= $this->a;
$this->a = $this->get(); $this->a = $this->get();
if ($this->a === $this->b) { if ($this->a === $this->b) {
break; break;
} }
if (ord($this->a) <= self::ORD_LF) { if (ord($this->a) <= self::ORD_LF) {
throw new JSMinException('Unterminated string literal.'); throw new JSMinException('Unterminated string literal.');
} }
if ($this->a === '\\') { if ($this->a === '\\') {
$this->output .= $this->a; $this->output .= $this->a;
$this->a = $this->get(); $this->a = $this->get();
} }
} }
} }
case 3: case 3:
$this->b = $this->next(); $this->b = $this->next();
if ($this->b === '/' && ( if ($this->b === '/' && (
$this->a === '(' || $this->a === ',' || $this->a === '=' || $this->a === '(' || $this->a === ',' || $this->a === '=' ||
$this->a === ':' || $this->a === '[' || $this->a === '!' || $this->a === ':' || $this->a === '[' || $this->a === '!' ||
$this->a === '&' || $this->a === '|' || $this->a === '?')) { $this->a === '&' || $this->a === '|' || $this->a === '?')) {
$this->output .= $this->a . $this->b; $this->output .= $this->a . $this->b;
for (;;) { for (;;) {
$this->a = $this->get(); $this->a = $this->get();
if ($this->a === '/') { if ($this->a === '/') {
break; break;
} elseif ($this->a === '\\') { } elseif ($this->a === '\\') {
$this->output .= $this->a; $this->output .= $this->a;
$this->a = $this->get(); $this->a = $this->get();
} elseif (ord($this->a) <= self::ORD_LF) { } elseif (ord($this->a) <= self::ORD_LF) {
throw new JSMinException('Unterminated regular expression '. throw new JSMinException('Unterminated regular expression '.
'literal.'); 'literal.');
} }
$this->output .= $this->a; $this->output .= $this->a;
} }
$this->b = $this->next(); $this->b = $this->next();
} }
} }
} }
protected function get() { protected function get() {
$c = $this->lookAhead; $c = $this->lookAhead;
$this->lookAhead = null; $this->lookAhead = null;
if ($c === null) { if ($c === null) {
if ($this->inputIndex < $this->inputLength) { if ($this->inputIndex < $this->inputLength) {
$c = $this->input[$this->inputIndex]; $c = $this->input[$this->inputIndex];
$this->inputIndex += 1; $this->inputIndex += 1;
} else { } else {
$c = null; $c = null;
} }
} }
if ($c === "\r") { if ($c === "\r") {
return "\n"; return "\n";
} }
if ($c === null || $c === "\n" || ord($c) >= self::ORD_SPACE) { if ($c === null || $c === "\n" || ord($c) >= self::ORD_SPACE) {
return $c; return $c;
} }
return ' '; return ' ';
} }
protected function isAlphaNum($c) { protected function isAlphaNum($c) {
return ord($c) > 126 || $c === '\\' || preg_match('/^[\w\$]$/', $c) === 1; return ord($c) > 126 || $c === '\\' || preg_match('/^[\w\$]$/', $c) === 1;
} }
protected function min() { protected function min() {
$this->a = "\n"; $this->a = "\n";
$this->action(3); $this->action(3);
while ($this->a !== null) { while ($this->a !== null) {
switch ($this->a) { switch ($this->a) {
case ' ': case ' ':
if ($this->isAlphaNum($this->b)) { if ($this->isAlphaNum($this->b)) {
$this->action(1); $this->action(1);
} else { } else {
$this->action(2); $this->action(2);
} }
break; break;
case "\n": case "\n":
switch ($this->b) { switch ($this->b) {
case '{': case '{':
case '[': case '[':
case '(': case '(':
case '+': case '+':
case '-': case '-':
$this->action(1); $this->action(1);
break; break;
case ' ': case ' ':
$this->action(3); $this->action(3);
break; break;
default: default:
if ($this->isAlphaNum($this->b)) { if ($this->isAlphaNum($this->b)) {
$this->action(1); $this->action(1);
} }
else { else {
$this->action(2); $this->action(2);
} }
} }
break; break;
default: default:
switch ($this->b) { switch ($this->b) {
case ' ': case ' ':
if ($this->isAlphaNum($this->a)) { if ($this->isAlphaNum($this->a)) {
$this->action(1); $this->action(1);
break; break;
} }
$this->action(3); $this->action(3);
break; break;
case "\n": case "\n":
switch ($this->a) { switch ($this->a) {
case '}': case '}':
case ']': case ']':
case ')': case ')':
case '+': case '+':
case '-': case '-':
case '"': case '"':
case "'": case "'":
$this->action(1); $this->action(1);
break; break;
default: default:
if ($this->isAlphaNum($this->a)) { if ($this->isAlphaNum($this->a)) {
$this->action(1); $this->action(1);
} }
else { else {
$this->action(3); $this->action(3);
} }
} }
break; break;
default: default:
$this->action(1); $this->action(1);
break; break;
} }
} }
} }
return $this->output; return $this->output;
} }
protected function next() { protected function next() {
$c = $this->get(); $c = $this->get();
if ($c === '/') { if ($c === '/') {
switch($this->peek()) { switch($this->peek()) {
case '/': case '/':
for (;;) { for (;;) {
$c = $this->get(); $c = $this->get();
if (ord($c) <= self::ORD_LF) { if (ord($c) <= self::ORD_LF) {
return $c; return $c;
} }
} }
case '*': case '*':
$this->get(); $this->get();
for (;;) { for (;;) {
switch($this->get()) { switch($this->get()) {
case '*': case '*':
if ($this->peek() === '/') { if ($this->peek() === '/') {
$this->get(); $this->get();
return ' '; return ' ';
} }
break; break;
case null: case null:
throw new JSMinException('Unterminated comment.'); throw new JSMinException('Unterminated comment.');
} }
} }
default: default:
return $c; return $c;
} }
} }
return $c; return $c;
} }
protected function peek() { protected function peek() {
$this->lookAhead = $this->get(); $this->lookAhead = $this->get();
return $this->lookAhead; return $this->lookAhead;
} }
} }
// -- Exceptions --------------------------------------------------------------- // -- Exceptions ---------------------------------------------------------------
class JSMinException extends Exception {} class JSMinException extends Exception {}
?> ?>

View File

@@ -1,243 +1,243 @@
<?php <?php
/** /**
* Class Minify * Class Minify
* @package Minify * @package Minify
*/ */
/** /**
* Minify_Source * Minify_Source
*/ */
require_once 'Minify/Source.php'; require_once 'Minify/Source.php';
/** /**
* Minify - Combines, minifies, and caches JavaScript and CSS files on demand. * Minify - Combines, minifies, and caches JavaScript and CSS files on demand.
* *
* See README for usage instructions (for now). * See README for usage instructions (for now).
* *
* This library was inspired by {@link mailto:flashkot@mail.ru jscsscomp by Maxim Martynyuk} * This library was inspired by {@link mailto:flashkot@mail.ru jscsscomp by Maxim Martynyuk}
* and by the article {@link http://www.hunlock.com/blogs/Supercharged_Javascript "Supercharged JavaScript" by Patrick Hunlock}. * and by the article {@link http://www.hunlock.com/blogs/Supercharged_Javascript "Supercharged JavaScript" by Patrick Hunlock}.
* *
* Requires PHP 5.1.0. * Requires PHP 5.1.0.
* Tested on PHP 5.1.6. * Tested on PHP 5.1.6.
* *
* @package Minify * @package Minify
* @author Ryan Grove <ryan@wonko.com> * @author Ryan Grove <ryan@wonko.com>
* @author Stephen Clay <steve@mrclay.org> * @author Stephen Clay <steve@mrclay.org>
* @copyright 2008 Ryan Grove, Stephen Clay. All rights reserved. * @copyright 2008 Ryan Grove, Stephen Clay. All rights reserved.
* @license http://opensource.org/licenses/bsd-license.php New BSD License * @license http://opensource.org/licenses/bsd-license.php New BSD License
* @link http://code.google.com/p/minify/ * @link http://code.google.com/p/minify/
*/ */
class Minify { class Minify {
const TYPE_CSS = 'text/css'; const TYPE_CSS = 'text/css';
const TYPE_HTML = 'text/html'; const TYPE_HTML = 'text/html';
// there is some debate over the ideal JS Content-Type, but this is the // there is some debate over the ideal JS Content-Type, but this is the
// Apache default and what Yahoo! uses.. // Apache default and what Yahoo! uses..
const TYPE_JS = 'application/x-javascript'; const TYPE_JS = 'application/x-javascript';
/** /**
* How many hours behind are the file modification times of uploaded files? * 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 * 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 * 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 * 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 * jumps ahead by a number of hours, set this variable to that number. If the mtime
* moves back, this should not be needed. * moves back, this should not be needed.
* *
* @var int $uploaderHoursBehind * @var int $uploaderHoursBehind
*/ */
public static $uploaderHoursBehind = 0; public static $uploaderHoursBehind = 0;
/** /**
* @see setCache() * @see setCache()
* @param mixed $cache object with identical interface as Minify_Cache_File or * @param mixed $cache object with identical interface as Minify_Cache_File or
* a directory path. (default = '') * a directory path. (default = '')
* @return null * @return null
* @deprecated * @deprecated
*/ */
public static function useServerCache($path = '') public static function useServerCache($path = '')
{ {
self::setCache($path); self::setCache($path);
} }
/** /**
* Specify a cache object (with identical interface as Minify_Cache_File) or * Specify a cache object (with identical interface as Minify_Cache_File) or
* a path to use with Minify_Cache_File. * a path to use with Minify_Cache_File.
* *
* If not called, Minify will not use a cache and, for each 200 response, will * If not called, Minify will not use a cache and, for each 200 response, will
* need to recombine files, minify and encode the output. * need to recombine files, minify and encode the output.
* *
* @param mixed $cache object with identical interface as Minify_Cache_File or * @param mixed $cache object with identical interface as Minify_Cache_File or
* a directory path. (default = '') * a directory path. (default = '')
* *
* @return null * @return null
*/ */
public static function setCache($cache = '') public static function setCache($cache = '')
{ {
if (is_string($cache)) { if (is_string($cache)) {
require_once 'Minify/Cache/File.php'; require_once 'Minify/Cache/File.php';
self::$_cache = new Minify_Cache_File($cache); self::$_cache = new Minify_Cache_File($cache);
} else { } else {
self::$_cache = $cache; self::$_cache = $cache;
} }
} }
private static $_cache = null; private static $_cache = null;
/** /**
* Serve a request for a minified file. * Serve a request for a minified file.
* *
* Here are the available options and defaults in the base controller: * Here are the available options and defaults in the base controller:
* *
* 'isPublic' : send "public" instead of "private" in Cache-Control * 'isPublic' : send "public" instead of "private" in Cache-Control
* headers, allowing shared caches to cache the output. (default true) * headers, allowing shared caches to cache the output. (default true)
* *
* 'quiet' : set to true to have serve() return an array rather than sending * 'quiet' : set to true to have serve() return an array rather than sending
* any headers/output (default false) * any headers/output (default false)
* *
* 'encodeOutput' : to disable content encoding, set this to false (default true) * 'encodeOutput' : to disable content encoding, set this to false (default true)
* *
* 'encodeMethod' : generally you should let this be determined by * 'encodeMethod' : generally you should let this be determined by
* HTTP_Encoder (leave null), but you can force a particular encoding * HTTP_Encoder (leave null), but you can force a particular encoding
* to be returned, by setting this to 'gzip', 'deflate', or '' (no encoding) * to be returned, by setting this to 'gzip', 'deflate', or '' (no encoding)
* *
* 'encodeLevel' : level of encoding compression (0 to 9, default 9) * 'encodeLevel' : level of encoding compression (0 to 9, default 9)
* *
* 'contentTypeCharset' : appended to the Content-Type header sent. Set to a falsey * 'contentTypeCharset' : appended to the Content-Type header sent. Set to a falsey
* value to remove. (default 'UTF-8') * value to remove. (default 'UTF-8')
* *
* 'maxAge' : set this to the number of seconds the client should use its cache * '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 * before revalidating with the server. This sets Cache-Control: max-age and the
* Expires header. Unlike the old 'setExpires' setting, this setting will NOT * Expires header. Unlike the old 'setExpires' setting, this setting will NOT
* prevent conditional GETs. Note this has nothing to do with server-side caching. * prevent conditional GETs. Note this has nothing to do with server-side caching.
* *
* 'rewriteCssUris' : If true, serve() will automatically set the 'currentDir' * 'rewriteCssUris' : If true, serve() will automatically set the 'currentDir'
* minifier option to enable URI rewriting in CSS files (default true) * minifier option to enable URI rewriting in CSS files (default true)
* *
* 'debug' : set to true to minify all sources with the 'Lines' controller, which * 'debug' : set to true to minify all sources with the 'Lines' controller, which
* eases the debugging of combined files. This also prevents 304 responses. * eases the debugging of combined files. This also prevents 304 responses.
* @see Minify_Lines::minify() * @see Minify_Lines::minify()
* *
* 'minifiers' : to override Minify's default choice of minifier function for * 'minifiers' : to override Minify's default choice of minifier function for
* a particular content-type, specify your callback under the key of the * a particular content-type, specify your callback under the key of the
* content-type: * content-type:
* <code> * <code>
* // call customCssMinifier($css) for all CSS minification * // call customCssMinifier($css) for all CSS minification
* $options['minifiers'][Minify::TYPE_CSS] = 'customCssMinifier'; * $options['minifiers'][Minify::TYPE_CSS] = 'customCssMinifier';
* *
* // don't minify Javascript at all * // don't minify Javascript at all
* $options['minifiers'][Minify::TYPE_JS] = ''; * $options['minifiers'][Minify::TYPE_JS] = '';
* </code> * </code>
* *
* 'minifierOptions' : to send options to the minifier function, specify your options * '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: * under the key of the content-type. E.g. To send the CSS minifier an option:
* <code> * <code>
* // give CSS minifier array('optionName' => 'optionValue') as 2nd argument * // give CSS minifier array('optionName' => 'optionValue') as 2nd argument
* $options['minifierOptions'][Minify::TYPE_CSS]['optionName'] = 'optionValue'; * $options['minifierOptions'][Minify::TYPE_CSS]['optionName'] = 'optionValue';
* </code> * </code>
* *
* 'contentType' : (optional) this is only needed if your file extension is not * '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 * 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 * extension, so this should not be used in a Groups config with other
* Javascript/CSS files. * Javascript/CSS files.
* *
* Any controller options are documented in that controller's setupSources() method. * Any controller options are documented in that controller's setupSources() method.
* *
* @param mixed instance of subclass of Minify_Controller_Base or string name of * @param mixed instance of subclass of Minify_Controller_Base or string name of
* controller. E.g. 'Files' * controller. E.g. 'Files'
* *
* @param array $options controller/serve options * @param array $options controller/serve options
* *
* @return mixed null, or, if the 'quiet' option is set to true, an array * @return mixed null, or, if the 'quiet' option is set to true, an array
* with keys "success" (bool), "statusCode" (int), "content" (string), and * with keys "success" (bool), "statusCode" (int), "content" (string), and
* "headers" (array). * "headers" (array).
*/ */
public static function serve($controller, $options = array()) { public static function serve($controller, $options = array()) {
if (is_string($controller)) { if (is_string($controller)) {
// make $controller into object // make $controller into object
$class = 'Minify_Controller_' . $controller; $class = 'Minify_Controller_' . $controller;
if (! class_exists($class, false)) { if (! class_exists($class, false)) {
require_once "Minify/Controller/" require_once "Minify/Controller/"
. str_replace('_', '/', $controller) . ".php"; . str_replace('_', '/', $controller) . ".php";
} }
$controller = new $class(); $controller = new $class();
} }
// set up controller sources and mix remaining options with // set up controller sources and mix remaining options with
// controller defaults // controller defaults
$options = $controller->setupSources($options); $options = $controller->setupSources($options);
$options = $controller->analyzeSources($options); $options = $controller->analyzeSources($options);
self::$_options = $controller->mixInDefaultOptions($options); self::$_options = $controller->mixInDefaultOptions($options);
// check request validity // check request validity
if (! $controller->sources) { if (! $controller->sources) {
// invalid request! // invalid request!
if (! self::$_options['quiet']) { if (! self::$_options['quiet']) {
header(self::$_options['badRequestHeader']); header(self::$_options['badRequestHeader']);
echo self::$_options['badRequestHeader']; echo self::$_options['badRequestHeader'];
return; return;
} else { } else {
list(,$statusCode) = explode(' ', self::$_options['badRequestHeader']); list(,$statusCode) = explode(' ', self::$_options['badRequestHeader']);
return array( return array(
'success' => false 'success' => false
,'statusCode' => (int)$statusCode ,'statusCode' => (int)$statusCode
,'content' => '' ,'content' => ''
,'headers' => array() ,'headers' => array()
); );
} }
} }
self::$_controller = $controller; self::$_controller = $controller;
if (self::$_options['debug']) { if (self::$_options['debug']) {
self::_setupDebug($controller->sources); self::_setupDebug($controller->sources);
self::$_options['maxAge'] = 0; self::$_options['maxAge'] = 0;
} }
// check client cache // check client cache
require_once 'HTTP/ConditionalGet.php'; require_once 'HTTP/ConditionalGet.php';
$cgOptions = array( $cgOptions = array(
'lastModifiedTime' => self::$_options['lastModifiedTime'] 'lastModifiedTime' => self::$_options['lastModifiedTime']
,'isPublic' => self::$_options['isPublic'] ,'isPublic' => self::$_options['isPublic']
); );
if (self::$_options['maxAge'] > 0) { if (self::$_options['maxAge'] > 0) {
$cgOptions['maxAge'] = self::$_options['maxAge']; $cgOptions['maxAge'] = self::$_options['maxAge'];
} }
$cg = new HTTP_ConditionalGet($cgOptions); $cg = new HTTP_ConditionalGet($cgOptions);
if ($cg->cacheIsValid) { if ($cg->cacheIsValid) {
// client's cache is valid // client's cache is valid
if (! self::$_options['quiet']) { if (! self::$_options['quiet']) {
$cg->sendHeaders(); $cg->sendHeaders();
return; return;
} else { } else {
return array( return array(
'success' => true 'success' => true
,'statusCode' => 304 ,'statusCode' => 304
,'content' => '' ,'content' => ''
,'headers' => $cg->getHeaders() ,'headers' => $cg->getHeaders()
); );
} }
} else { } else {
// client will need output // client will need output
$headers = $cg->getHeaders(); $headers = $cg->getHeaders();
unset($cg); unset($cg);
} }
// determine encoding // determine encoding
if (self::$_options['encodeOutput']) { if (self::$_options['encodeOutput']) {
if (self::$_options['encodeMethod'] !== null) { if (self::$_options['encodeMethod'] !== null) {
// controller specifically requested this // controller specifically requested this
$contentEncoding = self::$_options['encodeMethod']; $contentEncoding = self::$_options['encodeMethod'];
} else { } else {
// sniff request header // sniff request header
require_once 'HTTP/Encoder.php'; require_once 'HTTP/Encoder.php';
// depending on what the client accepts, $contentEncoding may be // depending on what the client accepts, $contentEncoding may be
// 'x-gzip' while our internal encodeMethod is 'gzip'. Calling // 'x-gzip' while our internal encodeMethod is 'gzip'. Calling
// getAcceptedEncoding() with false leaves out compress as an option. // getAcceptedEncoding() with false leaves out compress as an option.
list(self::$_options['encodeMethod'], $contentEncoding) = HTTP_Encoder::getAcceptedEncoding(false); list(self::$_options['encodeMethod'], $contentEncoding) = HTTP_Encoder::getAcceptedEncoding(false);
} }
} else { } else {
self::$_options['encodeMethod'] = ''; // identity (no encoding) self::$_options['encodeMethod'] = ''; // identity (no encoding)
} }
if (self::$_options['contentType'] === self::TYPE_CSS if (self::$_options['contentType'] === self::TYPE_CSS
@@ -252,184 +252,184 @@ class Minify {
} }
} }
} }
// check server cache // check server cache
if (null !== self::$_cache) { if (null !== self::$_cache) {
// using cache // using cache
// the goal is to use only the cache methods to sniff the length and // 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 // output the content, as they do not require ever loading the file into
// memory. // memory.
$cacheId = 'minify_' . self::_getCacheId(); $cacheId = 'minify_' . self::_getCacheId();
$encodingExtension = self::$_options['encodeMethod'] $encodingExtension = self::$_options['encodeMethod']
? ('deflate' === self::$_options['encodeMethod'] ? ('deflate' === self::$_options['encodeMethod']
? '.zd' ? '.zd'
: '.zg') : '.zg')
: ''; : '';
$fullCacheId = $cacheId . $encodingExtension; $fullCacheId = $cacheId . $encodingExtension;
// check cache for valid entry // check cache for valid entry
$cacheIsReady = self::$_cache->isValid($fullCacheId, self::$_options['lastModifiedTime']); $cacheIsReady = self::$_cache->isValid($fullCacheId, self::$_options['lastModifiedTime']);
if ($cacheIsReady) { if ($cacheIsReady) {
$cacheContentLength = self::$_cache->getSize($fullCacheId); $cacheContentLength = self::$_cache->getSize($fullCacheId);
} else { } else {
// generate & cache content // generate & cache content
$content = self::_combineMinify(); $content = self::_combineMinify();
self::$_cache->store($cacheId, $content); self::$_cache->store($cacheId, $content);
self::$_cache->store($cacheId . '.zd', gzdeflate($content, self::$_options['encodeLevel'])); self::$_cache->store($cacheId . '.zd', gzdeflate($content, self::$_options['encodeLevel']));
self::$_cache->store($cacheId . '.zg', gzencode($content, self::$_options['encodeLevel'])); self::$_cache->store($cacheId . '.zg', gzencode($content, self::$_options['encodeLevel']));
} }
} else { } else {
// no cache // no cache
$cacheIsReady = false; $cacheIsReady = false;
$content = self::_combineMinify(); $content = self::_combineMinify();
} }
if (! $cacheIsReady && self::$_options['encodeMethod']) { if (! $cacheIsReady && self::$_options['encodeMethod']) {
// still need to encode // still need to encode
$content = ('deflate' === self::$_options['encodeMethod']) $content = ('deflate' === self::$_options['encodeMethod'])
? gzdeflate($content, self::$_options['encodeLevel']) ? gzdeflate($content, self::$_options['encodeLevel'])
: gzencode($content, self::$_options['encodeLevel']); : gzencode($content, self::$_options['encodeLevel']);
} }
// add headers // add headers
$headers['Content-Length'] = $cacheIsReady $headers['Content-Length'] = $cacheIsReady
? $cacheContentLength ? $cacheContentLength
: strlen($content); : strlen($content);
$headers['Content-Type'] = self::$_options['contentTypeCharset'] $headers['Content-Type'] = self::$_options['contentTypeCharset']
? self::$_options['contentType'] . '; charset=' . self::$_options['contentTypeCharset'] ? self::$_options['contentType'] . '; charset=' . self::$_options['contentTypeCharset']
: self::$_options['contentType']; : self::$_options['contentType'];
if (self::$_options['encodeMethod'] !== '') { if (self::$_options['encodeMethod'] !== '') {
$headers['Content-Encoding'] = $contentEncoding; $headers['Content-Encoding'] = $contentEncoding;
$headers['Vary'] = 'Accept-Encoding'; $headers['Vary'] = 'Accept-Encoding';
} }
if (! self::$_options['quiet']) { if (! self::$_options['quiet']) {
// output headers & content // output headers & content
foreach ($headers as $name => $val) { foreach ($headers as $name => $val) {
header($name . ': ' . $val); header($name . ': ' . $val);
} }
if ($cacheIsReady) { if ($cacheIsReady) {
self::$_cache->display($fullCacheId); self::$_cache->display($fullCacheId);
} else { } else {
echo $content; echo $content;
} }
} else { } else {
return array( return array(
'success' => true 'success' => true
,'statusCode' => 200 ,'statusCode' => 200
,'content' => $cacheIsReady ,'content' => $cacheIsReady
? self::$_cache->fetch($fullCacheId) ? self::$_cache->fetch($fullCacheId)
: $content : $content
,'headers' => $headers ,'headers' => $headers
); );
} }
} }
/** /**
* @var Minify_Controller active controller for current request * @var Minify_Controller active controller for current request
*/ */
protected static $_controller = null; protected static $_controller = null;
/** /**
* @var array options for current request * @var array options for current request
*/ */
protected static $_options = null; protected static $_options = null;
/** /**
* Set up sources to use Minify_Lines * Set up sources to use Minify_Lines
* *
* @param array $sources Minify_Source instances * @param array $sources Minify_Source instances
* *
* @return null * @return null
*/ */
protected static function _setupDebug($sources) protected static function _setupDebug($sources)
{ {
foreach ($sources as $source) { foreach ($sources as $source) {
$source->minifier = array('Minify_Lines', 'minify'); $source->minifier = array('Minify_Lines', 'minify');
$id = $source->getId(); $id = $source->getId();
$source->minifyOptions = array( $source->minifyOptions = array(
'id' => (is_file($id) ? basename($id) : $id) 'id' => (is_file($id) ? basename($id) : $id)
); );
} }
} }
/** /**
* Combines sources and minifies the result. * Combines sources and minifies the result.
* *
* @return string * @return string
*/ */
protected static function _combineMinify() { protected static function _combineMinify() {
$type = self::$_options['contentType']; // ease readability $type = self::$_options['contentType']; // ease readability
// when combining scripts, make sure all statements separated // when combining scripts, make sure all statements separated
$implodeSeparator = ($type === self::TYPE_JS) $implodeSeparator = ($type === self::TYPE_JS)
? ';' ? ';'
: ''; : '';
// allow the user to pass a particular array of options to each // allow the user to pass a particular array of options to each
// minifier (designated by type). source objects may still override // minifier (designated by type). source objects may still override
// these // these
$defaultOptions = isset(self::$_options['minifierOptions'][$type]) $defaultOptions = isset(self::$_options['minifierOptions'][$type])
? self::$_options['minifierOptions'][$type] ? self::$_options['minifierOptions'][$type]
: array(); : array();
// if minifier not set, default is no minification. source objects // if minifier not set, default is no minification. source objects
// may still override this // may still override this
$defaultMinifier = isset(self::$_options['minifiers'][$type]) $defaultMinifier = isset(self::$_options['minifiers'][$type])
? self::$_options['minifiers'][$type] ? self::$_options['minifiers'][$type]
: false; : false;
if (Minify_Source::haveNoMinifyPrefs(self::$_controller->sources)) { if (Minify_Source::haveNoMinifyPrefs(self::$_controller->sources)) {
// all source have same options/minifier, better performance // all source have same options/minifier, better performance
// to combine, then minify once // to combine, then minify once
foreach (self::$_controller->sources as $source) { foreach (self::$_controller->sources as $source) {
$pieces[] = $source->getContent(); $pieces[] = $source->getContent();
} }
$content = implode($implodeSeparator, $pieces); $content = implode($implodeSeparator, $pieces);
if ($defaultMinifier) { if ($defaultMinifier) {
self::$_controller->loadMinifier($defaultMinifier); self::$_controller->loadMinifier($defaultMinifier);
$content = call_user_func($defaultMinifier, $content, $defaultOptions); $content = call_user_func($defaultMinifier, $content, $defaultOptions);
} }
} else { } else {
// minify each source with its own options and minifier, then combine // minify each source with its own options and minifier, then combine
foreach (self::$_controller->sources as $source) { foreach (self::$_controller->sources as $source) {
// allow the source to override our minifier and options // allow the source to override our minifier and options
$minifier = (null !== $source->minifier) $minifier = (null !== $source->minifier)
? $source->minifier ? $source->minifier
: $defaultMinifier; : $defaultMinifier;
$options = (null !== $source->minifyOptions) $options = (null !== $source->minifyOptions)
? array_merge($defaultOptions, $source->minifyOptions) ? array_merge($defaultOptions, $source->minifyOptions)
: $defaultOptions; : $defaultOptions;
if ($defaultMinifier) { if ($defaultMinifier) {
self::$_controller->loadMinifier($minifier); self::$_controller->loadMinifier($minifier);
// get source content and minify it // get source content and minify it
$pieces[] = call_user_func($minifier, $source->getContent(), $options); $pieces[] = call_user_func($minifier, $source->getContent(), $options);
} else { } else {
$pieces[] = $source->getContent(); $pieces[] = $source->getContent();
} }
} }
$content = implode($implodeSeparator, $pieces); $content = implode($implodeSeparator, $pieces);
} }
// do any post-processing (esp. for editing build URIs) // do any post-processing (esp. for editing build URIs)
if (self::$_options['postprocessorRequire']) { if (self::$_options['postprocessorRequire']) {
require_once self::$_options['postprocessorRequire']; require_once self::$_options['postprocessorRequire'];
} }
if (self::$_options['postprocessor']) { if (self::$_options['postprocessor']) {
$content = call_user_func(self::$_options['postprocessor'], $content, $type); $content = call_user_func(self::$_options['postprocessor'], $content, $type);
} }
return $content; return $content;
} }
/** /**
* Make a unique cache id for for this request. * Make a unique cache id for for this request.
* *
* Any settings that could affect output are taken into consideration * Any settings that could affect output are taken into consideration
* *
* @return string * @return string
*/ */
protected static function _getCacheId() { protected static function _getCacheId() {
return md5(serialize(array( return md5(serialize(array(
Minify_Source::getDigest(self::$_controller->sources) Minify_Source::getDigest(self::$_controller->sources)
,self::$_options['minifiers'] ,self::$_options['minifiers']
,self::$_options['minifierOptions'] ,self::$_options['minifierOptions']
,self::$_options['postprocessor'] ,self::$_options['postprocessor']
))); )));
} }
} }

View File

@@ -1,101 +1,101 @@
<?php <?php
/** /**
* Class Minify_Build * Class Minify_Build
* @package Minify * @package Minify
*/ */
require_once 'Minify/Source.php'; require_once 'Minify/Source.php';
/** /**
* Maintain a single last modification time for a group of Minify sources to * Maintain a single last modification time for a group of Minify sources to
* allow use of far off Expires headers in Minify. * allow use of far off Expires headers in Minify.
* *
* <code> * <code>
* // in config file * // in config file
* $groupSources = array( * $groupSources = array(
* 'js' => array('file1.js', 'file2.js') * 'js' => array('file1.js', 'file2.js')
* ,'css' => array('file1.css', 'file2.css', 'file3.css') * ,'css' => array('file1.css', 'file2.css', 'file3.css')
* ) * )
* *
* // during HTML generation * // during HTML generation
* $jsBuild = new Minify_Build($groupSources['js']); * $jsBuild = new Minify_Build($groupSources['js']);
* $cssBuild = new Minify_Build($groupSources['css']); * $cssBuild = new Minify_Build($groupSources['css']);
* *
* $script = "<script type='text/javascript' src='" * $script = "<script type='text/javascript' src='"
* . $jsBuild->uri('/min.php/js') . "'></script>"; * . $jsBuild->uri('/min.php/js') . "'></script>";
* $link = "<link rel='stylesheet' type='text/css' href='" * $link = "<link rel='stylesheet' type='text/css' href='"
* . $cssBuild->uri('/min.php/css') . "'>"; * . $cssBuild->uri('/min.php/css') . "'>";
* *
* // in min.php * // in min.php
* Minify::serve('Groups', array( * Minify::serve('Groups', array(
* 'groups' => $groupSources * 'groups' => $groupSources
* ,'setExpires' => (time() + 86400 * 365) * ,'setExpires' => (time() + 86400 * 365)
* )); * ));
* </code> * </code>
* *
* @package Minify * @package Minify
* @author Stephen Clay <steve@mrclay.org> * @author Stephen Clay <steve@mrclay.org>
*/ */
class Minify_Build { class Minify_Build {
/** /**
* Last modification time of all files in the build * Last modification time of all files in the build
* *
* @var int * @var int
*/ */
public $lastModified = 0; public $lastModified = 0;
/** /**
* String to use as ampersand in uri(). Set this to '&' if * String to use as ampersand in uri(). Set this to '&' if
* you are not HTML-escaping URIs. * you are not HTML-escaping URIs.
* *
* @var string * @var string
*/ */
public static $ampersand = '&amp;'; public static $ampersand = '&amp;';
/** /**
* Get a time-stamped URI * Get a time-stamped URI
* *
* <code> * <code>
* echo $b->uri('/site.js'); * echo $b->uri('/site.js');
* // outputs "/site.js?1678242" * // outputs "/site.js?1678242"
* *
* echo $b->uri('/scriptaculous.js?load=effects'); * echo $b->uri('/scriptaculous.js?load=effects');
* // outputs "/scriptaculous.js?load=effects&amp1678242" * // outputs "/scriptaculous.js?load=effects&amp1678242"
* </code> * </code>
* *
* @param string $uri * @param string $uri
* @return string * @return string
*/ */
public function uri($uri) { public function uri($uri) {
$sep = strpos($uri, '?') === false $sep = strpos($uri, '?') === false
? '?' ? '?'
: self::$ampersand; : self::$ampersand;
return "{$uri}{$sep}{$this->lastModified}"; return "{$uri}{$sep}{$this->lastModified}";
} }
/** /**
* Create a build object * Create a build object
* *
* @param array $sources array of Minify_Source objects and/or file paths * @param array $sources array of Minify_Source objects and/or file paths
* *
* @return null * @return null
*/ */
public function __construct($sources) public function __construct($sources)
{ {
$max = 0; $max = 0;
foreach ((array)$sources as $source) { foreach ((array)$sources as $source) {
if ($source instanceof Minify_Source) { if ($source instanceof Minify_Source) {
$max = max($max, $source->lastModified); $max = max($max, $source->lastModified);
} elseif (is_string($source)) { } elseif (is_string($source)) {
if (0 === strpos($source, '//')) { if (0 === strpos($source, '//')) {
$source = $_SERVER['DOCUMENT_ROOT'] . substr($source, 1); $source = $_SERVER['DOCUMENT_ROOT'] . substr($source, 1);
} }
if (is_file($source)) { if (is_file($source)) {
$max = max($max, filemtime($source)); $max = max($max, filemtime($source));
} }
} }
} }
$this->lastModified = $max; $this->lastModified = $max;
} }
} }

View File

@@ -1,4 +1,4 @@
<?php <?php
/** /**
* Class Minify_CSS * Class Minify_CSS
* @package Minify * @package Minify

View File

@@ -1,103 +1,103 @@
<?php <?php
/** /**
* Class Minify_Cache_File * Class Minify_Cache_File
* @package Minify * @package Minify
*/ */
class Minify_Cache_File { class Minify_Cache_File {
public function __construct($path = '') public function __construct($path = '')
{ {
if (! $path) { if (! $path) {
require_once 'Solar/Dir.php'; require_once 'Solar/Dir.php';
$path = rtrim(Solar_Dir::tmp(), DIRECTORY_SEPARATOR); $path = rtrim(Solar_Dir::tmp(), DIRECTORY_SEPARATOR);
} }
$this->_path = $path; $this->_path = $path;
} }
/** /**
* Write data to cache. * Write data to cache.
* *
* @param string $id cache id (e.g. a filename) * @param string $id cache id (e.g. a filename)
* *
* @param string $data * @param string $data
* *
* @return bool success * @return bool success
*/ */
public function store($id, $data) public function store($id, $data)
{ {
return self::_verifiedWrite($this->_path . '/' . $id, $data); return self::_verifiedWrite($this->_path . '/' . $id, $data);
} }
/** /**
* Get the size of a cache entry * Get the size of a cache entry
* *
* @param string $id cache id (e.g. a filename) * @param string $id cache id (e.g. a filename)
* *
* @return int size in bytes * @return int size in bytes
*/ */
public function getSize($id) public function getSize($id)
{ {
return filesize($this->_path . '/' . $id); return filesize($this->_path . '/' . $id);
} }
/** /**
* Does a valid cache entry exist? * Does a valid cache entry exist?
* *
* @param string $id cache id (e.g. a filename) * @param string $id cache id (e.g. a filename)
* *
* @param int $srcMtime mtime of the original source file(s) * @param int $srcMtime mtime of the original source file(s)
* *
* @return bool exists * @return bool exists
*/ */
public function isValid($id, $srcMtime) public function isValid($id, $srcMtime)
{ {
$file = $this->_path . '/' . $id; $file = $this->_path . '/' . $id;
return (file_exists($file) && (filemtime($file) >= $srcMtime)); return (file_exists($file) && (filemtime($file) >= $srcMtime));
} }
/** /**
* Send the cached content to output * Send the cached content to output
* *
* @param string $id cache id (e.g. a filename) * @param string $id cache id (e.g. a filename)
*/ */
public function display($id) public function display($id)
{ {
readfile($this->_path . '/' . $id); readfile($this->_path . '/' . $id);
} }
/** /**
* Fetch the cached content * Fetch the cached content
* *
* @param string $id cache id (e.g. a filename) * @param string $id cache id (e.g. a filename)
* *
* @return string * @return string
*/ */
public function fetch($id) public function fetch($id)
{ {
return file_get_contents($this->_path . '/' . $id); return file_get_contents($this->_path . '/' . $id);
} }
private $_path = null; private $_path = null;
/** /**
* Write data to file and verify its contents * Write data to file and verify its contents
* *
* @param string $file path * @param string $file path
* *
* @param string $data * @param string $data
* *
* @return bool success * @return bool success
*/ */
private static function _verifiedWrite($file, $data) private static function _verifiedWrite($file, $data)
{ {
if (! @file_put_contents($file, $data)) { if (! @file_put_contents($file, $data)) {
return false; return false;
} }
if (md5($data) !== md5_file($file)) { if (md5($data) !== md5_file($file)) {
@unlink($file); @unlink($file);
return false; return false;
} }
return true; return true;
} }
} }

View File

@@ -1,123 +1,123 @@
<?php <?php
/** /**
* Class Minify_Controller_Base * Class Minify_Controller_Base
* @package Minify * @package Minify
*/ */
/** /**
* Base class for Minify controller * Base class for Minify controller
* *
* The controller class validates a request and uses it to create sources * The controller class validates a request and uses it to create sources
* for minification and set options like contentType. It's also responsible * for minification and set options like contentType. It's also responsible
* for loading minifier code upon request. * for loading minifier code upon request.
* *
* @package Minify * @package Minify
* @author Stephen Clay <steve@mrclay.org> * @author Stephen Clay <steve@mrclay.org>
* *
* @todo add static function to ease setting currentPath for CSS files * @todo add static function to ease setting currentPath for CSS files
* (see line 83 of Version1.php) * (see line 83 of Version1.php)
*/ */
abstract class Minify_Controller_Base { abstract class Minify_Controller_Base {
/** /**
* Setup controller sources * Setup controller sources
* *
* You must override this method in your subclass controller to set * You must override this method in your subclass controller to set
* $this->sources. If the request is NOT valid, make sure $this->sources * $this->sources. If the request is NOT valid, make sure $this->sources
* is left an empty array. Then strip any controller-specific options from * is left an empty array. Then strip any controller-specific options from
* $options and return it. * $options and return it.
* *
* @param array $options controller and Minify options * @param array $options controller and Minify options
* *
* @param array $options Minify options * @param array $options Minify options
*/ */
abstract public function setupSources($options); abstract public function setupSources($options);
/** /**
* Get default Minify options for this controller. * Get default Minify options for this controller.
* *
* Override in subclass to change defaults * Override in subclass to change defaults
* *
* @return array options for Minify * @return array options for Minify
*/ */
public function getDefaultMinifyOptions() { public function getDefaultMinifyOptions() {
return array( return array(
'isPublic' => true 'isPublic' => true
,'encodeOutput' => true ,'encodeOutput' => true
,'encodeMethod' => null // determine later ,'encodeMethod' => null // determine later
,'encodeLevel' => 9 ,'encodeLevel' => 9
,'minifierOptions' => array() // no minifier options ,'minifierOptions' => array() // no minifier options
,'contentTypeCharset' => 'UTF-8' ,'contentTypeCharset' => 'UTF-8'
,'maxAge' => 1800 // 30 minutes ,'maxAge' => 1800 // 30 minutes
,'rewriteCssUris' => true ,'rewriteCssUris' => true
,'quiet' => false // serve() will send headers and output ,'quiet' => false // serve() will send headers and output
,'debug' => false ,'debug' => false
// if you override this, the response code MUST be directly after // if you override this, the response code MUST be directly after
// the first space. // the first space.
,'badRequestHeader' => 'HTTP/1.0 400 Bad Request' ,'badRequestHeader' => 'HTTP/1.0 400 Bad Request'
// callback function to see/modify content of all sources // callback function to see/modify content of all sources
,'postprocessor' => null ,'postprocessor' => null
// file to require to load preprocessor // file to require to load preprocessor
,'postprocessorRequire' => null ,'postprocessorRequire' => null
); );
} }
/** /**
* Get default minifiers for this controller. * Get default minifiers for this controller.
* *
* Override in subclass to change defaults * Override in subclass to change defaults
* *
* @return array minifier callbacks for common types * @return array minifier callbacks for common types
*/ */
public function getDefaultMinifers() { public function getDefaultMinifers() {
$ret[Minify::TYPE_JS] = array('Minify_Javascript', 'minify'); $ret[Minify::TYPE_JS] = array('Minify_Javascript', 'minify');
$ret[Minify::TYPE_CSS] = array('Minify_CSS', 'minify'); $ret[Minify::TYPE_CSS] = array('Minify_CSS', 'minify');
$ret[Minify::TYPE_HTML] = array('Minify_HTML', 'minify'); $ret[Minify::TYPE_HTML] = array('Minify_HTML', 'minify');
return $ret; return $ret;
} }
/** /**
* Load any code necessary to execute the given minifier callback. * Load any code necessary to execute the given minifier callback.
* *
* The controller is responsible for loading minification code on demand * The controller is responsible for loading minification code on demand
* via this method. This built-in function will only load classes for * via this method. This built-in function will only load classes for
* static method callbacks where the class isn't already defined. It uses * static method callbacks where the class isn't already defined. It uses
* the PEAR convention, so, given array('Jimmy_Minifier', 'minCss'), this * the PEAR convention, so, given array('Jimmy_Minifier', 'minCss'), this
* function will include 'Jimmy/Minifier.php' * function will include 'Jimmy/Minifier.php'
* *
* If you need code loaded on demand and this doesn't suit you, you'll need * If you need code loaded on demand and this doesn't suit you, you'll need
* to override this function in your subclass. * to override this function in your subclass.
* @see Minify_Controller_Page::loadMinifier() * @see Minify_Controller_Page::loadMinifier()
* *
* @param callback $minifierCallback callback of minifier function * @param callback $minifierCallback callback of minifier function
* *
* @return null * @return null
*/ */
public function loadMinifier($minifierCallback) public function loadMinifier($minifierCallback)
{ {
if (is_array($minifierCallback) if (is_array($minifierCallback)
&& is_string($minifierCallback[0]) && is_string($minifierCallback[0])
&& !class_exists($minifierCallback[0], false)) { && !class_exists($minifierCallback[0], false)) {
require str_replace('_', '/', $minifierCallback[0]) . '.php'; require str_replace('_', '/', $minifierCallback[0]) . '.php';
} }
} }
/** /**
* Is a user-given file within an allowable directory, existing, * Is a user-given file within an allowable directory, existing,
* and having an extension js/css/html/txt * and having an extension js/css/html/txt
* *
* This is a convenience function for controllers that have to accept * This is a convenience function for controllers that have to accept
* user-given paths * user-given paths
* *
* @param string $file full file path (already processed by realpath()) * @param string $file full file path (already processed by realpath())
* @param array $safeDirs directories where files are safe to serve * @param array $safeDirs directories where files are safe to serve
* @return bool file is safe * @return bool file is safe
*/ */
public static function _fileIsSafe($file, $safeDirs) public static function _fileIsSafe($file, $safeDirs)
{ {
$pathOk = false; $pathOk = false;
foreach ((array)$safeDirs as $safeDir) { foreach ((array)$safeDirs as $safeDir) {
if (strpos($file, $safeDir) === 0 && file_exists($file)) { if (strpos($file, $safeDir) === 0 && file_exists($file)) {
@@ -128,68 +128,68 @@ abstract class Minify_Controller_Base {
if (! $pathOk) { if (! $pathOk) {
return false; return false;
} }
$base = basename($file); $base = basename($file);
if ($base[0] === '.') { if ($base[0] === '.') {
return false; return false;
} }
list($revExt) = explode('.', strrev($base)); list($revExt) = explode('.', strrev($base));
return in_array(strrev($revExt), array('js', 'css', 'html', 'txt')); return in_array(strrev($revExt), array('js', 'css', 'html', 'txt'));
} }
/*public static function _haveSameExt /*public static function _haveSameExt
/** /**
* @var array instances of Minify_Source, which provide content and * @var array instances of Minify_Source, which provide content and
* any individual minification needs. * any individual minification needs.
* *
* @see Minify_Source * @see Minify_Source
*/ */
public $sources = array(); public $sources = array();
/** /**
* Mix in default controller options with user-given options * Mix in default controller options with user-given options
* *
* @param array $options user options * @param array $options user options
* *
* @return array mixed options * @return array mixed options
*/ */
public final function mixInDefaultOptions($options) public final function mixInDefaultOptions($options)
{ {
$ret = array_merge( $ret = array_merge(
$this->getDefaultMinifyOptions(), $options $this->getDefaultMinifyOptions(), $options
); );
if (! isset($options['minifiers'])) { if (! isset($options['minifiers'])) {
$options['minifiers'] = array(); $options['minifiers'] = array();
} }
$ret['minifiers'] = array_merge( $ret['minifiers'] = array_merge(
$this->getDefaultMinifers(), $options['minifiers'] $this->getDefaultMinifers(), $options['minifiers']
); );
return $ret; return $ret;
} }
/** /**
* Analyze sources (if there are any) and set $options 'contentType' * Analyze sources (if there are any) and set $options 'contentType'
* and 'lastModifiedTime' if they already aren't. * and 'lastModifiedTime' if they already aren't.
* *
* @param array $options options for Minify * @param array $options options for Minify
* *
* @return array options for Minify * @return array options for Minify
*/ */
public final function analyzeSources($options = array()) public final function analyzeSources($options = array())
{ {
if ($this->sources) { if ($this->sources) {
if (! isset($options['contentType'])) { if (! isset($options['contentType'])) {
$options['contentType'] = Minify_Source::getContentType($this->sources); $options['contentType'] = Minify_Source::getContentType($this->sources);
} }
// last modified is needed for caching, even if setExpires is set // last modified is needed for caching, even if setExpires is set
if (! isset($options['lastModifiedTime'])) { if (! isset($options['lastModifiedTime'])) {
$max = 0; $max = 0;
foreach ($this->sources as $source) { foreach ($this->sources as $source) {
$max = max($source->lastModified, $max); $max = max($source->lastModified, $max);
} }
$options['lastModifiedTime'] = $max; $options['lastModifiedTime'] = $max;
} }
} }
return $options; return $options;
} }
} }

View File

@@ -1,71 +1,71 @@
<?php <?php
/** /**
* Class Minify_Controller_Files * Class Minify_Controller_Files
* @package Minify * @package Minify
*/ */
require_once 'Minify/Controller/Base.php'; require_once 'Minify/Controller/Base.php';
/** /**
* Controller class for minifying a set of files * Controller class for minifying a set of files
* *
* E.g. the following would serve the minified Javascript for a site * E.g. the following would serve the minified Javascript for a site
* <code> * <code>
* Minify::serve('Files', array( * Minify::serve('Files', array(
* 'files' => array( * 'files' => array(
* '//js/jquery.js' * '//js/jquery.js'
* ,'//js/plugins.js' * ,'//js/plugins.js'
* ,'/home/username/file.js' * ,'/home/username/file.js'
* ) * )
* )); * ));
* </code> * </code>
* *
* As a shortcut, the controller will replace "//" at the beginning * As a shortcut, the controller will replace "//" at the beginning
* of a filename with $_SERVER['DOCUMENT_ROOT'] . '/'. * of a filename with $_SERVER['DOCUMENT_ROOT'] . '/'.
* *
* @package Minify * @package Minify
* @author Stephen Clay <steve@mrclay.org> * @author Stephen Clay <steve@mrclay.org>
*/ */
class Minify_Controller_Files extends Minify_Controller_Base { class Minify_Controller_Files extends Minify_Controller_Base {
/** /**
* Set up file sources * Set up file sources
* *
* @param array $options controller and Minify options * @param array $options controller and Minify options
* @return array Minify options * @return array Minify options
* *
* Controller options: * Controller options:
* *
* 'files': (required) array of complete file paths, or a single path * 'files': (required) array of complete file paths, or a single path
*/ */
public function setupSources($options) { public function setupSources($options) {
// strip controller options // strip controller options
$files = (array)$options['files']; $files = (array)$options['files'];
unset($options['files']); unset($options['files']);
$sources = array(); $sources = array();
foreach ($files as $file) { foreach ($files as $file) {
if ($file instanceof Minify_Source) { if ($file instanceof Minify_Source) {
$sources[] = $file; $sources[] = $file;
continue; continue;
} }
if (0 === strpos($file, '//')) { if (0 === strpos($file, '//')) {
$file = $_SERVER['DOCUMENT_ROOT'] . substr($file, 1); $file = $_SERVER['DOCUMENT_ROOT'] . substr($file, 1);
} }
$file = realpath($file); $file = realpath($file);
if (file_exists($file)) { if (file_exists($file)) {
$sources[] = new Minify_Source(array( $sources[] = new Minify_Source(array(
'filepath' => $file 'filepath' => $file
)); ));
} else { } else {
// file not found // file not found
return $options; return $options;
} }
} }
if ($sources) { if ($sources) {
$this->sources = $sources; $this->sources = $sources;
} }
return $options; return $options;
} }
} }

View File

@@ -1,50 +1,50 @@
<?php <?php
/** /**
* Class Minify_Controller_Groups * Class Minify_Controller_Groups
* @package Minify * @package Minify
*/ */
require_once 'Minify/Controller/Base.php'; require_once 'Minify/Controller/Base.php';
/** /**
* Controller class for serving predetermined groups of minimized sets, selected * Controller class for serving predetermined groups of minimized sets, selected
* by PATH_INFO * by PATH_INFO
* *
* <code> * <code>
* Minify::serve('Groups', array( * Minify::serve('Groups', array(
* 'groups' => array( * 'groups' => array(
* 'css' => array('//css/type.css', '//css/layout.css') * 'css' => array('//css/type.css', '//css/layout.css')
* ,'js' => array('//js/jquery.js', '//js/site.js') * ,'js' => array('//js/jquery.js', '//js/site.js')
* ) * )
* )); * ));
* </code> * </code>
* *
* If the above code were placed in /serve.php, it would enable the URLs * If the above code were placed in /serve.php, it would enable the URLs
* /serve.php/js and /serve.php/css * /serve.php/js and /serve.php/css
* *
* As a shortcut, the controller will replace "//" at the beginning * As a shortcut, the controller will replace "//" at the beginning
* of a filename with $_SERVER['DOCUMENT_ROOT'] . '/'. * of a filename with $_SERVER['DOCUMENT_ROOT'] . '/'.
* *
* @package Minify * @package Minify
* @author Stephen Clay <steve@mrclay.org> * @author Stephen Clay <steve@mrclay.org>
*/ */
class Minify_Controller_Groups extends Minify_Controller_Base { class Minify_Controller_Groups extends Minify_Controller_Base {
/** /**
* Set up groups of files as sources * Set up groups of files as sources
* *
* @param array $options controller and Minify options * @param array $options controller and Minify options
* @return array Minify options * @return array Minify options
* *
* Controller options: * Controller options:
* *
* 'groups': (required) array mapping PATH_INFO strings to arrays * 'groups': (required) array mapping PATH_INFO strings to arrays
* of complete file paths. @see Minify_Controller_Groups * of complete file paths. @see Minify_Controller_Groups
*/ */
public function setupSources($options) { public function setupSources($options) {
// strip controller options // strip controller options
$groups = $options['groups']; $groups = $options['groups'];
unset($options['groups']); unset($options['groups']);
// mod_fcgid places PATH_INFO in ORIG_PATH_INFO // mod_fcgid places PATH_INFO in ORIG_PATH_INFO
$pi = isset($_SERVER['ORIG_PATH_INFO']) $pi = isset($_SERVER['ORIG_PATH_INFO'])
@@ -57,29 +57,29 @@ class Minify_Controller_Groups extends Minify_Controller_Base {
// no PATH_INFO or not a valid group // no PATH_INFO or not a valid group
return $options; return $options;
} }
$sources = array(); $sources = array();
foreach ((array)$groups[$pi] as $file) { foreach ((array)$groups[$pi] as $file) {
if ($file instanceof Minify_Source) { if ($file instanceof Minify_Source) {
$sources[] = $file; $sources[] = $file;
continue; continue;
} }
if (0 === strpos($file, '//')) { if (0 === strpos($file, '//')) {
$file = $_SERVER['DOCUMENT_ROOT'] . substr($file, 1); $file = $_SERVER['DOCUMENT_ROOT'] . substr($file, 1);
} }
$file = realpath($file); $file = realpath($file);
if (file_exists($file)) { if (file_exists($file)) {
$sources[] = new Minify_Source(array( $sources[] = new Minify_Source(array(
'filepath' => $file 'filepath' => $file
)); ));
} else { } else {
// file doesn't exist // file doesn't exist
return $options; return $options;
} }
} }
if ($sources) { if ($sources) {
$this->sources = $sources; $this->sources = $sources;
} }
return $options; return $options;
} }
} }

View File

@@ -1,80 +1,80 @@
<?php <?php
/** /**
* Class Minify_Controller_Page * Class Minify_Controller_Page
* @package Minify * @package Minify
*/ */
require_once 'Minify/Controller/Base.php'; require_once 'Minify/Controller/Base.php';
/** /**
* Controller class for serving a single HTML page * Controller class for serving a single HTML page
* *
* @link http://code.google.com/p/minify/source/browse/trunk/web/examples/1/index.php#59 * @link http://code.google.com/p/minify/source/browse/trunk/web/examples/1/index.php#59
* @package Minify * @package Minify
* @author Stephen Clay <steve@mrclay.org> * @author Stephen Clay <steve@mrclay.org>
*/ */
class Minify_Controller_Page extends Minify_Controller_Base { class Minify_Controller_Page extends Minify_Controller_Base {
/** /**
* Set up source of HTML content * Set up source of HTML content
* *
* @param array $options controller and Minify options * @param array $options controller and Minify options
* @return array Minify options * @return array Minify options
* *
* Controller options: * Controller options:
* *
* 'content': (required) HTML markup * 'content': (required) HTML markup
* *
* 'id': (required) id of page (string for use in server-side caching) * 'id': (required) id of page (string for use in server-side caching)
* *
* 'lastModifiedTime': timestamp of when this content changed. This * 'lastModifiedTime': timestamp of when this content changed. This
* is recommended to allow both server and client-side caching. * is recommended to allow both server and client-side caching.
* *
* 'minifyAll': should all CSS and Javascript blocks be individually * 'minifyAll': should all CSS and Javascript blocks be individually
* minified? (default false) * minified? (default false)
* *
* @todo Add 'file' option to read HTML file. * @todo Add 'file' option to read HTML file.
*/ */
public function setupSources($options) { public function setupSources($options) {
// strip controller options // strip controller options
$sourceSpec = array( $sourceSpec = array(
'content' => $options['content'] 'content' => $options['content']
,'id' => $options['id'] ,'id' => $options['id']
); );
unset($options['content'], $options['id']); unset($options['content'], $options['id']);
if (isset($options['minifyAll'])) { if (isset($options['minifyAll'])) {
// this will be the 2nd argument passed to Minify_HTML::minify() // this will be the 2nd argument passed to Minify_HTML::minify()
$sourceSpec['minifyOptions'] = array( $sourceSpec['minifyOptions'] = array(
'cssMinifier' => array('Minify_CSS', 'minify') 'cssMinifier' => array('Minify_CSS', 'minify')
,'jsMinifier' => array('Minify_Javascript', 'minify') ,'jsMinifier' => array('Minify_Javascript', 'minify')
); );
$this->_loadCssJsMinifiers = true; $this->_loadCssJsMinifiers = true;
unset($options['minifyAll']); unset($options['minifyAll']);
} }
$this->sources[] = new Minify_Source($sourceSpec); $this->sources[] = new Minify_Source($sourceSpec);
// may not be needed // may not be needed
//$options['minifier'] = array('Minify_HTML', 'minify'); //$options['minifier'] = array('Minify_HTML', 'minify');
$options['contentType'] = Minify::TYPE_HTML; $options['contentType'] = Minify::TYPE_HTML;
return $options; return $options;
} }
protected $_loadCssJsMinifiers = false; protected $_loadCssJsMinifiers = false;
/** /**
* @see Minify_Controller_Base::loadMinifier() * @see Minify_Controller_Base::loadMinifier()
*/ */
public function loadMinifier($minifierCallback) public function loadMinifier($minifierCallback)
{ {
if ($this->_loadCssJsMinifiers) { if ($this->_loadCssJsMinifiers) {
// Minify will not call for these so we must manually load // Minify will not call for these so we must manually load
// them when Minify/HTML.php is called for. // them when Minify/HTML.php is called for.
require 'Minify/CSS.php'; require 'Minify/CSS.php';
require 'Minify/Javascript.php'; require 'Minify/Javascript.php';
} }
parent::loadMinifier($minifierCallback); // load Minify/HTML.php parent::loadMinifier($minifierCallback); // load Minify/HTML.php
} }
} }

View File

@@ -1,118 +1,118 @@
<?php <?php
/** /**
* Class Minify_Controller_Version1 * Class Minify_Controller_Version1
* @package Minify * @package Minify
*/ */
require_once 'Minify/Controller/Base.php'; require_once 'Minify/Controller/Base.php';
/** /**
* Controller class for emulating version 1 of minify.php * Controller class for emulating version 1 of minify.php
* *
* <code> * <code>
* Minify::serve('Version1'); * Minify::serve('Version1');
* </code> * </code>
* *
* @package Minify * @package Minify
* @author Stephen Clay <steve@mrclay.org> * @author Stephen Clay <steve@mrclay.org>
*/ */
class Minify_Controller_Version1 extends Minify_Controller_Base { class Minify_Controller_Version1 extends Minify_Controller_Base {
/** /**
* Set up groups of files as sources * Set up groups of files as sources
* *
* @param array $options controller and Minify options * @param array $options controller and Minify options
* @return array Minify options * @return array Minify options
* *
*/ */
public function setupSources($options) { public function setupSources($options) {
self::_setupDefines(); self::_setupDefines();
if (MINIFY_USE_CACHE) { if (MINIFY_USE_CACHE) {
$cacheDir = defined('MINIFY_CACHE_DIR') $cacheDir = defined('MINIFY_CACHE_DIR')
? MINIFY_CACHE_DIR ? MINIFY_CACHE_DIR
: ''; : '';
Minify::setCache($cacheDir); Minify::setCache($cacheDir);
} }
$options['badRequestHeader'] = 'HTTP/1.0 404 Not Found'; $options['badRequestHeader'] = 'HTTP/1.0 404 Not Found';
$options['contentTypeCharset'] = MINIFY_ENCODING; $options['contentTypeCharset'] = MINIFY_ENCODING;
// The following restrictions are to limit the URLs that minify will // The following restrictions are to limit the URLs that minify will
// respond to. Ideally there should be only one way to reference a file. // respond to. Ideally there should be only one way to reference a file.
if (! isset($_GET['files']) if (! isset($_GET['files'])
// verify at least one file, files are single comma separated, // verify at least one file, files are single comma separated,
// and are all same extension // and are all same extension
|| ! preg_match('/^[^,]+\\.(css|js)(,[^,]+\\.\\1)*$/', $_GET['files'], $m) || ! preg_match('/^[^,]+\\.(css|js)(,[^,]+\\.\\1)*$/', $_GET['files'], $m)
// no "//" (makes URL rewriting easier) // no "//" (makes URL rewriting easier)
|| strpos($_GET['files'], '//') !== false || strpos($_GET['files'], '//') !== false
// no "\" // no "\"
|| strpos($_GET['files'], '\\') !== false || strpos($_GET['files'], '\\') !== false
// no "./" // no "./"
|| preg_match('/(?:^|[^\\.])\\.\\//', $_GET['files']) || preg_match('/(?:^|[^\\.])\\.\\//', $_GET['files'])
) { ) {
return $options; return $options;
} }
$extension = $m[1]; $extension = $m[1];
$files = explode(',', $_GET['files']); $files = explode(',', $_GET['files']);
if (count($files) > MINIFY_MAX_FILES) { if (count($files) > MINIFY_MAX_FILES) {
return $options; return $options;
} }
// strings for prepending to relative/absolute paths // strings for prepending to relative/absolute paths
$prependRelPaths = dirname($_SERVER['SCRIPT_FILENAME']) $prependRelPaths = dirname($_SERVER['SCRIPT_FILENAME'])
. DIRECTORY_SEPARATOR; . DIRECTORY_SEPARATOR;
$prependAbsPaths = $_SERVER['DOCUMENT_ROOT']; $prependAbsPaths = $_SERVER['DOCUMENT_ROOT'];
$sources = array(); $sources = array();
$goodFiles = array(); $goodFiles = array();
$hasBadSource = false; $hasBadSource = false;
$allowDirs = isset($options['allowDirs']) $allowDirs = isset($options['allowDirs'])
? $options['allowDirs'] ? $options['allowDirs']
: MINIFY_BASE_DIR; : MINIFY_BASE_DIR;
foreach ($files as $file) { foreach ($files as $file) {
// prepend appropriate string for abs/rel paths // prepend appropriate string for abs/rel paths
$file = ($file[0] === '/' ? $prependAbsPaths : $prependRelPaths) . $file; $file = ($file[0] === '/' ? $prependAbsPaths : $prependRelPaths) . $file;
// make sure a real file! // make sure a real file!
$file = realpath($file); $file = realpath($file);
// don't allow unsafe or duplicate files // don't allow unsafe or duplicate files
if (parent::_fileIsSafe($file, $allowDirs) if (parent::_fileIsSafe($file, $allowDirs)
&& !in_array($file, $goodFiles)) && !in_array($file, $goodFiles))
{ {
$goodFiles[] = $file; $goodFiles[] = $file;
$srcOptions = array( $srcOptions = array(
'filepath' => $file 'filepath' => $file
); );
$this->sources[] = new Minify_Source($srcOptions); $this->sources[] = new Minify_Source($srcOptions);
} else { } else {
$hasBadSource = true; $hasBadSource = true;
break; break;
} }
} }
if ($hasBadSource) { if ($hasBadSource) {
$this->sources = array(); $this->sources = array();
} }
if (! MINIFY_REWRITE_CSS_URLS) { if (! MINIFY_REWRITE_CSS_URLS) {
$options['rewriteCssUris'] = false; $options['rewriteCssUris'] = false;
} }
return $options; return $options;
} }
private static function _setupDefines() private static function _setupDefines()
{ {
$defaults = array( $defaults = array(
'MINIFY_BASE_DIR' => realpath($_SERVER['DOCUMENT_ROOT']) 'MINIFY_BASE_DIR' => realpath($_SERVER['DOCUMENT_ROOT'])
,'MINIFY_ENCODING' => 'utf-8' ,'MINIFY_ENCODING' => 'utf-8'
,'MINIFY_MAX_FILES' => 16 ,'MINIFY_MAX_FILES' => 16
,'MINIFY_REWRITE_CSS_URLS' => true ,'MINIFY_REWRITE_CSS_URLS' => true
,'MINIFY_USE_CACHE' => true ,'MINIFY_USE_CACHE' => true
); );
foreach ($defaults as $const => $val) { foreach ($defaults as $const => $val) {
if (! defined($const)) { if (! defined($const)) {
define($const, $val); define($const, $val);
} }
} }
} }
} }

View File

@@ -1,88 +1,88 @@
<?php <?php
/** /**
* Class Minify_HTML * Class Minify_HTML
* @package Minify * @package Minify
*/ */
/** /**
* Compress HTML * Compress HTML
* *
* This is a heavy regex-based removal of whitespace, unnecessary comments and * This is a heavy regex-based removal of whitespace, unnecessary comments and
* tokens. IE conditional comments are preserved. There are also options to have * tokens. IE conditional comments are preserved. There are also options to have
* STYLE and SCRIPT blocks compressed by callback functions. * STYLE and SCRIPT blocks compressed by callback functions.
* *
* A test suite is available. * A test suite is available.
* *
* @package Minify * @package Minify
* @author Stephen Clay <steve@mrclay.org> * @author Stephen Clay <steve@mrclay.org>
*/ */
class Minify_HTML { class Minify_HTML {
/** /**
* "Minify" an HTML page * "Minify" an HTML page
* *
* @param string $html * @param string $html
* @param array $options * @param array $options
* @return string * @return string
*/ */
public static function minify($html, $options = array()) { public static function minify($html, $options = array()) {
if (isset($options['cssMinifier'])) { if (isset($options['cssMinifier'])) {
self::$_cssMinifier = $options['cssMinifier']; self::$_cssMinifier = $options['cssMinifier'];
} }
if (isset($options['jsMinifier'])) { if (isset($options['jsMinifier'])) {
self::$_jsMinifier = $options['jsMinifier']; self::$_jsMinifier = $options['jsMinifier'];
} }
$html = str_replace("\r\n", "\n", trim($html)); $html = str_replace("\r\n", "\n", trim($html));
self::$_isXhtml = (false !== strpos($html, '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML')); self::$_isXhtml = (false !== strpos($html, '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML'));
self::$_replacementHash = 'MINIFYHTML' . md5(time()); self::$_replacementHash = 'MINIFYHTML' . md5(time());
self::$_placeholders = array(); self::$_placeholders = array();
// replace SCRIPTs (and minify) with placeholders // replace SCRIPTs (and minify) with placeholders
$html = preg_replace_callback( $html = preg_replace_callback(
'/\\s*(<script\\b[^>]*?>)([\\s\\S]*?)<\\/script>\\s*/i' '/\\s*(<script\\b[^>]*?>)([\\s\\S]*?)<\\/script>\\s*/i'
,array('Minify_HTML', '_removeScriptCB') ,array('Minify_HTML', '_removeScriptCB')
,$html); ,$html);
// replace STYLEs (and minify) with placeholders // replace STYLEs (and minify) with placeholders
$html = preg_replace_callback( $html = preg_replace_callback(
'/\\s*(<style\\b[^>]*?>)([\\s\\S]*?)<\\/style>\\s*/i' '/\\s*(<style\\b[^>]*?>)([\\s\\S]*?)<\\/style>\\s*/i'
,array('Minify_HTML', '_removeStyleCB') ,array('Minify_HTML', '_removeStyleCB')
,$html); ,$html);
// remove HTML comments (but not IE conditional comments). // remove HTML comments (but not IE conditional comments).
$html = preg_replace('/<!--[^\\[][\\s\\S]*?-->/', '', $html); $html = preg_replace('/<!--[^\\[][\\s\\S]*?-->/', '', $html);
// replace PREs with placeholders // replace PREs with placeholders
$html = preg_replace_callback('/\\s*(<pre\\b[^>]*?>[\\s\\S]*?<\\/pre>)\\s*/i' $html = preg_replace_callback('/\\s*(<pre\\b[^>]*?>[\\s\\S]*?<\\/pre>)\\s*/i'
,array('Minify_HTML', '_removePreCB') ,array('Minify_HTML', '_removePreCB')
, $html); , $html);
// replace TEXTAREAs with placeholders // replace TEXTAREAs with placeholders
$html = preg_replace_callback( $html = preg_replace_callback(
'/\\s*(<textarea\\b[^>]*?>[\\s\\S]*?<\\/textarea>)\\s*/i' '/\\s*(<textarea\\b[^>]*?>[\\s\\S]*?<\\/textarea>)\\s*/i'
,array('Minify_HTML', '_removeTaCB') ,array('Minify_HTML', '_removeTaCB')
, $html); , $html);
// trim each line. // trim each line.
// @todo take into account attribute values that span multiple lines. // @todo take into account attribute values that span multiple lines.
$html = preg_replace('/^\\s+|\\s+$/m', '', $html); $html = preg_replace('/^\\s+|\\s+$/m', '', $html);
// remove ws around block/undisplayed elements // remove ws around block/undisplayed elements
$html = preg_replace('/\\s+(<\\/?(?:area|base(?:font)?|blockquote|body' $html = preg_replace('/\\s+(<\\/?(?:area|base(?:font)?|blockquote|body'
.'|caption|center|cite|col(?:group)?|dd|dir|div|dl|dt|fieldset|form' .'|caption|center|cite|col(?:group)?|dd|dir|div|dl|dt|fieldset|form'
.'|frame(?:set)?|h[1-6]|head|hr|html|legend|li|link|map|menu|meta' .'|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)' .'|ol|opt(?:group|ion)|p|param|t(?:able|body|head|d|h||r|foot|itle)'
.'|ul)\\b[^>]*>)/i', '$1', $html); .'|ul)\\b[^>]*>)/i', '$1', $html);
// remove ws outside of all elements // remove ws outside of all elements
$html = preg_replace_callback( $html = preg_replace_callback(
'/>([^<]+)</' '/>([^<]+)</'
,array('Minify_HTML', '_outsideTagCB') ,array('Minify_HTML', '_outsideTagCB')
,$html); ,$html);
// fill placeholders // fill placeholders
$html = str_replace( $html = str_replace(
@@ -90,13 +90,13 @@ class Minify_HTML {
,array_values(self::$_placeholders) ,array_values(self::$_placeholders)
,$html ,$html
); );
self::$_placeholders = array(); self::$_placeholders = array();
// use newlines before 1st attribute in open tags (to limit line lengths) // use newlines before 1st attribute in open tags (to limit line lengths)
$html = preg_replace('/(<[a-z\\-]+)\\s+([^>]+>)/i', "$1\n$2", $html); $html = preg_replace('/(<[a-z\\-]+)\\s+([^>]+>)/i', "$1\n$2", $html);
self::$_cssMinifier = self::$_jsMinifier = null; self::$_cssMinifier = self::$_jsMinifier = null;
return $html; return $html;
} }
protected static function _reservePlace($content) protected static function _reservePlace($content)
@@ -104,83 +104,83 @@ class Minify_HTML {
$placeholder = '%' . self::$_replacementHash . count(self::$_placeholders) . '%'; $placeholder = '%' . self::$_replacementHash . count(self::$_placeholders) . '%';
self::$_placeholders[$placeholder] = $content; self::$_placeholders[$placeholder] = $content;
return $placeholder; return $placeholder;
} }
protected static $_isXhtml = false; protected static $_isXhtml = false;
protected static $_replacementHash = null; protected static $_replacementHash = null;
protected static $_placeholders = array(); protected static $_placeholders = array();
protected static $_cssMinifier = null; protected static $_cssMinifier = null;
protected static $_jsMinifier = null; protected static $_jsMinifier = null;
protected static function _outsideTagCB($m) protected static function _outsideTagCB($m)
{ {
return '>' . preg_replace('/^\\s+|\\s+$/', ' ', $m[1]) . '<'; return '>' . preg_replace('/^\\s+|\\s+$/', ' ', $m[1]) . '<';
} }
protected static function _removePreCB($m) protected static function _removePreCB($m)
{ {
return self::_reservePlace($m[1]); return self::_reservePlace($m[1]);
} }
protected static function _removeTaCB($m) protected static function _removeTaCB($m)
{ {
return self::_reservePlace($m[1]); return self::_reservePlace($m[1]);
} }
protected static function _removeStyleCB($m) protected static function _removeStyleCB($m)
{ {
$openStyle = $m[1]; $openStyle = $m[1];
$css = $m[2]; $css = $m[2];
// remove HTML comments // remove HTML comments
$css = preg_replace('/(?:^\\s*<!--|-->\\s*$)/', '', $css); $css = preg_replace('/(?:^\\s*<!--|-->\\s*$)/', '', $css);
// remove CDATA section markers // remove CDATA section markers
$css = self::_removeCdata($css); $css = self::_removeCdata($css);
// minify // minify
$minifier = self::$_cssMinifier $minifier = self::$_cssMinifier
? self::$_cssMinifier ? self::$_cssMinifier
: 'trim'; : 'trim';
$css = call_user_func($minifier, $css); $css = call_user_func($minifier, $css);
return self::_reservePlace(self::_needsCdata($css) return self::_reservePlace(self::_needsCdata($css)
? "{$openStyle}/*<![CDATA[*/{$css}/*]]>*/</style>" ? "{$openStyle}/*<![CDATA[*/{$css}/*]]>*/</style>"
: "{$openStyle}{$css}</style>" : "{$openStyle}{$css}</style>"
); );
} }
protected static function _removeScriptCB($m) protected static function _removeScriptCB($m)
{ {
$openScript = $m[1]; $openScript = $m[1];
$js = $m[2]; $js = $m[2];
// remove HTML comments (and ending "//" if present) // remove HTML comments (and ending "//" if present)
$js = preg_replace('/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $js); $js = preg_replace('/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $js);
// remove CDATA section markers // remove CDATA section markers
$js = self::_removeCdata($js); $js = self::_removeCdata($js);
// minify // minify
$minifier = self::$_jsMinifier $minifier = self::$_jsMinifier
? self::$_jsMinifier ? self::$_jsMinifier
: 'trim'; : 'trim';
$js = call_user_func($minifier, $js); $js = call_user_func($minifier, $js);
return self::_reservePlace(self::_needsCdata($js) return self::_reservePlace(self::_needsCdata($js)
? "{$openScript}/*<![CDATA[*/{$js}/*]]>*/</script>" ? "{$openScript}/*<![CDATA[*/{$js}/*]]>*/</script>"
: "{$openScript}{$js}</script>" : "{$openScript}{$js}</script>"
); );
} }
protected static function _removeCdata($str) protected static function _removeCdata($str)
{ {
return (false !== strpos($str, '<![CDATA[')) return (false !== strpos($str, '<![CDATA['))
? str_replace(array('<![CDATA[', ']]>'), '', $str) ? str_replace(array('<![CDATA[', ']]>'), '', $str)
: $str; : $str;
} }
protected static function _needsCdata($str) protected static function _needsCdata($str)
{ {
return (self::$_isXhtml && preg_match('/(?:[<&]|\\-\\-|\\]\\]>)/', $str)); return (self::$_isXhtml && preg_match('/(?:[<&]|\\-\\-|\\]\\]>)/', $str));
} }
} }

View File

@@ -1,74 +1,74 @@
<?php <?php
/** /**
* Class Minify_Javascript * Class Minify_Javascript
* @package Minify * @package Minify
*/ */
require 'JSMin.php'; require 'JSMin.php';
/** /**
* Compress Javascript using Ryan Grove's JSMin class * Compress Javascript using Ryan Grove's JSMin class
* *
* @package Minify * @package Minify
* @author Stephen Clay <steve@mrclay.org> * @author Stephen Clay <steve@mrclay.org>
*/ */
class Minify_Javascript { class Minify_Javascript {
/** /**
* Minify a Javascript string * Minify a Javascript string
* *
* @param string $js * @param string $js
* *
* @param array $options available options: * @param array $options available options:
* *
* 'preserveComments': (default true) multi-line comments that begin * 'preserveComments': (default true) multi-line comments that begin
* with "/*!" will be preserved with newlines before and after to * with "/*!" will be preserved with newlines before and after to
* enhance readability. * enhance readability.
* *
* @return string * @return string
*/ */
public static function minify($js, $options = array()) public static function minify($js, $options = array())
{ {
if (isset($options['preserveComments']) if (isset($options['preserveComments'])
&& !$options['preserveComments']) { && !$options['preserveComments']) {
return trim(JSMin::minify($js)); return trim(JSMin::minify($js));
} }
$ret = ''; $ret = '';
while (1) { while (1) {
list($beforeComment, $comment, $afterComment) list($beforeComment, $comment, $afterComment)
= self::_nextYuiComment($js); = self::_nextYuiComment($js);
$ret .= trim(JSMin::minify($beforeComment)); $ret .= trim(JSMin::minify($beforeComment));
if (false === $comment) { if (false === $comment) {
break; break;
} }
$ret .= $comment; $ret .= $comment;
$js = $afterComment; $js = $afterComment;
} }
return $ret; return $ret;
} }
/** /**
* Extract comments that YUI Compressor preserves. * Extract comments that YUI Compressor preserves.
* *
* @param string $in input * @param string $in input
* *
* @return array 3 elements are returned. If a YUI comment is found, the * @return array 3 elements are returned. If a YUI comment is found, the
* 2nd element is the comment and the 1st and 2nd are the surrounding * 2nd element is the comment and the 1st and 2nd are the surrounding
* strings. If no comment is found, the entire string is returned as the * strings. If no comment is found, the entire string is returned as the
* 1st element and the other two are false. * 1st element and the other two are false.
*/ */
private static function _nextYuiComment($in) private static function _nextYuiComment($in)
{ {
return ( return (
(false !== ($start = strpos($in, '/*!'))) (false !== ($start = strpos($in, '/*!')))
&& (false !== ($end = strpos($in, '*/', $start + 3))) && (false !== ($end = strpos($in, '*/', $start + 3)))
) )
? array( ? array(
substr($in, 0, $start) substr($in, 0, $start)
,"\n/*" . substr($in, $start + 3, $end - $start - 1) . "\n" ,"\n/*" . substr($in, $start + 3, $end - $start - 1) . "\n"
,substr($in, -(strlen($in) - $end - 2)) ,substr($in, -(strlen($in) - $end - 2))
) )
: array($in, false, false); : array($in, false, false);
} }
} }

View File

@@ -1,124 +1,124 @@
<?php <?php
/** /**
* Class Minify_Lines * Class Minify_Lines
* @package Minify * @package Minify
*/ */
/** /**
* Add line numbers in C-style comments for easier debugging of combined content * Add line numbers in C-style comments for easier debugging of combined content
* *
* @package Minify * @package Minify
* @author Stephen Clay <steve@mrclay.org> * @author Stephen Clay <steve@mrclay.org>
*/ */
class Minify_Lines { class Minify_Lines {
/** /**
* Add line numbers in C-style comments * Add line numbers in C-style comments
* *
* This uses a very basic parser easily fooled by comment tokens inside * This uses a very basic parser easily fooled by comment tokens inside
* strings or regexes, but, otherwise, generally clean code will not be * strings or regexes, but, otherwise, generally clean code will not be
* mangled. * mangled.
* *
* @param string $content * @param string $content
* *
* @param array $options available options: * @param array $options available options:
* *
* 'id': (optional) string to identify file. E.g. file name/path * 'id': (optional) string to identify file. E.g. file name/path
* *
* @return string * @return string
*/ */
public static function minify($content, $options = array()) public static function minify($content, $options = array())
{ {
$id = (isset($options['id']) && $options['id']) $id = (isset($options['id']) && $options['id'])
? $options['id'] ? $options['id']
: ''; : '';
if (! $eol = self::_getEol($content)) { if (! $eol = self::_getEol($content)) {
return $content; return $content;
} }
$lines = explode($eol, $content); $lines = explode($eol, $content);
$numLines = count($lines); $numLines = count($lines);
// determine left padding // determine left padding
$padTo = strlen($numLines); $padTo = strlen($numLines);
$inComment = false; $inComment = false;
$i = 0; $i = 0;
$newLines = array(); $newLines = array();
while (null !== ($line = array_shift($lines))) { while (null !== ($line = array_shift($lines))) {
if (('' !== $id) && (0 == $i % 50)) { if (('' !== $id) && (0 == $i % 50)) {
array_push($newLines, '', "/* {$id} */", ''); array_push($newLines, '', "/* {$id} */", '');
} }
++$i; ++$i;
$newLines[] = self::_addNote($line, $i, $inComment, $padTo); $newLines[] = self::_addNote($line, $i, $inComment, $padTo);
$inComment = self::_eolInComment($line, $inComment); $inComment = self::_eolInComment($line, $inComment);
} }
return implode($eol, $newLines) . $eol; return implode($eol, $newLines) . $eol;
} }
/** /**
* Determine EOL character sequence * Determine EOL character sequence
* *
* @param string $str file content * @param string $str file content
* *
* @return string EOL char(s) or '' if no EOL could be found * @return string EOL char(s) or '' if no EOL could be found
*/ */
private static function _getEol($str) private static function _getEol($str)
{ {
$r = strpos($str, "\r"); $r = strpos($str, "\r");
$n = strpos($str, "\n"); $n = strpos($str, "\n");
if (false === $r && false === $n) { if (false === $r && false === $n) {
return ''; return '';
} }
return ($r !== false) return ($r !== false)
? ($n == ($r + 1) ? ($n == ($r + 1)
? "\r\n" ? "\r\n"
: "\r") : "\r")
: "\n"; : "\n";
} }
/** /**
* Is the parser within a C-style comment at the end of this line? * Is the parser within a C-style comment at the end of this line?
* *
* @param string $line current line of code * @param string $line current line of code
* *
* @param bool $inComment was the parser in a comment at the * @param bool $inComment was the parser in a comment at the
* beginning of the line? * beginning of the line?
* *
* @return bool * @return bool
*/ */
private static function _eolInComment($line, $inComment) private static function _eolInComment($line, $inComment)
{ {
while (strlen($line)) { while (strlen($line)) {
$search = $inComment $search = $inComment
? '*/' ? '*/'
: '/*'; : '/*';
$pos = strpos($line, $search); $pos = strpos($line, $search);
if (false === $pos) { if (false === $pos) {
return $inComment; return $inComment;
} else { } else {
$inComment = ! $inComment; $inComment = ! $inComment;
$line = substr($line, $pos + 2); $line = substr($line, $pos + 2);
} }
} }
return $inComment; return $inComment;
} }
/** /**
* Prepend a comment (or note) to the given line * Prepend a comment (or note) to the given line
* *
* @param string $line current line of code * @param string $line current line of code
* *
* @param string $note content of note/comment * @param string $note content of note/comment
* *
* @param bool $inComment was the parser in a comment at the * @param bool $inComment was the parser in a comment at the
* beginning of the line? * beginning of the line?
* *
* @param int $padTo minimum width of comment * @param int $padTo minimum width of comment
* *
* @return string * @return string
*/ */
private static function _addNote($line, $note, $inComment, $padTo) private static function _addNote($line, $note, $inComment, $padTo)
{ {
return $inComment return $inComment
? '/* ' . str_pad($note, $padTo, ' ', STR_PAD_RIGHT) . ' *| ' . $line ? '/* ' . str_pad($note, $padTo, ' ', STR_PAD_RIGHT) . ' *| ' . $line
: '/* ' . str_pad($note, $padTo, ' ', STR_PAD_RIGHT) . ' */ ' . $line; : '/* ' . str_pad($note, $padTo, ' ', STR_PAD_RIGHT) . ' */ ' . $line;
} }
} }

View File

@@ -1,177 +1,177 @@
<?php <?php
/** /**
* Class Minify_Source * Class Minify_Source
* @package Minify * @package Minify
*/ */
/** /**
* A content source to be minified by Minify. * A content source to be minified by Minify.
* *
* This allows per-source minification options and the mixing of files with * This allows per-source minification options and the mixing of files with
* content from other sources. * content from other sources.
* *
* @package Minify * @package Minify
* @author Stephen Clay <steve@mrclay.org> * @author Stephen Clay <steve@mrclay.org>
*/ */
class Minify_Source { class Minify_Source {
/** /**
* @var int time of last modification * @var int time of last modification
*/ */
public $lastModified = null; public $lastModified = null;
/** /**
* @var callback minifier function specifically for this source. * @var callback minifier function specifically for this source.
*/ */
public $minifier = null; public $minifier = null;
/** /**
* @var array minification options specific to this source. * @var array minification options specific to this source.
*/ */
public $minifyOptions = null; public $minifyOptions = null;
/** /**
* @var string full path of file * @var string full path of file
*/ */
public $filepath = null; public $filepath = null;
/** /**
* Create a Minify_Source * Create a Minify_Source
* *
* In the $spec array(), you can either provide a 'filepath' to an existing * 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 * file (existence will not be checked!) or give 'id' (unique string for
* the content), 'content' (the string content) and 'lastModified' * the content), 'content' (the string content) and 'lastModified'
* (unixtime of last update). * (unixtime of last update).
* *
* As a shortcut, the controller will replace "//" at the beginning * As a shortcut, the controller will replace "//" at the beginning
* of a filepath with $_SERVER['DOCUMENT_ROOT'] . '/'. * of a filepath with $_SERVER['DOCUMENT_ROOT'] . '/'.
* *
* @param array $spec options * @param array $spec options
*/ */
public function __construct($spec) public function __construct($spec)
{ {
if (isset($spec['filepath'])) { if (isset($spec['filepath'])) {
if (0 === strpos($spec['filepath'], '//')) { if (0 === strpos($spec['filepath'], '//')) {
$spec['filepath'] = $_SERVER['DOCUMENT_ROOT'] . substr($spec['filepath'], 1); $spec['filepath'] = $_SERVER['DOCUMENT_ROOT'] . substr($spec['filepath'], 1);
} }
$this->filepath = $spec['filepath']; $this->filepath = $spec['filepath'];
$this->_id = $spec['filepath']; $this->_id = $spec['filepath'];
$this->lastModified = filemtime($spec['filepath']) $this->lastModified = filemtime($spec['filepath'])
// offset for Windows uploaders with out of sync clocks // offset for Windows uploaders with out of sync clocks
+ round(Minify::$uploaderHoursBehind * 3600); + round(Minify::$uploaderHoursBehind * 3600);
} elseif (isset($spec['id'])) { } elseif (isset($spec['id'])) {
$this->_id = 'id::' . $spec['id']; $this->_id = 'id::' . $spec['id'];
if (isset($spec['content'])) { if (isset($spec['content'])) {
$this->_content = $spec['content']; $this->_content = $spec['content'];
} else { } else {
$this->_getContentFunc = $spec['getContentFunc']; $this->_getContentFunc = $spec['getContentFunc'];
} }
$this->lastModified = isset($spec['lastModified']) $this->lastModified = isset($spec['lastModified'])
? $spec['lastModified'] ? $spec['lastModified']
: time(); : time();
} }
if (isset($spec['minifier'])) { if (isset($spec['minifier'])) {
$this->minifier = $spec['minifier']; $this->minifier = $spec['minifier'];
} }
if (isset($spec['minifyOptions'])) { if (isset($spec['minifyOptions'])) {
$this->minifyOptions = $spec['minifyOptions']; $this->minifyOptions = $spec['minifyOptions'];
} }
} }
/** /**
* Get content * Get content
* *
* @return string * @return string
*/ */
public function getContent() public function getContent()
{ {
$content = (null !== $this->filepath) $content = (null !== $this->filepath)
? file_get_contents($this->filepath) ? file_get_contents($this->filepath)
: ((null !== $this->_content) : ((null !== $this->_content)
? $this->_content ? $this->_content
: call_user_func($this->_getContentFunc, $this->_id) : call_user_func($this->_getContentFunc, $this->_id)
); );
// remove UTF-8 BOM if present // remove UTF-8 BOM if present
return (pack("CCC",0xef,0xbb,0xbf) === substr($content, 0, 3)) return (pack("CCC",0xef,0xbb,0xbf) === substr($content, 0, 3))
? substr($content, 3) ? substr($content, 3)
: $content; : $content;
} }
/** /**
* Get id * Get id
* *
* @return string * @return string
*/ */
public function getId() public function getId()
{ {
return $this->_id; return $this->_id;
} }
/** /**
* Verifies a single minification call can handle all sources * Verifies a single minification call can handle all sources
* *
* @param array $sources Minify_Source instances * @param array $sources Minify_Source instances
* *
* @return bool true iff there no sources with specific minifier preferences. * @return bool true iff there no sources with specific minifier preferences.
*/ */
public static function haveNoMinifyPrefs($sources) public static function haveNoMinifyPrefs($sources)
{ {
foreach ($sources as $source) { foreach ($sources as $source) {
if (null !== $source->minifier if (null !== $source->minifier
|| null !== $source->minifyOptions) { || null !== $source->minifyOptions) {
return false; return false;
} }
} }
return true; return true;
} }
/** /**
* Get unique string for a set of sources * Get unique string for a set of sources
* *
* @param array $sources Minify_Source instances * @param array $sources Minify_Source instances
* *
* @return string * @return string
*/ */
public static function getDigest($sources) public static function getDigest($sources)
{ {
foreach ($sources as $source) { foreach ($sources as $source) {
$info[] = array( $info[] = array(
$source->_id, $source->minifier, $source->minifyOptions $source->_id, $source->minifier, $source->minifyOptions
); );
} }
return md5(serialize($info)); return md5(serialize($info));
} }
/** /**
* Guess content type from the first filename extension available * Guess content type from the first filename extension available
* *
* This is called if the user doesn't pass in a 'contentType' options * This is called if the user doesn't pass in a 'contentType' options
* *
* @param array $sources Minify_Source instances * @param array $sources Minify_Source instances
* *
* @return string content type. e.g. 'text/css' * @return string content type. e.g. 'text/css'
*/ */
public static function getContentType($sources) public static function getContentType($sources)
{ {
$exts = array( $exts = array(
'css' => Minify::TYPE_CSS 'css' => Minify::TYPE_CSS
,'js' => Minify::TYPE_JS ,'js' => Minify::TYPE_JS
,'html' => Minify::TYPE_HTML ,'html' => Minify::TYPE_HTML
); );
foreach ($sources as $source) { foreach ($sources as $source) {
if (null !== $source->filepath) { if (null !== $source->filepath) {
$segments = explode('.', $source->filepath); $segments = explode('.', $source->filepath);
$ext = array_pop($segments); $ext = array_pop($segments);
if (isset($exts[$ext])) { if (isset($exts[$ext])) {
return $exts[$ext]; return $exts[$ext];
} }
} }
} }
return 'text/plain'; return 'text/plain';
} }
protected $_content = null; protected $_content = null;
protected $_getContentFunc = null; protected $_getContentFunc = null;
protected $_id = null; protected $_id = null;
} }

View File

@@ -1,31 +1,31 @@
<?php <?php
/** /**
* Function min_group_uri() * Function min_group_uri()
* *
* @package Minify * @package Minify
*/ */
require_once dirname(__FILE__) . '/lib/Minify/Build.php'; require_once dirname(__FILE__) . '/lib/Minify/Build.php';
/** /**
* Get a timestamped URI to a minified resource using the default Minify install * Get a timestamped URI to a minified resource using the default Minify install
* *
* <code> * <code>
* <link rel="stylesheet" type="text/css" href="<?php min_group_uri('css'); ?>" /> * <link rel="stylesheet" type="text/css" href="<?php min_group_uri('css'); ?>" />
* <script type="text/javascript" src="<?php min_group_uri('js'); ?>"></script> * <script type="text/javascript" src="<?php min_group_uri('js'); ?>"></script>
* </code> * </code>
* *
* @param string $group a key of the array in groupsConfig.php * @param string $group a key of the array in groupsConfig.php
* @param string $ampersand '&' or '&amp;' (default '&amp;') * @param string $ampersand '&' or '&amp;' (default '&amp;')
* @return string * @return string
*/ */
function min_group_uri($group, $ampersand = '&amp;') function min_group_uri($group, $ampersand = '&amp;')
{ {
static $gc = false; static $gc = false;
if (false === $gc) { if (false === $gc) {
$gc = (require dirname(__FILE__) . '/groupsConfig.php'); $gc = (require dirname(__FILE__) . '/groupsConfig.php');
} }
$b = new Minify_Build($gc[$group]); $b = new Minify_Build($gc[$group]);
Minify_Build::$ampersand = $ampersand; Minify_Build::$ampersand = $ampersand;
return $b->uri('/' . basename(dirname(__FILE__)) . '/?g=' . $group); return $b->uri('/' . basename(dirname(__FILE__)) . '/?g=' . $group);
} }

View File

@@ -1,25 +1,25 @@
<?php <?php
/** /**
* The goal with this file is to benchmark serving the file doing the absolute * The goal with this file is to benchmark serving the file doing the absolute
* least operations possible. E.g. we know we'll have to check for the file, * least operations possible. E.g. we know we'll have to check for the file,
* check its size and the mtimes of it and the src file. * check its size and the mtimes of it and the src file.
*/ */
$src = realpath(dirname(__FILE__) . '/../minify/before.js'); $src = realpath(dirname(__FILE__) . '/../minify/before.js');
$cached = realpath(dirname(__FILE__) . '/../type-map') . '/before.js.zd'; $cached = realpath(dirname(__FILE__) . '/../type-map') . '/before.js.zd';
// clearstatcache() takes over 2ms on Athlon 64 X2 5600+! Avoid at all costs! // clearstatcache() takes over 2ms on Athlon 64 X2 5600+! Avoid at all costs!
//clearstatcache(); //clearstatcache();
filemtime($src); filemtime($src);
file_exists($cached); file_exists($cached);
filemtime($cached); filemtime($cached);
header('Cache-Control: public, max-age=31536000'); header('Cache-Control: public, max-age=31536000');
header('Expires: '. gmdate('D, d M Y H:i:s \G\M\T', $_SERVER['REQUEST_TIME'] + (86400 * 365))); header('Expires: '. gmdate('D, d M Y H:i:s \G\M\T', $_SERVER['REQUEST_TIME'] + (86400 * 365)));
header('Content-Type: application/x-javascript; charset=utf-8'); header('Content-Type: application/x-javascript; charset=utf-8');
header('Content-Encoding: deflate'); header('Content-Encoding: deflate');
header('Content-Length: ' . filesize($cached)); header('Content-Length: ' . filesize($cached));
header('Vary: Accept-Encoding'); header('Vary: Accept-Encoding');
readfile($cached); readfile($cached);

View File

@@ -1,15 +1,15 @@
<?php <?php
require '../../config.php'; require '../../config.php';
require 'Minify.php'; require 'Minify.php';
// give an explicit path to avoid having to load Solar/Dir.php // give an explicit path to avoid having to load Solar/Dir.php
Minify::setCache($minifyCachePath); Minify::setCache($minifyCachePath);
Minify::serve('Files', array( Minify::serve('Files', array(
'files' => array( 'files' => array(
dirname(__FILE__) . '/before.js' dirname(__FILE__) . '/before.js'
) )
,'setExpires' => $_SERVER['REQUEST_TIME'] + 31536000 // 1 yr ,'setExpires' => $_SERVER['REQUEST_TIME'] + 31536000 // 1 yr
)); ));

View File

@@ -1,15 +1,15 @@
<?php <?php
require '../../config.php'; require '../../config.php';
require 'Minify.php'; require 'Minify.php';
// give an explicit path to avoid having to load Solar/Dir.php // give an explicit path to avoid having to load Solar/Dir.php
Minify::setCache($minifyCachePath); Minify::setCache($minifyCachePath);
Minify::serve('Groups', array( Minify::serve('Groups', array(
'groups' => array( 'groups' => array(
'test' => array(dirname(__FILE__) . '/before.js') 'test' => array(dirname(__FILE__) . '/before.js')
) )
,'setExpires' => $_SERVER['REQUEST_TIME'] + 31536000 // 1 yr ,'setExpires' => $_SERVER['REQUEST_TIME'] + 31536000 // 1 yr
)); ));

View File

@@ -1,12 +1,12 @@
<?php <?php
require '../../config.php'; require '../../config.php';
define('MINIFY_BASE_DIR', realpath( define('MINIFY_BASE_DIR', realpath(
dirname(__FILE__) . '/../minify' dirname(__FILE__) . '/../minify'
)); ));
define('MINIFY_CACHE_DIR', $minifyCachePath); define('MINIFY_CACHE_DIR', $minifyCachePath);
require 'Minify.php'; require 'Minify.php';
Minify::serve('Version1'); Minify::serve('Version1');

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +1,17 @@
<?php <?php
/** /**
* Add the location of Minify's "lib" directory to the include_path. In * Add the location of Minify's "lib" directory to the include_path. In
* production this could be done via .htaccess or some other method. * production this could be done via .htaccess or some other method.
*/ */
ini_set('include_path', ini_set('include_path',
dirname(__FILE__) . '/../min/lib' dirname(__FILE__) . '/../min/lib'
. PATH_SEPARATOR . ini_get('include_path') . PATH_SEPARATOR . ini_get('include_path')
); );
/** /**
* Set $minifyCachePath to a PHP-writeable path to enable server-side caching * Set $minifyCachePath to a PHP-writeable path to enable server-side caching
* in all examples and tests. * in all examples and tests.
*/ */
$minifyCachePath = 'C:/xampp/tmp'; // ''; $minifyCachePath = 'C:/xampp/tmp'; // '';

View File

@@ -1,11 +1,11 @@
<?php <?php
$base = realpath(dirname(__FILE__) . '/..'); $base = realpath(dirname(__FILE__) . '/..');
$groupsSources = array( $groupsSources = array(
'js' => array( 'js' => array(
"{$base}/jquery-1.2.3.js" "{$base}/jquery-1.2.3.js"
,"{$base}/test space.js" ,"{$base}/test space.js"
) )
,'css' => array("{$base}/test.css") ,'css' => array("{$base}/test.css")
); );
unset($base); unset($base);

View File

@@ -1,14 +1,14 @@
<?php <?php
require '../../config.php'; require '../../config.php';
require '_groupsSources.php'; require '_groupsSources.php';
require 'Minify.php'; require 'Minify.php';
if ($minifyCachePath) { if ($minifyCachePath) {
Minify::setCache($minifyCachePath); Minify::setCache($minifyCachePath);
} }
Minify::serve('Groups', array( Minify::serve('Groups', array(
'groups' => $groupsSources 'groups' => $groupsSources
,'setExpires' => time() + 86400 * 365 ,'setExpires' => time() + 86400 * 365
)); ));

View File

@@ -1,11 +1,11 @@
<?php <?php
$base = realpath(dirname(__FILE__) . '/..'); $base = realpath(dirname(__FILE__) . '/..');
$groupsSources = array( $groupsSources = array(
'js' => array( 'js' => array(
"{$base}/jquery-1.2.3.js" "{$base}/jquery-1.2.3.js"
,"{$base}/test space.js" ,"{$base}/test space.js"
) )
,'css' => array("{$base}/test.css") ,'css' => array("{$base}/test.css")
); );
unset($base); unset($base);

View File

@@ -1,14 +1,14 @@
<?php <?php
require '../../config.php'; require '../../config.php';
require '_groupsSources.php'; require '_groupsSources.php';
require 'Minify.php'; require 'Minify.php';
if ($minifyCachePath) { if ($minifyCachePath) {
Minify::setCache($minifyCachePath); Minify::setCache($minifyCachePath);
} }
Minify::serve('Groups', array( Minify::serve('Groups', array(
'groups' => $groupsSources 'groups' => $groupsSources
,'setExpires' => time() + 86400 * 365 ,'setExpires' => time() + 86400 * 365
)); ));

View File

@@ -1,44 +1,44 @@
<?php <?php
/** /**
* This script will serve a single js/css file in this directory. Here we place * This script will serve a single js/css file in this directory. Here we place
* the front-end-controller logic in user code, then use the "Files" controller * the front-end-controller logic in user code, then use the "Files" controller
* to minify the file. Alternately, we could have created a custom controller * to minify the file. Alternately, we could have created a custom controller
* with the same logic and passed it to Minify::handleRequest(). * with the same logic and passed it to Minify::handleRequest().
*/ */
require '../../config.php'; require '../../config.php';
/** /**
* The Files controller only "knows" HTML, CSS, and JS files. Other files * The Files controller only "knows" HTML, CSS, and JS files. Other files
* would only be trim()ed and sent as plain/text. * would only be trim()ed and sent as plain/text.
*/ */
$serveExtensions = array('css', 'js'); $serveExtensions = array('css', 'js');
// serve // serve
if (isset($_GET['f'])) { if (isset($_GET['f'])) {
$filename = basename($_GET['f']); // remove any naughty bits $filename = basename($_GET['f']); // remove any naughty bits
$filenamePattern = '/[^\'"\\/\\\\]+\\.(?:' $filenamePattern = '/[^\'"\\/\\\\]+\\.(?:'
.implode('|', $serveExtensions). ')$/'; .implode('|', $serveExtensions). ')$/';
if (preg_match($filenamePattern, $filename) if (preg_match($filenamePattern, $filename)
&& file_exists(dirname(__FILE__) . '/../' . $filename)) { && file_exists(dirname(__FILE__) . '/../' . $filename)) {
require 'Minify.php'; require 'Minify.php';
if ($minifyCachePath) { if ($minifyCachePath) {
Minify::setCache($minifyCachePath); Minify::setCache($minifyCachePath);
} }
// The Files controller can serve an array of files, but here we just // The Files controller can serve an array of files, but here we just
// need one. // need one.
Minify::serve('Files', array( Minify::serve('Files', array(
// controller will cast a string to an array for you // controller will cast a string to an array for you
'files' => dirname(__FILE__) . '/../' . $filename 'files' => dirname(__FILE__) . '/../' . $filename
)); ));
exit(); exit();
} }
} }
header("HTTP/1.0 404 Not Found"); header("HTTP/1.0 404 Not Found");
echo "HTTP/1.0 404 Not Found"; echo "HTTP/1.0 404 Not Found";

View File

@@ -1,44 +1,44 @@
<?php <?php
require '../../config.php'; require '../../config.php';
require 'HTTP/ConditionalGet.php'; require 'HTTP/ConditionalGet.php';
// emulate regularly updating document // emulate regularly updating document
$every = 20; $every = 20;
$lastModified = round(time()/$every)*$every - $every; $lastModified = round(time()/$every)*$every - $every;
$cg = new HTTP_ConditionalGet(array( $cg = new HTTP_ConditionalGet(array(
'lastModifiedTime' => $lastModified 'lastModifiedTime' => $lastModified
)); ));
if ($cg->cacheIsValid) { if ($cg->cacheIsValid) {
$cg->sendHeaders(); $cg->sendHeaders();
// we're done // we're done
exit(); exit();
} }
// generate content // generate content
$title = 'Last-Modified is known : add Content-Length'; $title = 'Last-Modified is known : add Content-Length';
$explain = ' $explain = '
<p>Here, like <a href="./">the first example</a>, we know the Last-Modified time, <p>Here, like <a href="./">the first example</a>, we know the Last-Modified time,
but we also want to set the Content-Length to increase cacheability and allow but we also want to set the Content-Length to increase cacheability and allow
HTTP persistent connections. Instead of sending headers immediately, we first HTTP persistent connections. Instead of sending headers immediately, we first
generate our content, then use <code>setContentLength(strlen($content))</code> generate our content, then use <code>setContentLength(strlen($content))</code>
to add the header. Then finally call <code>sendHeaders()</code> and send the to add the header. Then finally call <code>sendHeaders()</code> and send the
content.</p> content.</p>
<p><strong>Note:</strong> This is not required if your PHP config buffers all <p><strong>Note:</strong> This is not required if your PHP config buffers all
output and your script doesn\'t do any incremental flushing of the output output and your script doesn\'t do any incremental flushing of the output
buffer. PHP will generally set Content-Length for you if it can.</p> buffer. PHP will generally set Content-Length for you if it can.</p>
<p>This script emulates a document that changes every ' .$every. ' seconds. <p>This script emulates a document that changes every ' .$every. ' seconds.
<br>This is version: ' . date('r', $lastModified) . '</p> <br>This is version: ' . date('r', $lastModified) . '</p>
'; ';
require '_include.php'; require '_include.php';
$content = get_content(array( $content = get_content(array(
'title' => $title 'title' => $title
,'explain' => $explain ,'explain' => $explain
)); ));
$cg->setContentLength(strlen($content)); $cg->setContentLength(strlen($content));
$cg->sendHeaders(); $cg->sendHeaders();
send_slowly($content); send_slowly($content);

View File

@@ -1,27 +1,27 @@
<?php <?php
require '../../config.php'; require '../../config.php';
require 'HTTP/ConditionalGet.php'; require 'HTTP/ConditionalGet.php';
// far expires // far expires
$cg = new HTTP_ConditionalGet(array( $cg = new HTTP_ConditionalGet(array(
'maxAge' => 20 'maxAge' => 20
,'lastModifiedTime' => filemtime(__FILE__) ,'lastModifiedTime' => filemtime(__FILE__)
)); ));
$cg->sendHeaders(); $cg->sendHeaders();
// generate, send content // generate, send content
$title = 'Last-Modified + Expires'; $title = 'Last-Modified + Expires';
$explain = ' $explain = '
<p>Here we set a static "lastModifiedTime" and "maxAge" to 20. The browser <p>Here we set a static "lastModifiedTime" and "maxAge" to 20. The browser
will consider this document fresh for 20 seconds, then revalidate its cache. After will consider this document fresh for 20 seconds, then revalidate its cache. After
the 304 response, the cache will be good for another 20 seconds. Unless you force the 304 response, the cache will be good for another 20 seconds. Unless you force
a reload, there will only be 304 responses for this page after the initial download. a reload, there will only be 304 responses for this page after the initial download.
'; ';
require '_include.php'; require '_include.php';
echo get_content(array( echo get_content(array(
'title' => $title 'title' => $title
,'explain' => $explain ,'explain' => $explain
)); ));

View File

@@ -1,30 +1,28 @@
<?php <?php
require '../config.php'; require '../config.php';
error_reporting(E_ALL | E_STRICT); error_reporting(E_ALL | E_STRICT);
ini_set('display_errors', 1); ini_set('display_errors', 1);
header('Content-Type: text/plain'); header('Content-Type: text/plain');
$thisDir = dirname(__FILE__); $thisDir = dirname(__FILE__);
/** /**
* pTest - PHP Unit Tester * pTest - PHP Unit Tester
* @param mixed $test Condition to test, evaluated as boolean * @param mixed $test Condition to test, evaluated as boolean
* @param string $message Descriptive message to output upon test * @param string $message Descriptive message to output upon test
* @url http://www.sitepoint.com/blogs/2007/08/13/ptest-php-unit-tester-in-9-lines-of-code/ * @url http://www.sitepoint.com/blogs/2007/08/13/ptest-php-unit-tester-in-9-lines-of-code/
*/ */
function assertTrue($test, $message) function assertTrue($test, $message)
{ {
static $count; static $count;
if (!isset($count)) $count = array('pass'=>0, 'fail'=>0, 'total'=>0); if (!isset($count)) $count = array('pass'=>0, 'fail'=>0, 'total'=>0);
$mode = $test ? 'pass' : 'fail'; $mode = $test ? 'pass' : 'fail';
$outMode = $test ? 'PASS' : '!FAIL'; $outMode = $test ? 'PASS' : '!FAIL';
printf("%s: %s (%d of %d tests run so far have %sed)\n", printf("%s: %s (%d of %d tests run so far have %sed)\n",
$outMode, $message, ++$count[$mode], ++$count['total'], $mode); $outMode, $message, ++$count[$mode], ++$count['total'], $mode);
return (bool)$test; return (bool)$test;
} }
?>

View File

@@ -1,42 +1,42 @@
<?php <?php
require_once '_inc.php'; require_once '_inc.php';
require_once 'Minify/CSS.php'; require_once 'Minify/CSS.php';
function test_CSS() function test_CSS()
{ {
global $thisDir; global $thisDir;
$cssPath = dirname(__FILE__) . '/_test_files/css'; $cssPath = dirname(__FILE__) . '/_test_files/css';
// build test file list // build test file list
$d = dir($cssPath); $d = dir($cssPath);
while (false !== ($entry = $d->read())) { while (false !== ($entry = $d->read())) {
if (preg_match('/^([\w\\-]+)\.css$/', $entry, $m)) { if (preg_match('/^([\w\\-]+)\.css$/', $entry, $m)) {
$list[] = $m[1]; $list[] = $m[1];
} }
} }
$d->close(); $d->close();
foreach ($list as $item) { foreach ($list as $item) {
$options = ($item === 'paths') $options = ($item === 'paths')
? array('prependRelativePath' => '../') ? array('prependRelativePath' => '../')
: array(); : array();
$src = file_get_contents($cssPath . "/{$item}.css"); $src = file_get_contents($cssPath . "/{$item}.css");
$minExpected = file_get_contents($cssPath . "/{$item}.min.css"); $minExpected = file_get_contents($cssPath . "/{$item}.min.css");
$minOutput = Minify_CSS::minify($src, $options); $minOutput = Minify_CSS::minify($src, $options);
$passed = assertTrue($minExpected === $minOutput, 'Minify_CSS : ' . $item); $passed = assertTrue($minExpected === $minOutput, 'Minify_CSS : ' . $item);
if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) { if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) {
echo "\n---Output: " .strlen($minOutput). " bytes\n\n{$minOutput}\n\n"; echo "\n---Output: " .strlen($minOutput). " bytes\n\n{$minOutput}\n\n";
if (!$passed) { if (!$passed) {
echo "---Expected: " .strlen($minExpected). " bytes\n\n{$minExpected}\n\n"; echo "---Expected: " .strlen($minExpected). " bytes\n\n{$minExpected}\n\n";
echo "---Source: " .strlen($src). " bytes\n\n{$src}\n\n\n"; echo "---Source: " .strlen($src). " bytes\n\n{$src}\n\n\n";
} }
} }
} }
} }
test_CSS(); test_CSS();

View File

@@ -1,59 +1,59 @@
<?php <?php
require_once '_inc.php'; require_once '_inc.php';
require_once 'Minify/HTML.php'; require_once 'Minify/HTML.php';
require_once 'Minify/CSS.php'; require_once 'Minify/CSS.php';
require_once 'Minify/Javascript.php'; require_once 'Minify/Javascript.php';
function test_HTML() function test_HTML()
{ {
global $thisDir; global $thisDir;
$src = file_get_contents($thisDir . '/_test_files/html/before.html'); $src = file_get_contents($thisDir . '/_test_files/html/before.html');
$minExpected = file_get_contents($thisDir . '/_test_files/html/before.min.html'); $minExpected = file_get_contents($thisDir . '/_test_files/html/before.min.html');
$time = microtime(true); $time = microtime(true);
$minOutput = Minify_HTML::minify($src, array( $minOutput = Minify_HTML::minify($src, array(
'cssMinifier' => array('Minify_CSS', 'minify') 'cssMinifier' => array('Minify_CSS', 'minify')
,'jsMinifier' => array('Minify_Javascript', 'minify') ,'jsMinifier' => array('Minify_Javascript', 'minify')
)); ));
$time = microtime(true) - $time; $time = microtime(true) - $time;
$passed = assertTrue($minExpected === $minOutput, 'Minify_HTML'); $passed = assertTrue($minExpected === $minOutput, 'Minify_HTML');
if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) { if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) {
if ($passed) { if ($passed) {
echo "\n---Source: ", strlen($src), " bytes\n" echo "\n---Source: ", strlen($src), " bytes\n"
, "---Output: ", strlen($minOutput), " bytes (", round($time * 1000), " ms)\n\n{$minOutput}\n\n\n"; , "---Output: ", strlen($minOutput), " bytes (", round($time * 1000), " ms)\n\n{$minOutput}\n\n\n";
} else { } else {
echo "\n---Output: ", strlen($minOutput), " bytes (", round($time * 1000), " ms)\n\n{$minOutput}\n\n" echo "\n---Output: ", strlen($minOutput), " bytes (", round($time * 1000), " ms)\n\n{$minOutput}\n\n"
, "---Expected: ", strlen($minExpected), " bytes\n\n{$minExpected}\n\n" , "---Expected: ", strlen($minExpected), " bytes\n\n{$minExpected}\n\n"
, "---Source: ", strlen($src), " bytes\n\n{$src}\n\n\n"; , "---Source: ", strlen($src), " bytes\n\n{$src}\n\n\n";
} }
} }
$src = file_get_contents($thisDir . '/_test_files/html/before2.html'); $src = file_get_contents($thisDir . '/_test_files/html/before2.html');
$minExpected = file_get_contents($thisDir . '/_test_files/html/before2.min.html'); $minExpected = file_get_contents($thisDir . '/_test_files/html/before2.min.html');
$time = microtime(true); $time = microtime(true);
$minOutput = Minify_HTML::minify($src, array( $minOutput = Minify_HTML::minify($src, array(
'cssMinifier' => array('Minify_CSS', 'minify') 'cssMinifier' => array('Minify_CSS', 'minify')
,'jsMinifier' => array('Minify_Javascript', 'minify') ,'jsMinifier' => array('Minify_Javascript', 'minify')
)); ));
$time = microtime(true) - $time; $time = microtime(true) - $time;
$passed = assertTrue($minExpected === $minOutput, 'Minify_HTML'); $passed = assertTrue($minExpected === $minOutput, 'Minify_HTML');
if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) { if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) {
if ($passed) { if ($passed) {
echo "\n---Source: ", strlen($src), " bytes\n" echo "\n---Source: ", strlen($src), " bytes\n"
, "---Output: ", strlen($minOutput), " bytes (", round($time * 1000), " ms)\n\n{$minOutput}\n\n\n"; , "---Output: ", strlen($minOutput), " bytes (", round($time * 1000), " ms)\n\n{$minOutput}\n\n\n";
} else { } else {
echo "\n---Output: ", strlen($minOutput), " bytes (", round($time * 1000), " ms)\n\n{$minOutput}\n\n" echo "\n---Output: ", strlen($minOutput), " bytes (", round($time * 1000), " ms)\n\n{$minOutput}\n\n"
, "---Expected: ", strlen($minExpected), " bytes\n\n{$minExpected}\n\n" , "---Expected: ", strlen($minExpected), " bytes\n\n{$minExpected}\n\n"
, "---Source: ", strlen($src), " bytes\n\n{$src}\n\n\n"; , "---Source: ", strlen($src), " bytes\n\n{$src}\n\n\n";
} }
} }
} }
test_HTML(); test_HTML();

View File

@@ -1,117 +1,117 @@
<?php <?php
require_once '_inc.php'; require_once '_inc.php';
require_once 'HTTP/ConditionalGet.php'; require_once 'HTTP/ConditionalGet.php';
function test_HTTP_ConditionalGet() function test_HTTP_ConditionalGet()
{ {
global $thisDir; global $thisDir;
$lmTime = time() - 900; $lmTime = time() - 900;
$gmtTime = gmdate('D, d M Y H:i:s \G\M\T', $lmTime); $gmtTime = gmdate('D, d M Y H:i:s \G\M\T', $lmTime);
$tests = array( $tests = array(
array( array(
'desc' => 'client has valid If-Modified-Since' 'desc' => 'client has valid If-Modified-Since'
,'inm' => null ,'inm' => null
,'ims' => $gmtTime ,'ims' => $gmtTime
,'exp' => array( ,'exp' => array(
'Last-Modified' => $gmtTime 'Last-Modified' => $gmtTime
,'ETag' => "\"{$lmTime}pri\"" ,'ETag' => "\"{$lmTime}pri\""
,'Cache-Control' => 'max-age=0, private, must-revalidate' ,'Cache-Control' => 'max-age=0, private, must-revalidate'
,'_responseCode' => 'HTTP/1.0 304 Not Modified' ,'_responseCode' => 'HTTP/1.0 304 Not Modified'
,'isValid' => true ,'isValid' => true
) )
) )
,array( ,array(
'desc' => 'client has valid If-Modified-Since with trailing semicolon' 'desc' => 'client has valid If-Modified-Since with trailing semicolon'
,'inm' => null ,'inm' => null
,'ims' => $gmtTime . ';' ,'ims' => $gmtTime . ';'
,'exp' => array( ,'exp' => array(
'Last-Modified' => $gmtTime 'Last-Modified' => $gmtTime
,'ETag' => "\"{$lmTime}pri\"" ,'ETag' => "\"{$lmTime}pri\""
,'Cache-Control' => 'max-age=0, private, must-revalidate' ,'Cache-Control' => 'max-age=0, private, must-revalidate'
,'_responseCode' => 'HTTP/1.0 304 Not Modified' ,'_responseCode' => 'HTTP/1.0 304 Not Modified'
,'isValid' => true ,'isValid' => true
) )
) )
,array( ,array(
'desc' => 'client has valid ETag' 'desc' => 'client has valid ETag'
,'inm' => "\"badEtagFoo\", \"{$lmTime}pri\"" ,'inm' => "\"badEtagFoo\", \"{$lmTime}pri\""
,'ims' => null ,'ims' => null
,'exp' => array( ,'exp' => array(
'Last-Modified' => $gmtTime 'Last-Modified' => $gmtTime
,'ETag' => "\"{$lmTime}pri\"" ,'ETag' => "\"{$lmTime}pri\""
,'Cache-Control' => 'max-age=0, private, must-revalidate' ,'Cache-Control' => 'max-age=0, private, must-revalidate'
,'_responseCode' => 'HTTP/1.0 304 Not Modified' ,'_responseCode' => 'HTTP/1.0 304 Not Modified'
,'isValid' => true ,'isValid' => true
) )
) )
,array( ,array(
'desc' => 'no conditional get' 'desc' => 'no conditional get'
,'inm' => null ,'inm' => null
,'ims' => null ,'ims' => null
,'exp' => array( ,'exp' => array(
'Last-Modified' => $gmtTime 'Last-Modified' => $gmtTime
,'ETag' => "\"{$lmTime}pri\"" ,'ETag' => "\"{$lmTime}pri\""
,'Cache-Control' => 'max-age=0, private, must-revalidate' ,'Cache-Control' => 'max-age=0, private, must-revalidate'
,'isValid' => false ,'isValid' => false
) )
) )
,array( ,array(
'desc' => 'client has invalid ETag' 'desc' => 'client has invalid ETag'
,'inm' => '"' . ($lmTime - 300) . 'pri"' ,'inm' => '"' . ($lmTime - 300) . 'pri"'
,'ims' => null ,'ims' => null
,'exp' => array( ,'exp' => array(
'Last-Modified' => $gmtTime 'Last-Modified' => $gmtTime
,'ETag' => "\"{$lmTime}pri\"" ,'ETag' => "\"{$lmTime}pri\""
,'Cache-Control' => 'max-age=0, private, must-revalidate' ,'Cache-Control' => 'max-age=0, private, must-revalidate'
,'isValid' => false ,'isValid' => false
) )
) )
,array( ,array(
'desc' => 'client has invalid If-Modified-Since' 'desc' => 'client has invalid If-Modified-Since'
,'inm' => null ,'inm' => null
,'ims' => gmdate('D, d M Y H:i:s \G\M\T', $lmTime - 300) ,'ims' => gmdate('D, d M Y H:i:s \G\M\T', $lmTime - 300)
,'exp' => array( ,'exp' => array(
'Last-Modified' => $gmtTime 'Last-Modified' => $gmtTime
,'ETag' => "\"{$lmTime}pri\"" ,'ETag' => "\"{$lmTime}pri\""
,'Cache-Control' => 'max-age=0, private, must-revalidate' ,'Cache-Control' => 'max-age=0, private, must-revalidate'
,'isValid' => false ,'isValid' => false
) )
) )
); );
foreach ($tests as $test) { foreach ($tests as $test) {
// setup env // setup env
if (null === $test['inm']) { if (null === $test['inm']) {
unset($_SERVER['HTTP_IF_NONE_MATCH']); unset($_SERVER['HTTP_IF_NONE_MATCH']);
} else { } else {
$_SERVER['HTTP_IF_NONE_MATCH'] = get_magic_quotes_gpc() $_SERVER['HTTP_IF_NONE_MATCH'] = get_magic_quotes_gpc()
? addslashes($test['inm']) ? addslashes($test['inm'])
: $test['inm'];; : $test['inm'];;
} }
if (null === $test['ims']) { if (null === $test['ims']) {
unset($_SERVER['HTTP_IF_MODIFIED_SINCE']); unset($_SERVER['HTTP_IF_MODIFIED_SINCE']);
} else { } else {
$_SERVER['HTTP_IF_MODIFIED_SINCE'] = $test['ims']; $_SERVER['HTTP_IF_MODIFIED_SINCE'] = $test['ims'];
} }
$exp = $test['exp']; $exp = $test['exp'];
$cg = new HTTP_ConditionalGet(array( $cg = new HTTP_ConditionalGet(array(
'lastModifiedTime' => $lmTime 'lastModifiedTime' => $lmTime
)); ));
$ret = $cg->getHeaders(); $ret = $cg->getHeaders();
$ret['isValid'] = $cg->cacheIsValid; $ret['isValid'] = $cg->cacheIsValid;
$passed = assertTrue($exp == $ret, 'HTTP_ConditionalGet : ' . $test['desc']); $passed = assertTrue($exp == $ret, 'HTTP_ConditionalGet : ' . $test['desc']);
if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) { if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) {
echo "\n--- INM = {$test['inm']} / IMS = {$test['ims']}\n"; echo "\n--- INM = {$test['inm']} / IMS = {$test['ims']}\n";
echo "Expected = " . preg_replace('/\\s+/', ' ', var_export($exp, 1)) . "\n"; echo "Expected = " . preg_replace('/\\s+/', ' ', var_export($exp, 1)) . "\n";
echo "Returned = " . preg_replace('/\\s+/', ' ', var_export($ret, 1)) . "\n\n"; echo "Returned = " . preg_replace('/\\s+/', ' ', var_export($ret, 1)) . "\n\n";
} }
} }
} }
test_HTTP_ConditionalGet(); test_HTTP_ConditionalGet();

View File

@@ -1,224 +1,224 @@
<?php <?php
require_once '_inc.php'; require_once '_inc.php';
require_once 'HTTP/Encoder.php'; require_once 'HTTP/Encoder.php';
function test_HTTP_Encoder() function test_HTTP_Encoder()
{ {
global $thisDir; global $thisDir;
$methodTests = array( $methodTests = array(
array( array(
'ua' => 'Any browser' 'ua' => 'Any browser'
,'ae' => 'compress, x-gzip' ,'ae' => 'compress, x-gzip'
,'exp' => array('gzip', 'x-gzip') ,'exp' => array('gzip', 'x-gzip')
,'desc' => 'recognize "x-gzip" as gzip' ,'desc' => 'recognize "x-gzip" as gzip'
) )
,array( ,array(
'ua' => 'Any browser' 'ua' => 'Any browser'
,'ae' => 'compress, x-gzip;q=0.5' ,'ae' => 'compress, x-gzip;q=0.5'
,'exp' => array('gzip', 'x-gzip') ,'exp' => array('gzip', 'x-gzip')
,'desc' => 'gzip w/ non-zero q' ,'desc' => 'gzip w/ non-zero q'
) )
,array( ,array(
'ua' => 'Any browser' 'ua' => 'Any browser'
,'ae' => 'compress, x-gzip;q=0' ,'ae' => 'compress, x-gzip;q=0'
,'exp' => array('compress', 'compress') ,'exp' => array('compress', 'compress')
,'desc' => 'gzip w/ zero q' ,'desc' => 'gzip w/ zero q'
) )
,array( ,array(
'ua' => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)' 'ua' => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)'
,'ae' => 'gzip, deflate' ,'ae' => 'gzip, deflate'
,'exp' => array('', '') ,'exp' => array('', '')
,'desc' => 'IE6 w/o "enhanced security"' ,'desc' => 'IE6 w/o "enhanced security"'
) )
,array( ,array(
'ua' => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)' 'ua' => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)'
,'ae' => 'gzip, deflate' ,'ae' => 'gzip, deflate'
,'exp' => array('deflate', 'deflate') ,'exp' => array('deflate', 'deflate')
,'desc' => 'IE6 w/ "enhanced security"' ,'desc' => 'IE6 w/ "enhanced security"'
) )
,array( ,array(
'ua' => 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.01)' 'ua' => 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.01)'
,'ae' => 'gzip, deflate' ,'ae' => 'gzip, deflate'
,'exp' => array('', '') ,'exp' => array('', '')
,'desc' => 'IE5.5' ,'desc' => 'IE5.5'
) )
,array( ,array(
'ua' => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; en) Opera 9.25' 'ua' => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; en) Opera 9.25'
,'ae' => 'gzip,deflate' ,'ae' => 'gzip,deflate'
,'exp' => array('deflate', 'deflate') ,'exp' => array('deflate', 'deflate')
,'desc' => 'Opera identifying as IE6' ,'desc' => 'Opera identifying as IE6'
) )
); );
foreach ($methodTests as $test) { foreach ($methodTests as $test) {
$_SERVER['HTTP_USER_AGENT'] = $test['ua']; $_SERVER['HTTP_USER_AGENT'] = $test['ua'];
$_SERVER['HTTP_ACCEPT_ENCODING'] = $test['ae']; $_SERVER['HTTP_ACCEPT_ENCODING'] = $test['ae'];
$exp = $test['exp']; $exp = $test['exp'];
$ret = HTTP_Encoder::getAcceptedEncoding(); $ret = HTTP_Encoder::getAcceptedEncoding();
$passed = assertTrue($exp == $ret, 'HTTP_Encoder : ' . $test['desc']); $passed = assertTrue($exp == $ret, 'HTTP_Encoder : ' . $test['desc']);
if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) { if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) {
echo "\n--- AE | UA = {$test['ae']} | {$test['ua']}\n"; echo "\n--- AE | UA = {$test['ae']} | {$test['ua']}\n";
echo "Expected = " . preg_replace('/\\s+/', ' ', var_export($exp, 1)) . "\n"; echo "Expected = " . preg_replace('/\\s+/', ' ', var_export($exp, 1)) . "\n";
echo "Returned = " . preg_replace('/\\s+/', ' ', var_export($ret, 1)) . "\n\n"; echo "Returned = " . preg_replace('/\\s+/', ' ', var_export($ret, 1)) . "\n\n";
} }
} }
$variedContent = file_get_contents($thisDir . '/_test_files/html/before.html') $variedContent = file_get_contents($thisDir . '/_test_files/html/before.html')
. file_get_contents($thisDir . '/_test_files/css/subsilver.css') . file_get_contents($thisDir . '/_test_files/css/subsilver.css')
. file_get_contents($thisDir . '/../examples/jquery-1.2.3.js'); . file_get_contents($thisDir . '/../examples/jquery-1.2.3.js');
$variedLength = strlen($variedContent); $variedLength = strlen($variedContent);
$encodingTests = array( $encodingTests = array(
array('method' => 'deflate', 'inv' => 'gzinflate', 'exp' => 32157) array('method' => 'deflate', 'inv' => 'gzinflate', 'exp' => 32157)
,array('method' => 'gzip', 'inv' => '_gzdecode', 'exp' => 32175) ,array('method' => 'gzip', 'inv' => '_gzdecode', 'exp' => 32175)
,array('method' => 'compress', 'inv' => 'gzuncompress', 'exp' => 32211) ,array('method' => 'compress', 'inv' => 'gzuncompress', 'exp' => 32211)
); );
foreach ($encodingTests as $test) { foreach ($encodingTests as $test) {
$e = new HTTP_Encoder(array( $e = new HTTP_Encoder(array(
'content' => $variedContent 'content' => $variedContent
,'method' => $test['method'] ,'method' => $test['method']
)); ));
$e->encode(9); $e->encode(9);
$ret = strlen($e->getContent()); $ret = strlen($e->getContent());
// test uncompression // test uncompression
$roundTrip = @call_user_func($test['inv'], $e->getContent()); $roundTrip = @call_user_func($test['inv'], $e->getContent());
$desc = "HTTP_Encoder : {$test['method']} : uncompress possible"; $desc = "HTTP_Encoder : {$test['method']} : uncompress possible";
$passed = assertTrue($variedContent == $roundTrip, $desc); $passed = assertTrue($variedContent == $roundTrip, $desc);
// test expected compressed size // test expected compressed size
$desc = "HTTP_Encoder : {$test['method']} : compressed to " $desc = "HTTP_Encoder : {$test['method']} : compressed to "
. sprintf('%4.2f%% of original', $ret/$variedLength*100); . sprintf('%4.2f%% of original', $ret/$variedLength*100);
$passed = assertTrue(abs($ret - $test['exp']) < 100, $desc); $passed = assertTrue(abs($ret - $test['exp']) < 100, $desc);
if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) { if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) {
echo "\n--- {$test['method']}: expected bytes: " echo "\n--- {$test['method']}: expected bytes: "
, "{$test['exp']}. Returned: {$ret} " , "{$test['exp']}. Returned: {$ret} "
, "(off by ". abs($ret - $test['exp']) . " bytes)\n\n"; , "(off by ". abs($ret - $test['exp']) . " bytes)\n\n";
} }
} }
} }
test_HTTP_Encoder(); test_HTTP_Encoder();
function _gzdecode($data) function _gzdecode($data)
{ {
$filename = $error = ''; $filename = $error = '';
return _phpman_gzdecode($data, $filename, $error); return _phpman_gzdecode($data, $filename, $error);
} }
// http://www.php.net/manual/en/function.gzdecode.php#82930 // http://www.php.net/manual/en/function.gzdecode.php#82930
function _phpman_gzdecode($data, &$filename='', &$error='', $maxlength=null) function _phpman_gzdecode($data, &$filename='', &$error='', $maxlength=null)
{ {
$len = strlen($data); $len = strlen($data);
if ($len < 18 || strcmp(substr($data,0,2),"\x1f\x8b")) { if ($len < 18 || strcmp(substr($data,0,2),"\x1f\x8b")) {
$error = "Not in GZIP format."; $error = "Not in GZIP format.";
return null; // Not GZIP format (See RFC 1952) return null; // Not GZIP format (See RFC 1952)
} }
$method = ord(substr($data,2,1)); // Compression method $method = ord(substr($data,2,1)); // Compression method
$flags = ord(substr($data,3,1)); // Flags $flags = ord(substr($data,3,1)); // Flags
if ($flags & 31 != $flags) { if ($flags & 31 != $flags) {
$error = "Reserved bits not allowed."; $error = "Reserved bits not allowed.";
return null; return null;
} }
// NOTE: $mtime may be negative (PHP integer limitations) // NOTE: $mtime may be negative (PHP integer limitations)
$mtime = unpack("V", substr($data,4,4)); $mtime = unpack("V", substr($data,4,4));
$mtime = $mtime[1]; $mtime = $mtime[1];
$xfl = substr($data,8,1); $xfl = substr($data,8,1);
$os = substr($data,8,1); $os = substr($data,8,1);
$headerlen = 10; $headerlen = 10;
$extralen = 0; $extralen = 0;
$extra = ""; $extra = "";
if ($flags & 4) { if ($flags & 4) {
// 2-byte length prefixed EXTRA data in header // 2-byte length prefixed EXTRA data in header
if ($len - $headerlen - 2 < 8) { if ($len - $headerlen - 2 < 8) {
return false; // invalid return false; // invalid
} }
$extralen = unpack("v",substr($data,8,2)); $extralen = unpack("v",substr($data,8,2));
$extralen = $extralen[1]; $extralen = $extralen[1];
if ($len - $headerlen - 2 - $extralen < 8) { if ($len - $headerlen - 2 - $extralen < 8) {
return false; // invalid return false; // invalid
} }
$extra = substr($data,10,$extralen); $extra = substr($data,10,$extralen);
$headerlen += 2 + $extralen; $headerlen += 2 + $extralen;
} }
$filenamelen = 0; $filenamelen = 0;
$filename = ""; $filename = "";
if ($flags & 8) { if ($flags & 8) {
// C-style string // C-style string
if ($len - $headerlen - 1 < 8) { if ($len - $headerlen - 1 < 8) {
return false; // invalid return false; // invalid
} }
$filenamelen = strpos(substr($data,$headerlen),chr(0)); $filenamelen = strpos(substr($data,$headerlen),chr(0));
if ($filenamelen === false || $len - $headerlen - $filenamelen - 1 < 8) { if ($filenamelen === false || $len - $headerlen - $filenamelen - 1 < 8) {
return false; // invalid return false; // invalid
} }
$filename = substr($data,$headerlen,$filenamelen); $filename = substr($data,$headerlen,$filenamelen);
$headerlen += $filenamelen + 1; $headerlen += $filenamelen + 1;
} }
$commentlen = 0; $commentlen = 0;
$comment = ""; $comment = "";
if ($flags & 16) { if ($flags & 16) {
// C-style string COMMENT data in header // C-style string COMMENT data in header
if ($len - $headerlen - 1 < 8) { if ($len - $headerlen - 1 < 8) {
return false; // invalid return false; // invalid
} }
$commentlen = strpos(substr($data,$headerlen),chr(0)); $commentlen = strpos(substr($data,$headerlen),chr(0));
if ($commentlen === false || $len - $headerlen - $commentlen - 1 < 8) { if ($commentlen === false || $len - $headerlen - $commentlen - 1 < 8) {
return false; // Invalid header format return false; // Invalid header format
} }
$comment = substr($data,$headerlen,$commentlen); $comment = substr($data,$headerlen,$commentlen);
$headerlen += $commentlen + 1; $headerlen += $commentlen + 1;
} }
$headercrc = ""; $headercrc = "";
if ($flags & 2) { if ($flags & 2) {
// 2-bytes (lowest order) of CRC32 on header present // 2-bytes (lowest order) of CRC32 on header present
if ($len - $headerlen - 2 < 8) { if ($len - $headerlen - 2 < 8) {
return false; // invalid return false; // invalid
} }
$calccrc = crc32(substr($data,0,$headerlen)) & 0xffff; $calccrc = crc32(substr($data,0,$headerlen)) & 0xffff;
$headercrc = unpack("v", substr($data,$headerlen,2)); $headercrc = unpack("v", substr($data,$headerlen,2));
$headercrc = $headercrc[1]; $headercrc = $headercrc[1];
if ($headercrc != $calccrc) { if ($headercrc != $calccrc) {
$error = "Header checksum failed."; $error = "Header checksum failed.";
return false; // Bad header CRC return false; // Bad header CRC
} }
$headerlen += 2; $headerlen += 2;
} }
// GZIP FOOTER // GZIP FOOTER
$datacrc = unpack("V",substr($data,-8,4)); $datacrc = unpack("V",substr($data,-8,4));
$datacrc = sprintf('%u',$datacrc[1] & 0xFFFFFFFF); $datacrc = sprintf('%u',$datacrc[1] & 0xFFFFFFFF);
$isize = unpack("V",substr($data,-4)); $isize = unpack("V",substr($data,-4));
$isize = $isize[1]; $isize = $isize[1];
// decompression: // decompression:
$bodylen = $len-$headerlen-8; $bodylen = $len-$headerlen-8;
if ($bodylen < 1) { if ($bodylen < 1) {
// IMPLEMENTATION BUG! // IMPLEMENTATION BUG!
return null; return null;
} }
$body = substr($data,$headerlen,$bodylen); $body = substr($data,$headerlen,$bodylen);
$data = ""; $data = "";
if ($bodylen > 0) { if ($bodylen > 0) {
switch ($method) { switch ($method) {
case 8: case 8:
// Currently the only supported compression method: // Currently the only supported compression method:
$data = gzinflate($body,$maxlength); $data = gzinflate($body,$maxlength);
break; break;
default: default:
$error = "Unknown compression method."; $error = "Unknown compression method.";
return false; return false;
} }
} // zero-byte body content is allowed } // zero-byte body content is allowed
// Verifiy CRC32 // Verifiy CRC32
$crc = sprintf("%u",crc32($data)); $crc = sprintf("%u",crc32($data));
$crcOK = $crc == $datacrc; $crcOK = $crc == $datacrc;
$lenOK = $isize == strlen($data); $lenOK = $isize == strlen($data);
if (!$lenOK || !$crcOK) { if (!$lenOK || !$crcOK) {
$error = ( $lenOK ? '' : 'Length check FAILED. ') . ( $crcOK ? '' : 'Checksum FAILED.'); $error = ( $lenOK ? '' : 'Length check FAILED. ') . ( $crcOK ? '' : 'Checksum FAILED.');
return false; return false;
} }
return $data; return $data;
} }

View File

@@ -1,37 +1,37 @@
<?php <?php
require_once '_inc.php'; require_once '_inc.php';
require_once 'Minify/Javascript.php'; require_once 'Minify/Javascript.php';
function test_Javascript() function test_Javascript()
{ {
global $thisDir; global $thisDir;
$src = file_get_contents($thisDir . '/_test_files/js/before.js'); $src = file_get_contents($thisDir . '/_test_files/js/before.js');
$minExpected = file_get_contents($thisDir . '/_test_files/js/before.min.js'); $minExpected = file_get_contents($thisDir . '/_test_files/js/before.min.js');
$minOutput = Minify_Javascript::minify($src); $minOutput = Minify_Javascript::minify($src);
$passed = assertTrue($minExpected == $minOutput, 'Minify_Javascript'); $passed = assertTrue($minExpected == $minOutput, 'Minify_Javascript');
if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) { if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) {
echo "\n---Output: " .strlen($minOutput). " bytes\n\n{$minOutput}\n\n"; echo "\n---Output: " .strlen($minOutput). " bytes\n\n{$minOutput}\n\n";
echo "---Expected: " .strlen($minExpected). " bytes\n\n{$minExpected}\n\n"; echo "---Expected: " .strlen($minExpected). " bytes\n\n{$minExpected}\n\n";
echo "---Source: " .strlen($src). " bytes\n\n{$src}\n\n\n"; echo "---Source: " .strlen($src). " bytes\n\n{$src}\n\n\n";
} }
//$src = file_get_contents($thisDir . '/_test_files/js/before.js'); //$src = file_get_contents($thisDir . '/_test_files/js/before.js');
$minExpected = file_get_contents($thisDir . '/_test_files/js/before_noComments.min.js'); $minExpected = file_get_contents($thisDir . '/_test_files/js/before_noComments.min.js');
$minOutput = Minify_Javascript::minify($src, array( $minOutput = Minify_Javascript::minify($src, array(
'preserveComments' => false 'preserveComments' => false
)); ));
$passed = assertTrue($minExpected == $minOutput, 'Minify_Javascript'); $passed = assertTrue($minExpected == $minOutput, 'Minify_Javascript');
if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) { if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) {
echo "\n---Output: " .strlen($minOutput). " bytes\n\n{$minOutput}\n\n"; echo "\n---Output: " .strlen($minOutput). " bytes\n\n{$minOutput}\n\n";
echo "---Expected: " .strlen($minExpected). " bytes\n\n{$minExpected}\n\n"; echo "---Expected: " .strlen($minExpected). " bytes\n\n{$minExpected}\n\n";
echo "---Source: " .strlen($src). " bytes\n\n{$src}\n\n\n"; echo "---Source: " .strlen($src). " bytes\n\n{$src}\n\n\n";
} }
} }
test_Javascript(); test_Javascript();

View File

@@ -1,33 +1,33 @@
<?php <?php
require_once '_inc.php'; require_once '_inc.php';
require_once 'Minify.php'; require_once 'Minify.php';
function test_Lines() function test_Lines()
{ {
global $thisDir; global $thisDir;
$exp = file_get_contents("{$thisDir}/_test_files/minify/lines_output.js"); $exp = file_get_contents("{$thisDir}/_test_files/minify/lines_output.js");
$ret = Minify::serve('Files', array( $ret = Minify::serve('Files', array(
'debug' => true 'debug' => true
,'quiet' => true ,'quiet' => true
,'encodeOutput' => false ,'encodeOutput' => false
,'files' => array( ,'files' => array(
"{$thisDir}/_test_files/minify/email.js" "{$thisDir}/_test_files/minify/email.js"
,"{$thisDir}/_test_files/minify/QueryString.js" ,"{$thisDir}/_test_files/minify/QueryString.js"
,"{$thisDir}/_test_files/js/before.js" ,"{$thisDir}/_test_files/js/before.js"
) )
)); ));
$passed = assertTrue($exp === $ret['content'], 'Minify_Lines'); $passed = assertTrue($exp === $ret['content'], 'Minify_Lines');
if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) { if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) {
echo "\n---Output: " .strlen($ret['content']). " bytes\n\n{$ret['content']}\n\n"; echo "\n---Output: " .strlen($ret['content']). " bytes\n\n{$ret['content']}\n\n";
if (!$passed) { if (!$passed) {
echo "---Expected: " .strlen($exp). " bytes\n\n{$exp}\n\n\n"; echo "---Expected: " .strlen($exp). " bytes\n\n{$exp}\n\n\n";
} }
} }
} }
test_Lines(); test_Lines();

View File

@@ -1,101 +1,101 @@
<?php <?php
// currently these only test serve() when passed the 'quiet' options // currently these only test serve() when passed the 'quiet' options
require_once '_inc.php'; require_once '_inc.php';
require_once 'Minify.php'; require_once 'Minify.php';
function test_Minify() function test_Minify()
{ {
global $thisDir; global $thisDir;
$minifyTestPath = dirname(__FILE__) . '/_test_files/minify'; $minifyTestPath = dirname(__FILE__) . '/_test_files/minify';
$tomorrow = $_SERVER['REQUEST_TIME'] + 86400; $tomorrow = $_SERVER['REQUEST_TIME'] + 86400;
$lastModified = $_SERVER['REQUEST_TIME'] - 86400; $lastModified = $_SERVER['REQUEST_TIME'] - 86400;
// Test 304 response // Test 304 response
// simulate conditional headers // simulate conditional headers
$_SERVER['HTTP_IF_NONE_MATCH'] = "\"{$lastModified}pub\""; $_SERVER['HTTP_IF_NONE_MATCH'] = "\"{$lastModified}pub\"";
$_SERVER['HTTP_IF_MODIFIED_SINCE'] = gmdate('D, d M Y H:i:s \G\M\T', $lastModified); $_SERVER['HTTP_IF_MODIFIED_SINCE'] = gmdate('D, d M Y H:i:s \G\M\T', $lastModified);
$expected = array ( $expected = array (
'success' => true 'success' => true
,'statusCode' => 304 ,'statusCode' => 304
,'content' => '', ,'content' => '',
'headers' => array( 'headers' => array(
'Expires' => gmdate('D, d M Y H:i:s \G\M\T', $_SERVER['REQUEST_TIME'] + 1800), 'Expires' => gmdate('D, d M Y H:i:s \G\M\T', $_SERVER['REQUEST_TIME'] + 1800),
'Last-Modified' => gmdate('D, d M Y H:i:s \G\M\T', $lastModified), 'Last-Modified' => gmdate('D, d M Y H:i:s \G\M\T', $lastModified),
'ETag' => "\"{$lastModified}pub\"", 'ETag' => "\"{$lastModified}pub\"",
'Cache-Control' => 'max-age=1800, public, must-revalidate', 'Cache-Control' => 'max-age=1800, public, must-revalidate',
'_responseCode' => 'HTTP/1.0 304 Not Modified', '_responseCode' => 'HTTP/1.0 304 Not Modified',
) )
); );
$output = Minify::serve('Files', array( $output = Minify::serve('Files', array(
'files' => $thisDir . '/_test_files/css/styles.css' // controller casts to array 'files' => $thisDir . '/_test_files/css/styles.css' // controller casts to array
,'quiet' => true ,'quiet' => true
,'lastModifiedTime' => $lastModified ,'lastModifiedTime' => $lastModified
,'encodeOutput' => false ,'encodeOutput' => false
)); ));
$passed = assertTrue($expected === $output, 'Minify : 304 response'); $passed = assertTrue($expected === $output, 'Minify : 304 response');
if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) { if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) {
echo "\nOutput: " .var_export($output, 1). "\n\n"; echo "\nOutput: " .var_export($output, 1). "\n\n";
if (! $passed) { if (! $passed) {
echo "\n\n\n\n---Expected: " .var_export($expected, 1). "\n\n"; echo "\n\n\n\n---Expected: " .var_export($expected, 1). "\n\n";
} }
} }
assertTrue( assertTrue(
//! class_exists('Cache_Lite_File', false) //! class_exists('Cache_Lite_File', false)
! class_exists('HTTP_Encoder', false) ! class_exists('HTTP_Encoder', false)
&& ! class_exists('Minify_CSS', false) && ! class_exists('Minify_CSS', false)
&& ! class_exists('Minify_Cache', false) && ! class_exists('Minify_Cache', false)
,'Encoder.php, CSS.php, Cache.php not loaded' ,'Encoder.php, CSS.php, Cache.php not loaded'
); );
// Test minifying JS and serving with Expires header // Test minifying JS and serving with Expires header
$content = preg_replace('/\\r\\n?/', "\n", file_get_contents($minifyTestPath . '/minified.js')); $content = preg_replace('/\\r\\n?/', "\n", file_get_contents($minifyTestPath . '/minified.js'));
$lastModified = filemtime($minifyTestPath . '/minified.js'); $lastModified = filemtime($minifyTestPath . '/minified.js');
$expected = array( $expected = array(
'success' => true 'success' => true
,'statusCode' => 200 ,'statusCode' => 200
// Minify_Javascript always converts to \n line endings // Minify_Javascript always converts to \n line endings
,'content' => $content ,'content' => $content
,'headers' => array ( ,'headers' => array (
'Expires' => gmdate('D, d M Y H:i:s \G\M\T', $tomorrow), 'Expires' => gmdate('D, d M Y H:i:s \G\M\T', $tomorrow),
'Last-Modified' => gmdate('D, d M Y H:i:s \G\M\T', $lastModified), 'Last-Modified' => gmdate('D, d M Y H:i:s \G\M\T', $lastModified),
'ETag' => "\"{$lastModified}pub\"", 'ETag' => "\"{$lastModified}pub\"",
'Cache-Control' => 'max-age=86400, public, must-revalidate', 'Cache-Control' => 'max-age=86400, public, must-revalidate',
'Content-Length' => strlen($content), 'Content-Length' => strlen($content),
'Content-Type' => 'application/x-javascript; charset=UTF-8', 'Content-Type' => 'application/x-javascript; charset=UTF-8',
) )
); );
$output = Minify::serve('Files', array( $output = Minify::serve('Files', array(
'files' => array( 'files' => array(
$minifyTestPath . '/email.js' $minifyTestPath . '/email.js'
,$minifyTestPath . '/QueryString.js' ,$minifyTestPath . '/QueryString.js'
) )
,'quiet' => true ,'quiet' => true
,'maxAge' => 86400 ,'maxAge' => 86400
,'encodeOutput' => false ,'encodeOutput' => false
)); ));
$passed = assertTrue($expected === $output, 'Minify : JS and Expires'); $passed = assertTrue($expected === $output, 'Minify : JS and Expires');
if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) { if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) {
echo "\nOutput: " .var_export($output, 1). "\n\n"; echo "\nOutput: " .var_export($output, 1). "\n\n";
if (! $passed) { if (! $passed) {
echo "\n\n\n\n---Expected: " .var_export($expected, 1). "\n\n"; echo "\n\n\n\n---Expected: " .var_export($expected, 1). "\n\n";
} }
} }
// Test minifying CSS and responding with Etag/Last-Modified // Test minifying CSS and responding with Etag/Last-Modified
// needed to expose E_STRICT warning in Cache_Lite_File // needed to expose E_STRICT warning in Cache_Lite_File
Minify::setCache(); Minify::setCache();
// don't allow conditional headers // don't allow conditional headers
unset($_SERVER['HTTP_IF_NONE_MATCH'], $_SERVER['HTTP_IF_MODIFIED_SINCE']); unset($_SERVER['HTTP_IF_NONE_MATCH'], $_SERVER['HTTP_IF_MODIFIED_SINCE']);
$pathToWebTest = str_replace( $pathToWebTest = str_replace(
DIRECTORY_SEPARATOR DIRECTORY_SEPARATOR
@@ -107,37 +107,37 @@ function test_Minify()
,$pathToWebTest ,$pathToWebTest
,file_get_contents($minifyTestPath . '/minified.css') ,file_get_contents($minifyTestPath . '/minified.css')
); );
$expected = array( $expected = array(
'success' => true 'success' => true
,'statusCode' => 200 ,'statusCode' => 200
,'content' => $expectedContent ,'content' => $expectedContent
,'headers' => array ( ,'headers' => array (
'Last-Modified' => gmdate('D, d M Y H:i:s \G\M\T', $lastModified), 'Last-Modified' => gmdate('D, d M Y H:i:s \G\M\T', $lastModified),
'ETag' => "\"{$lastModified}pub\"", 'ETag' => "\"{$lastModified}pub\"",
'Cache-Control' => 'max-age=0, public, must-revalidate', 'Cache-Control' => 'max-age=0, public, must-revalidate',
'Content-Length' => strlen($expectedContent), 'Content-Length' => strlen($expectedContent),
'Content-Type' => 'text/css; charset=UTF-8', 'Content-Type' => 'text/css; charset=UTF-8',
) )
); );
$output = Minify::serve('Files', array( $output = Minify::serve('Files', array(
'files' => array( 'files' => array(
$thisDir . '/_test_files/css/styles.css' $thisDir . '/_test_files/css/styles.css'
,$thisDir . '/_test_files/css/subsilver.css' ,$thisDir . '/_test_files/css/subsilver.css'
) )
,'quiet' => true ,'quiet' => true
,'lastModifiedTime' => $lastModified ,'lastModifiedTime' => $lastModified
,'encodeOutput' => false ,'encodeOutput' => false
,'maxAge' => false ,'maxAge' => false
)); ));
$passed = assertTrue($expected === $output, 'Minify : CSS and Etag/Last-Modified'); $passed = assertTrue($expected === $output, 'Minify : CSS and Etag/Last-Modified');
if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) { if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) {
echo "\nOutput: " .var_export($output, 1). "\n\n"; echo "\nOutput: " .var_export($output, 1). "\n\n";
if (! $passed) { if (! $passed) {
echo "\n\n\n\n---Expected: " .var_export($expected, 1). "\n\n"; echo "\n\n\n\n---Expected: " .var_export($expected, 1). "\n\n";
} }
} }
} }
test_Minify(); test_Minify();

View File

@@ -1,35 +1,35 @@
<?php <?php
require_once '_inc.php'; require_once '_inc.php';
require_once 'Minify/Build.php'; require_once 'Minify/Build.php';
function test_Minify_Build() function test_Minify_Build()
{ {
global $thisDir; global $thisDir;
$file1 = $thisDir . '/_test_files/css/paths.css'; $file1 = $thisDir . '/_test_files/css/paths.css';
$file2 = $thisDir . '/_test_files/css/styles.css'; $file2 = $thisDir . '/_test_files/css/styles.css';
$maxTime = max(filemtime($file1), filemtime($file2)); $maxTime = max(filemtime($file1), filemtime($file2));
$b = new Minify_Build($file1); $b = new Minify_Build($file1);
assertTrue($b->lastModified == filemtime($file1) assertTrue($b->lastModified == filemtime($file1)
,'Minify_Build : single file path'); ,'Minify_Build : single file path');
$b = new Minify_Build(array($file1, $file2)); $b = new Minify_Build(array($file1, $file2));
assertTrue($maxTime == $b->lastModified assertTrue($maxTime == $b->lastModified
,'Minify_Build : multiple file paths'); ,'Minify_Build : multiple file paths');
$b = new Minify_Build(array( $b = new Minify_Build(array(
$file1 $file1
,new Minify_Source(array('filepath' => $file2)) ,new Minify_Source(array('filepath' => $file2))
)); ));
assertTrue($maxTime == $b->lastModified assertTrue($maxTime == $b->lastModified
,'Minify_Build : file path and a Minify_Source'); ,'Minify_Build : file path and a Minify_Source');
assertTrue($b->uri('/path') == "/path?{$maxTime}" assertTrue($b->uri('/path') == "/path?{$maxTime}"
,'Minify_Build : uri() with no querystring'); ,'Minify_Build : uri() with no querystring');
assertTrue($b->uri('/path?hello') == "/path?hello&amp;{$maxTime}" assertTrue($b->uri('/path?hello') == "/path?hello&amp;{$maxTime}"
,'Minify_Build : uri() with existing querystring'); ,'Minify_Build : uri() with existing querystring');
} }
test_Minify_Build(); test_Minify_Build();

View File

@@ -1,10 +1,10 @@
<?php <?php
require 'test_Minify.php'; require 'test_Minify.php';
require 'test_Javascript.php'; require 'test_Javascript.php';
require 'test_CSS.php'; require 'test_CSS.php';
require 'test_Lines.php'; require 'test_Lines.php';
require 'test_HTML.php'; require 'test_HTML.php';
require 'test_Minify_Build.php'; require 'test_Minify_Build.php';
require 'test_HTTP_Encoder.php'; require 'test_HTTP_Encoder.php';
require 'test_HTTP_ConditionalGet.php'; require 'test_HTTP_ConditionalGet.php';

View File

@@ -1,32 +1,32 @@
<?php <?php
if (isset($_FILES['subject']['name'])) { if (isset($_FILES['subject']['name'])) {
require '../../min/lib/HTTP/Encoder.php'; require '../../min/lib/HTTP/Encoder.php';
$he = new HTTP_Encoder(array( $he = new HTTP_Encoder(array(
'content' => file_get_contents($_FILES['subject']['tmp_name']) 'content' => file_get_contents($_FILES['subject']['tmp_name'])
,'method' => $_POST['method'] ,'method' => $_POST['method']
)); ));
header('Content-Type: application/octet-stream'); header('Content-Type: application/octet-stream');
header('Content-Transfer-Encoding: binary'); header('Content-Transfer-Encoding: binary');
header("Content-Disposition: attachment; filename=\"{$_FILES['subject']['name']}." header("Content-Disposition: attachment; filename=\"{$_FILES['subject']['name']}."
. ($_POST['method'] == 'deflate' . ($_POST['method'] == 'deflate'
? 'zd' ? 'zd'
: ($_POST['method'] == 'gzip' : ($_POST['method'] == 'gzip'
? 'zg' ? 'zg'
: 'zc' : 'zc'
) )
) . '"'); ) . '"');
$he->encode(9); $he->encode(9);
echo $he->getContent(); echo $he->getContent();
exit(); exit();
} }
?> ?>
<form enctype="multipart/form-data" action="" method="post"> <form enctype="multipart/form-data" action="" method="post">
<p>Encode <input type="file" name="subject" /><br /> <p>Encode <input type="file" name="subject" /><br />
as <input type="submit" name="method" value="deflate" /> as <input type="submit" name="method" value="deflate" />
<input type="submit" name="method" value="gzip" /> <input type="submit" name="method" value="gzip" />
<input type="submit" name="method" value="compress" /> <input type="submit" name="method" value="compress" />
</p> </p>
</form> </form>

View File

@@ -1,50 +1,53 @@
<?php <?php
if (isset($_FILES['subject']['name']) if (isset($_FILES['subject']['name'])
&& preg_match('/\\.(js|css|html)$/', $_FILES['subject']['name'], $m) && preg_match('/\\.(js|css|x?html?)$/', $_FILES['subject']['name'], $m)
) { ) {
ini_set('include_path', ini_set('include_path',
dirname(__FILE__) . '/../../min/lib' dirname(__FILE__) . '/../../min/lib'
. PATH_SEPARATOR . ini_get('include_path') . PATH_SEPARATOR . ini_get('include_path')
); );
// eh why not // eh why not
require 'Minify/HTML.php'; require 'Minify/HTML.php';
require 'Minify/CSS.php'; require 'Minify/CSS.php';
require 'Minify/Javascript.php'; require 'Minify/Javascript.php';
$arg2 = null; $arg2 = null;
switch ($m[1]) { switch ($m[1]) {
case 'js': case 'js':
$type = 'Javascript'; $type = 'Javascript';
break; break;
case 'css': case 'css':
$type = 'CSS'; $type = 'CSS';
break; break;
case 'html': case 'html': // fallthrough
$type = 'HTML'; case 'htm': // fallthrough
$arg2 = array( case 'xhtml':
'cssMinifier' => array('Minify_CSS', 'minify') $type = 'HTML';
,'jsMinifier' => array('Minify_Javascript', 'minify') $arg2 = array(
); 'cssMinifier' => array('Minify_CSS', 'minify')
} ,'jsMinifier' => array('Minify_Javascript', 'minify')
$func = array('Minify_' . $type, 'minify'); );
}
$out = call_user_func($func, file_get_contents($_FILES['subject']['tmp_name']), $arg2); $func = array('Minify_' . $type, 'minify');
header('Content-Type: application/octet-stream'); $out = call_user_func($func, file_get_contents($_FILES['subject']['tmp_name']), $arg2);
header('Content-Transfer-Encoding: binary');
header('Content-Disposition: attachment; filename="' header('Content-Type: application/octet-stream');
. preg_replace('/\\.(\w+)$/', '.min.$1', $_FILES['subject']['name']) header('Content-Transfer-Encoding: binary');
. '"'); header('Content-Disposition: attachment; filename="'
. preg_replace('/\\.(\w+)$/', '.min.$1', $_FILES['subject']['name'])
echo $out; . '"');
exit();
} //@unlink($_FILES['subject']['tmp_name']);
echo $out;
?> exit();
<form enctype="multipart/form-data" action="" method="post"> }
<p>Minify <input type="file" name="subject" /><br />
<input type="submit" name="method" value="Go!" /> ?>
</p> <form enctype="multipart/form-data" action="" method="post">
<p>Minify <input type="file" name="subject" /><br />
<input type="submit" name="method" value="Go!" />
</p>
</form> </form>

View File

@@ -1,6 +1,6 @@
<?php <?php
require '../config.php'; // just to set include_path require '../config.php'; // just to set include_path
require 'Minify.php'; require 'Minify.php';
Minify::serve('Version1'); Minify::serve('Version1');