diff options
Diffstat (limited to 'system/core/Output.php')
-rw-r--r-- | system/core/Output.php | 714 |
1 files changed, 491 insertions, 223 deletions
diff --git a/system/core/Output.php b/system/core/Output.php index 7959befb7..a3155fece 100644 --- a/system/core/Output.php +++ b/system/core/Output.php @@ -1,112 +1,156 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.0 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2014 - 2017, British Columbia Institute of Technology + * + * 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 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 CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/) + * @license http://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 1.0.0 * @filesource */ - -// ------------------------------------------------------------------------ +defined('BASEPATH') OR exit('No direct script access allowed'); /** * Output Class * - * Responsible for sending final output to browser + * Responsible for sending final output to the browser. * * @package CodeIgniter * @subpackage Libraries * @category Output - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/libraries/output.html + * @author EllisLab Dev Team + * @link https://codeigniter.com/user_guide/libraries/output.html */ class CI_Output { /** - * Current output string + * Final output string * - * @var string - * @access protected + * @var string */ - protected $final_output; + public $final_output; + /** * Cache expiration time * - * @var int - * @access protected + * @var int */ - protected $cache_expiration = 0; + public $cache_expiration = 0; + /** * List of server headers * - * @var array - * @access protected + * @var array */ - protected $headers = array(); + public $headers = array(); + /** * List of mime types * - * @var array - * @access protected + * @var array */ - protected $mime_types = array(); + public $mimes = array(); + /** - * Determines wether profiler is enabled + * Mime-type for the current page * - * @var book - * @access protected + * @var string */ - protected $enable_profiler = FALSE; + protected $mime_type = 'text/html'; + /** - * Determines if output compression is enabled + * Enable Profiler flag * - * @var bool - * @access protected + * @var bool */ - protected $_zlib_oc = FALSE; + public $enable_profiler = FALSE; + + /** + * php.ini zlib.output_compression flag + * + * @var bool + */ + protected $_zlib_oc = FALSE; + + /** + * CI output compression flag + * + * @var bool + */ + protected $_compress_output = FALSE; + /** * List of profiler sections * - * @var array - * @access protected + * @var array */ - protected $_profiler_sections = array(); + protected $_profiler_sections = array(); + /** - * Whether or not to parse variables like {elapsed_time} and {memory_usage} + * Parse markers flag * - * @var bool - * @access protected + * Whether or not to parse variables like {elapsed_time} and {memory_usage}. + * + * @var bool */ - protected $parse_exec_vars = TRUE; + public $parse_exec_vars = TRUE; /** - * Constructor + * mbstring.func_overload flag * + * @var bool */ - function __construct() - { - $this->_zlib_oc = @ini_get('zlib.output_compression'); + protected static $func_overload; - // Get mime types for later - if (defined('ENVIRONMENT') AND file_exists(APPPATH.'config/'.ENVIRONMENT.'/mimes.php')) - { - include APPPATH.'config/'.ENVIRONMENT.'/mimes.php'; - } - else - { - include APPPATH.'config/mimes.php'; - } + /** + * Class constructor + * + * Determines whether zLib output compression will be used. + * + * @return void + */ + public function __construct() + { + $this->_zlib_oc = (bool) ini_get('zlib.output_compression'); + $this->_compress_output = ( + $this->_zlib_oc === FALSE + && config_item('compress_output') === TRUE + && extension_loaded('zlib') + ); + isset(self::$func_overload) OR self::$func_overload = (extension_loaded('mbstring') && ini_get('mbstring.func_overload')); - $this->mime_types = $mimes; + // Get mime types for later + $this->mimes =& get_mimes(); - log_message('debug', "Output Class Initialized"); + log_message('info', 'Output Class Initialized'); } // -------------------------------------------------------------------- @@ -114,12 +158,11 @@ class CI_Output { /** * Get Output * - * Returns the current output string + * Returns the current output string. * - * @access public * @return string */ - function get_output() + public function get_output() { return $this->final_output; } @@ -129,16 +172,14 @@ class CI_Output { /** * Set Output * - * Sets the output string + * Sets the output string. * - * @access public - * @param string - * @return void + * @param string $output Output data + * @return CI_Output */ - function set_output($output) + public function set_output($output) { $this->final_output = $output; - return $this; } @@ -147,23 +188,14 @@ class CI_Output { /** * Append Output * - * Appends data onto the output string + * Appends data onto the output string. * - * @access public - * @param string - * @return void + * @param string $output Data to append + * @return CI_Output */ - function append_output($output) + public function append_output($output) { - if ($this->final_output == '') - { - $this->final_output = $output; - } - else - { - $this->final_output .= $output; - } - + $this->final_output .= $output; return $this; } @@ -172,52 +204,49 @@ class CI_Output { /** * Set Header * - * Lets you set a server header which will be outputted with the final display. + * Lets you set a server header which will be sent with the final output. * - * Note: If a file is cached, headers will not be sent. We need to figure out - * how to permit header data to be saved with the cache data... + * Note: If a file is cached, headers will not be sent. + * @todo We need to figure out how to permit headers to be cached. * - * @access public - * @param string - * @param bool - * @return void + * @param string $header Header + * @param bool $replace Whether to replace the old header value, if already set + * @return CI_Output */ - function set_header($header, $replace = TRUE) + public function set_header($header, $replace = TRUE) { // If zlib.output_compression is enabled it will compress the output, // but it will not modify the content-length header to compensate for // the reduction, causing the browser to hang waiting for more data. // We'll just skip content-length in those cases. - - if ($this->_zlib_oc && strncasecmp($header, 'content-length', 14) == 0) + if ($this->_zlib_oc && strncasecmp($header, 'content-length', 14) === 0) { - return; + return $this; } $this->headers[] = array($header, $replace); - return $this; } // -------------------------------------------------------------------- /** - * Set Content Type Header + * Set Content-Type Header * - * @access public - * @param string extension of the file we're outputting - * @return void + * @param string $mime_type Extension of the file we're outputting + * @param string $charset Character set (default: NULL) + * @return CI_Output */ - function set_content_type($mime_type) + public function set_content_type($mime_type, $charset = NULL) { if (strpos($mime_type, '/') === FALSE) { $extension = ltrim($mime_type, '.'); // Is this extension supported? - if (isset($this->mime_types[$extension])) + if (isset($this->mimes[$extension])) { - $mime_type =& $this->mime_types[$extension]; + $mime_type =& $this->mimes[$extension]; if (is_array($mime_type)) { @@ -226,28 +255,89 @@ class CI_Output { } } - $header = 'Content-Type: '.$mime_type; + $this->mime_type = $mime_type; - $this->headers[] = array($header, TRUE); + if (empty($charset)) + { + $charset = config_item('charset'); + } + + $header = 'Content-Type: '.$mime_type + .(empty($charset) ? '' : '; charset='.$charset); + $this->headers[] = array($header, TRUE); return $this; } // -------------------------------------------------------------------- /** + * Get Current Content-Type Header + * + * @return string 'text/html', if not already set + */ + public function get_content_type() + { + for ($i = 0, $c = count($this->headers); $i < $c; $i++) + { + if (sscanf($this->headers[$i][0], 'Content-Type: %[^;]', $content_type) === 1) + { + return $content_type; + } + } + + return 'text/html'; + } + + // -------------------------------------------------------------------- + + /** + * Get Header + * + * @param string $header + * @return string + */ + public function get_header($header) + { + // Combine headers already sent with our batched headers + $headers = array_merge( + // We only need [x][0] from our multi-dimensional array + array_map('array_shift', $this->headers), + headers_list() + ); + + if (empty($headers) OR empty($header)) + { + return NULL; + } + + // Count backwards, in order to get the last matching header + for ($c = count($headers) - 1; $c > -1; $c--) + { + if (strncasecmp($header, $headers[$c], $l = self::strlen($header)) === 0) + { + return trim(self::substr($headers[$c], $l+1)); + } + } + + return NULL; + } + + // -------------------------------------------------------------------- + + /** * Set HTTP Status Header - * moved to Common procedural functions in 1.7.2 * - * @access public - * @param int the status code - * @param string - * @return void + * As of version 1.7.2, this is an alias for common function + * set_status_header(). + * + * @param int $code Status code (default: 200) + * @param string $text Optional message + * @return CI_Output */ - function set_status_header($code = 200, $text = '') + public function set_status_header($code = 200, $text = '') { set_status_header($code, $text); - return $this; } @@ -256,14 +346,12 @@ class CI_Output { /** * Enable/disable Profiler * - * @access public - * @param bool - * @return void + * @param bool $val TRUE to enable or FALSE to disable + * @return CI_Output */ - function enable_profiler($val = TRUE) + public function enable_profiler($val = TRUE) { - $this->enable_profiler = (is_bool($val)) ? $val : TRUE; - + $this->enable_profiler = is_bool($val) ? $val : TRUE; return $this; } @@ -272,17 +360,23 @@ class CI_Output { /** * Set Profiler Sections * - * Allows override of default / config settings for Profiler section display + * Allows override of default/config settings for + * Profiler section display. * - * @access public - * @param array - * @return void + * @param array $sections Profiler sections + * @return CI_Output */ - function set_profiler_sections($sections) + public function set_profiler_sections($sections) { + if (isset($sections['query_toggle_count'])) + { + $this->_profiler_sections['query_toggle_count'] = (int) $sections['query_toggle_count']; + unset($sections['query_toggle_count']); + } + foreach ($sections as $section => $enable) { - $this->_profiler_sections[$section] = ($enable !== FALSE) ? TRUE : FALSE; + $this->_profiler_sections[$section] = ($enable !== FALSE); } return $this; @@ -293,14 +387,12 @@ class CI_Output { /** * Set Cache * - * @access public - * @param integer - * @return void + * @param int $time Cache expiration time in minutes + * @return CI_Output */ - function cache($time) + public function cache($time) { - $this->cache_expiration = ( ! is_numeric($time)) ? 0 : $time; - + $this->cache_expiration = is_numeric($time) ? $time : 0; return $this; } @@ -309,27 +401,27 @@ class CI_Output { /** * Display Output * - * All "view" data is automatically put into this variable by the controller class: - * - * $this->final_output + * Processes and sends finalized output data to the browser along + * with any server headers and profile data. It also stops benchmark + * timers so the page rendering speed and memory usage can be shown. * - * This function sends the finalized output data to the browser along - * with any server headers and profile data. It also stops the - * benchmark timer so the page rendering speed and memory usage can be shown. + * Note: All "view" data is automatically put into $this->final_output + * by controller class. * - * @access public - * @param string - * @return mixed + * @uses CI_Output::$final_output + * @param string $output Output data override + * @return void */ - function _display($output = '') + public function _display($output = '') { - // Note: We use globals because we can't use $CI =& get_instance() + // Note: We use load_class() because we can't use $CI =& get_instance() // since this function is sometimes called by the caching mechanism, // which happens before the CI super object is available. - global $BM, $CFG; + $BM =& load_class('Benchmark', 'core'); + $CFG =& load_class('Config', 'core'); // Grab the super object if we can. - if (class_exists('CI_Controller')) + if (class_exists('CI_Controller', FALSE)) { $CI =& get_instance(); } @@ -337,14 +429,14 @@ class CI_Output { // -------------------------------------------------------------------- // Set the output data - if ($output == '') + if ($output === '') { $output =& $this->final_output; } // -------------------------------------------------------------------- - // Do we need to write a cache file? Only if the controller does not have its + // Do we need to write a cache file? Only if the controller does not have its // own _output() method and we are not dealing with a cache file, which we // can determine by the existence of the $CI object above if ($this->cache_expiration > 0 && isset($CI) && ! method_exists($CI, '_output')) @@ -361,24 +453,18 @@ class CI_Output { if ($this->parse_exec_vars === TRUE) { - $memory = ( ! function_exists('memory_get_usage')) ? '0' : round(memory_get_usage()/1024/1024, 2).'MB'; - - $output = str_replace('{elapsed_time}', $elapsed, $output); - $output = str_replace('{memory_usage}', $memory, $output); + $memory = round(memory_get_usage() / 1024 / 1024, 2).'MB'; + $output = str_replace(array('{elapsed_time}', '{memory_usage}'), array($elapsed, $memory), $output); } // -------------------------------------------------------------------- // Is compression requested? - if ($CFG->item('compress_output') === TRUE && $this->_zlib_oc == FALSE) + if (isset($CI) // This means that we're not serving a cache file, if we were, it would already be compressed + && $this->_compress_output === TRUE + && isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE) { - if (extension_loaded('zlib')) - { - if (isset($_SERVER['HTTP_ACCEPT_ENCODING']) AND strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE) - { - ob_start('ob_gzhandler'); - } - } + ob_start('ob_gzhandler'); } // -------------------------------------------------------------------- @@ -399,20 +485,34 @@ class CI_Output { // simply echo out the data and exit. if ( ! isset($CI)) { + if ($this->_compress_output === TRUE) + { + if (isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE) + { + header('Content-Encoding: gzip'); + header('Content-Length: '.self::strlen($output)); + } + else + { + // User agent doesn't support gzip compression, + // so we'll have to decompress our cache + $output = gzinflate(self::substr($output, 10, -8)); + } + } + echo $output; - log_message('debug', "Final output sent to browser"); - log_message('debug', "Total execution time: ".$elapsed); - return TRUE; + log_message('info', 'Final output sent to browser'); + log_message('debug', 'Total execution time: '.$elapsed); + return; } // -------------------------------------------------------------------- // Do we need to generate profile data? // If so, load the Profile class and run it. - if ($this->enable_profiler == TRUE) + if ($this->enable_profiler === TRUE) { $CI->load->library('profiler'); - if ( ! empty($this->_profiler_sections)) { $CI->profiler->set_sections($this->_profiler_sections); @@ -420,20 +520,13 @@ class CI_Output { // If the output data contains closing </body> and </html> tags // we will remove them and add them back after we insert the profile data - if (preg_match("|</body>.*?</html>|is", $output)) + $output = preg_replace('|</body>.*?</html>|is', '', $output, -1, $count).$CI->profiler->run(); + if ($count > 0) { - $output = preg_replace("|</body>.*?</html>|is", '', $output); - $output .= $CI->profiler->run(); $output .= '</body></html>'; } - else - { - $output .= $CI->profiler->run(); - } } - // -------------------------------------------------------------------- - // Does the controller contain a function named _output()? // If so send the output there. Otherwise, echo it. if (method_exists($CI, '_output')) @@ -442,133 +535,308 @@ class CI_Output { } else { - echo $output; // Send it to the browser! + echo $output; // Send it to the browser! } - log_message('debug', "Final output sent to browser"); - log_message('debug', "Total execution time: ".$elapsed); + log_message('info', 'Final output sent to browser'); + log_message('debug', 'Total execution time: '.$elapsed); } // -------------------------------------------------------------------- /** - * Write a Cache File + * Write Cache * - * @access public - * @param string + * @param string $output Output data to cache * @return void */ - function _write_cache($output) + public function _write_cache($output) { $CI =& get_instance(); $path = $CI->config->item('cache_path'); - - $cache_path = ($path == '') ? APPPATH.'cache/' : $path; + $cache_path = ($path === '') ? APPPATH.'cache/' : $path; if ( ! is_dir($cache_path) OR ! is_really_writable($cache_path)) { - log_message('error', "Unable to write cache file: ".$cache_path); + log_message('error', 'Unable to write cache file: '.$cache_path); return; } - $uri = $CI->config->item('base_url'). - $CI->config->item('index_page'). - $CI->uri->uri_string(); + $uri = $CI->config->item('base_url') + .$CI->config->item('index_page') + .$CI->uri->uri_string(); + + if (($cache_query_string = $CI->config->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING'])) + { + if (is_array($cache_query_string)) + { + $uri .= '?'.http_build_query(array_intersect_key($_GET, array_flip($cache_query_string))); + } + else + { + $uri .= '?'.$_SERVER['QUERY_STRING']; + } + } $cache_path .= md5($uri); - if ( ! $fp = @fopen($cache_path, FOPEN_WRITE_CREATE_DESTRUCTIVE)) + if ( ! $fp = @fopen($cache_path, 'w+b')) { - log_message('error', "Unable to write cache file: ".$cache_path); + log_message('error', 'Unable to write cache file: '.$cache_path); return; } + if ( ! flock($fp, LOCK_EX)) + { + log_message('error', 'Unable to secure a file lock for file at: '.$cache_path); + fclose($fp); + return; + } + + // If output compression is enabled, compress the cache + // itself, so that we don't have to do that each time + // we're serving it + if ($this->_compress_output === TRUE) + { + $output = gzencode($output); + + if ($this->get_header('content-type') === NULL) + { + $this->set_content_type($this->mime_type); + } + } + $expire = time() + ($this->cache_expiration * 60); - if (flock($fp, LOCK_EX)) + // Put together our serialized info. + $cache_info = serialize(array( + 'expire' => $expire, + 'headers' => $this->headers + )); + + $output = $cache_info.'ENDCI--->'.$output; + + for ($written = 0, $length = self::strlen($output); $written < $length; $written += $result) { - fwrite($fp, $expire.'TS--->'.$output); - flock($fp, LOCK_UN); + if (($result = fwrite($fp, self::substr($output, $written))) === FALSE) + { + break; + } } - else + + flock($fp, LOCK_UN); + fclose($fp); + + if ( ! is_int($result)) { - log_message('error', "Unable to secure a file lock for file at: ".$cache_path); + @unlink($cache_path); + log_message('error', 'Unable to write the complete cache content at: '.$cache_path); return; } - fclose($fp); - @chmod($cache_path, FILE_WRITE_MODE); - log_message('debug', "Cache file written: ".$cache_path); + chmod($cache_path, 0640); + log_message('debug', 'Cache file written: '.$cache_path); + + // Send HTTP cache-control headers to browser to match file cache settings. + $this->set_cache_header($_SERVER['REQUEST_TIME'], $expire); } // -------------------------------------------------------------------- /** - * Update/serve a cached file + * Update/serve cached output * - * @access public - * @param object config class - * @param object uri class - * @return void + * @uses CI_Config + * @uses CI_URI + * + * @param object &$CFG CI_Config class instance + * @param object &$URI CI_URI class instance + * @return bool TRUE on success or FALSE on failure */ - function _display_cache(&$CFG, &$URI) + public function _display_cache(&$CFG, &$URI) { - $cache_path = ($CFG->item('cache_path') == '') ? APPPATH.'cache/' : $CFG->item('cache_path'); + $cache_path = ($CFG->item('cache_path') === '') ? APPPATH.'cache/' : $CFG->item('cache_path'); - // Build the file path. The file name is an MD5 hash of the full URI - $uri = $CFG->item('base_url'). - $CFG->item('index_page'). - $URI->uri_string; + // Build the file path. The file name is an MD5 hash of the full URI + $uri = $CFG->item('base_url').$CFG->item('index_page').$URI->uri_string; + + if (($cache_query_string = $CFG->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING'])) + { + if (is_array($cache_query_string)) + { + $uri .= '?'.http_build_query(array_intersect_key($_GET, array_flip($cache_query_string))); + } + else + { + $uri .= '?'.$_SERVER['QUERY_STRING']; + } + } $filepath = $cache_path.md5($uri); - if ( ! @file_exists($filepath)) + if ( ! file_exists($filepath) OR ! $fp = @fopen($filepath, 'rb')) { return FALSE; } - if ( ! $fp = @fopen($filepath, FOPEN_READ)) + flock($fp, LOCK_SH); + + $cache = (filesize($filepath) > 0) ? fread($fp, filesize($filepath)) : ''; + + flock($fp, LOCK_UN); + fclose($fp); + + // Look for embedded serialized file info. + if ( ! preg_match('/^(.*)ENDCI--->/', $cache, $match)) { return FALSE; } - flock($fp, LOCK_SH); + $cache_info = unserialize($match[1]); + $expire = $cache_info['expire']; + + $last_modified = filemtime($filepath); - $cache = ''; - if (filesize($filepath) > 0) + // Has the file expired? + if ($_SERVER['REQUEST_TIME'] >= $expire && is_really_writable($cache_path)) { - $cache = fread($fp, filesize($filepath)); + // If so we'll delete it. + @unlink($filepath); + log_message('debug', 'Cache file has expired. File deleted.'); + return FALSE; } - flock($fp, LOCK_UN); - fclose($fp); + // Send the HTTP cache control headers + $this->set_cache_header($last_modified, $expire); - // Strip out the embedded timestamp - if ( ! preg_match("/(\d+TS--->)/", $cache, $match)) + // Add headers from cache file. + foreach ($cache_info['headers'] as $header) { + $this->set_header($header[0], $header[1]); + } + + // Display the cache + $this->_display(self::substr($cache, self::strlen($match[0]))); + log_message('debug', 'Cache file is current. Sending it to browser.'); + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Delete cache + * + * @param string $uri URI string + * @return bool + */ + public function delete_cache($uri = '') + { + $CI =& get_instance(); + $cache_path = $CI->config->item('cache_path'); + if ($cache_path === '') + { + $cache_path = APPPATH.'cache/'; + } + + if ( ! is_dir($cache_path)) + { + log_message('error', 'Unable to find cache path: '.$cache_path); return FALSE; } - // Has the file expired? If so we'll delete it. - if (time() >= trim(str_replace('TS--->', '', $match['1']))) + if (empty($uri)) { - if (is_really_writable($cache_path)) + $uri = $CI->uri->uri_string(); + + if (($cache_query_string = $CI->config->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING'])) { - @unlink($filepath); - log_message('debug', "Cache file has expired. File deleted"); - return FALSE; + if (is_array($cache_query_string)) + { + $uri .= '?'.http_build_query(array_intersect_key($_GET, array_flip($cache_query_string))); + } + else + { + $uri .= '?'.$_SERVER['QUERY_STRING']; + } } } - // Display the cache - $this->_display(str_replace($match['0'], '', $cache)); - log_message('debug', "Cache file is current. Sending it to browser."); + $cache_path .= md5($CI->config->item('base_url').$CI->config->item('index_page').ltrim($uri, '/')); + + if ( ! @unlink($cache_path)) + { + log_message('error', 'Unable to delete cache file for '.$uri); + return FALSE; + } + return TRUE; } + // -------------------------------------------------------------------- -} -// END Output Class + /** + * Set Cache Header + * + * Set the HTTP headers to match the server-side file cache settings + * in order to reduce bandwidth. + * + * @param int $last_modified Timestamp of when the page was last modified + * @param int $expiration Timestamp of when should the requested page expire from cache + * @return void + */ + public function set_cache_header($last_modified, $expiration) + { + $max_age = $expiration - $_SERVER['REQUEST_TIME']; + + if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $last_modified <= strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])) + { + $this->set_status_header(304); + exit; + } + + header('Pragma: public'); + header('Cache-Control: max-age='.$max_age.', public'); + header('Expires: '.gmdate('D, d M Y H:i:s', $expiration).' GMT'); + header('Last-modified: '.gmdate('D, d M Y H:i:s', $last_modified).' GMT'); + } + + // -------------------------------------------------------------------- + + /** + * Byte-safe strlen() + * + * @param string $str + * @return int + */ + protected static function strlen($str) + { + return (self::$func_overload) + ? mb_strlen($str, '8bit') + : strlen($str); + } + + // -------------------------------------------------------------------- + + /** + * Byte-safe substr() + * + * @param string $str + * @param int $start + * @param int $length + * @return string + */ + protected static function substr($str, $start, $length = NULL) + { + if (self::$func_overload) + { + // mb_substr($str, $start, null, '8bit') returns an empty + // string on PHP 5.3 + isset($length) OR $length = ($start >= 0 ? self::strlen($str) - $start : -$start); + return mb_substr($str, $start, $length, '8bit'); + } -/* End of file Output.php */ -/* Location: ./system/core/Output.php */
\ No newline at end of file + return isset($length) + ? substr($str, $start, $length) + : substr($str, $start); + } +} |