mirror of
https://github.com/vichan-devel/vichan.git
synced 2024-11-25 07:50:23 +01:00
Multiple changes.
Added $config['minify_js'] to compress Javascript with minify Added $config['additional_javascript_compile'] to put all Javascript files/addons into one file. Moved delete/report post controls to a shared template. Ability to have different Javascript files between boards.
This commit is contained in:
parent
1a3542b9ae
commit
9a133e87e5
@ -531,9 +531,16 @@
|
|||||||
|
|
||||||
// Automatically remove unnecessary whitespace when compiling HTML files from templates.
|
// Automatically remove unnecessary whitespace when compiling HTML files from templates.
|
||||||
$config['minify_html'] = true;
|
$config['minify_html'] = true;
|
||||||
|
// Minify Javascript using http://code.google.com/p/minify/
|
||||||
|
$config['minify_js'] = false;
|
||||||
|
|
||||||
// Additional Javascript files to include on board index and thread pages.
|
// Additional Javascript files to include on board index and thread pages.
|
||||||
// $config['additional_javascript'][] = 'something.js';
|
// $config['additional_javascript'][] = 'something.js';
|
||||||
|
// Where these script files are located (defaults to $config['root']).
|
||||||
|
// $config['additional_javascript_url'] = '/js/';
|
||||||
|
|
||||||
|
// Compile all additional scripts into one file ($config['file_script']) instead of including them seperately.
|
||||||
|
$config['additional_javascript_compile'] = false;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ====================
|
* ====================
|
||||||
|
1370
inc/contrib/minify/FirePHP.php
Executable file
1370
inc/contrib/minify/FirePHP.php
Executable file
File diff suppressed because it is too large
Load Diff
366
inc/contrib/minify/HTTP/ConditionalGet.php
Executable file
366
inc/contrib/minify/HTTP/ConditionalGet.php
Executable file
@ -0,0 +1,366 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Class HTTP_ConditionalGet
|
||||||
|
* @package Minify
|
||||||
|
* @subpackage HTTP
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implement conditional GET via a timestamp or hash of content
|
||||||
|
*
|
||||||
|
* E.g. Content from DB with update time:
|
||||||
|
* <code>
|
||||||
|
* list($updateTime, $content) = getDbUpdateAndContent();
|
||||||
|
* $cg = new HTTP_ConditionalGet(array(
|
||||||
|
* 'lastModifiedTime' => $updateTime
|
||||||
|
* ,'isPublic' => true
|
||||||
|
* ));
|
||||||
|
* $cg->sendHeaders();
|
||||||
|
* if ($cg->cacheIsValid) {
|
||||||
|
* exit();
|
||||||
|
* }
|
||||||
|
* echo $content;
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
|
* E.g. Shortcut for the above
|
||||||
|
* <code>
|
||||||
|
* HTTP_ConditionalGet::check($updateTime, true); // exits if client has cache
|
||||||
|
* echo $content;
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
|
* E.g. Content from DB with no update time:
|
||||||
|
* <code>
|
||||||
|
* $content = getContentFromDB();
|
||||||
|
* $cg = new HTTP_ConditionalGet(array(
|
||||||
|
* 'contentHash' => md5($content)
|
||||||
|
* ));
|
||||||
|
* $cg->sendHeaders();
|
||||||
|
* if ($cg->cacheIsValid) {
|
||||||
|
* exit();
|
||||||
|
* }
|
||||||
|
* echo $content;
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
|
* E.g. Static content with some static includes:
|
||||||
|
* <code>
|
||||||
|
* // before content
|
||||||
|
* $cg = new HTTP_ConditionalGet(array(
|
||||||
|
* 'lastUpdateTime' => max(
|
||||||
|
* filemtime(__FILE__)
|
||||||
|
* ,filemtime('/path/to/header.inc')
|
||||||
|
* ,filemtime('/path/to/footer.inc')
|
||||||
|
* )
|
||||||
|
* ));
|
||||||
|
* $cg->sendHeaders();
|
||||||
|
* if ($cg->cacheIsValid) {
|
||||||
|
* exit();
|
||||||
|
* }
|
||||||
|
* </code>
|
||||||
|
* @package Minify
|
||||||
|
* @subpackage HTTP
|
||||||
|
* @author Stephen Clay <steve@mrclay.org>
|
||||||
|
*/
|
||||||
|
class HTTP_ConditionalGet {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does the client have a valid copy of the requested resource?
|
||||||
|
*
|
||||||
|
* You'll want to check this after instantiating the object. If true, do
|
||||||
|
* not send content, just call sendHeaders() if you haven't already.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public $cacheIsValid = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $spec options
|
||||||
|
*
|
||||||
|
* 'isPublic': (bool) if false, the Cache-Control header will contain
|
||||||
|
* "private", allowing only browser caching. (default false)
|
||||||
|
*
|
||||||
|
* 'lastModifiedTime': (int) if given, both ETag AND Last-Modified headers
|
||||||
|
* will be sent with content. This is recommended.
|
||||||
|
*
|
||||||
|
* 'encoding': (string) if set, the header "Vary: Accept-Encoding" will
|
||||||
|
* always be sent and a truncated version of the encoding will be appended
|
||||||
|
* to the ETag. E.g. "pub123456;gz". This will also trigger a more lenient
|
||||||
|
* checking of the client's If-None-Match header, as the encoding portion of
|
||||||
|
* the ETag will be stripped before comparison.
|
||||||
|
*
|
||||||
|
* 'contentHash': (string) if given, only the ETag header can be sent with
|
||||||
|
* content (only HTTP1.1 clients can conditionally GET). The given string
|
||||||
|
* should be short with no quote characters and always change when the
|
||||||
|
* resource changes (recommend md5()). This is not needed/used if
|
||||||
|
* lastModifiedTime is given.
|
||||||
|
*
|
||||||
|
* 'eTag': (string) if given, this will be used as the ETag header rather
|
||||||
|
* than values based on lastModifiedTime or contentHash. Also the encoding
|
||||||
|
* string will not be appended to the given value as described above.
|
||||||
|
*
|
||||||
|
* 'invalidate': (bool) if true, the client cache will be considered invalid
|
||||||
|
* without testing. Effectively this disables conditional GET.
|
||||||
|
* (default false)
|
||||||
|
*
|
||||||
|
* 'maxAge': (int) if given, this will set the Cache-Control max-age in
|
||||||
|
* seconds, and also set the Expires header to the equivalent GMT date.
|
||||||
|
* After the max-age period has passed, the browser will again send a
|
||||||
|
* conditional GET to revalidate its cache.
|
||||||
|
*/
|
||||||
|
public function __construct($spec)
|
||||||
|
{
|
||||||
|
$scope = (isset($spec['isPublic']) && $spec['isPublic'])
|
||||||
|
? 'public'
|
||||||
|
: 'private';
|
||||||
|
$maxAge = 0;
|
||||||
|
// backwards compatibility (can be removed later)
|
||||||
|
if (isset($spec['setExpires'])
|
||||||
|
&& is_numeric($spec['setExpires'])
|
||||||
|
&& ! isset($spec['maxAge'])) {
|
||||||
|
$spec['maxAge'] = $spec['setExpires'] - $_SERVER['REQUEST_TIME'];
|
||||||
|
}
|
||||||
|
if (isset($spec['maxAge'])) {
|
||||||
|
$maxAge = $spec['maxAge'];
|
||||||
|
$this->_headers['Expires'] = self::gmtDate(
|
||||||
|
$_SERVER['REQUEST_TIME'] + $spec['maxAge']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$etagAppend = '';
|
||||||
|
if (isset($spec['encoding'])) {
|
||||||
|
$this->_stripEtag = true;
|
||||||
|
$this->_headers['Vary'] = 'Accept-Encoding';
|
||||||
|
if ('' !== $spec['encoding']) {
|
||||||
|
if (0 === strpos($spec['encoding'], 'x-')) {
|
||||||
|
$spec['encoding'] = substr($spec['encoding'], 2);
|
||||||
|
}
|
||||||
|
$etagAppend = ';' . substr($spec['encoding'], 0, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isset($spec['lastModifiedTime'])) {
|
||||||
|
$this->_setLastModified($spec['lastModifiedTime']);
|
||||||
|
if (isset($spec['eTag'])) { // Use it
|
||||||
|
$this->_setEtag($spec['eTag'], $scope);
|
||||||
|
} else { // base both headers on time
|
||||||
|
$this->_setEtag($spec['lastModifiedTime'] . $etagAppend, $scope);
|
||||||
|
}
|
||||||
|
} elseif (isset($spec['eTag'])) { // Use it
|
||||||
|
$this->_setEtag($spec['eTag'], $scope);
|
||||||
|
} elseif (isset($spec['contentHash'])) { // Use the hash as the ETag
|
||||||
|
$this->_setEtag($spec['contentHash'] . $etagAppend, $scope);
|
||||||
|
}
|
||||||
|
$privacy = ($scope === 'private')
|
||||||
|
? ', private'
|
||||||
|
: '';
|
||||||
|
$this->_headers['Cache-Control'] = "max-age={$maxAge}{$privacy}";
|
||||||
|
// invalidate cache if disabled, otherwise check
|
||||||
|
$this->cacheIsValid = (isset($spec['invalidate']) && $spec['invalidate'])
|
||||||
|
? false
|
||||||
|
: $this->_isCacheValid();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get array of output headers to be sent
|
||||||
|
*
|
||||||
|
* In the case of 304 responses, this array will only contain the response
|
||||||
|
* code header: array('_responseCode' => 'HTTP/1.0 304 Not Modified')
|
||||||
|
*
|
||||||
|
* Otherwise something like:
|
||||||
|
* <code>
|
||||||
|
* array(
|
||||||
|
* 'Cache-Control' => 'max-age=0, public'
|
||||||
|
* ,'ETag' => '"foobar"'
|
||||||
|
* )
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getHeaders()
|
||||||
|
{
|
||||||
|
return $this->_headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the Content-Length header in bytes
|
||||||
|
*
|
||||||
|
* With most PHP configs, as long as you don't flush() output, this method
|
||||||
|
* is not needed and PHP will buffer all output and set Content-Length for
|
||||||
|
* you. Otherwise you'll want to call this to let the client know up front.
|
||||||
|
*
|
||||||
|
* @param int $bytes
|
||||||
|
*
|
||||||
|
* @return int copy of input $bytes
|
||||||
|
*/
|
||||||
|
public function setContentLength($bytes)
|
||||||
|
{
|
||||||
|
return $this->_headers['Content-Length'] = $bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send headers
|
||||||
|
*
|
||||||
|
* @see getHeaders()
|
||||||
|
*
|
||||||
|
* Note this doesn't "clear" the headers. Calling sendHeaders() will
|
||||||
|
* call header() again (but probably have not effect) and getHeaders() will
|
||||||
|
* still return the headers.
|
||||||
|
*
|
||||||
|
* @return null
|
||||||
|
*/
|
||||||
|
public function sendHeaders()
|
||||||
|
{
|
||||||
|
$headers = $this->_headers;
|
||||||
|
if (array_key_exists('_responseCode', $headers)) {
|
||||||
|
// FastCGI environments require 3rd arg to header() to be set
|
||||||
|
list(, $code) = explode(' ', $headers['_responseCode'], 3);
|
||||||
|
header($headers['_responseCode'], true, $code);
|
||||||
|
unset($headers['_responseCode']);
|
||||||
|
}
|
||||||
|
foreach ($headers as $name => $val) {
|
||||||
|
header($name . ': ' . $val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exit if the client's cache is valid for this resource
|
||||||
|
*
|
||||||
|
* This is a convenience method for common use of the class
|
||||||
|
*
|
||||||
|
* @param int $lastModifiedTime if given, both ETag AND Last-Modified headers
|
||||||
|
* will be sent with content. This is recommended.
|
||||||
|
*
|
||||||
|
* @param bool $isPublic (default false) if true, the Cache-Control header
|
||||||
|
* will contain "public", allowing proxies to cache the content. Otherwise
|
||||||
|
* "private" will be sent, allowing only browser caching.
|
||||||
|
*
|
||||||
|
* @param array $options (default empty) additional options for constructor
|
||||||
|
*/
|
||||||
|
public static function check($lastModifiedTime = null, $isPublic = false, $options = array())
|
||||||
|
{
|
||||||
|
if (null !== $lastModifiedTime) {
|
||||||
|
$options['lastModifiedTime'] = (int)$lastModifiedTime;
|
||||||
|
}
|
||||||
|
$options['isPublic'] = (bool)$isPublic;
|
||||||
|
$cg = new HTTP_ConditionalGet($options);
|
||||||
|
$cg->sendHeaders();
|
||||||
|
if ($cg->cacheIsValid) {
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a GMT formatted date for use in HTTP headers
|
||||||
|
*
|
||||||
|
* <code>
|
||||||
|
* header('Expires: ' . HTTP_ConditionalGet::gmtdate($time));
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
|
* @param int $time unix timestamp
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function gmtDate($time)
|
||||||
|
{
|
||||||
|
return gmdate('D, d M Y H:i:s \G\M\T', $time);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $_headers = array();
|
||||||
|
protected $_lmTime = null;
|
||||||
|
protected $_etag = null;
|
||||||
|
protected $_stripEtag = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $hash
|
||||||
|
*
|
||||||
|
* @param string $scope
|
||||||
|
*/
|
||||||
|
protected function _setEtag($hash, $scope)
|
||||||
|
{
|
||||||
|
$this->_etag = '"' . substr($scope, 0, 3) . $hash . '"';
|
||||||
|
$this->_headers['ETag'] = $this->_etag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $time
|
||||||
|
*/
|
||||||
|
protected function _setLastModified($time)
|
||||||
|
{
|
||||||
|
$this->_lmTime = (int)$time;
|
||||||
|
$this->_headers['Last-Modified'] = self::gmtDate($time);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine validity of client cache and queue 304 header if valid
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function _isCacheValid()
|
||||||
|
{
|
||||||
|
if (null === $this->_etag) {
|
||||||
|
// lmTime is copied to ETag, so this condition implies that the
|
||||||
|
// server sent neither ETag nor Last-Modified, so the client can't
|
||||||
|
// possibly has a valid cache.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$isValid = ($this->resourceMatchedEtag() || $this->resourceNotModified());
|
||||||
|
if ($isValid) {
|
||||||
|
$this->_headers['_responseCode'] = 'HTTP/1.0 304 Not Modified';
|
||||||
|
}
|
||||||
|
return $isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function resourceMatchedEtag()
|
||||||
|
{
|
||||||
|
if (!isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$clientEtagList = get_magic_quotes_gpc()
|
||||||
|
? stripslashes($_SERVER['HTTP_IF_NONE_MATCH'])
|
||||||
|
: $_SERVER['HTTP_IF_NONE_MATCH'];
|
||||||
|
$clientEtags = explode(',', $clientEtagList);
|
||||||
|
|
||||||
|
$compareTo = $this->normalizeEtag($this->_etag);
|
||||||
|
foreach ($clientEtags as $clientEtag) {
|
||||||
|
if ($this->normalizeEtag($clientEtag) === $compareTo) {
|
||||||
|
// respond with the client's matched ETag, even if it's not what
|
||||||
|
// we would've sent by default
|
||||||
|
$this->_headers['ETag'] = trim($clientEtag);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $etag
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function normalizeEtag($etag) {
|
||||||
|
$etag = trim($etag);
|
||||||
|
return $this->_stripEtag
|
||||||
|
? preg_replace('/;\\w\\w"$/', '"', $etag)
|
||||||
|
: $etag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function resourceNotModified()
|
||||||
|
{
|
||||||
|
if (!isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// strip off IE's extra data (semicolon)
|
||||||
|
list($ifModifiedSince) = explode(';', $_SERVER['HTTP_IF_MODIFIED_SINCE'], 2);
|
||||||
|
if (strtotime($ifModifiedSince) >= $this->_lmTime) {
|
||||||
|
// Apache 2.2's behavior. If there was no ETag match, send the
|
||||||
|
// non-encoded version of the ETag value.
|
||||||
|
$this->_headers['ETag'] = $this->normalizeEtag($this->_etag);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
335
inc/contrib/minify/HTTP/Encoder.php
Executable file
335
inc/contrib/minify/HTTP/Encoder.php
Executable file
@ -0,0 +1,335 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Class HTTP_Encoder
|
||||||
|
* @package Minify
|
||||||
|
* @subpackage HTTP
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode and send gzipped/deflated content
|
||||||
|
*
|
||||||
|
* The "Vary: Accept-Encoding" header is sent. If the client allows encoding,
|
||||||
|
* Content-Encoding and Content-Length are added.
|
||||||
|
*
|
||||||
|
* <code>
|
||||||
|
* // Send a CSS file, compressed if possible
|
||||||
|
* $he = new HTTP_Encoder(array(
|
||||||
|
* 'content' => file_get_contents($cssFile)
|
||||||
|
* ,'type' => 'text/css'
|
||||||
|
* ));
|
||||||
|
* $he->encode();
|
||||||
|
* $he->sendAll();
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
|
* <code>
|
||||||
|
* // Shortcut to encoding output
|
||||||
|
* header('Content-Type: text/css'); // needed if not HTML
|
||||||
|
* HTTP_Encoder::output($css);
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
|
* <code>
|
||||||
|
* // Just sniff for the accepted encoding
|
||||||
|
* $encoding = HTTP_Encoder::getAcceptedEncoding();
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
|
* For more control over headers, use getHeaders() and getData() and send your
|
||||||
|
* own output.
|
||||||
|
*
|
||||||
|
* Note: If you don't need header mgmt, use PHP's native gzencode, gzdeflate,
|
||||||
|
* and gzcompress functions for gzip, deflate, and compress-encoding
|
||||||
|
* respectively.
|
||||||
|
*
|
||||||
|
* @package Minify
|
||||||
|
* @subpackage HTTP
|
||||||
|
* @author Stephen Clay <steve@mrclay.org>
|
||||||
|
*/
|
||||||
|
class HTTP_Encoder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should the encoder allow HTTP encoding to IE6?
|
||||||
|
*
|
||||||
|
* If you have many IE6 users and the bandwidth savings is worth troubling
|
||||||
|
* some of them, set this to true.
|
||||||
|
*
|
||||||
|
* By default, encoding is only offered to IE7+. When this is true,
|
||||||
|
* getAcceptedEncoding() will return an encoding for IE6 if its user agent
|
||||||
|
* string contains "SV1". This has been documented in many places as "safe",
|
||||||
|
* but there seem to be remaining, intermittent encoding bugs in patched
|
||||||
|
* IE6 on the wild web.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public static $encodeToIe6 = true;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default compression level for zlib operations
|
||||||
|
*
|
||||||
|
* This level is used if encode() is not given a $compressionLevel
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public static $compressionLevel = 6;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an HTTP Encoder object
|
||||||
|
*
|
||||||
|
* @param array $spec options
|
||||||
|
*
|
||||||
|
* 'content': (string required) content to be encoded
|
||||||
|
*
|
||||||
|
* 'type': (string) if set, the Content-Type header will have this value.
|
||||||
|
*
|
||||||
|
* 'method: (string) only set this if you are forcing a particular encoding
|
||||||
|
* method. If not set, the best method will be chosen by getAcceptedEncoding()
|
||||||
|
* The available methods are 'gzip', 'deflate', 'compress', and '' (no
|
||||||
|
* encoding)
|
||||||
|
*/
|
||||||
|
public function __construct($spec)
|
||||||
|
{
|
||||||
|
$this->_useMbStrlen = (function_exists('mb_strlen')
|
||||||
|
&& (ini_get('mbstring.func_overload') !== '')
|
||||||
|
&& ((int)ini_get('mbstring.func_overload') & 2));
|
||||||
|
$this->_content = $spec['content'];
|
||||||
|
$this->_headers['Content-Length'] = $this->_useMbStrlen
|
||||||
|
? (string)mb_strlen($this->_content, '8bit')
|
||||||
|
: (string)strlen($this->_content);
|
||||||
|
if (isset($spec['type'])) {
|
||||||
|
$this->_headers['Content-Type'] = $spec['type'];
|
||||||
|
}
|
||||||
|
if (isset($spec['method'])
|
||||||
|
&& in_array($spec['method'], array('gzip', 'deflate', 'compress', '')))
|
||||||
|
{
|
||||||
|
$this->_encodeMethod = array($spec['method'], $spec['method']);
|
||||||
|
} else {
|
||||||
|
$this->_encodeMethod = self::getAcceptedEncoding();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get content in current form
|
||||||
|
*
|
||||||
|
* Call after encode() for encoded content.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getContent()
|
||||||
|
{
|
||||||
|
return $this->_content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get array of output headers to be sent
|
||||||
|
*
|
||||||
|
* E.g.
|
||||||
|
* <code>
|
||||||
|
* array(
|
||||||
|
* 'Content-Length' => '615'
|
||||||
|
* ,'Content-Encoding' => 'x-gzip'
|
||||||
|
* ,'Vary' => 'Accept-Encoding'
|
||||||
|
* )
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getHeaders()
|
||||||
|
{
|
||||||
|
return $this->_headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send output headers
|
||||||
|
*
|
||||||
|
* You must call this before headers are sent and it probably cannot be
|
||||||
|
* used in conjunction with zlib output buffering / mod_gzip. Errors are
|
||||||
|
* not handled purposefully.
|
||||||
|
*
|
||||||
|
* @see getHeaders()
|
||||||
|
*/
|
||||||
|
public function sendHeaders()
|
||||||
|
{
|
||||||
|
foreach ($this->_headers as $name => $val) {
|
||||||
|
header($name . ': ' . $val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send output headers and content
|
||||||
|
*
|
||||||
|
* A shortcut for sendHeaders() and echo getContent()
|
||||||
|
*
|
||||||
|
* You must call this before headers are sent and it probably cannot be
|
||||||
|
* used in conjunction with zlib output buffering / mod_gzip. Errors are
|
||||||
|
* not handled purposefully.
|
||||||
|
*/
|
||||||
|
public function sendAll()
|
||||||
|
{
|
||||||
|
$this->sendHeaders();
|
||||||
|
echo $this->_content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the client's best encoding method from the HTTP Accept-Encoding
|
||||||
|
* header.
|
||||||
|
*
|
||||||
|
* If no Accept-Encoding header is set, or the browser is IE before v6 SP2,
|
||||||
|
* this will return ('', ''), the "identity" encoding.
|
||||||
|
*
|
||||||
|
* A syntax-aware scan is done of the Accept-Encoding, so the method must
|
||||||
|
* be non 0. The methods are favored in order of gzip, deflate, then
|
||||||
|
* compress. Deflate is always smallest and generally faster, but is
|
||||||
|
* rarely sent by servers, so client support could be buggier.
|
||||||
|
*
|
||||||
|
* @param bool $allowCompress allow the older compress encoding
|
||||||
|
*
|
||||||
|
* @param bool $allowDeflate allow the more recent deflate encoding
|
||||||
|
*
|
||||||
|
* @return array two values, 1st is the actual encoding method, 2nd is the
|
||||||
|
* alias of that method to use in the Content-Encoding header (some browsers
|
||||||
|
* call gzip "x-gzip" etc.)
|
||||||
|
*/
|
||||||
|
public static function getAcceptedEncoding($allowCompress = true, $allowDeflate = true)
|
||||||
|
{
|
||||||
|
// @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
|
||||||
|
|
||||||
|
if (! isset($_SERVER['HTTP_ACCEPT_ENCODING'])
|
||||||
|
|| self::isBuggyIe())
|
||||||
|
{
|
||||||
|
return array('', '');
|
||||||
|
}
|
||||||
|
$ae = $_SERVER['HTTP_ACCEPT_ENCODING'];
|
||||||
|
// gzip checks (quick)
|
||||||
|
if (0 === strpos($ae, 'gzip,') // most browsers
|
||||||
|
|| 0 === strpos($ae, 'deflate, gzip,') // opera
|
||||||
|
) {
|
||||||
|
return array('gzip', 'gzip');
|
||||||
|
}
|
||||||
|
// gzip checks (slow)
|
||||||
|
if (preg_match(
|
||||||
|
'@(?:^|,)\\s*((?:x-)?gzip)\\s*(?:$|,|;\\s*q=(?:0\\.|1))@'
|
||||||
|
,$ae
|
||||||
|
,$m)) {
|
||||||
|
return array('gzip', $m[1]);
|
||||||
|
}
|
||||||
|
if ($allowDeflate) {
|
||||||
|
// deflate checks
|
||||||
|
$aeRev = strrev($ae);
|
||||||
|
if (0 === strpos($aeRev, 'etalfed ,') // ie, webkit
|
||||||
|
|| 0 === strpos($aeRev, 'etalfed,') // gecko
|
||||||
|
|| 0 === strpos($ae, 'deflate,') // opera
|
||||||
|
// slow parsing
|
||||||
|
|| preg_match(
|
||||||
|
'@(?:^|,)\\s*deflate\\s*(?:$|,|;\\s*q=(?:0\\.|1))@', $ae)) {
|
||||||
|
return array('deflate', 'deflate');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($allowCompress && preg_match(
|
||||||
|
'@(?:^|,)\\s*((?:x-)?compress)\\s*(?:$|,|;\\s*q=(?:0\\.|1))@'
|
||||||
|
,$ae
|
||||||
|
,$m)) {
|
||||||
|
return array('compress', $m[1]);
|
||||||
|
}
|
||||||
|
return array('', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode (compress) the content
|
||||||
|
*
|
||||||
|
* If the encode method is '' (none) or compression level is 0, or the 'zlib'
|
||||||
|
* extension isn't loaded, we return false.
|
||||||
|
*
|
||||||
|
* Then the appropriate gz_* function is called to compress the content. If
|
||||||
|
* this fails, false is returned.
|
||||||
|
*
|
||||||
|
* The header "Vary: Accept-Encoding" is added. If encoding is successful,
|
||||||
|
* the Content-Length header is updated, and Content-Encoding is also added.
|
||||||
|
*
|
||||||
|
* @param int $compressionLevel given to zlib functions. If not given, the
|
||||||
|
* class default will be used.
|
||||||
|
*
|
||||||
|
* @return bool success true if the content was actually compressed
|
||||||
|
*/
|
||||||
|
public function encode($compressionLevel = null)
|
||||||
|
{
|
||||||
|
if (! self::isBuggyIe()) {
|
||||||
|
$this->_headers['Vary'] = 'Accept-Encoding';
|
||||||
|
}
|
||||||
|
if (null === $compressionLevel) {
|
||||||
|
$compressionLevel = self::$compressionLevel;
|
||||||
|
}
|
||||||
|
if ('' === $this->_encodeMethod[0]
|
||||||
|
|| ($compressionLevel == 0)
|
||||||
|
|| !extension_loaded('zlib'))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ($this->_encodeMethod[0] === 'deflate') {
|
||||||
|
$encoded = gzdeflate($this->_content, $compressionLevel);
|
||||||
|
} elseif ($this->_encodeMethod[0] === 'gzip') {
|
||||||
|
$encoded = gzencode($this->_content, $compressionLevel);
|
||||||
|
} else {
|
||||||
|
$encoded = gzcompress($this->_content, $compressionLevel);
|
||||||
|
}
|
||||||
|
if (false === $encoded) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$this->_headers['Content-Length'] = $this->_useMbStrlen
|
||||||
|
? (string)mb_strlen($encoded, '8bit')
|
||||||
|
: (string)strlen($encoded);
|
||||||
|
$this->_headers['Content-Encoding'] = $this->_encodeMethod[1];
|
||||||
|
$this->_content = $encoded;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode and send appropriate headers and content
|
||||||
|
*
|
||||||
|
* This is a convenience method for common use of the class
|
||||||
|
*
|
||||||
|
* @param string $content
|
||||||
|
*
|
||||||
|
* @param int $compressionLevel given to zlib functions. If not given, the
|
||||||
|
* class default will be used.
|
||||||
|
*
|
||||||
|
* @return bool success true if the content was actually compressed
|
||||||
|
*/
|
||||||
|
public static function output($content, $compressionLevel = null)
|
||||||
|
{
|
||||||
|
if (null === $compressionLevel) {
|
||||||
|
$compressionLevel = self::$compressionLevel;
|
||||||
|
}
|
||||||
|
$he = new HTTP_Encoder(array('content' => $content));
|
||||||
|
$ret = $he->encode($compressionLevel);
|
||||||
|
$he->sendAll();
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the browser an IE version earlier than 6 SP2?
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function isBuggyIe()
|
||||||
|
{
|
||||||
|
if (empty($_SERVER['HTTP_USER_AGENT'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$ua = $_SERVER['HTTP_USER_AGENT'];
|
||||||
|
// quick escape for non-IEs
|
||||||
|
if (0 !== strpos($ua, 'Mozilla/4.0 (compatible; MSIE ')
|
||||||
|
|| false !== strpos($ua, 'Opera')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// no regex = faaast
|
||||||
|
$version = (float)substr($ua, 30);
|
||||||
|
return self::$encodeToIe6
|
||||||
|
? ($version < 6 || ($version == 6 && false === strpos($ua, 'SV1')))
|
||||||
|
: ($version < 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $_content = '';
|
||||||
|
protected $_headers = array();
|
||||||
|
protected $_encodeMethod = array('', '');
|
||||||
|
protected $_useMbStrlen = false;
|
||||||
|
}
|
385
inc/contrib/minify/JSMin.php
Executable file
385
inc/contrib/minify/JSMin.php
Executable file
@ -0,0 +1,385 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* JSMin.php - modified PHP implementation of Douglas Crockford's JSMin.
|
||||||
|
*
|
||||||
|
* <code>
|
||||||
|
* $minifiedJs = JSMin::minify($js);
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
|
* This is a modified port of jsmin.c. Improvements:
|
||||||
|
*
|
||||||
|
* Does not choke on some regexp literals containing quote characters. E.g. /'/
|
||||||
|
*
|
||||||
|
* Spaces are preserved after some add/sub operators, so they are not mistakenly
|
||||||
|
* converted to post-inc/dec. E.g. a + ++b -> a+ ++b
|
||||||
|
*
|
||||||
|
* Preserves multi-line comments that begin with /*!
|
||||||
|
*
|
||||||
|
* PHP 5 or higher is required.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted to use this version of the library under the
|
||||||
|
* same terms as jsmin.c, which has the following license:
|
||||||
|
*
|
||||||
|
* --
|
||||||
|
* Copyright (c) 2002 Douglas Crockford (www.crockford.com)
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* 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
|
||||||
|
* so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* The Software shall be used for Good, not Evil.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* 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
|
||||||
|
* SOFTWARE.
|
||||||
|
* --
|
||||||
|
*
|
||||||
|
* @package JSMin
|
||||||
|
* @author Ryan Grove <ryan@wonko.com> (PHP port)
|
||||||
|
* @author Steve Clay <steve@mrclay.org> (modifications + cleanup)
|
||||||
|
* @author Andrea Giammarchi <http://www.3site.eu> (spaceBeforeRegExp)
|
||||||
|
* @copyright 2002 Douglas Crockford <douglas@crockford.com> (jsmin.c)
|
||||||
|
* @copyright 2008 Ryan Grove <ryan@wonko.com> (PHP port)
|
||||||
|
* @license http://opensource.org/licenses/mit-license.php MIT License
|
||||||
|
* @link http://code.google.com/p/jsmin-php/
|
||||||
|
*/
|
||||||
|
|
||||||
|
class JSMin {
|
||||||
|
const ORD_LF = 10;
|
||||||
|
const ORD_SPACE = 32;
|
||||||
|
const ACTION_KEEP_A = 1;
|
||||||
|
const ACTION_DELETE_A = 2;
|
||||||
|
const ACTION_DELETE_A_B = 3;
|
||||||
|
|
||||||
|
protected $a = "\n";
|
||||||
|
protected $b = '';
|
||||||
|
protected $input = '';
|
||||||
|
protected $inputIndex = 0;
|
||||||
|
protected $inputLength = 0;
|
||||||
|
protected $lookAhead = null;
|
||||||
|
protected $output = '';
|
||||||
|
protected $lastByteOut = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minify Javascript.
|
||||||
|
*
|
||||||
|
* @param string $js Javascript to be minified
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function minify($js)
|
||||||
|
{
|
||||||
|
$jsmin = new JSMin($js);
|
||||||
|
return $jsmin->min();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $input
|
||||||
|
*/
|
||||||
|
public function __construct($input)
|
||||||
|
{
|
||||||
|
$this->input = $input;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform minification, return result
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function min()
|
||||||
|
{
|
||||||
|
if ($this->output !== '') { // min already run
|
||||||
|
return $this->output;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mbIntEnc = null;
|
||||||
|
if (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) {
|
||||||
|
$mbIntEnc = mb_internal_encoding();
|
||||||
|
mb_internal_encoding('8bit');
|
||||||
|
}
|
||||||
|
$this->input = str_replace("\r\n", "\n", $this->input);
|
||||||
|
$this->inputLength = strlen($this->input);
|
||||||
|
|
||||||
|
$this->action(self::ACTION_DELETE_A_B);
|
||||||
|
|
||||||
|
while ($this->a !== null) {
|
||||||
|
// determine next command
|
||||||
|
$command = self::ACTION_KEEP_A; // default
|
||||||
|
if ($this->a === ' ') {
|
||||||
|
if (($this->lastByteOut === '+' || $this->lastByteOut === '-')
|
||||||
|
&& ($this->b === $this->lastByteOut)) {
|
||||||
|
// Don't delete this space. If we do, the addition/subtraction
|
||||||
|
// could be parsed as a post-increment
|
||||||
|
} elseif (! $this->isAlphaNum($this->b)) {
|
||||||
|
$command = self::ACTION_DELETE_A;
|
||||||
|
}
|
||||||
|
} elseif ($this->a === "\n") {
|
||||||
|
if ($this->b === ' ') {
|
||||||
|
$command = self::ACTION_DELETE_A_B;
|
||||||
|
// in case of mbstring.func_overload & 2, must check for null b,
|
||||||
|
// otherwise mb_strpos will give WARNING
|
||||||
|
} elseif ($this->b === null
|
||||||
|
|| (false === strpos('{[(+-', $this->b)
|
||||||
|
&& ! $this->isAlphaNum($this->b))) {
|
||||||
|
$command = self::ACTION_DELETE_A;
|
||||||
|
}
|
||||||
|
} elseif (! $this->isAlphaNum($this->a)) {
|
||||||
|
if ($this->b === ' '
|
||||||
|
|| ($this->b === "\n"
|
||||||
|
&& (false === strpos('}])+-"\'', $this->a)))) {
|
||||||
|
$command = self::ACTION_DELETE_A_B;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->action($command);
|
||||||
|
}
|
||||||
|
$this->output = trim($this->output);
|
||||||
|
|
||||||
|
if ($mbIntEnc !== null) {
|
||||||
|
mb_internal_encoding($mbIntEnc);
|
||||||
|
}
|
||||||
|
return $this->output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ACTION_KEEP_A = Output A. Copy B to A. Get the next B.
|
||||||
|
* ACTION_DELETE_A = Copy B to A. Get the next B.
|
||||||
|
* ACTION_DELETE_A_B = Get the next B.
|
||||||
|
*
|
||||||
|
* @param int $command
|
||||||
|
* @throws JSMin_UnterminatedRegExpException|JSMin_UnterminatedStringException
|
||||||
|
*/
|
||||||
|
protected function action($command)
|
||||||
|
{
|
||||||
|
if ($command === self::ACTION_DELETE_A_B
|
||||||
|
&& $this->b === ' '
|
||||||
|
&& ($this->a === '+' || $this->a === '-')) {
|
||||||
|
// Note: we're at an addition/substraction operator; the inputIndex
|
||||||
|
// will certainly be a valid index
|
||||||
|
if ($this->input[$this->inputIndex] === $this->a) {
|
||||||
|
// This is "+ +" or "- -". Don't delete the space.
|
||||||
|
$command = self::ACTION_KEEP_A;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch ($command) {
|
||||||
|
case self::ACTION_KEEP_A:
|
||||||
|
$this->output .= $this->a;
|
||||||
|
$this->lastByteOut = $this->a;
|
||||||
|
|
||||||
|
// fallthrough
|
||||||
|
case self::ACTION_DELETE_A:
|
||||||
|
$this->a = $this->b;
|
||||||
|
if ($this->a === "'" || $this->a === '"') { // string literal
|
||||||
|
$str = $this->a; // in case needed for exception
|
||||||
|
while (true) {
|
||||||
|
$this->output .= $this->a;
|
||||||
|
$this->lastByteOut = $this->a;
|
||||||
|
|
||||||
|
$this->a = $this->get();
|
||||||
|
if ($this->a === $this->b) { // end quote
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (ord($this->a) <= self::ORD_LF) {
|
||||||
|
throw new JSMin_UnterminatedStringException(
|
||||||
|
"JSMin: Unterminated String at byte "
|
||||||
|
. $this->inputIndex . ": {$str}");
|
||||||
|
}
|
||||||
|
$str .= $this->a;
|
||||||
|
if ($this->a === '\\') {
|
||||||
|
$this->output .= $this->a;
|
||||||
|
$this->lastByteOut = $this->a;
|
||||||
|
|
||||||
|
$this->a = $this->get();
|
||||||
|
$str .= $this->a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// fallthrough
|
||||||
|
case self::ACTION_DELETE_A_B:
|
||||||
|
$this->b = $this->next();
|
||||||
|
if ($this->b === '/' && $this->isRegexpLiteral()) { // RegExp literal
|
||||||
|
$this->output .= $this->a . $this->b;
|
||||||
|
$pattern = '/'; // in case needed for exception
|
||||||
|
while (true) {
|
||||||
|
$this->a = $this->get();
|
||||||
|
$pattern .= $this->a;
|
||||||
|
if ($this->a === '/') { // end pattern
|
||||||
|
break; // while (true)
|
||||||
|
} elseif ($this->a === '\\') {
|
||||||
|
$this->output .= $this->a;
|
||||||
|
$this->a = $this->get();
|
||||||
|
$pattern .= $this->a;
|
||||||
|
} elseif (ord($this->a) <= self::ORD_LF) {
|
||||||
|
throw new JSMin_UnterminatedRegExpException(
|
||||||
|
"JSMin: Unterminated RegExp at byte "
|
||||||
|
. $this->inputIndex .": {$pattern}");
|
||||||
|
}
|
||||||
|
$this->output .= $this->a;
|
||||||
|
$this->lastByteOut = $this->a;
|
||||||
|
}
|
||||||
|
$this->b = $this->next();
|
||||||
|
}
|
||||||
|
// end case ACTION_DELETE_A_B
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function isRegexpLiteral()
|
||||||
|
{
|
||||||
|
if (false !== strpos("\n{;(,=:[!&|?", $this->a)) { // we aren't dividing
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (' ' === $this->a) {
|
||||||
|
$length = strlen($this->output);
|
||||||
|
if ($length < 2) { // weird edge case
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// you can't divide a keyword
|
||||||
|
if (preg_match('/(?:case|else|in|return|typeof)$/', $this->output, $m)) {
|
||||||
|
if ($this->output === $m[0]) { // odd but could happen
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// make sure it's a keyword, not end of an identifier
|
||||||
|
$charBeforeKeyword = substr($this->output, $length - strlen($m[0]) - 1, 1);
|
||||||
|
if (! $this->isAlphaNum($charBeforeKeyword)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get next char. Convert ctrl char to space.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function get()
|
||||||
|
{
|
||||||
|
$c = $this->lookAhead;
|
||||||
|
$this->lookAhead = null;
|
||||||
|
if ($c === null) {
|
||||||
|
if ($this->inputIndex < $this->inputLength) {
|
||||||
|
$c = $this->input[$this->inputIndex];
|
||||||
|
$this->inputIndex += 1;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($c === "\r" || $c === "\n") {
|
||||||
|
return "\n";
|
||||||
|
}
|
||||||
|
if (ord($c) < self::ORD_SPACE) { // control char
|
||||||
|
return ' ';
|
||||||
|
}
|
||||||
|
return $c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get next char. If is ctrl character, translate to a space or newline.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function peek()
|
||||||
|
{
|
||||||
|
$this->lookAhead = $this->get();
|
||||||
|
return $this->lookAhead;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is $c a letter, digit, underscore, dollar sign, escape, or non-ASCII?
|
||||||
|
*
|
||||||
|
* @param string $c
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function isAlphaNum($c)
|
||||||
|
{
|
||||||
|
return (preg_match('/^[0-9a-zA-Z_\\$\\\\]$/', $c) || ord($c) > 126);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function singleLineComment()
|
||||||
|
{
|
||||||
|
$comment = '';
|
||||||
|
while (true) {
|
||||||
|
$get = $this->get();
|
||||||
|
$comment .= $get;
|
||||||
|
if (ord($get) <= self::ORD_LF) { // EOL reached
|
||||||
|
// if IE conditional comment
|
||||||
|
if (preg_match('/^\\/@(?:cc_on|if|elif|else|end)\\b/', $comment)) {
|
||||||
|
return "/{$comment}";
|
||||||
|
}
|
||||||
|
return $get;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
* @throws JSMin_UnterminatedCommentException
|
||||||
|
*/
|
||||||
|
protected function multipleLineComment()
|
||||||
|
{
|
||||||
|
$this->get();
|
||||||
|
$comment = '';
|
||||||
|
while (true) {
|
||||||
|
$get = $this->get();
|
||||||
|
if ($get === '*') {
|
||||||
|
if ($this->peek() === '/') { // end of comment reached
|
||||||
|
$this->get();
|
||||||
|
// if comment preserved by YUI Compressor
|
||||||
|
if (0 === strpos($comment, '!')) {
|
||||||
|
return "\n/*!" . substr($comment, 1) . "*/\n";
|
||||||
|
}
|
||||||
|
// if IE conditional comment
|
||||||
|
if (preg_match('/^@(?:cc_on|if|elif|else|end)\\b/', $comment)) {
|
||||||
|
return "/*{$comment}*/";
|
||||||
|
}
|
||||||
|
return ' ';
|
||||||
|
}
|
||||||
|
} elseif ($get === null) {
|
||||||
|
throw new JSMin_UnterminatedCommentException(
|
||||||
|
"JSMin: Unterminated comment at byte "
|
||||||
|
. $this->inputIndex . ": /*{$comment}");
|
||||||
|
}
|
||||||
|
$comment .= $get;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the next character, skipping over comments.
|
||||||
|
* Some comments may be preserved.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function next()
|
||||||
|
{
|
||||||
|
$get = $this->get();
|
||||||
|
if ($get !== '/') {
|
||||||
|
return $get;
|
||||||
|
}
|
||||||
|
switch ($this->peek()) {
|
||||||
|
case '/': return $this->singleLineComment();
|
||||||
|
case '*': return $this->multipleLineComment();
|
||||||
|
default: return $get;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class JSMin_UnterminatedStringException extends Exception {}
|
||||||
|
class JSMin_UnterminatedCommentException extends Exception {}
|
||||||
|
class JSMin_UnterminatedRegExpException extends Exception {}
|
2086
inc/contrib/minify/JSMinPlus.php
Executable file
2086
inc/contrib/minify/JSMinPlus.php
Executable file
File diff suppressed because it is too large
Load Diff
617
inc/contrib/minify/Minify.php
Executable file
617
inc/contrib/minify/Minify.php
Executable file
@ -0,0 +1,617 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Class Minify
|
||||||
|
* @package Minify
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minify_Source
|
||||||
|
*/
|
||||||
|
require_once 'Minify/Source.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* @link http://code.google.com/p/minify/
|
||||||
|
*/
|
||||||
|
class Minify {
|
||||||
|
|
||||||
|
const VERSION = '2.1.5';
|
||||||
|
const TYPE_CSS = 'text/css';
|
||||||
|
const TYPE_HTML = 'text/html';
|
||||||
|
// there is some debate over the ideal JS Content-Type, but this is the
|
||||||
|
// Apache default and what Yahoo! uses..
|
||||||
|
const TYPE_JS = 'application/x-javascript';
|
||||||
|
const URL_DEBUG = 'http://code.google.com/p/minify/wiki/Debugging';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How many hours behind are the file modification times of uploaded files?
|
||||||
|
*
|
||||||
|
* If you upload files from Windows to a non-Windows server, Windows may report
|
||||||
|
* incorrect mtimes for the files. Immediately after modifying and uploading a
|
||||||
|
* file, use the touch command to update the mtime on the server. If the mtime
|
||||||
|
* jumps ahead by a number of hours, set this variable to that number. If the mtime
|
||||||
|
* moves back, this should not be needed.
|
||||||
|
*
|
||||||
|
* @var int $uploaderHoursBehind
|
||||||
|
*/
|
||||||
|
public static $uploaderHoursBehind = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this string is not empty AND the serve() option 'bubbleCssImports' is
|
||||||
|
* NOT set, then serve() will check CSS files for @import declarations that
|
||||||
|
* appear too late in the combined stylesheet. If found, serve() will prepend
|
||||||
|
* the output with this warning.
|
||||||
|
*
|
||||||
|
* @var string $importWarning
|
||||||
|
*/
|
||||||
|
public static $importWarning = "/* See http://code.google.com/p/minify/wiki/CommonProblems#@imports_can_appear_in_invalid_locations_in_combined_CSS_files */\n";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Has the DOCUMENT_ROOT been set in user code?
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public static $isDocRootSet = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify a cache object (with identical interface as Minify_Cache_File) or
|
||||||
|
* a path to use with Minify_Cache_File.
|
||||||
|
*
|
||||||
|
* If not called, Minify will not use a cache and, for each 200 response, will
|
||||||
|
* need to recombine files, minify and encode the output.
|
||||||
|
*
|
||||||
|
* @param mixed $cache object with identical interface as Minify_Cache_File or
|
||||||
|
* a directory path, or null to disable caching. (default = '')
|
||||||
|
*
|
||||||
|
* @param bool $fileLocking (default = true) This only applies if the first
|
||||||
|
* parameter is a string.
|
||||||
|
*
|
||||||
|
* @return null
|
||||||
|
*/
|
||||||
|
public static function setCache($cache = '', $fileLocking = true)
|
||||||
|
{
|
||||||
|
if (is_string($cache)) {
|
||||||
|
require_once 'Minify/Cache/File.php';
|
||||||
|
self::$_cache = new Minify_Cache_File($cache, $fileLocking);
|
||||||
|
} else {
|
||||||
|
self::$_cache = $cache;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serve a request for a minified file.
|
||||||
|
*
|
||||||
|
* Here are the available options and defaults in the base controller:
|
||||||
|
*
|
||||||
|
* 'isPublic' : send "public" instead of "private" in Cache-Control
|
||||||
|
* headers, allowing shared caches to cache the output. (default true)
|
||||||
|
*
|
||||||
|
* 'quiet' : set to true to have serve() return an array rather than sending
|
||||||
|
* any headers/output (default false)
|
||||||
|
*
|
||||||
|
* 'encodeOutput' : set to false to disable content encoding, and not send
|
||||||
|
* the Vary header (default true)
|
||||||
|
*
|
||||||
|
* 'encodeMethod' : generally you should let this be determined by
|
||||||
|
* HTTP_Encoder (leave null), but you can force a particular encoding
|
||||||
|
* to be returned, by setting this to 'gzip' or '' (no encoding)
|
||||||
|
*
|
||||||
|
* 'encodeLevel' : level of encoding compression (0 to 9, default 9)
|
||||||
|
*
|
||||||
|
* 'contentTypeCharset' : appended to the Content-Type header sent. Set to a falsey
|
||||||
|
* value to remove. (default 'utf-8')
|
||||||
|
*
|
||||||
|
* 'maxAge' : set this to the number of seconds the client should use its cache
|
||||||
|
* before revalidating with the server. This sets Cache-Control: max-age and the
|
||||||
|
* Expires header. Unlike the old 'setExpires' setting, this setting will NOT
|
||||||
|
* prevent conditional GETs. Note this has nothing to do with server-side caching.
|
||||||
|
*
|
||||||
|
* 'rewriteCssUris' : If true, serve() will automatically set the 'currentDir'
|
||||||
|
* minifier option to enable URI rewriting in CSS files (default true)
|
||||||
|
*
|
||||||
|
* 'bubbleCssImports' : If true, all @import declarations in combined CSS
|
||||||
|
* files will be move to the top. Note this may alter effective CSS values
|
||||||
|
* due to a change in order. (default false)
|
||||||
|
*
|
||||||
|
* 'debug' : set to true to minify all sources with the 'Lines' controller, which
|
||||||
|
* eases the debugging of combined files. This also prevents 304 responses.
|
||||||
|
* @see Minify_Lines::minify()
|
||||||
|
*
|
||||||
|
* 'minifiers' : to override Minify's default choice of minifier function for
|
||||||
|
* a particular content-type, specify your callback under the key of the
|
||||||
|
* content-type:
|
||||||
|
* <code>
|
||||||
|
* // call customCssMinifier($css) for all CSS minification
|
||||||
|
* $options['minifiers'][Minify::TYPE_CSS] = 'customCssMinifier';
|
||||||
|
*
|
||||||
|
* // don't minify Javascript at all
|
||||||
|
* $options['minifiers'][Minify::TYPE_JS] = '';
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
|
* 'minifierOptions' : to send options to the minifier function, specify your options
|
||||||
|
* under the key of the content-type. E.g. To send the CSS minifier an option:
|
||||||
|
* <code>
|
||||||
|
* // give CSS minifier array('optionName' => 'optionValue') as 2nd argument
|
||||||
|
* $options['minifierOptions'][Minify::TYPE_CSS]['optionName'] = 'optionValue';
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
|
* 'contentType' : (optional) this is only needed if your file extension is not
|
||||||
|
* js/css/html. The given content-type will be sent regardless of source file
|
||||||
|
* extension, so this should not be used in a Groups config with other
|
||||||
|
* Javascript/CSS files.
|
||||||
|
*
|
||||||
|
* Any controller options are documented in that controller's setupSources() method.
|
||||||
|
*
|
||||||
|
* @param mixed $controller instance of subclass of Minify_Controller_Base or string
|
||||||
|
* name of controller. E.g. 'Files'
|
||||||
|
*
|
||||||
|
* @param array $options controller/serve options
|
||||||
|
*
|
||||||
|
* @return mixed null, or, if the 'quiet' option is set to true, an array
|
||||||
|
* with keys "success" (bool), "statusCode" (int), "content" (string), and
|
||||||
|
* "headers" (array).
|
||||||
|
*/
|
||||||
|
public static function serve($controller, $options = array())
|
||||||
|
{
|
||||||
|
if (! self::$isDocRootSet && 0 === stripos(PHP_OS, 'win')) {
|
||||||
|
self::setDocRoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_string($controller)) {
|
||||||
|
// make $controller into object
|
||||||
|
$class = 'Minify_Controller_' . $controller;
|
||||||
|
if (! class_exists($class, false)) {
|
||||||
|
require_once "Minify/Controller/"
|
||||||
|
. str_replace('_', '/', $controller) . ".php";
|
||||||
|
}
|
||||||
|
$controller = new $class();
|
||||||
|
/* @var Minify_Controller_Base $controller */
|
||||||
|
}
|
||||||
|
|
||||||
|
// set up controller sources and mix remaining options with
|
||||||
|
// controller defaults
|
||||||
|
$options = $controller->setupSources($options);
|
||||||
|
$options = $controller->analyzeSources($options);
|
||||||
|
self::$_options = $controller->mixInDefaultOptions($options);
|
||||||
|
|
||||||
|
// check request validity
|
||||||
|
if (! $controller->sources) {
|
||||||
|
// invalid request!
|
||||||
|
if (! self::$_options['quiet']) {
|
||||||
|
self::_errorExit(self::$_options['badRequestHeader'], self::URL_DEBUG);
|
||||||
|
} else {
|
||||||
|
list(,$statusCode) = explode(' ', self::$_options['badRequestHeader']);
|
||||||
|
return array(
|
||||||
|
'success' => false
|
||||||
|
,'statusCode' => (int)$statusCode
|
||||||
|
,'content' => ''
|
||||||
|
,'headers' => array()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self::$_controller = $controller;
|
||||||
|
|
||||||
|
if (self::$_options['debug']) {
|
||||||
|
self::_setupDebug($controller->sources);
|
||||||
|
self::$_options['maxAge'] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// determine encoding
|
||||||
|
if (self::$_options['encodeOutput']) {
|
||||||
|
$sendVary = true;
|
||||||
|
if (self::$_options['encodeMethod'] !== null) {
|
||||||
|
// controller specifically requested this
|
||||||
|
$contentEncoding = self::$_options['encodeMethod'];
|
||||||
|
} else {
|
||||||
|
// sniff request header
|
||||||
|
require_once 'HTTP/Encoder.php';
|
||||||
|
// depending on what the client accepts, $contentEncoding may be
|
||||||
|
// 'x-gzip' while our internal encodeMethod is 'gzip'. Calling
|
||||||
|
// getAcceptedEncoding(false, false) leaves out compress and deflate as options.
|
||||||
|
list(self::$_options['encodeMethod'], $contentEncoding) = HTTP_Encoder::getAcceptedEncoding(false, false);
|
||||||
|
$sendVary = ! HTTP_Encoder::isBuggyIe();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self::$_options['encodeMethod'] = ''; // identity (no encoding)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check client cache
|
||||||
|
require_once 'HTTP/ConditionalGet.php';
|
||||||
|
$cgOptions = array(
|
||||||
|
'lastModifiedTime' => self::$_options['lastModifiedTime']
|
||||||
|
,'isPublic' => self::$_options['isPublic']
|
||||||
|
,'encoding' => self::$_options['encodeMethod']
|
||||||
|
);
|
||||||
|
if (self::$_options['maxAge'] > 0) {
|
||||||
|
$cgOptions['maxAge'] = self::$_options['maxAge'];
|
||||||
|
} elseif (self::$_options['debug']) {
|
||||||
|
$cgOptions['invalidate'] = true;
|
||||||
|
}
|
||||||
|
$cg = new HTTP_ConditionalGet($cgOptions);
|
||||||
|
if ($cg->cacheIsValid) {
|
||||||
|
// client's cache is valid
|
||||||
|
if (! self::$_options['quiet']) {
|
||||||
|
$cg->sendHeaders();
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
return array(
|
||||||
|
'success' => true
|
||||||
|
,'statusCode' => 304
|
||||||
|
,'content' => ''
|
||||||
|
,'headers' => $cg->getHeaders()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// client will need output
|
||||||
|
$headers = $cg->getHeaders();
|
||||||
|
unset($cg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self::$_options['contentType'] === self::TYPE_CSS
|
||||||
|
&& self::$_options['rewriteCssUris']) {
|
||||||
|
foreach($controller->sources as $key => $source) {
|
||||||
|
if ($source->filepath
|
||||||
|
&& !isset($source->minifyOptions['currentDir'])
|
||||||
|
&& !isset($source->minifyOptions['prependRelativePath'])
|
||||||
|
) {
|
||||||
|
$source->minifyOptions['currentDir'] = dirname($source->filepath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check server cache
|
||||||
|
if (null !== self::$_cache && ! self::$_options['debug']) {
|
||||||
|
// using cache
|
||||||
|
// the goal is to use only the cache methods to sniff the length and
|
||||||
|
// output the content, as they do not require ever loading the file into
|
||||||
|
// memory.
|
||||||
|
$cacheId = self::_getCacheId();
|
||||||
|
$fullCacheId = (self::$_options['encodeMethod'])
|
||||||
|
? $cacheId . '.gz'
|
||||||
|
: $cacheId;
|
||||||
|
// check cache for valid entry
|
||||||
|
$cacheIsReady = self::$_cache->isValid($fullCacheId, self::$_options['lastModifiedTime']);
|
||||||
|
if ($cacheIsReady) {
|
||||||
|
$cacheContentLength = self::$_cache->getSize($fullCacheId);
|
||||||
|
} else {
|
||||||
|
// generate & cache content
|
||||||
|
try {
|
||||||
|
$content = self::_combineMinify();
|
||||||
|
} catch (Exception $e) {
|
||||||
|
self::$_controller->log($e->getMessage());
|
||||||
|
if (! self::$_options['quiet']) {
|
||||||
|
self::_errorExit(self::$_options['errorHeader'], self::URL_DEBUG);
|
||||||
|
}
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
self::$_cache->store($cacheId, $content);
|
||||||
|
if (function_exists('gzencode')) {
|
||||||
|
self::$_cache->store($cacheId . '.gz', gzencode($content, self::$_options['encodeLevel']));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// no cache
|
||||||
|
$cacheIsReady = false;
|
||||||
|
try {
|
||||||
|
$content = self::_combineMinify();
|
||||||
|
} catch (Exception $e) {
|
||||||
|
self::$_controller->log($e->getMessage());
|
||||||
|
if (! self::$_options['quiet']) {
|
||||||
|
self::_errorExit(self::$_options['errorHeader'], self::URL_DEBUG);
|
||||||
|
}
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (! $cacheIsReady && self::$_options['encodeMethod']) {
|
||||||
|
// still need to encode
|
||||||
|
$content = gzencode($content, self::$_options['encodeLevel']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add headers
|
||||||
|
$headers['Content-Length'] = $cacheIsReady
|
||||||
|
? $cacheContentLength
|
||||||
|
: ((function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2))
|
||||||
|
? mb_strlen($content, '8bit')
|
||||||
|
: strlen($content)
|
||||||
|
);
|
||||||
|
$headers['Content-Type'] = self::$_options['contentTypeCharset']
|
||||||
|
? self::$_options['contentType'] . '; charset=' . self::$_options['contentTypeCharset']
|
||||||
|
: self::$_options['contentType'];
|
||||||
|
if (self::$_options['encodeMethod'] !== '') {
|
||||||
|
$headers['Content-Encoding'] = $contentEncoding;
|
||||||
|
}
|
||||||
|
if (self::$_options['encodeOutput'] && $sendVary) {
|
||||||
|
$headers['Vary'] = 'Accept-Encoding';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! self::$_options['quiet']) {
|
||||||
|
// output headers & content
|
||||||
|
foreach ($headers as $name => $val) {
|
||||||
|
header($name . ': ' . $val);
|
||||||
|
}
|
||||||
|
if ($cacheIsReady) {
|
||||||
|
self::$_cache->display($fullCacheId);
|
||||||
|
} else {
|
||||||
|
echo $content;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return array(
|
||||||
|
'success' => true
|
||||||
|
,'statusCode' => 200
|
||||||
|
,'content' => $cacheIsReady
|
||||||
|
? self::$_cache->fetch($fullCacheId)
|
||||||
|
: $content
|
||||||
|
,'headers' => $headers
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return combined minified content for a set of sources
|
||||||
|
*
|
||||||
|
* No internal caching will be used and the content will not be HTTP encoded.
|
||||||
|
*
|
||||||
|
* @param array $sources array of filepaths and/or Minify_Source objects
|
||||||
|
*
|
||||||
|
* @param array $options (optional) array of options for serve. By default
|
||||||
|
* these are already set: quiet = true, encodeMethod = '', lastModifiedTime = 0.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function combine($sources, $options = array())
|
||||||
|
{
|
||||||
|
$cache = self::$_cache;
|
||||||
|
self::$_cache = null;
|
||||||
|
$options = array_merge(array(
|
||||||
|
'files' => (array)$sources
|
||||||
|
,'quiet' => true
|
||||||
|
,'encodeMethod' => ''
|
||||||
|
,'lastModifiedTime' => 0
|
||||||
|
), $options);
|
||||||
|
$out = self::serve('Files', $options);
|
||||||
|
self::$_cache = $cache;
|
||||||
|
return $out['content'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set $_SERVER['DOCUMENT_ROOT']. On IIS, the value is created from SCRIPT_FILENAME and SCRIPT_NAME.
|
||||||
|
*
|
||||||
|
* @param string $docRoot value to use for DOCUMENT_ROOT
|
||||||
|
*/
|
||||||
|
public static function setDocRoot($docRoot = '')
|
||||||
|
{
|
||||||
|
self::$isDocRootSet = true;
|
||||||
|
if ($docRoot) {
|
||||||
|
$_SERVER['DOCUMENT_ROOT'] = $docRoot;
|
||||||
|
} elseif (isset($_SERVER['SERVER_SOFTWARE'])
|
||||||
|
&& 0 === strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS/')) {
|
||||||
|
$_SERVER['DOCUMENT_ROOT'] = substr(
|
||||||
|
$_SERVER['SCRIPT_FILENAME']
|
||||||
|
,0
|
||||||
|
,strlen($_SERVER['SCRIPT_FILENAME']) - strlen($_SERVER['SCRIPT_NAME']));
|
||||||
|
$_SERVER['DOCUMENT_ROOT'] = rtrim($_SERVER['DOCUMENT_ROOT'], '\\');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Any Minify_Cache_* object or null (i.e. no server cache is used)
|
||||||
|
*
|
||||||
|
* @var Minify_Cache_File
|
||||||
|
*/
|
||||||
|
private static $_cache = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Active controller for current request
|
||||||
|
*
|
||||||
|
* @var Minify_Controller_Base
|
||||||
|
*/
|
||||||
|
protected static $_controller = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for current request
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected static $_options = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $header
|
||||||
|
*
|
||||||
|
* @param string $url
|
||||||
|
*/
|
||||||
|
protected static function _errorExit($header, $url)
|
||||||
|
{
|
||||||
|
$url = htmlspecialchars($url, ENT_QUOTES);
|
||||||
|
list(,$h1) = explode(' ', $header, 2);
|
||||||
|
$h1 = htmlspecialchars($h1);
|
||||||
|
// FastCGI environments require 3rd arg to header() to be set
|
||||||
|
list(, $code) = explode(' ', $header, 3);
|
||||||
|
header($header, true, $code);
|
||||||
|
header('Content-Type: text/html; charset=utf-8');
|
||||||
|
echo "<h1>$h1</h1>";
|
||||||
|
echo "<p>Please see <a href='$url'>$url</a>.</p>";
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up sources to use Minify_Lines
|
||||||
|
*
|
||||||
|
* @param array $sources Minify_Source instances
|
||||||
|
*/
|
||||||
|
protected static function _setupDebug($sources)
|
||||||
|
{
|
||||||
|
foreach ($sources as $source) {
|
||||||
|
$source->minifier = array('Minify_Lines', 'minify');
|
||||||
|
$id = $source->getId();
|
||||||
|
$source->minifyOptions = array(
|
||||||
|
'id' => (is_file($id) ? basename($id) : $id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combines sources and minifies the result.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected static function _combineMinify()
|
||||||
|
{
|
||||||
|
$type = self::$_options['contentType']; // ease readability
|
||||||
|
|
||||||
|
// when combining scripts, make sure all statements separated and
|
||||||
|
// trailing single line comment is terminated
|
||||||
|
$implodeSeparator = ($type === self::TYPE_JS)
|
||||||
|
? "\n;"
|
||||||
|
: '';
|
||||||
|
// allow the user to pass a particular array of options to each
|
||||||
|
// minifier (designated by type). source objects may still override
|
||||||
|
// these
|
||||||
|
$defaultOptions = isset(self::$_options['minifierOptions'][$type])
|
||||||
|
? self::$_options['minifierOptions'][$type]
|
||||||
|
: array();
|
||||||
|
// if minifier not set, default is no minification. source objects
|
||||||
|
// may still override this
|
||||||
|
$defaultMinifier = isset(self::$_options['minifiers'][$type])
|
||||||
|
? self::$_options['minifiers'][$type]
|
||||||
|
: false;
|
||||||
|
|
||||||
|
// process groups of sources with identical minifiers/options
|
||||||
|
$content = array();
|
||||||
|
$i = 0;
|
||||||
|
$l = count(self::$_controller->sources);
|
||||||
|
$groupToProcessTogether = array();
|
||||||
|
$lastMinifier = null;
|
||||||
|
$lastOptions = null;
|
||||||
|
do {
|
||||||
|
// get next source
|
||||||
|
$source = null;
|
||||||
|
if ($i < $l) {
|
||||||
|
$source = self::$_controller->sources[$i];
|
||||||
|
/* @var Minify_Source $source */
|
||||||
|
$sourceContent = $source->getContent();
|
||||||
|
|
||||||
|
// allow the source to override our minifier and options
|
||||||
|
$minifier = (null !== $source->minifier)
|
||||||
|
? $source->minifier
|
||||||
|
: $defaultMinifier;
|
||||||
|
$options = (null !== $source->minifyOptions)
|
||||||
|
? array_merge($defaultOptions, $source->minifyOptions)
|
||||||
|
: $defaultOptions;
|
||||||
|
}
|
||||||
|
// do we need to process our group right now?
|
||||||
|
if ($i > 0 // yes, we have at least the first group populated
|
||||||
|
&& (
|
||||||
|
! $source // yes, we ran out of sources
|
||||||
|
|| $type === self::TYPE_CSS // yes, to process CSS individually (avoiding PCRE bugs/limits)
|
||||||
|
|| $minifier !== $lastMinifier // yes, minifier changed
|
||||||
|
|| $options !== $lastOptions) // yes, options changed
|
||||||
|
)
|
||||||
|
{
|
||||||
|
// minify previous sources with last settings
|
||||||
|
$imploded = implode($implodeSeparator, $groupToProcessTogether);
|
||||||
|
$groupToProcessTogether = array();
|
||||||
|
if ($lastMinifier) {
|
||||||
|
self::$_controller->loadMinifier($lastMinifier);
|
||||||
|
try {
|
||||||
|
$content[] = call_user_func($lastMinifier, $imploded, $lastOptions);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
throw new Exception("Exception in minifier: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$content[] = $imploded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// add content to the group
|
||||||
|
if ($source) {
|
||||||
|
$groupToProcessTogether[] = $sourceContent;
|
||||||
|
$lastMinifier = $minifier;
|
||||||
|
$lastOptions = $options;
|
||||||
|
}
|
||||||
|
$i++;
|
||||||
|
} while ($source);
|
||||||
|
|
||||||
|
$content = implode($implodeSeparator, $content);
|
||||||
|
|
||||||
|
if ($type === self::TYPE_CSS && false !== strpos($content, '@import')) {
|
||||||
|
$content = self::_handleCssImports($content);
|
||||||
|
}
|
||||||
|
|
||||||
|
// do any post-processing (esp. for editing build URIs)
|
||||||
|
if (self::$_options['postprocessorRequire']) {
|
||||||
|
require_once self::$_options['postprocessorRequire'];
|
||||||
|
}
|
||||||
|
if (self::$_options['postprocessor']) {
|
||||||
|
$content = call_user_func(self::$_options['postprocessor'], $content, $type);
|
||||||
|
}
|
||||||
|
return $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a unique cache id for for this request.
|
||||||
|
*
|
||||||
|
* Any settings that could affect output are taken into consideration
|
||||||
|
*
|
||||||
|
* @param string $prefix
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected static function _getCacheId($prefix = 'minify')
|
||||||
|
{
|
||||||
|
$name = preg_replace('/[^a-zA-Z0-9\\.=_,]/', '', self::$_controller->selectionId);
|
||||||
|
$name = preg_replace('/\\.+/', '.', $name);
|
||||||
|
$name = substr($name, 0, 200 - 34 - strlen($prefix));
|
||||||
|
$md5 = md5(serialize(array(
|
||||||
|
Minify_Source::getDigest(self::$_controller->sources)
|
||||||
|
,self::$_options['minifiers']
|
||||||
|
,self::$_options['minifierOptions']
|
||||||
|
,self::$_options['postprocessor']
|
||||||
|
,self::$_options['bubbleCssImports']
|
||||||
|
,self::VERSION
|
||||||
|
)));
|
||||||
|
return "{$prefix}_{$name}_{$md5}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bubble CSS @imports to the top or prepend a warning if an import is detected not at the top.
|
||||||
|
*
|
||||||
|
* @param string $css
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected static function _handleCssImports($css)
|
||||||
|
{
|
||||||
|
if (self::$_options['bubbleCssImports']) {
|
||||||
|
// bubble CSS imports
|
||||||
|
preg_match_all('/@import.*?;/', $css, $imports);
|
||||||
|
$css = implode('', $imports[0]) . preg_replace('/@import.*?;/', '', $css);
|
||||||
|
} else if ('' !== self::$importWarning) {
|
||||||
|
// remove comments so we don't mistake { in a comment as a block
|
||||||
|
$noCommentCss = preg_replace('@/\\*[\\s\\S]*?\\*/@', '', $css);
|
||||||
|
$lastImportPos = strrpos($noCommentCss, '@import');
|
||||||
|
$firstBlockPos = strpos($noCommentCss, '{');
|
||||||
|
if (false !== $lastImportPos
|
||||||
|
&& false !== $firstBlockPos
|
||||||
|
&& $firstBlockPos < $lastImportPos
|
||||||
|
) {
|
||||||
|
// { appears before @import : prepend warning
|
||||||
|
$css = self::$importWarning . $css;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $css;
|
||||||
|
}
|
||||||
|
}
|
103
inc/contrib/minify/Minify/Build.php
Executable file
103
inc/contrib/minify/Minify/Build.php
Executable file
@ -0,0 +1,103 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Class Minify_Build
|
||||||
|
* @package Minify
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once 'Minify/Source.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maintain a single last modification time for a group of Minify sources to
|
||||||
|
* allow use of far off Expires headers in Minify.
|
||||||
|
*
|
||||||
|
* <code>
|
||||||
|
* // in config file
|
||||||
|
* $groupSources = array(
|
||||||
|
* 'js' => array('file1.js', 'file2.js')
|
||||||
|
* ,'css' => array('file1.css', 'file2.css', 'file3.css')
|
||||||
|
* )
|
||||||
|
*
|
||||||
|
* // during HTML generation
|
||||||
|
* $jsBuild = new Minify_Build($groupSources['js']);
|
||||||
|
* $cssBuild = new Minify_Build($groupSources['css']);
|
||||||
|
*
|
||||||
|
* $script = "<script type='text/javascript' src='"
|
||||||
|
* . $jsBuild->uri('/min.php/js') . "'></script>";
|
||||||
|
* $link = "<link rel='stylesheet' type='text/css' href='"
|
||||||
|
* . $cssBuild->uri('/min.php/css') . "'>";
|
||||||
|
*
|
||||||
|
* // in min.php
|
||||||
|
* Minify::serve('Groups', array(
|
||||||
|
* 'groups' => $groupSources
|
||||||
|
* ,'setExpires' => (time() + 86400 * 365)
|
||||||
|
* ));
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
|
* @package Minify
|
||||||
|
* @author Stephen Clay <steve@mrclay.org>
|
||||||
|
*/
|
||||||
|
class Minify_Build {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Last modification time of all files in the build
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $lastModified = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String to use as ampersand in uri(). Set this to '&' if
|
||||||
|
* you are not HTML-escaping URIs.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public static $ampersand = '&';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a time-stamped URI
|
||||||
|
*
|
||||||
|
* <code>
|
||||||
|
* echo $b->uri('/site.js');
|
||||||
|
* // outputs "/site.js?1678242"
|
||||||
|
*
|
||||||
|
* echo $b->uri('/scriptaculous.js?load=effects');
|
||||||
|
* // outputs "/scriptaculous.js?load=effects&1678242"
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
|
* @param string $uri
|
||||||
|
* @param boolean $forceAmpersand (default = false) Force the use of ampersand to
|
||||||
|
* append the timestamp to the URI.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function uri($uri, $forceAmpersand = false) {
|
||||||
|
$sep = ($forceAmpersand || strpos($uri, '?') !== false)
|
||||||
|
? self::$ampersand
|
||||||
|
: '?';
|
||||||
|
return "{$uri}{$sep}{$this->lastModified}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a build object
|
||||||
|
*
|
||||||
|
* @param array $sources array of Minify_Source objects and/or file paths
|
||||||
|
*
|
||||||
|
* @return null
|
||||||
|
*/
|
||||||
|
public function __construct($sources)
|
||||||
|
{
|
||||||
|
$max = 0;
|
||||||
|
foreach ((array)$sources as $source) {
|
||||||
|
if ($source instanceof Minify_Source) {
|
||||||
|
$max = max($max, $source->lastModified);
|
||||||
|
} elseif (is_string($source)) {
|
||||||
|
if (0 === strpos($source, '//')) {
|
||||||
|
$source = $_SERVER['DOCUMENT_ROOT'] . substr($source, 1);
|
||||||
|
}
|
||||||
|
if (is_file($source)) {
|
||||||
|
$max = max($max, filemtime($source));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->lastModified = $max;
|
||||||
|
}
|
||||||
|
}
|
99
inc/contrib/minify/Minify/CSS.php
Executable file
99
inc/contrib/minify/Minify/CSS.php
Executable file
@ -0,0 +1,99 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Class Minify_CSS
|
||||||
|
* @package Minify
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minify CSS
|
||||||
|
*
|
||||||
|
* This class uses Minify_CSS_Compressor and Minify_CSS_UriRewriter to
|
||||||
|
* minify CSS and rewrite relative URIs.
|
||||||
|
*
|
||||||
|
* @package Minify
|
||||||
|
* @author Stephen Clay <steve@mrclay.org>
|
||||||
|
* @author http://code.google.com/u/1stvamp/ (Issue 64 patch)
|
||||||
|
*/
|
||||||
|
class Minify_CSS {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minify a CSS string
|
||||||
|
*
|
||||||
|
* @param string $css
|
||||||
|
*
|
||||||
|
* @param array $options available options:
|
||||||
|
*
|
||||||
|
* 'preserveComments': (default true) multi-line comments that begin
|
||||||
|
* with "/*!" will be preserved with newlines before and after to
|
||||||
|
* enhance readability.
|
||||||
|
*
|
||||||
|
* 'removeCharsets': (default true) remove all @charset at-rules
|
||||||
|
*
|
||||||
|
* 'prependRelativePath': (default null) if given, this string will be
|
||||||
|
* prepended to all relative URIs in import/url declarations
|
||||||
|
*
|
||||||
|
* 'currentDir': (default null) if given, this is assumed to be the
|
||||||
|
* directory of the current CSS file. Using this, minify will rewrite
|
||||||
|
* all relative URIs in import/url declarations to correctly point to
|
||||||
|
* the desired files. For this to work, the files *must* exist and be
|
||||||
|
* visible by the PHP process.
|
||||||
|
*
|
||||||
|
* 'symlinks': (default = array()) If the CSS file is stored in
|
||||||
|
* a symlink-ed directory, provide an array of link paths to
|
||||||
|
* target paths, where the link paths are within the document root. Because
|
||||||
|
* paths need to be normalized for this to work, use "//" to substitute
|
||||||
|
* the doc root in the link paths (the array keys). E.g.:
|
||||||
|
* <code>
|
||||||
|
* array('//symlink' => '/real/target/path') // unix
|
||||||
|
* array('//static' => 'D:\\staticStorage') // Windows
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
|
* 'docRoot': (default = $_SERVER['DOCUMENT_ROOT'])
|
||||||
|
* see Minify_CSS_UriRewriter::rewrite
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function minify($css, $options = array())
|
||||||
|
{
|
||||||
|
$options = array_merge(array(
|
||||||
|
'removeCharsets' => true,
|
||||||
|
'preserveComments' => true,
|
||||||
|
'currentDir' => null,
|
||||||
|
'docRoot' => $_SERVER['DOCUMENT_ROOT'],
|
||||||
|
'prependRelativePath' => null,
|
||||||
|
'symlinks' => array(),
|
||||||
|
), $options);
|
||||||
|
|
||||||
|
if ($options['removeCharsets']) {
|
||||||
|
$css = preg_replace('/@charset[^;]+;\\s*/', '', $css);
|
||||||
|
}
|
||||||
|
require_once 'Minify/CSS/Compressor.php';
|
||||||
|
if (! $options['preserveComments']) {
|
||||||
|
$css = Minify_CSS_Compressor::process($css, $options);
|
||||||
|
} else {
|
||||||
|
require_once 'Minify/CommentPreserver.php';
|
||||||
|
$css = Minify_CommentPreserver::process(
|
||||||
|
$css
|
||||||
|
,array('Minify_CSS_Compressor', 'process')
|
||||||
|
,array($options)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (! $options['currentDir'] && ! $options['prependRelativePath']) {
|
||||||
|
return $css;
|
||||||
|
}
|
||||||
|
require_once 'Minify/CSS/UriRewriter.php';
|
||||||
|
if ($options['currentDir']) {
|
||||||
|
return Minify_CSS_UriRewriter::rewrite(
|
||||||
|
$css
|
||||||
|
,$options['currentDir']
|
||||||
|
,$options['docRoot']
|
||||||
|
,$options['symlinks']
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Minify_CSS_UriRewriter::prepend(
|
||||||
|
$css
|
||||||
|
,$options['prependRelativePath']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
249
inc/contrib/minify/Minify/CSS/Compressor.php
Executable file
249
inc/contrib/minify/Minify/CSS/Compressor.php
Executable file
@ -0,0 +1,249 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Class Minify_CSS_Compressor
|
||||||
|
* @package Minify
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compress CSS
|
||||||
|
*
|
||||||
|
* This is a heavy regex-based removal of whitespace, unnecessary
|
||||||
|
* comments and tokens, and some CSS value minimization, where practical.
|
||||||
|
* Many steps have been taken to avoid breaking comment-based hacks,
|
||||||
|
* including the ie5/mac filter (and its inversion), but expect tricky
|
||||||
|
* hacks involving comment tokens in 'content' value strings to break
|
||||||
|
* minimization badly. A test suite is available.
|
||||||
|
*
|
||||||
|
* @package Minify
|
||||||
|
* @author Stephen Clay <steve@mrclay.org>
|
||||||
|
* @author http://code.google.com/u/1stvamp/ (Issue 64 patch)
|
||||||
|
*/
|
||||||
|
class Minify_CSS_Compressor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minify a CSS string
|
||||||
|
*
|
||||||
|
* @param string $css
|
||||||
|
*
|
||||||
|
* @param array $options (currently ignored)
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function process($css, $options = array())
|
||||||
|
{
|
||||||
|
$obj = new Minify_CSS_Compressor($options);
|
||||||
|
return $obj->_process($css);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $_options = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Are we "in" a hack? I.e. are some browsers targetted until the next comment?
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $_inHack = false;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param array $options (currently ignored)
|
||||||
|
*/
|
||||||
|
private function __construct($options) {
|
||||||
|
$this->_options = $options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minify a CSS string
|
||||||
|
*
|
||||||
|
* @param string $css
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function _process($css)
|
||||||
|
{
|
||||||
|
$css = str_replace("\r\n", "\n", $css);
|
||||||
|
|
||||||
|
// preserve empty comment after '>'
|
||||||
|
// http://www.webdevout.net/css-hacks#in_css-selectors
|
||||||
|
$css = preg_replace('@>/\\*\\s*\\*/@', '>/*keep*/', $css);
|
||||||
|
|
||||||
|
// preserve empty comment between property and value
|
||||||
|
// http://css-discuss.incutio.com/?page=BoxModelHack
|
||||||
|
$css = preg_replace('@/\\*\\s*\\*/\\s*:@', '/*keep*/:', $css);
|
||||||
|
$css = preg_replace('@:\\s*/\\*\\s*\\*/@', ':/*keep*/', $css);
|
||||||
|
|
||||||
|
// apply callback to all valid comments (and strip out surrounding ws
|
||||||
|
$css = preg_replace_callback('@\\s*/\\*([\\s\\S]*?)\\*/\\s*@'
|
||||||
|
,array($this, '_commentCB'), $css);
|
||||||
|
|
||||||
|
// remove ws around { } and last semicolon in declaration block
|
||||||
|
$css = preg_replace('/\\s*{\\s*/', '{', $css);
|
||||||
|
$css = preg_replace('/;?\\s*}\\s*/', '}', $css);
|
||||||
|
|
||||||
|
// remove ws surrounding semicolons
|
||||||
|
$css = preg_replace('/\\s*;\\s*/', ';', $css);
|
||||||
|
|
||||||
|
// remove ws around urls
|
||||||
|
$css = preg_replace('/
|
||||||
|
url\\( # url(
|
||||||
|
\\s*
|
||||||
|
([^\\)]+?) # 1 = the URL (really just a bunch of non right parenthesis)
|
||||||
|
\\s*
|
||||||
|
\\) # )
|
||||||
|
/x', 'url($1)', $css);
|
||||||
|
|
||||||
|
// remove ws between rules and colons
|
||||||
|
$css = preg_replace('/
|
||||||
|
\\s*
|
||||||
|
([{;]) # 1 = beginning of block or rule separator
|
||||||
|
\\s*
|
||||||
|
([\\*_]?[\\w\\-]+) # 2 = property (and maybe IE filter)
|
||||||
|
\\s*
|
||||||
|
:
|
||||||
|
\\s*
|
||||||
|
(\\b|[#\'"-]) # 3 = first character of a value
|
||||||
|
/x', '$1$2:$3', $css);
|
||||||
|
|
||||||
|
// remove ws in selectors
|
||||||
|
$css = preg_replace_callback('/
|
||||||
|
(?: # non-capture
|
||||||
|
\\s*
|
||||||
|
[^~>+,\\s]+ # selector part
|
||||||
|
\\s*
|
||||||
|
[,>+~] # combinators
|
||||||
|
)+
|
||||||
|
\\s*
|
||||||
|
[^~>+,\\s]+ # selector part
|
||||||
|
{ # open declaration block
|
||||||
|
/x'
|
||||||
|
,array($this, '_selectorsCB'), $css);
|
||||||
|
|
||||||
|
// minimize hex colors
|
||||||
|
$css = preg_replace('/([^=])#([a-f\\d])\\2([a-f\\d])\\3([a-f\\d])\\4([\\s;\\}])/i'
|
||||||
|
, '$1#$2$3$4$5', $css);
|
||||||
|
|
||||||
|
// remove spaces between font families
|
||||||
|
$css = preg_replace_callback('/font-family:([^;}]+)([;}])/'
|
||||||
|
,array($this, '_fontFamilyCB'), $css);
|
||||||
|
|
||||||
|
$css = preg_replace('/@import\\s+url/', '@import url', $css);
|
||||||
|
|
||||||
|
// replace any ws involving newlines with a single newline
|
||||||
|
$css = preg_replace('/[ \\t]*\\n+\\s*/', "\n", $css);
|
||||||
|
|
||||||
|
// separate common descendent selectors w/ newlines (to limit line lengths)
|
||||||
|
$css = preg_replace('/([\\w#\\.\\*]+)\\s+([\\w#\\.\\*]+){/', "$1\n$2{", $css);
|
||||||
|
|
||||||
|
// Use newline after 1st numeric value (to limit line lengths).
|
||||||
|
$css = preg_replace('/
|
||||||
|
((?:padding|margin|border|outline):\\d+(?:px|em)?) # 1 = prop : 1st numeric value
|
||||||
|
\\s+
|
||||||
|
/x'
|
||||||
|
,"$1\n", $css);
|
||||||
|
|
||||||
|
// prevent triggering IE6 bug: http://www.crankygeek.com/ie6pebug/
|
||||||
|
$css = preg_replace('/:first-l(etter|ine)\\{/', ':first-l$1 {', $css);
|
||||||
|
|
||||||
|
return trim($css);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace what looks like a set of selectors
|
||||||
|
*
|
||||||
|
* @param array $m regex matches
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function _selectorsCB($m)
|
||||||
|
{
|
||||||
|
// remove ws around the combinators
|
||||||
|
return preg_replace('/\\s*([,>+~])\\s*/', '$1', $m[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a comment and return a replacement
|
||||||
|
*
|
||||||
|
* @param array $m regex matches
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function _commentCB($m)
|
||||||
|
{
|
||||||
|
$hasSurroundingWs = (trim($m[0]) !== $m[1]);
|
||||||
|
$m = $m[1];
|
||||||
|
// $m is the comment content w/o the surrounding tokens,
|
||||||
|
// but the return value will replace the entire comment.
|
||||||
|
if ($m === 'keep') {
|
||||||
|
return '/**/';
|
||||||
|
}
|
||||||
|
if ($m === '" "') {
|
||||||
|
// component of http://tantek.com/CSS/Examples/midpass.html
|
||||||
|
return '/*" "*/';
|
||||||
|
}
|
||||||
|
if (preg_match('@";\\}\\s*\\}/\\*\\s+@', $m)) {
|
||||||
|
// component of http://tantek.com/CSS/Examples/midpass.html
|
||||||
|
return '/*";}}/* */';
|
||||||
|
}
|
||||||
|
if ($this->_inHack) {
|
||||||
|
// inversion: feeding only to one browser
|
||||||
|
if (preg_match('@
|
||||||
|
^/ # comment started like /*/
|
||||||
|
\\s*
|
||||||
|
(\\S[\\s\\S]+?) # has at least some non-ws content
|
||||||
|
\\s*
|
||||||
|
/\\* # ends like /*/ or /**/
|
||||||
|
@x', $m, $n)) {
|
||||||
|
// end hack mode after this comment, but preserve the hack and comment content
|
||||||
|
$this->_inHack = false;
|
||||||
|
return "/*/{$n[1]}/**/";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (substr($m, -1) === '\\') { // comment ends like \*/
|
||||||
|
// begin hack mode and preserve hack
|
||||||
|
$this->_inHack = true;
|
||||||
|
return '/*\\*/';
|
||||||
|
}
|
||||||
|
if ($m !== '' && $m[0] === '/') { // comment looks like /*/ foo */
|
||||||
|
// begin hack mode and preserve hack
|
||||||
|
$this->_inHack = true;
|
||||||
|
return '/*/*/';
|
||||||
|
}
|
||||||
|
if ($this->_inHack) {
|
||||||
|
// a regular comment ends hack mode but should be preserved
|
||||||
|
$this->_inHack = false;
|
||||||
|
return '/**/';
|
||||||
|
}
|
||||||
|
// Issue 107: if there's any surrounding whitespace, it may be important, so
|
||||||
|
// replace the comment with a single space
|
||||||
|
return $hasSurroundingWs // remove all other comments
|
||||||
|
? ' '
|
||||||
|
: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a font-family listing and return a replacement
|
||||||
|
*
|
||||||
|
* @param array $m regex matches
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function _fontFamilyCB($m)
|
||||||
|
{
|
||||||
|
// Issue 210: must not eliminate WS between words in unquoted families
|
||||||
|
$pieces = preg_split('/(\'[^\']+\'|"[^"]+")/', $m[1], null, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
|
||||||
|
$out = 'font-family:';
|
||||||
|
while (null !== ($piece = array_shift($pieces))) {
|
||||||
|
if ($piece[0] !== '"' && $piece[0] !== "'") {
|
||||||
|
$piece = preg_replace('/\\s+/', ' ', $piece);
|
||||||
|
$piece = preg_replace('/\\s?,\\s?/', ',', $piece);
|
||||||
|
}
|
||||||
|
$out .= $piece;
|
||||||
|
}
|
||||||
|
return $out . $m[2];
|
||||||
|
}
|
||||||
|
}
|
310
inc/contrib/minify/Minify/CSS/UriRewriter.php
Executable file
310
inc/contrib/minify/Minify/CSS/UriRewriter.php
Executable file
@ -0,0 +1,310 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Class Minify_CSS_UriRewriter
|
||||||
|
* @package Minify
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rewrite file-relative URIs as root-relative in CSS files
|
||||||
|
*
|
||||||
|
* @package Minify
|
||||||
|
* @author Stephen Clay <steve@mrclay.org>
|
||||||
|
*/
|
||||||
|
class Minify_CSS_UriRewriter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* rewrite() and rewriteRelative() append debugging information here
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public static $debugText = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In CSS content, rewrite file relative URIs as root relative
|
||||||
|
*
|
||||||
|
* @param string $css
|
||||||
|
*
|
||||||
|
* @param string $currentDir The directory of the current CSS file.
|
||||||
|
*
|
||||||
|
* @param string $docRoot The document root of the web site in which
|
||||||
|
* the CSS file resides (default = $_SERVER['DOCUMENT_ROOT']).
|
||||||
|
*
|
||||||
|
* @param array $symlinks (default = array()) If the CSS file is stored in
|
||||||
|
* a symlink-ed directory, provide an array of link paths to
|
||||||
|
* target paths, where the link paths are within the document root. Because
|
||||||
|
* paths need to be normalized for this to work, use "//" to substitute
|
||||||
|
* the doc root in the link paths (the array keys). E.g.:
|
||||||
|
* <code>
|
||||||
|
* array('//symlink' => '/real/target/path') // unix
|
||||||
|
* array('//static' => 'D:\\staticStorage') // Windows
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function rewrite($css, $currentDir, $docRoot = null, $symlinks = array())
|
||||||
|
{
|
||||||
|
self::$_docRoot = self::_realpath(
|
||||||
|
$docRoot ? $docRoot : $_SERVER['DOCUMENT_ROOT']
|
||||||
|
);
|
||||||
|
self::$_currentDir = self::_realpath($currentDir);
|
||||||
|
self::$_symlinks = array();
|
||||||
|
|
||||||
|
// normalize symlinks
|
||||||
|
foreach ($symlinks as $link => $target) {
|
||||||
|
$link = ($link === '//')
|
||||||
|
? self::$_docRoot
|
||||||
|
: str_replace('//', self::$_docRoot . '/', $link);
|
||||||
|
$link = strtr($link, '/', DIRECTORY_SEPARATOR);
|
||||||
|
self::$_symlinks[$link] = self::_realpath($target);
|
||||||
|
}
|
||||||
|
|
||||||
|
self::$debugText .= "docRoot : " . self::$_docRoot . "\n"
|
||||||
|
. "currentDir : " . self::$_currentDir . "\n";
|
||||||
|
if (self::$_symlinks) {
|
||||||
|
self::$debugText .= "symlinks : " . var_export(self::$_symlinks, 1) . "\n";
|
||||||
|
}
|
||||||
|
self::$debugText .= "\n";
|
||||||
|
|
||||||
|
$css = self::_trimUrls($css);
|
||||||
|
|
||||||
|
// rewrite
|
||||||
|
$css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/'
|
||||||
|
,array(self::$className, '_processUriCB'), $css);
|
||||||
|
$css = preg_replace_callback('/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
|
||||||
|
,array(self::$className, '_processUriCB'), $css);
|
||||||
|
|
||||||
|
return $css;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In CSS content, prepend a path to relative URIs
|
||||||
|
*
|
||||||
|
* @param string $css
|
||||||
|
*
|
||||||
|
* @param string $path The path to prepend.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function prepend($css, $path)
|
||||||
|
{
|
||||||
|
self::$_prependPath = $path;
|
||||||
|
|
||||||
|
$css = self::_trimUrls($css);
|
||||||
|
|
||||||
|
// append
|
||||||
|
$css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/'
|
||||||
|
,array(self::$className, '_processUriCB'), $css);
|
||||||
|
$css = preg_replace_callback('/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
|
||||||
|
,array(self::$className, '_processUriCB'), $css);
|
||||||
|
|
||||||
|
self::$_prependPath = null;
|
||||||
|
return $css;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a root relative URI from a file relative URI
|
||||||
|
*
|
||||||
|
* <code>
|
||||||
|
* Minify_CSS_UriRewriter::rewriteRelative(
|
||||||
|
* '../img/hello.gif'
|
||||||
|
* , '/home/user/www/css' // path of CSS file
|
||||||
|
* , '/home/user/www' // doc root
|
||||||
|
* );
|
||||||
|
* // returns '/img/hello.gif'
|
||||||
|
*
|
||||||
|
* // example where static files are stored in a symlinked directory
|
||||||
|
* Minify_CSS_UriRewriter::rewriteRelative(
|
||||||
|
* 'hello.gif'
|
||||||
|
* , '/var/staticFiles/theme'
|
||||||
|
* , '/home/user/www'
|
||||||
|
* , array('/home/user/www/static' => '/var/staticFiles')
|
||||||
|
* );
|
||||||
|
* // returns '/static/theme/hello.gif'
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
|
* @param string $uri file relative URI
|
||||||
|
*
|
||||||
|
* @param string $realCurrentDir realpath of the current file's directory.
|
||||||
|
*
|
||||||
|
* @param string $realDocRoot realpath of the site document root.
|
||||||
|
*
|
||||||
|
* @param array $symlinks (default = array()) If the file is stored in
|
||||||
|
* a symlink-ed directory, provide an array of link paths to
|
||||||
|
* real target paths, where the link paths "appear" to be within the document
|
||||||
|
* root. E.g.:
|
||||||
|
* <code>
|
||||||
|
* array('/home/foo/www/not/real/path' => '/real/target/path') // unix
|
||||||
|
* array('C:\\htdocs\\not\\real' => 'D:\\real\\target\\path') // Windows
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function rewriteRelative($uri, $realCurrentDir, $realDocRoot, $symlinks = array())
|
||||||
|
{
|
||||||
|
// prepend path with current dir separator (OS-independent)
|
||||||
|
$path = strtr($realCurrentDir, '/', DIRECTORY_SEPARATOR)
|
||||||
|
. DIRECTORY_SEPARATOR . strtr($uri, '/', DIRECTORY_SEPARATOR);
|
||||||
|
|
||||||
|
self::$debugText .= "file-relative URI : {$uri}\n"
|
||||||
|
. "path prepended : {$path}\n";
|
||||||
|
|
||||||
|
// "unresolve" a symlink back to doc root
|
||||||
|
foreach ($symlinks as $link => $target) {
|
||||||
|
if (0 === strpos($path, $target)) {
|
||||||
|
// replace $target with $link
|
||||||
|
$path = $link . substr($path, strlen($target));
|
||||||
|
|
||||||
|
self::$debugText .= "symlink unresolved : {$path}\n";
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// strip doc root
|
||||||
|
$path = substr($path, strlen($realDocRoot));
|
||||||
|
|
||||||
|
self::$debugText .= "docroot stripped : {$path}\n";
|
||||||
|
|
||||||
|
// fix to root-relative URI
|
||||||
|
$uri = strtr($path, '/\\', '//');
|
||||||
|
$uri = self::removeDots($uri);
|
||||||
|
|
||||||
|
self::$debugText .= "traversals removed : {$uri}\n\n";
|
||||||
|
|
||||||
|
return $uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove instances of "./" and "../" where possible from a root-relative URI
|
||||||
|
*
|
||||||
|
* @param string $uri
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function removeDots($uri)
|
||||||
|
{
|
||||||
|
$uri = str_replace('/./', '/', $uri);
|
||||||
|
// inspired by patch from Oleg Cherniy
|
||||||
|
do {
|
||||||
|
$uri = preg_replace('@/[^/]+/\\.\\./@', '/', $uri, 1, $changed);
|
||||||
|
} while ($changed);
|
||||||
|
return $uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines which class to call as part of callbacks, change this
|
||||||
|
* if you extend Minify_CSS_UriRewriter
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected static $className = 'Minify_CSS_UriRewriter';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get realpath with any trailing slash removed. If realpath() fails,
|
||||||
|
* just remove the trailing slash.
|
||||||
|
*
|
||||||
|
* @param string $path
|
||||||
|
*
|
||||||
|
* @return mixed path with no trailing slash
|
||||||
|
*/
|
||||||
|
protected static function _realpath($path)
|
||||||
|
{
|
||||||
|
$realPath = realpath($path);
|
||||||
|
if ($realPath !== false) {
|
||||||
|
$path = $realPath;
|
||||||
|
}
|
||||||
|
return rtrim($path, '/\\');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Directory of this stylesheet
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private static $_currentDir = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DOC_ROOT
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private static $_docRoot = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* directory replacements to map symlink targets back to their
|
||||||
|
* source (within the document root) E.g. '/var/www/symlink' => '/var/realpath'
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private static $_symlinks = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Path to prepend
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private static $_prependPath = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $css
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private static function _trimUrls($css)
|
||||||
|
{
|
||||||
|
return preg_replace('/
|
||||||
|
url\\( # url(
|
||||||
|
\\s*
|
||||||
|
([^\\)]+?) # 1 = URI (assuming does not contain ")")
|
||||||
|
\\s*
|
||||||
|
\\) # )
|
||||||
|
/x', 'url($1)', $css);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $m
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private static function _processUriCB($m)
|
||||||
|
{
|
||||||
|
// $m matched either '/@import\\s+([\'"])(.*?)[\'"]/' or '/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
|
||||||
|
$isImport = ($m[0][0] === '@');
|
||||||
|
// determine URI and the quote character (if any)
|
||||||
|
if ($isImport) {
|
||||||
|
$quoteChar = $m[1];
|
||||||
|
$uri = $m[2];
|
||||||
|
} else {
|
||||||
|
// $m[1] is either quoted or not
|
||||||
|
$quoteChar = ($m[1][0] === "'" || $m[1][0] === '"')
|
||||||
|
? $m[1][0]
|
||||||
|
: '';
|
||||||
|
$uri = ($quoteChar === '')
|
||||||
|
? $m[1]
|
||||||
|
: substr($m[1], 1, strlen($m[1]) - 2);
|
||||||
|
}
|
||||||
|
// analyze URI
|
||||||
|
if ('/' !== $uri[0] // root-relative
|
||||||
|
&& false === strpos($uri, '//') // protocol (non-data)
|
||||||
|
&& 0 !== strpos($uri, 'data:') // data protocol
|
||||||
|
) {
|
||||||
|
// URI is file-relative: rewrite depending on options
|
||||||
|
if (self::$_prependPath === null) {
|
||||||
|
$uri = self::rewriteRelative($uri, self::$_currentDir, self::$_docRoot, self::$_symlinks);
|
||||||
|
} else {
|
||||||
|
$uri = self::$_prependPath . $uri;
|
||||||
|
if ($uri[0] === '/') {
|
||||||
|
$root = '';
|
||||||
|
$rootRelative = $uri;
|
||||||
|
$uri = $root . self::removeDots($rootRelative);
|
||||||
|
} elseif (preg_match('@^((https?\:)?//([^/]+))/@', $uri, $m) && (false !== strpos($m[3], '.'))) {
|
||||||
|
$root = $m[1];
|
||||||
|
$rootRelative = substr($uri, strlen($root));
|
||||||
|
$uri = $root . self::removeDots($rootRelative);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $isImport
|
||||||
|
? "@import {$quoteChar}{$uri}{$quoteChar}"
|
||||||
|
: "url({$quoteChar}{$uri}{$quoteChar})";
|
||||||
|
}
|
||||||
|
}
|
133
inc/contrib/minify/Minify/Cache/APC.php
Executable file
133
inc/contrib/minify/Minify/Cache/APC.php
Executable file
@ -0,0 +1,133 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Class Minify_Cache_APC
|
||||||
|
* @package Minify
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* APC-based cache class for Minify
|
||||||
|
*
|
||||||
|
* <code>
|
||||||
|
* Minify::setCache(new Minify_Cache_APC());
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
|
* @package Minify
|
||||||
|
* @author Chris Edwards
|
||||||
|
**/
|
||||||
|
class Minify_Cache_APC {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a Minify_Cache_APC object, to be passed to
|
||||||
|
* Minify::setCache().
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param int $expire seconds until expiration (default = 0
|
||||||
|
* meaning the item will not get an expiration date)
|
||||||
|
*
|
||||||
|
* @return null
|
||||||
|
*/
|
||||||
|
public function __construct($expire = 0)
|
||||||
|
{
|
||||||
|
$this->_exp = $expire;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write data to cache.
|
||||||
|
*
|
||||||
|
* @param string $id cache id
|
||||||
|
*
|
||||||
|
* @param string $data
|
||||||
|
*
|
||||||
|
* @return bool success
|
||||||
|
*/
|
||||||
|
public function store($id, $data)
|
||||||
|
{
|
||||||
|
return apc_store($id, "{$_SERVER['REQUEST_TIME']}|{$data}", $this->_exp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the size of a cache entry
|
||||||
|
*
|
||||||
|
* @param string $id cache id
|
||||||
|
*
|
||||||
|
* @return int size in bytes
|
||||||
|
*/
|
||||||
|
public function getSize($id)
|
||||||
|
{
|
||||||
|
if (! $this->_fetch($id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2))
|
||||||
|
? mb_strlen($this->_data, '8bit')
|
||||||
|
: strlen($this->_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does a valid cache entry exist?
|
||||||
|
*
|
||||||
|
* @param string $id cache id
|
||||||
|
*
|
||||||
|
* @param int $srcMtime mtime of the original source file(s)
|
||||||
|
*
|
||||||
|
* @return bool exists
|
||||||
|
*/
|
||||||
|
public function isValid($id, $srcMtime)
|
||||||
|
{
|
||||||
|
return ($this->_fetch($id) && ($this->_lm >= $srcMtime));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send the cached content to output
|
||||||
|
*
|
||||||
|
* @param string $id cache id
|
||||||
|
*/
|
||||||
|
public function display($id)
|
||||||
|
{
|
||||||
|
echo $this->_fetch($id)
|
||||||
|
? $this->_data
|
||||||
|
: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the cached content
|
||||||
|
*
|
||||||
|
* @param string $id cache id
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function fetch($id)
|
||||||
|
{
|
||||||
|
return $this->_fetch($id)
|
||||||
|
? $this->_data
|
||||||
|
: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
private $_exp = null;
|
||||||
|
|
||||||
|
// cache of most recently fetched id
|
||||||
|
private $_lm = null;
|
||||||
|
private $_data = null;
|
||||||
|
private $_id = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch data and timestamp from apc, store in instance
|
||||||
|
*
|
||||||
|
* @param string $id
|
||||||
|
*
|
||||||
|
* @return bool success
|
||||||
|
*/
|
||||||
|
private function _fetch($id)
|
||||||
|
{
|
||||||
|
if ($this->_id === $id) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
$ret = apc_fetch($id);
|
||||||
|
if (false === $ret) {
|
||||||
|
$this->_id = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
list($this->_lm, $this->_data) = explode('|', $ret, 2);
|
||||||
|
$this->_id = $id;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
195
inc/contrib/minify/Minify/Cache/File.php
Executable file
195
inc/contrib/minify/Minify/Cache/File.php
Executable file
@ -0,0 +1,195 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Class Minify_Cache_File
|
||||||
|
* @package Minify
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Minify_Cache_File {
|
||||||
|
|
||||||
|
public function __construct($path = '', $fileLocking = false)
|
||||||
|
{
|
||||||
|
if (! $path) {
|
||||||
|
$path = self::tmp();
|
||||||
|
}
|
||||||
|
$this->_locking = $fileLocking;
|
||||||
|
$this->_path = $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write data to cache.
|
||||||
|
*
|
||||||
|
* @param string $id cache id (e.g. a filename)
|
||||||
|
*
|
||||||
|
* @param string $data
|
||||||
|
*
|
||||||
|
* @return bool success
|
||||||
|
*/
|
||||||
|
public function store($id, $data)
|
||||||
|
{
|
||||||
|
$flag = $this->_locking
|
||||||
|
? LOCK_EX
|
||||||
|
: null;
|
||||||
|
$file = $this->_path . '/' . $id;
|
||||||
|
if (! @file_put_contents($file, $data, $flag)) {
|
||||||
|
$this->_log("Minify_Cache_File: Write failed to '$file'");
|
||||||
|
}
|
||||||
|
// write control
|
||||||
|
if ($data !== $this->fetch($id)) {
|
||||||
|
@unlink($file);
|
||||||
|
$this->_log("Minify_Cache_File: Post-write read failed for '$file'");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the size of a cache entry
|
||||||
|
*
|
||||||
|
* @param string $id cache id (e.g. a filename)
|
||||||
|
*
|
||||||
|
* @return int size in bytes
|
||||||
|
*/
|
||||||
|
public function getSize($id)
|
||||||
|
{
|
||||||
|
return filesize($this->_path . '/' . $id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does a valid cache entry exist?
|
||||||
|
*
|
||||||
|
* @param string $id cache id (e.g. a filename)
|
||||||
|
*
|
||||||
|
* @param int $srcMtime mtime of the original source file(s)
|
||||||
|
*
|
||||||
|
* @return bool exists
|
||||||
|
*/
|
||||||
|
public function isValid($id, $srcMtime)
|
||||||
|
{
|
||||||
|
$file = $this->_path . '/' . $id;
|
||||||
|
return (is_file($file) && (filemtime($file) >= $srcMtime));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send the cached content to output
|
||||||
|
*
|
||||||
|
* @param string $id cache id (e.g. a filename)
|
||||||
|
*/
|
||||||
|
public function display($id)
|
||||||
|
{
|
||||||
|
if ($this->_locking) {
|
||||||
|
$fp = fopen($this->_path . '/' . $id, 'rb');
|
||||||
|
flock($fp, LOCK_SH);
|
||||||
|
fpassthru($fp);
|
||||||
|
flock($fp, LOCK_UN);
|
||||||
|
fclose($fp);
|
||||||
|
} else {
|
||||||
|
readfile($this->_path . '/' . $id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the cached content
|
||||||
|
*
|
||||||
|
* @param string $id cache id (e.g. a filename)
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function fetch($id)
|
||||||
|
{
|
||||||
|
if ($this->_locking) {
|
||||||
|
$fp = fopen($this->_path . '/' . $id, 'rb');
|
||||||
|
flock($fp, LOCK_SH);
|
||||||
|
$ret = stream_get_contents($fp);
|
||||||
|
flock($fp, LOCK_UN);
|
||||||
|
fclose($fp);
|
||||||
|
return $ret;
|
||||||
|
} else {
|
||||||
|
return file_get_contents($this->_path . '/' . $id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the cache path used
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getPath()
|
||||||
|
{
|
||||||
|
return $this->_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a usable temp directory
|
||||||
|
*
|
||||||
|
* Adapted from Solar/Dir.php
|
||||||
|
* @author Paul M. Jones <pmjones@solarphp.com>
|
||||||
|
* @license http://opensource.org/licenses/bsd-license.php BSD
|
||||||
|
* @link http://solarphp.com/trac/core/browser/trunk/Solar/Dir.php
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function tmp()
|
||||||
|
{
|
||||||
|
static $tmp = null;
|
||||||
|
if (! $tmp) {
|
||||||
|
$tmp = function_exists('sys_get_temp_dir')
|
||||||
|
? sys_get_temp_dir()
|
||||||
|
: self::_tmp();
|
||||||
|
$tmp = rtrim($tmp, DIRECTORY_SEPARATOR);
|
||||||
|
}
|
||||||
|
return $tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the OS-specific directory for temporary files
|
||||||
|
*
|
||||||
|
* @author Paul M. Jones <pmjones@solarphp.com>
|
||||||
|
* @license http://opensource.org/licenses/bsd-license.php BSD
|
||||||
|
* @link http://solarphp.com/trac/core/browser/trunk/Solar/Dir.php
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected static function _tmp()
|
||||||
|
{
|
||||||
|
// non-Windows system?
|
||||||
|
if (strtolower(substr(PHP_OS, 0, 3)) != 'win') {
|
||||||
|
$tmp = empty($_ENV['TMPDIR']) ? getenv('TMPDIR') : $_ENV['TMPDIR'];
|
||||||
|
if ($tmp) {
|
||||||
|
return $tmp;
|
||||||
|
} else {
|
||||||
|
return '/tmp';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Windows 'TEMP'
|
||||||
|
$tmp = empty($_ENV['TEMP']) ? getenv('TEMP') : $_ENV['TEMP'];
|
||||||
|
if ($tmp) {
|
||||||
|
return $tmp;
|
||||||
|
}
|
||||||
|
// Windows 'TMP'
|
||||||
|
$tmp = empty($_ENV['TMP']) ? getenv('TMP') : $_ENV['TMP'];
|
||||||
|
if ($tmp) {
|
||||||
|
return $tmp;
|
||||||
|
}
|
||||||
|
// Windows 'windir'
|
||||||
|
$tmp = empty($_ENV['windir']) ? getenv('windir') : $_ENV['windir'];
|
||||||
|
if ($tmp) {
|
||||||
|
return $tmp;
|
||||||
|
}
|
||||||
|
// final fallback for Windows
|
||||||
|
return getenv('SystemRoot') . '\\temp';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send message to the Minify logger
|
||||||
|
* @param string $msg
|
||||||
|
* @return null
|
||||||
|
*/
|
||||||
|
protected function _log($msg)
|
||||||
|
{
|
||||||
|
require_once 'Minify/Logger.php';
|
||||||
|
Minify_Logger::log($msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private $_path = null;
|
||||||
|
private $_locking = null;
|
||||||
|
}
|
140
inc/contrib/minify/Minify/Cache/Memcache.php
Executable file
140
inc/contrib/minify/Minify/Cache/Memcache.php
Executable file
@ -0,0 +1,140 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Class Minify_Cache_Memcache
|
||||||
|
* @package Minify
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Memcache-based cache class for Minify
|
||||||
|
*
|
||||||
|
* <code>
|
||||||
|
* // fall back to disk caching if memcache can't connect
|
||||||
|
* $memcache = new Memcache;
|
||||||
|
* if ($memcache->connect('localhost', 11211)) {
|
||||||
|
* Minify::setCache(new Minify_Cache_Memcache($memcache));
|
||||||
|
* } else {
|
||||||
|
* Minify::setCache();
|
||||||
|
* }
|
||||||
|
* </code>
|
||||||
|
**/
|
||||||
|
class Minify_Cache_Memcache {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a Minify_Cache_Memcache object, to be passed to
|
||||||
|
* Minify::setCache().
|
||||||
|
*
|
||||||
|
* @param Memcache $memcache already-connected instance
|
||||||
|
*
|
||||||
|
* @param int $expire seconds until expiration (default = 0
|
||||||
|
* meaning the item will not get an expiration date)
|
||||||
|
*
|
||||||
|
* @return null
|
||||||
|
*/
|
||||||
|
public function __construct($memcache, $expire = 0)
|
||||||
|
{
|
||||||
|
$this->_mc = $memcache;
|
||||||
|
$this->_exp = $expire;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write data to cache.
|
||||||
|
*
|
||||||
|
* @param string $id cache id
|
||||||
|
*
|
||||||
|
* @param string $data
|
||||||
|
*
|
||||||
|
* @return bool success
|
||||||
|
*/
|
||||||
|
public function store($id, $data)
|
||||||
|
{
|
||||||
|
return $this->_mc->set($id, "{$_SERVER['REQUEST_TIME']}|{$data}", 0, $this->_exp);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the size of a cache entry
|
||||||
|
*
|
||||||
|
* @param string $id cache id
|
||||||
|
*
|
||||||
|
* @return int size in bytes
|
||||||
|
*/
|
||||||
|
public function getSize($id)
|
||||||
|
{
|
||||||
|
if (! $this->_fetch($id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2))
|
||||||
|
? mb_strlen($this->_data, '8bit')
|
||||||
|
: strlen($this->_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does a valid cache entry exist?
|
||||||
|
*
|
||||||
|
* @param string $id cache id
|
||||||
|
*
|
||||||
|
* @param int $srcMtime mtime of the original source file(s)
|
||||||
|
*
|
||||||
|
* @return bool exists
|
||||||
|
*/
|
||||||
|
public function isValid($id, $srcMtime)
|
||||||
|
{
|
||||||
|
return ($this->_fetch($id) && ($this->_lm >= $srcMtime));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send the cached content to output
|
||||||
|
*
|
||||||
|
* @param string $id cache id
|
||||||
|
*/
|
||||||
|
public function display($id)
|
||||||
|
{
|
||||||
|
echo $this->_fetch($id)
|
||||||
|
? $this->_data
|
||||||
|
: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the cached content
|
||||||
|
*
|
||||||
|
* @param string $id cache id
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function fetch($id)
|
||||||
|
{
|
||||||
|
return $this->_fetch($id)
|
||||||
|
? $this->_data
|
||||||
|
: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
private $_mc = null;
|
||||||
|
private $_exp = null;
|
||||||
|
|
||||||
|
// cache of most recently fetched id
|
||||||
|
private $_lm = null;
|
||||||
|
private $_data = null;
|
||||||
|
private $_id = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch data and timestamp from memcache, store in instance
|
||||||
|
*
|
||||||
|
* @param string $id
|
||||||
|
*
|
||||||
|
* @return bool success
|
||||||
|
*/
|
||||||
|
private function _fetch($id)
|
||||||
|
{
|
||||||
|
if ($this->_id === $id) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
$ret = $this->_mc->get($id);
|
||||||
|
if (false === $ret) {
|
||||||
|
$this->_id = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
list($this->_lm, $this->_data) = explode('|', $ret, 2);
|
||||||
|
$this->_id = $id;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
142
inc/contrib/minify/Minify/Cache/ZendPlatform.php
Executable file
142
inc/contrib/minify/Minify/Cache/ZendPlatform.php
Executable file
@ -0,0 +1,142 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Class Minify_Cache_ZendPlatform
|
||||||
|
* @package Minify
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ZendPlatform-based cache class for Minify
|
||||||
|
*
|
||||||
|
* Based on Minify_Cache_APC, uses output_cache_get/put (currently deprecated)
|
||||||
|
*
|
||||||
|
* <code>
|
||||||
|
* Minify::setCache(new Minify_Cache_ZendPlatform());
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
|
* @package Minify
|
||||||
|
* @author Patrick van Dissel
|
||||||
|
*/
|
||||||
|
class Minify_Cache_ZendPlatform {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a Minify_Cache_ZendPlatform object, to be passed to
|
||||||
|
* Minify::setCache().
|
||||||
|
*
|
||||||
|
* @param int $expire seconds until expiration (default = 0
|
||||||
|
* meaning the item will not get an expiration date)
|
||||||
|
*
|
||||||
|
* @return null
|
||||||
|
*/
|
||||||
|
public function __construct($expire = 0)
|
||||||
|
{
|
||||||
|
$this->_exp = $expire;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write data to cache.
|
||||||
|
*
|
||||||
|
* @param string $id cache id
|
||||||
|
*
|
||||||
|
* @param string $data
|
||||||
|
*
|
||||||
|
* @return bool success
|
||||||
|
*/
|
||||||
|
public function store($id, $data)
|
||||||
|
{
|
||||||
|
return output_cache_put($id, "{$_SERVER['REQUEST_TIME']}|{$data}");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the size of a cache entry
|
||||||
|
*
|
||||||
|
* @param string $id cache id
|
||||||
|
*
|
||||||
|
* @return int size in bytes
|
||||||
|
*/
|
||||||
|
public function getSize($id)
|
||||||
|
{
|
||||||
|
return $this->_fetch($id)
|
||||||
|
? strlen($this->_data)
|
||||||
|
: false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does a valid cache entry exist?
|
||||||
|
*
|
||||||
|
* @param string $id cache id
|
||||||
|
*
|
||||||
|
* @param int $srcMtime mtime of the original source file(s)
|
||||||
|
*
|
||||||
|
* @return bool exists
|
||||||
|
*/
|
||||||
|
public function isValid($id, $srcMtime)
|
||||||
|
{
|
||||||
|
$ret = ($this->_fetch($id) && ($this->_lm >= $srcMtime));
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send the cached content to output
|
||||||
|
*
|
||||||
|
* @param string $id cache id
|
||||||
|
*/
|
||||||
|
public function display($id)
|
||||||
|
{
|
||||||
|
echo $this->_fetch($id)
|
||||||
|
? $this->_data
|
||||||
|
: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the cached content
|
||||||
|
*
|
||||||
|
* @param string $id cache id
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function fetch($id)
|
||||||
|
{
|
||||||
|
return $this->_fetch($id)
|
||||||
|
? $this->_data
|
||||||
|
: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private $_exp = null;
|
||||||
|
|
||||||
|
|
||||||
|
// cache of most recently fetched id
|
||||||
|
private $_lm = null;
|
||||||
|
private $_data = null;
|
||||||
|
private $_id = null;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch data and timestamp from ZendPlatform, store in instance
|
||||||
|
*
|
||||||
|
* @param string $id
|
||||||
|
*
|
||||||
|
* @return bool success
|
||||||
|
*/
|
||||||
|
private function _fetch($id)
|
||||||
|
{
|
||||||
|
if ($this->_id === $id) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
$ret = output_cache_get($id, $this->_exp);
|
||||||
|
if (false === $ret) {
|
||||||
|
$this->_id = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
list($this->_lm, $this->_data) = explode('|', $ret, 2);
|
||||||
|
$this->_id = $id;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
89
inc/contrib/minify/Minify/CommentPreserver.php
Executable file
89
inc/contrib/minify/Minify/CommentPreserver.php
Executable file
@ -0,0 +1,89 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Class Minify_CommentPreserver
|
||||||
|
* @package Minify
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a string in pieces preserving C-style comments that begin with "/*!"
|
||||||
|
*
|
||||||
|
* @package Minify
|
||||||
|
* @author Stephen Clay <steve@mrclay.org>
|
||||||
|
*/
|
||||||
|
class Minify_CommentPreserver {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String to be prepended to each preserved comment
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public static $prepend = "\n";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String to be appended to each preserved comment
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public static $append = "\n";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a string outside of C-style comments that begin with "/*!"
|
||||||
|
*
|
||||||
|
* On each non-empty string outside these comments, the given processor
|
||||||
|
* function will be called. The comments will be surrounded by
|
||||||
|
* Minify_CommentPreserver::$preprend and Minify_CommentPreserver::$append.
|
||||||
|
*
|
||||||
|
* @param string $content
|
||||||
|
* @param callback $processor function
|
||||||
|
* @param array $args array of extra arguments to pass to the processor
|
||||||
|
* function (default = array())
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function process($content, $processor, $args = array())
|
||||||
|
{
|
||||||
|
$ret = '';
|
||||||
|
while (true) {
|
||||||
|
list($beforeComment, $comment, $afterComment) = self::_nextComment($content);
|
||||||
|
if ('' !== $beforeComment) {
|
||||||
|
$callArgs = $args;
|
||||||
|
array_unshift($callArgs, $beforeComment);
|
||||||
|
$ret .= call_user_func_array($processor, $callArgs);
|
||||||
|
}
|
||||||
|
if (false === $comment) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$ret .= $comment;
|
||||||
|
$content = $afterComment;
|
||||||
|
}
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract comments that YUI Compressor preserves.
|
||||||
|
*
|
||||||
|
* @param string $in input
|
||||||
|
*
|
||||||
|
* @return array 3 elements are returned. If a YUI comment is found, the
|
||||||
|
* 2nd element is the comment and the 1st and 3rd are the surrounding
|
||||||
|
* strings. If no comment is found, the entire string is returned as the
|
||||||
|
* 1st element and the other two are false.
|
||||||
|
*/
|
||||||
|
private static function _nextComment($in)
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
false === ($start = strpos($in, '/*!'))
|
||||||
|
|| false === ($end = strpos($in, '*/', $start + 3))
|
||||||
|
) {
|
||||||
|
return array($in, false, false);
|
||||||
|
}
|
||||||
|
$ret = array(
|
||||||
|
substr($in, 0, $start)
|
||||||
|
,self::$prepend . '/*!' . substr($in, $start + 3, $end - $start - 1) . self::$append
|
||||||
|
);
|
||||||
|
$endChars = (strlen($in) - $end - 2);
|
||||||
|
$ret[] = (0 === $endChars)
|
||||||
|
? ''
|
||||||
|
: substr($in, -$endChars);
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
}
|
250
inc/contrib/minify/Minify/Controller/Base.php
Executable file
250
inc/contrib/minify/Minify/Controller/Base.php
Executable file
@ -0,0 +1,250 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Class Minify_Controller_Base
|
||||||
|
* @package Minify
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for Minify controller
|
||||||
|
*
|
||||||
|
* The controller class validates a request and uses it to create sources
|
||||||
|
* for minification and set options like contentType. It's also responsible
|
||||||
|
* for loading minifier code upon request.
|
||||||
|
*
|
||||||
|
* @package Minify
|
||||||
|
* @author Stephen Clay <steve@mrclay.org>
|
||||||
|
*/
|
||||||
|
abstract class Minify_Controller_Base {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup controller sources and set an needed options for Minify::source
|
||||||
|
*
|
||||||
|
* You must override this method in your subclass controller to set
|
||||||
|
* $this->sources. If the request is NOT valid, make sure $this->sources
|
||||||
|
* is left an empty array. Then strip any controller-specific options from
|
||||||
|
* $options and return it. To serve files, $this->sources must be an array of
|
||||||
|
* Minify_Source objects.
|
||||||
|
*
|
||||||
|
* @param array $options controller and Minify options
|
||||||
|
*
|
||||||
|
* @return array $options Minify::serve options
|
||||||
|
*/
|
||||||
|
abstract public function setupSources($options);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get default Minify options for this controller.
|
||||||
|
*
|
||||||
|
* Override in subclass to change defaults
|
||||||
|
*
|
||||||
|
* @return array options for Minify
|
||||||
|
*/
|
||||||
|
public function getDefaultMinifyOptions() {
|
||||||
|
return array(
|
||||||
|
'isPublic' => true
|
||||||
|
,'encodeOutput' => function_exists('gzdeflate')
|
||||||
|
,'encodeMethod' => null // determine later
|
||||||
|
,'encodeLevel' => 9
|
||||||
|
,'minifierOptions' => array() // no minifier options
|
||||||
|
,'contentTypeCharset' => 'utf-8'
|
||||||
|
,'maxAge' => 1800 // 30 minutes
|
||||||
|
,'rewriteCssUris' => true
|
||||||
|
,'bubbleCssImports' => false
|
||||||
|
,'quiet' => false // serve() will send headers and output
|
||||||
|
,'debug' => false
|
||||||
|
|
||||||
|
// if you override these, the response codes MUST be directly after
|
||||||
|
// the first space.
|
||||||
|
,'badRequestHeader' => 'HTTP/1.0 400 Bad Request'
|
||||||
|
,'errorHeader' => 'HTTP/1.0 500 Internal Server Error'
|
||||||
|
|
||||||
|
// callback function to see/modify content of all sources
|
||||||
|
,'postprocessor' => null
|
||||||
|
// file to require to load preprocessor
|
||||||
|
,'postprocessorRequire' => null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get default minifiers for this controller.
|
||||||
|
*
|
||||||
|
* Override in subclass to change defaults
|
||||||
|
*
|
||||||
|
* @return array minifier callbacks for common types
|
||||||
|
*/
|
||||||
|
public function getDefaultMinifers() {
|
||||||
|
$ret[Minify::TYPE_JS] = array('JSMin', 'minify');
|
||||||
|
$ret[Minify::TYPE_CSS] = array('Minify_CSS', 'minify');
|
||||||
|
$ret[Minify::TYPE_HTML] = array('Minify_HTML', 'minify');
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load any code necessary to execute the given minifier callback.
|
||||||
|
*
|
||||||
|
* The controller is responsible for loading minification code on demand
|
||||||
|
* via this method. This built-in function will only load classes for
|
||||||
|
* static method callbacks where the class isn't already defined. It uses
|
||||||
|
* the PEAR convention, so, given array('Jimmy_Minifier', 'minCss'), this
|
||||||
|
* function will include 'Jimmy/Minifier.php'.
|
||||||
|
*
|
||||||
|
* If you need code loaded on demand and this doesn't suit you, you'll need
|
||||||
|
* to override this function in your subclass.
|
||||||
|
* @see Minify_Controller_Page::loadMinifier()
|
||||||
|
*
|
||||||
|
* @param callback $minifierCallback callback of minifier function
|
||||||
|
*
|
||||||
|
* @return null
|
||||||
|
*/
|
||||||
|
public function loadMinifier($minifierCallback)
|
||||||
|
{
|
||||||
|
if (is_array($minifierCallback)
|
||||||
|
&& is_string($minifierCallback[0])
|
||||||
|
&& !class_exists($minifierCallback[0], false)) {
|
||||||
|
|
||||||
|
require str_replace('_', '/', $minifierCallback[0]) . '.php';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is a user-given file within an allowable directory, existing,
|
||||||
|
* and having an extension js/css/html/txt ?
|
||||||
|
*
|
||||||
|
* This is a convenience function for controllers that have to accept
|
||||||
|
* user-given paths
|
||||||
|
*
|
||||||
|
* @param string $file full file path (already processed by realpath())
|
||||||
|
*
|
||||||
|
* @param array $safeDirs directories where files are safe to serve. Files can also
|
||||||
|
* be in subdirectories of these directories.
|
||||||
|
*
|
||||||
|
* @return bool file is safe
|
||||||
|
*
|
||||||
|
* @deprecated use checkAllowDirs, checkNotHidden instead
|
||||||
|
*/
|
||||||
|
public static function _fileIsSafe($file, $safeDirs)
|
||||||
|
{
|
||||||
|
$pathOk = false;
|
||||||
|
foreach ((array)$safeDirs as $safeDir) {
|
||||||
|
if (strpos($file, $safeDir) === 0) {
|
||||||
|
$pathOk = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$base = basename($file);
|
||||||
|
if (! $pathOk || ! is_file($file) || $base[0] === '.') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
list($revExt) = explode('.', strrev($base));
|
||||||
|
return in_array(strrev($revExt), array('js', 'css', 'html', 'txt'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $file
|
||||||
|
* @param array $allowDirs
|
||||||
|
* @param string $uri
|
||||||
|
* @return bool
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public static function checkAllowDirs($file, $allowDirs, $uri)
|
||||||
|
{
|
||||||
|
foreach ((array)$allowDirs as $allowDir) {
|
||||||
|
if (strpos($file, $allowDir) === 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Exception("File '$file' is outside \$allowDirs. If the path is"
|
||||||
|
. " resolved via an alias/symlink, look into the \$min_symlinks option."
|
||||||
|
. " E.g. \$min_symlinks['/" . dirname($uri) . "'] = '" . dirname($file) . "';");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $file
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public static function checkNotHidden($file)
|
||||||
|
{
|
||||||
|
$b = basename($file);
|
||||||
|
if (0 === strpos($b, '.')) {
|
||||||
|
throw new Exception("Filename '$b' starts with period (may be hidden)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* instances of Minify_Source, which provide content and any individual minification needs.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*
|
||||||
|
* @see Minify_Source
|
||||||
|
*/
|
||||||
|
public $sources = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Short name to place inside cache id
|
||||||
|
*
|
||||||
|
* The setupSources() method may choose to set this, making it easier to
|
||||||
|
* recognize a particular set of sources/settings in the cache folder. It
|
||||||
|
* will be filtered and truncated to make the final cache id <= 250 bytes.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $selectionId = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mix in default controller options with user-given options
|
||||||
|
*
|
||||||
|
* @param array $options user options
|
||||||
|
*
|
||||||
|
* @return array mixed options
|
||||||
|
*/
|
||||||
|
public final function mixInDefaultOptions($options)
|
||||||
|
{
|
||||||
|
$ret = array_merge(
|
||||||
|
$this->getDefaultMinifyOptions(), $options
|
||||||
|
);
|
||||||
|
if (! isset($options['minifiers'])) {
|
||||||
|
$options['minifiers'] = array();
|
||||||
|
}
|
||||||
|
$ret['minifiers'] = array_merge(
|
||||||
|
$this->getDefaultMinifers(), $options['minifiers']
|
||||||
|
);
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analyze sources (if there are any) and set $options 'contentType'
|
||||||
|
* and 'lastModifiedTime' if they already aren't.
|
||||||
|
*
|
||||||
|
* @param array $options options for Minify
|
||||||
|
*
|
||||||
|
* @return array options for Minify
|
||||||
|
*/
|
||||||
|
public final function analyzeSources($options = array())
|
||||||
|
{
|
||||||
|
if ($this->sources) {
|
||||||
|
if (! isset($options['contentType'])) {
|
||||||
|
$options['contentType'] = Minify_Source::getContentType($this->sources);
|
||||||
|
}
|
||||||
|
// last modified is needed for caching, even if setExpires is set
|
||||||
|
if (! isset($options['lastModifiedTime'])) {
|
||||||
|
$max = 0;
|
||||||
|
foreach ($this->sources as $source) {
|
||||||
|
$max = max($source->lastModified, $max);
|
||||||
|
}
|
||||||
|
$options['lastModifiedTime'] = $max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send message to the Minify logger
|
||||||
|
*
|
||||||
|
* @param string $msg
|
||||||
|
*
|
||||||
|
* @return null
|
||||||
|
*/
|
||||||
|
public function log($msg) {
|
||||||
|
require_once 'Minify/Logger.php';
|
||||||
|
Minify_Logger::log($msg);
|
||||||
|
}
|
||||||
|
}
|
78
inc/contrib/minify/Minify/Controller/Files.php
Executable file
78
inc/contrib/minify/Minify/Controller/Files.php
Executable file
@ -0,0 +1,78 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Class Minify_Controller_Files
|
||||||
|
* @package Minify
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once 'Minify/Controller/Base.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller class for minifying a set of files
|
||||||
|
*
|
||||||
|
* E.g. the following would serve the minified Javascript for a site
|
||||||
|
* <code>
|
||||||
|
* Minify::serve('Files', array(
|
||||||
|
* 'files' => array(
|
||||||
|
* '//js/jquery.js'
|
||||||
|
* ,'//js/plugins.js'
|
||||||
|
* ,'/home/username/file.js'
|
||||||
|
* )
|
||||||
|
* ));
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
|
* As a shortcut, the controller will replace "//" at the beginning
|
||||||
|
* of a filename with $_SERVER['DOCUMENT_ROOT'] . '/'.
|
||||||
|
*
|
||||||
|
* @package Minify
|
||||||
|
* @author Stephen Clay <steve@mrclay.org>
|
||||||
|
*/
|
||||||
|
class Minify_Controller_Files extends Minify_Controller_Base {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up file sources
|
||||||
|
*
|
||||||
|
* @param array $options controller and Minify options
|
||||||
|
* @return array Minify options
|
||||||
|
*
|
||||||
|
* Controller options:
|
||||||
|
*
|
||||||
|
* 'files': (required) array of complete file paths, or a single path
|
||||||
|
*/
|
||||||
|
public function setupSources($options) {
|
||||||
|
// strip controller options
|
||||||
|
|
||||||
|
$files = $options['files'];
|
||||||
|
// if $files is a single object, casting will break it
|
||||||
|
if (is_object($files)) {
|
||||||
|
$files = array($files);
|
||||||
|
} elseif (! is_array($files)) {
|
||||||
|
$files = (array)$files;
|
||||||
|
}
|
||||||
|
unset($options['files']);
|
||||||
|
|
||||||
|
$sources = array();
|
||||||
|
foreach ($files as $file) {
|
||||||
|
if ($file instanceof Minify_Source) {
|
||||||
|
$sources[] = $file;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (0 === strpos($file, '//')) {
|
||||||
|
$file = $_SERVER['DOCUMENT_ROOT'] . substr($file, 1);
|
||||||
|
}
|
||||||
|
$realPath = realpath($file);
|
||||||
|
if (is_file($realPath)) {
|
||||||
|
$sources[] = new Minify_Source(array(
|
||||||
|
'filepath' => $realPath
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
$this->log("The path \"{$file}\" could not be found (or was not a file)");
|
||||||
|
return $options;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($sources) {
|
||||||
|
$this->sources = $sources;
|
||||||
|
}
|
||||||
|
return $options;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
93
inc/contrib/minify/Minify/Controller/Groups.php
Executable file
93
inc/contrib/minify/Minify/Controller/Groups.php
Executable file
@ -0,0 +1,93 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Class Minify_Controller_Groups
|
||||||
|
* @package Minify
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once 'Minify/Controller/Base.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller class for serving predetermined groups of minimized sets, selected
|
||||||
|
* by PATH_INFO
|
||||||
|
*
|
||||||
|
* <code>
|
||||||
|
* Minify::serve('Groups', array(
|
||||||
|
* 'groups' => array(
|
||||||
|
* 'css' => array('//css/type.css', '//css/layout.css')
|
||||||
|
* ,'js' => array('//js/jquery.js', '//js/site.js')
|
||||||
|
* )
|
||||||
|
* ));
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
|
* If the above code were placed in /serve.php, it would enable the URLs
|
||||||
|
* /serve.php/js and /serve.php/css
|
||||||
|
*
|
||||||
|
* As a shortcut, the controller will replace "//" at the beginning
|
||||||
|
* of a filename with $_SERVER['DOCUMENT_ROOT'] . '/'.
|
||||||
|
*
|
||||||
|
* @package Minify
|
||||||
|
* @author Stephen Clay <steve@mrclay.org>
|
||||||
|
*/
|
||||||
|
class Minify_Controller_Groups extends Minify_Controller_Base {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up groups of files as sources
|
||||||
|
*
|
||||||
|
* @param array $options controller and Minify options
|
||||||
|
*
|
||||||
|
* 'groups': (required) array mapping PATH_INFO strings to arrays
|
||||||
|
* of complete file paths. @see Minify_Controller_Groups
|
||||||
|
*
|
||||||
|
* @return array Minify options
|
||||||
|
*/
|
||||||
|
public function setupSources($options) {
|
||||||
|
// strip controller options
|
||||||
|
$groups = $options['groups'];
|
||||||
|
unset($options['groups']);
|
||||||
|
|
||||||
|
// mod_fcgid places PATH_INFO in ORIG_PATH_INFO
|
||||||
|
$pi = isset($_SERVER['ORIG_PATH_INFO'])
|
||||||
|
? substr($_SERVER['ORIG_PATH_INFO'], 1)
|
||||||
|
: (isset($_SERVER['PATH_INFO'])
|
||||||
|
? substr($_SERVER['PATH_INFO'], 1)
|
||||||
|
: false
|
||||||
|
);
|
||||||
|
if (false === $pi || ! isset($groups[$pi])) {
|
||||||
|
// no PATH_INFO or not a valid group
|
||||||
|
$this->log("Missing PATH_INFO or no group set for \"$pi\"");
|
||||||
|
return $options;
|
||||||
|
}
|
||||||
|
$sources = array();
|
||||||
|
|
||||||
|
$files = $groups[$pi];
|
||||||
|
// if $files is a single object, casting will break it
|
||||||
|
if (is_object($files)) {
|
||||||
|
$files = array($files);
|
||||||
|
} elseif (! is_array($files)) {
|
||||||
|
$files = (array)$files;
|
||||||
|
}
|
||||||
|
foreach ($files as $file) {
|
||||||
|
if ($file instanceof Minify_Source) {
|
||||||
|
$sources[] = $file;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (0 === strpos($file, '//')) {
|
||||||
|
$file = $_SERVER['DOCUMENT_ROOT'] . substr($file, 1);
|
||||||
|
}
|
||||||
|
$realPath = realpath($file);
|
||||||
|
if (is_file($realPath)) {
|
||||||
|
$sources[] = new Minify_Source(array(
|
||||||
|
'filepath' => $realPath
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
$this->log("The path \"{$file}\" could not be found (or was not a file)");
|
||||||
|
return $options;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($sources) {
|
||||||
|
$this->sources = $sources;
|
||||||
|
}
|
||||||
|
return $options;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
231
inc/contrib/minify/Minify/Controller/MinApp.php
Executable file
231
inc/contrib/minify/Minify/Controller/MinApp.php
Executable file
@ -0,0 +1,231 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Class Minify_Controller_MinApp
|
||||||
|
* @package Minify
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once 'Minify/Controller/Base.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller class for requests to /min/index.php
|
||||||
|
*
|
||||||
|
* @package Minify
|
||||||
|
* @author Stephen Clay <steve@mrclay.org>
|
||||||
|
*/
|
||||||
|
class Minify_Controller_MinApp extends Minify_Controller_Base {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up groups of files as sources
|
||||||
|
*
|
||||||
|
* @param array $options controller and Minify options
|
||||||
|
*
|
||||||
|
* @return array Minify options
|
||||||
|
*/
|
||||||
|
public function setupSources($options) {
|
||||||
|
// filter controller options
|
||||||
|
$cOptions = array_merge(
|
||||||
|
array(
|
||||||
|
'allowDirs' => '//'
|
||||||
|
,'groupsOnly' => false
|
||||||
|
,'groups' => array()
|
||||||
|
,'noMinPattern' => '@[-\\.]min\\.(?:js|css)$@i' // matched against basename
|
||||||
|
)
|
||||||
|
,(isset($options['minApp']) ? $options['minApp'] : array())
|
||||||
|
);
|
||||||
|
unset($options['minApp']);
|
||||||
|
$sources = array();
|
||||||
|
$this->selectionId = '';
|
||||||
|
$firstMissingResource = null;
|
||||||
|
|
||||||
|
if (isset($_GET['g'])) {
|
||||||
|
// add group(s)
|
||||||
|
$this->selectionId .= 'g=' . $_GET['g'];
|
||||||
|
$keys = explode(',', $_GET['g']);
|
||||||
|
if ($keys != array_unique($keys)) {
|
||||||
|
$this->log("Duplicate group key found.");
|
||||||
|
return $options;
|
||||||
|
}
|
||||||
|
$keys = explode(',', $_GET['g']);
|
||||||
|
foreach ($keys as $key) {
|
||||||
|
if (! isset($cOptions['groups'][$key])) {
|
||||||
|
$this->log("A group configuration for \"{$key}\" was not found");
|
||||||
|
return $options;
|
||||||
|
}
|
||||||
|
$files = $cOptions['groups'][$key];
|
||||||
|
// if $files is a single object, casting will break it
|
||||||
|
if (is_object($files)) {
|
||||||
|
$files = array($files);
|
||||||
|
} elseif (! is_array($files)) {
|
||||||
|
$files = (array)$files;
|
||||||
|
}
|
||||||
|
foreach ($files as $file) {
|
||||||
|
if ($file instanceof Minify_Source) {
|
||||||
|
$sources[] = $file;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (0 === strpos($file, '//')) {
|
||||||
|
$file = $_SERVER['DOCUMENT_ROOT'] . substr($file, 1);
|
||||||
|
}
|
||||||
|
$realpath = realpath($file);
|
||||||
|
if ($realpath && is_file($realpath)) {
|
||||||
|
$sources[] = $this->_getFileSource($realpath, $cOptions);
|
||||||
|
} else {
|
||||||
|
$this->log("The path \"{$file}\" (realpath \"{$realpath}\") could not be found (or was not a file)");
|
||||||
|
if (null === $firstMissingResource) {
|
||||||
|
$firstMissingResource = basename($file);
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
$secondMissingResource = basename($file);
|
||||||
|
$this->log("More than one file was missing: '$firstMissingResource', '$secondMissingResource'");
|
||||||
|
return $options;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($sources) {
|
||||||
|
try {
|
||||||
|
$this->checkType($sources[0]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->log($e->getMessage());
|
||||||
|
return $options;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (! $cOptions['groupsOnly'] && isset($_GET['f'])) {
|
||||||
|
// try user files
|
||||||
|
// The following restrictions are to limit the URLs that minify will
|
||||||
|
// respond to.
|
||||||
|
if (// verify at least one file, files are single comma separated,
|
||||||
|
// and are all same extension
|
||||||
|
! preg_match('/^[^,]+\\.(css|js)(?:,[^,]+\\.\\1)*$/', $_GET['f'], $m)
|
||||||
|
// no "//"
|
||||||
|
|| strpos($_GET['f'], '//') !== false
|
||||||
|
// no "\"
|
||||||
|
|| strpos($_GET['f'], '\\') !== false
|
||||||
|
) {
|
||||||
|
$this->log("GET param 'f' was invalid");
|
||||||
|
return $options;
|
||||||
|
}
|
||||||
|
$ext = ".{$m[1]}";
|
||||||
|
try {
|
||||||
|
$this->checkType($m[1]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->log($e->getMessage());
|
||||||
|
return $options;
|
||||||
|
}
|
||||||
|
$files = explode(',', $_GET['f']);
|
||||||
|
if ($files != array_unique($files)) {
|
||||||
|
$this->log("Duplicate files were specified");
|
||||||
|
return $options;
|
||||||
|
}
|
||||||
|
if (isset($_GET['b'])) {
|
||||||
|
// check for validity
|
||||||
|
if (preg_match('@^[^/]+(?:/[^/]+)*$@', $_GET['b'])
|
||||||
|
&& false === strpos($_GET['b'], '..')
|
||||||
|
&& $_GET['b'] !== '.') {
|
||||||
|
// valid base
|
||||||
|
$base = "/{$_GET['b']}/";
|
||||||
|
} else {
|
||||||
|
$this->log("GET param 'b' was invalid");
|
||||||
|
return $options;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$base = '/';
|
||||||
|
}
|
||||||
|
$allowDirs = array();
|
||||||
|
foreach ((array)$cOptions['allowDirs'] as $allowDir) {
|
||||||
|
$allowDirs[] = realpath(str_replace('//', $_SERVER['DOCUMENT_ROOT'] . '/', $allowDir));
|
||||||
|
}
|
||||||
|
$basenames = array(); // just for cache id
|
||||||
|
foreach ($files as $file) {
|
||||||
|
$uri = $base . $file;
|
||||||
|
$path = $_SERVER['DOCUMENT_ROOT'] . $uri;
|
||||||
|
$realpath = realpath($path);
|
||||||
|
if (false === $realpath || ! is_file($realpath)) {
|
||||||
|
$this->log("The path \"{$path}\" (realpath \"{$realpath}\") could not be found (or was not a file)");
|
||||||
|
if (null === $firstMissingResource) {
|
||||||
|
$firstMissingResource = $uri;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
$secondMissingResource = $uri;
|
||||||
|
$this->log("More than one file was missing: '$firstMissingResource', '$secondMissingResource`'");
|
||||||
|
return $options;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
parent::checkNotHidden($realpath);
|
||||||
|
parent::checkAllowDirs($realpath, $allowDirs, $uri);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->log($e->getMessage());
|
||||||
|
return $options;
|
||||||
|
}
|
||||||
|
$sources[] = $this->_getFileSource($realpath, $cOptions);
|
||||||
|
$basenames[] = basename($realpath, $ext);
|
||||||
|
}
|
||||||
|
if ($this->selectionId) {
|
||||||
|
$this->selectionId .= '_f=';
|
||||||
|
}
|
||||||
|
$this->selectionId .= implode(',', $basenames) . $ext;
|
||||||
|
}
|
||||||
|
if ($sources) {
|
||||||
|
if (null !== $firstMissingResource) {
|
||||||
|
array_unshift($sources, new Minify_Source(array(
|
||||||
|
'id' => 'missingFile'
|
||||||
|
// should not cause cache invalidation
|
||||||
|
,'lastModified' => 0
|
||||||
|
// due to caching, filename is unreliable.
|
||||||
|
,'content' => "/* Minify: at least one missing file. See " . Minify::URL_DEBUG . " */\n"
|
||||||
|
,'minifier' => ''
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
$this->sources = $sources;
|
||||||
|
} else {
|
||||||
|
$this->log("No sources to serve");
|
||||||
|
}
|
||||||
|
return $options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $file
|
||||||
|
*
|
||||||
|
* @param array $cOptions
|
||||||
|
*
|
||||||
|
* @return Minify_Source
|
||||||
|
*/
|
||||||
|
protected function _getFileSource($file, $cOptions)
|
||||||
|
{
|
||||||
|
$spec['filepath'] = $file;
|
||||||
|
if ($cOptions['noMinPattern']
|
||||||
|
&& preg_match($cOptions['noMinPattern'], basename($file))) {
|
||||||
|
$spec['minifier'] = '';
|
||||||
|
}
|
||||||
|
return new Minify_Source($spec);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $_type = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make sure that only source files of a single type are registered
|
||||||
|
*
|
||||||
|
* @param string $sourceOrExt
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function checkType($sourceOrExt)
|
||||||
|
{
|
||||||
|
if ($sourceOrExt === 'js') {
|
||||||
|
$type = Minify::TYPE_JS;
|
||||||
|
} elseif ($sourceOrExt === 'css') {
|
||||||
|
$type = Minify::TYPE_CSS;
|
||||||
|
} elseif ($sourceOrExt->contentType !== null) {
|
||||||
|
$type = $sourceOrExt->contentType;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($this->_type === null) {
|
||||||
|
$this->_type = $type;
|
||||||
|
} elseif ($this->_type !== $type) {
|
||||||
|
throw new Exception('Content-Type mismatch');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
87
inc/contrib/minify/Minify/Controller/Page.php
Executable file
87
inc/contrib/minify/Minify/Controller/Page.php
Executable file
@ -0,0 +1,87 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Class Minify_Controller_Page
|
||||||
|
* @package Minify
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once 'Minify/Controller/Base.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller class for serving a single HTML page
|
||||||
|
*
|
||||||
|
* @link http://code.google.com/p/minify/source/browse/trunk/web/examples/1/index.php#59
|
||||||
|
* @package Minify
|
||||||
|
* @author Stephen Clay <steve@mrclay.org>
|
||||||
|
*/
|
||||||
|
class Minify_Controller_Page extends Minify_Controller_Base {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up source of HTML content
|
||||||
|
*
|
||||||
|
* @param array $options controller and Minify options
|
||||||
|
* @return array Minify options
|
||||||
|
*
|
||||||
|
* Controller options:
|
||||||
|
*
|
||||||
|
* 'content': (required) HTML markup
|
||||||
|
*
|
||||||
|
* 'id': (required) id of page (string for use in server-side caching)
|
||||||
|
*
|
||||||
|
* 'lastModifiedTime': timestamp of when this content changed. This
|
||||||
|
* is recommended to allow both server and client-side caching.
|
||||||
|
*
|
||||||
|
* 'minifyAll': should all CSS and Javascript blocks be individually
|
||||||
|
* minified? (default false)
|
||||||
|
*
|
||||||
|
* @todo Add 'file' option to read HTML file.
|
||||||
|
*/
|
||||||
|
public function setupSources($options) {
|
||||||
|
if (isset($options['file'])) {
|
||||||
|
$sourceSpec = array(
|
||||||
|
'filepath' => $options['file']
|
||||||
|
);
|
||||||
|
$f = $options['file'];
|
||||||
|
} else {
|
||||||
|
// strip controller options
|
||||||
|
$sourceSpec = array(
|
||||||
|
'content' => $options['content']
|
||||||
|
,'id' => $options['id']
|
||||||
|
);
|
||||||
|
$f = $options['id'];
|
||||||
|
unset($options['content'], $options['id']);
|
||||||
|
}
|
||||||
|
// something like "builder,index.php" or "directory,file.html"
|
||||||
|
$this->selectionId = strtr(substr($f, 1 + strlen(dirname(dirname($f)))), '/\\', ',,');
|
||||||
|
|
||||||
|
if (isset($options['minifyAll'])) {
|
||||||
|
// this will be the 2nd argument passed to Minify_HTML::minify()
|
||||||
|
$sourceSpec['minifyOptions'] = array(
|
||||||
|
'cssMinifier' => array('Minify_CSS', 'minify')
|
||||||
|
,'jsMinifier' => array('JSMin', 'minify')
|
||||||
|
);
|
||||||
|
$this->_loadCssJsMinifiers = true;
|
||||||
|
unset($options['minifyAll']);
|
||||||
|
}
|
||||||
|
$this->sources[] = new Minify_Source($sourceSpec);
|
||||||
|
|
||||||
|
$options['contentType'] = Minify::TYPE_HTML;
|
||||||
|
return $options;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $_loadCssJsMinifiers = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see Minify_Controller_Base::loadMinifier()
|
||||||
|
*/
|
||||||
|
public function loadMinifier($minifierCallback)
|
||||||
|
{
|
||||||
|
if ($this->_loadCssJsMinifiers) {
|
||||||
|
// Minify will not call for these so we must manually load
|
||||||
|
// them when Minify/HTML.php is called for.
|
||||||
|
require_once 'Minify/CSS.php';
|
||||||
|
require_once 'JSMin.php';
|
||||||
|
}
|
||||||
|
parent::loadMinifier($minifierCallback); // load Minify/HTML.php
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
118
inc/contrib/minify/Minify/Controller/Version1.php
Executable file
118
inc/contrib/minify/Minify/Controller/Version1.php
Executable file
@ -0,0 +1,118 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Class Minify_Controller_Version1
|
||||||
|
* @package Minify
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once 'Minify/Controller/Base.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller class for emulating version 1 of minify.php (mostly a proof-of-concept)
|
||||||
|
*
|
||||||
|
* <code>
|
||||||
|
* Minify::serve('Version1');
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
|
* @package Minify
|
||||||
|
* @author Stephen Clay <steve@mrclay.org>
|
||||||
|
*/
|
||||||
|
class Minify_Controller_Version1 extends Minify_Controller_Base {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up groups of files as sources
|
||||||
|
*
|
||||||
|
* @param array $options controller and Minify options
|
||||||
|
* @return array Minify options
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function setupSources($options) {
|
||||||
|
self::_setupDefines();
|
||||||
|
if (MINIFY_USE_CACHE) {
|
||||||
|
$cacheDir = defined('MINIFY_CACHE_DIR')
|
||||||
|
? MINIFY_CACHE_DIR
|
||||||
|
: '';
|
||||||
|
Minify::setCache($cacheDir);
|
||||||
|
}
|
||||||
|
$options['badRequestHeader'] = 'HTTP/1.0 404 Not Found';
|
||||||
|
$options['contentTypeCharset'] = MINIFY_ENCODING;
|
||||||
|
|
||||||
|
// The following restrictions are to limit the URLs that minify will
|
||||||
|
// respond to. Ideally there should be only one way to reference a file.
|
||||||
|
if (! isset($_GET['files'])
|
||||||
|
// verify at least one file, files are single comma separated,
|
||||||
|
// and are all same extension
|
||||||
|
|| ! preg_match('/^[^,]+\\.(css|js)(,[^,]+\\.\\1)*$/', $_GET['files'], $m)
|
||||||
|
// no "//" (makes URL rewriting easier)
|
||||||
|
|| strpos($_GET['files'], '//') !== false
|
||||||
|
// no "\"
|
||||||
|
|| strpos($_GET['files'], '\\') !== false
|
||||||
|
// no "./"
|
||||||
|
|| preg_match('/(?:^|[^\\.])\\.\\//', $_GET['files'])
|
||||||
|
) {
|
||||||
|
return $options;
|
||||||
|
}
|
||||||
|
$extension = $m[1];
|
||||||
|
|
||||||
|
$files = explode(',', $_GET['files']);
|
||||||
|
if (count($files) > MINIFY_MAX_FILES) {
|
||||||
|
return $options;
|
||||||
|
}
|
||||||
|
|
||||||
|
// strings for prepending to relative/absolute paths
|
||||||
|
$prependRelPaths = dirname($_SERVER['SCRIPT_FILENAME'])
|
||||||
|
. DIRECTORY_SEPARATOR;
|
||||||
|
$prependAbsPaths = $_SERVER['DOCUMENT_ROOT'];
|
||||||
|
|
||||||
|
$sources = array();
|
||||||
|
$goodFiles = array();
|
||||||
|
$hasBadSource = false;
|
||||||
|
|
||||||
|
$allowDirs = isset($options['allowDirs'])
|
||||||
|
? $options['allowDirs']
|
||||||
|
: MINIFY_BASE_DIR;
|
||||||
|
|
||||||
|
foreach ($files as $file) {
|
||||||
|
// prepend appropriate string for abs/rel paths
|
||||||
|
$file = ($file[0] === '/' ? $prependAbsPaths : $prependRelPaths) . $file;
|
||||||
|
// make sure a real file!
|
||||||
|
$file = realpath($file);
|
||||||
|
// don't allow unsafe or duplicate files
|
||||||
|
if (parent::_fileIsSafe($file, $allowDirs)
|
||||||
|
&& !in_array($file, $goodFiles))
|
||||||
|
{
|
||||||
|
$goodFiles[] = $file;
|
||||||
|
$srcOptions = array(
|
||||||
|
'filepath' => $file
|
||||||
|
);
|
||||||
|
$this->sources[] = new Minify_Source($srcOptions);
|
||||||
|
} else {
|
||||||
|
$hasBadSource = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($hasBadSource) {
|
||||||
|
$this->sources = array();
|
||||||
|
}
|
||||||
|
if (! MINIFY_REWRITE_CSS_URLS) {
|
||||||
|
$options['rewriteCssUris'] = false;
|
||||||
|
}
|
||||||
|
return $options;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function _setupDefines()
|
||||||
|
{
|
||||||
|
$defaults = array(
|
||||||
|
'MINIFY_BASE_DIR' => realpath($_SERVER['DOCUMENT_ROOT'])
|
||||||
|
,'MINIFY_ENCODING' => 'utf-8'
|
||||||
|
,'MINIFY_MAX_FILES' => 16
|
||||||
|
,'MINIFY_REWRITE_CSS_URLS' => true
|
||||||
|
,'MINIFY_USE_CACHE' => true
|
||||||
|
);
|
||||||
|
foreach ($defaults as $const => $val) {
|
||||||
|
if (! defined($const)) {
|
||||||
|
define($const, $val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
26
inc/contrib/minify/Minify/DebugDetector.php
Executable file
26
inc/contrib/minify/Minify/DebugDetector.php
Executable file
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect whether request should be debugged
|
||||||
|
*
|
||||||
|
* @package Minify
|
||||||
|
* @author Stephen Clay <steve@mrclay.org>
|
||||||
|
*/
|
||||||
|
class Minify_DebugDetector {
|
||||||
|
public static function shouldDebugRequest($cookie, $get, $requestUri)
|
||||||
|
{
|
||||||
|
if (isset($get['debug'])) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (! empty($cookie['minifyDebug'])) {
|
||||||
|
foreach (preg_split('/\\s+/', $cookie['minifyDebug']) as $debugUri) {
|
||||||
|
$pattern = '@' . preg_quote($debugUri, '@') . '@i';
|
||||||
|
$pattern = str_replace(array('\\*', '\\?'), array('.*', '.'), $pattern);
|
||||||
|
if (preg_match($pattern, $requestUri)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
246
inc/contrib/minify/Minify/HTML.php
Executable file
246
inc/contrib/minify/Minify/HTML.php
Executable file
@ -0,0 +1,246 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Class Minify_HTML
|
||||||
|
* @package Minify
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compress HTML
|
||||||
|
*
|
||||||
|
* This is a heavy regex-based removal of whitespace, unnecessary comments and
|
||||||
|
* tokens. IE conditional comments are preserved. There are also options to have
|
||||||
|
* STYLE and SCRIPT blocks compressed by callback functions.
|
||||||
|
*
|
||||||
|
* A test suite is available.
|
||||||
|
*
|
||||||
|
* @package Minify
|
||||||
|
* @author Stephen Clay <steve@mrclay.org>
|
||||||
|
*/
|
||||||
|
class Minify_HTML {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "Minify" an HTML page
|
||||||
|
*
|
||||||
|
* @param string $html
|
||||||
|
*
|
||||||
|
* @param array $options
|
||||||
|
*
|
||||||
|
* 'cssMinifier' : (optional) callback function to process content of STYLE
|
||||||
|
* elements.
|
||||||
|
*
|
||||||
|
* 'jsMinifier' : (optional) callback function to process content of SCRIPT
|
||||||
|
* elements. Note: the type attribute is ignored.
|
||||||
|
*
|
||||||
|
* 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
|
||||||
|
* unset, minify will sniff for an XHTML doctype.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function minify($html, $options = array()) {
|
||||||
|
$min = new Minify_HTML($html, $options);
|
||||||
|
return $min->process();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a minifier object
|
||||||
|
*
|
||||||
|
* @param string $html
|
||||||
|
*
|
||||||
|
* @param array $options
|
||||||
|
*
|
||||||
|
* 'cssMinifier' : (optional) callback function to process content of STYLE
|
||||||
|
* elements.
|
||||||
|
*
|
||||||
|
* 'jsMinifier' : (optional) callback function to process content of SCRIPT
|
||||||
|
* elements. Note: the type attribute is ignored.
|
||||||
|
*
|
||||||
|
* 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
|
||||||
|
* unset, minify will sniff for an XHTML doctype.
|
||||||
|
*
|
||||||
|
* @return null
|
||||||
|
*/
|
||||||
|
public function __construct($html, $options = array())
|
||||||
|
{
|
||||||
|
$this->_html = str_replace("\r\n", "\n", trim($html));
|
||||||
|
if (isset($options['xhtml'])) {
|
||||||
|
$this->_isXhtml = (bool)$options['xhtml'];
|
||||||
|
}
|
||||||
|
if (isset($options['cssMinifier'])) {
|
||||||
|
$this->_cssMinifier = $options['cssMinifier'];
|
||||||
|
}
|
||||||
|
if (isset($options['jsMinifier'])) {
|
||||||
|
$this->_jsMinifier = $options['jsMinifier'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minify the markeup given in the constructor
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function process()
|
||||||
|
{
|
||||||
|
if ($this->_isXhtml === null) {
|
||||||
|
$this->_isXhtml = (false !== strpos($this->_html, '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_replacementHash = 'MINIFYHTML' . md5($_SERVER['REQUEST_TIME']);
|
||||||
|
$this->_placeholders = array();
|
||||||
|
|
||||||
|
// replace SCRIPTs (and minify) with placeholders
|
||||||
|
$this->_html = preg_replace_callback(
|
||||||
|
'/(\\s*)<script(\\b[^>]*?>)([\\s\\S]*?)<\\/script>(\\s*)/i'
|
||||||
|
,array($this, '_removeScriptCB')
|
||||||
|
,$this->_html);
|
||||||
|
|
||||||
|
// replace STYLEs (and minify) with placeholders
|
||||||
|
$this->_html = preg_replace_callback(
|
||||||
|
'/\\s*<style(\\b[^>]*>)([\\s\\S]*?)<\\/style>\\s*/i'
|
||||||
|
,array($this, '_removeStyleCB')
|
||||||
|
,$this->_html);
|
||||||
|
|
||||||
|
// remove HTML comments (not containing IE conditional comments).
|
||||||
|
$this->_html = preg_replace_callback(
|
||||||
|
'/<!--([\\s\\S]*?)-->/'
|
||||||
|
,array($this, '_commentCB')
|
||||||
|
,$this->_html);
|
||||||
|
|
||||||
|
// replace PREs with placeholders
|
||||||
|
$this->_html = preg_replace_callback('/\\s*<pre(\\b[^>]*?>[\\s\\S]*?<\\/pre>)\\s*/i'
|
||||||
|
,array($this, '_removePreCB')
|
||||||
|
,$this->_html);
|
||||||
|
|
||||||
|
// replace TEXTAREAs with placeholders
|
||||||
|
$this->_html = preg_replace_callback(
|
||||||
|
'/\\s*<textarea(\\b[^>]*?>[\\s\\S]*?<\\/textarea>)\\s*/i'
|
||||||
|
,array($this, '_removeTextareaCB')
|
||||||
|
,$this->_html);
|
||||||
|
|
||||||
|
// trim each line.
|
||||||
|
// @todo take into account attribute values that span multiple lines.
|
||||||
|
$this->_html = preg_replace('/^\\s+|\\s+$/m', '', $this->_html);
|
||||||
|
|
||||||
|
// remove ws around block/undisplayed elements
|
||||||
|
$this->_html = preg_replace('/\\s+(<\\/?(?:area|base(?:font)?|blockquote|body'
|
||||||
|
.'|caption|center|cite|col(?:group)?|dd|dir|div|dl|dt|fieldset|form'
|
||||||
|
.'|frame(?:set)?|h[1-6]|head|hr|html|legend|li|link|map|menu|meta'
|
||||||
|
.'|ol|opt(?:group|ion)|p|param|t(?:able|body|head|d|h||r|foot|itle)'
|
||||||
|
.'|ul)\\b[^>]*>)/i', '$1', $this->_html);
|
||||||
|
|
||||||
|
// remove ws outside of all elements
|
||||||
|
$this->_html = preg_replace(
|
||||||
|
'/>(\\s(?:\\s*))?([^<]+)(\\s(?:\s*))?</'
|
||||||
|
,'>$1$2$3<'
|
||||||
|
,$this->_html);
|
||||||
|
|
||||||
|
// use newlines before 1st attribute in open tags (to limit line lengths)
|
||||||
|
$this->_html = preg_replace('/(<[a-z\\-]+)\\s+([^>]+>)/i', "$1\n$2", $this->_html);
|
||||||
|
|
||||||
|
// fill placeholders
|
||||||
|
$this->_html = str_replace(
|
||||||
|
array_keys($this->_placeholders)
|
||||||
|
,array_values($this->_placeholders)
|
||||||
|
,$this->_html
|
||||||
|
);
|
||||||
|
// issue 229: multi-pass to catch scripts that didn't get replaced in textareas
|
||||||
|
$this->_html = str_replace(
|
||||||
|
array_keys($this->_placeholders)
|
||||||
|
,array_values($this->_placeholders)
|
||||||
|
,$this->_html
|
||||||
|
);
|
||||||
|
return $this->_html;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _commentCB($m)
|
||||||
|
{
|
||||||
|
return (0 === strpos($m[1], '[') || false !== strpos($m[1], '<!['))
|
||||||
|
? $m[0]
|
||||||
|
: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _reservePlace($content)
|
||||||
|
{
|
||||||
|
$placeholder = '%' . $this->_replacementHash . count($this->_placeholders) . '%';
|
||||||
|
$this->_placeholders[$placeholder] = $content;
|
||||||
|
return $placeholder;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $_isXhtml = null;
|
||||||
|
protected $_replacementHash = null;
|
||||||
|
protected $_placeholders = array();
|
||||||
|
protected $_cssMinifier = null;
|
||||||
|
protected $_jsMinifier = null;
|
||||||
|
|
||||||
|
protected function _removePreCB($m)
|
||||||
|
{
|
||||||
|
return $this->_reservePlace("<pre{$m[1]}");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _removeTextareaCB($m)
|
||||||
|
{
|
||||||
|
return $this->_reservePlace("<textarea{$m[1]}");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _removeStyleCB($m)
|
||||||
|
{
|
||||||
|
$openStyle = "<style{$m[1]}";
|
||||||
|
$css = $m[2];
|
||||||
|
// remove HTML comments
|
||||||
|
$css = preg_replace('/(?:^\\s*<!--|-->\\s*$)/', '', $css);
|
||||||
|
|
||||||
|
// remove CDATA section markers
|
||||||
|
$css = $this->_removeCdata($css);
|
||||||
|
|
||||||
|
// minify
|
||||||
|
$minifier = $this->_cssMinifier
|
||||||
|
? $this->_cssMinifier
|
||||||
|
: 'trim';
|
||||||
|
$css = call_user_func($minifier, $css);
|
||||||
|
|
||||||
|
return $this->_reservePlace($this->_needsCdata($css)
|
||||||
|
? "{$openStyle}/*<![CDATA[*/{$css}/*]]>*/</style>"
|
||||||
|
: "{$openStyle}{$css}</style>"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _removeScriptCB($m)
|
||||||
|
{
|
||||||
|
$openScript = "<script{$m[2]}";
|
||||||
|
$js = $m[3];
|
||||||
|
|
||||||
|
// whitespace surrounding? preserve at least one space
|
||||||
|
$ws1 = ($m[1] === '') ? '' : ' ';
|
||||||
|
$ws2 = ($m[4] === '') ? '' : ' ';
|
||||||
|
|
||||||
|
// remove HTML comments (and ending "//" if present)
|
||||||
|
$js = preg_replace('/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $js);
|
||||||
|
|
||||||
|
// remove CDATA section markers
|
||||||
|
$js = $this->_removeCdata($js);
|
||||||
|
|
||||||
|
// minify
|
||||||
|
$minifier = $this->_jsMinifier
|
||||||
|
? $this->_jsMinifier
|
||||||
|
: 'trim';
|
||||||
|
$js = call_user_func($minifier, $js);
|
||||||
|
|
||||||
|
return $this->_reservePlace($this->_needsCdata($js)
|
||||||
|
? "{$ws1}{$openScript}/*<![CDATA[*/{$js}/*]]>*/</script>{$ws2}"
|
||||||
|
: "{$ws1}{$openScript}{$js}</script>{$ws2}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _removeCdata($str)
|
||||||
|
{
|
||||||
|
return (false !== strpos($str, '<![CDATA['))
|
||||||
|
? str_replace(array('<![CDATA[', ']]>'), '', $str)
|
||||||
|
: $str;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _needsCdata($str)
|
||||||
|
{
|
||||||
|
return ($this->_isXhtml && preg_match('/(?:[<&]|\\-\\-|\\]\\]>)/', $str));
|
||||||
|
}
|
||||||
|
}
|
193
inc/contrib/minify/Minify/HTML/Helper.php
Executable file
193
inc/contrib/minify/Minify/HTML/Helper.php
Executable file
@ -0,0 +1,193 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Class Minify_HTML_Helper
|
||||||
|
* @package Minify
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helpers for writing Minfy URIs into HTML
|
||||||
|
*
|
||||||
|
* @package Minify
|
||||||
|
* @author Stephen Clay <steve@mrclay.org>
|
||||||
|
*/
|
||||||
|
class Minify_HTML_Helper {
|
||||||
|
public $rewriteWorks = true;
|
||||||
|
public $minAppUri = '/min';
|
||||||
|
public $groupsConfigFile = '';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get an HTML-escaped Minify URI for a group or set of files
|
||||||
|
*
|
||||||
|
* @param mixed $keyOrFiles a group key or array of filepaths/URIs
|
||||||
|
* @param array $opts options:
|
||||||
|
* 'farExpires' : (default true) append a modified timestamp for cache revving
|
||||||
|
* 'debug' : (default false) append debug flag
|
||||||
|
* 'charset' : (default 'UTF-8') for htmlspecialchars
|
||||||
|
* 'minAppUri' : (default '/min') URI of min directory
|
||||||
|
* 'rewriteWorks' : (default true) does mod_rewrite work in min app?
|
||||||
|
* 'groupsConfigFile' : specify if different
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function getUri($keyOrFiles, $opts = array())
|
||||||
|
{
|
||||||
|
$opts = array_merge(array( // default options
|
||||||
|
'farExpires' => true
|
||||||
|
,'debug' => false
|
||||||
|
,'charset' => 'UTF-8'
|
||||||
|
,'minAppUri' => '/min'
|
||||||
|
,'rewriteWorks' => true
|
||||||
|
,'groupsConfigFile' => ''
|
||||||
|
), $opts);
|
||||||
|
$h = new self;
|
||||||
|
$h->minAppUri = $opts['minAppUri'];
|
||||||
|
$h->rewriteWorks = $opts['rewriteWorks'];
|
||||||
|
$h->groupsConfigFile = $opts['groupsConfigFile'];
|
||||||
|
if (is_array($keyOrFiles)) {
|
||||||
|
$h->setFiles($keyOrFiles, $opts['farExpires']);
|
||||||
|
} else {
|
||||||
|
$h->setGroup($keyOrFiles, $opts['farExpires']);
|
||||||
|
}
|
||||||
|
$uri = $h->getRawUri($opts['farExpires'], $opts['debug']);
|
||||||
|
return htmlspecialchars($uri, ENT_QUOTES, $opts['charset']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get non-HTML-escaped URI to minify the specified files
|
||||||
|
*/
|
||||||
|
public function getRawUri($farExpires = true, $debug = false)
|
||||||
|
{
|
||||||
|
$path = rtrim($this->minAppUri, '/') . '/';
|
||||||
|
if (! $this->rewriteWorks) {
|
||||||
|
$path .= '?';
|
||||||
|
}
|
||||||
|
if (null === $this->_groupKey) {
|
||||||
|
// @todo: implement shortest uri
|
||||||
|
$path = self::_getShortestUri($this->_filePaths, $path);
|
||||||
|
} else {
|
||||||
|
$path .= "g=" . $this->_groupKey;
|
||||||
|
}
|
||||||
|
if ($debug) {
|
||||||
|
$path .= "&debug";
|
||||||
|
} elseif ($farExpires && $this->_lastModified) {
|
||||||
|
$path .= "&" . $this->_lastModified;
|
||||||
|
}
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setFiles($files, $checkLastModified = true)
|
||||||
|
{
|
||||||
|
$this->_groupKey = null;
|
||||||
|
if ($checkLastModified) {
|
||||||
|
$this->_lastModified = self::getLastModified($files);
|
||||||
|
}
|
||||||
|
// normalize paths like in /min/f=<paths>
|
||||||
|
foreach ($files as $k => $file) {
|
||||||
|
if (0 === strpos($file, '//')) {
|
||||||
|
$file = substr($file, 2);
|
||||||
|
} elseif (0 === strpos($file, '/')
|
||||||
|
|| 1 === strpos($file, ':\\')) {
|
||||||
|
$file = substr($file, strlen($_SERVER['DOCUMENT_ROOT']) + 1);
|
||||||
|
}
|
||||||
|
$file = strtr($file, '\\', '/');
|
||||||
|
$files[$k] = $file;
|
||||||
|
}
|
||||||
|
$this->_filePaths = $files;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setGroup($key, $checkLastModified = true)
|
||||||
|
{
|
||||||
|
$this->_groupKey = $key;
|
||||||
|
if ($checkLastModified) {
|
||||||
|
if (! $this->groupsConfigFile) {
|
||||||
|
$this->groupsConfigFile = dirname(dirname(dirname(dirname(__FILE__)))) . '/groupsConfig.php';
|
||||||
|
}
|
||||||
|
if (is_file($this->groupsConfigFile)) {
|
||||||
|
$gc = (require $this->groupsConfigFile);
|
||||||
|
if (isset($gc[$key])) {
|
||||||
|
$this->_lastModified = self::getLastModified($gc[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getLastModified($sources, $lastModified = 0)
|
||||||
|
{
|
||||||
|
$max = $lastModified;
|
||||||
|
foreach ((array)$sources as $source) {
|
||||||
|
if (is_object($source) && isset($source->lastModified)) {
|
||||||
|
$max = max($max, $source->lastModified);
|
||||||
|
} elseif (is_string($source)) {
|
||||||
|
if (0 === strpos($source, '//')) {
|
||||||
|
$source = $_SERVER['DOCUMENT_ROOT'] . substr($source, 1);
|
||||||
|
}
|
||||||
|
if (is_file($source)) {
|
||||||
|
$max = max($max, filemtime($source));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $max;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $_groupKey = null; // if present, URI will be like g=...
|
||||||
|
protected $_filePaths = array();
|
||||||
|
protected $_lastModified = null;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In a given array of strings, find the character they all have at
|
||||||
|
* a particular index
|
||||||
|
*
|
||||||
|
* @param array $arr array of strings
|
||||||
|
* @param int $pos index to check
|
||||||
|
* @return mixed a common char or '' if any do not match
|
||||||
|
*/
|
||||||
|
protected static function _getCommonCharAtPos($arr, $pos) {
|
||||||
|
$l = count($arr);
|
||||||
|
$c = $arr[0][$pos];
|
||||||
|
if ($c === '' || $l === 1)
|
||||||
|
return $c;
|
||||||
|
for ($i = 1; $i < $l; ++$i)
|
||||||
|
if ($arr[$i][$pos] !== $c)
|
||||||
|
return '';
|
||||||
|
return $c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the shortest URI to minify the set of source files
|
||||||
|
*
|
||||||
|
* @param array $paths root-relative URIs of files
|
||||||
|
* @param string $minRoot root-relative URI of the "min" application
|
||||||
|
*/
|
||||||
|
protected static function _getShortestUri($paths, $minRoot = '/min/') {
|
||||||
|
$pos = 0;
|
||||||
|
$base = '';
|
||||||
|
$c;
|
||||||
|
while (true) {
|
||||||
|
$c = self::_getCommonCharAtPos($paths, $pos);
|
||||||
|
if ($c === '') {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
$base .= $c;
|
||||||
|
}
|
||||||
|
++$pos;
|
||||||
|
}
|
||||||
|
$base = preg_replace('@[^/]+$@', '', $base);
|
||||||
|
$uri = $minRoot . 'f=' . implode(',', $paths);
|
||||||
|
|
||||||
|
if (substr($base, -1) === '/') {
|
||||||
|
// we have a base dir!
|
||||||
|
$basedPaths = $paths;
|
||||||
|
$l = count($paths);
|
||||||
|
for ($i = 0; $i < $l; ++$i) {
|
||||||
|
$basedPaths[$i] = substr($paths[$i], strlen($base));
|
||||||
|
}
|
||||||
|
$base = substr($base, 0, strlen($base) - 1);
|
||||||
|
$bUri = $minRoot . 'b=' . $base . '&f=' . implode(',', $basedPaths);
|
||||||
|
|
||||||
|
$uri = strlen($uri) < strlen($bUri)
|
||||||
|
? $uri
|
||||||
|
: $bUri;
|
||||||
|
}
|
||||||
|
return $uri;
|
||||||
|
}
|
||||||
|
}
|
216
inc/contrib/minify/Minify/ImportProcessor.php
Executable file
216
inc/contrib/minify/Minify/ImportProcessor.php
Executable file
@ -0,0 +1,216 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Class Minify_ImportProcessor
|
||||||
|
* @package Minify
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Linearize a CSS/JS file by including content specified by CSS import
|
||||||
|
* declarations. In CSS files, relative URIs are fixed.
|
||||||
|
*
|
||||||
|
* @imports will be processed regardless of where they appear in the source
|
||||||
|
* files; i.e. @imports commented out or in string content will still be
|
||||||
|
* processed!
|
||||||
|
*
|
||||||
|
* This has a unit test but should be considered "experimental".
|
||||||
|
*
|
||||||
|
* @package Minify
|
||||||
|
* @author Stephen Clay <steve@mrclay.org>
|
||||||
|
* @author Simon Schick <simonsimcity@gmail.com>
|
||||||
|
*/
|
||||||
|
class Minify_ImportProcessor {
|
||||||
|
|
||||||
|
public static $filesIncluded = array();
|
||||||
|
|
||||||
|
public static function process($file)
|
||||||
|
{
|
||||||
|
self::$filesIncluded = array();
|
||||||
|
self::$_isCss = (strtolower(substr($file, -4)) === '.css');
|
||||||
|
$obj = new Minify_ImportProcessor(dirname($file));
|
||||||
|
return $obj->_getContent($file);
|
||||||
|
}
|
||||||
|
|
||||||
|
// allows callback funcs to know the current directory
|
||||||
|
private $_currentDir = null;
|
||||||
|
|
||||||
|
// allows callback funcs to know the directory of the file that inherits this one
|
||||||
|
private $_previewsDir = null;
|
||||||
|
|
||||||
|
// allows _importCB to write the fetched content back to the obj
|
||||||
|
private $_importedContent = '';
|
||||||
|
|
||||||
|
private static $_isCss = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param String $currentDir
|
||||||
|
* @param String $previewsDir Is only used internally
|
||||||
|
*/
|
||||||
|
private function __construct($currentDir, $previewsDir = "")
|
||||||
|
{
|
||||||
|
$this->_currentDir = $currentDir;
|
||||||
|
$this->_previewsDir = $previewsDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function _getContent($file, $is_imported = false)
|
||||||
|
{
|
||||||
|
$file = realpath($file);
|
||||||
|
if (! $file
|
||||||
|
|| in_array($file, self::$filesIncluded)
|
||||||
|
|| false === ($content = @file_get_contents($file))
|
||||||
|
) {
|
||||||
|
// file missing, already included, or failed read
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
self::$filesIncluded[] = realpath($file);
|
||||||
|
$this->_currentDir = dirname($file);
|
||||||
|
|
||||||
|
// remove UTF-8 BOM if present
|
||||||
|
if (pack("CCC",0xef,0xbb,0xbf) === substr($content, 0, 3)) {
|
||||||
|
$content = substr($content, 3);
|
||||||
|
}
|
||||||
|
// ensure uniform EOLs
|
||||||
|
$content = str_replace("\r\n", "\n", $content);
|
||||||
|
|
||||||
|
// process @imports
|
||||||
|
$content = preg_replace_callback(
|
||||||
|
'/
|
||||||
|
@import\\s+
|
||||||
|
(?:url\\(\\s*)? # maybe url(
|
||||||
|
[\'"]? # maybe quote
|
||||||
|
(.*?) # 1 = URI
|
||||||
|
[\'"]? # maybe end quote
|
||||||
|
(?:\\s*\\))? # maybe )
|
||||||
|
([a-zA-Z,\\s]*)? # 2 = media list
|
||||||
|
; # end token
|
||||||
|
/x'
|
||||||
|
,array($this, '_importCB')
|
||||||
|
,$content
|
||||||
|
);
|
||||||
|
|
||||||
|
// You only need to rework the import-path if the script is imported
|
||||||
|
if (self::$_isCss && $is_imported) {
|
||||||
|
// rewrite remaining relative URIs
|
||||||
|
$content = preg_replace_callback(
|
||||||
|
'/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
|
||||||
|
,array($this, '_urlCB')
|
||||||
|
,$content
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->_importedContent . $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function _importCB($m)
|
||||||
|
{
|
||||||
|
$url = $m[1];
|
||||||
|
$mediaList = preg_replace('/\\s+/', '', $m[2]);
|
||||||
|
|
||||||
|
if (strpos($url, '://') > 0) {
|
||||||
|
// protocol, leave in place for CSS, comment for JS
|
||||||
|
return self::$_isCss
|
||||||
|
? $m[0]
|
||||||
|
: "/* Minify_ImportProcessor will not include remote content */";
|
||||||
|
}
|
||||||
|
if ('/' === $url[0]) {
|
||||||
|
// protocol-relative or root path
|
||||||
|
$url = ltrim($url, '/');
|
||||||
|
$file = realpath($_SERVER['DOCUMENT_ROOT']) . DIRECTORY_SEPARATOR
|
||||||
|
. strtr($url, '/', DIRECTORY_SEPARATOR);
|
||||||
|
} else {
|
||||||
|
// relative to current path
|
||||||
|
$file = $this->_currentDir . DIRECTORY_SEPARATOR
|
||||||
|
. strtr($url, '/', DIRECTORY_SEPARATOR);
|
||||||
|
}
|
||||||
|
$obj = new Minify_ImportProcessor(dirname($file), $this->_currentDir);
|
||||||
|
$content = $obj->_getContent($file, true);
|
||||||
|
if ('' === $content) {
|
||||||
|
// failed. leave in place for CSS, comment for JS
|
||||||
|
return self::$_isCss
|
||||||
|
? $m[0]
|
||||||
|
: "/* Minify_ImportProcessor could not fetch '{$file}' */";
|
||||||
|
}
|
||||||
|
return (!self::$_isCss || preg_match('@(?:^$|\\ball\\b)@', $mediaList))
|
||||||
|
? $content
|
||||||
|
: "@media {$mediaList} {\n{$content}\n}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
private function _urlCB($m)
|
||||||
|
{
|
||||||
|
// $m[1] is either quoted or not
|
||||||
|
$quote = ($m[1][0] === "'" || $m[1][0] === '"')
|
||||||
|
? $m[1][0]
|
||||||
|
: '';
|
||||||
|
$url = ($quote === '')
|
||||||
|
? $m[1]
|
||||||
|
: substr($m[1], 1, strlen($m[1]) - 2);
|
||||||
|
if ('/' !== $url[0]) {
|
||||||
|
if (strpos($url, '//') > 0) {
|
||||||
|
// probably starts with protocol, do not alter
|
||||||
|
} else {
|
||||||
|
// prepend path with current dir separator (OS-independent)
|
||||||
|
$path = $this->_currentDir
|
||||||
|
. DIRECTORY_SEPARATOR . strtr($url, '/', DIRECTORY_SEPARATOR);
|
||||||
|
// update the relative path by the directory of the file that imported this one
|
||||||
|
$url = self::getPathDiff(realpath($this->_previewsDir), $path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "url({$quote}{$url}{$quote})";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $from
|
||||||
|
* @param string $to
|
||||||
|
* @param string $ps
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function getPathDiff($from, $to, $ps = DIRECTORY_SEPARATOR)
|
||||||
|
{
|
||||||
|
$realFrom = $this->truepath($from);
|
||||||
|
$realTo = $this->truepath($to);
|
||||||
|
|
||||||
|
$arFrom = explode($ps, rtrim($realFrom, $ps));
|
||||||
|
$arTo = explode($ps, rtrim($realTo, $ps));
|
||||||
|
while (count($arFrom) && count($arTo) && ($arFrom[0] == $arTo[0]))
|
||||||
|
{
|
||||||
|
array_shift($arFrom);
|
||||||
|
array_shift($arTo);
|
||||||
|
}
|
||||||
|
return str_pad("", count($arFrom) * 3, '..' . $ps) . implode($ps, $arTo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is to replace PHP's extremely buggy realpath().
|
||||||
|
* @param string $path The original path, can be relative etc.
|
||||||
|
* @return string The resolved path, it might not exist.
|
||||||
|
* @see http://stackoverflow.com/questions/4049856/replace-phps-realpath
|
||||||
|
*/
|
||||||
|
function truepath($path)
|
||||||
|
{
|
||||||
|
// whether $path is unix or not
|
||||||
|
$unipath = strlen($path) == 0 || $path{0} != '/';
|
||||||
|
// attempts to detect if path is relative in which case, add cwd
|
||||||
|
if (strpos($path, ':') === false && $unipath)
|
||||||
|
$path = $this->_currentDir . DIRECTORY_SEPARATOR . $path;
|
||||||
|
|
||||||
|
// resolve path parts (single dot, double dot and double delimiters)
|
||||||
|
$path = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $path);
|
||||||
|
$parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen');
|
||||||
|
$absolutes = array();
|
||||||
|
foreach ($parts as $part) {
|
||||||
|
if ('.' == $part)
|
||||||
|
continue;
|
||||||
|
if ('..' == $part) {
|
||||||
|
array_pop($absolutes);
|
||||||
|
} else {
|
||||||
|
$absolutes[] = $part;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$path = implode(DIRECTORY_SEPARATOR, $absolutes);
|
||||||
|
// resolve any symlinks
|
||||||
|
if (file_exists($path) && linkinfo($path) > 0)
|
||||||
|
$path = readlink($path);
|
||||||
|
// put initial separator that could have been lost
|
||||||
|
$path = !$unipath ? '/' . $path : $path;
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
}
|
133
inc/contrib/minify/Minify/JS/ClosureCompiler.php
Executable file
133
inc/contrib/minify/Minify/JS/ClosureCompiler.php
Executable file
@ -0,0 +1,133 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Class Minify_JS_ClosureCompiler
|
||||||
|
* @package Minify
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minify Javascript using Google's Closure Compiler API
|
||||||
|
*
|
||||||
|
* @link http://code.google.com/closure/compiler/
|
||||||
|
* @package Minify
|
||||||
|
* @author Stephen Clay <steve@mrclay.org>
|
||||||
|
*
|
||||||
|
* @todo can use a stream wrapper to unit test this?
|
||||||
|
*/
|
||||||
|
class Minify_JS_ClosureCompiler {
|
||||||
|
const URL = 'http://closure-compiler.appspot.com/compile';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minify Javascript code via HTTP request to the Closure Compiler API
|
||||||
|
*
|
||||||
|
* @param string $js input code
|
||||||
|
* @param array $options unused at this point
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function minify($js, array $options = array())
|
||||||
|
{
|
||||||
|
$obj = new self($options);
|
||||||
|
return $obj->min($js);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param array $options
|
||||||
|
*
|
||||||
|
* fallbackFunc : default array($this, 'fallback');
|
||||||
|
*/
|
||||||
|
public function __construct(array $options = array())
|
||||||
|
{
|
||||||
|
$this->_fallbackFunc = isset($options['fallbackMinifier'])
|
||||||
|
? $options['fallbackMinifier']
|
||||||
|
: array($this, '_fallback');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function min($js)
|
||||||
|
{
|
||||||
|
$postBody = $this->_buildPostBody($js);
|
||||||
|
$bytes = (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2))
|
||||||
|
? mb_strlen($postBody, '8bit')
|
||||||
|
: strlen($postBody);
|
||||||
|
if ($bytes > 200000) {
|
||||||
|
throw new Minify_JS_ClosureCompiler_Exception(
|
||||||
|
'POST content larger than 200000 bytes'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$response = $this->_getResponse($postBody);
|
||||||
|
if (preg_match('/^Error\(\d\d?\):/', $response)) {
|
||||||
|
if (is_callable($this->_fallbackFunc)) {
|
||||||
|
$response = "/* Received errors from Closure Compiler API:\n$response"
|
||||||
|
. "\n(Using fallback minifier)\n*/\n";
|
||||||
|
$response .= call_user_func($this->_fallbackFunc, $js);
|
||||||
|
} else {
|
||||||
|
throw new Minify_JS_ClosureCompiler_Exception($response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($response === '') {
|
||||||
|
$errors = $this->_getResponse($this->_buildPostBody($js, true));
|
||||||
|
throw new Minify_JS_ClosureCompiler_Exception($errors);
|
||||||
|
}
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $_fallbackFunc = null;
|
||||||
|
|
||||||
|
protected function _getResponse($postBody)
|
||||||
|
{
|
||||||
|
$allowUrlFopen = preg_match('/1|yes|on|true/i', ini_get('allow_url_fopen'));
|
||||||
|
if ($allowUrlFopen) {
|
||||||
|
$contents = file_get_contents(self::URL, false, stream_context_create(array(
|
||||||
|
'http' => array(
|
||||||
|
'method' => 'POST',
|
||||||
|
'header' => 'Content-type: application/x-www-form-urlencoded',
|
||||||
|
'content' => $postBody,
|
||||||
|
'max_redirects' => 0,
|
||||||
|
'timeout' => 15,
|
||||||
|
)
|
||||||
|
)));
|
||||||
|
} elseif (defined('CURLOPT_POST')) {
|
||||||
|
$ch = curl_init(self::URL);
|
||||||
|
curl_setopt($ch, CURLOPT_POST, true);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-type: application/x-www-form-urlencoded'));
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $postBody);
|
||||||
|
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
|
||||||
|
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15);
|
||||||
|
$contents = curl_exec($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
} else {
|
||||||
|
throw new Minify_JS_ClosureCompiler_Exception(
|
||||||
|
"Could not make HTTP request: allow_url_open is false and cURL not available"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (false === $contents) {
|
||||||
|
throw new Minify_JS_ClosureCompiler_Exception(
|
||||||
|
"No HTTP response from server"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return trim($contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _buildPostBody($js, $returnErrors = false)
|
||||||
|
{
|
||||||
|
return http_build_query(array(
|
||||||
|
'js_code' => $js,
|
||||||
|
'output_info' => ($returnErrors ? 'errors' : 'compiled_code'),
|
||||||
|
'output_format' => 'text',
|
||||||
|
'compilation_level' => 'SIMPLE_OPTIMIZATIONS'
|
||||||
|
), null, '&');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default fallback function if CC API fails
|
||||||
|
* @param string $js
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function _fallback($js)
|
||||||
|
{
|
||||||
|
require_once 'JSMin.php';
|
||||||
|
return JSMin::minify($js);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Minify_JS_ClosureCompiler_Exception extends Exception {}
|
137
inc/contrib/minify/Minify/Lines.php
Executable file
137
inc/contrib/minify/Minify/Lines.php
Executable file
@ -0,0 +1,137 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Class Minify_Lines
|
||||||
|
* @package Minify
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add line numbers in C-style comments for easier debugging of combined content
|
||||||
|
*
|
||||||
|
* @package Minify
|
||||||
|
* @author Stephen Clay <steve@mrclay.org>
|
||||||
|
* @author Adam Pedersen (Issue 55 fix)
|
||||||
|
*/
|
||||||
|
class Minify_Lines {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add line numbers in C-style comments
|
||||||
|
*
|
||||||
|
* This uses a very basic parser easily fooled by comment tokens inside
|
||||||
|
* strings or regexes, but, otherwise, generally clean code will not be
|
||||||
|
* mangled. URI rewriting can also be performed.
|
||||||
|
*
|
||||||
|
* @param string $content
|
||||||
|
*
|
||||||
|
* @param array $options available options:
|
||||||
|
*
|
||||||
|
* 'id': (optional) string to identify file. E.g. file name/path
|
||||||
|
*
|
||||||
|
* 'currentDir': (default null) if given, this is assumed to be the
|
||||||
|
* directory of the current CSS file. Using this, minify will rewrite
|
||||||
|
* all relative URIs in import/url declarations to correctly point to
|
||||||
|
* the desired files, and prepend a comment with debugging information about
|
||||||
|
* this process.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function minify($content, $options = array())
|
||||||
|
{
|
||||||
|
$id = (isset($options['id']) && $options['id'])
|
||||||
|
? $options['id']
|
||||||
|
: '';
|
||||||
|
$content = str_replace("\r\n", "\n", $content);
|
||||||
|
|
||||||
|
// Hackily rewrite strings with XPath expressions that are
|
||||||
|
// likely to throw off our dumb parser (for Prototype 1.6.1).
|
||||||
|
$content = str_replace('"/*"', '"/"+"*"', $content);
|
||||||
|
$content = preg_replace('@([\'"])(\\.?//?)\\*@', '$1$2$1+$1*', $content);
|
||||||
|
|
||||||
|
$lines = explode("\n", $content);
|
||||||
|
$numLines = count($lines);
|
||||||
|
// determine left padding
|
||||||
|
$padTo = strlen((string) $numLines); // e.g. 103 lines = 3 digits
|
||||||
|
$inComment = false;
|
||||||
|
$i = 0;
|
||||||
|
$newLines = array();
|
||||||
|
while (null !== ($line = array_shift($lines))) {
|
||||||
|
if (('' !== $id) && (0 == $i % 50)) {
|
||||||
|
array_push($newLines, '', "/* {$id} */", '');
|
||||||
|
}
|
||||||
|
++$i;
|
||||||
|
$newLines[] = self::_addNote($line, $i, $inComment, $padTo);
|
||||||
|
$inComment = self::_eolInComment($line, $inComment);
|
||||||
|
}
|
||||||
|
$content = implode("\n", $newLines) . "\n";
|
||||||
|
|
||||||
|
// check for desired URI rewriting
|
||||||
|
if (isset($options['currentDir'])) {
|
||||||
|
require_once 'Minify/CSS/UriRewriter.php';
|
||||||
|
Minify_CSS_UriRewriter::$debugText = '';
|
||||||
|
$content = Minify_CSS_UriRewriter::rewrite(
|
||||||
|
$content
|
||||||
|
,$options['currentDir']
|
||||||
|
,isset($options['docRoot']) ? $options['docRoot'] : $_SERVER['DOCUMENT_ROOT']
|
||||||
|
,isset($options['symlinks']) ? $options['symlinks'] : array()
|
||||||
|
);
|
||||||
|
$content = "/* Minify_CSS_UriRewriter::\$debugText\n\n"
|
||||||
|
. Minify_CSS_UriRewriter::$debugText . "*/\n"
|
||||||
|
. $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the parser within a C-style comment at the end of this line?
|
||||||
|
*
|
||||||
|
* @param string $line current line of code
|
||||||
|
*
|
||||||
|
* @param bool $inComment was the parser in a comment at the
|
||||||
|
* beginning of the line?
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private static function _eolInComment($line, $inComment)
|
||||||
|
{
|
||||||
|
while (strlen($line)) {
|
||||||
|
$search = $inComment
|
||||||
|
? '*/'
|
||||||
|
: '/*';
|
||||||
|
$pos = strpos($line, $search);
|
||||||
|
if (false === $pos) {
|
||||||
|
return $inComment;
|
||||||
|
} else {
|
||||||
|
if ($pos == 0
|
||||||
|
|| ($inComment
|
||||||
|
? substr($line, $pos, 3)
|
||||||
|
: substr($line, $pos-1, 3)) != '*/*')
|
||||||
|
{
|
||||||
|
$inComment = ! $inComment;
|
||||||
|
}
|
||||||
|
$line = substr($line, $pos + 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $inComment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepend a comment (or note) to the given line
|
||||||
|
*
|
||||||
|
* @param string $line current line of code
|
||||||
|
*
|
||||||
|
* @param string $note content of note/comment
|
||||||
|
*
|
||||||
|
* @param bool $inComment was the parser in a comment at the
|
||||||
|
* beginning of the line?
|
||||||
|
*
|
||||||
|
* @param int $padTo minimum width of comment
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private static function _addNote($line, $note, $inComment, $padTo)
|
||||||
|
{
|
||||||
|
return $inComment
|
||||||
|
? '/* ' . str_pad($note, $padTo, ' ', STR_PAD_RIGHT) . ' *| ' . $line
|
||||||
|
: '/* ' . str_pad($note, $padTo, ' ', STR_PAD_RIGHT) . ' */ ' . $line;
|
||||||
|
}
|
||||||
|
}
|
47
inc/contrib/minify/Minify/Logger.php
Executable file
47
inc/contrib/minify/Minify/Logger.php
Executable file
@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Class Minify_Logger
|
||||||
|
* @package Minify
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message logging class
|
||||||
|
*
|
||||||
|
* @package Minify
|
||||||
|
* @author Stephen Clay <steve@mrclay.org>
|
||||||
|
*
|
||||||
|
* @todo lose this singleton! pass log object in Minify::serve and distribute to others
|
||||||
|
*/
|
||||||
|
class Minify_Logger {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set logger object.
|
||||||
|
*
|
||||||
|
* The object should have a method "log" that accepts a value as 1st argument and
|
||||||
|
* an optional string label as the 2nd.
|
||||||
|
*
|
||||||
|
* @param mixed $obj or a "falsey" value to disable
|
||||||
|
* @return null
|
||||||
|
*/
|
||||||
|
public static function setLogger($obj = null) {
|
||||||
|
self::$_logger = $obj
|
||||||
|
? $obj
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pass a message to the logger (if set)
|
||||||
|
*
|
||||||
|
* @param string $msg message to log
|
||||||
|
* @return null
|
||||||
|
*/
|
||||||
|
public static function log($msg, $label = 'Minify') {
|
||||||
|
if (! self::$_logger) return;
|
||||||
|
self::$_logger->log($msg, $label);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var mixed logger object (like FirePHP) or null (i.e. no logger available)
|
||||||
|
*/
|
||||||
|
private static $_logger = null;
|
||||||
|
}
|
37
inc/contrib/minify/Minify/Packer.php
Executable file
37
inc/contrib/minify/Minify/Packer.php
Executable file
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Class Minify_Packer
|
||||||
|
*
|
||||||
|
* To use this class you must first download the PHP port of Packer
|
||||||
|
* and place the file "class.JavaScriptPacker.php" in /lib (or your
|
||||||
|
* include_path).
|
||||||
|
* @link http://joliclic.free.fr/php/javascript-packer/en/
|
||||||
|
*
|
||||||
|
* Be aware that, as long as HTTP encoding is used, scripts minified with JSMin
|
||||||
|
* will provide better client-side performance, as they need not be unpacked in
|
||||||
|
* client-side code.
|
||||||
|
*
|
||||||
|
* @package Minify
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (false === (@include 'class.JavaScriptPacker.php')) {
|
||||||
|
trigger_error(
|
||||||
|
'The script "class.JavaScriptPacker.php" is required. Please see: http:'
|
||||||
|
.'//code.google.com/p/minify/source/browse/trunk/min/lib/Minify/Packer.php'
|
||||||
|
,E_USER_ERROR
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minify Javascript using Dean Edward's Packer
|
||||||
|
*
|
||||||
|
* @package Minify
|
||||||
|
*/
|
||||||
|
class Minify_Packer {
|
||||||
|
public static function minify($code, $options = array())
|
||||||
|
{
|
||||||
|
// @todo: set encoding options based on $options :)
|
||||||
|
$packer = new JavascriptPacker($code, 'Normal', true, false);
|
||||||
|
return trim($packer->pack());
|
||||||
|
}
|
||||||
|
}
|
187
inc/contrib/minify/Minify/Source.php
Executable file
187
inc/contrib/minify/Minify/Source.php
Executable file
@ -0,0 +1,187 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Class Minify_Source
|
||||||
|
* @package Minify
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A content source to be minified by Minify.
|
||||||
|
*
|
||||||
|
* This allows per-source minification options and the mixing of files with
|
||||||
|
* content from other sources.
|
||||||
|
*
|
||||||
|
* @package Minify
|
||||||
|
* @author Stephen Clay <steve@mrclay.org>
|
||||||
|
*/
|
||||||
|
class Minify_Source {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int time of last modification
|
||||||
|
*/
|
||||||
|
public $lastModified = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var callback minifier function specifically for this source.
|
||||||
|
*/
|
||||||
|
public $minifier = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array minification options specific to this source.
|
||||||
|
*/
|
||||||
|
public $minifyOptions = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string full path of file
|
||||||
|
*/
|
||||||
|
public $filepath = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string HTTP Content Type (Minify requires one of the constants Minify::TYPE_*)
|
||||||
|
*/
|
||||||
|
public $contentType = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a Minify_Source
|
||||||
|
*
|
||||||
|
* In the $spec array(), you can either provide a 'filepath' to an existing
|
||||||
|
* file (existence will not be checked!) or give 'id' (unique string for
|
||||||
|
* the content), 'content' (the string content) and 'lastModified'
|
||||||
|
* (unixtime of last update).
|
||||||
|
*
|
||||||
|
* As a shortcut, the controller will replace "//" at the beginning
|
||||||
|
* of a filepath with $_SERVER['DOCUMENT_ROOT'] . '/'.
|
||||||
|
*
|
||||||
|
* @param array $spec options
|
||||||
|
*/
|
||||||
|
public function __construct($spec)
|
||||||
|
{
|
||||||
|
if (isset($spec['filepath'])) {
|
||||||
|
if (0 === strpos($spec['filepath'], '//')) {
|
||||||
|
$spec['filepath'] = $_SERVER['DOCUMENT_ROOT'] . substr($spec['filepath'], 1);
|
||||||
|
}
|
||||||
|
$segments = explode('.', $spec['filepath']);
|
||||||
|
$ext = strtolower(array_pop($segments));
|
||||||
|
switch ($ext) {
|
||||||
|
case 'js' : $this->contentType = 'application/x-javascript';
|
||||||
|
break;
|
||||||
|
case 'css' : $this->contentType = 'text/css';
|
||||||
|
break;
|
||||||
|
case 'htm' : // fallthrough
|
||||||
|
case 'html' : $this->contentType = 'text/html';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$this->filepath = $spec['filepath'];
|
||||||
|
$this->_id = $spec['filepath'];
|
||||||
|
$this->lastModified = filemtime($spec['filepath'])
|
||||||
|
// offset for Windows uploaders with out of sync clocks
|
||||||
|
+ round(Minify::$uploaderHoursBehind * 3600);
|
||||||
|
} elseif (isset($spec['id'])) {
|
||||||
|
$this->_id = 'id::' . $spec['id'];
|
||||||
|
if (isset($spec['content'])) {
|
||||||
|
$this->_content = $spec['content'];
|
||||||
|
} else {
|
||||||
|
$this->_getContentFunc = $spec['getContentFunc'];
|
||||||
|
}
|
||||||
|
$this->lastModified = isset($spec['lastModified'])
|
||||||
|
? $spec['lastModified']
|
||||||
|
: time();
|
||||||
|
}
|
||||||
|
if (isset($spec['contentType'])) {
|
||||||
|
$this->contentType = $spec['contentType'];
|
||||||
|
}
|
||||||
|
if (isset($spec['minifier'])) {
|
||||||
|
$this->minifier = $spec['minifier'];
|
||||||
|
}
|
||||||
|
if (isset($spec['minifyOptions'])) {
|
||||||
|
$this->minifyOptions = $spec['minifyOptions'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get content
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getContent()
|
||||||
|
{
|
||||||
|
$content = (null !== $this->filepath)
|
||||||
|
? file_get_contents($this->filepath)
|
||||||
|
: ((null !== $this->_content)
|
||||||
|
? $this->_content
|
||||||
|
: call_user_func($this->_getContentFunc, $this->_id)
|
||||||
|
);
|
||||||
|
// remove UTF-8 BOM if present
|
||||||
|
return (pack("CCC",0xef,0xbb,0xbf) === substr($content, 0, 3))
|
||||||
|
? substr($content, 3)
|
||||||
|
: $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get id
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getId()
|
||||||
|
{
|
||||||
|
return $this->_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies a single minification call can handle all sources
|
||||||
|
*
|
||||||
|
* @param array $sources Minify_Source instances
|
||||||
|
*
|
||||||
|
* @return bool true iff there no sources with specific minifier preferences.
|
||||||
|
*/
|
||||||
|
public static function haveNoMinifyPrefs($sources)
|
||||||
|
{
|
||||||
|
foreach ($sources as $source) {
|
||||||
|
if (null !== $source->minifier
|
||||||
|
|| null !== $source->minifyOptions) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get unique string for a set of sources
|
||||||
|
*
|
||||||
|
* @param array $sources Minify_Source instances
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function getDigest($sources)
|
||||||
|
{
|
||||||
|
foreach ($sources as $source) {
|
||||||
|
$info[] = array(
|
||||||
|
$source->_id, $source->minifier, $source->minifyOptions
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return md5(serialize($info));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get content type from a group of sources
|
||||||
|
*
|
||||||
|
* This is called if the user doesn't pass in a 'contentType' options
|
||||||
|
*
|
||||||
|
* @param array $sources Minify_Source instances
|
||||||
|
*
|
||||||
|
* @return string content type. e.g. 'text/css'
|
||||||
|
*/
|
||||||
|
public static function getContentType($sources)
|
||||||
|
{
|
||||||
|
foreach ($sources as $source) {
|
||||||
|
if ($source->contentType !== null) {
|
||||||
|
return $source->contentType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 'text/plain';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $_content = null;
|
||||||
|
protected $_getContentFunc = null;
|
||||||
|
protected $_id = null;
|
||||||
|
}
|
||||||
|
|
382
inc/contrib/minify/Minify/YUI/CssCompressor.java
Executable file
382
inc/contrib/minify/Minify/YUI/CssCompressor.java
Executable file
@ -0,0 +1,382 @@
|
|||||||
|
/*
|
||||||
|
* YUI Compressor
|
||||||
|
* http://developer.yahoo.com/yui/compressor/
|
||||||
|
* Author: Julien Lecomte - http://www.julienlecomte.net/
|
||||||
|
* Author: Isaac Schlueter - http://foohack.com/
|
||||||
|
* Author: Stoyan Stefanov - http://phpied.com/
|
||||||
|
* Copyright (c) 2011 Yahoo! Inc. All rights reserved.
|
||||||
|
* The copyrights embodied in the content of this file are licensed
|
||||||
|
* by Yahoo! Inc. under the BSD (revised) open source license.
|
||||||
|
*/
|
||||||
|
package com.yahoo.platform.yui.compressor;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class CssCompressor {
|
||||||
|
|
||||||
|
private StringBuffer srcsb = new StringBuffer();
|
||||||
|
|
||||||
|
public CssCompressor(Reader in) throws IOException {
|
||||||
|
// Read the stream...
|
||||||
|
int c;
|
||||||
|
while ((c = in.read()) != -1) {
|
||||||
|
srcsb.append((char) c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Leave data urls alone to increase parse performance.
|
||||||
|
protected String extractDataUrls(String css, ArrayList preservedTokens) {
|
||||||
|
|
||||||
|
int maxIndex = css.length() - 1;
|
||||||
|
int appendIndex = 0;
|
||||||
|
|
||||||
|
StringBuffer sb = new StringBuffer();
|
||||||
|
|
||||||
|
Pattern p = Pattern.compile("url\\(\\s*([\"']?)data\\:");
|
||||||
|
Matcher m = p.matcher(css);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Since we need to account for non-base64 data urls, we need to handle
|
||||||
|
* ' and ) being part of the data string. Hence switching to indexOf,
|
||||||
|
* to determine whether or not we have matching string terminators and
|
||||||
|
* handling sb appends directly, instead of using matcher.append* methods.
|
||||||
|
*/
|
||||||
|
|
||||||
|
while (m.find()) {
|
||||||
|
|
||||||
|
int startIndex = m.start() + 4; // "url(".length()
|
||||||
|
String terminator = m.group(1); // ', " or empty (not quoted)
|
||||||
|
|
||||||
|
if (terminator.length() == 0) {
|
||||||
|
terminator = ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean foundTerminator = false;
|
||||||
|
|
||||||
|
int endIndex = m.end() - 1;
|
||||||
|
while(foundTerminator == false && endIndex+1 <= maxIndex) {
|
||||||
|
endIndex = css.indexOf(terminator, endIndex+1);
|
||||||
|
|
||||||
|
if ((endIndex > 0) && (css.charAt(endIndex-1) != '\\')) {
|
||||||
|
foundTerminator = true;
|
||||||
|
if (!")".equals(terminator)) {
|
||||||
|
endIndex = css.indexOf(")", endIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enough searching, start moving stuff over to the buffer
|
||||||
|
sb.append(css.substring(appendIndex, m.start()));
|
||||||
|
|
||||||
|
if (foundTerminator) {
|
||||||
|
String token = css.substring(startIndex, endIndex);
|
||||||
|
token = token.replaceAll("\\s+", "");
|
||||||
|
preservedTokens.add(token);
|
||||||
|
|
||||||
|
String preserver = "url(___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___)";
|
||||||
|
sb.append(preserver);
|
||||||
|
|
||||||
|
appendIndex = endIndex + 1;
|
||||||
|
} else {
|
||||||
|
// No end terminator found, re-add the whole match. Should we throw/warn here?
|
||||||
|
sb.append(css.substring(m.start(), m.end()));
|
||||||
|
appendIndex = m.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append(css.substring(appendIndex));
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void compress(Writer out, int linebreakpos)
|
||||||
|
throws IOException {
|
||||||
|
|
||||||
|
Pattern p;
|
||||||
|
Matcher m;
|
||||||
|
String css = srcsb.toString();
|
||||||
|
|
||||||
|
int startIndex = 0;
|
||||||
|
int endIndex = 0;
|
||||||
|
int i = 0;
|
||||||
|
int max = 0;
|
||||||
|
ArrayList preservedTokens = new ArrayList(0);
|
||||||
|
ArrayList comments = new ArrayList(0);
|
||||||
|
String token;
|
||||||
|
int totallen = css.length();
|
||||||
|
String placeholder;
|
||||||
|
|
||||||
|
css = this.extractDataUrls(css, preservedTokens);
|
||||||
|
|
||||||
|
StringBuffer sb = new StringBuffer(css);
|
||||||
|
|
||||||
|
// collect all comment blocks...
|
||||||
|
while ((startIndex = sb.indexOf("/*", startIndex)) >= 0) {
|
||||||
|
endIndex = sb.indexOf("*/", startIndex + 2);
|
||||||
|
if (endIndex < 0) {
|
||||||
|
endIndex = totallen;
|
||||||
|
}
|
||||||
|
|
||||||
|
token = sb.substring(startIndex + 2, endIndex);
|
||||||
|
comments.add(token);
|
||||||
|
sb.replace(startIndex + 2, endIndex, "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + (comments.size() - 1) + "___");
|
||||||
|
startIndex += 2;
|
||||||
|
}
|
||||||
|
css = sb.toString();
|
||||||
|
|
||||||
|
// preserve strings so their content doesn't get accidentally minified
|
||||||
|
sb = new StringBuffer();
|
||||||
|
p = Pattern.compile("(\"([^\\\\\"]|\\\\.|\\\\)*\")|(\'([^\\\\\']|\\\\.|\\\\)*\')");
|
||||||
|
m = p.matcher(css);
|
||||||
|
while (m.find()) {
|
||||||
|
token = m.group();
|
||||||
|
char quote = token.charAt(0);
|
||||||
|
token = token.substring(1, token.length() - 1);
|
||||||
|
|
||||||
|
// maybe the string contains a comment-like substring?
|
||||||
|
// one, maybe more? put'em back then
|
||||||
|
if (token.indexOf("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_") >= 0) {
|
||||||
|
for (i = 0, max = comments.size(); i < max; i += 1) {
|
||||||
|
token = token.replace("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___", comments.get(i).toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// minify alpha opacity in filter strings
|
||||||
|
token = token.replaceAll("(?i)progid:DXImageTransform.Microsoft.Alpha\\(Opacity=", "alpha(opacity=");
|
||||||
|
|
||||||
|
preservedTokens.add(token);
|
||||||
|
String preserver = quote + "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___" + quote;
|
||||||
|
m.appendReplacement(sb, preserver);
|
||||||
|
}
|
||||||
|
m.appendTail(sb);
|
||||||
|
css = sb.toString();
|
||||||
|
|
||||||
|
|
||||||
|
// strings are safe, now wrestle the comments
|
||||||
|
for (i = 0, max = comments.size(); i < max; i += 1) {
|
||||||
|
|
||||||
|
token = comments.get(i).toString();
|
||||||
|
placeholder = "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___";
|
||||||
|
|
||||||
|
// ! in the first position of the comment means preserve
|
||||||
|
// so push to the preserved tokens while stripping the !
|
||||||
|
if (token.startsWith("!")) {
|
||||||
|
preservedTokens.add(token);
|
||||||
|
css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// \ in the last position looks like hack for Mac/IE5
|
||||||
|
// shorten that to /*\*/ and the next one to /**/
|
||||||
|
if (token.endsWith("\\")) {
|
||||||
|
preservedTokens.add("\\");
|
||||||
|
css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___");
|
||||||
|
i = i + 1; // attn: advancing the loop
|
||||||
|
preservedTokens.add("");
|
||||||
|
css = css.replace("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___", "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep empty comments after child selectors (IE7 hack)
|
||||||
|
// e.g. html >/**/ body
|
||||||
|
if (token.length() == 0) {
|
||||||
|
startIndex = css.indexOf(placeholder);
|
||||||
|
if (startIndex > 2) {
|
||||||
|
if (css.charAt(startIndex - 3) == '>') {
|
||||||
|
preservedTokens.add("");
|
||||||
|
css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// in all other cases kill the comment
|
||||||
|
css = css.replace("/*" + placeholder + "*/", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Normalize all whitespace strings to single spaces. Easier to work with that way.
|
||||||
|
css = css.replaceAll("\\s+", " ");
|
||||||
|
|
||||||
|
// Remove the spaces before the things that should not have spaces before them.
|
||||||
|
// But, be careful not to turn "p :link {...}" into "p:link{...}"
|
||||||
|
// Swap out any pseudo-class colons with the token, and then swap back.
|
||||||
|
sb = new StringBuffer();
|
||||||
|
p = Pattern.compile("(^|\\})(([^\\{:])+:)+([^\\{]*\\{)");
|
||||||
|
m = p.matcher(css);
|
||||||
|
while (m.find()) {
|
||||||
|
String s = m.group();
|
||||||
|
s = s.replaceAll(":", "___YUICSSMIN_PSEUDOCLASSCOLON___");
|
||||||
|
s = s.replaceAll( "\\\\", "\\\\\\\\" ).replaceAll( "\\$", "\\\\\\$" );
|
||||||
|
m.appendReplacement(sb, s);
|
||||||
|
}
|
||||||
|
m.appendTail(sb);
|
||||||
|
css = sb.toString();
|
||||||
|
// Remove spaces before the things that should not have spaces before them.
|
||||||
|
css = css.replaceAll("\\s+([!{};:>+\\(\\)\\],])", "$1");
|
||||||
|
// bring back the colon
|
||||||
|
css = css.replaceAll("___YUICSSMIN_PSEUDOCLASSCOLON___", ":");
|
||||||
|
|
||||||
|
// retain space for special IE6 cases
|
||||||
|
css = css.replaceAll(":first\\-(line|letter)(\\{|,)", ":first-$1 $2");
|
||||||
|
|
||||||
|
// no space after the end of a preserved comment
|
||||||
|
css = css.replaceAll("\\*/ ", "*/");
|
||||||
|
|
||||||
|
// If there is a @charset, then only allow one, and push to the top of the file.
|
||||||
|
css = css.replaceAll("^(.*)(@charset \"[^\"]*\";)", "$2$1");
|
||||||
|
css = css.replaceAll("^(\\s*@charset [^;]+;\\s*)+", "$1");
|
||||||
|
|
||||||
|
// Put the space back in some cases, to support stuff like
|
||||||
|
// @media screen and (-webkit-min-device-pixel-ratio:0){
|
||||||
|
css = css.replaceAll("\\band\\(", "and (");
|
||||||
|
|
||||||
|
// Remove the spaces after the things that should not have spaces after them.
|
||||||
|
css = css.replaceAll("([!{}:;>+\\(\\[,])\\s+", "$1");
|
||||||
|
|
||||||
|
// remove unnecessary semicolons
|
||||||
|
css = css.replaceAll(";+}", "}");
|
||||||
|
|
||||||
|
// Replace 0(px,em,%) with 0.
|
||||||
|
css = css.replaceAll("([\\s:])(0)(px|em|%|in|cm|mm|pc|pt|ex)", "$1$2");
|
||||||
|
|
||||||
|
// Replace 0 0 0 0; with 0.
|
||||||
|
css = css.replaceAll(":0 0 0 0(;|})", ":0$1");
|
||||||
|
css = css.replaceAll(":0 0 0(;|})", ":0$1");
|
||||||
|
css = css.replaceAll(":0 0(;|})", ":0$1");
|
||||||
|
|
||||||
|
|
||||||
|
// Replace background-position:0; with background-position:0 0;
|
||||||
|
// same for transform-origin
|
||||||
|
sb = new StringBuffer();
|
||||||
|
p = Pattern.compile("(?i)(background-position|transform-origin|webkit-transform-origin|moz-transform-origin|o-transform-origin|ms-transform-origin):0(;|})");
|
||||||
|
m = p.matcher(css);
|
||||||
|
while (m.find()) {
|
||||||
|
m.appendReplacement(sb, m.group(1).toLowerCase() + ":0 0" + m.group(2));
|
||||||
|
}
|
||||||
|
m.appendTail(sb);
|
||||||
|
css = sb.toString();
|
||||||
|
|
||||||
|
// Replace 0.6 to .6, but only when preceded by : or a white-space
|
||||||
|
css = css.replaceAll("(:|\\s)0+\\.(\\d+)", "$1.$2");
|
||||||
|
|
||||||
|
// Shorten colors from rgb(51,102,153) to #336699
|
||||||
|
// This makes it more likely that it'll get further compressed in the next step.
|
||||||
|
p = Pattern.compile("rgb\\s*\\(\\s*([0-9,\\s]+)\\s*\\)");
|
||||||
|
m = p.matcher(css);
|
||||||
|
sb = new StringBuffer();
|
||||||
|
while (m.find()) {
|
||||||
|
String[] rgbcolors = m.group(1).split(",");
|
||||||
|
StringBuffer hexcolor = new StringBuffer("#");
|
||||||
|
for (i = 0; i < rgbcolors.length; i++) {
|
||||||
|
int val = Integer.parseInt(rgbcolors[i]);
|
||||||
|
if (val < 16) {
|
||||||
|
hexcolor.append("0");
|
||||||
|
}
|
||||||
|
hexcolor.append(Integer.toHexString(val));
|
||||||
|
}
|
||||||
|
m.appendReplacement(sb, hexcolor.toString());
|
||||||
|
}
|
||||||
|
m.appendTail(sb);
|
||||||
|
css = sb.toString();
|
||||||
|
|
||||||
|
// Shorten colors from #AABBCC to #ABC. Note that we want to make sure
|
||||||
|
// the color is not preceded by either ", " or =. Indeed, the property
|
||||||
|
// filter: chroma(color="#FFFFFF");
|
||||||
|
// would become
|
||||||
|
// filter: chroma(color="#FFF");
|
||||||
|
// which makes the filter break in IE.
|
||||||
|
// We also want to make sure we're only compressing #AABBCC patterns inside { }, not id selectors ( #FAABAC {} )
|
||||||
|
// We also want to avoid compressing invalid values (e.g. #AABBCCD to #ABCD)
|
||||||
|
p = Pattern.compile("(\\=\\s*?[\"']?)?" + "#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])" + "(:?\\}|[^0-9a-fA-F{][^{]*?\\})");
|
||||||
|
|
||||||
|
m = p.matcher(css);
|
||||||
|
sb = new StringBuffer();
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
|
while (m.find(index)) {
|
||||||
|
|
||||||
|
sb.append(css.substring(index, m.start()));
|
||||||
|
|
||||||
|
boolean isFilter = (m.group(1) != null && !"".equals(m.group(1)));
|
||||||
|
|
||||||
|
if (isFilter) {
|
||||||
|
// Restore, as is. Compression will break filters
|
||||||
|
sb.append(m.group(1) + "#" + m.group(2) + m.group(3) + m.group(4) + m.group(5) + m.group(6) + m.group(7));
|
||||||
|
} else {
|
||||||
|
if( m.group(2).equalsIgnoreCase(m.group(3)) &&
|
||||||
|
m.group(4).equalsIgnoreCase(m.group(5)) &&
|
||||||
|
m.group(6).equalsIgnoreCase(m.group(7))) {
|
||||||
|
|
||||||
|
// #AABBCC pattern
|
||||||
|
sb.append("#" + (m.group(3) + m.group(5) + m.group(7)).toLowerCase());
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Non-compressible color, restore, but lower case.
|
||||||
|
sb.append("#" + (m.group(2) + m.group(3) + m.group(4) + m.group(5) + m.group(6) + m.group(7)).toLowerCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
index = m.end(7);
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append(css.substring(index));
|
||||||
|
css = sb.toString();
|
||||||
|
|
||||||
|
// border: none -> border:0
|
||||||
|
sb = new StringBuffer();
|
||||||
|
p = Pattern.compile("(?i)(border|border-top|border-right|border-bottom|border-right|outline|background):none(;|})");
|
||||||
|
m = p.matcher(css);
|
||||||
|
while (m.find()) {
|
||||||
|
m.appendReplacement(sb, m.group(1).toLowerCase() + ":0" + m.group(2));
|
||||||
|
}
|
||||||
|
m.appendTail(sb);
|
||||||
|
css = sb.toString();
|
||||||
|
|
||||||
|
// shorter opacity IE filter
|
||||||
|
css = css.replaceAll("(?i)progid:DXImageTransform.Microsoft.Alpha\\(Opacity=", "alpha(opacity=");
|
||||||
|
|
||||||
|
// Remove empty rules.
|
||||||
|
css = css.replaceAll("[^\\}\\{/;]+\\{\\}", "");
|
||||||
|
|
||||||
|
// TODO: Should this be after we re-insert tokens. These could alter the break points. However then
|
||||||
|
// we'd need to make sure we don't break in the middle of a string etc.
|
||||||
|
if (linebreakpos >= 0) {
|
||||||
|
// Some source control tools don't like it when files containing lines longer
|
||||||
|
// than, say 8000 characters, are checked in. The linebreak option is used in
|
||||||
|
// that case to split long lines after a specific column.
|
||||||
|
i = 0;
|
||||||
|
int linestartpos = 0;
|
||||||
|
sb = new StringBuffer(css);
|
||||||
|
while (i < sb.length()) {
|
||||||
|
char c = sb.charAt(i++);
|
||||||
|
if (c == '}' && i - linestartpos > linebreakpos) {
|
||||||
|
sb.insert(i, '\n');
|
||||||
|
linestartpos = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
css = sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace multiple semi-colons in a row by a single one
|
||||||
|
// See SF bug #1980989
|
||||||
|
css = css.replaceAll(";;+", ";");
|
||||||
|
|
||||||
|
// restore preserved comments and strings
|
||||||
|
for(i = 0, max = preservedTokens.size(); i < max; i++) {
|
||||||
|
css = css.replace("___YUICSSMIN_PRESERVED_TOKEN_" + i + "___", preservedTokens.get(i).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim the final string (for any leading or trailing white spaces)
|
||||||
|
css = css.trim();
|
||||||
|
|
||||||
|
// Write the output...
|
||||||
|
out.write(css);
|
||||||
|
}
|
||||||
|
}
|
171
inc/contrib/minify/Minify/YUI/CssCompressor.php
Executable file
171
inc/contrib/minify/Minify/YUI/CssCompressor.php
Executable file
@ -0,0 +1,171 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Class Minify_YUI_CssCompressor
|
||||||
|
* @package Minify
|
||||||
|
*
|
||||||
|
* YUI Compressor
|
||||||
|
* Author: Julien Lecomte - http://www.julienlecomte.net/
|
||||||
|
* Author: Isaac Schlueter - http://foohack.com/
|
||||||
|
* Author: Stoyan Stefanov - http://phpied.com/
|
||||||
|
* Author: Steve Clay - http://www.mrclay.org/ (PHP port)
|
||||||
|
* Copyright (c) 2009 Yahoo! Inc. All rights reserved.
|
||||||
|
* The copyrights embodied in the content of this file are licensed
|
||||||
|
* by Yahoo! Inc. under the BSD (revised) open source license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compress CSS (incomplete DO NOT USE)
|
||||||
|
*
|
||||||
|
* @see https://github.com/yui/yuicompressor/blob/master/src/com/yahoo/platform/yui/compressor/CssCompressor.java
|
||||||
|
*
|
||||||
|
* @package Minify
|
||||||
|
*/
|
||||||
|
class Minify_YUI_CssCompressor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minify a CSS string
|
||||||
|
*
|
||||||
|
* @param string $css
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function compress($css, $linebreakpos = 0)
|
||||||
|
{
|
||||||
|
$css = str_replace("\r\n", "\n", $css);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo comment removal
|
||||||
|
* @todo re-port from newer Java version
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Normalize all whitespace strings to single spaces. Easier to work with that way.
|
||||||
|
$css = preg_replace('@\s+@', ' ', $css);
|
||||||
|
|
||||||
|
// Make a pseudo class for the Box Model Hack
|
||||||
|
$css = preg_replace("@\"\\\\\"}\\\\\"\"@", "___PSEUDOCLASSBMH___", $css);
|
||||||
|
|
||||||
|
// Remove the spaces before the things that should not have spaces before them.
|
||||||
|
// But, be careful not to turn "p :link {...}" into "p:link{...}"
|
||||||
|
// Swap out any pseudo-class colons with the token, and then swap back.
|
||||||
|
$css = preg_replace_callback("@(^|\\})(([^\\{:])+:)+([^\\{]*\\{)@", array($this, '_removeSpacesCB'), $css);
|
||||||
|
|
||||||
|
$css = preg_replace("@\\s+([!{};:>+\\(\\)\\],])@", "$1", $css);
|
||||||
|
$css = str_replace("___PSEUDOCLASSCOLON___", ":", $css);
|
||||||
|
|
||||||
|
// Remove the spaces after the things that should not have spaces after them.
|
||||||
|
$css = preg_replace("@([!{}:;>+\\(\\[,])\\s+@", "$1", $css);
|
||||||
|
|
||||||
|
// Add the semicolon where it's missing.
|
||||||
|
$css = preg_replace("@([^;\\}])}@", "$1;}", $css);
|
||||||
|
|
||||||
|
// Replace 0(px,em,%) with 0.
|
||||||
|
$css = preg_replace("@([\\s:])(0)(px|em|%|in|cm|mm|pc|pt|ex)@", "$1$2", $css);
|
||||||
|
|
||||||
|
// Replace 0 0 0 0; with 0.
|
||||||
|
$css = str_replace(":0 0 0 0;", ":0;", $css);
|
||||||
|
$css = str_replace(":0 0 0;", ":0;", $css);
|
||||||
|
$css = str_replace(":0 0;", ":0;", $css);
|
||||||
|
|
||||||
|
// Replace background-position:0; with background-position:0 0;
|
||||||
|
$css = str_replace("background-position:0;", "background-position:0 0;", $css);
|
||||||
|
|
||||||
|
// Replace 0.6 to .6, but only when preceded by : or a white-space
|
||||||
|
$css = preg_replace("@(:|\\s)0+\\.(\\d+)@", "$1.$2", $css);
|
||||||
|
|
||||||
|
// Shorten colors from rgb(51,102,153) to #336699
|
||||||
|
// This makes it more likely that it'll get further compressed in the next step.
|
||||||
|
$css = preg_replace_callback("@rgb\\s*\\(\\s*([0-9,\\s]+)\\s*\\)@", array($this, '_shortenRgbCB'), $css);
|
||||||
|
|
||||||
|
// Shorten colors from #AABBCC to #ABC. Note that we want to make sure
|
||||||
|
// the color is not preceded by either ", " or =. Indeed, the property
|
||||||
|
// filter: chroma(color="#FFFFFF");
|
||||||
|
// would become
|
||||||
|
// filter: chroma(color="#FFF");
|
||||||
|
// which makes the filter break in IE.
|
||||||
|
$css = preg_replace_callback("@([^\"'=\\s])(\\s*)#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])@", array($this, '_shortenHexCB'), $css);
|
||||||
|
|
||||||
|
// Remove empty rules.
|
||||||
|
$css = preg_replace("@[^\\}]+\\{;\\}@", "", $css);
|
||||||
|
|
||||||
|
$linebreakpos = isset($this->_options['linebreakpos'])
|
||||||
|
? $this->_options['linebreakpos']
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
if ($linebreakpos > 0) {
|
||||||
|
// Some source control tools don't like it when files containing lines longer
|
||||||
|
// than, say 8000 characters, are checked in. The linebreak option is used in
|
||||||
|
// that case to split long lines after a specific column.
|
||||||
|
$i = 0;
|
||||||
|
$linestartpos = 0;
|
||||||
|
$sb = $css;
|
||||||
|
|
||||||
|
// make sure strlen returns byte count
|
||||||
|
$mbIntEnc = null;
|
||||||
|
if (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) {
|
||||||
|
$mbIntEnc = mb_internal_encoding();
|
||||||
|
mb_internal_encoding('8bit');
|
||||||
|
}
|
||||||
|
$sbLength = strlen($css);
|
||||||
|
while ($i < $sbLength) {
|
||||||
|
$c = $sb[$i++];
|
||||||
|
if ($c === '}' && $i - $linestartpos > $linebreakpos) {
|
||||||
|
$sb = substr_replace($sb, "\n", $i, 0);
|
||||||
|
$sbLength++;
|
||||||
|
$linestartpos = $i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$css = $sb;
|
||||||
|
|
||||||
|
// undo potential mb_encoding change
|
||||||
|
if ($mbIntEnc !== null) {
|
||||||
|
mb_internal_encoding($mbIntEnc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace the pseudo class for the Box Model Hack
|
||||||
|
$css = str_replace("___PSEUDOCLASSBMH___", "\"\\\\\"}\\\\\"\"", $css);
|
||||||
|
|
||||||
|
// Replace multiple semi-colons in a row by a single one
|
||||||
|
// See SF bug #1980989
|
||||||
|
$css = preg_replace("@;;+@", ";", $css);
|
||||||
|
|
||||||
|
// prevent triggering IE6 bug: http://www.crankygeek.com/ie6pebug/
|
||||||
|
$css = preg_replace('/:first-l(etter|ine)\\{/', ':first-l$1 {', $css);
|
||||||
|
|
||||||
|
// Trim the final string (for any leading or trailing white spaces)
|
||||||
|
$css = trim($css);
|
||||||
|
|
||||||
|
return $css;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _removeSpacesCB($m)
|
||||||
|
{
|
||||||
|
return str_replace(':', '___PSEUDOCLASSCOLON___', $m[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _shortenRgbCB($m)
|
||||||
|
{
|
||||||
|
$rgbcolors = explode(',', $m[1]);
|
||||||
|
$hexcolor = '#';
|
||||||
|
for ($i = 0; $i < count($rgbcolors); $i++) {
|
||||||
|
$val = round($rgbcolors[$i]);
|
||||||
|
if ($val < 16) {
|
||||||
|
$hexcolor .= '0';
|
||||||
|
}
|
||||||
|
$hexcolor .= dechex($val);
|
||||||
|
}
|
||||||
|
return $hexcolor;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _shortenHexCB($m)
|
||||||
|
{
|
||||||
|
// Test for AABBCC pattern
|
||||||
|
if ((strtolower($m[3])===strtolower($m[4])) &&
|
||||||
|
(strtolower($m[5])===strtolower($m[6])) &&
|
||||||
|
(strtolower($m[7])===strtolower($m[8]))) {
|
||||||
|
return $m[1] . $m[2] . "#" . $m[3] . $m[5] . $m[7];
|
||||||
|
} else {
|
||||||
|
return $m[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
148
inc/contrib/minify/Minify/YUICompressor.php
Executable file
148
inc/contrib/minify/Minify/YUICompressor.php
Executable file
@ -0,0 +1,148 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Class Minify_YUICompressor
|
||||||
|
* @package Minify
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compress Javascript/CSS using the YUI Compressor
|
||||||
|
*
|
||||||
|
* You must set $jarFile and $tempDir before calling the minify functions.
|
||||||
|
* Also, depending on your shell's environment, you may need to specify
|
||||||
|
* the full path to java in $javaExecutable or use putenv() to setup the
|
||||||
|
* Java environment.
|
||||||
|
*
|
||||||
|
* <code>
|
||||||
|
* Minify_YUICompressor::$jarFile = '/path/to/yuicompressor-2.3.5.jar';
|
||||||
|
* Minify_YUICompressor::$tempDir = '/tmp';
|
||||||
|
* $code = Minify_YUICompressor::minifyJs(
|
||||||
|
* $code
|
||||||
|
* ,array('nomunge' => true, 'line-break' => 1000)
|
||||||
|
* );
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
|
* @todo unit tests, $options docs
|
||||||
|
*
|
||||||
|
* @package Minify
|
||||||
|
* @author Stephen Clay <steve@mrclay.org>
|
||||||
|
*/
|
||||||
|
class Minify_YUICompressor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filepath of the YUI Compressor jar file. This must be set before
|
||||||
|
* calling minifyJs() or minifyCss().
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public static $jarFile = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writable temp directory. This must be set before calling minifyJs()
|
||||||
|
* or minifyCss().
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public static $tempDir = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filepath of "java" executable (may be needed if not in shell's PATH)
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public static $javaExecutable = 'java';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minify a Javascript string
|
||||||
|
*
|
||||||
|
* @param string $js
|
||||||
|
*
|
||||||
|
* @param array $options (verbose is ignored)
|
||||||
|
*
|
||||||
|
* @see http://www.julienlecomte.net/yuicompressor/README
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function minifyJs($js, $options = array())
|
||||||
|
{
|
||||||
|
return self::_minify('js', $js, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minify a CSS string
|
||||||
|
*
|
||||||
|
* @param string $css
|
||||||
|
*
|
||||||
|
* @param array $options (verbose is ignored)
|
||||||
|
*
|
||||||
|
* @see http://www.julienlecomte.net/yuicompressor/README
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function minifyCss($css, $options = array())
|
||||||
|
{
|
||||||
|
return self::_minify('css', $css, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function _minify($type, $content, $options)
|
||||||
|
{
|
||||||
|
self::_prepare();
|
||||||
|
if (! ($tmpFile = tempnam(self::$tempDir, 'yuic_'))) {
|
||||||
|
throw new Exception('Minify_YUICompressor : could not create temp file.');
|
||||||
|
}
|
||||||
|
file_put_contents($tmpFile, $content);
|
||||||
|
exec(self::_getCmd($options, $type, $tmpFile), $output, $result_code);
|
||||||
|
unlink($tmpFile);
|
||||||
|
if ($result_code != 0) {
|
||||||
|
throw new Exception('Minify_YUICompressor : YUI compressor execution failed.');
|
||||||
|
}
|
||||||
|
return implode("\n", $output);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function _getCmd($userOptions, $type, $tmpFile)
|
||||||
|
{
|
||||||
|
$o = array_merge(
|
||||||
|
array(
|
||||||
|
'charset' => ''
|
||||||
|
,'line-break' => 5000
|
||||||
|
,'type' => $type
|
||||||
|
,'nomunge' => false
|
||||||
|
,'preserve-semi' => false
|
||||||
|
,'disable-optimizations' => false
|
||||||
|
)
|
||||||
|
,$userOptions
|
||||||
|
);
|
||||||
|
$cmd = self::$javaExecutable . ' -jar ' . escapeshellarg(self::$jarFile)
|
||||||
|
. " --type {$type}"
|
||||||
|
. (preg_match('/^[\\da-zA-Z0-9\\-]+$/', $o['charset'])
|
||||||
|
? " --charset {$o['charset']}"
|
||||||
|
: '')
|
||||||
|
. (is_numeric($o['line-break']) && $o['line-break'] >= 0
|
||||||
|
? ' --line-break ' . (int)$o['line-break']
|
||||||
|
: '');
|
||||||
|
if ($type === 'js') {
|
||||||
|
foreach (array('nomunge', 'preserve-semi', 'disable-optimizations') as $opt) {
|
||||||
|
$cmd .= $o[$opt]
|
||||||
|
? " --{$opt}"
|
||||||
|
: '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $cmd . ' ' . escapeshellarg($tmpFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function _prepare()
|
||||||
|
{
|
||||||
|
if (! is_file(self::$jarFile)) {
|
||||||
|
throw new Exception('Minify_YUICompressor : $jarFile('.self::$jarFile.') is not a valid file.');
|
||||||
|
}
|
||||||
|
if (! is_executable(self::$jarFile)) {
|
||||||
|
throw new Exception('Minify_YUICompressor : $jarFile('.self::$jarFile.') is not executable.');
|
||||||
|
}
|
||||||
|
if (! is_dir(self::$tempDir)) {
|
||||||
|
throw new Exception('Minify_YUICompressor : $tempDir('.self::$tempDir.') is not a valid direcotry.');
|
||||||
|
}
|
||||||
|
if (! is_writable(self::$tempDir)) {
|
||||||
|
throw new Exception('Minify_YUICompressor : $tempDir('.self::$tempDir.') is not writable.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
381
inc/contrib/minify/MrClay/Cli.php
Executable file
381
inc/contrib/minify/MrClay/Cli.php
Executable file
@ -0,0 +1,381 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace MrClay;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forms a front controller for a console app, handling and validating arguments (options)
|
||||||
|
*
|
||||||
|
* Instantiate, add arguments, then call validate(). Afterwards, the user's valid arguments
|
||||||
|
* and their values will be available in $cli->values.
|
||||||
|
*
|
||||||
|
* You may also specify that some arguments be used to provide input/output. By communicating
|
||||||
|
* solely through the file pointers provided by openInput()/openOutput(), you can make your
|
||||||
|
* app more flexible to end users.
|
||||||
|
*
|
||||||
|
* @author Steve Clay <steve@mrclay.org>
|
||||||
|
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||||
|
*/
|
||||||
|
class Cli {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array validation errors
|
||||||
|
*/
|
||||||
|
public $errors = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array option values available after validation.
|
||||||
|
*
|
||||||
|
* E.g. array(
|
||||||
|
* 'a' => false // option was missing
|
||||||
|
* ,'b' => true // option was present
|
||||||
|
* ,'c' => "Hello" // option had value
|
||||||
|
* ,'f' => "/home/user/file" // file path from root
|
||||||
|
* ,'f.raw' => "~/file" // file path as given to option
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
public $values = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public $moreArgs = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public $debug = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool The user wants help info
|
||||||
|
*/
|
||||||
|
public $isHelpRequest = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array of Cli\Arg
|
||||||
|
*/
|
||||||
|
protected $_args = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var resource
|
||||||
|
*/
|
||||||
|
protected $_stdin = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var resource
|
||||||
|
*/
|
||||||
|
protected $_stdout = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param bool $exitIfNoStdin (default true) Exit() if STDIN is not defined
|
||||||
|
*/
|
||||||
|
public function __construct($exitIfNoStdin = true)
|
||||||
|
{
|
||||||
|
if ($exitIfNoStdin && ! defined('STDIN')) {
|
||||||
|
exit('This script is for command-line use only.');
|
||||||
|
}
|
||||||
|
if (isset($GLOBALS['argv'][1])
|
||||||
|
&& ($GLOBALS['argv'][1] === '-?' || $GLOBALS['argv'][1] === '--help')) {
|
||||||
|
$this->isHelpRequest = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Cli\Arg|string $letter
|
||||||
|
* @return Cli\Arg
|
||||||
|
*/
|
||||||
|
public function addOptionalArg($letter)
|
||||||
|
{
|
||||||
|
return $this->addArgument($letter, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Cli\Arg|string $letter
|
||||||
|
* @return Cli\Arg
|
||||||
|
*/
|
||||||
|
public function addRequiredArg($letter)
|
||||||
|
{
|
||||||
|
return $this->addArgument($letter, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $letter
|
||||||
|
* @param bool $required
|
||||||
|
* @param Cli\Arg|null $arg
|
||||||
|
* @return Cli\Arg
|
||||||
|
* @throws \InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function addArgument($letter, $required, Cli\Arg $arg = null)
|
||||||
|
{
|
||||||
|
if (! preg_match('/^[a-zA-Z]$/', $letter)) {
|
||||||
|
throw new \InvalidArgumentException('$letter must be in [a-zA-z]');
|
||||||
|
}
|
||||||
|
if (! $arg) {
|
||||||
|
$arg = new Cli\Arg($required);
|
||||||
|
}
|
||||||
|
$this->_args[$letter] = $arg;
|
||||||
|
return $arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $letter
|
||||||
|
* @return Cli\Arg|null
|
||||||
|
*/
|
||||||
|
public function getArgument($letter)
|
||||||
|
{
|
||||||
|
return isset($this->_args[$letter]) ? $this->_args[$letter] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read and validate options
|
||||||
|
*
|
||||||
|
* @return bool true if all options are valid
|
||||||
|
*/
|
||||||
|
public function validate()
|
||||||
|
{
|
||||||
|
$options = '';
|
||||||
|
$this->errors = array();
|
||||||
|
$this->values = array();
|
||||||
|
$this->_stdin = null;
|
||||||
|
|
||||||
|
if ($this->isHelpRequest) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$lettersUsed = '';
|
||||||
|
foreach ($this->_args as $letter => $arg) {
|
||||||
|
/* @var Cli\Arg $arg */
|
||||||
|
$options .= $letter;
|
||||||
|
$lettersUsed .= $letter;
|
||||||
|
|
||||||
|
if ($arg->mayHaveValue || $arg->mustHaveValue) {
|
||||||
|
$options .= ($arg->mustHaveValue ? ':' : '::');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->debug['argv'] = $GLOBALS['argv'];
|
||||||
|
$argvCopy = array_slice($GLOBALS['argv'], 1);
|
||||||
|
$o = getopt($options);
|
||||||
|
$this->debug['getopt_options'] = $options;
|
||||||
|
$this->debug['getopt_return'] = $o;
|
||||||
|
|
||||||
|
foreach ($this->_args as $letter => $arg) {
|
||||||
|
/* @var Cli\Arg $arg */
|
||||||
|
$this->values[$letter] = false;
|
||||||
|
if (isset($o[$letter])) {
|
||||||
|
if (is_bool($o[$letter])) {
|
||||||
|
|
||||||
|
// remove from argv copy
|
||||||
|
$k = array_search("-$letter", $argvCopy);
|
||||||
|
if ($k !== false) {
|
||||||
|
array_splice($argvCopy, $k, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($arg->mustHaveValue) {
|
||||||
|
$this->addError($letter, "Missing value");
|
||||||
|
} else {
|
||||||
|
$this->values[$letter] = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// string
|
||||||
|
$this->values[$letter] = $o[$letter];
|
||||||
|
$v =& $this->values[$letter];
|
||||||
|
|
||||||
|
// remove from argv copy
|
||||||
|
// first look for -ovalue or -o=value
|
||||||
|
$pattern = "/^-{$letter}=?" . preg_quote($v, '/') . "$/";
|
||||||
|
$foundInArgv = false;
|
||||||
|
foreach ($argvCopy as $k => $argV) {
|
||||||
|
if (preg_match($pattern, $argV)) {
|
||||||
|
array_splice($argvCopy, $k, 1);
|
||||||
|
$foundInArgv = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (! $foundInArgv) {
|
||||||
|
// space separated
|
||||||
|
$k = array_search("-$letter", $argvCopy);
|
||||||
|
if ($k !== false) {
|
||||||
|
array_splice($argvCopy, $k, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that value isn't really another option
|
||||||
|
if (strlen($lettersUsed) > 1) {
|
||||||
|
$pattern = "/^-[" . str_replace($letter, '', $lettersUsed) . "]/i";
|
||||||
|
if (preg_match($pattern, $v)) {
|
||||||
|
$this->addError($letter, "Value was read as another option: %s", $v);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($arg->assertFile || $arg->assertDir) {
|
||||||
|
if ($v[0] !== '/' && $v[0] !== '~') {
|
||||||
|
$this->values["$letter.raw"] = $v;
|
||||||
|
$v = getcwd() . "/$v";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($arg->assertFile) {
|
||||||
|
if ($arg->useAsInfile) {
|
||||||
|
$this->_stdin = $v;
|
||||||
|
} elseif ($arg->useAsOutfile) {
|
||||||
|
$this->_stdout = $v;
|
||||||
|
}
|
||||||
|
if ($arg->assertReadable && ! is_readable($v)) {
|
||||||
|
$this->addError($letter, "File not readable: %s", $v);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($arg->assertWritable) {
|
||||||
|
if (is_file($v)) {
|
||||||
|
if (! is_writable($v)) {
|
||||||
|
$this->addError($letter, "File not writable: %s", $v);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (! is_writable(dirname($v))) {
|
||||||
|
$this->addError($letter, "Directory not writable: %s", dirname($v));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} elseif ($arg->assertDir && $arg->assertWritable && ! is_writable($v)) {
|
||||||
|
$this->addError($letter, "Directory not readable: %s", $v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($arg->isRequired()) {
|
||||||
|
$this->addError($letter, "Missing");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->moreArgs = $argvCopy;
|
||||||
|
reset($this->moreArgs);
|
||||||
|
return empty($this->errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the full paths of file(s) passed in as unspecified arguments
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getPathArgs()
|
||||||
|
{
|
||||||
|
$r = $this->moreArgs;
|
||||||
|
foreach ($r as $k => $v) {
|
||||||
|
if ($v[0] !== '/' && $v[0] !== '~') {
|
||||||
|
$v = getcwd() . "/$v";
|
||||||
|
$v = str_replace('/./', '/', $v);
|
||||||
|
do {
|
||||||
|
$v = preg_replace('@/[^/]+/\\.\\./@', '/', $v, 1, $changed);
|
||||||
|
} while ($changed);
|
||||||
|
$r[$k] = $v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $r;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a short list of errors with options
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getErrorReport()
|
||||||
|
{
|
||||||
|
if (empty($this->errors)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
$r = "Some arguments did not pass validation:\n";
|
||||||
|
foreach ($this->errors as $letter => $arr) {
|
||||||
|
$r .= " $letter : " . implode(', ', $arr) . "\n";
|
||||||
|
}
|
||||||
|
$r .= "\n";
|
||||||
|
return $r;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getArgumentsListing()
|
||||||
|
{
|
||||||
|
$r = "\n";
|
||||||
|
foreach ($this->_args as $letter => $arg) {
|
||||||
|
/* @var Cli\Arg $arg */
|
||||||
|
$desc = $arg->getDescription();
|
||||||
|
$flag = " -$letter ";
|
||||||
|
if ($arg->mayHaveValue) {
|
||||||
|
$flag .= "[VAL]";
|
||||||
|
} elseif ($arg->mustHaveValue) {
|
||||||
|
$flag .= "VAL";
|
||||||
|
}
|
||||||
|
if ($arg->assertFile) {
|
||||||
|
$flag = str_replace('VAL', 'FILE', $flag);
|
||||||
|
} elseif ($arg->assertDir) {
|
||||||
|
$flag = str_replace('VAL', 'DIR', $flag);
|
||||||
|
}
|
||||||
|
if ($arg->isRequired()) {
|
||||||
|
$desc = "(required) $desc";
|
||||||
|
}
|
||||||
|
$flag = str_pad($flag, 12, " ", STR_PAD_RIGHT);
|
||||||
|
$desc = wordwrap($desc, 70);
|
||||||
|
$r .= $flag . str_replace("\n", "\n ", $desc) . "\n\n";
|
||||||
|
}
|
||||||
|
return $r;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get resource of open input stream. May be STDIN or a file pointer
|
||||||
|
* to the file specified by an option with 'STDIN'.
|
||||||
|
*
|
||||||
|
* @return resource
|
||||||
|
*/
|
||||||
|
public function openInput()
|
||||||
|
{
|
||||||
|
if (null === $this->_stdin) {
|
||||||
|
return STDIN;
|
||||||
|
} else {
|
||||||
|
$this->_stdin = fopen($this->_stdin, 'rb');
|
||||||
|
return $this->_stdin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function closeInput()
|
||||||
|
{
|
||||||
|
if (null !== $this->_stdin) {
|
||||||
|
fclose($this->_stdin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get resource of open output stream. May be STDOUT or a file pointer
|
||||||
|
* to the file specified by an option with 'STDOUT'. The file will be
|
||||||
|
* truncated to 0 bytes on opening.
|
||||||
|
*
|
||||||
|
* @return resource
|
||||||
|
*/
|
||||||
|
public function openOutput()
|
||||||
|
{
|
||||||
|
if (null === $this->_stdout) {
|
||||||
|
return STDOUT;
|
||||||
|
} else {
|
||||||
|
$this->_stdout = fopen($this->_stdout, 'wb');
|
||||||
|
return $this->_stdout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function closeOutput()
|
||||||
|
{
|
||||||
|
if (null !== $this->_stdout) {
|
||||||
|
fclose($this->_stdout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $letter
|
||||||
|
* @param string $msg
|
||||||
|
* @param string $value
|
||||||
|
*/
|
||||||
|
protected function addError($letter, $msg, $value = null)
|
||||||
|
{
|
||||||
|
if ($value !== null) {
|
||||||
|
$value = var_export($value, 1);
|
||||||
|
}
|
||||||
|
$this->errors[$letter][] = sprintf($msg, $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
181
inc/contrib/minify/MrClay/Cli/Arg.php
Executable file
181
inc/contrib/minify/MrClay/Cli/Arg.php
Executable file
@ -0,0 +1,181 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace MrClay\Cli;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An argument for a CLI app. This specifies the argument, what values it expects and
|
||||||
|
* how it's treated during validation.
|
||||||
|
*
|
||||||
|
* By default, the argument will be assumed to be an optional letter flag with no value following.
|
||||||
|
*
|
||||||
|
* If the argument may receive a value, call mayHaveValue(). If there's whitespace after the
|
||||||
|
* flag, the value will be returned as true instead of the string.
|
||||||
|
*
|
||||||
|
* If the argument MUST be accompanied by a value, call mustHaveValue(). In this case, whitespace
|
||||||
|
* is permitted between the flag and its value.
|
||||||
|
*
|
||||||
|
* Use assertFile() or assertDir() to indicate that the argument must return a string value
|
||||||
|
* specifying a file or directory. During validation, the value will be resolved to a
|
||||||
|
* full file/dir path (not necessarily existing!) and the original value will be accessible
|
||||||
|
* via a "*.raw" key. E.g. $cli->values['f.raw']
|
||||||
|
*
|
||||||
|
* Use assertReadable()/assertWritable() to cause the validator to test the file/dir for
|
||||||
|
* read/write permissions respectively.
|
||||||
|
*
|
||||||
|
* @method \MrClay\Cli\Arg mayHaveValue() Assert that the argument, if present, may receive a string value
|
||||||
|
* @method \MrClay\Cli\Arg mustHaveValue() Assert that the argument, if present, must receive a string value
|
||||||
|
* @method \MrClay\Cli\Arg assertFile() Assert that the argument's value must specify a file
|
||||||
|
* @method \MrClay\Cli\Arg assertDir() Assert that the argument's value must specify a directory
|
||||||
|
* @method \MrClay\Cli\Arg assertReadable() Assert that the specified file/dir must be readable
|
||||||
|
* @method \MrClay\Cli\Arg assertWritable() Assert that the specified file/dir must be writable
|
||||||
|
*
|
||||||
|
* @property-read bool mayHaveValue
|
||||||
|
* @property-read bool mustHaveValue
|
||||||
|
* @property-read bool assertFile
|
||||||
|
* @property-read bool assertDir
|
||||||
|
* @property-read bool assertReadable
|
||||||
|
* @property-read bool assertWritable
|
||||||
|
* @property-read bool useAsInfile
|
||||||
|
* @property-read bool useAsOutfile
|
||||||
|
*
|
||||||
|
* @author Steve Clay <steve@mrclay.org>
|
||||||
|
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||||
|
*/
|
||||||
|
class Arg {
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getDefaultSpec()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
'mayHaveValue' => false,
|
||||||
|
'mustHaveValue' => false,
|
||||||
|
'assertFile' => false,
|
||||||
|
'assertDir' => false,
|
||||||
|
'assertReadable' => false,
|
||||||
|
'assertWritable' => false,
|
||||||
|
'useAsInfile' => false,
|
||||||
|
'useAsOutfile' => false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $spec = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $required = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param bool $isRequired
|
||||||
|
*/
|
||||||
|
public function __construct($isRequired = false)
|
||||||
|
{
|
||||||
|
$this->spec = $this->getDefaultSpec();
|
||||||
|
$this->required = (bool) $isRequired;
|
||||||
|
if ($isRequired) {
|
||||||
|
$this->spec['mustHaveValue'] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert that the argument's value points to a writable file. When
|
||||||
|
* Cli::openOutput() is called, a write pointer to this file will
|
||||||
|
* be provided.
|
||||||
|
* @return Arg
|
||||||
|
*/
|
||||||
|
public function useAsOutfile()
|
||||||
|
{
|
||||||
|
$this->spec['useAsOutfile'] = true;
|
||||||
|
return $this->assertFile()->assertWritable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert that the argument's value points to a readable file. When
|
||||||
|
* Cli::openInput() is called, a read pointer to this file will
|
||||||
|
* be provided.
|
||||||
|
* @return Arg
|
||||||
|
*/
|
||||||
|
public function useAsInfile()
|
||||||
|
{
|
||||||
|
$this->spec['useAsInfile'] = true;
|
||||||
|
return $this->assertFile()->assertReadable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getSpec()
|
||||||
|
{
|
||||||
|
return $this->spec;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $desc
|
||||||
|
* @return Arg
|
||||||
|
*/
|
||||||
|
public function setDescription($desc)
|
||||||
|
{
|
||||||
|
$this->description = $desc;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getDescription()
|
||||||
|
{
|
||||||
|
return $this->description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isRequired()
|
||||||
|
{
|
||||||
|
return $this->required;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note: magic methods declared in class PHPDOC
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
* @param array $args
|
||||||
|
* @return Arg
|
||||||
|
* @throws \BadMethodCallException
|
||||||
|
*/
|
||||||
|
public function __call($name, array $args = array())
|
||||||
|
{
|
||||||
|
if (array_key_exists($name, $this->spec)) {
|
||||||
|
$this->spec[$name] = true;
|
||||||
|
if ($name === 'assertFile' || $name === 'assertDir') {
|
||||||
|
$this->spec['mustHaveValue'] = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new \BadMethodCallException('Method does not exist');
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note: magic properties declared in class PHPDOC
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
* @return bool|null
|
||||||
|
*/
|
||||||
|
public function __get($name)
|
||||||
|
{
|
||||||
|
if (array_key_exists($name, $this->spec)) {
|
||||||
|
return $this->spec[$name];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -100,7 +100,9 @@
|
|||||||
if(!isset($config['url_stylesheet']))
|
if(!isset($config['url_stylesheet']))
|
||||||
$config['url_stylesheet'] = $config['uri_stylesheets'] . 'style.css';
|
$config['url_stylesheet'] = $config['uri_stylesheets'] . 'style.css';
|
||||||
if(!isset($config['url_javascript']))
|
if(!isset($config['url_javascript']))
|
||||||
$config['url_javascript'] = $config['root'] . 'main.js';
|
$config['url_javascript'] = $config['root'] . $config['file_script'];
|
||||||
|
if(!isset($config['additional_javascript_url']))
|
||||||
|
$config['additional_javascript_url'] = $config['root'];
|
||||||
|
|
||||||
if($config['root_file']) {
|
if($config['root_file']) {
|
||||||
chdir($config['root_file']);
|
chdir($config['root_file']);
|
||||||
@ -1266,10 +1268,22 @@
|
|||||||
'uri' => addslashes((!empty($uri) ? $config['uri_stylesheets'] : '') . $uri));
|
'uri' => addslashes((!empty($uri) ? $config['uri_stylesheets'] : '') . $uri));
|
||||||
}
|
}
|
||||||
|
|
||||||
file_write($config['file_script'], Element('main.js', Array(
|
$script = Element('main.js', Array(
|
||||||
'config' => $config,
|
'config' => $config,
|
||||||
'stylesheets' => $stylesheets
|
'stylesheets' => $stylesheets
|
||||||
)));
|
));
|
||||||
|
if($config['additional_javascript_compile']) {
|
||||||
|
foreach($config['additional_javascript'] as $file) {
|
||||||
|
$script .= file_get_contents($file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($config['minify_js']) {
|
||||||
|
require_once 'inc/contrib/minify/JSMin.php';
|
||||||
|
$script = JSMin::minify($script);
|
||||||
|
}
|
||||||
|
|
||||||
|
file_write($config['file_script'], $script);
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkDNSBL() {
|
function checkDNSBL() {
|
||||||
|
8
mod.php
8
mod.php
@ -1920,6 +1920,8 @@
|
|||||||
$body .= 'Generating Javascript file…<br/>';
|
$body .= 'Generating Javascript file…<br/>';
|
||||||
buildJavascript();
|
buildJavascript();
|
||||||
|
|
||||||
|
$main_js = $config['file_script'];
|
||||||
|
|
||||||
$boards = listBoards();
|
$boards = listBoards();
|
||||||
|
|
||||||
foreach($boards as &$board) {
|
foreach($boards as &$board) {
|
||||||
@ -1929,6 +1931,12 @@
|
|||||||
$body .= 'Creating index pages<br/>';
|
$body .= 'Creating index pages<br/>';
|
||||||
buildIndex();
|
buildIndex();
|
||||||
|
|
||||||
|
if($config['file_script'] != $main_js) {
|
||||||
|
// different javascript file
|
||||||
|
$body .= 'Generating Javascript file…<br/>';
|
||||||
|
buildJavascript();
|
||||||
|
}
|
||||||
|
|
||||||
$query = query(sprintf("SELECT `id` FROM `posts_%s` WHERE `thread` IS NULL", $board['uri'])) or error(db_error());
|
$query = query(sprintf("SELECT `id` FROM `posts_%s` WHERE `thread` IS NULL", $board['uri'])) or error(db_error());
|
||||||
while($post = $query->fetch()) {
|
while($post = $query->fetch()) {
|
||||||
$body .= "Rebuilding #{$post['id']}<br/>";
|
$body .= "Rebuilding #{$post['id']}<br/>";
|
||||||
|
@ -8,7 +8,12 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
|
||||||
{% if config.meta_keywords %}<meta name="keywords" content="{{ config.meta_keywords }}" />{% endif %}
|
{% if config.meta_keywords %}<meta name="keywords" content="{{ config.meta_keywords }}" />{% endif %}
|
||||||
<link rel="stylesheet" type="text/css" id="stylesheet" href="{{ config.uri_stylesheets }}{{ config.default_stylesheet.1 }}" />
|
<link rel="stylesheet" type="text/css" id="stylesheet" href="{{ config.uri_stylesheets }}{{ config.default_stylesheet.1 }}" />
|
||||||
{% if not nojavascript %}<script type="text/javascript" src="{{ config.url_javascript }}"></script>{% for javascript in config.additional_javascript %}<script type="text/javascript" src="{{ javascript }}"></script>{% endfor %}{% endif %}
|
{% if not nojavascript %}
|
||||||
|
<script type="text/javascript" src="{{ config.url_javascript }}"></script>
|
||||||
|
{% if not config.additional_javascript_compile %}
|
||||||
|
{% for javascript in config.additional_javascript %}<script type="text/javascript" src="{{ config.additional_javascript_url }}{{ javascript }}"></script>{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
{% if config.recaptcha %}<style type="text/css">{% raw %}
|
{% if config.recaptcha %}<style type="text/css">{% raw %}
|
||||||
.recaptcha_image_cell {
|
.recaptcha_image_cell {
|
||||||
background: none !important;
|
background: none !important;
|
||||||
@ -44,18 +49,7 @@
|
|||||||
{% if config.javascript_local_time %}
|
{% if config.javascript_local_time %}
|
||||||
<script>{% raw %}init_localtime();{% endraw %}</script>
|
<script>{% raw %}init_localtime();{% endraw %}</script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% include 'report_delete.html' %}
|
||||||
<div class="delete">
|
|
||||||
{% trans %}Delete Post{% endtrans %} [<input title="Delete file only" type="checkbox" name="file" id="delete_file" />
|
|
||||||
<label for="delete_file">{% trans %}File{% endtrans %}</label>] <label for="password">{% trans %}Password{% endtrans %}</label>
|
|
||||||
<input id="password" type="password" name="password" size="12" maxlength="18" />
|
|
||||||
<input type="submit" name="delete" value="{% trans %}Delete{% endtrans %}" />
|
|
||||||
</div>
|
|
||||||
<div class="delete" style="clear:both">
|
|
||||||
<label for="reason">{% trans %}Reason{% endtrans %}</label>
|
|
||||||
<input id="reason" type="text" name="reason" size="20" maxlength="30" />
|
|
||||||
<input type="submit" name="report" value="{% trans %}Report{% endtrans %}" />
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
<div class="pages">{{ btn.prev }} {% for page in pages %}
|
<div class="pages">{{ btn.prev }} {% for page in pages %}
|
||||||
[<a {% if page.selected %}class="selected"{% endif %}{% if not page.selected %}href="{{ page.link }}"{% endif %}>{{ page.num }}</a>]{% if loop.last %} {% endif %}
|
[<a {% if page.selected %}class="selected"{% endif %}{% if not page.selected %}href="{{ page.link }}"{% endif %}>{{ page.num }}</a>]{% if loop.last %} {% endif %}
|
||||||
|
11
templates/report_delete.html
Normal file
11
templates/report_delete.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<div class="delete">
|
||||||
|
{% trans %}Delete Post{% endtrans %} [<input title="Delete file only" type="checkbox" name="file" id="delete_file" />
|
||||||
|
<label for="delete_file">{% trans %}File{% endtrans %}</label>] <label for="password">{% trans %}Password{% endtrans %}</label>
|
||||||
|
<input id="password" type="password" name="password" size="12" maxlength="18" />
|
||||||
|
<input type="submit" name="delete" value="{% trans %}Delete{% endtrans %}" />
|
||||||
|
</div>
|
||||||
|
<div class="delete" style="clear:both">
|
||||||
|
<label for="reason">{% trans %}Reason{% endtrans %}</label>
|
||||||
|
<input id="reason" type="text" name="reason" size="20" maxlength="30" />
|
||||||
|
<input type="submit" name="report" value="{% trans %}Report{% endtrans %}" />
|
||||||
|
</div>
|
@ -8,7 +8,12 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
|
||||||
{% if config.meta_keywords %}<meta name="keywords" content="{{ config.meta_keywords }}" />{% endif %}
|
{% if config.meta_keywords %}<meta name="keywords" content="{{ config.meta_keywords }}" />{% endif %}
|
||||||
<link rel="stylesheet" type="text/css" id="stylesheet" href="{{ config.uri_stylesheets }}{{ config.default_stylesheet.1 }}" />
|
<link rel="stylesheet" type="text/css" id="stylesheet" href="{{ config.uri_stylesheets }}{{ config.default_stylesheet.1 }}" />
|
||||||
{% if not nojavascript %}<script type="text/javascript" src="{{ config.url_javascript }}"></script>{% for javascript in config.additional_javascript %}<script type="text/javascript" src="{{ javascript }}"></script>{% endfor %}{% endif %}
|
{% if not nojavascript %}
|
||||||
|
<script type="text/javascript" src="{{ config.url_javascript }}"></script>
|
||||||
|
{% if not config.additional_javascript_compile %}
|
||||||
|
{% for javascript in config.additional_javascript %}<script type="text/javascript" src="{{ config.additional_javascript_url }}{{ javascript }}"></script>{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
{% if config.recaptcha %}<style type="text/css">{% raw %}
|
{% if config.recaptcha %}<style type="text/css">{% raw %}
|
||||||
.recaptcha_image_cell {
|
.recaptcha_image_cell {
|
||||||
background: none !important;
|
background: none !important;
|
||||||
@ -46,18 +51,7 @@
|
|||||||
{% if config.javascript_local_time %}
|
{% if config.javascript_local_time %}
|
||||||
<script>{% raw %}init_localtime();{% endraw %}</script>
|
<script>{% raw %}init_localtime();{% endraw %}</script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% include 'report_delete.html' %}
|
||||||
<div class="delete">
|
|
||||||
{% trans %}Delete Post{% endtrans %} [<input title="Delete file only" type="checkbox" name="file" id="delete_file" />
|
|
||||||
<label for="delete_file">{% trans %}File{% endtrans %}</label>] <label for="password">{% trans %}Password{% endtrans %}</label>
|
|
||||||
<input id="password" type="password" name="password" size="12" maxlength="18" />
|
|
||||||
<input type="submit" name="delete" value="{% trans %}Delete{% endtrans %}" />
|
|
||||||
</div>
|
|
||||||
<div class="delete" style="clear:both">
|
|
||||||
<label for="reason">{% trans %}Reason{% endtrans %}</label>
|
|
||||||
<input id="reason" type="text" name="reason" size="20" maxlength="30" />
|
|
||||||
<input type="submit" name="report" value="{% trans %}Report{% endtrans %}" />
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
<a href="{{ return }}">[{% trans %}Return{% endtrans %}]</a>
|
<a href="{{ return }}">[{% trans %}Return{% endtrans %}]</a>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user