[/**

* CodeIgniter

*

* An open source application development framework for

*

* @package CodeIgniter

* @author ExpressionEngine Dev Team

* @copyright Copyright (c) 2008 - 2011, EllisLab, Inc.

* @license http://codeigniter.com/user_guide/license.html

* @link http://codeigniter.com

* @since Version 1.0

* @filesource

*/

// ------------------------------------------------------------------------

/**

* Output Class

*

* Responsible 负责 for sending final output to browser

* 负责把最终的输出发送到浏览器

* @package CodeIgniter

* @subpackage Libraries

* @category Output

* @author ExpressionEngine Dev Team

* @link http://codeigniter.com/user_guide/libraries/output.html

*/

class CI_Output {

/**

* Current output string

* 当前输出的字符串

*

* @var string

* @access protected

*/

protected $final_output;

/**

* Cache expiration time

* 缓存终结的时间

* @var int

* @access protected

*/

protected $cache_expiration = 0;

/**

* List of server headers

* 服务器头列表

* @var array

* @access protected

*/

protected $headers = array();

/**

* List of mime types

*

* @var array

* @access protected

*/

protected $mime_types = array();

/**

* Determines wether profiler is enabled

* 是否启用分析器

* @var book

* @access protected

*/

protected $enable_profiler = FALSE;

/**

* Determines if output compression is enabled

* 是否开启输出压缩

* @var bool

* @access protected

*/

protected $_zlib_oc = FALSE;

/**

* List of profiler sections

* 分析器列表

*

* @var array

* @access protected

*/

protected $_profiler_sections = array();

/**

* Whether or not to parse variables like {elapsed_time} and {memory_usage}

* 是否解析变量{elapsed_time} and {memory_usage}

* 注意文档说这里有错误详见http://codeigniter.org.cn/user_guide/libraries/output.html

* 最下方

* @var bool

* @access protected

*/

protected $parse_exec_vars = TRUE;

/**

* Constructor

*

*/

function __construct()

{

// 返回配置项zlib.output_compression的值并赋给$this->_zlib_oc

// 如果配置项中开启了输出压缩功能则 $this->_zlib_oc 的值为on

$this->_zlib_oc = @ini_get('zlib.output_compression');

// Get mime types for later

// 获取mimetype

if (defined('ENVIRONMENT') AND file_exists(APPPATH.'config/'.ENVIRONMENT.'/mimes.

{

include APPPATH.'config/'.ENVIRONMENT.'/mimes.

}

else

{

include APPPATH.'config/mimes.

}

// $mimes 是mimes.

$this->mime_types = $mimes;

log_message('debug', "Output Class Initialized");

}

// --------------------------------------------------------------------

/**

* Get Output

* 使用这个方法,你可以得到将要输出的数据,并把它保存起来

* Returns the current output string

* 返回当前输出的字符串

* @access public

* @return string

*/

function get_output()

{

return $this->final_output;

}

// --------------------------------------------------------------------

/**

* Set Output

*

* Sets the output string

* 设置输出的字符串

* @access public

* @param string

* @return void

*/

function set_output($output)

{

$this->final_output = $output;

return $this;

}

// --------------------------------------------------------------------

/**

* Append Output

* 在最终输出字符串后,追加数据

* Appends data onto the output string

*

* @access public

* @param string

* @return void

*/

function append_output($output)

{

if ($this->final_output == '')

{

$this->final_output = $output;

}

else

{

$this->final_output .= $output;

}

return $this;

}

// --------------------------------------------------------------------

/**

* Set Header

* 使用此方法,允许你设置将会被发送到浏览器的HTTP协议的标头,作用相当于

* Lets you set a server header which will be outputted with the final display.

* 允许您设置一个服务器头用于最终的显示输出。

* 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...

*

* @access public

* @param string

* @param bool

* @return void

*/

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)

{

return;

}

$this->headers[] = array($header, $replace);

return $this;

}

// --------------------------------------------------------------------

/**

* Set Content Type Header

* 设置Content-Type

* @access public

* @param string extension of the file we're outputting

* @return void

*/

function set_content_type($mime_type)

{

if (strpos($mime_type, '/') === FALSE)

{

$extension = ltrim($mime_type, '.');

// Is this extension supported?

if (isset($this->mime_types[$extension]))

{

$mime_type =& $this->mime_types[$extension];

if (is_array($mime_type))

{

$mime_type = current($mime_type);

}

}

}

$header = 'Content-Type: '.$mime_type;

$this->headers[] = array($header, TRUE);

return $this;

}

// --------------------------------------------------------------------

/**

* Set HTTP Status Header

* moved to Common procedural functions in 1.7.2

* 允许你手动设置服务器状态头(header)

* @access public

* @param int the status code

* @param string

* @return void

*/

function set_status_header($code = 200, $text = '')

{

set_status_header($code, $text);

return $this;

}

// --------------------------------------------------------------------

/**

* Enable/disable Profiler

* 允许你开启或禁用分析器

* @access public

* @param bool

* @return void

*/

function enable_profiler($val = TRUE)

{

$this->enable_profiler = (is_bool($val)) ? $val : TRUE;

return $this;

}

// --------------------------------------------------------------------

/**

* Set Profiler Sections

* 设置$this->_profiler_sections

* Allows override of default / config settings for Profiler section display

* 允许你在评测器启用时,控制(开/关)其特定部分

*

* @access public

* @param array

* @return void

*/

function set_profiler_sections($sections)

{

foreach ($sections as $section => $enable)

{

$this->_profiler_sections[$section] = ($enable !== FALSE) ? TRUE : FALSE;

}

return $this;

}

// --------------------------------------------------------------------

/**

* Set Cache

* 设置缓存以及缓存时间

* @access public

* @param integer 其中 $time 是你希望缓存更新的 分钟 数

* @return void

*/

function cache($time)

{

$this->cache_expiration = ( ! is_numeric($time)) ? 0 : $time;

return $this;

}

// --------------------------------------------------------------------

/**

* Display Output

* 显示输出

* All "view" data is automatically put into this variable by the controller class:

*

* $this->final_output

*

* 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.

*

* @access public

* @param string

* @return mixed

*/

function _display($output = '')

{

// Note: We use globals 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 是因为我们不能使用$CI =& get_instance()

global $BM, $CFG;

// Grab the super object if we can.

// //当然如果可以拿到超级控制器,我们先拿过来。

if (class_exists('CI_Controller'))

{

$CI =& get_instance();

}

// --------------------------------------------------------------------

// Set the output data

// 设置输出数据

if ($output == '')

{

$output =& $this->final_output;

}

// --------------------------------------------------------------------

// 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

// 如果缓存时间>0 ,$CI 超级对象存在并且超级对象下面存在_output 方法

// 调用_write_cache 方法,写一个缓存文件

if ($this->cache_expiration > 0 && isset($CI) && ! method_exists($CI, '_output'))

{

$this->_write_cache($output);

}

// --------------------------------------------------------------------

// Parse out the elapsed time and memory usage,

// then swap the pseudo-variables with the data

// 计算代码执行时间和内存使用时间

$elapsed = $BM->elapsed_time('total_execution_time_start', 'total_execution_time_end');

// 如果$this->parse_exec_vars为true,将输出中的{elapsed_time},{memory_usage}

// 替换为计算出的时间。

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);

}

// --------------------------------------------------------------------

// Is compression requested?压缩传输的处理。

if ($CFG->item('compress_output') === TRUE && $this->_zlib_oc == FALSE)

{

if (extension_loaded('zlib'))

{

if (isset($_SERVER['HTTP_ACCEPT_ENCODING']) AND strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE)

{

ob_start('ob_gzhandler');

}

}

}

// --------------------------------------------------------------------

// Are there any server headers to send?

// 有没有服务器头发送?

if (count($this->headers) > 0)

{

foreach ($this->headers as $header)

{

@header($header[0], $header[1]);

}

}

// --------------------------------------------------------------------

// Does the $CI object exist?

// If not we know we are dealing with a cache file so we'll

// simply echo out the data and exit.

// 如果没有$CI就证明当前是一个缓存的输出,我们只简单的发送数据并退出

if ( ! isset($CI))

{

echo $output;

log_message('debug', "Final output sent to browser");

log_message('debug', "Total execution time: ".$elapsed);

return TRUE;

}

// --------------------------------------------------------------------

// Do we need to generate profile data?

// If so, load the Profile class and run it.

// 如果开启了性能分析我们就调用,

// 会生成一些报告到页面尾部用于辅助我们调试。

if ($this->enable_profiler == TRUE)

{

$CI->load->library('profiler');

if ( ! emptyempty($this->_profiler_sections))

{

$CI->profiler->set_sections($this->_profiler_sections);

}

// If the output data contains closing and tags

// we will remove them and add them back after we insert the profile data

// 如果存在标签,我们将删除,插入我们的性能分析代码后再添加回去

if (preg_match("|.*?|is", $output))

{

$output = preg_replace("|.*?|is", '', $output);

$output .= $CI->profiler->run();

$output .= '';

}

else

{

$output .= $CI->profiler->run();

}

}

// --------------------------------------------------------------------

// Does the controller contain a function named _output()?

// If so send the output there. Otherwise, echo it.

// 如果控制器中有_output 这个方法我们直接调用

// 如果没有直接发送数据到浏览器

if (method_exists($CI, '_output'))

{

$CI->_output($output);

}

else

{

echo $output; // Send it to the browser!

}

log_message('debug', "Final output sent to browser");

log_message('debug', "Total execution time: ".$elapsed);

}

