www.gusucode.com > Flarum开源的PHP国外手机版论坛 0.1 Beta2 源码程序 > Flarum_v0.1.beta2/flarum/vendor/filp/whoops/src/Whoops/Handler/PrettyPageHandler.php
<?php /** * Whoops - php errors for cool kids * @author Filipe Dobreira <http://github.com/filp> */ namespace Whoops\Handler; use InvalidArgumentException; use RuntimeException; use UnexpectedValueException; use Whoops\Exception\Formatter; use Whoops\Util\Misc; use Whoops\Util\TemplateHelper; class PrettyPageHandler extends Handler { /** * Search paths to be scanned for resources, in the reverse * order they're declared. * * @var array */ private $searchPaths = array(); /** * Fast lookup cache for known resource locations. * * @var array */ private $resourceCache = array(); /** * The name of the custom css file. * * @var string */ private $customCss = null; /** * @var array[] */ private $extraTables = array(); /** * @var bool */ private $handleUnconditionally = false; /** * @var string */ private $pageTitle = "Whoops! There was an error."; /** * A string identifier for a known IDE/text editor, or a closure * that resolves a string that can be used to open a given file * in an editor. If the string contains the special substrings * %file or %line, they will be replaced with the correct data. * * @example * "txmt://open?url=%file&line=%line" * @var mixed $editor */ protected $editor; /** * A list of known editor strings * @var array */ protected $editors = array( "sublime" => "subl://open?url=file://%file&line=%line", "textmate" => "txmt://open?url=file://%file&line=%line", "emacs" => "emacs://open?url=file://%file&line=%line", "macvim" => "mvim://open/?url=file://%file&line=%line", "phpstorm" => "phpstorm://open?file=%file&line=%line", ); /** * Constructor. */ public function __construct() { if (ini_get('xdebug.file_link_format') || extension_loaded('xdebug')) { // Register editor using xdebug's file_link_format option. $this->editors['xdebug'] = function ($file, $line) { return str_replace(array('%f', '%l'), array($file, $line), ini_get('xdebug.file_link_format')); }; } // Add the default, local resource search path: $this->searchPaths[] = __DIR__ . "/../Resources"; } /** * @return int|null */ public function handle() { if (!$this->handleUnconditionally()) { // Check conditions for outputting HTML: // @todo: Make this more robust if (php_sapi_name() === 'cli') { // Help users who have been relying on an internal test value // fix their code to the proper method if (isset($_ENV['whoops-test'])) { throw new \Exception( 'Use handleUnconditionally instead of whoops-test' .' environment variable' ); } return Handler::DONE; } } // @todo: Make this more dynamic $helper = new TemplateHelper(); $templateFile = $this->getResource("views/layout.html.php"); $cssFile = $this->getResource("css/whoops.base.css"); $zeptoFile = $this->getResource("js/zepto.min.js"); $jsFile = $this->getResource("js/whoops.base.js"); if ($this->customCss) { $customCssFile = $this->getResource($this->customCss); } $inspector = $this->getInspector(); $frames = $inspector->getFrames(); $code = $inspector->getException()->getCode(); if ($inspector->getException() instanceof \ErrorException) { // ErrorExceptions wrap the php-error types within the "severity" property $code = Misc::translateErrorCode($inspector->getException()->getSeverity()); } // List of variables that will be passed to the layout template. $vars = array( "page_title" => $this->getPageTitle(), // @todo: Asset compiler "stylesheet" => file_get_contents($cssFile), "zepto" => file_get_contents($zeptoFile), "javascript" => file_get_contents($jsFile), // Template paths: "header" => $this->getResource("views/header.html.php"), "frame_list" => $this->getResource("views/frame_list.html.php"), "frame_code" => $this->getResource("views/frame_code.html.php"), "env_details" => $this->getResource("views/env_details.html.php"), "title" => $this->getPageTitle(), "name" => explode("\\", $inspector->getExceptionName()), "message" => $inspector->getException()->getMessage(), "code" => $code, "plain_exception" => Formatter::formatExceptionPlain($inspector), "frames" => $frames, "has_frames" => !!count($frames), "handler" => $this, "handlers" => $this->getRun()->getHandlers(), "tables" => array( "GET Data" => $_GET, "POST Data" => $_POST, "Files" => $_FILES, "Cookies" => $_COOKIE, "Session" => isset($_SESSION) ? $_SESSION : array(), "Server/Request Data" => $_SERVER, "Environment Variables" => $_ENV, ), ); if (isset($customCssFile)) { $vars["stylesheet"] .= file_get_contents($customCssFile); } // Add extra entries list of data tables: // @todo: Consolidate addDataTable and addDataTableCallback $extraTables = array_map(function ($table) { return $table instanceof \Closure ? $table() : $table; }, $this->getDataTables()); $vars["tables"] = array_merge($extraTables, $vars["tables"]); $helper->setVariables($vars); $helper->render($templateFile); return Handler::QUIT; } /** * Adds an entry to the list of tables displayed in the template. * The expected data is a simple associative array. Any nested arrays * will be flattened with print_r * @param string $label * @param array $data */ public function addDataTable($label, array $data) { $this->extraTables[$label] = $data; } /** * Lazily adds an entry to the list of tables displayed in the table. * The supplied callback argument will be called when the error is rendered, * it should produce a simple associative array. Any nested arrays will * be flattened with print_r. * * @throws InvalidArgumentException If $callback is not callable * @param string $label * @param callable $callback Callable returning an associative array */ public function addDataTableCallback($label, /* callable */ $callback) { if (!is_callable($callback)) { throw new InvalidArgumentException('Expecting callback argument to be callable'); } $this->extraTables[$label] = function () use ($callback) { try { $result = call_user_func($callback); // Only return the result if it can be iterated over by foreach(). return is_array($result) || $result instanceof \Traversable ? $result : array(); } catch (\Exception $e) { // Don't allow failure to break the rendering of the original exception. return array(); } }; } /** * Returns all the extra data tables registered with this handler. * Optionally accepts a 'label' parameter, to only return the data * table under that label. * @param string|null $label * @return array[]|callable */ public function getDataTables($label = null) { if ($label !== null) { return isset($this->extraTables[$label]) ? $this->extraTables[$label] : array(); } return $this->extraTables; } /** * Allows to disable all attempts to dynamically decide whether to * handle or return prematurely. * Set this to ensure that the handler will perform no matter what. * @param bool|null $value * @return bool|null */ public function handleUnconditionally($value = null) { if (func_num_args() == 0) { return $this->handleUnconditionally; } $this->handleUnconditionally = (bool) $value; } /** * Adds an editor resolver, identified by a string * name, and that may be a string path, or a callable * resolver. If the callable returns a string, it will * be set as the file reference's href attribute. * * @example * $run->addEditor('macvim', "mvim://open?url=file://%file&line=%line") * @example * $run->addEditor('remove-it', function($file, $line) { * unlink($file); * return "http://stackoverflow.com"; * }); * @param string $identifier * @param string $resolver */ public function addEditor($identifier, $resolver) { $this->editors[$identifier] = $resolver; } /** * Set the editor to use to open referenced files, by a string * identifier, or a callable that will be executed for every * file reference, with a $file and $line argument, and should * return a string. * * @example * $run->setEditor(function($file, $line) { return "file:///{$file}"; }); * @example * $run->setEditor('sublime'); * * @throws InvalidArgumentException If invalid argument identifier provided * @param string|callable $editor */ public function setEditor($editor) { if (!is_callable($editor) && !isset($this->editors[$editor])) { throw new InvalidArgumentException( "Unknown editor identifier: $editor. Known editors:" . implode(",", array_keys($this->editors)) ); } $this->editor = $editor; } /** * Given a string file path, and an integer file line, * executes the editor resolver and returns, if available, * a string that may be used as the href property for that * file reference. * * @throws InvalidArgumentException If editor resolver does not return a string * @param string $filePath * @param int $line * @return string|bool */ public function getEditorHref($filePath, $line) { $editor = $this->getEditor($filePath, $line); if (!$editor) { return false; } // Check that the editor is a string, and replace the // %line and %file placeholders: if (!isset($editor['url']) || !is_string($editor['url'])) { throw new UnexpectedValueException( __METHOD__ . " should always resolve to a string or a valid editor array; got something else instead." ); } $editor['url'] = str_replace("%line", rawurlencode($line), $editor['url']); $editor['url'] = str_replace("%file", rawurlencode($filePath), $editor['url']); return $editor['url']; } /** * Given a boolean if the editor link should * act as an Ajax request. The editor must be a * valid callable function/closure * * @throws UnexpectedValueException If editor resolver does not return a boolean * @param string $filePath * @param int $line * @return bool */ public function getEditorAjax($filePath, $line) { $editor = $this->getEditor($filePath, $line); // Check that the ajax is a bool if (!isset($editor['ajax']) || !is_bool($editor['ajax'])) { throw new UnexpectedValueException( __METHOD__ . " should always resolve to a bool; got something else instead." ); } return $editor['ajax']; } /** * Given a boolean if the editor link should * act as an Ajax request. The editor must be a * valid callable function/closure * * @throws UnexpectedValueException If editor resolver does not return a boolean * @param string $filePath * @param int $line * @return mixed */ protected function getEditor($filePath, $line) { if ($this->editor === null && !is_string($this->editor) && !is_callable($this->editor)) { return false; } else if(is_string($this->editor) && isset($this->editors[$this->editor]) && !is_callable($this->editors[$this->editor])) { return array( 'ajax' => false, 'url' => $this->editors[$this->editor], ); } else if(is_callable($this->editor) || (isset($this->editors[$this->editor]) && is_callable($this->editors[$this->editor]))) { if(is_callable($this->editor)) { $callback = call_user_func($this->editor, $filePath, $line); } else { $callback = call_user_func($this->editors[$this->editor], $filePath, $line); } return array( 'ajax' => isset($callback['ajax']) ? $callback['ajax'] : false, 'url' => (is_array($callback) ? $callback['url'] : $callback), ); } return false; } /** * @param string $title * @return void */ public function setPageTitle($title) { $this->pageTitle = (string) $title; } /** * @return string */ public function getPageTitle() { return $this->pageTitle; } /** * Adds a path to the list of paths to be searched for * resources. * * @throws InvalidArgumnetException If $path is not a valid directory * * @param string $path * @return void */ public function addResourcePath($path) { if (!is_dir($path)) { throw new InvalidArgumentException( "'$path' is not a valid directory" ); } array_unshift($this->searchPaths, $path); } /** * Adds a custom css file to be loaded. * * @param string $name * @return void */ public function addCustomCss($name) { $this->customCss = $name; } /** * @return array */ public function getResourcePaths() { return $this->searchPaths; } /** * Finds a resource, by its relative path, in all available search paths. * The search is performed starting at the last search path, and all the * way back to the first, enabling a cascading-type system of overrides * for all resources. * * @throws RuntimeException If resource cannot be found in any of the available paths * * @param string $resource * @return string */ protected function getResource($resource) { // If the resource was found before, we can speed things up // by caching its absolute, resolved path: if (isset($this->resourceCache[$resource])) { return $this->resourceCache[$resource]; } // Search through available search paths, until we find the // resource we're after: foreach ($this->searchPaths as $path) { $fullPath = $path . "/$resource"; if (is_file($fullPath)) { // Cache the result: $this->resourceCache[$resource] = $fullPath; return $fullPath; } } // If we got this far, nothing was found. throw new RuntimeException( "Could not find resource '$resource' in any resource paths." . "(searched: " . join(", ", $this->searchPaths). ")" ); } /** * @deprecated * * @return string */ public function getResourcesPath() { $allPaths = $this->getResourcePaths(); // Compat: return only the first path added return end($allPaths) ?: null; } /** * @deprecated * * @param string $resourcesPath * @return void */ public function setResourcesPath($resourcesPath) { $this->addResourcePath($resourcesPath); } }