diff --git a/framework/yii/base/ErrorHandler.php b/framework/yii/base/ErrorHandler.php index 8340723..7db5161 100644 --- a/framework/yii/base/ErrorHandler.php +++ b/framework/yii/base/ErrorHandler.php @@ -7,6 +7,8 @@ namespace yii\base; +use Yii; + /** * ErrorHandler handles uncaught PHP errors and exceptions. * @@ -14,6 +16,7 @@ namespace yii\base; * nature of the errors and the mode the application runs at. * * @author Qiang Xue + * @author Timur Ruziev * @since 2.0 */ class ErrorHandler extends Component @@ -31,49 +34,50 @@ class ErrorHandler extends Component */ public $discardExistingOutput = 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 ErrorHandler will handle the error display. + * @var string the route (e.g. '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 ErrorHandler + * will handle the error display. */ public $errorAction; /** - * @var string the path of the view file for rendering exceptions + * @var string the path of the view file for rendering exceptions and errors. */ - public $exceptionView = '@yii/views/exception.php'; + public $mainView = '@yii/views/errorHandler/main.php'; /** - * @var string the path of the view file for rendering errors + * @var string the path of the view file for rendering exceptions and errors call stack element. */ - public $errorView = '@yii/views/error.php'; + public $callStackItemView = '@yii/views/errorHandler/callStackItem.php'; /** - * @var \Exception the exception that is being handled currently + * @var \Exception the exception that is being handled currently. */ public $exception; /** - * Handles exception - * @param \Exception $exception + * Handles exception. + * @param \Exception $exception to be handled. */ public function handle($exception) { $this->exception = $exception; - if ($this->discardExistingOutput) { $this->clearOutput(); } - $this->renderException($exception); } /** - * Renders exception - * @param \Exception $exception + * Renders exception. + * @param \Exception $exception to be handled. */ protected function renderException($exception) { if ($this->errorAction !== null) { - \Yii::$app->runAction($this->errorAction); - } elseif (\Yii::$app instanceof \yii\web\Application) { + Yii::$app->runAction($this->errorAction); + } elseif (!(Yii::$app instanceof \yii\web\Application)) { + Yii::$app->renderException($exception); + } else { if (!headers_sent()) { if ($exception instanceof HttpException) { header('HTTP/1.0 ' . $exception->statusCode . ' ' . $exception->getName()); @@ -82,7 +86,7 @@ class ErrorHandler extends Component } } if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') { - \Yii::$app->renderException($exception); + Yii::$app->renderException($exception); } else { // if there is an error during error rendering it's useful to // display PHP error in debug mode instead of a blank screen @@ -90,194 +94,145 @@ class ErrorHandler extends Component ini_set('display_errors', 1); } - $view = new View; - if (!YII_DEBUG || $exception instanceof UserException) { - $viewName = $this->errorView; - } else { - $viewName = $this->exceptionView; + $view = new View(); + $request = ''; + foreach (array('GET', 'POST', 'SERVER', 'FILES', 'COOKIE', 'SESSION', 'ENV') as $name) { + if (!empty($GLOBALS['_' . $name])) { + $request .= '$_' . $name . ' = ' . var_export($GLOBALS['_' . $name], true) . ";\n\n"; + } } - echo $view->renderFile($viewName, array( + $request = rtrim($request, "\n\n"); + echo $view->renderFile($this->mainView, array( 'exception' => $exception, + 'request' => $request, ), $this); } - } else { - \Yii::$app->renderException($exception); } } /** - * Returns server and Yii version information. - * @return string server version information. + * Converts special characters to HTML entities. + * @param string $text to encode. + * @return string encoded original text. */ - public function getVersionInfo() + public function htmlEncode($text) { - $version = 'Yii Framework/' . \Yii::getVersion(); - if (isset($_SERVER['SERVER_SOFTWARE'])) { - $version = $_SERVER['SERVER_SOFTWARE'] . ' ' . $version; - } - return $version; + return htmlspecialchars($text, ENT_QUOTES, Yii::$app->charset); } /** - * Converts arguments array to its string representation - * - * @param array $args arguments array to be converted - * @return string string representation of the arguments array + * Removes all output echoed before calling this method. */ - public function argumentsToString($args) + public function clearOutput() { - $isAssoc = $args !== array_values($args); - $count = 0; - 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]; - } + // the following manual level counting is to deal with zlib.output_compression set to On + for ($level = ob_get_level(); $level > 0; --$level) { + @ob_end_clean(); } - return implode(', ', $args); } /** - * 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. + * Adds informational links to the given PHP type/class. + * @param string $code type/class name to be linkified. + * @return string linkified with HTML type/class name. */ - public function isCoreCode($trace) + public function addTypeLinks($code) { - if (isset($trace['file'])) { - return $trace['file'] === 'unknown' || strpos(realpath($trace['file']), YII_PATH . DIRECTORY_SEPARATOR) === 0; + $html = ''; + if (strpos($code, '\\') !== false) { + // namespaced class + foreach (explode('\\', $code) as $part) { + $html .= '' . $this->htmlEncode($part) . '\\'; + } + $html = rtrim($html, '\\'); } - return false; + return $html; } /** - * 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 + * Creates HTML containing link to the page with the information on given HTTP status code. + * @param integer $statusCode to be used to generate information link. + * @return string generated HTML with HTTP status code information. */ - public function renderSourceCode($file, $errorLine, $maxLines) + public function createHttpStatusLink($statusCode) { - $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("%0{$lineNumberWidth}d %s", $i + 1, $this->htmlEncode(str_replace("\t", ' ', $lines[$i]))); - if (!$isErrorLine) { - $output .= $code; - } else { - $output .= '' . $code . ''; - } - } - echo '
' . $output . '
'; + return '' . (int)$statusCode . ''; } /** - * Renders calls stack trace - * @param array $trace + * Renders a single call stack element. + * @param string $file name where call has happened. + * @param integer $line number on which call has happened. + * @param integer $index number of the call stack element. + * @return string HTML content of the rendered call stack element. */ - public function renderTrace($trace) + public function renderCallStackItem($file, $line, $index) { - $count = 0; - echo "\n"; - foreach ($trace as $n => $t) { - if ($this->isCoreCode($t)) { - $cssClass = 'core collapsed'; - } elseif (++$count > 3) { - $cssClass = 'app collapsed'; - } else { - $cssClass = 'app expanded'; - } - - $hasCode = isset($t['file']) && $t['file'] !== 'unknown' && is_file($t['file']); - echo "\n"; + $line--; // adjust line number from one-based to zero-based + $lines = @file($file); + if ($line < 0 || $lines === false || ($lineCount = count($lines)) < $line + 1) { + return ''; } - echo '
#$n"; - echo '
'; - if ($hasCode) { - echo '
+
-
'; - } - echo ' '; - if (isset($t['file'])) { - echo $this->htmlEncode($t['file']) . '(' . $t['line'] . '): '; - } - if (!empty($t['class'])) { - echo '' . $t['class'] . '' . $t['type']; - } - echo '' . $t['function'] . ''; - echo '(' . (empty($t['args']) ? '' : $this->htmlEncode($this->argumentsToString($t['args']))) . ')'; - echo '
'; - if ($hasCode) { - $this->renderSourceCode($t['file'], $t['line'], $this->maxTraceSourceLines); - } - echo "
'; + + $half = (int)(($index == 0 ? $this->maxSourceLines : $this->maxTraceSourceLines) / 2); + $begin = $line - $half > 0 ? $line - $half : 0; + $end = $line + $half < $lineCount ? $line + $half : $lineCount - 1; + + $view = new View(); + return $view->renderFile($this->callStackItemView, array( + 'file' => $file, + 'line' => $line, + 'index' => $index, + 'lines' => $lines, + 'begin' => $begin, + 'end' => $end, + ), $this); } /** - * Converts special characters to HTML entities - * @param string $text text to encode - * @return string + * Determines whether given name of the file belongs to the framework. + * @param string $file name to be checked. + * @return boolean whether given name of the file belongs to the framework. */ - public function htmlEncode($text) + public function isCoreFile($file) { - return htmlspecialchars($text, ENT_QUOTES, \Yii::$app->charset); + return $file === 'unknown' || strpos(realpath($file), YII_PATH . DIRECTORY_SEPARATOR) === 0; } - public function clearOutput() + /** + * Creates string containing HTML link which refers to the home page of determined web-server software + * and its full name. + * @return string server software information hyperlink. + */ + public function createServerInformationLink() { - // the following manual level counting is to deal with zlib.output_compression set to On - for ($level = ob_get_level(); $level > 0; --$level) { - @ob_end_clean(); + static $serverUrls = array( + 'http://httpd.apache.org/' => array('apache'), + 'http://nginx.org/' => array('nginx'), + 'http://lighttpd.net/' => array('lighttpd'), + 'http://gwan.com/' => array('g-wan', 'gwan'), + 'http://iis.net/' => array('iis', 'services'), + 'http://php.net/manual/en/features.commandline.webserver.php' => array('development'), + ); + if (isset($_SERVER['SERVER_SOFTWARE'])) { + foreach ($serverUrls as $url => $keywords) { + foreach ($keywords as $keyword) { + if (stripos($_SERVER['SERVER_SOFTWARE'], $keyword) !== false ) { + return '' . $this->htmlEncode($_SERVER['SERVER_SOFTWARE']) . ''; + } + } + } } + return ''; } /** - * @param \Exception $exception + * Creates string containing HTML link which refers to the page with the current version + * of the framework and version number text. + * @return string framework version information hyperlink. */ - public function renderAsHtml($exception) + public function createFrameworkVersionLink() { - $view = new View; - $name = !YII_DEBUG || $exception instanceof HttpException ? $this->errorView : $this->exceptionView; - echo $view->renderFile($name, array( - 'exception' => $exception, - ), $this); + return '' . $this->htmlEncode(Yii::getVersion()) . ''; } } diff --git a/framework/yii/views/error.php b/framework/yii/views/error.php deleted file mode 100644 index 009050a..0000000 --- a/framework/yii/views/error.php +++ /dev/null @@ -1,67 +0,0 @@ -context; -$title = $context->htmlEncode($exception instanceof \yii\base\Exception ? $exception->getName() : get_class($exception)); -?> - - - - - <?php echo $title?> - - - - - -

-

htmlEncode($exception->getMessage()))?>