// --------------------------------------------------------------------

/**

* Write a Cache File

* 生成一个缓存文件

* @access public

* @param string

* @return void

*/

function _write_cache($output)

{

// 调用超级对象

$CI =& get_instance();

// 找到缓存路径

$path = $CI->config->item('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);

return;

}

// 获取$uri

$uri = $CI->config->item('base_url').

$CI->config->item('index_page').

$CI->uri->uri_string();

// 生成缓存文件。

$cache_path .= md5($uri);

if ( ! $fp = @fopen($cache_path, FOPEN_WRITE_CREATE_DESTRUCTIVE))

{

log_message('error', "Unable to write cache file: ".$cache_path);

return;

}

// 计算缓存文件过期时间。

$expire = time() + ($this->cache_expiration * 60);

if (flock($fp, LOCK_EX))

{

fwrite($fp, $expire.'TS--->'.$output);

flock($fp, LOCK_UN);

}

else

{

log_message('error', "Unable to secure a file lock for file at: ".$cache_path);

return;

}

fclose($fp);

@chmod($cache_path, FILE_WRITE_MODE);

log_message('debug', "Cache file written: ".$cache_path);

}

// --------------------------------------------------------------------

/**

* Update/serve a cached file

* 在CodeIgniter.

* 如果在CodeIgniter.

* 本次请求的运行将直接结束,直接以缓存输出作为响应。

*

* @access public

* @param object config class

* @param object uri class

* @return void

*/

