The "Vary: Accept-Encoding" header is sent. If the client allows encoding, Content-Encoding and Content-Length are added. Send a CSS file, compressed if possible $he = new HTTP_Encoder(array( 'content' => file_get_contents($cssFile) ,'type' => 'text/css' )); $he->encode(); $he->sendAll(); Shortcut to encoding output header('Content-Type: text/css'); // needed if not HTML HTTP_Encoder::output($css); Just sniff for the accepted encoding $encoding = HTTP_Encoder::getAcceptedEncoding(); 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.
Author: Stephen Clay (steve@mrclay.org)
Ejemplo n.º 1
0
 /**
  * Gzip (encode) the HTTP response and write to output.
  * @param string $strBody        The content that should be in the response.
  * @param string $strContentType The MIME type of the content.
  */
 public static function send($strBody, $strContentType = "text/html")
 {
     $objEncoder = new \HTTP_Encoder(array("content" => $strBody, "type" => $strContentType));
     $objEncoder->encode();
     $objEncoder->sendAll();
     exit;
 }
Ejemplo n.º 2
0
 public static function render($arrFilter)
 {
     $dtLastModified = 0;
     //*** Load sources from sources directory.
     if (is_dir($GLOBALS["_PATHS"]['css'])) {
         //*** Directory exists.
         foreach ($arrFilter as $strFilter) {
             $strFile = $GLOBALS["_PATHS"]['css'] . "{$strFilter}.css";
             $dtLastModified = self::getLastModified($strFile, $dtLastModified);
             //*** Auto check custom files.
             if (strpos($strFilter, "-custom") === false) {
                 $strFile = $GLOBALS["_PATHS"]['css'] . "{$strFilter}-custom.css";
                 $dtLastModified = self::getLastModified($strFile, $dtLastModified);
             }
         }
     }
     //*** Check if we can send a "Not Modified" header.
     \HTTP_ConditionalGet::check($dtLastModified, true, array("maxAge" => 1200000));
     //*** Modified. Get contents.
     $strOutput = self::minify($arrFilter);
     //*** Gzip the CSS.
     $objEncoder = new \HTTP_Encoder(array("content" => $strOutput, "type" => "text/css"));
     $objEncoder->encode();
     $objEncoder->sendAll();
 }
Ejemplo n.º 3
0
 public static function render($arrFilter)
 {
     $dtLastModified = 0;
     //*** Load sources from sources directory.
     if (is_dir($GLOBALS["_PATHS"]['js'])) {
         //*** Directory exists.
         foreach ($arrFilter as $strFilter) {
             $strFile = $GLOBALS["_PATHS"]['js'] . $strFilter . ".js";
             if (is_file($strFile)) {
                 $lngLastModified = filemtime($strFile);
                 if (empty($dtLastModified) || $lngLastModified > $dtLastModified) {
                     $dtLastModified = $lngLastModified;
                 }
             }
         }
     }
     //*** Check if we can send a "Not Modified" header.
     \HTTP_ConditionalGet::check($dtLastModified, true, array("maxAge" => 1200000));
     //*** Modified. Get contents.
     $strOutput = self::minify($arrFilter);
     //*** Gzip the Javascript.
     $objEncoder = new \HTTP_Encoder(array("content" => $strOutput, "type" => "text/javascript"));
     $objEncoder->encode();
     $objEncoder->sendAll();
 }
