123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617 |
- <?php
- /**
- * This file contains the error handler application component.
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @link http://www.yiiframework.com/
- * @copyright 2008-2013 Yii Software LLC
- * @license http://www.yiiframework.com/license/
- */
- Yii::import('CHtml',true);
- /**
- * CErrorHandler handles uncaught PHP errors and exceptions.
- *
- * It displays these errors using appropriate views based on the
- * nature of the error and the mode the application runs at.
- * It also chooses the most preferred language for displaying the error.
- *
- * CErrorHandler uses two sets of views:
- * <ul>
- * <li>development views, named as <code>exception.php</code>;
- * <li>production views, named as <code>error<StatusCode>.php</code>;
- * </ul>
- * where <StatusCode> stands for the HTTP error code (e.g. error500.php).
- * Localized views are named similarly but located under a subdirectory
- * whose name is the language code (e.g. zh_cn/error500.php).
- *
- * Development views are displayed when the application is in debug mode
- * (i.e. YII_DEBUG is defined as true). Detailed error information with source code
- * are displayed in these views. Production views are meant to be shown
- * to end-users and are used when the application is in production mode.
- * For security reasons, they only display the error message without any
- * sensitive information.
- *
- * CErrorHandler looks for the view templates from the following locations in order:
- * <ol>
- * <li><code>themes/ThemeName/views/system</code>: when a theme is active.</li>
- * <li><code>protected/views/system</code></li>
- * <li><code>framework/views</code></li>
- * </ol>
- * If the view is not found in a directory, it will be looked for in the next directory.
- *
- * The property {@link maxSourceLines} can be changed to specify the number
- * of source code lines to be displayed in development views.
- *
- * CErrorHandler is a core application component that can be accessed via
- * {@link CApplication::getErrorHandler()}.
- *
- * @property array $error The error details. Null if there is no error.
- * @property Exception|null $exception exception instance. Null if there is no exception.
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @package system.base
- * @since 1.0
- */
- class CErrorHandler extends CApplicationComponent
- {
- /**
- * @var integer maximum number of source code lines to be displayed. Defaults to 25.
- */
- public $maxSourceLines=25;
- /**
- * @var integer maximum number of trace source code lines to be displayed. Defaults to 10.
- * @since 1.1.6
- */
- public $maxTraceSourceLines = 10;
- /**
- * @var string the application administrator information (could be a name or email link). It is displayed in error pages to end users. Defaults to 'the webmaster'.
- */
- public $adminInfo='the webmaster';
- /**
- * @var boolean whether to discard any existing page output before error display. Defaults to true.
- */
- public $discardOutput=true;
- /**
- * @var string the route (eg 'site/error') to the controller action that will be used to display external errors.
- * Inside the action, it can retrieve the error information by Yii::app()->errorHandler->error.
- * This property defaults to null, meaning CErrorHandler will handle the error display.
- */
- public $errorAction;
- private $_error;
- private $_exception;
- /**
- * Handles the exception/error event.
- * This method is invoked by the application whenever it captures
- * an exception or PHP error.
- * @param CEvent $event the event containing the exception/error information
- */
- public function handle($event)
- {
- // set event as handled to prevent it from being handled by other event handlers
- $event->handled=true;
- if($this->discardOutput)
- {
- $gzHandler=false;
- foreach(ob_list_handlers() as $h)
- {
- if(strpos($h,'gzhandler')!==false)
- $gzHandler=true;
- }
- // the following manual level counting is to deal with zlib.output_compression set to On
- // for an output buffer created by zlib.output_compression set to On ob_end_clean will fail
- for($level=ob_get_level();$level>0;--$level)
- {
- if(!@ob_end_clean())
- @ob_clean();
- }
- // reset headers in case there was an ob_start("ob_gzhandler") before
- if($gzHandler && !headers_sent() && ob_list_handlers()===array())
- {
- if(function_exists('header_remove')) // php >= 5.3
- {
- header_remove('Vary');
- header_remove('Content-Encoding');
- }
- else
- {
- header('Vary:');
- header('Content-Encoding:');
- }
- }
- }
- if($event instanceof CExceptionEvent)
- $this->handleException($event->exception);
- else // CErrorEvent
- $this->handleError($event);
- }
- /**
- * Returns the details about the error that is currently being handled.
- * The error is returned in terms of an array, with the following information:
- * <ul>
- * <li>code - the HTTP status code (e.g. 403, 500)</li>
- * <li>type - the error type (e.g. 'CHttpException', 'PHP Error')</li>
- * <li>message - the error message</li>
- * <li>file - the name of the PHP script file where the error occurs</li>
- * <li>line - the line number of the code where the error occurs</li>
- * <li>trace - the call stack of the error</li>
- * <li>source - the context source code where the error occurs</li>
- * </ul>
- * @return array the error details. Null if there is no error.
- */
- public function getError()
- {
- return $this->_error;
- }
- /**
- * Returns the instance of the exception that is currently being handled.
- * @return Exception|null exception instance. Null if there is no exception.
- */
- public function getException()
- {
- return $this->_exception;
- }
- /**
- * Handles the exception.
- * @param Exception $exception the exception captured
- */
- protected function handleException($exception)
- {
- $app=Yii::app();
- if($app instanceof CWebApplication)
- {
- if(($trace=$this->getExactTrace($exception))===null)
- {
- $fileName=$exception->getFile();
- $errorLine=$exception->getLine();
- }
- else
- {
- $fileName=$trace['file'];
- $errorLine=$trace['line'];
- }
- $trace = $exception->getTrace();
- foreach($trace as $i=>$t)
- {
- if(!isset($t['file']))
- $trace[$i]['file']='unknown';
- if(!isset($t['line']))
- $trace[$i]['line']=0;
- if(!isset($t['function']))
- $trace[$i]['function']='unknown';
- unset($trace[$i]['object']);
- }
- $this->_exception=$exception;
- $this->_error=$data=array(
- 'code'=>($exception instanceof CHttpException)?$exception->statusCode:500,
- 'type'=>get_class($exception),
- 'errorCode'=>$exception->getCode(),
- 'message'=>$exception->getMessage(),
- 'file'=>$fileName,
- 'line'=>$errorLine,
- 'trace'=>$exception->getTraceAsString(),
- 'traces'=>$trace,
- );
- if(!headers_sent())
- {
- $httpVersion=Yii::app()->request->getHttpVersion();
- header("HTTP/$httpVersion {$data['code']} ".$this->getHttpHeader($data['code'], get_class($exception)));
- }
- $this->renderException();
- }
- else
- $app->displayException($exception);
- }
- /**
- * Handles the PHP error.
- * @param CErrorEvent $event the PHP error event
- */
- protected function handleError($event)
- {
- $trace=debug_backtrace();
- // skip the first 3 stacks as they do not tell the error position
- if(count($trace)>3)
- $trace=array_slice($trace,3);
- $traceString='';
- foreach($trace as $i=>$t)
- {
- if(!isset($t['file']))
- $trace[$i]['file']='unknown';
- if(!isset($t['line']))
- $trace[$i]['line']=0;
- if(!isset($t['function']))
- $trace[$i]['function']='unknown';
- $traceString.="#$i {$trace[$i]['file']}({$trace[$i]['line']}): ";
- if(isset($t['object']) && is_object($t['object']))
- $traceString.=get_class($t['object']).'->';
- $traceString.="{$trace[$i]['function']}()\n";
- unset($trace[$i]['object']);
- }
- $app=Yii::app();
- if($app instanceof CWebApplication)
- {
- switch($event->code)
- {
- case E_WARNING:
- $type = 'PHP warning';
- break;
- case E_NOTICE:
- $type = 'PHP notice';
- break;
- case E_USER_ERROR:
- $type = 'User error';
- break;
- case E_USER_WARNING:
- $type = 'User warning';
- break;
- case E_USER_NOTICE:
- $type = 'User notice';
- break;
- case E_RECOVERABLE_ERROR:
- $type = 'Recoverable error';
- break;
- default:
- $type = 'PHP error';
- }
- $this->_exception=null;
- $this->_error=array(
- 'code'=>500,
- 'type'=>$type,
- 'message'=>$event->message,
- 'file'=>$event->file,
- 'line'=>$event->line,
- 'trace'=>$traceString,
- 'traces'=>$trace,
- );
- if(!headers_sent())
- {
- $httpVersion=Yii::app()->request->getHttpVersion();
- header("HTTP/$httpVersion 500 Internal Server Error");
- }
- $this->renderError();
- }
- else
- $app->displayError($event->code,$event->message,$event->file,$event->line);
- }
- /**
- * whether the current request is an AJAX (XMLHttpRequest) request.
- * @return boolean whether the current request is an AJAX request.
- */
- protected function isAjaxRequest()
- {
- return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH']==='XMLHttpRequest';
- }
- /**
- * Returns the exact trace where the problem occurs.
- * @param Exception $exception the uncaught exception
- * @return array the exact trace where the problem occurs
- */
- protected function getExactTrace($exception)
- {
- $traces=$exception->getTrace();
- foreach($traces as $trace)
- {
- // property access exception
- if(isset($trace['function']) && ($trace['function']==='__get' || $trace['function']==='__set'))
- return $trace;
- }
- return null;
- }
- /**
- * Renders the view.
- * @param string $view the view name (file name without extension).
- * See {@link getViewFile} for how a view file is located given its name.
- * @param array $data data to be passed to the view
- */
- protected function render($view,$data)
- {
- $data['version']=$this->getVersionInfo();
- $data['time']=time();
- $data['admin']=$this->adminInfo;
- include($this->getViewFile($view,$data['code']));
- }
- /**
- * Renders the exception information.
- * This method will display information from current {@link error} value.
- */
- protected function renderException()
- {
- $exception=$this->getException();
- if($exception instanceof CHttpException || !YII_DEBUG)
- $this->renderError();
- else
- {
- if($this->isAjaxRequest())
- Yii::app()->displayException($exception);
- else
- $this->render('exception',$this->getError());
- }
- }
- /**
- * Renders the current error information.
- * This method will display information from current {@link error} value.
- */
- protected function renderError()
- {
- if($this->errorAction!==null)
- Yii::app()->runController($this->errorAction);
- else
- {
- $data=$this->getError();
- if($this->isAjaxRequest())
- Yii::app()->displayError($data['code'],$data['message'],$data['file'],$data['line']);
- elseif(YII_DEBUG)
- $this->render('exception',$data);
- else
- $this->render('error',$data);
- }
- }
- /**
- * Determines which view file should be used.
- * @param string $view view name (either 'exception' or 'error')
- * @param integer $code HTTP status code
- * @return string view file path
- */
- protected function getViewFile($view,$code)
- {
- $viewPaths=array(
- Yii::app()->getTheme()===null ? null : Yii::app()->getTheme()->getSystemViewPath(),
- Yii::app() instanceof CWebApplication ? Yii::app()->getSystemViewPath() : null,
- YII_PATH.DIRECTORY_SEPARATOR.'views',
- );
- foreach($viewPaths as $i=>$viewPath)
- {
- if($viewPath!==null)
- {
- $viewFile=$this->getViewFileInternal($viewPath,$view,$code,$i===2?'en_us':null);
- if(is_file($viewFile))
- return $viewFile;
- }
- }
- }
- /**
- * Looks for the view under the specified directory.
- * @param string $viewPath the directory containing the views
- * @param string $view view name (either 'exception' or 'error')
- * @param integer $code HTTP status code
- * @param string $srcLanguage the language that the view file is in
- * @return string view file path
- */
- protected function getViewFileInternal($viewPath,$view,$code,$srcLanguage=null)
- {
- $app=Yii::app();
- if($view==='error')
- {
- $viewFile=$app->findLocalizedFile($viewPath.DIRECTORY_SEPARATOR."error{$code}.php",$srcLanguage);
- if(!is_file($viewFile))
- $viewFile=$app->findLocalizedFile($viewPath.DIRECTORY_SEPARATOR.'error.php',$srcLanguage);
- }
- else
- $viewFile=$viewPath.DIRECTORY_SEPARATOR."exception.php";
- return $viewFile;
- }
- /**
- * Returns server version information.
- * If the application is in production mode, empty string is returned.
- * @return string server version information. Empty if in production mode.
- */
- protected function getVersionInfo()
- {
- if(YII_DEBUG)
- {
- $version='<a href="http://www.yiiframework.com/">Yii Framework</a>/'.Yii::getVersion();
- if(isset($_SERVER['SERVER_SOFTWARE']))
- $version=$_SERVER['SERVER_SOFTWARE'].' '.$version;
- }
- else
- $version='';
- return $version;
- }
- /**
- * Converts arguments array to its string representation
- *
- * @param array $args arguments array to be converted
- * @return string string representation of the arguments array
- */
- protected function argumentsToString($args)
- {
- $count=0;
- $isAssoc=$args!==array_values($args);
- foreach($args as $key => $value)
- {
- $count++;
- if($count>=5)
- {
- if($count>5)
- unset($args[$key]);
- else
- $args[$key]='...';
- continue;
- }
- if(is_object($value))
- $args[$key] = get_class($value);
- elseif(is_bool($value))
- $args[$key] = $value ? 'true' : 'false';
- elseif(is_string($value))
- {
- if(strlen($value)>64)
- $args[$key] = '"'.substr($value,0,64).'..."';
- else
- $args[$key] = '"'.$value.'"';
- }
- elseif(is_array($value))
- $args[$key] = 'array('.$this->argumentsToString($value).')';
- elseif($value===null)
- $args[$key] = 'null';
- elseif(is_resource($value))
- $args[$key] = 'resource';
- if(is_string($key))
- {
- $args[$key] = '"'.$key.'" => '.$args[$key];
- }
- elseif($isAssoc)
- {
- $args[$key] = $key.' => '.$args[$key];
- }
- }
- $out = implode(", ", $args);
- return $out;
- }
- /**
- * Returns a value indicating whether the call stack is from application code.
- * @param array $trace the trace data
- * @return boolean whether the call stack is from application code.
- */
- protected function isCoreCode($trace)
- {
- if(isset($trace['file']))
- {
- $systemPath=realpath(dirname(__FILE__).'/..');
- return $trace['file']==='unknown' || strpos(realpath($trace['file']),$systemPath.DIRECTORY_SEPARATOR)===0;
- }
- return false;
- }
- /**
- * Renders the source code around the error line.
- * @param string $file source file path
- * @param integer $errorLine the error line number
- * @param integer $maxLines maximum number of lines to display
- * @return string the rendering result
- */
- protected function renderSourceCode($file,$errorLine,$maxLines)
- {
- $errorLine--; // adjust line number to 0-based from 1-based
- if($errorLine<0 || ($lines=@file($file))===false || ($lineCount=count($lines))<=$errorLine)
- return '';
- $halfLines=(int)($maxLines/2);
- $beginLine=$errorLine-$halfLines>0 ? $errorLine-$halfLines:0;
- $endLine=$errorLine+$halfLines<$lineCount?$errorLine+$halfLines:$lineCount-1;
- $lineNumberWidth=strlen($endLine+1);
- $output='';
- for($i=$beginLine;$i<=$endLine;++$i)
- {
- $isErrorLine = $i===$errorLine;
- $code=sprintf("<span class=\"ln".($isErrorLine?' error-ln':'')."\">%0{$lineNumberWidth}d</span> %s",$i+1,CHtml::encode(str_replace("\t",' ',$lines[$i])));
- if(!$isErrorLine)
- $output.=$code;
- else
- $output.='<span class="error">'.$code.'</span>';
- }
- return '<div class="code"><pre>'.$output.'</pre></div>';
- }
- /**
- * Return correct message for each known http error code
- * @param integer $httpCode error code to map
- * @param string $replacement replacement error string that is returned if code is unknown
- * @return string the textual representation of the given error code or the replacement string if the error code is unknown
- */
- protected function getHttpHeader($httpCode, $replacement='')
- {
- $httpCodes = array(
- 100 => 'Continue',
- 101 => 'Switching Protocols',
- 102 => 'Processing',
- 118 => 'Connection timed out',
- 200 => 'OK',
- 201 => 'Created',
- 202 => 'Accepted',
- 203 => 'Non-Authoritative',
- 204 => 'No Content',
- 205 => 'Reset Content',
- 206 => 'Partial Content',
- 207 => 'Multi-Status',
- 210 => 'Content Different',
- 300 => 'Multiple Choices',
- 301 => 'Moved Permanently',
- 302 => 'Found',
- 303 => 'See Other',
- 304 => 'Not Modified',
- 305 => 'Use Proxy',
- 307 => 'Temporary Redirect',
- 310 => 'Too many Redirect',
- 400 => 'Bad Request',
- 401 => 'Unauthorized',
- 402 => 'Payment Required',
- 403 => 'Forbidden',
- 404 => 'Not Found',
- 405 => 'Method Not Allowed',
- 406 => 'Not Acceptable',
- 407 => 'Proxy Authentication Required',
- 408 => 'Request Time-out',
- 409 => 'Conflict',
- 410 => 'Gone',
- 411 => 'Length Required',
- 412 => 'Precondition Failed',
- 413 => 'Request Entity Too Large',
- 414 => 'Request-URI Too Long',
- 415 => 'Unsupported Media Type',
- 416 => 'Requested range unsatisfiable',
- 417 => 'Expectation failed',
- 418 => 'I’m a teapot',
- 422 => 'Unprocessable entity',
- 423 => 'Locked',
- 424 => 'Method failure',
- 425 => 'Unordered Collection',
- 426 => 'Upgrade Required',
- 449 => 'Retry With',
- 450 => 'Blocked by Windows Parental Controls',
- 500 => 'Internal Server Error',
- 501 => 'Not Implemented',
- 502 => 'Bad Gateway ou Proxy Error',
- 503 => 'Service Unavailable',
- 504 => 'Gateway Time-out',
- 505 => 'HTTP Version not supported',
- 507 => 'Insufficient storage',
- 509 => 'Bandwidth Limit Exceeded',
- );
- if(isset($httpCodes[$httpCode]))
- return $httpCodes[$httpCode];
- else
- return $replacement;
- }
- }
|