2008-08-29 22:56:34 +00:00
< ? php
/**
2016-01-22 00:30:38 +02:00
* Class Minify
2008-08-29 22:56:34 +00:00
* @ package Minify
*/
2016-01-22 00:30:38 +02:00
2016-02-27 01:13:28 -05:00
use Psr\Log\LoggerInterface ;
2008-08-29 22:56:34 +00:00
/**
* Minify - Combines , minifies , and caches JavaScript and CSS files on demand .
*
* See README for usage instructions ( for now ) .
*
* This library was inspired by { @ link mailto : flashkot @ mail . ru jscsscomp by Maxim Martynyuk }
* and by the article { @ link http :// www . hunlock . com / blogs / Supercharged_Javascript " Supercharged JavaScript " by Patrick Hunlock } .
*
* Requires PHP 5.1 . 0.
* Tested on PHP 5.1 . 6.
*
* @ package Minify
* @ author Ryan Grove < ryan @ wonko . com >
* @ author Stephen Clay < steve @ mrclay . org >
* @ copyright 2008 Ryan Grove , Stephen Clay . All rights reserved .
* @ license http :// opensource . org / licenses / bsd - license . php New BSD License
2015-09-29 10:12:47 -04:00
* @ link https :// github . com / mrclay / minify
2008-08-29 22:56:34 +00:00
*/
class Minify {
2016-01-22 00:30:38 +02:00
2015-09-29 10:12:47 -04:00
const VERSION = '3.0.0' ;
2008-08-29 22:56:34 +00:00
const TYPE_CSS = 'text/css' ;
const TYPE_HTML = 'text/html' ;
// there is some debate over the ideal JS Content-Type, but this is the
// Apache default and what Yahoo! uses..
const TYPE_JS = 'application/x-javascript' ;
2015-09-27 16:25:33 -04:00
const URL_DEBUG = 'https://github.com/mrclay/minify/blob/master/docs/Debugging.wiki.md' ;
2014-09-22 15:04:05 -04:00
2014-09-23 11:09:09 -04:00
/**
* Any Minify_Cache_ * object or null ( i . e . no server cache is used )
*
* @ var Minify_CacheInterface
*/
private $cache = null ;
2014-09-22 15:04:05 -04:00
2009-02-26 17:06:31 +00:00
/**
2014-09-23 11:09:09 -04:00
* Active controller for current request
2009-02-26 17:06:31 +00:00
*
2014-09-23 11:09:09 -04:00
* @ var Minify_Controller_Base
2009-02-26 17:06:31 +00:00
*/
2014-09-23 11:09:09 -04:00
protected $controller = null ;
2011-09-03 20:39:25 -04:00
/**
2014-09-23 11:09:09 -04:00
* @ var Minify_SourceInterface []
2011-09-03 20:39:25 -04:00
*/
2014-09-23 11:09:09 -04:00
protected $sources ;
/**
* @ var string
*/
protected $selectionId ;
/**
* Options for current request
*
* @ var array
*/
protected $options = null ;
2016-02-27 01:13:28 -05:00
/**
* @ var LoggerInterface | null
*/
protected $logger = null ;
2014-09-23 11:09:09 -04:00
/**
* @ param Minify_CacheInterface $cache
2016-02-27 01:13:28 -05:00
* @ param LoggerInterface $logger
2014-09-23 11:09:09 -04:00
*/
2016-02-27 01:13:28 -05:00
public function __construct ( Minify_CacheInterface $cache , LoggerInterface $logger = null ) {
2014-09-22 15:04:05 -04:00
$this -> cache = $cache ;
2016-02-27 01:13:28 -05:00
$this -> logger = $logger ;
2014-09-20 15:15:25 +03:00
}
2014-09-23 11:09:09 -04:00
/**
* Get default Minify options .
*
* @ return array options for Minify
*/
public function getDefaultOptions ()
{
return array (
'isPublic' => true ,
'encodeOutput' => function_exists ( 'gzdeflate' ),
'encodeMethod' => null , // determine later
'encodeLevel' => 9 ,
'minifiers' => array (
2015-09-28 15:36:52 -04:00
Minify :: TYPE_JS => array ( 'JSMin\\JSMin' , 'minify' ),
2015-09-28 20:30:20 -04:00
Minify :: TYPE_CSS => array ( 'Minify_CSSmin' , 'minify' ),
2014-09-23 11:09:09 -04:00
Minify :: TYPE_HTML => array ( 'Minify_HTML' , 'minify' ),
),
'minifierOptions' => array (), // no minifier options
'contentTypeCharset' => 'utf-8' ,
'maxAge' => 1800 , // 30 minutes
'rewriteCssUris' => true ,
'bubbleCssImports' => false ,
'quiet' => false , // serve() will send headers and output
'debug' => false ,
2015-09-27 17:44:11 -04:00
'concatOnly' => false ,
2014-09-23 11:09:09 -04:00
// if you override these, the response codes MUST be directly after
// the first space.
'badRequestHeader' => 'HTTP/1.0 400 Bad Request' ,
'errorHeader' => 'HTTP/1.0 500 Internal Server Error' ,
// callback function to see/modify content of all sources
'postprocessor' => null ,
// file to require to load preprocessor
'postprocessorRequire' => null ,
/**
* If this string is not empty AND the serve () option 'bubbleCssImports' is
* NOT set , then serve () will check CSS files for @ import declarations that
* appear too late in the combined stylesheet . If found , serve () will prepend
* the output with this warning .
*/
2015-09-27 16:25:33 -04:00
'importWarning' => " /* See https://github.com/mrclay/minify/blob/master/docs/CommonProblems.wiki.md#imports-can-appear-in-invalid-locations-in-combined-css-files */ \n "
2014-09-23 11:09:09 -04:00
);
}
2016-01-22 00:30:38 +02:00
2008-08-29 22:56:34 +00:00
/**
2016-01-22 00:30:38 +02:00
* Serve a request for a minified file .
*
2014-09-23 11:09:09 -04:00
* Here are the available options and defaults :
2016-01-22 00:30:38 +02:00
*
* 'isPublic' : send " public " instead of " private " in Cache - Control
2008-08-29 22:56:34 +00:00
* headers , allowing shared caches to cache the output . ( default true )
2016-01-22 00:30:38 +02:00
*
2008-08-29 22:56:34 +00:00
* 'quiet' : set to true to have serve () return an array rather than sending
* any headers / output ( default false )
2016-01-22 00:30:38 +02:00
*
2009-03-30 01:47:40 +00:00
* 'encodeOutput' : set to false to disable content encoding , and not send
* the Vary header ( default true )
2016-01-22 00:30:38 +02:00
*
* 'encodeMethod' : generally you should let this be determined by
2008-08-29 22:56:34 +00:00
* HTTP_Encoder ( leave null ), but you can force a particular encoding
2009-06-30 19:44:55 +00:00
* to be returned , by setting this to 'gzip' or '' ( no encoding )
2016-01-22 00:30:38 +02:00
*
2008-08-29 22:56:34 +00:00
* 'encodeLevel' : level of encoding compression ( 0 to 9 , default 9 )
2016-01-22 00:30:38 +02:00
*
2008-08-29 22:56:34 +00:00
* 'contentTypeCharset' : appended to the Content - Type header sent . Set to a falsey
2016-01-22 00:30:38 +02:00
* value to remove . ( default 'utf-8' )
*
2008-08-29 22:56:34 +00:00
* 'maxAge' : set this to the number of seconds the client should use its cache
* before revalidating with the server . This sets Cache - Control : max - age and the
* Expires header . Unlike the old 'setExpires' setting , this setting will NOT
* prevent conditional GETs . Note this has nothing to do with server - side caching .
2016-01-22 00:30:38 +02:00
*
2008-08-18 23:38:39 +00:00
* 'rewriteCssUris' : If true , serve () will automatically set the 'currentDir'
* minifier option to enable URI rewriting in CSS files ( default true )
2016-01-22 00:30:38 +02:00
*
2009-02-26 17:06:31 +00:00
* 'bubbleCssImports' : If true , all @ import declarations in combined CSS
* files will be move to the top . Note this may alter effective CSS values
* due to a change in order . ( default false )
2016-01-22 00:30:38 +02:00
*
2008-08-29 22:56:34 +00:00
* 'debug' : set to true to minify all sources with the 'Lines' controller , which
* eases the debugging of combined files . This also prevents 304 responses .
* @ see Minify_Lines :: minify ()
2015-09-02 17:08:33 -03:00
*
* 'concatOnly' : set to true to disable minification and simply concatenate the files .
* For JS , no minifier will be used . For CSS , only URI rewriting is still performed .
2016-01-22 00:30:38 +02:00
*
* 'minifiers' : to override Minify ' s default choice of minifier function for
* a particular content - type , specify your callback under the key of the
2008-08-29 22:56:34 +00:00
* content - type :
* < code >
* // call customCssMinifier($css) for all CSS minification
* $options [ 'minifiers' ][ Minify :: TYPE_CSS ] = 'customCssMinifier' ;
2016-01-22 00:30:38 +02:00
*
2008-08-29 22:56:34 +00:00
* // don't minify Javascript at all
* $options [ 'minifiers' ][ Minify :: TYPE_JS ] = '' ;
* </ code >
2016-01-22 00:30:38 +02:00
*
2008-08-29 22:56:34 +00:00
* 'minifierOptions' : to send options to the minifier function , specify your options
2016-01-22 00:30:38 +02:00
* under the key of the content - type . E . g . To send the CSS minifier an option :
2008-08-29 22:56:34 +00:00
* < code >
2016-01-22 00:30:38 +02:00
* // give CSS minifier array('optionName' => 'optionValue') as 2nd argument
2008-08-29 22:56:34 +00:00
* $options [ 'minifierOptions' ][ Minify :: TYPE_CSS ][ 'optionName' ] = 'optionValue' ;
* </ code >
2016-01-22 00:30:38 +02:00
*
* 'contentType' : ( optional ) this is only needed if your file extension is not
2008-08-29 22:56:34 +00:00
* js / css / html . The given content - type will be sent regardless of source file
* extension , so this should not be used in a Groups config with other
* Javascript / CSS files .
2014-09-23 11:09:09 -04:00
*
* 'importWarning' : serve () will check CSS files for @ import declarations that
* appear too late in the combined stylesheet . If found , serve () will prepend
* the output with this warning . To disable this , set this option to empty string .
2016-01-22 00:30:38 +02:00
*
2014-09-23 11:09:09 -04:00
* Any controller options are documented in that controller ' s createConfiguration () method .
2016-01-22 00:30:38 +02:00
*
2014-09-23 11:09:09 -04:00
* @ param Minify_ControllerInterface $controller instance of subclass of Minify_Controller_Base
2016-01-22 00:30:38 +02:00
*
2014-09-23 11:09:09 -04:00
* @ param array $options controller / serve options
2016-01-22 00:30:38 +02:00
*
2012-06-04 12:17:55 -04:00
* @ return null | array if the 'quiet' option is set to true , an array
2008-08-29 22:56:34 +00:00
* with keys " success " ( bool ), " statusCode " ( int ), " content " ( string ), and
* " headers " ( array ) .
2012-06-04 12:17:55 -04:00
*
* @ throws Exception
2008-08-29 22:56:34 +00:00
*/
2014-09-23 11:09:09 -04:00
public function serve ( Minify_ControllerInterface $controller , $options = array ())
2009-04-21 20:53:29 +00:00
{
2014-09-23 11:09:09 -04:00
$options = array_merge ( $this -> getDefaultOptions (), $options );
$config = $controller -> createConfiguration ( $options );
$this -> sources = $config -> getSources ();
$this -> selectionId = $config -> getSelectionId ();
$this -> options = $this -> analyzeSources ( $config -> getOptions ());
2016-02-27 01:13:28 -05:00
if ( ! $this -> options [ 'quiet' ] && ! headers_sent ()) {
ini_set ( 'zlib.output_compression' , '0' );
}
2008-08-29 22:56:34 +00:00
// check request validity
2014-09-23 11:09:09 -04:00
if ( ! $this -> sources ) {
2008-08-29 22:56:34 +00:00
// invalid request!
2014-09-22 15:04:05 -04:00
if ( ! $this -> options [ 'quiet' ]) {
$this -> errorExit ( $this -> options [ 'badRequestHeader' ], self :: URL_DEBUG );
2008-08-29 22:56:34 +00:00
} else {
2014-09-22 15:04:05 -04:00
list (, $statusCode ) = explode ( ' ' , $this -> options [ 'badRequestHeader' ]);
2016-01-22 00:30:38 +02:00
2008-08-29 22:56:34 +00:00
return array (
2014-09-23 11:09:09 -04:00
'success' => false ,
'statusCode' => ( int ) $statusCode ,
'content' => '' ,
'headers' => array (),
2008-08-29 22:56:34 +00:00
);
}
}
2016-01-22 00:30:38 +02:00
2014-09-22 15:04:05 -04:00
$this -> controller = $controller ;
2016-01-22 00:30:38 +02:00
2014-09-22 15:04:05 -04:00
if ( $this -> options [ 'debug' ]) {
2014-09-23 11:09:09 -04:00
$this -> setupDebug ();
2014-09-22 15:04:05 -04:00
$this -> options [ 'maxAge' ] = 0 ;
2008-08-29 22:56:34 +00:00
}
2016-01-22 00:30:38 +02:00
2009-03-30 01:47:40 +00:00
// determine encoding
2014-09-22 15:04:05 -04:00
if ( $this -> options [ 'encodeOutput' ]) {
Work on: Issue 125, Issue 126, Issue 132, Issue 134, Issue 138, Issue 139, Issue 147, Issue 149, Issue 151, Issue 162, Issue 166
2010-05-09 16:43:47 +00:00
$sendVary = true ;
2014-09-22 15:04:05 -04:00
if ( $this -> options [ 'encodeMethod' ] !== null ) {
2009-03-30 01:47:40 +00:00
// controller specifically requested this
2014-09-22 15:04:05 -04:00
$contentEncoding = $this -> options [ 'encodeMethod' ];
2009-03-30 01:47:40 +00:00
} else {
// sniff request header
2012-09-30 17:51:34 -04:00
// depending on what the client accepts, $contentEncoding may be
2009-03-30 01:47:40 +00:00
// 'x-gzip' while our internal encodeMethod is 'gzip'. Calling
2009-06-30 19:44:55 +00:00
// getAcceptedEncoding(false, false) leaves out compress and deflate as options.
2014-09-22 15:04:05 -04:00
list ( $this -> options [ 'encodeMethod' ], $contentEncoding ) = HTTP_Encoder :: getAcceptedEncoding ( false , false );
Work on: Issue 125, Issue 126, Issue 132, Issue 134, Issue 138, Issue 139, Issue 147, Issue 149, Issue 151, Issue 162, Issue 166
2010-05-09 16:43:47 +00:00
$sendVary = ! HTTP_Encoder :: isBuggyIe ();
2009-03-30 01:47:40 +00:00
}
} else {
2014-09-22 15:04:05 -04:00
$this -> options [ 'encodeMethod' ] = '' ; // identity (no encoding)
2009-03-30 01:47:40 +00:00
}
2016-01-22 00:30:38 +02:00
2008-08-29 22:56:34 +00:00
// check client cache
$cgOptions = array (
2014-09-23 11:09:09 -04:00
'lastModifiedTime' => $this -> options [ 'lastModifiedTime' ],
'isPublic' => $this -> options [ 'isPublic' ],
'encoding' => $this -> options [ 'encodeMethod' ],
2008-08-29 22:56:34 +00:00
);
2014-09-23 11:09:09 -04:00
2014-09-22 15:04:05 -04:00
if ( $this -> options [ 'maxAge' ] > 0 ) {
$cgOptions [ 'maxAge' ] = $this -> options [ 'maxAge' ];
} elseif ( $this -> options [ 'debug' ]) {
2010-05-10 07:44:40 +00:00
$cgOptions [ 'invalidate' ] = true ;
2008-08-29 22:56:34 +00:00
}
2014-09-23 11:09:09 -04:00
2008-08-29 22:56:34 +00:00
$cg = new HTTP_ConditionalGet ( $cgOptions );
if ( $cg -> cacheIsValid ) {
// client's cache is valid
2014-09-22 15:04:05 -04:00
if ( ! $this -> options [ 'quiet' ]) {
2008-08-29 22:56:34 +00:00
$cg -> sendHeaders ();
2016-01-22 00:30:38 +02:00
2008-08-29 22:56:34 +00:00
return ;
} else {
return array (
2014-09-23 11:09:09 -04:00
'success' => true ,
'statusCode' => 304 ,
'content' => '' ,
'headers' => $cg -> getHeaders (),
2008-08-29 22:56:34 +00:00
);
}
} else {
// client will need output
$headers = $cg -> getHeaders ();
unset ( $cg );
}
2016-01-22 00:30:38 +02:00
2014-09-23 11:09:09 -04:00
if ( $this -> options [ 'contentType' ] === self :: TYPE_CSS && $this -> options [ 'rewriteCssUris' ]) {
$this -> setupUriRewrites ();
2008-08-18 23:38:39 +00:00
}
2015-09-02 17:08:33 -03:00
2015-09-27 17:44:11 -04:00
if ( $this -> options [ 'concatOnly' ]) {
$this -> options [ 'minifiers' ][ self :: TYPE_JS ] = false ;
foreach ( $this -> sources as $key => $source ) {
if ( $this -> options [ 'contentType' ] === self :: TYPE_JS ) {
$source -> setMinifier ( " " );
} elseif ( $this -> options [ 'contentType' ] === self :: TYPE_CSS ) {
2015-09-28 20:30:20 -04:00
$source -> setMinifier ( array ( 'Minify_CSSmin' , 'minify' ));
2015-09-27 17:44:11 -04:00
$sourceOpts = $source -> getMinifierOptions ();
$sourceOpts [ 'compress' ] = false ;
$source -> setMinifierOptions ( $sourceOpts );
2008-08-18 23:38:39 +00:00
}
}
}
2016-01-22 00:30:38 +02:00
2008-08-29 22:56:34 +00:00
// check server cache
2014-09-22 15:04:05 -04:00
if ( ! $this -> options [ 'debug' ]) {
2008-08-29 22:56:34 +00:00
// using cache
2016-01-22 00:30:38 +02:00
// the goal is to use only the cache methods to sniff the length and
2008-08-29 22:56:34 +00:00
// output the content, as they do not require ever loading the file into
// memory.
2014-09-22 15:04:05 -04:00
$cacheId = $this -> _getCacheId ();
2014-09-23 11:09:09 -04:00
$fullCacheId = ( $this -> options [ 'encodeMethod' ]) ? $cacheId . '.gz' : $cacheId ;
2008-08-29 22:56:34 +00:00
// check cache for valid entry
2016-01-22 00:30:38 +02:00
$cacheIsReady = $this -> cache -> isValid ( $fullCacheId , $this -> options [ 'lastModifiedTime' ]);
2008-08-29 22:56:34 +00:00
if ( $cacheIsReady ) {
2016-01-22 00:30:38 +02:00
$cacheContentLength = $this -> cache -> getSize ( $fullCacheId );
2008-08-29 22:56:34 +00:00
} else {
// generate & cache content
Work on: Issue 125, Issue 126, Issue 132, Issue 134, Issue 138, Issue 139, Issue 147, Issue 149, Issue 151, Issue 162, Issue 166
2010-05-09 16:43:47 +00:00
try {
2014-09-22 15:04:05 -04:00
$content = $this -> combineMinify ();
Work on: Issue 125, Issue 126, Issue 132, Issue 134, Issue 138, Issue 139, Issue 147, Issue 149, Issue 151, Issue 162, Issue 166
2010-05-09 16:43:47 +00:00
} catch ( Exception $e ) {
2016-02-27 01:13:28 -05:00
$this -> logger && $this -> logger -> critical ( $e -> getMessage ());
2014-09-22 15:04:05 -04:00
if ( ! $this -> options [ 'quiet' ]) {
$this -> errorExit ( $this -> options [ 'errorHeader' ], self :: URL_DEBUG );
Work on: Issue 125, Issue 126, Issue 132, Issue 134, Issue 138, Issue 139, Issue 147, Issue 149, Issue 151, Issue 162, Issue 166
2010-05-09 16:43:47 +00:00
}
throw $e ;
}
2014-09-22 15:04:05 -04:00
$this -> cache -> store ( $cacheId , $content );
if ( function_exists ( 'gzencode' ) && $this -> options [ 'encodeMethod' ]) {
$this -> cache -> store ( $cacheId . '.gz' , gzencode ( $content , $this -> options [ 'encodeLevel' ]));
2008-10-08 14:20:34 +00:00
}
2008-08-29 22:56:34 +00:00
}
} else {
// no cache
$cacheIsReady = false ;
Work on: Issue 125, Issue 126, Issue 132, Issue 134, Issue 138, Issue 139, Issue 147, Issue 149, Issue 151, Issue 162, Issue 166
2010-05-09 16:43:47 +00:00
try {
2014-09-22 15:04:05 -04:00
$content = $this -> combineMinify ();
Work on: Issue 125, Issue 126, Issue 132, Issue 134, Issue 138, Issue 139, Issue 147, Issue 149, Issue 151, Issue 162, Issue 166
2010-05-09 16:43:47 +00:00
} catch ( Exception $e ) {
2016-02-27 01:13:28 -05:00
$this -> logger && $this -> logger -> critical ( $e -> getMessage ());
2014-09-22 15:04:05 -04:00
if ( ! $this -> options [ 'quiet' ]) {
$this -> errorExit ( $this -> options [ 'errorHeader' ], self :: URL_DEBUG );
Work on: Issue 125, Issue 126, Issue 132, Issue 134, Issue 138, Issue 139, Issue 147, Issue 149, Issue 151, Issue 162, Issue 166
2010-05-09 16:43:47 +00:00
}
throw $e ;
}
2008-08-29 22:56:34 +00:00
}
2014-09-22 15:04:05 -04:00
if ( ! $cacheIsReady && $this -> options [ 'encodeMethod' ]) {
2008-08-29 22:56:34 +00:00
// still need to encode
2014-09-22 15:04:05 -04:00
$content = gzencode ( $content , $this -> options [ 'encodeLevel' ]);
2008-08-29 22:56:34 +00:00
}
2016-01-22 00:30:38 +02:00
2008-08-29 22:56:34 +00:00
// add headers
2014-09-23 11:09:09 -04:00
if ( $cacheIsReady ) {
$headers [ 'Content-Length' ] = $cacheContentLength ;
} else {
if ( function_exists ( 'mb_strlen' ) && (( int ) ini_get ( 'mbstring.func_overload' ) & 2 )) {
$headers [ 'Content-Length' ] = mb_strlen ( $content , '8bit' );
} else {
$headers [ 'Content-Length' ] = strlen ( $content );
}
}
$headers [ 'Content-Type' ] = $this -> options [ 'contentType' ];
if ( $this -> options [ 'contentTypeCharset' ]) {
$headers [ 'Content-Type' ] .= '; charset=' . $this -> options [ 'contentTypeCharset' ];
}
2014-09-22 15:04:05 -04:00
if ( $this -> options [ 'encodeMethod' ] !== '' ) {
2008-08-29 22:56:34 +00:00
$headers [ 'Content-Encoding' ] = $contentEncoding ;
2009-03-30 01:47:40 +00:00
}
2014-09-22 15:04:05 -04:00
if ( $this -> options [ 'encodeOutput' ] && $sendVary ) {
2008-08-29 22:56:34 +00:00
$headers [ 'Vary' ] = 'Accept-Encoding' ;
}
2014-09-22 15:04:05 -04:00
if ( ! $this -> options [ 'quiet' ]) {
2008-08-29 22:56:34 +00:00
// output headers & content
foreach ( $headers as $name => $val ) {
header ( $name . ': ' . $val );
}
if ( $cacheIsReady ) {
2014-09-22 15:04:05 -04:00
$this -> cache -> display ( $fullCacheId );
2008-08-29 22:56:34 +00:00
} else {
echo $content ;
}
} else {
return array (
2014-09-23 11:09:09 -04:00
'success' => true ,
'statusCode' => 200 ,
'content' => $cacheIsReady ? $this -> cache -> fetch ( $fullCacheId ) : $content ,
'headers' => $headers ,
2008-08-29 22:56:34 +00:00
);
}
}
2014-09-21 11:52:29 +03:00
2008-09-04 00:38:08 +00:00
/**
* Return combined minified content for a set of sources
*
* No internal caching will be used and the content will not be HTTP encoded .
2016-01-22 00:30:38 +02:00
*
2008-09-04 00:38:08 +00:00
* @ param array $sources array of filepaths and / or Minify_Source objects
2016-01-22 00:30:38 +02:00
*
2015-09-29 13:17:31 -04:00
* @ param array $options ( optional ) array of options for serve .
2016-01-22 00:30:38 +02:00
*
2008-09-04 00:38:08 +00:00
* @ return string
*/
2014-09-22 15:04:05 -04:00
public function combine ( $sources , $options = array ())
2008-09-04 00:38:08 +00:00
{
2015-09-29 13:17:31 -04:00
$tmpCache = $this -> cache ;
2014-09-23 11:09:09 -04:00
$this -> cache = new Minify_Cache_Null ();
2015-09-29 13:17:31 -04:00
$env = new Minify_Env ();
2015-11-17 16:46:23 +02:00
$sourceFactory = new Minify_Source_Factory ( $env , array (
2015-09-29 13:17:31 -04:00
'checkAllowDirs' => false ,
2015-11-17 16:46:23 +02:00
), $this -> cache );
2016-02-27 01:13:28 -05:00
$controller = new Minify_Controller_Files ( $env , $sourceFactory , $this -> logger );
2015-09-29 13:17:31 -04:00
$options = array_merge ( $options , array (
2014-09-23 11:09:09 -04:00
'files' => ( array ) $sources ,
'quiet' => true ,
'encodeMethod' => '' ,
'lastModifiedTime' => 0 ,
2015-09-29 13:17:31 -04:00
));
2014-09-23 11:09:09 -04:00
$out = $this -> serve ( $controller , $options );
2015-09-29 13:17:31 -04:00
$this -> cache = $tmpCache ;
2016-01-22 00:30:38 +02:00
2008-09-04 00:38:08 +00:00
return $out [ 'content' ];
}
2014-09-22 15:04:05 -04:00
2012-03-10 11:48:32 -05:00
/**
2015-09-29 14:35:43 -04:00
* Show an error page
2012-03-10 11:48:32 -05:00
*
2015-09-29 15:59:02 -04:00
* @ param string $header Full header . E . g . 'HTTP/1.0 500 Internal Server Error'
* @ param string $url URL to direct the user to
* @ param string $msgHtml HTML message for the client
2015-09-29 14:35:43 -04:00
*
* @ return void
* @ internal This is not part of the public API and is subject to change
* @ access private
2012-03-10 11:48:32 -05:00
*/
2015-09-29 15:59:02 -04:00
public function errorExit ( $header , $url = '' , $msgHtml = '' )
Work on: Issue 125, Issue 126, Issue 132, Issue 134, Issue 138, Issue 139, Issue 147, Issue 149, Issue 151, Issue 162, Issue 166
2010-05-09 16:43:47 +00:00
{
$url = htmlspecialchars ( $url , ENT_QUOTES );
list (, $h1 ) = explode ( ' ' , $header , 2 );
$h1 = htmlspecialchars ( $h1 );
2010-07-25 05:11:41 +00:00
// FastCGI environments require 3rd arg to header() to be set
list (, $code ) = explode ( ' ' , $header , 3 );
header ( $header , true , $code );
Work on: Issue 125, Issue 126, Issue 132, Issue 134, Issue 138, Issue 139, Issue 147, Issue 149, Issue 151, Issue 162, Issue 166
2010-05-09 16:43:47 +00:00
header ( 'Content-Type: text/html; charset=utf-8' );
echo " <h1> $h1 </h1> " ;
2015-09-29 15:59:02 -04:00
if ( $msgHtml ) {
echo $msgHtml ;
}
if ( $url ) {
echo " <p>Please see <a href=' $url '> $url </a>.</p> " ;
}
2014-09-20 10:47:59 +02:00
exit ;
Work on: Issue 125, Issue 126, Issue 132, Issue 134, Issue 138, Issue 139, Issue 147, Issue 149, Issue 151, Issue 162, Issue 166
2010-05-09 16:43:47 +00:00
}
2014-09-23 11:09:09 -04:00
/**
* Setup CSS sources for URI rewriting
*/
protected function setupUriRewrites ()
{
foreach ( $this -> sources as $key => $source ) {
$file = $source -> getFilePath ();
$minifyOptions = $source -> getMinifierOptions ();
if ( $file
&& ! isset ( $minifyOptions [ 'currentDir' ])
&& ! isset ( $minifyOptions [ 'prependRelativePath' ])
) {
$minifyOptions [ 'currentDir' ] = dirname ( $file );
$source -> setMinifierOptions ( $minifyOptions );
}
}
}
2008-08-29 22:56:34 +00:00
/**
* Set up sources to use Minify_Lines
*/
2014-09-23 11:09:09 -04:00
protected function setupDebug ()
2008-08-29 22:56:34 +00:00
{
2014-09-23 11:09:09 -04:00
foreach ( $this -> sources as $source ) {
2014-09-20 23:37:50 -04:00
$source -> setMinifier ( array ( 'Minify_Lines' , 'minify' ));
2008-08-29 22:56:34 +00:00
$id = $source -> getId ();
2014-09-20 23:37:50 -04:00
$source -> setMinifierOptions ( array (
2014-09-23 11:09:09 -04:00
'id' => ( is_file ( $id ) ? basename ( $id ) : $id ),
2014-09-20 23:37:50 -04:00
));
2008-08-29 22:56:34 +00:00
}
}
2016-01-22 00:30:38 +02:00
2008-08-29 22:56:34 +00:00
/**
* Combines sources and minifies the result .
*
* @ return string
2013-07-19 22:46:02 -04:00
*
* @ throws Exception
2008-08-29 22:56:34 +00:00
*/
2014-09-22 15:04:05 -04:00
protected function combineMinify ()
2009-04-21 20:53:29 +00:00
{
2014-09-22 15:04:05 -04:00
$type = $this -> options [ 'contentType' ]; // ease readability
2016-01-22 00:30:38 +02:00
2009-01-06 16:32:28 +00:00
// when combining scripts, make sure all statements separated and
// trailing single line comment is terminated
2014-09-23 11:09:09 -04:00
$implodeSeparator = ( $type === self :: TYPE_JS ) ? " \n ; " : '' ;
2008-08-29 22:56:34 +00:00
// allow the user to pass a particular array of options to each
// minifier (designated by type). source objects may still override
// these
2014-09-23 11:09:09 -04:00
if ( isset ( $this -> options [ 'minifierOptions' ][ $type ])) {
$defaultOptions = $this -> options [ 'minifierOptions' ][ $type ];
} else {
$defaultOptions = array ();
}
2008-08-29 22:56:34 +00:00
// if minifier not set, default is no minification. source objects
// may still override this
2014-09-23 11:09:09 -04:00
if ( isset ( $this -> options [ 'minifiers' ][ $type ])) {
$defaultMinifier = $this -> options [ 'minifiers' ][ $type ];
} else {
$defaultMinifier = false ;
}
2011-10-13 22:58:50 -04:00
// process groups of sources with identical minifiers/options
$content = array ();
$i = 0 ;
2014-09-23 11:09:09 -04:00
$l = count ( $this -> sources );
2011-10-13 22:58:50 -04:00
$groupToProcessTogether = array ();
$lastMinifier = null ;
$lastOptions = null ;
do {
// get next source
$source = null ;
if ( $i < $l ) {
2014-09-23 11:09:09 -04:00
$source = $this -> sources [ $i ];
2011-10-13 22:58:50 -04:00
$sourceContent = $source -> getContent ();
// allow the source to override our minifier and options
2014-09-20 23:37:50 -04:00
$minifier = $source -> getMinifier ();
if ( ! $minifier ) {
$minifier = $defaultMinifier ;
}
$options = array_merge ( $defaultOptions , $source -> getMinifierOptions ());
2011-10-13 22:58:50 -04:00
}
// do we need to process our group right now?
2011-10-14 16:10:05 -04:00
if ( $i > 0 // yes, we have at least the first group populated
2011-10-13 22:58:50 -04:00
&& (
! $source // yes, we ran out of sources
|| $type === self :: TYPE_CSS // yes, to process CSS individually (avoiding PCRE bugs/limits)
|| $minifier !== $lastMinifier // yes, minifier changed
|| $options !== $lastOptions ) // yes, options changed
)
{
// minify previous sources with last settings
$imploded = implode ( $implodeSeparator , $groupToProcessTogether );
$groupToProcessTogether = array ();
if ( $lastMinifier ) {
try {
$content [] = call_user_func ( $lastMinifier , $imploded , $lastOptions );
} catch ( Exception $e ) {
throw new Exception ( " Exception in minifier: " . $e -> getMessage ());
}
} else {
$content [] = $imploded ;
2010-05-10 07:44:40 +00:00
}
2008-08-29 22:56:34 +00:00
}
2011-10-13 22:58:50 -04:00
// add content to the group
if ( $source ) {
$groupToProcessTogether [] = $sourceContent ;
$lastMinifier = $minifier ;
$lastOptions = $options ;
}
$i ++ ;
} while ( $source );
$content = implode ( $implodeSeparator , $content );
2016-01-22 00:30:38 +02:00
2009-02-26 17:06:31 +00:00
if ( $type === self :: TYPE_CSS && false !== strpos ( $content , '@import' )) {
2014-09-22 15:04:05 -04:00
$content = $this -> handleCssImports ( $content );
2009-02-26 17:06:31 +00:00
}
2016-01-22 00:30:38 +02:00
2008-08-29 22:56:34 +00:00
// do any post-processing (esp. for editing build URIs)
2014-09-22 15:04:05 -04:00
if ( $this -> options [ 'postprocessorRequire' ]) {
require_once $this -> options [ 'postprocessorRequire' ];
2008-08-29 22:56:34 +00:00
}
2014-09-22 15:04:05 -04:00
if ( $this -> options [ 'postprocessor' ]) {
$content = call_user_func ( $this -> options [ 'postprocessor' ], $content , $type );
2008-08-29 22:56:34 +00:00
}
2016-01-22 00:30:38 +02:00
2008-08-29 22:56:34 +00:00
return $content ;
}
2016-01-22 00:30:38 +02:00
2008-08-29 22:56:34 +00:00
/**
* Make a unique cache id for for this request .
2016-01-22 00:30:38 +02:00
*
* Any settings that could affect output are taken into consideration
2008-08-29 22:56:34 +00:00
*
2010-05-10 07:44:40 +00:00
* @ param string $prefix
*
2008-08-29 22:56:34 +00:00
* @ return string
*/
2014-09-22 15:04:05 -04:00
protected function _getCacheId ( $prefix = 'minify' )
2009-04-21 20:53:29 +00:00
{
2014-09-23 11:09:09 -04:00
$name = preg_replace ( '/[^a-zA-Z0-9\\.=_,]/' , '' , $this -> selectionId );
2010-05-10 07:44:40 +00:00
$name = preg_replace ( '/\\.+/' , '.' , $name );
2013-10-27 04:12:27 +00:00
$name = substr ( $name , 0 , 100 - 34 - strlen ( $prefix ));
2010-05-10 07:44:40 +00:00
$md5 = md5 ( serialize ( array (
2014-09-23 11:09:09 -04:00
Minify_SourceSet :: getDigest ( $this -> sources ),
$this -> options [ 'minifiers' ],
$this -> options [ 'minifierOptions' ],
$this -> options [ 'postprocessor' ],
$this -> options [ 'bubbleCssImports' ],
Minify :: VERSION ,
2008-08-29 22:56:34 +00:00
)));
2016-01-22 00:30:38 +02:00
2010-05-10 07:44:40 +00:00
return " { $prefix } _ { $name } _ { $md5 } " ;
2009-01-06 16:32:28 +00:00
}
2016-01-22 00:30:38 +02:00
2009-02-26 17:06:31 +00:00
/**
2012-03-10 11:48:32 -05:00
* Bubble CSS @ imports to the top or prepend a warning if an import is detected not at the top .
*
* @ param string $css
*
* @ return string
2009-02-26 17:06:31 +00:00
*/
2014-09-22 15:04:05 -04:00
protected function handleCssImports ( $css )
2009-04-21 20:53:29 +00:00
{
2014-09-22 15:04:05 -04:00
if ( $this -> options [ 'bubbleCssImports' ]) {
2009-02-26 17:06:31 +00:00
// bubble CSS imports
Work on: Issue 125, Issue 126, Issue 132, Issue 134, Issue 138, Issue 139, Issue 147, Issue 149, Issue 151, Issue 162, Issue 166
2010-05-09 16:43:47 +00:00
preg_match_all ( '/@import.*?;/' , $css , $imports );
2009-02-26 17:06:31 +00:00
$css = implode ( '' , $imports [ 0 ]) . preg_replace ( '/@import.*?;/' , '' , $css );
2016-01-22 00:30:38 +02:00
2014-09-23 11:09:09 -04:00
return $css ;
}
if ( '' === $this -> options [ 'importWarning' ]) {
return $css ;
}
// remove comments so we don't mistake { in a comment as a block
$noCommentCss = preg_replace ( '@/\\*[\\s\\S]*?\\*/@' , '' , $css );
$lastImportPos = strrpos ( $noCommentCss , '@import' );
$firstBlockPos = strpos ( $noCommentCss , '{' );
if ( false !== $lastImportPos
&& false !== $firstBlockPos
&& $firstBlockPos < $lastImportPos
) {
// { appears before @import : prepend warning
$css = $this -> options [ 'importWarning' ] . $css ;
2009-02-26 17:06:31 +00:00
}
2016-01-22 00:30:38 +02:00
2009-02-26 17:06:31 +00:00
return $css ;
}
2014-09-23 11:09:09 -04:00
/**
* Analyze sources ( if there are any ) and set $options 'contentType'
* and 'lastModifiedTime' if they already aren ' t .
*
* @ param array $options options for Minify
*
* @ return array options for Minify
*/
protected function analyzeSources ( $options = array ())
{
if ( ! $this -> sources ) {
return $options ;
}
$type = null ;
foreach ( $this -> sources as $source ) {
2015-09-29 13:18:10 -04:00
$sourceType = $source -> getContentType ();
if ( ! empty ( $options [ 'contentType' ])) {
// just verify sources have null content type or match the options
if ( $sourceType !== null && $sourceType !== $options [ 'contentType' ]) {
2016-02-27 01:13:28 -05:00
$this -> logger && $this -> logger -> warning ( 'ContentType mismatch' );
2016-01-22 00:30:38 +02:00
2016-02-27 01:13:28 -05:00
$this -> sources = array ();
2015-09-29 13:18:10 -04:00
return $options ;
}
continue ;
}
2014-09-23 11:09:09 -04:00
if ( $type === null ) {
2015-09-29 13:18:10 -04:00
$type = $sourceType ;
} elseif ( $sourceType !== $type ) {
2014-09-23 11:09:09 -04:00
2016-02-27 01:13:28 -05:00
$this -> logger && $this -> logger -> warning ( 'ContentType mismatch' );
2014-09-23 11:09:09 -04:00
$this -> sources = array ();
return $options ;
}
}
2015-09-29 13:18:10 -04:00
if ( empty ( $options [ 'contentType' ])) {
if ( null === $type ) {
$type = 'text/plain' ;
}
$options [ 'contentType' ] = $type ;
2014-09-23 11:09:09 -04:00
}
// last modified is needed for caching, even if setExpires is set
if ( ! isset ( $options [ 'lastModifiedTime' ])) {
$max = 0 ;
foreach ( $this -> sources as $source ) {
$max = max ( $source -> getLastModified (), $max );
}
$options [ 'lastModifiedTime' ] = $max ;
}
return $options ;
}
2008-08-29 22:56:34 +00:00
}