getAcceptedEncoding() public static method

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.
public static getAcceptedEncoding ( boolean $allowCompress = true, boolean $allowDeflate = true ) : array
$allowCompress boolean allow the older compress encoding
$allowDeflate boolean 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.)
Example #1
0
function test_HTTP_Encoder()
{
    global $thisDir;
    HTTP_Encoder::$encodeToIe6 = true;
    $methodTests = array(array('ua' => 'Any browser', 'ae' => 'compress, x-gzip', 'exp' => array('gzip', 'x-gzip'), 'desc' => 'recognize "x-gzip" as gzip'), array('ua' => 'Any browser', 'ae' => 'compress, x-gzip;q=0.5', 'exp' => array('gzip', 'x-gzip'), 'desc' => 'gzip w/ non-zero q'), array('ua' => 'Any browser', 'ae' => 'compress, x-gzip;q=0', 'exp' => array('compress', 'compress'), 'desc' => 'gzip w/ zero q'), array('ua' => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)', 'ae' => 'gzip, deflate', 'exp' => array('', ''), 'desc' => 'IE6 w/o "enhanced security"'), array('ua' => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)', 'ae' => 'gzip, deflate', 'exp' => array('deflate', 'deflate'), 'desc' => 'IE6 w/ "enhanced security"'), array('ua' => 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.01)', 'ae' => 'gzip, deflate', 'exp' => array('', ''), 'desc' => 'IE5.5'), array('ua' => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; en) Opera 9.25', 'ae' => 'gzip,deflate', 'exp' => array('deflate', 'deflate'), 'desc' => 'Opera identifying as IE6'));
    foreach ($methodTests as $test) {
        $_SERVER['HTTP_USER_AGENT'] = $test['ua'];
        $_SERVER['HTTP_ACCEPT_ENCODING'] = $test['ae'];
        $exp = $test['exp'];
        $ret = HTTP_Encoder::getAcceptedEncoding();
        $passed = assertTrue($exp == $ret, 'HTTP_Encoder : ' . $test['desc']);
        if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) {
            echo "\n--- AE | UA = {$test['ae']} | {$test['ua']}\n";
            echo "Expected = " . preg_replace('/\\s+/', ' ', var_export($exp, 1)) . "\n";
            echo "Returned = " . preg_replace('/\\s+/', ' ', var_export($ret, 1)) . "\n\n";
        }
    }
    HTTP_Encoder::$encodeToIe6 = false;
    $methodTests = array(array('ua' => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)', 'ae' => 'gzip, deflate', 'exp' => array('', ''), 'desc' => 'IE6 w/ "enhanced security"'));
    foreach ($methodTests as $test) {
        $_SERVER['HTTP_USER_AGENT'] = $test['ua'];
        $_SERVER['HTTP_ACCEPT_ENCODING'] = $test['ae'];
        $exp = $test['exp'];
        $ret = HTTP_Encoder::getAcceptedEncoding();
        $passed = assertTrue($exp == $ret, 'HTTP_Encoder : ' . $test['desc']);
        if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) {
            echo "\n--- AE | UA = {$test['ae']} | {$test['ua']}\n";
            echo "Expected = " . preg_replace('/\\s+/', ' ', var_export($exp, 1)) . "\n";
            echo "Returned = " . preg_replace('/\\s+/', ' ', var_export($ret, 1)) . "\n\n";
        }
    }
    if (!function_exists('gzdeflate')) {
        echo "!WARN: HTTP_Encoder : Zlib support is not present in PHP. Encoding cannot be performed/tested.\n";
        return;
    }
    // test compression of varied content (HTML,JS, & CSS)
    $variedContent = file_get_contents($thisDir . '/_test_files/html/before.html') . file_get_contents($thisDir . '/_test_files/css/subsilver.css') . file_get_contents($thisDir . '/_test_files/js/jquery-1.2.3.js');
    $variedLength = strlen($variedContent);
    $encodingTests = array(array('method' => 'deflate', 'inv' => 'gzinflate', 'exp' => 32157), array('method' => 'gzip', 'inv' => '_gzdecode', 'exp' => 32175), array('method' => 'compress', 'inv' => 'gzuncompress', 'exp' => 32211));
    foreach ($encodingTests as $test) {
        $e = new HTTP_Encoder(array('content' => $variedContent, 'method' => $test['method']));
        $e->encode(9);
        $ret = strlen($e->getContent());
        // test uncompression
        $roundTrip = @call_user_func($test['inv'], $e->getContent());
        $desc = "HTTP_Encoder : {$test['method']} : uncompress possible";
        $passed = assertTrue($variedContent == $roundTrip, $desc);
        // test expected compressed size
        $desc = "HTTP_Encoder : {$test['method']} : compressed to " . sprintf('%4.2f%% of original', $ret / $variedLength * 100);
        $passed = assertTrue(abs($ret - $test['exp']) < 100, $desc);
        if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) {
            echo "\n--- {$test['method']}: expected bytes: ", "{$test['exp']}. Returned: {$ret} ", "(off by " . abs($ret - $test['exp']) . " bytes)\n\n";
        }
    }
}
Example #2
0
 /**
  * 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);
     }
 }
Example #3
0
<?php

require __DIR__ . '/../../bootstrap.php';
// emulate regularly updating document
$every = 20;
$lastModified = round(time() / $every) * $every - $every;
list($enc, ) = HTTP_Encoder::getAcceptedEncoding();
$cg = new HTTP_ConditionalGet(array('lastModifiedTime' => $lastModified, 'encoding' => $enc));
$cg->sendHeaders();
if ($cg->cacheIsValid) {
    // we're done
    exit;
}
// output encoded content
$title = 'ConditionalGet + Encoder';
$explain = '
<p>Using ConditionalGet and Encoder is straightforward. First impliment the
ConditionalGet, then if the cache is not valid, encode and send the content</p>
<p>This script emulates a document that changes every ' . $every . ' seconds.
<br>This is version: ' . date('r', $lastModified) . '</p>
';
require '_include.php';
$content = get_content(array('title' => $title, 'explain' => $explain));
$he = new HTTP_Encoder(array('content' => get_content(array('title' => $title, 'explain' => $explain))));
$he->encode();
// usually you would just $he->sendAll(), but here we want to emulate slow
// connection
$he->sendHeaders();
send_slowly($he->getContent());
Example #4
0
 /**
  * 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 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 (is_string($controller)) {
         // make $controller into object
         $class = 'Minify_Controller_' . $controller;
         if (!class_exists($class, false)) {
             require_once W3TC_LIB_MINIFY_DIR . "/Minify/Controller/" . str_replace('_', '/', $controller) . ".php";
         }
         $controller = new $class();
     }
     // 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']) {
             header(self::$_options['badRequestHeader']);
             echo self::$_options['badRequestHeader'];
             return;
         } 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']) {
         if (self::$_options['encodeMethod'] !== null) {
             // controller specifically requested this
             $contentEncoding = self::$_options['encodeMethod'];
         } else {
             // sniff request header
             require_once W3TC_LIB_MINIFY_DIR . '/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(self::$_options['encodeOutput']);
         }
     } else {
         self::$_options['encodeMethod'] = '';
         // identity (no encoding)
     }
     // check client cache
     require_once W3TC_LIB_MINIFY_DIR . '/HTTP/ConditionalGet.php';
     $cgOptions = array('cacheHeaders' => self::$_options['cacheHeaders'], 'lastModifiedTime' => self::$_options['lastModifiedTime'], 'isPublic' => self::$_options['isPublic'], 'encoding' => self::$_options['encodeMethod']);
     if (self::$_options['maxAge'] > 0) {
         $cgOptions['maxAge'] = self::$_options['maxAge'];
     }
     $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) {
         reset($controller->sources);
         while (list($key, $source) = each($controller->sources)) {
             if (self::$_options['rewriteCssUris'] && $source->filepath && !isset($source->minifyOptions['currentDir']) && !isset($source->minifyOptions['prependRelativePath'])) {
                 $source->minifyOptions['currentDir'] = dirname($source->filepath);
             }
             $source->minifyOptions['processCssImports'] = self::$_options['processCssImports'];
         }
     }
     // check server cache
     if (null !== self::$_cache) {
         // 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 . '.' . self::$_options['encodeMethod'] : $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
             $content = self::_combineMinify();
             self::$_cache->store($cacheId, $content);
             if (self::$_options['encodeOutput'] && function_exists('gzencode')) {
                 self::$_cache->store($cacheId . '.' . self::$_options['encodeMethod'], gzencode($content, self::$_options['encodeLevel']));
             }
         }
     } else {
         // no cache
         $cacheIsReady = false;
         $content = self::_combineMinify();
     }
     if (!$cacheIsReady) {
         switch (self::$_options['encodeMethod']) {
             case 'gzip':
                 $content = gzencode($content, self::$_options['encodeLevel']);
                 break;
             case 'deflate':
                 $content = gzdeflate($content, self::$_options['encodeLevel']);
                 break;
         }
         // still need to encode
     }
     // add headers
     $headers['Content-Length'] = $cacheIsReady ? $cacheContentLength : 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']) {
         $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);
     }
 }
Example #5
0
 /**
  * Serve a request for a minified file. 
  * 
  * Here are the available options and defaults:
  * 
  * '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()
  *
  * 'concatOnly' : set to true to disable minification and simply concatenate the files.
  * For JS, no minifier will be used. For CSS, only URI rewriting is still performed.
  * 
  * '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.
  *
  * 'importWarning' : serve() will check CSS files for @import declarations that
  * appear too late in the combined stylesheet. If found, serve() will prepend
  * the output with this warning. To disable this, set this option to empty string.
  * 
  * Any controller options are documented in that controller's createConfiguration() method.
  * 
  * @param Minify_ControllerInterface $controller instance of subclass of Minify_Controller_Base
  * 
  * @param array                      $options    controller/serve options
  * 
  * @return null|array if the 'quiet' option is set to true, an array
  * with keys "success" (bool), "statusCode" (int), "content" (string), and
  * "headers" (array).
  *
  * @throws Exception
  */
 public function serve(Minify_ControllerInterface $controller, $options = array())
 {
     $options = array_merge($this->getDefaultOptions(), $options);
     $config = $controller->createConfiguration($options);
     $this->sources = $config->getSources();
     $this->selectionId = $config->getSelectionId();
     $this->options = $this->analyzeSources($config->getOptions());
     // check request validity
     if (!$this->sources) {
         // invalid request!
         if (!$this->options['quiet']) {
             $this->errorExit($this->options['badRequestHeader'], self::URL_DEBUG);
         } else {
             list(, $statusCode) = explode(' ', $this->options['badRequestHeader']);
             return array('success' => false, 'statusCode' => (int) $statusCode, 'content' => '', 'headers' => array());
         }
     }
     $this->controller = $controller;
     if ($this->options['debug']) {
         $this->setupDebug();
         $this->options['maxAge'] = 0;
     }
     // determine encoding
     if ($this->options['encodeOutput']) {
         $sendVary = true;
         if ($this->options['encodeMethod'] !== null) {
             // controller specifically requested this
             $contentEncoding = $this->options['encodeMethod'];
         } else {
             // sniff request header
             // depending on what the client accepts, $contentEncoding may be
             // 'x-gzip' while our internal encodeMethod is 'gzip'. Calling
             // getAcceptedEncoding(false, false) leaves out compress and deflate as options.
             list($this->options['encodeMethod'], $contentEncoding) = HTTP_Encoder::getAcceptedEncoding(false, false);
             $sendVary = !HTTP_Encoder::isBuggyIe();
         }
     } else {
         $this->options['encodeMethod'] = '';
         // identity (no encoding)
     }
     // check client cache
     $cgOptions = array('lastModifiedTime' => $this->options['lastModifiedTime'], 'isPublic' => $this->options['isPublic'], 'encoding' => $this->options['encodeMethod']);
     if ($this->options['maxAge'] > 0) {
         $cgOptions['maxAge'] = $this->options['maxAge'];
     } elseif ($this->options['debug']) {
         $cgOptions['invalidate'] = true;
     }
     $cg = new HTTP_ConditionalGet($cgOptions);
     if ($cg->cacheIsValid) {
         // client's cache is valid
         if (!$this->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 ($this->options['contentType'] === self::TYPE_CSS && $this->options['rewriteCssUris']) {
         $this->setupUriRewrites();
     }
     if ($this->options['concatOnly']) {
         $this->options['minifiers'][self::TYPE_JS] = false;
         foreach ($this->sources as $key => $source) {
             if ($this->options['contentType'] === self::TYPE_JS) {
                 $source->setMinifier("");
             } elseif ($this->options['contentType'] === self::TYPE_CSS) {
                 $source->setMinifier(array('Minify_CSSmin', 'minify'));
                 $sourceOpts = $source->getMinifierOptions();
                 $sourceOpts['compress'] = false;
                 $source->setMinifierOptions($sourceOpts);
             }
         }
     }
     // check server cache
     if (!$this->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 = $this->_getCacheId();
         $fullCacheId = $this->options['encodeMethod'] ? $cacheId . '.gz' : $cacheId;
         // check cache for valid entry
         $cacheIsReady = $this->cache->isValid($fullCacheId, $this->options['lastModifiedTime']);
         if ($cacheIsReady) {
             $cacheContentLength = $this->cache->getSize($fullCacheId);
         } else {
             // generate & cache content
             try {
                 $content = $this->combineMinify();
             } catch (Exception $e) {
                 $this->controller->log($e->getMessage());
                 if (!$this->options['quiet']) {
                     $this->errorExit($this->options['errorHeader'], self::URL_DEBUG);
                 }
                 throw $e;
             }
             $this->cache->store($cacheId, $content);
             if (function_exists('gzencode') && $this->options['encodeMethod']) {
                 $this->cache->store($cacheId . '.gz', gzencode($content, $this->options['encodeLevel']));
             }
         }
     } else {
         // no cache
         $cacheIsReady = false;
         try {
             $content = $this->combineMinify();
         } catch (Exception $e) {
             $this->controller->log($e->getMessage());
             if (!$this->options['quiet']) {
                 $this->errorExit($this->options['errorHeader'], self::URL_DEBUG);
             }
             throw $e;
         }
     }
     if (!$cacheIsReady && $this->options['encodeMethod']) {
         // still need to encode
         $content = gzencode($content, $this->options['encodeLevel']);
     }
     // add headers
     if ($cacheIsReady) {
         $headers['Content-Length'] = $cacheContentLength;
     } else {
         if (function_exists('mb_strlen') && (int) ini_get('mbstring.func_overload') & 2) {
             $headers['Content-Length'] = mb_strlen($content, '8bit');
         } else {
             $headers['Content-Length'] = strlen($content);
         }
     }
     $headers['Content-Type'] = $this->options['contentType'];
     if ($this->options['contentTypeCharset']) {
         $headers['Content-Type'] .= '; charset=' . $this->options['contentTypeCharset'];
     }
     if ($this->options['encodeMethod'] !== '') {
         $headers['Content-Encoding'] = $contentEncoding;
     }
     if ($this->options['encodeOutput'] && $sendVary) {
         $headers['Vary'] = 'Accept-Encoding';
     }
     if (!$this->options['quiet']) {
         // output headers & content
         foreach ($headers as $name => $val) {
             header($name . ': ' . $val);
         }
         if ($cacheIsReady) {
             $this->cache->display($fullCacheId);
         } else {
             echo $content;
         }
     } else {
         return array('success' => true, 'statusCode' => 200, 'content' => $cacheIsReady ? $this->cache->fetch($fullCacheId) : $content, 'headers' => $headers);
     }
 }