Ejemplo n.º 4
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";
        }
    }
}
Ejemplo n.º 5
0
 /**
  * Runs minify
  */
 function process()
 {
     require_once W3TC_LIB_MINIFY_DIR . '/Minify.php';
     require_once W3TC_LIB_MINIFY_DIR . '/HTTP/Encoder.php';
     HTTP_Encoder::$encodeToIe6 = $this->_config->get_boolean('minify.comprss.ie6');
     Minify::$uploaderHoursBehind = $this->_config->get_integer('minify.fixtime');
     Minify::setCache($this->_get_cache());
     $serve_options = $this->_config->get_array('minify.options');
     $serve_options['maxAge'] = $this->_config->get_integer('minify.maxage');
     $serve_options['encodeOutput'] = $this->_config->get_boolean('minify.compress');
     $serve_options['postprocessor'] = array(&$this, 'postprocessor');
     if (stripos(PHP_OS, 'win') === 0) {
         Minify::setDocRoot();
     }
     foreach ($this->_config->get_array('minify.symlinks') as $link => $target) {
         $link = str_replace('//', realpath($_SERVER['DOCUMENT_ROOT']), $link);
         $link = strtr($link, '/', DIRECTORY_SEPARATOR);
         $serve_options['minifierOptions']['text/css']['symlinks'][$link] = realpath($target);
     }
     if ($this->_config->get_boolean('minify.debug')) {
         $serve_options['debug'] = true;
     }
     if ($this->_config->get('minify.debug')) {
         require_once W3TC_LIB_MINIFY_DIR . '/Minify/Logger.php';
         Minify_Logger::setLogger($this);
     }
     if (isset($_GET['f']) || isset($_GET['gg']) && isset($_GET['g']) && isset($_GET['t'])) {
         if (isset($_GET['gg']) && isset($_GET['g']) && isset($_GET['t'])) {
             $serve_options['minApp']['groups'] = $this->get_groups($_GET['gg'], $_GET['t']);
             if ($_GET['t'] == 'js' && (in_array($_GET['g'], array('include', 'include-nb')) && $this->_config->get_boolean('minify.js.combine.header') || in_array($_GET['g'], array('include-footer', 'include-footer-nb')) && $this->_config->get_boolean('minify.js.combine.footer'))) {
                 $serve_options['minifiers']['application/x-javascript'] = array($this, 'minify_stub');
             }
             /**
              * Setup user-friendly cache ID for disk cache
              */
             if ($this->_config->get_string('minify.engine') == 'file') {
                 $cacheId = sprintf('%s.%s.%s', $_GET['gg'], $_GET['g'], $_GET['t']);
                 Minify::setCacheId($cacheId);
             }
         }
         @header('Pragma: public');
         try {
             Minify::serve('MinApp', $serve_options);
         } catch (Exception $exception) {
             printf('<strong>W3 Total Cache Error:</strong> Minify error: %s', $exception->getMessage());
         }
     } else {
         die('This file cannot be accessed directly');
     }
 }
Ejemplo n.º 6
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);
     }
 }
Ejemplo n.º 7
0
 /**
  * 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;
 }
Ejemplo n.º 8
0
<?php

/**
 * AJAX checks for zlib.output_compression
 * 
 * @package Minify
 */
$_oc = ini_get('zlib.output_compression');
// allow access only if builder is enabled
require __DIR__ . '/../config.php';
if (!$min_enableBuilder) {
    header('Location: /');
    exit;
}
if (isset($_GET['hello'])) {
    // echo 'World!'
    // try to prevent double encoding (may not have an effect)
    ini_set('zlib.output_compression', '0');
    require $min_libPath . '/HTTP/Encoder.php';
    HTTP_Encoder::$encodeToIe6 = true;
    // just in case
    $he = new HTTP_Encoder(array('content' => 'World!', 'method' => 'deflate'));
    $he->encode();
    $he->sendAll();
} else {
    // echo status "0" or "1"
    header('Content-Type: text/plain');
    echo (int) $_oc;
}
 /**
  * Echoes the Javascript if not cached.
  *
  * Echoes the javascript with the correct content.
  * Since the content is dinamic, i use the hash function.
  *
  * @param string $javascript
  *
  * @return void
  */
 private function _echo_javascript($javascript)
 {
     $conditional_get = new HTTP_ConditionalGet(array('contentHash' => md5($javascript)));
     $conditional_get->sendHeaders();
     if (!$conditional_get->cacheIsValid) {
         $http_encoder = new HTTP_Encoder(array('content' => $javascript, 'type' => 'text/javascript'));
         $compression_level = null;
         if ($this->_registry->get('model.settings')->get('disable_gzip_compression')) {
             // set the compression level to 0 to disable it.
             $compression_level = 0;
         }
         $http_encoder->encode($compression_level);
         $http_encoder->sendAll();
     }
     Ai1ec_Http_Response_Helper::stop(0);
 }
Ejemplo n.º 10
0
 /**
  * Overrides parent function and removed Content-Length header to avoid
  * some problems if our JavaScript is somehow prepended by 3rd party code.
  *
  * @return void Method does not return.
  */
 public function sendHeaders()
 {
     unset($this->_headers['Content-Length']);
     parent::sendHeaders();
 }
Ejemplo n.º 11
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());
Ejemplo n.º 12
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);
     }
 }
Ejemplo n.º 13
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);
     }
 }