-

- The above error occurred while the Web server was processing your request. -

-

- Please contact us if you think this is a server error. Thank you. -

-
- - versionInfo : ''?> -
- - diff --git a/framework/yii/views/errorHandler/callStackItem.php b/framework/yii/views/errorHandler/callStackItem.php new file mode 100644 index 0000000..3854ea4 --- /dev/null +++ b/framework/yii/views/errorHandler/callStackItem.php @@ -0,0 +1,34 @@ +context; +?> + +
  • +
    +
    + . + in htmlEncode($file); ?> + at line + +
    +
    +
    +
    + +
    + +
    + '; ?> +
    htmlEncode($lines[$i]); ?>
    +
    +
    +
  • diff --git a/framework/yii/views/errorHandler/main.php b/framework/yii/views/errorHandler/main.php new file mode 100644 index 0000000..f63f246 --- /dev/null +++ b/framework/yii/views/errorHandler/main.php @@ -0,0 +1,416 @@ +context; +?> + + + + + + + + <?php echo $context->htmlEncode($exception->getName() . ' – ' . get_class($exception)); ?> + + <?php echo $context->htmlEncode(get_class($exception)); ?> + + + + + + +
    + + Gears +

    + htmlEncode($exception->getName()); ?> + – addTypeLinks(get_class($exception)); ?> +

    +

    htmlEncode($exception->getMessage()); ?>

    + + Attention +

    + ExceptionaddTypeLinks(get_class($exception)); ?> + + – createHttpStatusLink($exception->statusCode); ?> + +

    +

    htmlEncode($exception->getName()); ?>

    + +
    + +
    +
      + renderCallStackItem($exception->getFile(), $exception->getLine(), 1); ?> + getTrace(), $length = count($trace); $i < $length; ++$i): ?> + renderCallStackItem($trace[$i]['file'], $trace[$i]['line'], $i + 1); ?> + +
    +
    + +
    +
    +
    htmlEncode($request); ?>
    +
    +
    + + + + + + + + + + + diff --git a/framework/yii/views/exception.php b/framework/yii/views/exception.php deleted file mode 100644 index 0f26ed7..0000000 --- a/framework/yii/views/exception.php +++ /dev/null @@ -1,210 +0,0 @@ -context; -$title = $context->htmlEncode($exception instanceof \yii\base\Exception ? $exception->getName().' ('.get_class($exception).')' : get_class($exception)); -?> - - - - - <?php echo $title?> - - - - -
    -

    - -

    - htmlEncode($exception->getMessage()))?> -

    - -
    -

    - htmlEncode($exception->getFile()) . '(' . $exception->getLine() . ')'?> -

    - renderSourceCode($exception->getFile(), $exception->getLine(), $context->maxSourceLines)?> -
    - - -
    -

    Stack Trace

    - renderTrace($exception->getTrace())?> -
    - - -
    - - getVersionInfo() : ''?> -
    -
    - - - - -