function _display_cache(&$CFG, &$URI)

{

// 取得保存缓存的路径

$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

// 一条准确的路由都会对应一个缓存文件,缓存文件是对应路由字符串的md5密文。

$uri = $CFG->item('base_url').

$CFG->item('index_page').

$URI->uri_string;

$filepath = $cache_path.md5($uri);

// 如果没有此缓存文件,获取缓存内容失败,则可以返回FALSE。

if ( ! @file_exists($filepath))

{

return FALSE;

}

// 如果有此缓存文件,但是无法读,获取缓存内容失败,同样返回FALSE。

if ( ! $fp = @fopen($filepath, FOPEN_READ))

{

return FALSE;

}

// 打开到缓存文件,并以$fp作为句柄。下一步先取得共享锁(读取)。

flock($fp, LOCK_SH);

$cache = '';

if (filesize($filepath) > 0)

{

$cache = fread($fp, filesize($filepath));

}

// 解锁

flock($fp, LOCK_UN);

// 关闭文件连接。

fclose($fp);

// 下面这个TS--->字样,只是因为CI的缓存文件里面的内容

// 是规定以数字+TS--->开头而已。这个数字是代表创建时间。

// 如果不符合此结构,可视为非CI的缓存文件,或者文件已损坏,

// 获取缓存内容失败,返回FALSE。

// Strip out the embedded timestamp

if ( ! preg_match("/(\d+TS--->)/", $cache, $match))

{

return FALSE;

}

// Has the file expired? If so we'll delete it.

// 判断缓存是否过期,如果过期我们将删除它

if (time() >= trim(str_replace('TS--->', '', $match['1'])))

{

if (is_really_writable($cache_path))

{

@unlink($filepath);

log_message('debug', "Cache file has expired. File deleted");

return FALSE;

}

}

// Display the cache

$this->_display(str_replace($match['0'], '', $cache));

log_message('debug', "Cache file is current. Sending it to browser.");

return TRUE;

}

}

// END Output Class

/* End of file Output.

/* Location: ./system/core/Output.

08-23 14:52