├── .mailmap ├── CHANGELOG.md ├── LICENSE.md ├── SECURITY.md ├── composer.json └── src └── Whoops ├── Exception ├── ErrorException.php ├── Formatter.php ├── Frame.php ├── FrameCollection.php └── Inspector.php ├── Handler ├── CallbackHandler.php ├── Handler.php ├── HandlerInterface.php ├── JsonResponseHandler.php ├── PlainTextHandler.php ├── PrettyPageHandler.php └── XmlResponseHandler.php ├── Inspector ├── InspectorFactory.php ├── InspectorFactoryInterface.php └── InspectorInterface.php ├── Resources ├── css │ ├── prism.css │ └── whoops.base.css ├── js │ ├── clipboard.min.js │ ├── prism.js │ ├── whoops.base.js │ └── zepto.min.js └── views │ ├── env_details.html.php │ ├── frame_code.html.php │ ├── frame_list.html.php │ ├── frames_container.html.php │ ├── frames_description.html.php │ ├── header.html.php │ ├── header_outer.html.php │ ├── layout.html.php │ ├── panel_details.html.php │ ├── panel_details_outer.html.php │ ├── panel_left.html.php │ └── panel_left_outer.html.php ├── Run.php ├── RunInterface.php └── Util ├── HtmlDumperOutput.php ├── Misc.php ├── SystemFacade.php └── TemplateHelper.php /.mailmap: -------------------------------------------------------------------------------- 1 | Denis Sokolov <denis@sokolov.cc> 2 | Filipe Dobreira <dobreira@gmail.com> 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## v2.18.0 4 | 5 | * Line numbers are now clickable. 6 | 7 | ## v2.17.0 8 | 9 | * Support cursor IDE. 10 | 11 | ## v2.16.0 12 | 13 | * Support PHP `8.4`. 14 | * Drop support for PHP older than `7.1`. 15 | 16 | ## v2.15.4 17 | 18 | * Improve link color in comments. 19 | 20 | ## v2.15.3 21 | 22 | * Improve performance of the syntax highlighting (#758). 23 | 24 | ## v2.15.2 25 | 26 | * Fixed missing code highlight, which additionally led to issue with switching tabs, between application and all frames ([#747](https://github.com/filp/whoops/issues/747)). 27 | 28 | ## v2.15.1 29 | 30 | * Fixed bug with PrettyPageHandler "*Calling `getFrameFilters` method on null*" ([#751](https://github.com/filp/whoops/pull/751)). 31 | 32 | ## v2.15.0 33 | 34 | * Add addFrameFilter ([#749](https://github.com/filp/whoops/pull/749)) 35 | 36 | ## v2.14.6 37 | 38 | * Upgraded prismJS to version `1.29.0` due to security issue ([#741][i741]). 39 | 40 | [i741]: https://github.com/filp/whoops/pull/741 41 | 42 | ## v2.14.5 43 | 44 | * Allow `ArrayAccess` on super globals. 45 | 46 | ## v2.14.4 47 | 48 | * Fix PHP `5.5` support. 49 | * Allow to use psr/log `2` or `3`. 50 | 51 | ## v2.14.3 52 | 53 | * Support PHP `8.1`. 54 | 55 | ## v2.14.1 56 | 57 | * Fix syntax highlighting scrolling too far. 58 | * Improve the way we detect xdebug linkformat. 59 | 60 | ## v2.14.0 61 | 62 | * Switched syntax highlighting to Prism.js. 63 | 64 | Avoids licensing issues with prettify, and uses a maintained, modern project. 65 | 66 | ## v2.13.0 67 | 68 | * Add Netbeans editor. 69 | 70 | ## v2.12.1 71 | 72 | * Avoid redirecting away from an error. 73 | 74 | ## v2.12.0 75 | 76 | * Hide non-string values in super globals when requested. 77 | 78 | ## v2.11.0 79 | 80 | * Customize exit code. 81 | 82 | ## v2.10.0 83 | 84 | * Better chaining on handler classes. 85 | 86 | ## v2.9.2 87 | 88 | * Fix copy button styles. 89 | 90 | ## v2.9.1 91 | 92 | * Fix xdebug function crash on PHP `8`. 93 | 94 | ## v2.9.0 95 | 96 | * `JsonResponseHandler` includes the exception code. 97 | 98 | ## v2.8.0 99 | 100 | * Support PHP 8. 101 | 102 | ## v2.7.3 103 | 104 | * `PrettyPageHandler` functionality to hide superglobal keys has a clearer name 105 | (`hideSuperglobalKey`). 106 | 107 | ## v2.7.2 108 | 109 | * `PrettyPageHandler` now accepts custom js files. 110 | * `PrettyPageHandler` and `templateHelper` is now accessible through inheritance. 111 | 112 | ## v2.7.1 113 | 114 | * Fix a PHP warning in some cases with anonymous classes. 115 | 116 | ## v2.7.0 117 | 118 | * Added `removeFirstHandler` and `removeLastHandler`. 119 | 120 | ## v2.6.0 121 | 122 | * Fix 2.4.0 `pushHandler` changing the order of handlers. 123 | 124 | ## v2.5.1 125 | 126 | * Fix error messaging in a rare case. 127 | 128 | ## v2.5.0 129 | 130 | * Automatically configure xdebug if available. 131 | 132 | ## v2.4.1 133 | 134 | * Try harder to close all output buffers. 135 | 136 | ## v2.4.0 137 | 138 | * Allow to prepend and append handlers. 139 | 140 | ## v2.3.2 141 | 142 | * Various fixes from the community. 143 | 144 | ## v2.3.1 145 | 146 | * Prevent exception in Whoops when caught exception frame is not related to real file. 147 | 148 | ## v2.3.0 149 | 150 | * Show previous exception messages. 151 | 152 | ## v2.2.0 153 | 154 | * Support PHP `7.2`. 155 | 156 | ## v2.1.0 157 | 158 | * Add a `SystemFacade` to allow clients to override Whoops behavior. 159 | * Show frame arguments in `PrettyPageHandler`. 160 | * Highlight the line with the error. 161 | * Add icons to search on Google and Stack Overflow. 162 | 163 | ## v2.0.0 164 | 165 | Backwards compatibility breaking changes: 166 | 167 | * `Run` class is now `final`. If you inherited from `Run`, please now instead use a custom `SystemFacade` injected into the `Run` constructor, or contribute your changes to our core. 168 | * PHP < 5.5 support dropped. 169 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Only the latest released version of Whoops is supported. 6 | To facilitate upgrades we almost never make backwards-incompatible changes. 7 | 8 | ## Reporting a Vulnerability 9 | 10 | Please report vulnerabilities over email, by sending an email to `denis` at `sokolov` dot `cc`. 11 | 12 | 13 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "filp/whoops", 3 | "license": "MIT", 4 | "description": "php error handling for cool kids", 5 | "keywords": ["library", "error", "handling", "exception", "whoops", "throwable"], 6 | "homepage": "https://filp.github.io/whoops/", 7 | "authors": [ 8 | { 9 | "name": "Filipe Dobreira", 10 | "homepage": "https://github.com/filp", 11 | "role": "Developer" 12 | } 13 | ], 14 | "scripts": { 15 | "demo": "php -S localhost:8000 ./examples/example.php", 16 | "test": "phpunit --testdox tests" 17 | }, 18 | "require": { 19 | "php": "^7.1 || ^8.0", 20 | "psr/log": "^1.0.1 || ^2.0 || ^3.0" 21 | }, 22 | "require-dev": { 23 | "phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3", 24 | "mockery/mockery": "^1.0", 25 | "symfony/var-dumper": "^4.0 || ^5.0" 26 | }, 27 | "suggest": { 28 | "symfony/var-dumper": "Pretty print complex values better with var-dumper available", 29 | "whoops/soap": "Formats errors as SOAP responses" 30 | }, 31 | "autoload": { 32 | "psr-4": { 33 | "Whoops\\": "src/Whoops/" 34 | } 35 | }, 36 | "autoload-dev": { 37 | "psr-4": { 38 | "Whoops\\": "tests/Whoops/" 39 | } 40 | }, 41 | "extra": { 42 | "branch-alias": { 43 | "dev-master": "2.7-dev" 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Whoops/Exception/ErrorException.php: -------------------------------------------------------------------------------- 1 | <?php 2 | /** 3 | * Whoops - php errors for cool kids 4 | * @author Filipe Dobreira <http://github.com/filp> 5 | */ 6 | 7 | namespace Whoops\Exception; 8 | 9 | use ErrorException as BaseErrorException; 10 | 11 | /** 12 | * Wraps ErrorException; mostly used for typing (at least now) 13 | * to easily cleanup the stack trace of redundant info. 14 | */ 15 | class ErrorException extends BaseErrorException 16 | { 17 | } 18 | -------------------------------------------------------------------------------- /src/Whoops/Exception/Formatter.php: -------------------------------------------------------------------------------- 1 | <?php 2 | /** 3 | * Whoops - php errors for cool kids 4 | * @author Filipe Dobreira <http://github.com/filp> 5 | */ 6 | 7 | namespace Whoops\Exception; 8 | 9 | use Whoops\Inspector\InspectorInterface; 10 | 11 | class Formatter 12 | { 13 | /** 14 | * Returns all basic information about the exception in a simple array 15 | * for further convertion to other languages 16 | * @param InspectorInterface $inspector 17 | * @param bool $shouldAddTrace 18 | * @param array<callable> $frameFilters 19 | * @return array 20 | */ 21 | public static function formatExceptionAsDataArray(InspectorInterface $inspector, $shouldAddTrace, array $frameFilters = []) 22 | { 23 | $exception = $inspector->getException(); 24 | $response = [ 25 | 'type' => get_class($exception), 26 | 'message' => $exception->getMessage(), 27 | 'code' => $exception->getCode(), 28 | 'file' => $exception->getFile(), 29 | 'line' => $exception->getLine(), 30 | ]; 31 | 32 | if ($shouldAddTrace) { 33 | $frames = $inspector->getFrames($frameFilters); 34 | $frameData = []; 35 | 36 | foreach ($frames as $frame) { 37 | /** @var Frame $frame */ 38 | $frameData[] = [ 39 | 'file' => $frame->getFile(), 40 | 'line' => $frame->getLine(), 41 | 'function' => $frame->getFunction(), 42 | 'class' => $frame->getClass(), 43 | 'args' => $frame->getArgs(), 44 | ]; 45 | } 46 | 47 | $response['trace'] = $frameData; 48 | } 49 | 50 | return $response; 51 | } 52 | 53 | public static function formatExceptionPlain(InspectorInterface $inspector) 54 | { 55 | $message = $inspector->getException()->getMessage(); 56 | $frames = $inspector->getFrames(); 57 | 58 | $plain = $inspector->getExceptionName(); 59 | $plain .= ' thrown with message "'; 60 | $plain .= $message; 61 | $plain .= '"'."\n\n"; 62 | 63 | $plain .= "Stacktrace:\n"; 64 | foreach ($frames as $i => $frame) { 65 | $plain .= "#". (count($frames) - $i - 1). " "; 66 | $plain .= $frame->getClass() ?: ''; 67 | $plain .= $frame->getClass() && $frame->getFunction() ? ":" : ""; 68 | $plain .= $frame->getFunction() ?: ''; 69 | $plain .= ' in '; 70 | $plain .= ($frame->getFile() ?: '<#unknown>'); 71 | $plain .= ':'; 72 | $plain .= (int) $frame->getLine(). "\n"; 73 | } 74 | 75 | return $plain; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Whoops/Exception/Frame.php: -------------------------------------------------------------------------------- 1 | <?php 2 | /** 3 | * Whoops - php errors for cool kids 4 | * @author Filipe Dobreira <http://github.com/filp> 5 | */ 6 | 7 | namespace Whoops\Exception; 8 | 9 | use InvalidArgumentException; 10 | use Serializable; 11 | 12 | class Frame implements Serializable 13 | { 14 | /** 15 | * @var array 16 | */ 17 | protected $frame; 18 | 19 | /** 20 | * @var string 21 | */ 22 | protected $fileContentsCache; 23 | 24 | /** 25 | * @var array[] 26 | */ 27 | protected $comments = []; 28 | 29 | /** 30 | * @var bool 31 | */ 32 | protected $application; 33 | 34 | public function __construct(array $frame) 35 | { 36 | $this->frame = $frame; 37 | } 38 | 39 | /** 40 | * @param bool $shortened 41 | * @return string|null 42 | */ 43 | public function getFile($shortened = false) 44 | { 45 | if (empty($this->frame['file'])) { 46 | return null; 47 | } 48 | 49 | $file = $this->frame['file']; 50 | 51 | // Check if this frame occurred within an eval(). 52 | // @todo: This can be made more reliable by checking if we've entered 53 | // eval() in a previous trace, but will need some more work on the upper 54 | // trace collector(s). 55 | if (preg_match('/^(.*)\((\d+)\) : (?:eval\(\)\'d|assert) code$/', $file, $matches)) { 56 | $file = $this->frame['file'] = $matches[1]; 57 | $this->frame['line'] = (int) $matches[2]; 58 | } 59 | 60 | if ($shortened && is_string($file)) { 61 | // Replace the part of the path that all frames have in common, and add 'soft hyphens' for smoother line-breaks. 62 | $dirname = dirname(dirname(dirname(dirname(dirname(dirname(__DIR__)))))); 63 | if ($dirname !== '/') { 64 | $file = str_replace($dirname, "…", $file); 65 | } 66 | $file = str_replace("/", "/­", $file); 67 | } 68 | 69 | return $file; 70 | } 71 | 72 | /** 73 | * @return int|null 74 | */ 75 | public function getLine() 76 | { 77 | return isset($this->frame['line']) ? $this->frame['line'] : null; 78 | } 79 | 80 | /** 81 | * @return string|null 82 | */ 83 | public function getClass() 84 | { 85 | return isset($this->frame['class']) ? $this->frame['class'] : null; 86 | } 87 | 88 | /** 89 | * @return string|null 90 | */ 91 | public function getFunction() 92 | { 93 | return isset($this->frame['function']) ? $this->frame['function'] : null; 94 | } 95 | 96 | /** 97 | * @return array 98 | */ 99 | public function getArgs() 100 | { 101 | return isset($this->frame['args']) ? (array) $this->frame['args'] : []; 102 | } 103 | 104 | /** 105 | * Returns the full contents of the file for this frame, 106 | * if it's known. 107 | * @return string|null 108 | */ 109 | public function getFileContents() 110 | { 111 | if ($this->fileContentsCache === null && $filePath = $this->getFile()) { 112 | // Leave the stage early when 'Unknown' or '[internal]' is passed 113 | // this would otherwise raise an exception when 114 | // open_basedir is enabled. 115 | if ($filePath === "Unknown" || $filePath === '[internal]') { 116 | return null; 117 | } 118 | 119 | try { 120 | $this->fileContentsCache = file_get_contents($filePath); 121 | } catch (ErrorException $exception) { 122 | // Internal file paths of PHP extensions cannot be opened 123 | } 124 | } 125 | 126 | return $this->fileContentsCache; 127 | } 128 | 129 | /** 130 | * Adds a comment to this frame, that can be received and 131 | * used by other handlers. For example, the PrettyPage handler 132 | * can attach these comments under the code for each frame. 133 | * 134 | * An interesting use for this would be, for example, code analysis 135 | * & annotations. 136 | * 137 | * @param string $comment 138 | * @param string $context Optional string identifying the origin of the comment 139 | */ 140 | public function addComment($comment, $context = 'global') 141 | { 142 | $this->comments[] = [ 143 | 'comment' => $comment, 144 | 'context' => $context, 145 | ]; 146 | } 147 | 148 | /** 149 | * Returns all comments for this frame. Optionally allows 150 | * a filter to only retrieve comments from a specific 151 | * context. 152 | * 153 | * @param string $filter 154 | * @return array[] 155 | */ 156 | public function getComments($filter = null) 157 | { 158 | $comments = $this->comments; 159 | 160 | if ($filter !== null) { 161 | $comments = array_filter($comments, function ($c) use ($filter) { 162 | return $c['context'] == $filter; 163 | }); 164 | } 165 | 166 | return $comments; 167 | } 168 | 169 | /** 170 | * Returns the array containing the raw frame data from which 171 | * this Frame object was built 172 | * 173 | * @return array 174 | */ 175 | public function getRawFrame() 176 | { 177 | return $this->frame; 178 | } 179 | 180 | /** 181 | * Returns the contents of the file for this frame as an 182 | * array of lines, and optionally as a clamped range of lines. 183 | * 184 | * NOTE: lines are 0-indexed 185 | * 186 | * @example 187 | * Get all lines for this file 188 | * $frame->getFileLines(); // => array( 0 => '<?php', 1 => '...', ...) 189 | * @example 190 | * Get one line for this file, starting at line 10 (zero-indexed, remember!) 191 | * $frame->getFileLines(9, 1); // array( 9 => '...' ) 192 | * 193 | * @throws InvalidArgumentException if $length is less than or equal to 0 194 | * @param int $start 195 | * @param int $length 196 | * @return string[]|null 197 | */ 198 | public function getFileLines($start = 0, $length = null) 199 | { 200 | if (null !== ($contents = $this->getFileContents())) { 201 | $lines = explode("\n", $contents); 202 | 203 | // Get a subset of lines from $start to $end 204 | if ($length !== null) { 205 | $start = (int) $start; 206 | $length = (int) $length; 207 | if ($start < 0) { 208 | $start = 0; 209 | } 210 | 211 | if ($length <= 0) { 212 | throw new InvalidArgumentException( 213 | "\$length($length) cannot be lower or equal to 0" 214 | ); 215 | } 216 | 217 | $lines = array_slice($lines, $start, $length, true); 218 | } 219 | 220 | return $lines; 221 | } 222 | } 223 | 224 | /** 225 | * Implements the Serializable interface, with special 226 | * steps to also save the existing comments. 227 | * 228 | * @see Serializable::serialize 229 | * @return string 230 | */ 231 | public function serialize() 232 | { 233 | $frame = $this->frame; 234 | if (!empty($this->comments)) { 235 | $frame['_comments'] = $this->comments; 236 | } 237 | 238 | return serialize($frame); 239 | } 240 | 241 | public function __serialize() 242 | { 243 | $frame = $this->frame; 244 | if (!empty($this->comments)) { 245 | $frame['_comments'] = $this->comments; 246 | } 247 | return $frame; 248 | } 249 | 250 | /** 251 | * Unserializes the frame data, while also preserving 252 | * any existing comment data. 253 | * 254 | * @see Serializable::unserialize 255 | * @param string $serializedFrame 256 | */ 257 | public function unserialize($serializedFrame) 258 | { 259 | $frame = unserialize($serializedFrame); 260 | 261 | if (!empty($frame['_comments'])) { 262 | $this->comments = $frame['_comments']; 263 | unset($frame['_comments']); 264 | } 265 | 266 | $this->frame = $frame; 267 | } 268 | 269 | public function __unserialize($frame) 270 | { 271 | if (!empty($frame['_comments'])) { 272 | $this->comments = $frame['_comments']; 273 | unset($frame['_comments']); 274 | } 275 | 276 | $this->frame = $frame; 277 | } 278 | 279 | /** 280 | * Compares Frame against one another 281 | * @param Frame $frame 282 | * @return bool 283 | */ 284 | public function equals(Frame $frame) 285 | { 286 | if (!$this->getFile() || $this->getFile() === 'Unknown' || !$this->getLine()) { 287 | return false; 288 | } 289 | return $frame->getFile() === $this->getFile() && $frame->getLine() === $this->getLine(); 290 | } 291 | 292 | /** 293 | * Returns whether this frame belongs to the application or not. 294 | * 295 | * @return boolean 296 | */ 297 | public function isApplication() 298 | { 299 | return $this->application; 300 | } 301 | 302 | /** 303 | * Mark as an frame belonging to the application. 304 | * 305 | * @param boolean $application 306 | */ 307 | public function setApplication($application) 308 | { 309 | $this->application = $application; 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /src/Whoops/Exception/FrameCollection.php: -------------------------------------------------------------------------------- 1 | <?php 2 | /** 3 | * Whoops - php errors for cool kids 4 | * @author Filipe Dobreira <http://github.com/filp> 5 | */ 6 | 7 | namespace Whoops\Exception; 8 | 9 | use ArrayAccess; 10 | use ArrayIterator; 11 | use Countable; 12 | use IteratorAggregate; 13 | use ReturnTypeWillChange; 14 | use Serializable; 15 | use UnexpectedValueException; 16 | 17 | /** 18 | * Exposes a fluent interface for dealing with an ordered list 19 | * of stack-trace frames. 20 | */ 21 | class FrameCollection implements ArrayAccess, IteratorAggregate, Serializable, Countable 22 | { 23 | /** 24 | * @var array[] 25 | */ 26 | private $frames; 27 | 28 | public function __construct(array $frames) 29 | { 30 | $this->frames = array_map(function ($frame) { 31 | return new Frame($frame); 32 | }, $frames); 33 | } 34 | 35 | /** 36 | * Filters frames using a callable, returns the same FrameCollection 37 | * 38 | * @param callable $callable 39 | * @return FrameCollection 40 | */ 41 | public function filter($callable) 42 | { 43 | $this->frames = array_values(array_filter($this->frames, $callable)); 44 | return $this; 45 | } 46 | 47 | /** 48 | * Map the collection of frames 49 | * 50 | * @param callable $callable 51 | * @return FrameCollection 52 | */ 53 | public function map($callable) 54 | { 55 | // Contain the map within a higher-order callable 56 | // that enforces type-correctness for the $callable 57 | $this->frames = array_map(function ($frame) use ($callable) { 58 | $frame = call_user_func($callable, $frame); 59 | 60 | if (!$frame instanceof Frame) { 61 | throw new UnexpectedValueException( 62 | "Callable to " . __CLASS__ . "::map must return a Frame object" 63 | ); 64 | } 65 | 66 | return $frame; 67 | }, $this->frames); 68 | 69 | return $this; 70 | } 71 | 72 | /** 73 | * Returns an array with all frames, does not affect 74 | * the internal array. 75 | * 76 | * @todo If this gets any more complex than this, 77 | * have getIterator use this method. 78 | * @see FrameCollection::getIterator 79 | * @return array 80 | */ 81 | public function getArray() 82 | { 83 | return $this->frames; 84 | } 85 | 86 | /** 87 | * @see IteratorAggregate::getIterator 88 | * @return ArrayIterator 89 | */ 90 | #[ReturnTypeWillChange] 91 | public function getIterator() 92 | { 93 | return new ArrayIterator($this->frames); 94 | } 95 | 96 | /** 97 | * @see ArrayAccess::offsetExists 98 | * @param int $offset 99 | */ 100 | #[ReturnTypeWillChange] 101 | public function offsetExists($offset) 102 | { 103 | return isset($this->frames[$offset]); 104 | } 105 | 106 | /** 107 | * @see ArrayAccess::offsetGet 108 | * @param int $offset 109 | */ 110 | #[ReturnTypeWillChange] 111 | public function offsetGet($offset) 112 | { 113 | return $this->frames[$offset]; 114 | } 115 | 116 | /** 117 | * @see ArrayAccess::offsetSet 118 | * @param int $offset 119 | */ 120 | #[ReturnTypeWillChange] 121 | public function offsetSet($offset, $value) 122 | { 123 | throw new \Exception(__CLASS__ . ' is read only'); 124 | } 125 | 126 | /** 127 | * @see ArrayAccess::offsetUnset 128 | * @param int $offset 129 | */ 130 | #[ReturnTypeWillChange] 131 | public function offsetUnset($offset) 132 | { 133 | throw new \Exception(__CLASS__ . ' is read only'); 134 | } 135 | 136 | /** 137 | * @see Countable::count 138 | * @return int 139 | */ 140 | #[ReturnTypeWillChange] 141 | public function count() 142 | { 143 | return count($this->frames); 144 | } 145 | 146 | /** 147 | * Count the frames that belongs to the application. 148 | * 149 | * @return int 150 | */ 151 | public function countIsApplication() 152 | { 153 | return count(array_filter($this->frames, function (Frame $f) { 154 | return $f->isApplication(); 155 | })); 156 | } 157 | 158 | /** 159 | * @see Serializable::serialize 160 | * @return string 161 | */ 162 | #[ReturnTypeWillChange] 163 | public function serialize() 164 | { 165 | return serialize($this->frames); 166 | } 167 | 168 | /** 169 | * @see Serializable::unserialize 170 | * @param string $serializedFrames 171 | */ 172 | #[ReturnTypeWillChange] 173 | public function unserialize($serializedFrames) 174 | { 175 | $this->frames = unserialize($serializedFrames); 176 | } 177 | 178 | public function __serialize() 179 | { 180 | return $this->frames; 181 | } 182 | 183 | public function __unserialize(array $serializedFrames) 184 | { 185 | $this->frames = $serializedFrames; 186 | } 187 | 188 | /** 189 | * @param Frame[] $frames Array of Frame instances, usually from $e->getPrevious() 190 | */ 191 | public function prependFrames(array $frames) 192 | { 193 | $this->frames = array_merge($frames, $this->frames); 194 | } 195 | 196 | /** 197 | * Gets the innermost part of stack trace that is not the same as that of outer exception 198 | * 199 | * @param FrameCollection $parentFrames Outer exception frames to compare tail against 200 | * @return Frame[] 201 | */ 202 | public function topDiff(FrameCollection $parentFrames) 203 | { 204 | $diff = $this->frames; 205 | 206 | $parentFrames = $parentFrames->getArray(); 207 | $p = count($parentFrames)-1; 208 | 209 | for ($i = count($diff)-1; $i >= 0 && $p >= 0; $i--) { 210 | /** @var Frame $tailFrame */ 211 | $tailFrame = $diff[$i]; 212 | if ($tailFrame->equals($parentFrames[$p])) { 213 | unset($diff[$i]); 214 | } 215 | $p--; 216 | } 217 | return $diff; 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /src/Whoops/Exception/Inspector.php: -------------------------------------------------------------------------------- 1 | <?php 2 | /** 3 | * Whoops - php errors for cool kids 4 | * @author Filipe Dobreira <http://github.com/filp> 5 | */ 6 | 7 | namespace Whoops\Exception; 8 | 9 | use Whoops\Inspector\InspectorFactory; 10 | use Whoops\Inspector\InspectorInterface; 11 | use Whoops\Util\Misc; 12 | 13 | class Inspector implements InspectorInterface 14 | { 15 | /** 16 | * @var \Throwable 17 | */ 18 | private $exception; 19 | 20 | /** 21 | * @var \Whoops\Exception\FrameCollection 22 | */ 23 | private $frames; 24 | 25 | /** 26 | * @var \Whoops\Exception\Inspector 27 | */ 28 | private $previousExceptionInspector; 29 | 30 | /** 31 | * @var \Throwable[] 32 | */ 33 | private $previousExceptions; 34 | 35 | /** 36 | * @var \Whoops\Inspector\InspectorFactoryInterface|null 37 | */ 38 | protected $inspectorFactory; 39 | 40 | /** 41 | * @param \Throwable $exception The exception to inspect 42 | * @param \Whoops\Inspector\InspectorFactoryInterface $factory 43 | */ 44 | public function __construct($exception, $factory = null) 45 | { 46 | $this->exception = $exception; 47 | $this->inspectorFactory = $factory ?: new InspectorFactory(); 48 | } 49 | 50 | /** 51 | * @return \Throwable 52 | */ 53 | public function getException() 54 | { 55 | return $this->exception; 56 | } 57 | 58 | /** 59 | * @return string 60 | */ 61 | public function getExceptionName() 62 | { 63 | return get_class($this->exception); 64 | } 65 | 66 | /** 67 | * @return string 68 | */ 69 | public function getExceptionMessage() 70 | { 71 | return $this->extractDocrefUrl($this->exception->getMessage())['message']; 72 | } 73 | 74 | /** 75 | * @return string[] 76 | */ 77 | public function getPreviousExceptionMessages() 78 | { 79 | return array_map(function ($prev) { 80 | /** @var \Throwable $prev */ 81 | return $this->extractDocrefUrl($prev->getMessage())['message']; 82 | }, $this->getPreviousExceptions()); 83 | } 84 | 85 | /** 86 | * @return int[] 87 | */ 88 | public function getPreviousExceptionCodes() 89 | { 90 | return array_map(function ($prev) { 91 | /** @var \Throwable $prev */ 92 | return $prev->getCode(); 93 | }, $this->getPreviousExceptions()); 94 | } 95 | 96 | /** 97 | * Returns a url to the php-manual related to the underlying error - when available. 98 | * 99 | * @return string|null 100 | */ 101 | public function getExceptionDocrefUrl() 102 | { 103 | return $this->extractDocrefUrl($this->exception->getMessage())['url']; 104 | } 105 | 106 | private function extractDocrefUrl($message) 107 | { 108 | $docref = [ 109 | 'message' => $message, 110 | 'url' => null, 111 | ]; 112 | 113 | // php embbeds urls to the manual into the Exception message with the following ini-settings defined 114 | // http://php.net/manual/en/errorfunc.configuration.php#ini.docref-root 115 | if (!ini_get('html_errors') || !ini_get('docref_root')) { 116 | return $docref; 117 | } 118 | 119 | $pattern = "/\[<a href='([^']+)'>(?:[^<]+)<\/a>\]/"; 120 | if (preg_match($pattern, $message, $matches)) { 121 | // -> strip those automatically generated links from the exception message 122 | $docref['message'] = preg_replace($pattern, '', $message, 1); 123 | $docref['url'] = $matches[1]; 124 | } 125 | 126 | return $docref; 127 | } 128 | 129 | /** 130 | * Does the wrapped Exception has a previous Exception? 131 | * @return bool 132 | */ 133 | public function hasPreviousException() 134 | { 135 | return $this->previousExceptionInspector || $this->exception->getPrevious(); 136 | } 137 | 138 | /** 139 | * Returns an Inspector for a previous Exception, if any. 140 | * @todo Clean this up a bit, cache stuff a bit better. 141 | * @return Inspector 142 | */ 143 | public function getPreviousExceptionInspector() 144 | { 145 | if ($this->previousExceptionInspector === null) { 146 | $previousException = $this->exception->getPrevious(); 147 | 148 | if ($previousException) { 149 | $this->previousExceptionInspector = $this->inspectorFactory->create($previousException); 150 | } 151 | } 152 | 153 | return $this->previousExceptionInspector; 154 | } 155 | 156 | 157 | /** 158 | * Returns an array of all previous exceptions for this inspector's exception 159 | * @return \Throwable[] 160 | */ 161 | public function getPreviousExceptions() 162 | { 163 | if ($this->previousExceptions === null) { 164 | $this->previousExceptions = []; 165 | 166 | $prev = $this->exception->getPrevious(); 167 | while ($prev !== null) { 168 | $this->previousExceptions[] = $prev; 169 | $prev = $prev->getPrevious(); 170 | } 171 | } 172 | 173 | return $this->previousExceptions; 174 | } 175 | 176 | /** 177 | * Returns an iterator for the inspected exception's 178 | * frames. 179 | * 180 | * @param array<callable> $frameFilters 181 | * 182 | * @return \Whoops\Exception\FrameCollection 183 | */ 184 | public function getFrames(array $frameFilters = []) 185 | { 186 | if ($this->frames === null) { 187 | $frames = $this->getTrace($this->exception); 188 | 189 | // Fill empty line/file info for call_user_func_array usages (PHP Bug #44428) 190 | foreach ($frames as $k => $frame) { 191 | if (empty($frame['file'])) { 192 | // Default values when file and line are missing 193 | $file = '[internal]'; 194 | $line = 0; 195 | 196 | $next_frame = !empty($frames[$k + 1]) ? $frames[$k + 1] : []; 197 | 198 | if ($this->isValidNextFrame($next_frame)) { 199 | $file = $next_frame['file']; 200 | $line = $next_frame['line']; 201 | } 202 | 203 | $frames[$k]['file'] = $file; 204 | $frames[$k]['line'] = $line; 205 | } 206 | } 207 | 208 | // Find latest non-error handling frame index ($i) used to remove error handling frames 209 | $i = 0; 210 | foreach ($frames as $k => $frame) { 211 | if ($frame['file'] == $this->exception->getFile() && $frame['line'] == $this->exception->getLine()) { 212 | $i = $k; 213 | } 214 | } 215 | 216 | // Remove error handling frames 217 | if ($i > 0) { 218 | array_splice($frames, 0, $i); 219 | } 220 | 221 | $firstFrame = $this->getFrameFromException($this->exception); 222 | array_unshift($frames, $firstFrame); 223 | 224 | $this->frames = new FrameCollection($frames); 225 | 226 | if ($previousInspector = $this->getPreviousExceptionInspector()) { 227 | // Keep outer frame on top of the inner one 228 | $outerFrames = $this->frames; 229 | $newFrames = clone $previousInspector->getFrames(); 230 | // I assume it will always be set, but let's be safe 231 | if (isset($newFrames[0])) { 232 | $newFrames[0]->addComment( 233 | $previousInspector->getExceptionMessage(), 234 | 'Exception message:' 235 | ); 236 | } 237 | $newFrames->prependFrames($outerFrames->topDiff($newFrames)); 238 | $this->frames = $newFrames; 239 | } 240 | 241 | // Apply frame filters callbacks on the frames stack 242 | if (!empty($frameFilters)) { 243 | foreach ($frameFilters as $filterCallback) { 244 | $this->frames->filter($filterCallback); 245 | } 246 | } 247 | } 248 | 249 | return $this->frames; 250 | } 251 | 252 | /** 253 | * Gets the backtrace from an exception. 254 | * 255 | * If xdebug is installed 256 | * 257 | * @param \Throwable $e 258 | * @return array 259 | */ 260 | protected function getTrace($e) 261 | { 262 | $traces = $e->getTrace(); 263 | 264 | // Get trace from xdebug if enabled, failure exceptions only trace to the shutdown handler by default 265 | if (!$e instanceof \ErrorException) { 266 | return $traces; 267 | } 268 | 269 | if (!Misc::isLevelFatal($e->getSeverity())) { 270 | return $traces; 271 | } 272 | 273 | if (!extension_loaded('xdebug') || !function_exists('xdebug_is_enabled') || !xdebug_is_enabled()) { 274 | return $traces; 275 | } 276 | 277 | // Use xdebug to get the full stack trace and remove the shutdown handler stack trace 278 | $stack = array_reverse(xdebug_get_function_stack()); 279 | $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); 280 | $traces = array_diff_key($stack, $trace); 281 | 282 | return $traces; 283 | } 284 | 285 | /** 286 | * Given an exception, generates an array in the format 287 | * generated by Exception::getTrace() 288 | * @param \Throwable $exception 289 | * @return array 290 | */ 291 | protected function getFrameFromException($exception) 292 | { 293 | return [ 294 | 'file' => $exception->getFile(), 295 | 'line' => $exception->getLine(), 296 | 'class' => get_class($exception), 297 | 'args' => [ 298 | $exception->getMessage(), 299 | ], 300 | ]; 301 | } 302 | 303 | /** 304 | * Given an error, generates an array in the format 305 | * generated by ErrorException 306 | * @param ErrorException $exception 307 | * @return array 308 | */ 309 | protected function getFrameFromError(ErrorException $exception) 310 | { 311 | return [ 312 | 'file' => $exception->getFile(), 313 | 'line' => $exception->getLine(), 314 | 'class' => null, 315 | 'args' => [], 316 | ]; 317 | } 318 | 319 | /** 320 | * Determine if the frame can be used to fill in previous frame's missing info 321 | * happens for call_user_func and call_user_func_array usages (PHP Bug #44428) 322 | * 323 | * @return bool 324 | */ 325 | protected function isValidNextFrame(array $frame) 326 | { 327 | if (empty($frame['file'])) { 328 | return false; 329 | } 330 | 331 | if (empty($frame['line'])) { 332 | return false; 333 | } 334 | 335 | if (empty($frame['function']) || !stristr($frame['function'], 'call_user_func')) { 336 | return false; 337 | } 338 | 339 | return true; 340 | } 341 | } 342 | -------------------------------------------------------------------------------- /src/Whoops/Handler/CallbackHandler.php: -------------------------------------------------------------------------------- 1 | <?php 2 | /** 3 | * Whoops - php errors for cool kids 4 | * @author Filipe Dobreira <http://github.com/filp> 5 | */ 6 | 7 | namespace Whoops\Handler; 8 | 9 | use InvalidArgumentException; 10 | 11 | /** 12 | * Wrapper for Closures passed as handlers. Can be used 13 | * directly, or will be instantiated automagically by Whoops\Run 14 | * if passed to Run::pushHandler 15 | */ 16 | class CallbackHandler extends Handler 17 | { 18 | /** 19 | * @var callable 20 | */ 21 | protected $callable; 22 | 23 | /** 24 | * @throws InvalidArgumentException If argument is not callable 25 | * @param callable $callable 26 | */ 27 | public function __construct($callable) 28 | { 29 | if (!is_callable($callable)) { 30 | throw new InvalidArgumentException( 31 | 'Argument to ' . __METHOD__ . ' must be valid callable' 32 | ); 33 | } 34 | 35 | $this->callable = $callable; 36 | } 37 | 38 | /** 39 | * @return int|null 40 | */ 41 | public function handle() 42 | { 43 | $exception = $this->getException(); 44 | $inspector = $this->getInspector(); 45 | $run = $this->getRun(); 46 | $callable = $this->callable; 47 | 48 | // invoke the callable directly, to get simpler stacktraces (in comparison to call_user_func). 49 | // this assumes that $callable is a properly typed php-callable, which we check in __construct(). 50 | return $callable($exception, $inspector, $run); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Whoops/Handler/Handler.php: -------------------------------------------------------------------------------- 1 | <?php 2 | /** 3 | * Whoops - php errors for cool kids 4 | * @author Filipe Dobreira <http://github.com/filp> 5 | */ 6 | 7 | namespace Whoops\Handler; 8 | 9 | use Whoops\Inspector\InspectorInterface; 10 | use Whoops\RunInterface; 11 | 12 | /** 13 | * Abstract implementation of a Handler. 14 | */ 15 | abstract class Handler implements HandlerInterface 16 | { 17 | /* 18 | Return constants that can be returned from Handler::handle 19 | to message the handler walker. 20 | */ 21 | const DONE = 0x10; // returning this is optional, only exists for 22 | // semantic purposes 23 | /** 24 | * The Handler has handled the Throwable in some way, and wishes to skip any other Handler. 25 | * Execution will continue. 26 | */ 27 | const LAST_HANDLER = 0x20; 28 | /** 29 | * The Handler has handled the Throwable in some way, and wishes to quit/stop execution 30 | */ 31 | const QUIT = 0x30; 32 | 33 | /** 34 | * @var RunInterface 35 | */ 36 | private $run; 37 | 38 | /** 39 | * @var InspectorInterface $inspector 40 | */ 41 | private $inspector; 42 | 43 | /** 44 | * @var \Throwable $exception 45 | */ 46 | private $exception; 47 | 48 | /** 49 | * @param RunInterface $run 50 | */ 51 | public function setRun(RunInterface $run) 52 | { 53 | $this->run = $run; 54 | } 55 | 56 | /** 57 | * @return RunInterface 58 | */ 59 | protected function getRun() 60 | { 61 | return $this->run; 62 | } 63 | 64 | /** 65 | * @param InspectorInterface $inspector 66 | */ 67 | public function setInspector(InspectorInterface $inspector) 68 | { 69 | $this->inspector = $inspector; 70 | } 71 | 72 | /** 73 | * @return InspectorInterface 74 | */ 75 | protected function getInspector() 76 | { 77 | return $this->inspector; 78 | } 79 | 80 | /** 81 | * @param \Throwable $exception 82 | */ 83 | public function setException($exception) 84 | { 85 | $this->exception = $exception; 86 | } 87 | 88 | /** 89 | * @return \Throwable 90 | */ 91 | protected function getException() 92 | { 93 | return $this->exception; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/Whoops/Handler/HandlerInterface.php: -------------------------------------------------------------------------------- 1 | <?php 2 | /** 3 | * Whoops - php errors for cool kids 4 | * @author Filipe Dobreira <http://github.com/filp> 5 | */ 6 | 7 | namespace Whoops\Handler; 8 | 9 | use Whoops\Inspector\InspectorInterface; 10 | use Whoops\RunInterface; 11 | 12 | interface HandlerInterface 13 | { 14 | /** 15 | * @return int|null A handler may return nothing, or a Handler::HANDLE_* constant 16 | */ 17 | public function handle(); 18 | 19 | /** 20 | * @param RunInterface $run 21 | * @return void 22 | */ 23 | public function setRun(RunInterface $run); 24 | 25 | /** 26 | * @param \Throwable $exception 27 | * @return void 28 | */ 29 | public function setException($exception); 30 | 31 | /** 32 | * @param InspectorInterface $inspector 33 | * @return void 34 | */ 35 | public function setInspector(InspectorInterface $inspector); 36 | } 37 | -------------------------------------------------------------------------------- /src/Whoops/Handler/JsonResponseHandler.php: -------------------------------------------------------------------------------- 1 | <?php 2 | /** 3 | * Whoops - php errors for cool kids 4 | * @author Filipe Dobreira <http://github.com/filp> 5 | */ 6 | 7 | namespace Whoops\Handler; 8 | 9 | use Whoops\Exception\Formatter; 10 | 11 | /** 12 | * Catches an exception and converts it to a JSON 13 | * response. Additionally can also return exception 14 | * frames for consumption by an API. 15 | */ 16 | class JsonResponseHandler extends Handler 17 | { 18 | /** 19 | * @var bool 20 | */ 21 | private $returnFrames = false; 22 | 23 | /** 24 | * @var bool 25 | */ 26 | private $jsonApi = false; 27 | 28 | /** 29 | * Returns errors[[]] instead of error[] to be in compliance with the json:api spec 30 | * @param bool $jsonApi Default is false 31 | * @return static 32 | */ 33 | public function setJsonApi($jsonApi = false) 34 | { 35 | $this->jsonApi = (bool) $jsonApi; 36 | return $this; 37 | } 38 | 39 | /** 40 | * @param bool|null $returnFrames 41 | * @return bool|static 42 | */ 43 | public function addTraceToOutput($returnFrames = null) 44 | { 45 | if (func_num_args() == 0) { 46 | return $this->returnFrames; 47 | } 48 | 49 | $this->returnFrames = (bool) $returnFrames; 50 | return $this; 51 | } 52 | 53 | /** 54 | * @return int 55 | */ 56 | public function handle() 57 | { 58 | if ($this->jsonApi === true) { 59 | $response = [ 60 | 'errors' => [ 61 | Formatter::formatExceptionAsDataArray( 62 | $this->getInspector(), 63 | $this->addTraceToOutput(), 64 | $this->getRun()->getFrameFilters() 65 | ), 66 | ] 67 | ]; 68 | } else { 69 | $response = [ 70 | 'error' => Formatter::formatExceptionAsDataArray( 71 | $this->getInspector(), 72 | $this->addTraceToOutput(), 73 | $this->getRun()->getFrameFilters() 74 | ), 75 | ]; 76 | } 77 | 78 | echo json_encode($response, defined('JSON_PARTIAL_OUTPUT_ON_ERROR') ? JSON_PARTIAL_OUTPUT_ON_ERROR : 0); 79 | 80 | return Handler::QUIT; 81 | } 82 | 83 | /** 84 | * @return string 85 | */ 86 | public function contentType() 87 | { 88 | return 'application/json'; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Whoops/Handler/PlainTextHandler.php: -------------------------------------------------------------------------------- 1 | <?php 2 | /** 3 | * Whoops - php errors for cool kids 4 | * @author Filipe Dobreira <http://github.com/filp> 5 | * Plaintext handler for command line and logs. 6 | * @author Pierre-Yves Landuré <https://howto.biapy.com/> 7 | */ 8 | 9 | namespace Whoops\Handler; 10 | 11 | use InvalidArgumentException; 12 | use Psr\Log\LoggerInterface; 13 | use Whoops\Exception\Frame; 14 | 15 | /** 16 | * Handler outputing plaintext error messages. Can be used 17 | * directly, or will be instantiated automagically by Whoops\Run 18 | * if passed to Run::pushHandler 19 | */ 20 | class PlainTextHandler extends Handler 21 | { 22 | const VAR_DUMP_PREFIX = ' | '; 23 | 24 | /** 25 | * @var \Psr\Log\LoggerInterface 26 | */ 27 | protected $logger; 28 | 29 | /** 30 | * @var callable 31 | */ 32 | protected $dumper; 33 | 34 | /** 35 | * @var bool 36 | */ 37 | private $addTraceToOutput = true; 38 | 39 | /** 40 | * @var bool|integer 41 | */ 42 | private $addTraceFunctionArgsToOutput = false; 43 | 44 | /** 45 | * @var integer 46 | */ 47 | private $traceFunctionArgsOutputLimit = 1024; 48 | 49 | /** 50 | * @var bool 51 | */ 52 | private $addPreviousToOutput = true; 53 | 54 | /** 55 | * @var bool 56 | */ 57 | private $loggerOnly = false; 58 | 59 | /** 60 | * Constructor. 61 | * @throws InvalidArgumentException If argument is not null or a LoggerInterface 62 | * @param \Psr\Log\LoggerInterface|null $logger 63 | */ 64 | public function __construct($logger = null) 65 | { 66 | $this->setLogger($logger); 67 | } 68 | 69 | /** 70 | * Set the output logger interface. 71 | * @throws InvalidArgumentException If argument is not null or a LoggerInterface 72 | * @param \Psr\Log\LoggerInterface|null $logger 73 | */ 74 | public function setLogger($logger = null) 75 | { 76 | if (! (is_null($logger) 77 | || $logger instanceof LoggerInterface)) { 78 | throw new InvalidArgumentException( 79 | 'Argument to ' . __METHOD__ . 80 | " must be a valid Logger Interface (aka. Monolog), " . 81 | get_class($logger) . ' given.' 82 | ); 83 | } 84 | 85 | $this->logger = $logger; 86 | } 87 | 88 | /** 89 | * @return \Psr\Log\LoggerInterface|null 90 | */ 91 | public function getLogger() 92 | { 93 | return $this->logger; 94 | } 95 | 96 | /** 97 | * Set var dumper callback function. 98 | * 99 | * @param callable $dumper 100 | * @return static 101 | */ 102 | public function setDumper(callable $dumper) 103 | { 104 | $this->dumper = $dumper; 105 | return $this; 106 | } 107 | 108 | /** 109 | * Add error trace to output. 110 | * @param bool|null $addTraceToOutput 111 | * @return bool|static 112 | */ 113 | public function addTraceToOutput($addTraceToOutput = null) 114 | { 115 | if (func_num_args() == 0) { 116 | return $this->addTraceToOutput; 117 | } 118 | 119 | $this->addTraceToOutput = (bool) $addTraceToOutput; 120 | return $this; 121 | } 122 | 123 | /** 124 | * Add previous exceptions to output. 125 | * @param bool|null $addPreviousToOutput 126 | * @return bool|static 127 | */ 128 | public function addPreviousToOutput($addPreviousToOutput = null) 129 | { 130 | if (func_num_args() == 0) { 131 | return $this->addPreviousToOutput; 132 | } 133 | 134 | $this->addPreviousToOutput = (bool) $addPreviousToOutput; 135 | return $this; 136 | } 137 | 138 | /** 139 | * Add error trace function arguments to output. 140 | * Set to True for all frame args, or integer for the n first frame args. 141 | * @param bool|integer|null $addTraceFunctionArgsToOutput 142 | * @return static|bool|integer 143 | */ 144 | public function addTraceFunctionArgsToOutput($addTraceFunctionArgsToOutput = null) 145 | { 146 | if (func_num_args() == 0) { 147 | return $this->addTraceFunctionArgsToOutput; 148 | } 149 | 150 | if (! is_integer($addTraceFunctionArgsToOutput)) { 151 | $this->addTraceFunctionArgsToOutput = (bool) $addTraceFunctionArgsToOutput; 152 | } else { 153 | $this->addTraceFunctionArgsToOutput = $addTraceFunctionArgsToOutput; 154 | } 155 | return $this; 156 | } 157 | 158 | /** 159 | * Set the size limit in bytes of frame arguments var_dump output. 160 | * If the limit is reached, the var_dump output is discarded. 161 | * Prevent memory limit errors. 162 | * @var integer 163 | * @return static 164 | */ 165 | public function setTraceFunctionArgsOutputLimit($traceFunctionArgsOutputLimit) 166 | { 167 | $this->traceFunctionArgsOutputLimit = (integer) $traceFunctionArgsOutputLimit; 168 | return $this; 169 | } 170 | 171 | /** 172 | * Create plain text response and return it as a string 173 | * @return string 174 | */ 175 | public function generateResponse() 176 | { 177 | $exception = $this->getException(); 178 | $message = $this->getExceptionOutput($exception); 179 | 180 | if ($this->addPreviousToOutput) { 181 | $previous = $exception->getPrevious(); 182 | while ($previous) { 183 | $message .= "\n\nCaused by\n" . $this->getExceptionOutput($previous); 184 | $previous = $previous->getPrevious(); 185 | } 186 | } 187 | 188 | 189 | return $message . $this->getTraceOutput() . "\n"; 190 | } 191 | 192 | /** 193 | * Get the size limit in bytes of frame arguments var_dump output. 194 | * If the limit is reached, the var_dump output is discarded. 195 | * Prevent memory limit errors. 196 | * @return integer 197 | */ 198 | public function getTraceFunctionArgsOutputLimit() 199 | { 200 | return $this->traceFunctionArgsOutputLimit; 201 | } 202 | 203 | /** 204 | * Only output to logger. 205 | * @param bool|null $loggerOnly 206 | * @return static|bool 207 | */ 208 | public function loggerOnly($loggerOnly = null) 209 | { 210 | if (func_num_args() == 0) { 211 | return $this->loggerOnly; 212 | } 213 | 214 | $this->loggerOnly = (bool) $loggerOnly; 215 | return $this; 216 | } 217 | 218 | /** 219 | * Test if handler can output to stdout. 220 | * @return bool 221 | */ 222 | private function canOutput() 223 | { 224 | return !$this->loggerOnly(); 225 | } 226 | 227 | /** 228 | * Get the frame args var_dump. 229 | * @param \Whoops\Exception\Frame $frame [description] 230 | * @param integer $line [description] 231 | * @return string 232 | */ 233 | private function getFrameArgsOutput(Frame $frame, $line) 234 | { 235 | if ($this->addTraceFunctionArgsToOutput() === false 236 | || $this->addTraceFunctionArgsToOutput() < $line) { 237 | return ''; 238 | } 239 | 240 | // Dump the arguments: 241 | ob_start(); 242 | $this->dump($frame->getArgs()); 243 | if (ob_get_length() > $this->getTraceFunctionArgsOutputLimit()) { 244 | // The argument var_dump is to big. 245 | // Discarded to limit memory usage. 246 | ob_clean(); 247 | return sprintf( 248 | "\n%sArguments dump length greater than %d Bytes. Discarded.", 249 | self::VAR_DUMP_PREFIX, 250 | $this->getTraceFunctionArgsOutputLimit() 251 | ); 252 | } 253 | 254 | return sprintf( 255 | "\n%s", 256 | preg_replace('/^/m', self::VAR_DUMP_PREFIX, ob_get_clean()) 257 | ); 258 | } 259 | 260 | /** 261 | * Dump variable. 262 | * 263 | * @param mixed $var 264 | * @return void 265 | */ 266 | protected function dump($var) 267 | { 268 | if ($this->dumper) { 269 | call_user_func($this->dumper, $var); 270 | } else { 271 | var_dump($var); 272 | } 273 | } 274 | 275 | /** 276 | * Get the exception trace as plain text. 277 | * @return string 278 | */ 279 | private function getTraceOutput() 280 | { 281 | if (! $this->addTraceToOutput()) { 282 | return ''; 283 | } 284 | $inspector = $this->getInspector(); 285 | $frames = $inspector->getFrames($this->getRun()->getFrameFilters()); 286 | 287 | $response = "\nStack trace:"; 288 | 289 | $line = 1; 290 | foreach ($frames as $frame) { 291 | /** @var Frame $frame */ 292 | $class = $frame->getClass(); 293 | 294 | $template = "\n%3d. %s->%s() %s:%d%s"; 295 | if (! $class) { 296 | // Remove method arrow (->) from output. 297 | $template = "\n%3d. %s%s() %s:%d%s"; 298 | } 299 | 300 | $response .= sprintf( 301 | $template, 302 | $line, 303 | $class, 304 | $frame->getFunction(), 305 | $frame->getFile(), 306 | $frame->getLine(), 307 | $this->getFrameArgsOutput($frame, $line) 308 | ); 309 | 310 | $line++; 311 | } 312 | 313 | return $response; 314 | } 315 | 316 | /** 317 | * Get the exception as plain text. 318 | * @param \Throwable $exception 319 | * @return string 320 | */ 321 | private function getExceptionOutput($exception) 322 | { 323 | return sprintf( 324 | "%s: %s in file %s on line %d", 325 | get_class($exception), 326 | $exception->getMessage(), 327 | $exception->getFile(), 328 | $exception->getLine() 329 | ); 330 | } 331 | 332 | /** 333 | * @return int 334 | */ 335 | public function handle() 336 | { 337 | $response = $this->generateResponse(); 338 | 339 | if ($this->getLogger()) { 340 | $this->getLogger()->error($response); 341 | } 342 | 343 | if (! $this->canOutput()) { 344 | return Handler::DONE; 345 | } 346 | 347 | echo $response; 348 | 349 | return Handler::QUIT; 350 | } 351 | 352 | /** 353 | * @return string 354 | */ 355 | public function contentType() 356 | { 357 | return 'text/plain'; 358 | } 359 | } 360 | -------------------------------------------------------------------------------- /src/Whoops/Handler/XmlResponseHandler.php: -------------------------------------------------------------------------------- 1 | <?php 2 | /** 3 | * Whoops - php errors for cool kids 4 | * @author Filipe Dobreira <http://github.com/filp> 5 | */ 6 | 7 | namespace Whoops\Handler; 8 | 9 | use SimpleXMLElement; 10 | use Whoops\Exception\Formatter; 11 | 12 | /** 13 | * Catches an exception and converts it to an XML 14 | * response. Additionally can also return exception 15 | * frames for consumption by an API. 16 | */ 17 | class XmlResponseHandler extends Handler 18 | { 19 | /** 20 | * @var bool 21 | */ 22 | private $returnFrames = false; 23 | 24 | /** 25 | * @param bool|null $returnFrames 26 | * @return bool|static 27 | */ 28 | public function addTraceToOutput($returnFrames = null) 29 | { 30 | if (func_num_args() == 0) { 31 | return $this->returnFrames; 32 | } 33 | 34 | $this->returnFrames = (bool) $returnFrames; 35 | return $this; 36 | } 37 | 38 | /** 39 | * @return int 40 | */ 41 | public function handle() 42 | { 43 | $response = [ 44 | 'error' => Formatter::formatExceptionAsDataArray( 45 | $this->getInspector(), 46 | $this->addTraceToOutput(), 47 | $this->getRun()->getFrameFilters() 48 | ), 49 | ]; 50 | 51 | echo self::toXml($response); 52 | 53 | return Handler::QUIT; 54 | } 55 | 56 | /** 57 | * @return string 58 | */ 59 | public function contentType() 60 | { 61 | return 'application/xml'; 62 | } 63 | 64 | /** 65 | * @param SimpleXMLElement $node Node to append data to, will be modified in place 66 | * @param array|\Traversable $data 67 | * @return SimpleXMLElement The modified node, for chaining 68 | */ 69 | private static function addDataToNode(\SimpleXMLElement $node, $data) 70 | { 71 | assert(is_array($data) || $data instanceof Traversable); 72 | 73 | foreach ($data as $key => $value) { 74 | if (is_numeric($key)) { 75 | // Convert the key to a valid string 76 | $key = "unknownNode_". (string) $key; 77 | } 78 | 79 | // Delete any char not allowed in XML element names 80 | $key = preg_replace('/[^a-z0-9\-\_\.\:]/i', '', $key); 81 | 82 | if (is_array($value)) { 83 | $child = $node->addChild($key); 84 | self::addDataToNode($child, $value); 85 | } else { 86 | $value = str_replace('&', '&', print_r($value, true)); 87 | $node->addChild($key, $value); 88 | } 89 | } 90 | 91 | return $node; 92 | } 93 | 94 | /** 95 | * The main function for converting to an XML document. 96 | * 97 | * @param array|\Traversable $data 98 | * @return string XML 99 | */ 100 | private static function toXml($data) 101 | { 102 | assert(is_array($data) || $data instanceof Traversable); 103 | 104 | $node = simplexml_load_string("<?xml version='1.0' encoding='utf-8'?><root />"); 105 | 106 | return self::addDataToNode($node, $data)->asXML(); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/Whoops/Inspector/InspectorFactory.php: -------------------------------------------------------------------------------- 1 | <?php 2 | /** 3 | * Whoops - php errors for cool kids 4 | * @author Filipe Dobreira <http://github.com/filp> 5 | */ 6 | 7 | namespace Whoops\Inspector; 8 | 9 | use Whoops\Exception\Inspector; 10 | 11 | class InspectorFactory implements InspectorFactoryInterface 12 | { 13 | /** 14 | * @param \Throwable $exception 15 | * @return InspectorInterface 16 | */ 17 | public function create($exception) 18 | { 19 | return new Inspector($exception, $this); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Whoops/Inspector/InspectorFactoryInterface.php: -------------------------------------------------------------------------------- 1 | <?php 2 | /** 3 | * Whoops - php errors for cool kids 4 | * @author Filipe Dobreira <http://github.com/filp> 5 | */ 6 | 7 | namespace Whoops\Inspector; 8 | 9 | interface InspectorFactoryInterface 10 | { 11 | /** 12 | * @param \Throwable $exception 13 | * @return InspectorInterface 14 | */ 15 | public function create($exception); 16 | } 17 | -------------------------------------------------------------------------------- /src/Whoops/Inspector/InspectorInterface.php: -------------------------------------------------------------------------------- 1 | <?php 2 | /** 3 | * Whoops - php errors for cool kids 4 | * @author Filipe Dobreira <http://github.com/filp> 5 | */ 6 | 7 | namespace Whoops\Inspector; 8 | 9 | interface InspectorInterface 10 | { 11 | /** 12 | * @return \Throwable 13 | */ 14 | public function getException(); 15 | 16 | /** 17 | * @return string 18 | */ 19 | public function getExceptionName(); 20 | 21 | /** 22 | * @return string 23 | */ 24 | public function getExceptionMessage(); 25 | 26 | /** 27 | * @return string[] 28 | */ 29 | public function getPreviousExceptionMessages(); 30 | 31 | /** 32 | * @return int[] 33 | */ 34 | public function getPreviousExceptionCodes(); 35 | 36 | /** 37 | * Returns a url to the php-manual related to the underlying error - when available. 38 | * 39 | * @return string|null 40 | */ 41 | public function getExceptionDocrefUrl(); 42 | 43 | /** 44 | * Does the wrapped Exception has a previous Exception? 45 | * @return bool 46 | */ 47 | public function hasPreviousException(); 48 | 49 | /** 50 | * Returns an Inspector for a previous Exception, if any. 51 | * @todo Clean this up a bit, cache stuff a bit better. 52 | * @return InspectorInterface 53 | */ 54 | public function getPreviousExceptionInspector(); 55 | 56 | /** 57 | * Returns an array of all previous exceptions for this inspector's exception 58 | * @return \Throwable[] 59 | */ 60 | public function getPreviousExceptions(); 61 | 62 | /** 63 | * Returns an iterator for the inspected exception's 64 | * frames. 65 | * 66 | * @param array<callable> $frameFilters 67 | * 68 | * @return \Whoops\Exception\FrameCollection 69 | */ 70 | public function getFrames(array $frameFilters = []); 71 | } 72 | -------------------------------------------------------------------------------- /src/Whoops/Resources/css/prism.css: -------------------------------------------------------------------------------- 1 | /* PrismJS 1.30.0 2 | https://prismjs.com/download.html#themes=prism-tomorrow&languages=markup+markup-templating+php&plugins=line-highlight+line-numbers */ 3 | code[class*=language-],pre[class*=language-]{color:#ccc;background:0 0;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#2d2d2d}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.block-comment,.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#999}.token.punctuation{color:#ccc}.token.attr-name,.token.deleted,.token.namespace,.token.tag{color:#e2777a}.token.function-name{color:#6196cc}.token.boolean,.token.function,.token.number{color:#f08d49}.token.class-name,.token.constant,.token.property,.token.symbol{color:#f8c555}.token.atrule,.token.builtin,.token.important,.token.keyword,.token.selector{color:#cc99cd}.token.attr-value,.token.char,.token.regex,.token.string,.token.variable{color:#7ec699}.token.entity,.token.operator,.token.url{color:#67cdcc}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.inserted{color:green} 4 | pre[data-line]{position:relative;padding:1em 0 1em 3em}.line-highlight{position:absolute;left:0;right:0;padding:inherit 0;margin-top:1em;background:hsla(24,20%,50%,.08);background:linear-gradient(to right,hsla(24,20%,50%,.1) 70%,hsla(24,20%,50%,0));pointer-events:none;line-height:inherit;white-space:pre}@media print{.line-highlight{-webkit-print-color-adjust:exact;color-adjust:exact}}.line-highlight:before,.line-highlight[data-end]:after{content:attr(data-start);position:absolute;top:.4em;left:.6em;min-width:1em;padding:0 .5em;background-color:hsla(24,20%,50%,.4);color:#f4f1ef;font:bold 65%/1.5 sans-serif;text-align:center;vertical-align:.3em;border-radius:999px;text-shadow:none;box-shadow:0 1px #fff}.line-highlight[data-end]:after{content:attr(data-end);top:auto;bottom:.4em}.line-numbers .line-highlight:after,.line-numbers .line-highlight:before{content:none}pre[id].linkable-line-numbers span.line-numbers-rows{pointer-events:all}pre[id].linkable-line-numbers span.line-numbers-rows>span:before{cursor:pointer}pre[id].linkable-line-numbers span.line-numbers-rows>span:hover:before{background-color:rgba(128,128,128,.2)} 5 | pre[class*=language-].line-numbers{position:relative;padding-left:3.8em;counter-reset:linenumber}pre[class*=language-].line-numbers>code{position:relative;white-space:inherit}.line-numbers .line-numbers-rows{position:absolute;pointer-events:none;top:0;font-size:100%;left:-3.8em;width:3em;letter-spacing:-1px;border-right:1px solid #999;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.line-numbers-rows>span{display:block;counter-increment:linenumber}.line-numbers-rows>span:before{content:counter(linenumber);color:#999;display:block;padding-right:.8em;text-align:right} 6 | -------------------------------------------------------------------------------- /src/Whoops/Resources/css/whoops.base.css: -------------------------------------------------------------------------------- 1 | body { 2 | font: 12px "Helvetica Neue", helvetica, arial, sans-serif; 3 | color: #131313; 4 | background: #eeeeee; 5 | padding:0; 6 | margin: 0; 7 | max-height: 100%; 8 | 9 | text-rendering: optimizeLegibility; 10 | } 11 | a { 12 | text-decoration: none; 13 | } 14 | 15 | .Whoops.container { 16 | position: relative; 17 | z-index: 9999999999; 18 | } 19 | 20 | .panel { 21 | overflow-y: scroll; 22 | height: 100%; 23 | position: fixed; 24 | margin: 0; 25 | left: 0; 26 | top: 0; 27 | } 28 | 29 | .branding { 30 | position: absolute; 31 | top: 10px; 32 | right: 20px; 33 | color: #777777; 34 | font-size: 10px; 35 | z-index: 100; 36 | } 37 | .branding a { 38 | color: #e95353; 39 | } 40 | 41 | header { 42 | color: white; 43 | box-sizing: border-box; 44 | background-color: #2a2a2a; 45 | padding: 35px 40px; 46 | max-height: 180px; 47 | overflow: hidden; 48 | transition: 0.5s; 49 | } 50 | 51 | header.header-expand { 52 | max-height: 1000px; 53 | } 54 | 55 | .exc-title { 56 | margin: 0; 57 | color: #bebebe; 58 | font-size: 14px; 59 | } 60 | .exc-title-primary, .exc-title-secondary { 61 | color: #e95353; 62 | } 63 | 64 | .exc-message { 65 | font-size: 20px; 66 | word-wrap: break-word; 67 | margin: 4px 0 0 0; 68 | color: white; 69 | } 70 | .exc-message span { 71 | display: block; 72 | } 73 | .exc-message-empty-notice { 74 | color: #a29d9d; 75 | font-weight: 300; 76 | } 77 | 78 | .prev-exc-title { 79 | margin: 10px 0; 80 | } 81 | 82 | .prev-exc-title + ul { 83 | margin: 0; 84 | padding: 0 0 0 20px; 85 | line-height: 12px; 86 | } 87 | 88 | .prev-exc-title + ul li { 89 | font: 12px "Helvetica Neue", helvetica, arial, sans-serif; 90 | } 91 | 92 | .prev-exc-title + ul li .prev-exc-code { 93 | display: inline-block; 94 | color: #bebebe; 95 | } 96 | 97 | .details-container { 98 | left: 30%; 99 | width: 70%; 100 | background: #fafafa; 101 | } 102 | .details { 103 | padding: 5px; 104 | } 105 | 106 | .details-heading { 107 | color: #4288CE; 108 | font-weight: 300; 109 | padding-bottom: 10px; 110 | margin-bottom: 10px; 111 | border-bottom: 1px solid rgba(0, 0, 0, .1); 112 | } 113 | 114 | .details pre.sf-dump { 115 | white-space: pre; 116 | word-wrap: inherit; 117 | } 118 | 119 | .details pre.sf-dump, 120 | .details pre.sf-dump .sf-dump-num, 121 | .details pre.sf-dump .sf-dump-const, 122 | .details pre.sf-dump .sf-dump-str, 123 | .details pre.sf-dump .sf-dump-note, 124 | .details pre.sf-dump .sf-dump-ref, 125 | .details pre.sf-dump .sf-dump-public, 126 | .details pre.sf-dump .sf-dump-protected, 127 | .details pre.sf-dump .sf-dump-private, 128 | .details pre.sf-dump .sf-dump-meta, 129 | .details pre.sf-dump .sf-dump-key, 130 | .details pre.sf-dump .sf-dump-index { 131 | color: #463C54; 132 | } 133 | 134 | .left-panel { 135 | width: 30%; 136 | background: #ded8d8; 137 | } 138 | 139 | .frames-description { 140 | background: rgba(0, 0, 0, .05); 141 | padding: 8px 15px; 142 | color: #a29d9d; 143 | font-size: 11px; 144 | } 145 | 146 | .frames-description.frames-description-application { 147 | text-align: center; 148 | font-size: 12px; 149 | } 150 | .frames-container.frames-container-application .frame:not(.frame-application) { 151 | display: none; 152 | } 153 | 154 | .frames-tab { 155 | color: #a29d9d; 156 | display: inline-block; 157 | padding: 4px 8px; 158 | margin: 0 2px; 159 | border-radius: 3px; 160 | } 161 | 162 | .frames-tab.frames-tab-active { 163 | background-color: #2a2a2a; 164 | color: #bebebe; 165 | } 166 | 167 | .frame { 168 | padding: 14px; 169 | cursor: pointer; 170 | transition: all 0.1s ease; 171 | background: #eeeeee; 172 | } 173 | .frame:not(:last-child) { 174 | border-bottom: 1px solid rgba(0, 0, 0, .05); 175 | } 176 | 177 | .frame.active { 178 | box-shadow: inset -5px 0 0 0 #4288CE; 179 | color: #4288CE; 180 | } 181 | 182 | .frame:not(.active):hover { 183 | background: #BEE9EA; 184 | } 185 | 186 | .frame-method-info { 187 | margin-bottom: 10px; 188 | } 189 | 190 | .frame-class, .frame-function, .frame-index { 191 | font-size: 14px; 192 | } 193 | 194 | .frame-index { 195 | float: left; 196 | } 197 | 198 | .frame-method-info { 199 | margin-left: 24px; 200 | } 201 | 202 | .frame-index { 203 | font-size: 11px; 204 | color: #a29d9d; 205 | background-color: rgba(0, 0, 0, .05); 206 | height: 18px; 207 | width: 18px; 208 | line-height: 18px; 209 | border-radius: 5px; 210 | padding: 0 1px 0 1px; 211 | text-align: center; 212 | display: inline-block; 213 | } 214 | 215 | .frame-application .frame-index { 216 | background-color: #2a2a2a; 217 | color: #bebebe; 218 | } 219 | 220 | .frame-file { 221 | font-family: "Inconsolata", "Fira Mono", "Source Code Pro", Monaco, Consolas, "Lucida Console", monospace; 222 | color: #a29d9d; 223 | } 224 | 225 | .frame-file .editor-link { 226 | color: #a29d9d; 227 | } 228 | 229 | .frame-line { 230 | font-weight: bold; 231 | } 232 | 233 | .frame-line:before { 234 | content: ":"; 235 | } 236 | 237 | .frame-code { 238 | padding: 5px; 239 | background: #303030; 240 | display: none; 241 | } 242 | 243 | .frame-code.active { 244 | display: block; 245 | } 246 | 247 | .frame-code .frame-file { 248 | color: #a29d9d; 249 | padding: 12px 6px; 250 | 251 | border-bottom: none; 252 | } 253 | 254 | .code-block { 255 | padding: 10px; 256 | margin: 0; 257 | border-radius: 6px; 258 | box-shadow: 0 3px 0 rgba(0, 0, 0, .05), 259 | 0 10px 30px rgba(0, 0, 0, .05), 260 | inset 0 0 1px 0 rgba(255, 255, 255, .07); 261 | -moz-tab-size: 4; 262 | -o-tab-size: 4; 263 | tab-size: 4; 264 | } 265 | 266 | .linenums { 267 | margin: 0; 268 | margin-left: 10px; 269 | } 270 | 271 | .frame-comments { 272 | border-top: none; 273 | margin-top: 15px; 274 | 275 | font-size: 12px; 276 | } 277 | 278 | .frame-comments.empty { 279 | } 280 | 281 | .frame-comments.empty:before { 282 | content: "No comments for this stack frame."; 283 | font-weight: 300; 284 | color: #a29d9d; 285 | } 286 | 287 | .frame-comment { 288 | padding: 10px; 289 | color: #e3e3e3; 290 | border-radius: 6px; 291 | background-color: rgba(255, 255, 255, .05); 292 | } 293 | 294 | .frame-comment a { 295 | font-weight: bold; 296 | text-decoration: underline; 297 | color: #c6c6c6; 298 | } 299 | 300 | .frame-comment:not(:last-child) { 301 | border-bottom: 1px dotted rgba(0, 0, 0, .3); 302 | } 303 | 304 | .frame-comment-context { 305 | font-size: 10px; 306 | color: white; 307 | } 308 | 309 | .delimiter { 310 | display: inline-block; 311 | } 312 | 313 | .data-table-container label { 314 | font-size: 16px; 315 | color: #303030; 316 | font-weight: bold; 317 | margin: 10px 0; 318 | 319 | display: block; 320 | 321 | margin-bottom: 5px; 322 | padding-bottom: 5px; 323 | } 324 | .data-table { 325 | width: 100%; 326 | margin-bottom: 10px; 327 | } 328 | 329 | .data-table tbody { 330 | font: 13px "Inconsolata", "Fira Mono", "Source Code Pro", Monaco, Consolas, "Lucida Console", monospace; 331 | } 332 | 333 | .data-table thead { 334 | display: none; 335 | } 336 | 337 | .data-table tr { 338 | padding: 5px 0; 339 | } 340 | 341 | .data-table td:first-child { 342 | width: 20%; 343 | min-width: 130px; 344 | overflow: hidden; 345 | font-weight: bold; 346 | color: #463C54; 347 | padding-right: 5px; 348 | 349 | } 350 | 351 | .data-table td:last-child { 352 | width: 80%; 353 | -ms-word-break: break-all; 354 | word-break: break-all; 355 | word-break: break-word; 356 | -webkit-hyphens: auto; 357 | -moz-hyphens: auto; 358 | hyphens: auto; 359 | } 360 | 361 | .data-table span.empty { 362 | color: rgba(0, 0, 0, .3); 363 | font-weight: 300; 364 | } 365 | .data-table label.empty { 366 | display: inline; 367 | } 368 | 369 | .handler { 370 | padding: 4px 0; 371 | font: 14px "Inconsolata", "Fira Mono", "Source Code Pro", Monaco, Consolas, "Lucida Console", monospace; 372 | } 373 | 374 | #plain-exception { 375 | display: none; 376 | } 377 | 378 | .rightButton { 379 | cursor: pointer; 380 | border: 0; 381 | opacity: .8; 382 | background: none; 383 | 384 | color: rgba(255, 255, 255, 0.1); 385 | box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.1); 386 | 387 | border-radius: 3px; 388 | 389 | outline: none !important; 390 | } 391 | 392 | .rightButton:hover { 393 | box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.3); 394 | color: rgba(255, 255, 255, 0.3); 395 | } 396 | 397 | /* inspired by githubs kbd styles */ 398 | kbd { 399 | -moz-border-bottom-colors: none; 400 | -moz-border-left-colors: none; 401 | -moz-border-right-colors: none; 402 | -moz-border-top-colors: none; 403 | background-color: #fcfcfc; 404 | border-color: #ccc #ccc #bbb; 405 | border-image: none; 406 | border-style: solid; 407 | border-width: 1px; 408 | color: #555; 409 | display: inline-block; 410 | font-size: 11px; 411 | line-height: 10px; 412 | padding: 3px 5px; 413 | vertical-align: middle; 414 | } 415 | 416 | 417 | /* == Media queries */ 418 | 419 | /* Expand the spacing in the details section */ 420 | @media (min-width: 1000px) { 421 | .details, .frame-code { 422 | padding: 20px 40px; 423 | } 424 | 425 | .details-container { 426 | left: 32%; 427 | width: 68%; 428 | } 429 | 430 | .frames-container { 431 | margin: 5px; 432 | } 433 | 434 | .left-panel { 435 | width: 32%; 436 | } 437 | } 438 | 439 | /* Stack panels */ 440 | @media (max-width: 600px) { 441 | .panel { 442 | position: static; 443 | width: 100%; 444 | } 445 | } 446 | 447 | /* Stack details tables */ 448 | @media (max-width: 400px) { 449 | .data-table, 450 | .data-table tbody, 451 | .data-table tbody tr, 452 | .data-table tbody td { 453 | display: block; 454 | width: 100%; 455 | } 456 | 457 | .data-table tbody tr:first-child { 458 | padding-top: 0; 459 | } 460 | 461 | .data-table tbody td:first-child, 462 | .data-table tbody td:last-child { 463 | padding-left: 0; 464 | padding-right: 0; 465 | } 466 | 467 | .data-table tbody td:last-child { 468 | padding-top: 3px; 469 | } 470 | } 471 | 472 | .tooltipped { 473 | position: relative 474 | } 475 | .tooltipped:after { 476 | position: absolute; 477 | z-index: 1000000; 478 | display: none; 479 | padding: 5px 8px; 480 | color: #fff; 481 | text-align: center; 482 | text-decoration: none; 483 | text-shadow: none; 484 | text-transform: none; 485 | letter-spacing: normal; 486 | word-wrap: break-word; 487 | white-space: pre; 488 | pointer-events: none; 489 | content: attr(aria-label); 490 | background: rgba(0, 0, 0, 0.8); 491 | border-radius: 3px; 492 | -webkit-font-smoothing: subpixel-antialiased 493 | } 494 | .tooltipped:before { 495 | position: absolute; 496 | z-index: 1000001; 497 | display: none; 498 | width: 0; 499 | height: 0; 500 | color: rgba(0, 0, 0, 0.8); 501 | pointer-events: none; 502 | content: ""; 503 | border: 5px solid transparent 504 | } 505 | .tooltipped:hover:before, 506 | .tooltipped:hover:after, 507 | .tooltipped:active:before, 508 | .tooltipped:active:after, 509 | .tooltipped:focus:before, 510 | .tooltipped:focus:after { 511 | display: inline-block; 512 | text-decoration: none 513 | } 514 | .tooltipped-s:after { 515 | top: 100%; 516 | right: 50%; 517 | margin-top: 5px 518 | } 519 | .tooltipped-s:before { 520 | top: auto; 521 | right: 50%; 522 | bottom: -5px; 523 | margin-right: -5px; 524 | border-bottom-color: rgba(0, 0, 0, 0.8) 525 | } 526 | 527 | pre.sf-dump { 528 | padding: 0px !important; 529 | margin: 0px !important; 530 | } 531 | 532 | .search-for-help { 533 | width: 85%; 534 | padding: 0; 535 | margin: 10px 0; 536 | list-style-type: none; 537 | display: inline-block; 538 | } 539 | .search-for-help li { 540 | display: inline-block; 541 | margin-right: 5px; 542 | } 543 | .search-for-help li:last-child { 544 | margin-right: 0; 545 | } 546 | .search-for-help li a { 547 | 548 | } 549 | .search-for-help li a i { 550 | width: 16px; 551 | height: 16px; 552 | overflow: hidden; 553 | display: block; 554 | } 555 | .search-for-help li a svg { 556 | fill: #fff; 557 | } 558 | .search-for-help li a svg path { 559 | background-size: contain; 560 | } 561 | 562 | .line-numbers-rows span { 563 | pointer-events: auto; 564 | cursor: pointer; 565 | } 566 | .line-numbers-rows span:hover { 567 | text-decoration: underline; 568 | } 569 | -------------------------------------------------------------------------------- /src/Whoops/Resources/js/clipboard.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * clipboard.js v2.0.11 3 | * https://clipboardjs.com/ 4 | * 5 | * Licensed MIT © Zeno Rocha 6 | */ 7 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={686:function(t,e,n){"use strict";n.d(e,{default:function(){return b}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),r=n.n(e);function c(t){try{return document.execCommand(t)}catch(t){return}}var a=function(t){t=r()(t);return c("cut"),t};function o(t,e){var n,o,t=(n=t,o="rtl"===document.documentElement.getAttribute("dir"),(t=document.createElement("textarea")).style.fontSize="12pt",t.style.border="0",t.style.padding="0",t.style.margin="0",t.style.position="absolute",t.style[o?"right":"left"]="-9999px",o=window.pageYOffset||document.documentElement.scrollTop,t.style.top="".concat(o,"px"),t.setAttribute("readonly",""),t.value=n,t);return e.container.appendChild(t),e=r()(t),c("copy"),t.remove(),e}var f=function(t){var e=1<arguments.length&&void 0!==arguments[1]?arguments[1]:{container:document.body},n="";return"string"==typeof t?n=o(t,e):t instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(null==t?void 0:t.type)?n=o(t.value,e):(n=r()(t),c("copy")),n};function l(t){return(l="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}var s=function(){var t=0<arguments.length&&void 0!==arguments[0]?arguments[0]:{},e=t.action,n=void 0===e?"copy":e,o=t.container,e=t.target,t=t.text;if("copy"!==n&&"cut"!==n)throw new Error('Invalid "action" value, use either "copy" or "cut"');if(void 0!==e){if(!e||"object"!==l(e)||1!==e.nodeType)throw new Error('Invalid "target" value, use a valid Element');if("copy"===n&&e.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if("cut"===n&&(e.hasAttribute("readonly")||e.hasAttribute("disabled")))throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes')}return t?f(t,{container:o}):e?"cut"===n?a(e):f(e,{container:o}):void 0};function p(t){return(p="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function d(t,e){for(var n=0;n<e.length;n++){var o=e[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(t,o.key,o)}}function y(t,e){return(y=Object.setPrototypeOf||function(t,e){return t.__proto__=e,t})(t,e)}function h(n){var o=function(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],function(){})),!0}catch(t){return!1}}();return function(){var t,e=v(n);return t=o?(t=v(this).constructor,Reflect.construct(e,arguments,t)):e.apply(this,arguments),e=this,!(t=t)||"object"!==p(t)&&"function"!=typeof t?function(t){if(void 0!==t)return t;throw new ReferenceError("this hasn't been initialised - super() hasn't been called")}(e):t}}function v(t){return(v=Object.setPrototypeOf?Object.getPrototypeOf:function(t){return t.__proto__||Object.getPrototypeOf(t)})(t)}function m(t,e){t="data-clipboard-".concat(t);if(e.hasAttribute(t))return e.getAttribute(t)}var b=function(){!function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function");t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,writable:!0,configurable:!0}}),e&&y(t,e)}(r,i());var t,e,n,o=h(r);function r(t,e){var n;return function(t){if(!(t instanceof r))throw new TypeError("Cannot call a class as a function")}(this),(n=o.call(this)).resolveOptions(e),n.listenClick(t),n}return t=r,n=[{key:"copy",value:function(t){var e=1<arguments.length&&void 0!==arguments[1]?arguments[1]:{container:document.body};return f(t,e)}},{key:"cut",value:function(t){return a(t)}},{key:"isSupported",value:function(){var t=0<arguments.length&&void 0!==arguments[0]?arguments[0]:["copy","cut"],t="string"==typeof t?[t]:t,e=!!document.queryCommandSupported;return t.forEach(function(t){e=e&&!!document.queryCommandSupported(t)}),e}}],(e=[{key:"resolveOptions",value:function(){var t=0<arguments.length&&void 0!==arguments[0]?arguments[0]:{};this.action="function"==typeof t.action?t.action:this.defaultAction,this.target="function"==typeof t.target?t.target:this.defaultTarget,this.text="function"==typeof t.text?t.text:this.defaultText,this.container="object"===p(t.container)?t.container:document.body}},{key:"listenClick",value:function(t){var e=this;this.listener=u()(t,"click",function(t){return e.onClick(t)})}},{key:"onClick",value:function(t){var e=t.delegateTarget||t.currentTarget,n=this.action(e)||"copy",t=s({action:n,container:this.container,target:this.target(e),text:this.text(e)});this.emit(t?"success":"error",{action:n,text:t,trigger:e,clearSelection:function(){e&&e.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(t){return m("action",t)}},{key:"defaultTarget",value:function(t){t=m("target",t);if(t)return document.querySelector(t)}},{key:"defaultText",value:function(t){return m("text",t)}},{key:"destroy",value:function(){this.listener.destroy()}}])&&d(t.prototype,e),n&&d(t,n),r}()},828:function(t){var e;"undefined"==typeof Element||Element.prototype.matches||((e=Element.prototype).matches=e.matchesSelector||e.mozMatchesSelector||e.msMatchesSelector||e.oMatchesSelector||e.webkitMatchesSelector),t.exports=function(t,e){for(;t&&9!==t.nodeType;){if("function"==typeof t.matches&&t.matches(e))return t;t=t.parentNode}}},438:function(t,e,n){var u=n(828);function i(t,e,n,o,r){var i=function(e,n,t,o){return function(t){t.delegateTarget=u(t.target,n),t.delegateTarget&&o.call(e,t)}}.apply(this,arguments);return t.addEventListener(n,i,r),{destroy:function(){t.removeEventListener(n,i,r)}}}t.exports=function(t,e,n,o,r){return"function"==typeof t.addEventListener?i.apply(null,arguments):"function"==typeof n?i.bind(null,document).apply(null,arguments):("string"==typeof t&&(t=document.querySelectorAll(t)),Array.prototype.map.call(t,function(t){return i(t,e,n,o,r)}))}},879:function(t,n){n.node=function(t){return void 0!==t&&t instanceof HTMLElement&&1===t.nodeType},n.nodeList=function(t){var e=Object.prototype.toString.call(t);return void 0!==t&&("[object NodeList]"===e||"[object HTMLCollection]"===e)&&"length"in t&&(0===t.length||n.node(t[0]))},n.string=function(t){return"string"==typeof t||t instanceof String},n.fn=function(t){return"[object Function]"===Object.prototype.toString.call(t)}},370:function(t,e,n){var f=n(879),l=n(438);t.exports=function(t,e,n){if(!t&&!e&&!n)throw new Error("Missing required arguments");if(!f.string(e))throw new TypeError("Second argument must be a String");if(!f.fn(n))throw new TypeError("Third argument must be a Function");if(f.node(t))return c=e,a=n,(u=t).addEventListener(c,a),{destroy:function(){u.removeEventListener(c,a)}};if(f.nodeList(t))return o=t,r=e,i=n,Array.prototype.forEach.call(o,function(t){t.addEventListener(r,i)}),{destroy:function(){Array.prototype.forEach.call(o,function(t){t.removeEventListener(r,i)})}};if(f.string(t))return t=t,e=e,n=n,l(document.body,t,e,n);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList");var o,r,i,u,c,a}},817:function(t){t.exports=function(t){var e,n="SELECT"===t.nodeName?(t.focus(),t.value):"INPUT"===t.nodeName||"TEXTAREA"===t.nodeName?((e=t.hasAttribute("readonly"))||t.setAttribute("readonly",""),t.select(),t.setSelectionRange(0,t.value.length),e||t.removeAttribute("readonly"),t.value):(t.hasAttribute("contenteditable")&&t.focus(),n=window.getSelection(),(e=document.createRange()).selectNodeContents(t),n.removeAllRanges(),n.addRange(e),n.toString());return n}},279:function(t){function e(){}e.prototype={on:function(t,e,n){var o=this.e||(this.e={});return(o[t]||(o[t]=[])).push({fn:e,ctx:n}),this},once:function(t,e,n){var o=this;function r(){o.off(t,r),e.apply(n,arguments)}return r._=e,this.on(t,r,n)},emit:function(t){for(var e=[].slice.call(arguments,1),n=((this.e||(this.e={}))[t]||[]).slice(),o=0,r=n.length;o<r;o++)n[o].fn.apply(n[o].ctx,e);return this},off:function(t,e){var n=this.e||(this.e={}),o=n[t],r=[];if(o&&e)for(var i=0,u=o.length;i<u;i++)o[i].fn!==e&&o[i].fn._!==e&&r.push(o[i]);return r.length?n[t]=r:delete n[t],this}},t.exports=e,t.exports.TinyEmitter=e}},r={},o.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return o.d(e,{a:e}),e},o.d=function(t,e){for(var n in e)o.o(e,n)&&!o.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:e[n]})},o.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},o(686).default;function o(t){if(r[t])return r[t].exports;var e=r[t]={exports:{}};return n[t](e,e.exports,o),e.exports}var n,r}); -------------------------------------------------------------------------------- /src/Whoops/Resources/js/prism.js: -------------------------------------------------------------------------------- 1 | /* PrismJS 1.30.0 2 | https://prismjs.com/download.html#themes=prism-tomorrow&languages=markup+markup-templating+php&plugins=line-highlight+line-numbers */ 3 | var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(e){var n=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,t=0,r={},a={manual:e.Prism&&e.Prism.manual,disableWorkerMessageHandler:e.Prism&&e.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof i?new i(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,"&").replace(/</g,"<").replace(/\u00a0/g," ")},type:function(e){return Object.prototype.toString.call(e).slice(8,-1)},objId:function(e){return e.__id||Object.defineProperty(e,"__id",{value:++t}),e.__id},clone:function e(n,t){var r,i;switch(t=t||{},a.util.type(n)){case"Object":if(i=a.util.objId(n),t[i])return t[i];for(var l in r={},t[i]=r,n)n.hasOwnProperty(l)&&(r[l]=e(n[l],t));return r;case"Array":return i=a.util.objId(n),t[i]?t[i]:(r=[],t[i]=r,n.forEach((function(n,a){r[a]=e(n,t)})),r);default:return n}},getLanguage:function(e){for(;e;){var t=n.exec(e.className);if(t)return t[1].toLowerCase();e=e.parentElement}return"none"},setLanguage:function(e,t){e.className=e.className.replace(RegExp(n,"gi"),""),e.classList.add("language-"+t)},currentScript:function(){if("undefined"==typeof document)return null;if(document.currentScript&&"SCRIPT"===document.currentScript.tagName)return document.currentScript;try{throw new Error}catch(r){var e=(/at [^(\r\n]*\((.*):[^:]+:[^:]+\)$/i.exec(r.stack)||[])[1];if(e){var n=document.getElementsByTagName("script");for(var t in n)if(n[t].src==e)return n[t]}return null}},isActive:function(e,n,t){for(var r="no-"+n;e;){var a=e.classList;if(a.contains(n))return!0;if(a.contains(r))return!1;e=e.parentElement}return!!t}},languages:{plain:r,plaintext:r,text:r,txt:r,extend:function(e,n){var t=a.util.clone(a.languages[e]);for(var r in n)t[r]=n[r];return t},insertBefore:function(e,n,t,r){var i=(r=r||a.languages)[e],l={};for(var o in i)if(i.hasOwnProperty(o)){if(o==n)for(var s in t)t.hasOwnProperty(s)&&(l[s]=t[s]);t.hasOwnProperty(o)||(l[o]=i[o])}var u=r[e];return r[e]=l,a.languages.DFS(a.languages,(function(n,t){t===u&&n!=e&&(this[n]=l)})),l},DFS:function e(n,t,r,i){i=i||{};var l=a.util.objId;for(var o in n)if(n.hasOwnProperty(o)){t.call(n,o,n[o],r||o);var s=n[o],u=a.util.type(s);"Object"!==u||i[l(s)]?"Array"!==u||i[l(s)]||(i[l(s)]=!0,e(s,t,o,i)):(i[l(s)]=!0,e(s,t,null,i))}}},plugins:{},highlightAll:function(e,n){a.highlightAllUnder(document,e,n)},highlightAllUnder:function(e,n,t){var r={callback:t,container:e,selector:'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'};a.hooks.run("before-highlightall",r),r.elements=Array.prototype.slice.apply(r.container.querySelectorAll(r.selector)),a.hooks.run("before-all-elements-highlight",r);for(var i,l=0;i=r.elements[l++];)a.highlightElement(i,!0===n,r.callback)},highlightElement:function(n,t,r){var i=a.util.getLanguage(n),l=a.languages[i];a.util.setLanguage(n,i);var o=n.parentElement;o&&"pre"===o.nodeName.toLowerCase()&&a.util.setLanguage(o,i);var s={element:n,language:i,grammar:l,code:n.textContent};function u(e){s.highlightedCode=e,a.hooks.run("before-insert",s),s.element.innerHTML=s.highlightedCode,a.hooks.run("after-highlight",s),a.hooks.run("complete",s),r&&r.call(s.element)}if(a.hooks.run("before-sanity-check",s),(o=s.element.parentElement)&&"pre"===o.nodeName.toLowerCase()&&!o.hasAttribute("tabindex")&&o.setAttribute("tabindex","0"),!s.code)return a.hooks.run("complete",s),void(r&&r.call(s.element));if(a.hooks.run("before-highlight",s),s.grammar)if(t&&e.Worker){var c=new Worker(a.filename);c.onmessage=function(e){u(e.data)},c.postMessage(JSON.stringify({language:s.language,code:s.code,immediateClose:!0}))}else u(a.highlight(s.code,s.grammar,s.language));else u(a.util.encode(s.code))},highlight:function(e,n,t){var r={code:e,grammar:n,language:t};if(a.hooks.run("before-tokenize",r),!r.grammar)throw new Error('The language "'+r.language+'" has no grammar.');return r.tokens=a.tokenize(r.code,r.grammar),a.hooks.run("after-tokenize",r),i.stringify(a.util.encode(r.tokens),r.language)},tokenize:function(e,n){var t=n.rest;if(t){for(var r in t)n[r]=t[r];delete n.rest}var a=new s;return u(a,a.head,e),o(e,a,n,a.head,0),function(e){for(var n=[],t=e.head.next;t!==e.tail;)n.push(t.value),t=t.next;return n}(a)},hooks:{all:{},add:function(e,n){var t=a.hooks.all;t[e]=t[e]||[],t[e].push(n)},run:function(e,n){var t=a.hooks.all[e];if(t&&t.length)for(var r,i=0;r=t[i++];)r(n)}},Token:i};function i(e,n,t,r){this.type=e,this.content=n,this.alias=t,this.length=0|(r||"").length}function l(e,n,t,r){e.lastIndex=n;var a=e.exec(t);if(a&&r&&a[1]){var i=a[1].length;a.index+=i,a[0]=a[0].slice(i)}return a}function o(e,n,t,r,s,g){for(var f in t)if(t.hasOwnProperty(f)&&t[f]){var h=t[f];h=Array.isArray(h)?h:[h];for(var d=0;d<h.length;++d){if(g&&g.cause==f+","+d)return;var v=h[d],p=v.inside,m=!!v.lookbehind,y=!!v.greedy,k=v.alias;if(y&&!v.pattern.global){var x=v.pattern.toString().match(/[imsuy]*$/)[0];v.pattern=RegExp(v.pattern.source,x+"g")}for(var b=v.pattern||v,w=r.next,A=s;w!==n.tail&&!(g&&A>=g.reach);A+=w.value.length,w=w.next){var P=w.value;if(n.length>e.length)return;if(!(P instanceof i)){var E,S=1;if(y){if(!(E=l(b,A,e,m))||E.index>=e.length)break;var L=E.index,O=E.index+E[0].length,C=A;for(C+=w.value.length;L>=C;)C+=(w=w.next).value.length;if(A=C-=w.value.length,w.value instanceof i)continue;for(var j=w;j!==n.tail&&(C<O||"string"==typeof j.value);j=j.next)S++,C+=j.value.length;S--,P=e.slice(A,C),E.index-=A}else if(!(E=l(b,0,P,m)))continue;L=E.index;var N=E[0],_=P.slice(0,L),M=P.slice(L+N.length),W=A+P.length;g&&W>g.reach&&(g.reach=W);var I=w.prev;if(_&&(I=u(n,I,_),A+=_.length),c(n,I,S),w=u(n,I,new i(f,p?a.tokenize(N,p):N,k,N)),M&&u(n,w,M),S>1){var T={cause:f+","+d,reach:W};o(e,n,t,w.prev,A,T),g&&T.reach>g.reach&&(g.reach=T.reach)}}}}}}function s(){var e={value:null,prev:null,next:null},n={value:null,prev:e,next:null};e.next=n,this.head=e,this.tail=n,this.length=0}function u(e,n,t){var r=n.next,a={value:t,prev:n,next:r};return n.next=a,r.prev=a,e.length++,a}function c(e,n,t){for(var r=n.next,a=0;a<t&&r!==e.tail;a++)r=r.next;n.next=r,r.prev=n,e.length-=a}if(e.Prism=a,i.stringify=function e(n,t){if("string"==typeof n)return n;if(Array.isArray(n)){var r="";return n.forEach((function(n){r+=e(n,t)})),r}var i={type:n.type,content:e(n.content,t),tag:"span",classes:["token",n.type],attributes:{},language:t},l=n.alias;l&&(Array.isArray(l)?Array.prototype.push.apply(i.classes,l):i.classes.push(l)),a.hooks.run("wrap",i);var o="";for(var s in i.attributes)o+=" "+s+'="'+(i.attributes[s]||"").replace(/"/g,""")+'"';return"<"+i.tag+' class="'+i.classes.join(" ")+'"'+o+">"+i.content+"</"+i.tag+">"},!e.document)return e.addEventListener?(a.disableWorkerMessageHandler||e.addEventListener("message",(function(n){var t=JSON.parse(n.data),r=t.language,i=t.code,l=t.immediateClose;e.postMessage(a.highlight(i,a.languages[r],r)),l&&e.close()}),!1),a):a;var g=a.util.currentScript();function f(){a.manual||a.highlightAll()}if(g&&(a.filename=g.src,g.hasAttribute("data-manual")&&(a.manual=!0)),!a.manual){var h=document.readyState;"loading"===h||"interactive"===h&&g&&g.defer?document.addEventListener("DOMContentLoaded",f):window.requestAnimationFrame?window.requestAnimationFrame(f):window.setTimeout(f,16)}return a}(_self);"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism); 4 | Prism.languages.markup={comment:{pattern:/<!--(?:(?!<!--)[\s\S])*?-->/,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/<!DOCTYPE(?:[^>"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|<!--(?:[^-]|-(?!->))*-->)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^<!|>$|[[\]]/,"doctype-tag":/^DOCTYPE/i,name:/[^\s<>'"]+/}},cdata:{pattern:/<!\[CDATA\[[\s\S]*?\]\]>/i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=lt;%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.languages.markup.doctype.inside["internal-subset"].inside=Prism.languages.markup,Prism.hooks.add("wrap",(function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))})),Object.defineProperty(Prism.languages.markup.tag,"addInlined",{value:function(a,e){var s={};s["language-"+e]={pattern:/(^<!\[CDATA\[)[\s\S]+?(?=\]\]>$)/i,lookbehind:!0,inside:Prism.languages[e]},s.cdata=/^<!\[CDATA\[|\]\]>$/i;var t={"included-cdata":{pattern:/<!\[CDATA\[[\s\S]*?\]\]>/i,inside:s}};t["language-"+e]={pattern:/[\s\S]+/,inside:Prism.languages[e]};var n={};n[a]={pattern:RegExp("(<__[^>]*>)(?:<!\\[CDATA\\[(?:[^\\]]|\\](?!\\]>))*\\]\\]>|(?!<!\\[CDATA\\[)[^])*?(?=</__>)".replace(/__/g,(function(){return a})),"i"),lookbehind:!0,greedy:!0,inside:t},Prism.languages.insertBefore("markup","cdata",n)}}),Object.defineProperty(Prism.languages.markup.tag,"addAttribute",{value:function(a,e){Prism.languages.markup.tag.inside["special-attr"].push({pattern:RegExp("(^|[\"'\\s])(?:"+a+")\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s'\">=]+(?=[\\s>]))","i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[e,"language-"+e],inside:Prism.languages[e]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup,Prism.languages.xml=Prism.languages.extend("markup",{}),Prism.languages.ssml=Prism.languages.xml,Prism.languages.atom=Prism.languages.xml,Prism.languages.rss=Prism.languages.xml; 5 | !function(e){function n(e,n){return"___"+e.toUpperCase()+n+"___"}Object.defineProperties(e.languages["markup-templating"]={},{buildPlaceholders:{value:function(t,a,r,o){if(t.language===a){var c=t.tokenStack=[];t.code=t.code.replace(r,(function(e){if("function"==typeof o&&!o(e))return e;for(var r,i=c.length;-1!==t.code.indexOf(r=n(a,i));)++i;return c[i]=e,r})),t.grammar=e.languages.markup}}},tokenizePlaceholders:{value:function(t,a){if(t.language===a&&t.tokenStack){t.grammar=e.languages[a];var r=0,o=Object.keys(t.tokenStack);!function c(i){for(var u=0;u<i.length&&!(r>=o.length);u++){var g=i[u];if("string"==typeof g||g.content&&"string"==typeof g.content){var l=o[r],s=t.tokenStack[l],f="string"==typeof g?g:g.content,p=n(a,l),k=f.indexOf(p);if(k>-1){++r;var m=f.substring(0,k),d=new e.Token(a,e.tokenize(s,t.grammar),"language-"+a,s),h=f.substring(k+p.length),v=[];m&&v.push.apply(v,c([m])),v.push(d),h&&v.push.apply(v,c([h])),"string"==typeof g?i.splice.apply(i,[u,1].concat(v)):g.content=v}}else g.content&&c(g.content)}return i}(t.tokens)}}}})}(Prism); 6 | !function(e){var a=/\/\*[\s\S]*?\*\/|\/\/.*|#(?!\[).*/,t=[{pattern:/\b(?:false|true)\b/i,alias:"boolean"},{pattern:/(::\s*)\b[a-z_]\w*\b(?!\s*\()/i,greedy:!0,lookbehind:!0},{pattern:/(\b(?:case|const)\s+)\b[a-z_]\w*(?=\s*[;=])/i,greedy:!0,lookbehind:!0},/\b(?:null)\b/i,/\b[A-Z_][A-Z0-9_]*\b(?!\s*\()/],i=/\b0b[01]+(?:_[01]+)*\b|\b0o[0-7]+(?:_[0-7]+)*\b|\b0x[\da-f]+(?:_[\da-f]+)*\b|(?:\b\d+(?:_\d+)*\.?(?:\d+(?:_\d+)*)?|\B\.\d+)(?:e[+-]?\d+)?/i,n=/<?=>|\?\?=?|\.{3}|\??->|[!=]=?=?|::|\*\*=?|--|\+\+|&&|\|\||<<|>>|[?~]|[/^|%*&<>.+-]=?/,s=/[{}\[\](),:;]/;e.languages.php={delimiter:{pattern:/\?>$|^<\?(?:php(?=\s)|=)?/i,alias:"important"},comment:a,variable:/\$+(?:\w+\b|(?=\{))/,package:{pattern:/(namespace\s+|use\s+(?:function\s+)?)(?:\\?\b[a-z_]\w*)+\b(?!\\)/i,lookbehind:!0,inside:{punctuation:/\\/}},"class-name-definition":{pattern:/(\b(?:class|enum|interface|trait)\s+)\b[a-z_]\w*(?!\\)\b/i,lookbehind:!0,alias:"class-name"},"function-definition":{pattern:/(\bfunction\s+)[a-z_]\w*(?=\s*\()/i,lookbehind:!0,alias:"function"},keyword:[{pattern:/(\(\s*)\b(?:array|bool|boolean|float|int|integer|object|string)\b(?=\s*\))/i,alias:"type-casting",greedy:!0,lookbehind:!0},{pattern:/([(,?]\s*)\b(?:array(?!\s*\()|bool|callable|(?:false|null)(?=\s*\|)|float|int|iterable|mixed|object|self|static|string)\b(?=\s*\$)/i,alias:"type-hint",greedy:!0,lookbehind:!0},{pattern:/(\)\s*:\s*(?:\?\s*)?)\b(?:array(?!\s*\()|bool|callable|(?:false|null)(?=\s*\|)|float|int|iterable|mixed|never|object|self|static|string|void)\b/i,alias:"return-type",greedy:!0,lookbehind:!0},{pattern:/\b(?:array(?!\s*\()|bool|float|int|iterable|mixed|object|string|void)\b/i,alias:"type-declaration",greedy:!0},{pattern:/(\|\s*)(?:false|null)\b|\b(?:false|null)(?=\s*\|)/i,alias:"type-declaration",greedy:!0,lookbehind:!0},{pattern:/\b(?:parent|self|static)(?=\s*::)/i,alias:"static-context",greedy:!0},{pattern:/(\byield\s+)from\b/i,lookbehind:!0},/\bclass\b/i,{pattern:/((?:^|[^\s>:]|(?:^|[^-])>|(?:^|[^:]):)\s*)\b(?:abstract|and|array|as|break|callable|case|catch|clone|const|continue|declare|default|die|do|echo|else|elseif|empty|enddeclare|endfor|endforeach|endif|endswitch|endwhile|enum|eval|exit|extends|final|finally|fn|for|foreach|function|global|goto|if|implements|include|include_once|instanceof|insteadof|interface|isset|list|match|namespace|never|new|or|parent|print|private|protected|public|readonly|require|require_once|return|self|static|switch|throw|trait|try|unset|use|var|while|xor|yield|__halt_compiler)\b/i,lookbehind:!0}],"argument-name":{pattern:/([(,]\s*)\b[a-z_]\w*(?=\s*:(?!:))/i,lookbehind:!0},"class-name":[{pattern:/(\b(?:extends|implements|instanceof|new(?!\s+self|\s+static))\s+|\bcatch\s*\()\b[a-z_]\w*(?!\\)\b/i,greedy:!0,lookbehind:!0},{pattern:/(\|\s*)\b[a-z_]\w*(?!\\)\b/i,greedy:!0,lookbehind:!0},{pattern:/\b[a-z_]\w*(?!\\)\b(?=\s*\|)/i,greedy:!0},{pattern:/(\|\s*)(?:\\?\b[a-z_]\w*)+\b/i,alias:"class-name-fully-qualified",greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}},{pattern:/(?:\\?\b[a-z_]\w*)+\b(?=\s*\|)/i,alias:"class-name-fully-qualified",greedy:!0,inside:{punctuation:/\\/}},{pattern:/(\b(?:extends|implements|instanceof|new(?!\s+self\b|\s+static\b))\s+|\bcatch\s*\()(?:\\?\b[a-z_]\w*)+\b(?!\\)/i,alias:"class-name-fully-qualified",greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}},{pattern:/\b[a-z_]\w*(?=\s*\$)/i,alias:"type-declaration",greedy:!0},{pattern:/(?:\\?\b[a-z_]\w*)+(?=\s*\$)/i,alias:["class-name-fully-qualified","type-declaration"],greedy:!0,inside:{punctuation:/\\/}},{pattern:/\b[a-z_]\w*(?=\s*::)/i,alias:"static-context",greedy:!0},{pattern:/(?:\\?\b[a-z_]\w*)+(?=\s*::)/i,alias:["class-name-fully-qualified","static-context"],greedy:!0,inside:{punctuation:/\\/}},{pattern:/([(,?]\s*)[a-z_]\w*(?=\s*\$)/i,alias:"type-hint",greedy:!0,lookbehind:!0},{pattern:/([(,?]\s*)(?:\\?\b[a-z_]\w*)+(?=\s*\$)/i,alias:["class-name-fully-qualified","type-hint"],greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}},{pattern:/(\)\s*:\s*(?:\?\s*)?)\b[a-z_]\w*(?!\\)\b/i,alias:"return-type",greedy:!0,lookbehind:!0},{pattern:/(\)\s*:\s*(?:\?\s*)?)(?:\\?\b[a-z_]\w*)+\b(?!\\)/i,alias:["class-name-fully-qualified","return-type"],greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}}],constant:t,function:{pattern:/(^|[^\\\w])\\?[a-z_](?:[\w\\]*\w)?(?=\s*\()/i,lookbehind:!0,inside:{punctuation:/\\/}},property:{pattern:/(->\s*)\w+/,lookbehind:!0},number:i,operator:n,punctuation:s};var l={pattern:/\{\$(?:\{(?:\{[^{}]+\}|[^{}]+)\}|[^{}])+\}|(^|[^\\{])\$+(?:\w+(?:\[[^\r\n\[\]]+\]|->\w+)?)/,lookbehind:!0,inside:e.languages.php},r=[{pattern:/<<<'([^']+)'[\r\n](?:.*[\r\n])*?\1;/,alias:"nowdoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<<'[^']+'|[a-z_]\w*;$/i,alias:"symbol",inside:{punctuation:/^<<<'?|[';]$/}}}},{pattern:/<<<(?:"([^"]+)"[\r\n](?:.*[\r\n])*?\1;|([a-z_]\w*)[\r\n](?:.*[\r\n])*?\2;)/i,alias:"heredoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<<(?:"[^"]+"|[a-z_]\w*)|[a-z_]\w*;$/i,alias:"symbol",inside:{punctuation:/^<<<"?|[";]$/}},interpolation:l}},{pattern:/`(?:\\[\s\S]|[^\\`])*`/,alias:"backtick-quoted-string",greedy:!0},{pattern:/'(?:\\[\s\S]|[^\\'])*'/,alias:"single-quoted-string",greedy:!0},{pattern:/"(?:\\[\s\S]|[^\\"])*"/,alias:"double-quoted-string",greedy:!0,inside:{interpolation:l}}];e.languages.insertBefore("php","variable",{string:r,attribute:{pattern:/#\[(?:[^"'\/#]|\/(?![*/])|\/\/.*$|#(?!\[).*$|\/\*(?:[^*]|\*(?!\/))*\*\/|"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*')+\](?=\s*[a-z$#])/im,greedy:!0,inside:{"attribute-content":{pattern:/^(#\[)[\s\S]+(?=\]$)/,lookbehind:!0,inside:{comment:a,string:r,"attribute-class-name":[{pattern:/([^:]|^)\b[a-z_]\w*(?!\\)\b/i,alias:"class-name",greedy:!0,lookbehind:!0},{pattern:/([^:]|^)(?:\\?\b[a-z_]\w*)+/i,alias:["class-name","class-name-fully-qualified"],greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}}],constant:t,number:i,operator:n,punctuation:s}},delimiter:{pattern:/^#\[|\]$/,alias:"punctuation"}}}}),e.hooks.add("before-tokenize",(function(a){/<\?/.test(a.code)&&e.languages["markup-templating"].buildPlaceholders(a,"php",/<\?(?:[^"'/#]|\/(?![*/])|("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|(?:\/\/|#(?!\[))(?:[^?\n\r]|\?(?!>))*(?=$|\?>|[\r\n])|#\[|\/\*(?:[^*]|\*(?!\/))*(?:\*\/|$))*?(?:\?>|$)/g)})),e.hooks.add("after-tokenize",(function(a){e.languages["markup-templating"].tokenizePlaceholders(a,"php")}))}(Prism); 7 | !function(){if("undefined"!=typeof Prism&&"undefined"!=typeof document&&document.querySelector){var e,t="line-numbers",i="linkable-line-numbers",n=/\n(?!$)/g,r=!0;Prism.plugins.lineHighlight={highlightLines:function(o,u,c){var h=(u="string"==typeof u?u:o.getAttribute("data-line")||"").replace(/\s+/g,"").split(",").filter(Boolean),d=+o.getAttribute("data-line-offset")||0,f=(function(){if(void 0===e){var t=document.createElement("div");t.style.fontSize="13px",t.style.lineHeight="1.5",t.style.padding="0",t.style.border="0",t.innerHTML=" <br /> ",document.body.appendChild(t),e=38===t.offsetHeight,document.body.removeChild(t)}return e}()?parseInt:parseFloat)(getComputedStyle(o).lineHeight),p=Prism.util.isActive(o,t),g=o.querySelector("code"),m=p?o:g||o,v=[],y=g.textContent.match(n),b=y?y.length+1:1,A=g&&m!=g?function(e,t){var i=getComputedStyle(e),n=getComputedStyle(t);function r(e){return+e.substr(0,e.length-2)}return t.offsetTop+r(n.borderTopWidth)+r(n.paddingTop)-r(i.paddingTop)}(o,g):0;h.forEach((function(e){var t=e.split("-"),i=+t[0],n=+t[1]||i;if(!((n=Math.min(b+d,n))<i)){var r=o.querySelector('.line-highlight[data-range="'+e+'"]')||document.createElement("div");if(v.push((function(){r.setAttribute("aria-hidden","true"),r.setAttribute("data-range",e),r.className=(c||"")+" line-highlight"})),p&&Prism.plugins.lineNumbers){var s=Prism.plugins.lineNumbers.getLine(o,i),l=Prism.plugins.lineNumbers.getLine(o,n);if(s){var a=s.offsetTop+A+"px";v.push((function(){r.style.top=a}))}if(l){var u=l.offsetTop-s.offsetTop+l.offsetHeight+"px";v.push((function(){r.style.height=u}))}}else v.push((function(){r.setAttribute("data-start",String(i)),n>i&&r.setAttribute("data-end",String(n)),r.style.top=(i-d-1)*f+A+"px",r.textContent=new Array(n-i+2).join(" \n")}));v.push((function(){r.style.width=o.scrollWidth+"px"})),v.push((function(){m.appendChild(r)}))}}));var P=o.id;if(p&&Prism.util.isActive(o,i)&&P){l(o,i)||v.push((function(){o.classList.add(i)}));var E=parseInt(o.getAttribute("data-start")||"1");s(".line-numbers-rows > span",o).forEach((function(e,t){var i=t+E;e.onclick=function(){var e=P+"."+i;r=!1,location.hash=e,setTimeout((function(){r=!0}),1)}}))}return function(){v.forEach(a)}}};var o=0;Prism.hooks.add("before-sanity-check",(function(e){var t=e.element.parentElement;if(u(t)){var i=0;s(".line-highlight",t).forEach((function(e){i+=e.textContent.length,e.parentNode.removeChild(e)})),i&&/^(?: \n)+$/.test(e.code.slice(-i))&&(e.code=e.code.slice(0,-i))}})),Prism.hooks.add("complete",(function e(i){var n=i.element.parentElement;if(u(n)){clearTimeout(o);var r=Prism.plugins.lineNumbers,s=i.plugins&&i.plugins.lineNumbers;l(n,t)&&r&&!s?Prism.hooks.add("line-numbers",e):(Prism.plugins.lineHighlight.highlightLines(n)(),o=setTimeout(c,1))}})),window.addEventListener("hashchange",c),window.addEventListener("resize",(function(){s("pre").filter(u).map((function(e){return Prism.plugins.lineHighlight.highlightLines(e)})).forEach(a)}))}function s(e,t){return Array.prototype.slice.call((t||document).querySelectorAll(e))}function l(e,t){return e.classList.contains(t)}function a(e){e()}function u(e){return!!(e&&/pre/i.test(e.nodeName)&&(e.hasAttribute("data-line")||e.id&&Prism.util.isActive(e,i)))}function c(){var e=location.hash.slice(1);s(".temporary.line-highlight").forEach((function(e){e.parentNode.removeChild(e)}));var t=(e.match(/\.([\d,-]+)$/)||[,""])[1];if(t&&!document.getElementById(e)){var i=e.slice(0,e.lastIndexOf(".")),n=document.getElementById(i);n&&(n.hasAttribute("data-line")||n.setAttribute("data-line",""),Prism.plugins.lineHighlight.highlightLines(n,t,"temporary ")(),r&&document.querySelector(".temporary.line-highlight").scrollIntoView())}}}(); 8 | !function(){if("undefined"!=typeof Prism&&"undefined"!=typeof document){var e="line-numbers",n=/\n(?!$)/g,t=Prism.plugins.lineNumbers={getLine:function(n,t){if("PRE"===n.tagName&&n.classList.contains(e)){var i=n.querySelector(".line-numbers-rows");if(i){var r=parseInt(n.getAttribute("data-start"),10)||1,s=r+(i.children.length-1);t<r&&(t=r),t>s&&(t=s);var l=t-r;return i.children[l]}}},resize:function(e){r([e])},assumeViewportIndependence:!0},i=void 0;window.addEventListener("resize",(function(){t.assumeViewportIndependence&&i===window.innerWidth||(i=window.innerWidth,r(Array.prototype.slice.call(document.querySelectorAll("pre.line-numbers"))))})),Prism.hooks.add("complete",(function(t){if(t.code){var i=t.element,s=i.parentNode;if(s&&/pre/i.test(s.nodeName)&&!i.querySelector(".line-numbers-rows")&&Prism.util.isActive(i,e)){i.classList.remove(e),s.classList.add(e);var l,o=t.code.match(n),a=o?o.length+1:1,u=new Array(a+1).join("<span></span>");(l=document.createElement("span")).setAttribute("aria-hidden","true"),l.className="line-numbers-rows",l.innerHTML=u,s.hasAttribute("data-start")&&(s.style.counterReset="linenumber "+(parseInt(s.getAttribute("data-start"),10)-1)),t.element.appendChild(l),r([s]),Prism.hooks.run("line-numbers",t)}}})),Prism.hooks.add("line-numbers",(function(e){e.plugins=e.plugins||{},e.plugins.lineNumbers=!0}))}function r(e){if(0!=(e=e.filter((function(e){var n,t=(n=e,n?window.getComputedStyle?getComputedStyle(n):n.currentStyle||null:null)["white-space"];return"pre-wrap"===t||"pre-line"===t}))).length){var t=e.map((function(e){var t=e.querySelector("code"),i=e.querySelector(".line-numbers-rows");if(t&&i){var r=e.querySelector(".line-numbers-sizer"),s=t.textContent.split(n);r||((r=document.createElement("span")).className="line-numbers-sizer",t.appendChild(r)),r.innerHTML="0",r.style.display="block";var l=r.getBoundingClientRect().height;return r.innerHTML="",{element:e,lines:s,lineHeights:[],oneLinerHeight:l,sizer:r}}})).filter(Boolean);t.forEach((function(e){var n=e.sizer,t=e.lines,i=e.lineHeights,r=e.oneLinerHeight;i[t.length-1]=void 0,t.forEach((function(e,t){if(e&&e.length>1){var s=n.appendChild(document.createElement("span"));s.style.display="block",s.textContent=e}else i[t]=r}))})),t.forEach((function(e){for(var n=e.sizer,t=e.lineHeights,i=0,r=0;r<t.length;r++)void 0===t[r]&&(t[r]=n.children[i++].getBoundingClientRect().height)})),t.forEach((function(e){var n=e.sizer,t=e.element.querySelector(".line-numbers-rows");n.style.display="none",n.innerHTML="",e.lineHeights.forEach((function(e,n){t.children[n].style.height=e+"px"}))}))}}}(); 9 | -------------------------------------------------------------------------------- /src/Whoops/Resources/js/whoops.base.js: -------------------------------------------------------------------------------- 1 | Zepto(function($) { 2 | var $leftPanel = $('.left-panel'); 3 | var $frameContainer = $('.frames-container'); 4 | var $appFramesTab = $('#application-frames-tab'); 5 | var $allFramesTab = $('#all-frames-tab'); 6 | var $container = $('.details-container'); 7 | var $activeLine = $frameContainer.find('.frame.active'); 8 | var $activeFrame = $container.find('.frame-code.active'); 9 | var $ajaxEditors = $('.editor-link[data-ajax]'); 10 | var $header = $('header'); 11 | 12 | $header.on('mouseenter', function () { 13 | if ($header.find('.exception').height() >= 145) { 14 | $header.addClass('header-expand'); 15 | } 16 | }); 17 | $header.on('mouseleave', function () { 18 | $header.removeClass('header-expand'); 19 | }); 20 | 21 | /* 22 | * add prettyprint classes to our current active codeblock 23 | * run prettyPrint() to highlight the active code 24 | * scroll to the line when prettyprint is done 25 | * highlight the current line 26 | */ 27 | var renderCurrentCodeblock = function(id) { 28 | Prism.highlightAllUnder(document.querySelector('.frame-code-container .frame-code.active')); 29 | highlightCurrentLine(); 30 | } 31 | 32 | /* 33 | * Highlight the active and neighboring lines for the current frame 34 | * Adjust the offset to make sure that line is veritcally centered 35 | */ 36 | 37 | var highlightCurrentLine = function() { 38 | // We show more code than needed, purely for proper syntax highlighting 39 | // Let’s hide a big chunk of that code and then scroll the remaining block 40 | $activeFrame.find('.code-block').first().css({ 41 | maxHeight: 345, 42 | overflow: 'hidden', 43 | }); 44 | 45 | var line = $activeFrame.find('.code-block .line-highlight').first()[0]; 46 | // [internal] frames might not contain a code-block 47 | if (line) { 48 | line.scrollIntoView(); 49 | line.parentElement.scrollTop -= 180; 50 | } 51 | 52 | $container.scrollTop(0); 53 | } 54 | 55 | /* 56 | * click handler for loading codeblocks 57 | */ 58 | 59 | $frameContainer.on('click', '.frame', function() { 60 | 61 | var $this = $(this); 62 | var id = /frame\-line\-([\d]*)/.exec($this.attr('id'))[1]; 63 | var $codeFrame = $('#frame-code-' + id); 64 | 65 | if ($codeFrame) { 66 | 67 | $activeLine.removeClass('active'); 68 | $activeFrame.removeClass('active'); 69 | 70 | $this.addClass('active'); 71 | $codeFrame.addClass('active'); 72 | 73 | $activeLine = $this; 74 | $activeFrame = $codeFrame; 75 | 76 | renderCurrentCodeblock(id); 77 | 78 | } 79 | 80 | }); 81 | 82 | var clipboard = new ClipboardJS('.clipboard'); 83 | var showTooltip = function(elem, msg) { 84 | elem.classList.add('tooltipped', 'tooltipped-s'); 85 | elem.setAttribute('aria-label', msg); 86 | }; 87 | 88 | clipboard.on('success', function(e) { 89 | e.clearSelection(); 90 | 91 | showTooltip(e.trigger, 'Copied!'); 92 | }); 93 | 94 | clipboard.on('error', function(e) { 95 | showTooltip(e.trigger, fallbackMessage(e.action)); 96 | }); 97 | 98 | var btn = document.querySelector('.clipboard'); 99 | 100 | btn.addEventListener('mouseleave', function(e) { 101 | e.currentTarget.classList.remove('tooltipped', 'tooltipped-s'); 102 | e.currentTarget.removeAttribute('aria-label'); 103 | }); 104 | 105 | function fallbackMessage(action) { 106 | var actionMsg = ''; 107 | var actionKey = (action === 'cut' ? 'X' : 'C'); 108 | 109 | if (/Mac/i.test(navigator.userAgent)) { 110 | actionMsg = 'Press ⌘-' + actionKey + ' to ' + action; 111 | } else { 112 | actionMsg = 'Press Ctrl-' + actionKey + ' to ' + action; 113 | } 114 | 115 | return actionMsg; 116 | } 117 | 118 | function scrollIntoView($node, $parent) { 119 | var nodeOffset = $node.offset(); 120 | var nodeTop = nodeOffset.top; 121 | var nodeBottom = nodeTop + nodeOffset.height; 122 | var parentScrollTop = $parent.scrollTop(); 123 | var parentHeight = $parent.height(); 124 | 125 | if (nodeTop < 0) { 126 | $parent.scrollTop(parentScrollTop + nodeTop); 127 | } else if (nodeBottom > parentHeight) { 128 | $parent.scrollTop(parentScrollTop + nodeBottom - parentHeight); 129 | } 130 | } 131 | 132 | $(document).on('keydown', function(e) { 133 | var applicationFrames = $frameContainer.hasClass('frames-container-application'), 134 | frameClass = applicationFrames ? '.frame.frame-application' : '.frame'; 135 | 136 | if(e.ctrlKey || e.which === 74 || e.which === 75) { 137 | // CTRL+Arrow-UP/k and Arrow-Down/j support: 138 | // 1) select the next/prev element 139 | // 2) make sure the newly selected element is within the view-scope 140 | // 3) focus the (right) container, so arrow-up/down (without ctrl) scroll the details 141 | if (e.which === 38 /* arrow up */ || e.which === 75 /* k */) { 142 | $activeLine.prev(frameClass).click(); 143 | scrollIntoView($activeLine, $leftPanel); 144 | $container.focus(); 145 | e.preventDefault(); 146 | } else if (e.which === 40 /* arrow down */ || e.which === 74 /* j */) { 147 | $activeLine.next(frameClass).click(); 148 | scrollIntoView($activeLine, $leftPanel); 149 | $container.focus(); 150 | e.preventDefault(); 151 | } 152 | } else if (e.which == 78 /* n */) { 153 | if ($appFramesTab.length) { 154 | setActiveFramesTab($('.frames-tab:not(.frames-tab-active)')); 155 | } 156 | } 157 | }); 158 | 159 | // Avoid to quit the page with some protocol (e.g. IntelliJ Platform REST API) 160 | $ajaxEditors.on('click', function(e){ 161 | e.preventDefault(); 162 | $.get(this.href); 163 | }); 164 | 165 | // Symfony VarDumper: Close the by default expanded objects 166 | $('.sf-dump-expanded') 167 | .removeClass('sf-dump-expanded') 168 | .addClass('sf-dump-compact'); 169 | $('.sf-dump-toggle span').html('▶'); 170 | 171 | // Make the given frames-tab active 172 | function setActiveFramesTab($tab) { 173 | $tab.addClass('frames-tab-active'); 174 | 175 | if ($tab.attr('id') == 'application-frames-tab') { 176 | $frameContainer.addClass('frames-container-application'); 177 | $allFramesTab.removeClass('frames-tab-active'); 178 | } else { 179 | $frameContainer.removeClass('frames-container-application'); 180 | $appFramesTab.removeClass('frames-tab-active'); 181 | } 182 | } 183 | 184 | $('a.frames-tab').on('click', function(e) { 185 | e.preventDefault(); 186 | setActiveFramesTab($(this)); 187 | }); 188 | 189 | // Open editor from code block rows number 190 | $(document).delegate('.line-numbers-rows > span', 'click', function(e) { 191 | var linkTag = $(this).closest('.frame-code').find('.editor-link'); 192 | if (!linkTag) return; 193 | var editorUrl = linkTag.attr('href'); 194 | var requiresAjax = linkTag.data('ajax'); 195 | 196 | var lineOffset = $(this).closest('[data-line-offset]').data('line-offset'); 197 | var lineNumber = lineOffset + $(this).index(); 198 | 199 | var realLine = $(this).closest('[data-line]').data('line'); 200 | if (!realLine) return; 201 | var fileUrl = editorUrl.replace( 202 | new RegExp('([:=])' + realLine), 203 | '$1' + lineNumber 204 | ); 205 | 206 | if (requiresAjax) { 207 | $.get(fileUrl); 208 | } else { 209 | $('<a>').attr('href', fileUrl).trigger('click'); 210 | } 211 | }); 212 | 213 | // Render late enough for highlightCurrentLine to be ready 214 | renderCurrentCodeblock(); 215 | }); 216 | -------------------------------------------------------------------------------- /src/Whoops/Resources/js/zepto.min.js: -------------------------------------------------------------------------------- 1 | /* Zepto v1.2.0 - zepto event ajax form ie - zeptojs.com/license */ 2 | !function(t,e){"function"==typeof define&&define.amd?define(function(){return e(t)}):e(t)}(this,function(t){var e=function(){function $(t){return null==t?String(t):S[C.call(t)]||"object"}function F(t){return"function"==$(t)}function k(t){return null!=t&&t==t.window}function M(t){return null!=t&&t.nodeType==t.DOCUMENT_NODE}function R(t){return"object"==$(t)}function Z(t){return R(t)&&!k(t)&&Object.getPrototypeOf(t)==Object.prototype}function z(t){var e=!!t&&"length"in t&&t.length,n=r.type(t);return"function"!=n&&!k(t)&&("array"==n||0===e||"number"==typeof e&&e>0&&e-1 in t)}function q(t){return a.call(t,function(t){return null!=t})}function H(t){return t.length>0?r.fn.concat.apply([],t):t}function I(t){return t.replace(/::/g,"/").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/([a-z\d])([A-Z])/g,"$1_$2").replace(/_/g,"-").toLowerCase()}function V(t){return t in l?l[t]:l[t]=new RegExp("(^|\\s)"+t+"(\\s|$)")}function _(t,e){return"number"!=typeof e||h[I(t)]?e:e+"px"}function B(t){var e,n;return c[t]||(e=f.createElement(t),f.body.appendChild(e),n=getComputedStyle(e,"").getPropertyValue("display"),e.parentNode.removeChild(e),"none"==n&&(n="block"),c[t]=n),c[t]}function U(t){return"children"in t?u.call(t.children):r.map(t.childNodes,function(t){return 1==t.nodeType?t:void 0})}function X(t,e){var n,r=t?t.length:0;for(n=0;r>n;n++)this[n]=t[n];this.length=r,this.selector=e||""}function J(t,r,i){for(n in r)i&&(Z(r[n])||L(r[n]))?(Z(r[n])&&!Z(t[n])&&(t[n]={}),L(r[n])&&!L(t[n])&&(t[n]=[]),J(t[n],r[n],i)):r[n]!==e&&(t[n]=r[n])}function W(t,e){return null==e?r(t):r(t).filter(e)}function Y(t,e,n,r){return F(e)?e.call(t,n,r):e}function G(t,e,n){null==n?t.removeAttribute(e):t.setAttribute(e,n)}function K(t,n){var r=t.className||"",i=r&&r.baseVal!==e;return n===e?i?r.baseVal:r:void(i?r.baseVal=n:t.className=n)}function Q(t){try{return t?"true"==t||("false"==t?!1:"null"==t?null:+t+""==t?+t:/^[\[\{]/.test(t)?r.parseJSON(t):t):t}catch(e){return t}}function tt(t,e){e(t);for(var n=0,r=t.childNodes.length;r>n;n++)tt(t.childNodes[n],e)}var e,n,r,i,O,P,o=[],s=o.concat,a=o.filter,u=o.slice,f=t.document,c={},l={},h={"column-count":1,columns:1,"font-weight":1,"line-height":1,opacity:1,"z-index":1,zoom:1},p=/^\s*<(\w+|!)[^>]*>/,d=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,m=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,g=/^(?:body|html)$/i,v=/([A-Z])/g,y=["val","css","html","text","data","width","height","offset"],x=["after","prepend","before","append"],b=f.createElement("table"),E=f.createElement("tr"),j={tr:f.createElement("tbody"),tbody:b,thead:b,tfoot:b,td:E,th:E,"*":f.createElement("div")},w=/complete|loaded|interactive/,T=/^[\w-]*$/,S={},C=S.toString,N={},A=f.createElement("div"),D={tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},L=Array.isArray||function(t){return t instanceof Array};return N.matches=function(t,e){if(!e||!t||1!==t.nodeType)return!1;var n=t.matches||t.webkitMatchesSelector||t.mozMatchesSelector||t.oMatchesSelector||t.matchesSelector;if(n)return n.call(t,e);var r,i=t.parentNode,o=!i;return o&&(i=A).appendChild(t),r=~N.qsa(i,e).indexOf(t),o&&A.removeChild(t),r},O=function(t){return t.replace(/-+(.)?/g,function(t,e){return e?e.toUpperCase():""})},P=function(t){return a.call(t,function(e,n){return t.indexOf(e)==n})},N.fragment=function(t,n,i){var o,s,a;return d.test(t)&&(o=r(f.createElement(RegExp.$1))),o||(t.replace&&(t=t.replace(m,"<$1></$2>")),n===e&&(n=p.test(t)&&RegExp.$1),n in j||(n="*"),a=j[n],a.innerHTML=""+t,o=r.each(u.call(a.childNodes),function(){a.removeChild(this)})),Z(i)&&(s=r(o),r.each(i,function(t,e){y.indexOf(t)>-1?s[t](e):s.attr(t,e)})),o},N.Z=function(t,e){return new X(t,e)},N.isZ=function(t){return t instanceof N.Z},N.init=function(t,n){var i;if(!t)return N.Z();if("string"==typeof t)if(t=t.trim(),"<"==t[0]&&p.test(t))i=N.fragment(t,RegExp.$1,n),t=null;else{if(n!==e)return r(n).find(t);i=N.qsa(f,t)}else{if(F(t))return r(f).ready(t);if(N.isZ(t))return t;if(L(t))i=q(t);else if(R(t))i=[t],t=null;else if(p.test(t))i=N.fragment(t.trim(),RegExp.$1,n),t=null;else{if(n!==e)return r(n).find(t);i=N.qsa(f,t)}}return N.Z(i,t)},r=function(t,e){return N.init(t,e)},r.extend=function(t){var e,n=u.call(arguments,1);return"boolean"==typeof t&&(e=t,t=n.shift()),n.forEach(function(n){J(t,n,e)}),t},N.qsa=function(t,e){var n,r="#"==e[0],i=!r&&"."==e[0],o=r||i?e.slice(1):e,s=T.test(o);return t.getElementById&&s&&r?(n=t.getElementById(o))?[n]:[]:1!==t.nodeType&&9!==t.nodeType&&11!==t.nodeType?[]:u.call(s&&!r&&t.getElementsByClassName?i?t.getElementsByClassName(o):t.getElementsByTagName(e):t.querySelectorAll(e))},r.contains=f.documentElement.contains?function(t,e){return t!==e&&t.contains(e)}:function(t,e){for(;e&&(e=e.parentNode);)if(e===t)return!0;return!1},r.type=$,r.isFunction=F,r.isWindow=k,r.isArray=L,r.isPlainObject=Z,r.isEmptyObject=function(t){var e;for(e in t)return!1;return!0},r.isNumeric=function(t){var e=Number(t),n=typeof t;return null!=t&&"boolean"!=n&&("string"!=n||t.length)&&!isNaN(e)&&isFinite(e)||!1},r.inArray=function(t,e,n){return o.indexOf.call(e,t,n)},r.camelCase=O,r.trim=function(t){return null==t?"":String.prototype.trim.call(t)},r.uuid=0,r.support={},r.expr={},r.noop=function(){},r.map=function(t,e){var n,i,o,r=[];if(z(t))for(i=0;i<t.length;i++)n=e(t[i],i),null!=n&&r.push(n);else for(o in t)n=e(t[o],o),null!=n&&r.push(n);return H(r)},r.each=function(t,e){var n,r;if(z(t)){for(n=0;n<t.length;n++)if(e.call(t[n],n,t[n])===!1)return t}else for(r in t)if(e.call(t[r],r,t[r])===!1)return t;return t},r.grep=function(t,e){return a.call(t,e)},t.JSON&&(r.parseJSON=JSON.parse),r.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(t,e){S["[object "+e+"]"]=e.toLowerCase()}),r.fn={constructor:N.Z,length:0,forEach:o.forEach,reduce:o.reduce,push:o.push,sort:o.sort,splice:o.splice,indexOf:o.indexOf,concat:function(){var t,e,n=[];for(t=0;t<arguments.length;t++)e=arguments[t],n[t]=N.isZ(e)?e.toArray():e;return s.apply(N.isZ(this)?this.toArray():this,n)},map:function(t){return r(r.map(this,function(e,n){return t.call(e,n,e)}))},slice:function(){return r(u.apply(this,arguments))},ready:function(t){return w.test(f.readyState)&&f.body?t(r):f.addEventListener("DOMContentLoaded",function(){t(r)},!1),this},get:function(t){return t===e?u.call(this):this[t>=0?t:t+this.length]},toArray:function(){return this.get()},size:function(){return this.length},remove:function(){return this.each(function(){null!=this.parentNode&&this.parentNode.removeChild(this)})},each:function(t){return o.every.call(this,function(e,n){return t.call(e,n,e)!==!1}),this},filter:function(t){return F(t)?this.not(this.not(t)):r(a.call(this,function(e){return N.matches(e,t)}))},add:function(t,e){return r(P(this.concat(r(t,e))))},is:function(t){return this.length>0&&N.matches(this[0],t)},not:function(t){var n=[];if(F(t)&&t.call!==e)this.each(function(e){t.call(this,e)||n.push(this)});else{var i="string"==typeof t?this.filter(t):z(t)&&F(t.item)?u.call(t):r(t);this.forEach(function(t){i.indexOf(t)<0&&n.push(t)})}return r(n)},has:function(t){return this.filter(function(){return R(t)?r.contains(this,t):r(this).find(t).size()})},eq:function(t){return-1===t?this.slice(t):this.slice(t,+t+1)},first:function(){var t=this[0];return t&&!R(t)?t:r(t)},last:function(){var t=this[this.length-1];return t&&!R(t)?t:r(t)},find:function(t){var e,n=this;return e=t?"object"==typeof t?r(t).filter(function(){var t=this;return o.some.call(n,function(e){return r.contains(e,t)})}):1==this.length?r(N.qsa(this[0],t)):this.map(function(){return N.qsa(this,t)}):r()},closest:function(t,e){var n=[],i="object"==typeof t&&r(t);return this.each(function(r,o){for(;o&&!(i?i.indexOf(o)>=0:N.matches(o,t));)o=o!==e&&!M(o)&&o.parentNode;o&&n.indexOf(o)<0&&n.push(o)}),r(n)},parents:function(t){for(var e=[],n=this;n.length>0;)n=r.map(n,function(t){return(t=t.parentNode)&&!M(t)&&e.indexOf(t)<0?(e.push(t),t):void 0});return W(e,t)},parent:function(t){return W(P(this.pluck("parentNode")),t)},children:function(t){return W(this.map(function(){return U(this)}),t)},contents:function(){return this.map(function(){return this.contentDocument||u.call(this.childNodes)})},siblings:function(t){return W(this.map(function(t,e){return a.call(U(e.parentNode),function(t){return t!==e})}),t)},empty:function(){return this.each(function(){this.innerHTML=""})},pluck:function(t){return r.map(this,function(e){return e[t]})},show:function(){return this.each(function(){"none"==this.style.display&&(this.style.display=""),"none"==getComputedStyle(this,"").getPropertyValue("display")&&(this.style.display=B(this.nodeName))})},replaceWith:function(t){return this.before(t).remove()},wrap:function(t){var e=F(t);if(this[0]&&!e)var n=r(t).get(0),i=n.parentNode||this.length>1;return this.each(function(o){r(this).wrapAll(e?t.call(this,o):i?n.cloneNode(!0):n)})},wrapAll:function(t){if(this[0]){r(this[0]).before(t=r(t));for(var e;(e=t.children()).length;)t=e.first();r(t).append(this)}return this},wrapInner:function(t){var e=F(t);return this.each(function(n){var i=r(this),o=i.contents(),s=e?t.call(this,n):t;o.length?o.wrapAll(s):i.append(s)})},unwrap:function(){return this.parent().each(function(){r(this).replaceWith(r(this).children())}),this},clone:function(){return this.map(function(){return this.cloneNode(!0)})},hide:function(){return this.css("display","none")},toggle:function(t){return this.each(function(){var n=r(this);(t===e?"none"==n.css("display"):t)?n.show():n.hide()})},prev:function(t){return r(this.pluck("previousElementSibling")).filter(t||"*")},next:function(t){return r(this.pluck("nextElementSibling")).filter(t||"*")},html:function(t){return 0 in arguments?this.each(function(e){var n=this.innerHTML;r(this).empty().append(Y(this,t,e,n))}):0 in this?this[0].innerHTML:null},text:function(t){return 0 in arguments?this.each(function(e){var n=Y(this,t,e,this.textContent);this.textContent=null==n?"":""+n}):0 in this?this.pluck("textContent").join(""):null},attr:function(t,r){var i;return"string"!=typeof t||1 in arguments?this.each(function(e){if(1===this.nodeType)if(R(t))for(n in t)G(this,n,t[n]);else G(this,t,Y(this,r,e,this.getAttribute(t)))}):0 in this&&1==this[0].nodeType&&null!=(i=this[0].getAttribute(t))?i:e},removeAttr:function(t){return this.each(function(){1===this.nodeType&&t.split(" ").forEach(function(t){G(this,t)},this)})},prop:function(t,e){return t=D[t]||t,1 in arguments?this.each(function(n){this[t]=Y(this,e,n,this[t])}):this[0]&&this[0][t]},removeProp:function(t){return t=D[t]||t,this.each(function(){delete this[t]})},data:function(t,n){var r="data-"+t.replace(v,"-$1").toLowerCase(),i=1 in arguments?this.attr(r,n):this.attr(r);return null!==i?Q(i):e},val:function(t){return 0 in arguments?(null==t&&(t=""),this.each(function(e){this.value=Y(this,t,e,this.value)})):this[0]&&(this[0].multiple?r(this[0]).find("option").filter(function(){return this.selected}).pluck("value"):this[0].value)},offset:function(e){if(e)return this.each(function(t){var n=r(this),i=Y(this,e,t,n.offset()),o=n.offsetParent().offset(),s={top:i.top-o.top,left:i.left-o.left};"static"==n.css("position")&&(s.position="relative"),n.css(s)});if(!this.length)return null;if(f.documentElement!==this[0]&&!r.contains(f.documentElement,this[0]))return{top:0,left:0};var n=this[0].getBoundingClientRect();return{left:n.left+t.pageXOffset,top:n.top+t.pageYOffset,width:Math.round(n.width),height:Math.round(n.height)}},css:function(t,e){if(arguments.length<2){var i=this[0];if("string"==typeof t){if(!i)return;return i.style[O(t)]||getComputedStyle(i,"").getPropertyValue(t)}if(L(t)){if(!i)return;var o={},s=getComputedStyle(i,"");return r.each(t,function(t,e){o[e]=i.style[O(e)]||s.getPropertyValue(e)}),o}}var a="";if("string"==$(t))e||0===e?a=I(t)+":"+_(t,e):this.each(function(){this.style.removeProperty(I(t))});else for(n in t)t[n]||0===t[n]?a+=I(n)+":"+_(n,t[n])+";":this.each(function(){this.style.removeProperty(I(n))});return this.each(function(){this.style.cssText+=";"+a})},index:function(t){return t?this.indexOf(r(t)[0]):this.parent().children().indexOf(this[0])},hasClass:function(t){return t?o.some.call(this,function(t){return this.test(K(t))},V(t)):!1},addClass:function(t){return t?this.each(function(e){if("className"in this){i=[];var n=K(this),o=Y(this,t,e,n);o.split(/\s+/g).forEach(function(t){r(this).hasClass(t)||i.push(t)},this),i.length&&K(this,n+(n?" ":"")+i.join(" "))}}):this},removeClass:function(t){return this.each(function(n){if("className"in this){if(t===e)return K(this,"");i=K(this),Y(this,t,n,i).split(/\s+/g).forEach(function(t){i=i.replace(V(t)," ")}),K(this,i.trim())}})},toggleClass:function(t,n){return t?this.each(function(i){var o=r(this),s=Y(this,t,i,K(this));s.split(/\s+/g).forEach(function(t){(n===e?!o.hasClass(t):n)?o.addClass(t):o.removeClass(t)})}):this},scrollTop:function(t){if(this.length){var n="scrollTop"in this[0];return t===e?n?this[0].scrollTop:this[0].pageYOffset:this.each(n?function(){this.scrollTop=t}:function(){this.scrollTo(this.scrollX,t)})}},scrollLeft:function(t){if(this.length){var n="scrollLeft"in this[0];return t===e?n?this[0].scrollLeft:this[0].pageXOffset:this.each(n?function(){this.scrollLeft=t}:function(){this.scrollTo(t,this.scrollY)})}},position:function(){if(this.length){var t=this[0],e=this.offsetParent(),n=this.offset(),i=g.test(e[0].nodeName)?{top:0,left:0}:e.offset();return n.top-=parseFloat(r(t).css("margin-top"))||0,n.left-=parseFloat(r(t).css("margin-left"))||0,i.top+=parseFloat(r(e[0]).css("border-top-width"))||0,i.left+=parseFloat(r(e[0]).css("border-left-width"))||0,{top:n.top-i.top,left:n.left-i.left}}},offsetParent:function(){return this.map(function(){for(var t=this.offsetParent||f.body;t&&!g.test(t.nodeName)&&"static"==r(t).css("position");)t=t.offsetParent;return t})}},r.fn.detach=r.fn.remove,["width","height"].forEach(function(t){var n=t.replace(/./,function(t){return t[0].toUpperCase()});r.fn[t]=function(i){var o,s=this[0];return i===e?k(s)?s["inner"+n]:M(s)?s.documentElement["scroll"+n]:(o=this.offset())&&o[t]:this.each(function(e){s=r(this),s.css(t,Y(this,i,e,s[t]()))})}}),x.forEach(function(n,i){var o=i%2;r.fn[n]=function(){var n,a,s=r.map(arguments,function(t){var i=[];return n=$(t),"array"==n?(t.forEach(function(t){return t.nodeType!==e?i.push(t):r.zepto.isZ(t)?i=i.concat(t.get()):void(i=i.concat(N.fragment(t)))}),i):"object"==n||null==t?t:N.fragment(t)}),u=this.length>1;return s.length<1?this:this.each(function(e,n){a=o?n:n.parentNode,n=0==i?n.nextSibling:1==i?n.firstChild:2==i?n:null;var c=r.contains(f.documentElement,a);s.forEach(function(e){if(u)e=e.cloneNode(!0);else if(!a)return r(e).remove();a.insertBefore(e,n),c&&tt(e,function(e){if(!(null==e.nodeName||"SCRIPT"!==e.nodeName.toUpperCase()||e.type&&"text/javascript"!==e.type||e.src)){var n=e.ownerDocument?e.ownerDocument.defaultView:t;n.eval.call(n,e.innerHTML)}})})})},r.fn[o?n+"To":"insert"+(i?"Before":"After")]=function(t){return r(t)[n](this),this}}),N.Z.prototype=X.prototype=r.fn,N.uniq=P,N.deserializeValue=Q,r.zepto=N,r}();return t.Zepto=e,void 0===t.amp;&(t.$=e),function(e){function h(t){return t._zid||(t._zid=n++)}function p(t,e,n,r){if(e=d(e),e.ns)var i=m(e.ns);return(a[h(t)]||[]).filter(function(t){return t&&(!e.e||t.e==e.e)&&(!e.ns||i.test(t.ns))&&(!n||h(t.fn)===h(n))&&(!r||t.sel==r)})}function d(t){var e=(""+t).split(".");return{e:e[0],ns:e.slice(1).sort().join(" ")}}function m(t){return new RegExp("(?:^| )"+t.replace(" "," .* ?")+"(?: |$)")}function g(t,e){return t.del&&!f&&t.e in c||!!e}function v(t){return l[t]||f&&c[t]||t}function y(t,n,i,o,s,u,f){var c=h(t),p=a[c]||(a[c]=[]);n.split(/\s/).forEach(function(n){if("ready"==n)return e(document).ready(i);var a=d(n);a.fn=i,a.sel=s,a.e in l&&(i=function(t){var n=t.relatedTarget;return!n||n!==this&&!e.contains(this,n)?a.fn.apply(this,arguments):void 0}),a.del=u;var c=u||i;a.proxy=function(e){if(e=T(e),!e.isImmediatePropagationStopped()){e.data=o;var n=c.apply(t,e._args==r?[e]:[e].concat(e._args));return n===!1&&(e.preventDefault(),e.stopPropagation()),n}},a.i=p.length,p.push(a),"addEventListener"in t&&t.addEventListener(v(a.e),a.proxy,g(a,f))})}function x(t,e,n,r,i){var o=h(t);(e||"").split(/\s/).forEach(function(e){p(t,e,n,r).forEach(function(e){delete a[o][e.i],"removeEventListener"in t&&t.removeEventListener(v(e.e),e.proxy,g(e,i))})})}function T(t,n){return(n||!t.isDefaultPrevented)&&(n||(n=t),e.each(w,function(e,r){var i=n[e];t[e]=function(){return this[r]=b,i&&i.apply(n,arguments)},t[r]=E}),t.timeStamp||(t.timeStamp=Date.now()),(n.defaultPrevented!==r?n.defaultPrevented:"returnValue"in n?n.returnValue===!1:n.getPreventDefault&&n.getPreventDefault())&&(t.isDefaultPrevented=b)),t}function S(t){var e,n={originalEvent:t};for(e in t)j.test(e)||t[e]===r||(n[e]=t[e]);return T(n,t)}var r,n=1,i=Array.prototype.slice,o=e.isFunction,s=function(t){return"string"==typeof t},a={},u={},f="onfocusin"in t,c={focus:"focusin",blur:"focusout"},l={mouseenter:"mouseover",mouseleave:"mouseout"};u.click=u.mousedown=u.mouseup=u.mousemove="MouseEvents",e.event={add:y,remove:x},e.proxy=function(t,n){var r=2 in arguments&&i.call(arguments,2);if(o(t)){var a=function(){return t.apply(n,r?r.concat(i.call(arguments)):arguments)};return a._zid=h(t),a}if(s(n))return r?(r.unshift(t[n],t),e.proxy.apply(null,r)):e.proxy(t[n],t);throw new TypeError("expected function")},e.fn.bind=function(t,e,n){return this.on(t,e,n)},e.fn.unbind=function(t,e){return this.off(t,e)},e.fn.one=function(t,e,n,r){return this.on(t,e,n,r,1)};var b=function(){return!0},E=function(){return!1},j=/^([A-Z]|returnValue$|layer[XY]$|webkitMovement[XY]$)/,w={preventDefault:"isDefaultPrevented",stopImmediatePropagation:"isImmediatePropagationStopped",stopPropagation:"isPropagationStopped"};e.fn.delegate=function(t,e,n){return this.on(e,t,n)},e.fn.undelegate=function(t,e,n){return this.off(e,t,n)},e.fn.live=function(t,n){return e(document.body).delegate(this.selector,t,n),this},e.fn.die=function(t,n){return e(document.body).undelegate(this.selector,t,n),this},e.fn.on=function(t,n,a,u,f){var c,l,h=this;return t&&!s(t)?(e.each(t,function(t,e){h.on(t,n,a,e,f)}),h):(s(n)||o(u)||u===!1||(u=a,a=n,n=r),(u===r||a===!1)&&(u=a,a=r),u===!1&&(u=E),h.each(function(r,o){f&&(c=function(t){return x(o,t.type,u),u.apply(this,arguments)}),n&&(l=function(t){var r,s=e(t.target).closest(n,o).get(0);return s&&s!==o?(r=e.extend(S(t),{currentTarget:s,liveFired:o}),(c||u).apply(s,[r].concat(i.call(arguments,1)))):void 0}),y(o,t,u,a,n,l||c)}))},e.fn.off=function(t,n,i){var a=this;return t&&!s(t)?(e.each(t,function(t,e){a.off(t,n,e)}),a):(s(n)||o(i)||i===!1||(i=n,n=r),i===!1&&(i=E),a.each(function(){x(this,t,i,n)}))},e.fn.trigger=function(t,n){return t=s(t)||e.isPlainObject(t)?e.Event(t):T(t),t._args=n,this.each(function(){t.type in c&&"function"==typeof this[t.type]?this[t.type]():"dispatchEvent"in this?this.dispatchEvent(t):e(this).triggerHandler(t,n)})},e.fn.triggerHandler=function(t,n){var r,i;return this.each(function(o,a){r=S(s(t)?e.Event(t):t),r._args=n,r.target=a,e.each(p(a,t.type||t),function(t,e){return i=e.proxy(r),r.isImmediatePropagationStopped()?!1:void 0})}),i},"focusin focusout focus blur load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select keydown keypress keyup error".split(" ").forEach(function(t){e.fn[t]=function(e){return 0 in arguments?this.bind(t,e):this.trigger(t)}}),e.Event=function(t,e){s(t)||(e=t,t=e.type);var n=document.createEvent(u[t]||"Events"),r=!0;if(e)for(var i in e)"bubbles"==i?r=!!e[i]:n[i]=e[i];return n.initEvent(t,r,!0),T(n)}}(e),function(e){function p(t,n,r){var i=e.Event(n);return e(t).trigger(i,r),!i.isDefaultPrevented()}function d(t,e,n,i){return t.global?p(e||r,n,i):void 0}function m(t){t.global&&0===e.active++&&d(t,null,"ajaxStart")}function g(t){t.global&&!--e.active&&d(t,null,"ajaxStop")}function v(t,e){var n=e.context;return e.beforeSend.call(n,t,e)===!1||d(e,n,"ajaxBeforeSend",[t,e])===!1?!1:void d(e,n,"ajaxSend",[t,e])}function y(t,e,n,r){var i=n.context,o="success";n.success.call(i,t,o,e),r&&r.resolveWith(i,[t,o,e]),d(n,i,"ajaxSuccess",[e,n,t]),b(o,e,n)}function x(t,e,n,r,i){var o=r.context;r.error.call(o,n,e,t),i&&i.rejectWith(o,[n,e,t]),d(r,o,"ajaxError",[n,r,t||e]),b(e,n,r)}function b(t,e,n){var r=n.context;n.complete.call(r,e,t),d(n,r,"ajaxComplete",[e,n]),g(n)}function E(t,e,n){if(n.dataFilter==j)return t;var r=n.context;return n.dataFilter.call(r,t,e)}function j(){}function w(t){return t&&(t=t.split(";",2)[0]),t&&(t==c?"html":t==f?"json":a.test(t)?"script":u.test(t)&&"xml")||"text"}function T(t,e){return""==e?t:(t+"&"+e).replace(/[&?]{1,2}/,"?")}function S(t){t.processData&&t.data&&"string"!=e.type(t.data)&&(t.data=e.param(t.data,t.traditional)),!t.data||t.type&&"GET"!=t.type.toUpperCase()&&"jsonp"!=t.dataType||(t.url=T(t.url,t.data),t.data=void 0)}function C(t,n,r,i){return e.isFunction(n)&&(i=r,r=n,n=void 0),e.isFunction(r)||(i=r,r=void 0),{url:t,data:n,success:r,dataType:i}}function O(t,n,r,i){var o,s=e.isArray(n),a=e.isPlainObject(n);e.each(n,function(n,u){o=e.type(u),i&&(n=r?i:i+"["+(a||"object"==o||"array"==o?n:"")+"]"),!i&&s?t.add(u.name,u.value):"array"==o||!r&&"object"==o?O(t,u,r,n):t.add(n,u)})}var i,o,n=+new Date,r=t.document,s=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,a=/^(?:text|application)\/javascript/i,u=/^(?:text|application)\/xml/i,f="application/json",c="text/html",l=/^\s*$/,h=r.createElement("a");h.href=t.location.href,e.active=0,e.ajaxJSONP=function(i,o){if(!("type"in i))return e.ajax(i);var c,p,s=i.jsonpCallback,a=(e.isFunction(s)?s():s)||"Zepto"+n++,u=r.createElement("script"),f=t[a],l=function(t){e(u).triggerHandler("error",t||"abort")},h={abort:l};return o&&o.promise(h),e(u).on("load error",function(n,r){clearTimeout(p),e(u).off().remove(),"error"!=n.type&&c?y(c[0],h,i,o):x(null,r||"error",h,i,o),t[a]=f,c&&e.isFunction(f)&&f(c[0]),f=c=void 0}),v(h,i)===!1?(l("abort"),h):(t[a]=function(){c=arguments},u.src=i.url.replace(/\?(.+)=\?/,"?$1="+a),r.head.appendChild(u),i.timeout>0&&(p=setTimeout(function(){l("timeout")},i.timeout)),h)},e.ajaxSettings={type:"GET",beforeSend:j,success:j,error:j,complete:j,context:null,global:!0,xhr:function(){return new t.XMLHttpRequest},accepts:{script:"text/javascript, application/javascript, application/x-javascript",json:f,xml:"application/xml, text/xml",html:c,text:"text/plain"},crossDomain:!1,timeout:0,processData:!0,cache:!0,dataFilter:j},e.ajax=function(n){var u,f,s=e.extend({},n||{}),a=e.Deferred&&e.Deferred();for(i in e.ajaxSettings)void 0===s[i]&&(s[i]=e.ajaxSettings[i]);m(s),s.crossDomain||(u=r.createElement("a"),u.href=s.url,u.href=u.href,s.crossDomain=h.protocol+"//"+h.host!=u.protocol+"//"+u.host),s.url||(s.url=t.location.toString()),(f=s.url.indexOf("#"))>-1&&(s.url=s.url.slice(0,f)),S(s);var c=s.dataType,p=/\?.+=\?/.test(s.url);if(p&&(c="jsonp"),s.cache!==!1&&(n&&n.cache===!0||"script"!=c&&"jsonp"!=c)||(s.url=T(s.url,"_="+Date.now())),"jsonp"==c)return p||(s.url=T(s.url,s.jsonp?s.jsonp+"=?":s.jsonp===!1?"":"callback=?")),e.ajaxJSONP(s,a);var P,d=s.accepts[c],g={},b=function(t,e){g[t.toLowerCase()]=[t,e]},C=/^([\w-]+:)\/\//.test(s.url)?RegExp.$1:t.location.protocol,N=s.xhr(),O=N.setRequestHeader;if(a&&a.promise(N),s.crossDomain||b("X-Requested-With","XMLHttpRequest"),b("Accept",d||"*/*"),(d=s.mimeType||d)&&(d.indexOf(",")>-1&&(d=d.split(",",2)[0]),N.overrideMimeType&&N.overrideMimeType(d)),(s.contentType||s.contentType!==!1&&s.data&&"GET"!=s.type.toUpperCase())&&b("Content-Type",s.contentType||"application/x-www-form-urlencoded"),s.headers)for(o in s.headers)b(o,s.headers[o]);if(N.setRequestHeader=b,N.onreadystatechange=function(){if(4==N.readyState){N.onreadystatechange=j,clearTimeout(P);var t,n=!1;if(N.status>=200&&N.status<300||304==N.status||0==N.status&&"file:"==C){if(c=c||w(s.mimeType||N.getResponseHeader("content-type")),"arraybuffer"==N.responseType||"blob"==N.responseType)t=N.response;else{t=N.responseText;try{t=E(t,c,s),"script"==c?(1,eval)(t):"xml"==c?t=N.responseXML:"json"==c&&(t=l.test(t)?null:e.parseJSON(t))}catch(r){n=r}if(n)return x(n,"parsererror",N,s,a)}y(t,N,s,a)}else x(N.statusText||null,N.status?"error":"abort",N,s,a)}},v(N,s)===!1)return N.abort(),x(null,"abort",N,s,a),N;var A="async"in s?s.async:!0;if(N.open(s.type,s.url,A,s.username,s.password),s.xhrFields)for(o in s.xhrFields)N[o]=s.xhrFields[o];for(o in g)O.apply(N,g[o]);return s.timeout>0&&(P=setTimeout(function(){N.onreadystatechange=j,N.abort(),x(null,"timeout",N,s,a)},s.timeout)),N.send(s.data?s.data:null),N},e.get=function(){return e.ajax(C.apply(null,arguments))},e.post=function(){var t=C.apply(null,arguments);return t.type="POST",e.ajax(t)},e.getJSON=function(){var t=C.apply(null,arguments);return t.dataType="json",e.ajax(t)},e.fn.load=function(t,n,r){if(!this.length)return this;var a,i=this,o=t.split(/\s/),u=C(t,n,r),f=u.success;return o.length>1&&(u.url=o[0],a=o[1]),u.success=function(t){i.html(a?e("<div>").html(t.replace(s,"")).find(a):t),f&&f.apply(i,arguments)},e.ajax(u),this};var N=encodeURIComponent;e.param=function(t,n){var r=[];return r.add=function(t,n){e.isFunction(n)&&(n=n()),null==n&&(n=""),this.push(N(t)+"="+N(n))},O(r,t,n),r.join("&").replace(/%20/g,"+")}}(e),function(t){t.fn.serializeArray=function(){var e,n,r=[],i=function(t){return t.forEach?t.forEach(i):void r.push({name:e,value:t})};return this[0]&&t.each(this[0].elements,function(r,o){n=o.type,e=o.name,e&&"fieldset"!=o.nodeName.toLowerCase()&&!o.disabled&&"submit"!=n&&"reset"!=n&&"button"!=n&&"file"!=n&&("radio"!=n&&"checkbox"!=n||o.checked)&&i(t(o).val())}),r},t.fn.serialize=function(){var t=[];return this.serializeArray().forEach(function(e){t.push(encodeURIComponent(e.name)+"="+encodeURIComponent(e.value))}),t.join("&")},t.fn.submit=function(e){if(0 in arguments)this.bind("submit",e);else if(this.length){var n=t.Event("submit");this.eq(0).trigger(n),n.isDefaultPrevented()||this.get(0).submit()}return this}}(e),function(){try{getComputedStyle(void 0)}catch(e){var n=getComputedStyle;t.getComputedStyle=function(t,e){try{return n(t,e)}catch(r){return null}}}}(),e}); -------------------------------------------------------------------------------- /src/Whoops/Resources/views/env_details.html.php: -------------------------------------------------------------------------------- 1 | <?php /* List data-table values, i.e: $_SERVER, $_GET, .... */ ?> 2 | <div class="details"> 3 | <h2 class="details-heading">Environment & details:</h2> 4 | 5 | <div class="data-table-container" id="data-tables"> 6 | <?php foreach ($tables as $label => $data): ?> 7 | <div class="data-table" id="sg-<?php echo $tpl->escape($tpl->slug($label)) ?>"> 8 | <?php if (!empty($data)): ?> 9 | <label><?php echo $tpl->escape($label) ?></label> 10 | <table class="data-table"> 11 | <thead> 12 | <tr> 13 | <td class="data-table-k">Key</td> 14 | <td class="data-table-v">Value</td> 15 | </tr> 16 | </thead> 17 | <?php foreach ($data as $k => $value): ?> 18 | <tr> 19 | <td><?php echo $tpl->escape($k) ?></td> 20 | <td><?php echo $tpl->dump($value) ?></td> 21 | </tr> 22 | <?php endforeach ?> 23 | </table> 24 | <?php else: ?> 25 | <label class="empty"><?php echo $tpl->escape($label) ?></label> 26 | <span class="empty">empty</span> 27 | <?php endif ?> 28 | </div> 29 | <?php endforeach ?> 30 | </div> 31 | 32 | <?php /* List registered handlers, in order of first to last registered */ ?> 33 | <div class="data-table-container" id="handlers"> 34 | <label>Registered Handlers</label> 35 | <?php foreach ($handlers as $i => $h): ?> 36 | <div class="handler <?php echo ($h === $handler) ? 'active' : ''?>"> 37 | <?php echo $i ?>. <?php echo $tpl->escape(get_class($h)) ?> 38 | </div> 39 | <?php endforeach ?> 40 | </div> 41 | 42 | </div> 43 | -------------------------------------------------------------------------------- /src/Whoops/Resources/views/frame_code.html.php: -------------------------------------------------------------------------------- 1 | <?php /* Display a code block for all frames in the stack. 2 | * @todo: This should PROBABLY be done on-demand, lest 3 | * we get 200 frames to process. */ ?> 4 | <div class="frame-code-container <?php echo (!$has_frames ? 'empty' : '') ?>"> 5 | <?php foreach ($frames as $i => $frame): ?> 6 | <?php $line = $frame->getLine(); ?> 7 | <div class="frame-code <?php echo ($i == 0 ) ? 'active' : '' ?>" id="frame-code-<?php echo $i ?>"> 8 | <div class="frame-file"> 9 | <?php $filePath = $frame->getFile(); ?> 10 | <?php if ($filePath && $editorHref = $handler->getEditorHref($filePath, (int) $line)): ?> 11 | <a href="<?php echo $editorHref ?>" class="editor-link"<?php echo ($handler->getEditorAjax($filePath, (int) $line) ? ' data-ajax' : '') ?>> 12 | Open: 13 | <strong><?php echo $tpl->breakOnDelimiter('/', $tpl->escape($filePath ?: '<#unknown>')) ?></strong> 14 | </a> 15 | <?php else: ?> 16 | <strong><?php echo $tpl->breakOnDelimiter('/', $tpl->escape($filePath ?: '<#unknown>')) ?></strong> 17 | <?php endif ?> 18 | </div> 19 | <?php 20 | // Do nothing if there's no line to work off 21 | if ($line !== null): 22 | 23 | // the $line is 1-indexed, we nab -1 where needed to account for this 24 | $range = $frame->getFileLines($line - 20, 40); 25 | 26 | // getFileLines can return null if there is no source code 27 | if ($range): 28 | $range = array_map(function ($line) { return empty($line) ? ' ' : $line;}, $range); 29 | $start = key($range) + 1; 30 | $code = join("\n", $range); 31 | ?> 32 | <pre class="code-block line-numbers" 33 | data-line="<?php echo $line ?>" 34 | data-line-offset="<?php echo $start ?>" 35 | data-start="<?php echo $start ?>" 36 | ><code class="language-php"><?php echo $tpl->escape($code) ?></code></pre> 37 | 38 | <?php endif ?> 39 | <?php endif ?> 40 | 41 | <?php $frameArgs = $tpl->dumpArgs($frame); ?> 42 | <?php if ($frameArgs): ?> 43 | <div class="frame-file"> 44 | Arguments 45 | </div> 46 | <div id="frame-code-args-<?=$i?>" class="code-block frame-args"> 47 | <?php echo $frameArgs; ?> 48 | </div> 49 | <?php endif ?> 50 | 51 | <?php 52 | // Append comments for this frame 53 | $comments = $frame->getComments(); 54 | ?> 55 | <div class="frame-comments <?php echo empty($comments) ? 'empty' : '' ?>"> 56 | <?php foreach ($comments as $commentNo => $comment): ?> 57 | <?php extract($comment) ?> 58 | <div class="frame-comment" id="comment-<?php echo $i . '-' . $commentNo ?>"> 59 | <span class="frame-comment-context"><?php echo $tpl->escape($context) ?></span> 60 | <?php echo $tpl->escapeButPreserveUris($comment) ?> 61 | </div> 62 | <?php endforeach ?> 63 | </div> 64 | 65 | </div> 66 | <?php endforeach ?> 67 | </div> 68 | -------------------------------------------------------------------------------- /src/Whoops/Resources/views/frame_list.html.php: -------------------------------------------------------------------------------- 1 | <?php /* List file names & line numbers for all stack frames; 2 | clicking these links/buttons will display the code view 3 | for that particular frame */ ?> 4 | <?php foreach ($frames as $i => $frame): ?> 5 | <div class="frame <?php echo ($i == 0 ? 'active' : '') ?> <?php echo ($frame->isApplication() ? 'frame-application' : '') ?>" id="frame-line-<?php echo $i ?>"> 6 | <span class="frame-index"><?php echo (count($frames) - $i - 1) ?></span> 7 | <div class="frame-method-info"> 8 | <span class="frame-class"><?php echo $tpl->breakOnDelimiter('\\', $tpl->escape($frame->getClass() ?: '')) ?></span> 9 | <span class="frame-function"><?php echo $tpl->breakOnDelimiter('\\', $tpl->escape($frame->getFunction() ?: '')) ?></span> 10 | </div> 11 | 12 | <div class="frame-file"> 13 | <?php echo $frame->getFile() ? $tpl->breakOnDelimiter('/', $tpl->shorten($tpl->escape($frame->getFile()))) : '<#unknown>' ?><!-- 14 | --><span class="frame-line"><?php echo (int) $frame->getLine() ?></span> 15 | </div> 16 | </div> 17 | <?php endforeach; 18 | -------------------------------------------------------------------------------- /src/Whoops/Resources/views/frames_container.html.php: -------------------------------------------------------------------------------- 1 | <div class="frames-container <?php echo $active_frames_tab == 'application' ? 'frames-container-application' : '' ?>"> 2 | <?php $tpl->render($frame_list) ?> 3 | </div> -------------------------------------------------------------------------------- /src/Whoops/Resources/views/frames_description.html.php: -------------------------------------------------------------------------------- 1 | <div class="frames-description <?php echo $has_frames_tabs ? 'frames-description-application' : '' ?>"> 2 | <?php if ($has_frames_tabs): ?> 3 | <a href="#" id="application-frames-tab" class="frames-tab <?php echo $active_frames_tab == 'application' ? 'frames-tab-active' : '' ?>"> 4 | Application frames (<?php echo $frames->countIsApplication() ?>) 5 | </a> 6 | <a href="#" id="all-frames-tab" class="frames-tab <?php echo $active_frames_tab == 'all' ? 'frames-tab-active' : '' ?>"> 7 | All frames (<?php echo count($frames) ?>) 8 | </a> 9 | <?php else: ?> 10 | <span> 11 | Stack frames (<?php echo count($frames) ?>) 12 | </span> 13 | <?php endif; ?> 14 | </div> 15 | -------------------------------------------------------------------------------- /src/Whoops/Resources/views/header.html.php: -------------------------------------------------------------------------------- 1 | <div class="exception"> 2 | <div class="exc-title"> 3 | <?php foreach ($name as $i => $nameSection): ?> 4 | <?php if ($i == count($name) - 1): ?> 5 | <span class="exc-title-primary"><?php echo $tpl->escape($nameSection) ?></span> 6 | <?php else: ?> 7 | <?php echo $tpl->escape($nameSection) . ' \\' ?> 8 | <?php endif ?> 9 | <?php endforeach ?> 10 | <?php if ($code): ?> 11 | <span title="Exception Code">(<?php echo $tpl->escape($code) ?>)</span> 12 | <?php endif ?> 13 | </div> 14 | 15 | <div class="exc-message"> 16 | <?php if (!empty($message)): ?> 17 | <span><?php echo $tpl->escape($message) ?></span> 18 | 19 | 20 | <?php if (count($previousMessages)): ?> 21 | <div class="exc-title prev-exc-title"> 22 | <span class="exc-title-secondary">Previous exceptions</span> 23 | </div> 24 | 25 | <ul> 26 | <?php foreach ($previousMessages as $i => $previousMessage): ?> 27 | <li> 28 | <?php echo $tpl->escape($previousMessage) ?> 29 | <span class="prev-exc-code">(<?php echo $previousCodes[$i] ?>)</span> 30 | </li> 31 | <?php endforeach; ?> 32 | </ul> 33 | <?php endif ?> 34 | 35 | 36 | 37 | <?php else: ?> 38 | <span class="exc-message-empty-notice">No message</span> 39 | <?php endif ?> 40 | 41 | <ul class="search-for-help"> 42 | <?php if (!empty($docref_url)): ?> 43 | <li> 44 | <a rel="noopener noreferrer" target="_blank" href="<?php echo $docref_url; ?>" title="Search for help in the PHP manual."> 45 | <!-- PHP icon by Icons Solid --> 46 | <!-- https://www.iconfinder.com/icons/322421/book_icon --> 47 | <!-- Free for commercial use --> 48 | <svg height="16px" id="Layer_1" style="enable-background:new 0 0 32 32;" version="1.1" viewBox="0 0 32 32" width="16px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g transform="translate(240 0)"><path d="M-211,4v26h-24c-1.104,0-2-0.895-2-2s0.896-2,2-2h22V0h-22c-2.209,0-4,1.791-4,4v24c0,2.209,1.791,4,4,4h26V4H-211z M-235,8V2h20v22h-20V8z M-219,6h-12V4h12V6z M-223,10h-8V8h8V10z M-227,14h-4v-2h4V14z"/></g></svg> 49 | </a> 50 | </li> 51 | <?php endif ?> 52 | <li> 53 | <a rel="noopener noreferrer" target="_blank" href="https://google.com/search?q=<?php echo urlencode(implode('\\', $name).' '.$message) ?>" title="Search for help on Google."> 54 | <!-- Google icon by Alfredo H, from https://www.iconfinder.com/alfredoh --> 55 | <!-- Creative Commons (Attribution 3.0 Unported) --> 56 | <!-- http://creativecommons.org/licenses/by/3.0/ --> 57 | <svg class="google" height="16" viewBox="0 0 512 512" width="16" xmlns="http://www.w3.org/2000/svg"> 58 | <path d="M457.732 216.625c2.628 14.04 4.063 28.743 4.063 44.098C461.795 380.688 381.48 466 260.205 466c-116.024 0-210-93.977-210-210s93.976-210 210-210c56.703 0 104.076 20.867 140.44 54.73l-59.205 59.197v-.135c-22.046-21.002-50-31.762-81.236-31.762-69.297 0-125.604 58.537-125.604 127.84 0 69.29 56.306 127.97 125.604 127.97 62.87 0 105.653-35.966 114.46-85.313h-114.46v-81.902h197.528z"/> 59 | </svg> 60 | </a> 61 | </li> 62 | <li> 63 | <a rel="noopener noreferrer" target="_blank" href="https://duckduckgo.com/?q=<?php echo urlencode(implode('\\', $name).' '.$message) ?>" title="Search for help on DuckDuckGo."> 64 | <!-- DuckDuckGo icon by IconBaandar Team, from https://www.iconfinder.com/iconbaandar --> 65 | <!-- Creative Commons (Attribution 3.0 Unported) --> 66 | <!-- http://creativecommons.org/licenses/by/3.0/ --> 67 | <svg class="duckduckgo" height="16" viewBox="150 150 1675 1675" width="16" xmlns="http://www.w3.org/2000/svg"> 68 | <path d="M1792 1024c0 204.364-80.472 398.56-224.955 543.04-144.483 144.48-338.68 224.95-543.044 224.95-204.36 0-398.56-80.47-543.04-224.95-144.48-144.482-224.95-338.676-224.95-543.04 0-204.365 80.47-398.562 224.96-543.045C625.44 336.47 819.64 256 1024 256c204.367 0 398.565 80.47 543.05 224.954C1711.532 625.437 1792 819.634 1792 1024zm-270.206 497.787C1654.256 1389.327 1728 1211.36 1728 1024c0-187.363-73.74-365.332-206.203-497.796C1389.332 393.74 1211.363 320 1024 320s-365.33 73.742-497.795 206.205C393.742 658.67 320 836.637 320 1024c0 187.36 73.744 365.326 206.206 497.787C658.67 1654.25 836.638 1727.99 1024 1727.99c187.362 0 365.33-73.74 497.794-206.203z"/> 69 | <path d="M1438.64 1177.41c0-.03-.005-.017-.01.004l.01-.004z"/> 70 | <path d="M1499.8 976.878c.03-.156-.024-.048-.11.107l.11-.107z"/> 71 | <path d="M1105.19 991.642zm-68.013-376.128c-8.087-10.14-18.028-19.965-29.89-29.408-13.29-10.582-29-20.76-47.223-30.443-35.07-18.624-74.482-31.61-115.265-38.046-39.78-6.28-80.84-6.256-120.39.917l1.37 31.562c1.8.164 7.7 3.9 14.36 8.32-20.68 5.94-39.77 14.447-39.48 39.683l.2 17.48 17.3-1.73c29.38-2.95 60.17-2.06 90.32 2.61 9.21 1.42 18.36 3.2 27.38 5.32l-4.33 1.15c-20.45 5.58-38.93 12.52-54.25 20.61-46.28 24.32-75.51 60.85-90.14 108.37-14.14 45.95-14.27 101.81-2.72 166.51l.06.06c15.14 84.57 64.16 316.39 104.11 505.39 19.78 93.59 37.38 176.83 47.14 224.4 3.26 15.84 5.03 31.02 5.52 45.52.3 9.08.09 17.96-.58 26.62-.45 5.8-1.11 11.51-1.96 17.112l31.62 4.75c.71-4.705 1.3-9.494 1.76-14.373 48.964 10.517 99.78 16.05 151.88 16.05 60.68 0 119.61-7.505 175.91-21.64 3.04 6.08 6.08 12.19 9.11 18.32l28.62-14.128c-2.11-4.27-4.235-8.55-6.37-12.84-23.005-46.124-47.498-93.01-68.67-133.534-15.39-29.466-29.01-55.53-39.046-75.58-26.826-53.618-53.637-119.47-68.28-182.368-8.78-37.705-13.128-74.098-10.308-105.627-15.31-6.28-26.69-11.8-31.968-15.59l-.01.015c-14.22-10.2-31.11-28.12-41.82-49.717-8.618-17.376-13.4-37.246-10.147-57.84 3.17-19.84 27.334-46.714 57.843-67.46v-.063c26.554-18.05 58.75-32.506 86.32-34.31 7.835-.51 16.31-1.008 23.99-1.45 33.45-1.95 50.243-2.93 84.475-11.42 10.88-2.697 26.19-6.56 43.53-11.09 2.364-40.7-5.947-87.596-21.04-133.234-22.004-66.53-58.68-131.25-97.627-170.21-12.543-12.55-28.17-22.79-45.9-30.933-16.88-7.753-35.64-13.615-55.436-17.782zm-10.658 178.553s6.77-42.485 58.39-33.977c27.96 4.654 37.89 29.833 37.89 29.833s-25.31-14.46-44.95-14.198c-40.33.53-51.35 18.342-51.35 18.342zm-240.45-18.802c48.49-19.853 72.11 11.298 72.11 11.298s-35.21-15.928-69.46 5.59c-34.19 21.477-32.92 43.452-32.92 43.452s-18.17-40.5 30.26-60.34zm296.5 95.4c0-6.677 2.68-12.694 7.01-17.02 4.37-4.37 10.42-7.074 17.1-7.074 6.73 0 12.79 2.7 17.15 7.05 4.33 4.33 7.01 10.36 7.01 17.05 0 6.74-2.7 12.81-7.07 17.18-4.33 4.33-10.37 7.01-17.1 7.01-6.68 0-12.72-2.69-17.05-7.03-4.36-4.37-7.07-10.43-7.07-17.16zm-268.42 51.27c0-8.535 3.41-16.22 8.93-21.738 5.55-5.55 13.25-8.982 21.81-8.982 8.51 0 16.18 3.415 21.7 8.934 5.55 5.55 8.98 13.25 8.98 21.78 0 8.53-3.44 16.23-8.98 21.79-5.52 5.52-13.19 8.93-21.71 8.93-8.55 0-16.26-3.43-21.82-8.99-5.52-5.52-8.93-13.2-8.93-21.74z"/> 72 | <path d="M1102.48 986.34zm390.074-64.347c-28.917-11.34-74.89-12.68-93.32-3.778-11.5 5.567-35.743 13.483-63.565 21.707-25.75 7.606-53.9 15.296-78.15 21.702-17.69 4.67-33.3 8.66-44.4 11.435-34.92 8.76-52.05 9.77-86.17 11.78-7.84.46-16.48.97-24.48 1.5-28.12 1.86-60.97 16.77-88.05 35.4v.06c-31.12 21.4-55.77 49.12-59.01 69.59-3.32 21.24 1.56 41.74 10.35 59.67 10.92 22.28 28.15 40.77 42.66 51.29l.01-.02c5.38 3.9 16.98 9.6 32.6 16.08 26.03 10.79 63.2 23.76 101.25 34.23 43.6 11.99 89.11 21.05 121.69 20.41 34.26-.69 77.73-10.52 114.54-24.67 22.15-8.52 42.21-18.71 56.88-29.58 17.85-13.22 28.7-28.42 28.4-44.74-.07-3.89-.72-7.63-1.97-11.21l-.02.01c-11.6-33.06-50.37-23.59-105.53-10.12-46.86 11.445-107.94 26.365-169.01 20.434-32.56-3.167-54.45-10.61-67.88-20.133-5.96-4.224-9.93-8.67-12.18-13.11-1.96-3.865-2.68-7.84-2.33-11.714.39-4.42 2.17-9.048 5.1-13.57l-.05-.03c7.86-12.118 23.082-9.72 43.93-6.43 25.91 4.08 58.2 9.172 99.013-3.61 39.63-12.378 87.76-29.9 131.184-47.39 42.405-17.08 80.08-34.078 100.74-46.18 25.46-14.87 37.57-29.428 40.59-42.866 2.725-12.152-.89-22.48-8.903-31.07-5.87-6.29-14.254-11.31-23.956-15.115z"/> 73 | </svg> 74 | </a> 75 | </li> 76 | <li> 77 | <a rel="noopener noreferrer" target="_blank" href="https://stackoverflow.com/search?q=<?php echo urlencode(implode('\\', $name).' '.$message) ?>" title="Search for help on Stack Overflow."> 78 | <!-- Stack Overflow icon by Picons.me, from https://www.iconfinder.com/Picons --> 79 | <!-- Free for commercial use --> 80 | <svg class="stackoverflow" height="16" viewBox="-1163 1657.697 56.693 56.693" width="16" xmlns="http://www.w3.org/2000/svg"> 81 | <path d="M-1126.04 1689.533l-16.577-9.778 2.088-3.54 16.578 9.778zM-1127.386 1694.635l-18.586-4.996 1.068-3.97 18.586 4.995zM-1127.824 1700.137l-19.165-1.767.378-4.093 19.165 1.767zM-1147.263 1701.293h19.247v4.11h-19.247z"/> 82 | <path d="M-1121.458 1710.947s0 .96-.032.96v.016h-30.796s-.96 0-.96-.016h-.032v-20.03h3.288v16.805h25.244v-16.804h3.288v19.07zM-1130.667 1667.04l10.844 15.903-3.396 2.316-10.843-15.903zM-1118.313 1663.044l3.29 18.963-4.05.703-3.29-18.963z"/> 83 | </svg> 84 | </a> 85 | </li> 86 | </ul> 87 | 88 | <span id="plain-exception"><?php echo $tpl->escape($plain_exception) ?></span> 89 | <button id="copy-button" class="rightButton clipboard" data-clipboard-text="<?php echo $tpl->escape($plain_exception) ?>" title="Copy exception details to clipboard"> 90 | COPY 91 | </button> 92 | <button id="hide-error" class="rightButton" title="Hide error message" onclick="document.getElementsByClassName('Whoops')[0].style.display = 'none';"> 93 | HIDE 94 | </button> 95 | </div> 96 | </div> 97 | -------------------------------------------------------------------------------- /src/Whoops/Resources/views/header_outer.html.php: -------------------------------------------------------------------------------- 1 | <header> 2 | <?php $tpl->render($header) ?> 3 | </header> 4 | -------------------------------------------------------------------------------- /src/Whoops/Resources/views/layout.html.php: -------------------------------------------------------------------------------- 1 | <?php 2 | /** 3 | * Layout template file for Whoops's pretty error output. 4 | */ 5 | ?> 6 | <!DOCTYPE html><?php echo $preface; ?> 7 | <html> 8 | <head> 9 | <meta charset="utf-8"> 10 | <meta name="robots" content="noindex,nofollow"/> 11 | <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/> 12 | <title><?php echo $tpl->escape($page_title) ?></title> 13 | 14 | <style><?php echo $stylesheet ?></style> 15 | <style><?php echo $prismCss ?></style> 16 | </head> 17 | <body> 18 | 19 | <div class="Whoops container"> 20 | <div class="stack-container"> 21 | 22 | <?php $tpl->render($panel_left_outer) ?> 23 | 24 | <?php $tpl->render($panel_details_outer) ?> 25 | 26 | </div> 27 | </div> 28 | 29 | <script data-manual><?php echo $prismJs ?></script> 30 | <script><?php echo $zepto ?></script> 31 | <script><?php echo $clipboard ?></script> 32 | <script><?php echo $javascript ?></script> 33 | </body> 34 | </html> 35 | -------------------------------------------------------------------------------- /src/Whoops/Resources/views/panel_details.html.php: -------------------------------------------------------------------------------- 1 | <?php $tpl->render($frame_code) ?> 2 | <?php $tpl->render($env_details) ?> -------------------------------------------------------------------------------- /src/Whoops/Resources/views/panel_details_outer.html.php: -------------------------------------------------------------------------------- 1 | <div class="panel details-container cf"> 2 | <?php $tpl->render($panel_details) ?> 3 | </div> -------------------------------------------------------------------------------- /src/Whoops/Resources/views/panel_left.html.php: -------------------------------------------------------------------------------- 1 | <?php 2 | $tpl->render($header_outer); 3 | $tpl->render($frames_description); 4 | $tpl->render($frames_container); 5 | -------------------------------------------------------------------------------- /src/Whoops/Resources/views/panel_left_outer.html.php: -------------------------------------------------------------------------------- 1 | <div class="panel left-panel cf <?php echo (!$has_frames ? 'empty' : '') ?>"> 2 | <?php $tpl->render($panel_left) ?> 3 | </div> -------------------------------------------------------------------------------- /src/Whoops/Run.php: -------------------------------------------------------------------------------- 1 | <?php 2 | /** 3 | * Whoops - php errors for cool kids 4 | * @author Filipe Dobreira <http://github.com/filp> 5 | */ 6 | 7 | namespace Whoops; 8 | 9 | use InvalidArgumentException; 10 | use Throwable; 11 | use Whoops\Exception\ErrorException; 12 | use Whoops\Handler\CallbackHandler; 13 | use Whoops\Handler\Handler; 14 | use Whoops\Handler\HandlerInterface; 15 | use Whoops\Inspector\CallableInspectorFactory; 16 | use Whoops\Inspector\InspectorFactory; 17 | use Whoops\Inspector\InspectorFactoryInterface; 18 | use Whoops\Inspector\InspectorInterface; 19 | use Whoops\Util\Misc; 20 | use Whoops\Util\SystemFacade; 21 | 22 | final class Run implements RunInterface 23 | { 24 | /** 25 | * @var bool 26 | */ 27 | private $isRegistered; 28 | 29 | /** 30 | * @var bool 31 | */ 32 | private $allowQuit = true; 33 | 34 | /** 35 | * @var bool 36 | */ 37 | private $sendOutput = true; 38 | 39 | /** 40 | * @var integer|false 41 | */ 42 | private $sendHttpCode = 500; 43 | 44 | /** 45 | * @var integer|false 46 | */ 47 | private $sendExitCode = 1; 48 | 49 | /** 50 | * @var HandlerInterface[] 51 | */ 52 | private $handlerStack = []; 53 | 54 | /** 55 | * @var array 56 | * @psalm-var list<array{patterns: string, levels: int}> 57 | */ 58 | private $silencedPatterns = []; 59 | 60 | /** 61 | * @var SystemFacade 62 | */ 63 | private $system; 64 | 65 | /** 66 | * In certain scenarios, like in shutdown handler, we can not throw exceptions. 67 | * 68 | * @var bool 69 | */ 70 | private $canThrowExceptions = true; 71 | 72 | /** 73 | * The inspector factory to create inspectors. 74 | * 75 | * @var InspectorFactoryInterface 76 | */ 77 | private $inspectorFactory; 78 | 79 | /** 80 | * @var array<callable> 81 | */ 82 | private $frameFilters = []; 83 | 84 | public function __construct(?SystemFacade $system = null) 85 | { 86 | $this->system = $system ?: new SystemFacade; 87 | $this->inspectorFactory = new InspectorFactory(); 88 | } 89 | 90 | public function __destruct() 91 | { 92 | $this->unregister(); 93 | } 94 | 95 | /** 96 | * Explicitly request your handler runs as the last of all currently registered handlers. 97 | * 98 | * @param callable|HandlerInterface $handler 99 | * 100 | * @return Run 101 | */ 102 | public function appendHandler($handler) 103 | { 104 | array_unshift($this->handlerStack, $this->resolveHandler($handler)); 105 | return $this; 106 | } 107 | 108 | /** 109 | * Explicitly request your handler runs as the first of all currently registered handlers. 110 | * 111 | * @param callable|HandlerInterface $handler 112 | * 113 | * @return Run 114 | */ 115 | public function prependHandler($handler) 116 | { 117 | return $this->pushHandler($handler); 118 | } 119 | 120 | /** 121 | * Register your handler as the last of all currently registered handlers (to be executed first). 122 | * Prefer using appendHandler and prependHandler for clarity. 123 | * 124 | * @param callable|HandlerInterface $handler 125 | * 126 | * @return Run 127 | * 128 | * @throws InvalidArgumentException If argument is not callable or instance of HandlerInterface. 129 | */ 130 | public function pushHandler($handler) 131 | { 132 | $this->handlerStack[] = $this->resolveHandler($handler); 133 | return $this; 134 | } 135 | 136 | /** 137 | * Removes and returns the last handler pushed to the handler stack. 138 | * 139 | * @see Run::removeFirstHandler(), Run::removeLastHandler() 140 | * 141 | * @return HandlerInterface|null 142 | */ 143 | public function popHandler() 144 | { 145 | return array_pop($this->handlerStack); 146 | } 147 | 148 | /** 149 | * Removes the first handler. 150 | * 151 | * @return void 152 | */ 153 | public function removeFirstHandler() 154 | { 155 | array_pop($this->handlerStack); 156 | } 157 | 158 | /** 159 | * Removes the last handler. 160 | * 161 | * @return void 162 | */ 163 | public function removeLastHandler() 164 | { 165 | array_shift($this->handlerStack); 166 | } 167 | 168 | /** 169 | * Returns an array with all handlers, in the order they were added to the stack. 170 | * 171 | * @return array 172 | */ 173 | public function getHandlers() 174 | { 175 | return $this->handlerStack; 176 | } 177 | 178 | /** 179 | * Clears all handlers in the handlerStack, including the default PrettyPage handler. 180 | * 181 | * @return Run 182 | */ 183 | public function clearHandlers() 184 | { 185 | $this->handlerStack = []; 186 | return $this; 187 | } 188 | 189 | public function getFrameFilters() 190 | { 191 | return $this->frameFilters; 192 | } 193 | 194 | public function clearFrameFilters() 195 | { 196 | $this->frameFilters = []; 197 | return $this; 198 | } 199 | 200 | /** 201 | * Registers this instance as an error handler. 202 | * 203 | * @return Run 204 | */ 205 | public function register() 206 | { 207 | if (!$this->isRegistered) { 208 | // Workaround PHP bug 42098 209 | // https://bugs.php.net/bug.php?id=42098 210 | class_exists("\\Whoops\\Exception\\ErrorException"); 211 | class_exists("\\Whoops\\Exception\\FrameCollection"); 212 | class_exists("\\Whoops\\Exception\\Frame"); 213 | class_exists("\\Whoops\\Exception\\Inspector"); 214 | class_exists("\\Whoops\\Inspector\\InspectorFactory"); 215 | 216 | $this->system->setErrorHandler([$this, self::ERROR_HANDLER]); 217 | $this->system->setExceptionHandler([$this, self::EXCEPTION_HANDLER]); 218 | $this->system->registerShutdownFunction([$this, self::SHUTDOWN_HANDLER]); 219 | 220 | $this->isRegistered = true; 221 | } 222 | 223 | return $this; 224 | } 225 | 226 | /** 227 | * Unregisters all handlers registered by this Whoops\Run instance. 228 | * 229 | * @return Run 230 | */ 231 | public function unregister() 232 | { 233 | if ($this->isRegistered) { 234 | $this->system->restoreExceptionHandler(); 235 | $this->system->restoreErrorHandler(); 236 | 237 | $this->isRegistered = false; 238 | } 239 | 240 | return $this; 241 | } 242 | 243 | /** 244 | * Should Whoops allow Handlers to force the script to quit? 245 | * 246 | * @param bool|int $exit 247 | * 248 | * @return bool 249 | */ 250 | public function allowQuit($exit = null) 251 | { 252 | if (func_num_args() == 0) { 253 | return $this->allowQuit; 254 | } 255 | 256 | return $this->allowQuit = (bool) $exit; 257 | } 258 | 259 | /** 260 | * Silence particular errors in particular files. 261 | * 262 | * @param array|string $patterns List or a single regex pattern to match. 263 | * @param int $levels Defaults to E_STRICT | E_DEPRECATED. 264 | * 265 | * @return Run 266 | */ 267 | public function silenceErrorsInPaths($patterns, $levels = 10240) 268 | { 269 | $this->silencedPatterns = array_merge( 270 | $this->silencedPatterns, 271 | array_map( 272 | function ($pattern) use ($levels) { 273 | return [ 274 | "pattern" => $pattern, 275 | "levels" => $levels, 276 | ]; 277 | }, 278 | (array) $patterns 279 | ) 280 | ); 281 | 282 | return $this; 283 | } 284 | 285 | /** 286 | * Returns an array with silent errors in path configuration. 287 | * 288 | * @return array 289 | */ 290 | public function getSilenceErrorsInPaths() 291 | { 292 | return $this->silencedPatterns; 293 | } 294 | 295 | /** 296 | * Should Whoops send HTTP error code to the browser if possible? 297 | * Whoops will by default send HTTP code 500, but you may wish to 298 | * use 502, 503, or another 5xx family code. 299 | * 300 | * @param bool|int $code 301 | * 302 | * @return int|false 303 | * 304 | * @throws InvalidArgumentException 305 | */ 306 | public function sendHttpCode($code = null) 307 | { 308 | if (func_num_args() == 0) { 309 | return $this->sendHttpCode; 310 | } 311 | 312 | if (!$code) { 313 | return $this->sendHttpCode = false; 314 | } 315 | 316 | if ($code === true) { 317 | $code = 500; 318 | } 319 | 320 | if ($code < 400 || 600 <= $code) { 321 | throw new InvalidArgumentException( 322 | "Invalid status code '$code', must be 4xx or 5xx" 323 | ); 324 | } 325 | 326 | return $this->sendHttpCode = $code; 327 | } 328 | 329 | /** 330 | * Should Whoops exit with a specific code on the CLI if possible? 331 | * Whoops will exit with 1 by default, but you can specify something else. 332 | * 333 | * @param int $code 334 | * 335 | * @return int 336 | * 337 | * @throws InvalidArgumentException 338 | */ 339 | public function sendExitCode($code = null) 340 | { 341 | if (func_num_args() == 0) { 342 | return $this->sendExitCode; 343 | } 344 | 345 | if ($code < 0 || 255 <= $code) { 346 | throw new InvalidArgumentException( 347 | "Invalid status code '$code', must be between 0 and 254" 348 | ); 349 | } 350 | 351 | return $this->sendExitCode = (int) $code; 352 | } 353 | 354 | /** 355 | * Should Whoops push output directly to the client? 356 | * If this is false, output will be returned by handleException. 357 | * 358 | * @param bool|int $send 359 | * 360 | * @return bool 361 | */ 362 | public function writeToOutput($send = null) 363 | { 364 | if (func_num_args() == 0) { 365 | return $this->sendOutput; 366 | } 367 | 368 | return $this->sendOutput = (bool) $send; 369 | } 370 | 371 | /** 372 | * Handles an exception, ultimately generating a Whoops error page. 373 | * 374 | * @param Throwable $exception 375 | * 376 | * @return string Output generated by handlers. 377 | */ 378 | public function handleException($exception) 379 | { 380 | // Walk the registered handlers in the reverse order 381 | // they were registered, and pass off the exception 382 | $inspector = $this->getInspector($exception); 383 | 384 | // Capture output produced while handling the exception, 385 | // we might want to send it straight away to the client, 386 | // or return it silently. 387 | $this->system->startOutputBuffering(); 388 | 389 | // Just in case there are no handlers: 390 | $handlerResponse = null; 391 | $handlerContentType = null; 392 | 393 | try { 394 | foreach (array_reverse($this->handlerStack) as $handler) { 395 | $handler->setRun($this); 396 | $handler->setInspector($inspector); 397 | $handler->setException($exception); 398 | 399 | // The HandlerInterface does not require an Exception passed to handle() 400 | // and neither of our bundled handlers use it. 401 | // However, 3rd party handlers may have already relied on this parameter, 402 | // and removing it would be possibly breaking for users. 403 | $handlerResponse = $handler->handle($exception); 404 | 405 | // Collect the content type for possible sending in the headers. 406 | $handlerContentType = method_exists($handler, 'contentType') ? $handler->contentType() : null; 407 | 408 | if (in_array($handlerResponse, [Handler::LAST_HANDLER, Handler::QUIT])) { 409 | // The Handler has handled the exception in some way, and 410 | // wishes to quit execution (Handler::QUIT), or skip any 411 | // other handlers (Handler::LAST_HANDLER). If $this->allowQuit 412 | // is false, Handler::QUIT behaves like Handler::LAST_HANDLER 413 | break; 414 | } 415 | } 416 | 417 | $willQuit = $handlerResponse == Handler::QUIT && $this->allowQuit(); 418 | } finally { 419 | $output = $this->system->cleanOutputBuffer(); 420 | } 421 | 422 | // If we're allowed to, send output generated by handlers directly 423 | // to the output, otherwise, and if the script doesn't quit, return 424 | // it so that it may be used by the caller 425 | if ($this->writeToOutput()) { 426 | // @todo Might be able to clean this up a bit better 427 | if ($willQuit) { 428 | // Cleanup all other output buffers before sending our output: 429 | while ($this->system->getOutputBufferLevel() > 0) { 430 | $this->system->endOutputBuffering(); 431 | } 432 | 433 | // Send any headers if needed: 434 | if (Misc::canSendHeaders() && $handlerContentType) { 435 | header("Content-Type: {$handlerContentType}"); 436 | } 437 | } 438 | 439 | $this->writeToOutputNow($output); 440 | } 441 | 442 | if ($willQuit) { 443 | // HHVM fix for https://github.com/facebook/hhvm/issues/4055 444 | $this->system->flushOutputBuffer(); 445 | 446 | $this->system->stopExecution( 447 | $this->sendExitCode() 448 | ); 449 | } 450 | 451 | return $output; 452 | } 453 | 454 | /** 455 | * Converts generic PHP errors to \ErrorException instances, before passing them off to be handled. 456 | * 457 | * This method MUST be compatible with set_error_handler. 458 | * 459 | * @param int $level 460 | * @param string $message 461 | * @param string|null $file 462 | * @param int|null $line 463 | * 464 | * @return bool 465 | * 466 | * @throws ErrorException 467 | */ 468 | public function handleError($level, $message, $file = null, $line = null) 469 | { 470 | if ($level & $this->system->getErrorReportingLevel()) { 471 | foreach ($this->silencedPatterns as $entry) { 472 | $pathMatches = (bool) preg_match($entry["pattern"], $file); 473 | $levelMatches = $level & $entry["levels"]; 474 | if ($pathMatches && $levelMatches) { 475 | // Ignore the error, abort handling 476 | // See https://github.com/filp/whoops/issues/418 477 | return true; 478 | } 479 | } 480 | 481 | // XXX we pass $level for the "code" param only for BC reasons. 482 | // see https://github.com/filp/whoops/issues/267 483 | $exception = new ErrorException($message, /*code*/ $level, /*severity*/ $level, $file, $line); 484 | if ($this->canThrowExceptions) { 485 | throw $exception; 486 | } else { 487 | $this->handleException($exception); 488 | } 489 | // Do not propagate errors which were already handled by Whoops. 490 | return true; 491 | } 492 | 493 | // Propagate error to the next handler, allows error_get_last() to 494 | // work on silenced errors. 495 | return false; 496 | } 497 | 498 | /** 499 | * Special case to deal with Fatal errors and the like. 500 | * 501 | * @return void 502 | */ 503 | public function handleShutdown() 504 | { 505 | // If we reached this step, we are in shutdown handler. 506 | // An exception thrown in a shutdown handler will not be propagated 507 | // to the exception handler. Pass that information along. 508 | $this->canThrowExceptions = false; 509 | 510 | // If we are not currently registered, we should not do anything 511 | if (!$this->isRegistered) { 512 | return; 513 | } 514 | 515 | $error = $this->system->getLastError(); 516 | if ($error && Misc::isLevelFatal($error['type'])) { 517 | // If there was a fatal error, 518 | // it was not handled in handleError yet. 519 | $this->allowQuit = false; 520 | $this->handleError( 521 | $error['type'], 522 | $error['message'], 523 | $error['file'], 524 | $error['line'] 525 | ); 526 | } 527 | } 528 | 529 | 530 | /** 531 | * @param InspectorFactoryInterface $factory 532 | * 533 | * @return void 534 | */ 535 | public function setInspectorFactory(InspectorFactoryInterface $factory) 536 | { 537 | $this->inspectorFactory = $factory; 538 | } 539 | 540 | public function addFrameFilter($filterCallback) 541 | { 542 | if (!is_callable($filterCallback)) { 543 | throw new \InvalidArgumentException(sprintf( 544 | "A frame filter must be of type callable, %s type given.", 545 | gettype($filterCallback) 546 | )); 547 | } 548 | 549 | $this->frameFilters[] = $filterCallback; 550 | return $this; 551 | } 552 | 553 | /** 554 | * @param Throwable $exception 555 | * 556 | * @return InspectorInterface 557 | */ 558 | private function getInspector($exception) 559 | { 560 | return $this->inspectorFactory->create($exception); 561 | } 562 | 563 | /** 564 | * Resolves the giving handler. 565 | * 566 | * @param callable|HandlerInterface $handler 567 | * 568 | * @return HandlerInterface 569 | * 570 | * @throws InvalidArgumentException 571 | */ 572 | private function resolveHandler($handler) 573 | { 574 | if (is_callable($handler)) { 575 | $handler = new CallbackHandler($handler); 576 | } 577 | 578 | if (!$handler instanceof HandlerInterface) { 579 | throw new InvalidArgumentException( 580 | "Handler must be a callable, or instance of " 581 | . "Whoops\\Handler\\HandlerInterface" 582 | ); 583 | } 584 | 585 | return $handler; 586 | } 587 | 588 | /** 589 | * Echo something to the browser. 590 | * 591 | * @param string $output 592 | * 593 | * @return Run 594 | */ 595 | private function writeToOutputNow($output) 596 | { 597 | if ($this->sendHttpCode() && Misc::canSendHeaders()) { 598 | $this->system->setHttpResponseCode( 599 | $this->sendHttpCode() 600 | ); 601 | } 602 | 603 | echo $output; 604 | 605 | return $this; 606 | } 607 | } 608 | -------------------------------------------------------------------------------- /src/Whoops/RunInterface.php: -------------------------------------------------------------------------------- 1 | <?php 2 | /** 3 | * Whoops - php errors for cool kids 4 | * @author Filipe Dobreira <http://github.com/filp> 5 | */ 6 | 7 | namespace Whoops; 8 | 9 | use InvalidArgumentException; 10 | use Whoops\Exception\ErrorException; 11 | use Whoops\Handler\HandlerInterface; 12 | 13 | interface RunInterface 14 | { 15 | const EXCEPTION_HANDLER = "handleException"; 16 | const ERROR_HANDLER = "handleError"; 17 | const SHUTDOWN_HANDLER = "handleShutdown"; 18 | 19 | /** 20 | * Pushes a handler to the end of the stack 21 | * 22 | * @throws InvalidArgumentException If argument is not callable or instance of HandlerInterface 23 | * @param Callable|HandlerInterface $handler 24 | * @return Run 25 | */ 26 | public function pushHandler($handler); 27 | 28 | /** 29 | * Removes the last handler in the stack and returns it. 30 | * Returns null if there"s nothing else to pop. 31 | * 32 | * @return null|HandlerInterface 33 | */ 34 | public function popHandler(); 35 | 36 | /** 37 | * Returns an array with all handlers, in the 38 | * order they were added to the stack. 39 | * 40 | * @return array 41 | */ 42 | public function getHandlers(); 43 | 44 | /** 45 | * Clears all handlers in the handlerStack, including 46 | * the default PrettyPage handler. 47 | * 48 | * @return Run 49 | */ 50 | public function clearHandlers(); 51 | 52 | /** 53 | * @return array<callable> 54 | */ 55 | public function getFrameFilters(); 56 | 57 | /** 58 | * @return Run 59 | */ 60 | public function clearFrameFilters(); 61 | 62 | /** 63 | * Registers this instance as an error handler. 64 | * 65 | * @return Run 66 | */ 67 | public function register(); 68 | 69 | /** 70 | * Unregisters all handlers registered by this Whoops\Run instance 71 | * 72 | * @return Run 73 | */ 74 | public function unregister(); 75 | 76 | /** 77 | * Should Whoops allow Handlers to force the script to quit? 78 | * 79 | * @param bool|int $exit 80 | * @return bool 81 | */ 82 | public function allowQuit($exit = null); 83 | 84 | /** 85 | * Silence particular errors in particular files 86 | * 87 | * @param array|string $patterns List or a single regex pattern to match 88 | * @param int $levels Defaults to E_STRICT | E_DEPRECATED 89 | * @return \Whoops\Run 90 | */ 91 | public function silenceErrorsInPaths($patterns, $levels = 10240); 92 | 93 | /** 94 | * Should Whoops send HTTP error code to the browser if possible? 95 | * Whoops will by default send HTTP code 500, but you may wish to 96 | * use 502, 503, or another 5xx family code. 97 | * 98 | * @param bool|int $code 99 | * @return int|false 100 | */ 101 | public function sendHttpCode($code = null); 102 | 103 | /** 104 | * Should Whoops exit with a specific code on the CLI if possible? 105 | * Whoops will exit with 1 by default, but you can specify something else. 106 | * 107 | * @param int $code 108 | * @return int 109 | */ 110 | public function sendExitCode($code = null); 111 | 112 | /** 113 | * Should Whoops push output directly to the client? 114 | * If this is false, output will be returned by handleException 115 | * 116 | * @param bool|int $send 117 | * @return bool 118 | */ 119 | public function writeToOutput($send = null); 120 | 121 | /** 122 | * Handles an exception, ultimately generating a Whoops error 123 | * page. 124 | * 125 | * @param \Throwable $exception 126 | * @return string Output generated by handlers 127 | */ 128 | public function handleException($exception); 129 | 130 | /** 131 | * Converts generic PHP errors to \ErrorException 132 | * instances, before passing them off to be handled. 133 | * 134 | * This method MUST be compatible with set_error_handler. 135 | * 136 | * @param int $level 137 | * @param string $message 138 | * @param string $file 139 | * @param int $line 140 | * 141 | * @return bool 142 | * @throws ErrorException 143 | */ 144 | public function handleError($level, $message, $file = null, $line = null); 145 | 146 | /** 147 | * Special case to deal with Fatal errors and the like. 148 | */ 149 | public function handleShutdown(); 150 | 151 | /** 152 | * Registers a filter callback in the frame filters stack. 153 | * 154 | * @param callable $filterCallback 155 | * @return \Whoops\Run 156 | */ 157 | public function addFrameFilter($filterCallback); 158 | } 159 | -------------------------------------------------------------------------------- /src/Whoops/Util/HtmlDumperOutput.php: -------------------------------------------------------------------------------- 1 | <?php 2 | /** 3 | * Whoops - php errors for cool kids 4 | * @author Filipe Dobreira <http://github.com/filp> 5 | */ 6 | 7 | namespace Whoops\Util; 8 | 9 | /** 10 | * Used as output callable for Symfony\Component\VarDumper\Dumper\HtmlDumper::dump() 11 | * 12 | * @see TemplateHelper::dump() 13 | */ 14 | class HtmlDumperOutput 15 | { 16 | private $output; 17 | 18 | public function __invoke($line, $depth) 19 | { 20 | // A negative depth means "end of dump" 21 | if ($depth >= 0) { 22 | // Adds a two spaces indentation to the line 23 | $this->output .= str_repeat(' ', $depth) . $line . "\n"; 24 | } 25 | } 26 | 27 | public function getOutput() 28 | { 29 | return $this->output; 30 | } 31 | 32 | public function clear() 33 | { 34 | $this->output = null; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Whoops/Util/Misc.php: -------------------------------------------------------------------------------- 1 | <?php 2 | /** 3 | * Whoops - php errors for cool kids 4 | * @author Filipe Dobreira <http://github.com/filp> 5 | */ 6 | 7 | namespace Whoops\Util; 8 | 9 | class Misc 10 | { 11 | /** 12 | * Can we at this point in time send HTTP headers? 13 | * 14 | * Currently this checks if we are even serving an HTTP request, 15 | * as opposed to running from a command line. 16 | * 17 | * If we are serving an HTTP request, we check if it's not too late. 18 | * 19 | * @return bool 20 | */ 21 | public static function canSendHeaders() 22 | { 23 | return isset($_SERVER["REQUEST_URI"]) && !headers_sent(); 24 | } 25 | 26 | public static function isAjaxRequest() 27 | { 28 | return ( 29 | !empty($_SERVER['HTTP_X_REQUESTED_WITH']) 30 | && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest'); 31 | } 32 | 33 | /** 34 | * Check, if possible, that this execution was triggered by a command line. 35 | * @return bool 36 | */ 37 | public static function isCommandLine() 38 | { 39 | return PHP_SAPI == 'cli'; 40 | } 41 | 42 | /** 43 | * Translate ErrorException code into the represented constant. 44 | * 45 | * @param int $error_code 46 | * @return string 47 | */ 48 | public static function translateErrorCode($error_code) 49 | { 50 | $constants = get_defined_constants(true); 51 | if (array_key_exists('Core', $constants)) { 52 | foreach ($constants['Core'] as $constant => $value) { 53 | if (substr($constant, 0, 2) == 'E_' && $value == $error_code) { 54 | return $constant; 55 | } 56 | } 57 | } 58 | return "E_UNKNOWN"; 59 | } 60 | 61 | /** 62 | * Determine if an error level is fatal (halts execution) 63 | * 64 | * @param int $level 65 | * @return bool 66 | */ 67 | public static function isLevelFatal($level) 68 | { 69 | $errors = E_ERROR; 70 | $errors |= E_PARSE; 71 | $errors |= E_CORE_ERROR; 72 | $errors |= E_CORE_WARNING; 73 | $errors |= E_COMPILE_ERROR; 74 | $errors |= E_COMPILE_WARNING; 75 | return ($level & $errors) > 0; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Whoops/Util/SystemFacade.php: -------------------------------------------------------------------------------- 1 | <?php 2 | /** 3 | * Whoops - php errors for cool kids 4 | * @author Filipe Dobreira <http://github.com/filp> 5 | */ 6 | 7 | namespace Whoops\Util; 8 | 9 | class SystemFacade 10 | { 11 | /** 12 | * Turns on output buffering. 13 | * 14 | * @return bool 15 | */ 16 | public function startOutputBuffering() 17 | { 18 | return ob_start(); 19 | } 20 | 21 | /** 22 | * @param callable $handler 23 | * @param int $types 24 | * 25 | * @return callable|null 26 | */ 27 | public function setErrorHandler(callable $handler, $types = 'use-php-defaults') 28 | { 29 | // Since PHP 5.4 the constant E_ALL contains all errors (even E_STRICT) 30 | if ($types === 'use-php-defaults') { 31 | $types = E_ALL; 32 | } 33 | return set_error_handler($handler, $types); 34 | } 35 | 36 | /** 37 | * @param callable $handler 38 | * 39 | * @return callable|null 40 | */ 41 | public function setExceptionHandler(callable $handler) 42 | { 43 | return set_exception_handler($handler); 44 | } 45 | 46 | /** 47 | * @return void 48 | */ 49 | public function restoreExceptionHandler() 50 | { 51 | restore_exception_handler(); 52 | } 53 | 54 | /** 55 | * @return void 56 | */ 57 | public function restoreErrorHandler() 58 | { 59 | restore_error_handler(); 60 | } 61 | 62 | /** 63 | * @param callable $function 64 | * 65 | * @return void 66 | */ 67 | public function registerShutdownFunction(callable $function) 68 | { 69 | register_shutdown_function($function); 70 | } 71 | 72 | /** 73 | * @return string|false 74 | */ 75 | public function cleanOutputBuffer() 76 | { 77 | return ob_get_clean(); 78 | } 79 | 80 | /** 81 | * @return int 82 | */ 83 | public function getOutputBufferLevel() 84 | { 85 | return ob_get_level(); 86 | } 87 | 88 | /** 89 | * @return bool 90 | */ 91 | public function endOutputBuffering() 92 | { 93 | return ob_end_clean(); 94 | } 95 | 96 | /** 97 | * @return void 98 | */ 99 | public function flushOutputBuffer() 100 | { 101 | flush(); 102 | } 103 | 104 | /** 105 | * @return int 106 | */ 107 | public function getErrorReportingLevel() 108 | { 109 | return error_reporting(); 110 | } 111 | 112 | /** 113 | * @return array|null 114 | */ 115 | public function getLastError() 116 | { 117 | return error_get_last(); 118 | } 119 | 120 | /** 121 | * @param int $httpCode 122 | * 123 | * @return int 124 | */ 125 | public function setHttpResponseCode($httpCode) 126 | { 127 | if (!headers_sent()) { 128 | // Ensure that no 'location' header is present as otherwise this 129 | // will override the HTTP code being set here, and mask the 130 | // expected error page. 131 | header_remove('location'); 132 | } 133 | 134 | return http_response_code($httpCode); 135 | } 136 | 137 | /** 138 | * @param int $exitStatus 139 | */ 140 | public function stopExecution($exitStatus) 141 | { 142 | exit($exitStatus); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/Whoops/Util/TemplateHelper.php: -------------------------------------------------------------------------------- 1 | <?php 2 | /** 3 | * Whoops - php errors for cool kids 4 | * @author Filipe Dobreira <http://github.com/filp> 5 | */ 6 | 7 | namespace Whoops\Util; 8 | 9 | use Symfony\Component\VarDumper\Caster\Caster; 10 | use Symfony\Component\VarDumper\Cloner\AbstractCloner; 11 | use Symfony\Component\VarDumper\Cloner\VarCloner; 12 | use Symfony\Component\VarDumper\Dumper\HtmlDumper; 13 | use Whoops\Exception\Frame; 14 | 15 | /** 16 | * Exposes useful tools for working with/in templates 17 | */ 18 | class TemplateHelper 19 | { 20 | /** 21 | * An array of variables to be passed to all templates 22 | * @var array 23 | */ 24 | private $variables = []; 25 | 26 | /** 27 | * @var HtmlDumper 28 | */ 29 | private $htmlDumper; 30 | 31 | /** 32 | * @var HtmlDumperOutput 33 | */ 34 | private $htmlDumperOutput; 35 | 36 | /** 37 | * @var AbstractCloner 38 | */ 39 | private $cloner; 40 | 41 | /** 42 | * @var string 43 | */ 44 | private $applicationRootPath; 45 | 46 | public function __construct() 47 | { 48 | // root path for ordinary composer projects 49 | $this->applicationRootPath = dirname(dirname(dirname(dirname(dirname(dirname(__DIR__)))))); 50 | } 51 | 52 | /** 53 | * Escapes a string for output in an HTML document 54 | * 55 | * @param string $raw 56 | * @return string 57 | */ 58 | public function escape($raw) 59 | { 60 | $flags = ENT_QUOTES; 61 | 62 | // HHVM has all constants defined, but only ENT_IGNORE 63 | // works at the moment 64 | if (defined("ENT_SUBSTITUTE") && !defined("HHVM_VERSION")) { 65 | $flags |= ENT_SUBSTITUTE; 66 | } else { 67 | // This is for 5.3. 68 | // The documentation warns of a potential security issue, 69 | // but it seems it does not apply in our case, because 70 | // we do not blacklist anything anywhere. 71 | $flags |= ENT_IGNORE; 72 | } 73 | 74 | $raw = str_replace(chr(9), ' ', $raw); 75 | 76 | return htmlspecialchars($raw, $flags, "UTF-8"); 77 | } 78 | 79 | /** 80 | * Escapes a string for output in an HTML document, but preserves 81 | * URIs within it, and converts them to clickable anchor elements. 82 | * 83 | * @param string $raw 84 | * @return string 85 | */ 86 | public function escapeButPreserveUris($raw) 87 | { 88 | $escaped = $this->escape($raw); 89 | return preg_replace( 90 | "@([A-z]+?://([-\w\.]+[-\w])+(:\d+)?(/([\w/_\.#-]*(\?\S+)?[^\.\s])?)?)@", 91 | "<a href=\"$1\" target=\"_blank\" rel=\"noreferrer noopener\">$1</a>", 92 | $escaped 93 | ); 94 | } 95 | 96 | /** 97 | * Makes sure that the given string breaks on the delimiter. 98 | * 99 | * @param string $delimiter 100 | * @param string $s 101 | * @return string 102 | */ 103 | public function breakOnDelimiter($delimiter, $s) 104 | { 105 | $parts = explode($delimiter, $s); 106 | foreach ($parts as &$part) { 107 | $part = '<span class="delimiter">' . $part . '</span>'; 108 | } 109 | 110 | return implode($delimiter, $parts); 111 | } 112 | 113 | /** 114 | * Replace the part of the path that all files have in common. 115 | * 116 | * @param string $path 117 | * @return string 118 | */ 119 | public function shorten($path) 120 | { 121 | if ($this->applicationRootPath != "/") { 122 | $path = str_replace($this->applicationRootPath, '…', $path); 123 | } 124 | 125 | return $path; 126 | } 127 | 128 | private function getDumper() 129 | { 130 | if (!$this->htmlDumper && class_exists('Symfony\Component\VarDumper\Cloner\VarCloner')) { 131 | $this->htmlDumperOutput = new HtmlDumperOutput(); 132 | // re-use the same var-dumper instance, so it won't re-render the global styles/scripts on each dump. 133 | $this->htmlDumper = new HtmlDumper($this->htmlDumperOutput); 134 | 135 | $styles = [ 136 | 'default' => 'color:#FFFFFF; line-height:normal; font:12px "Inconsolata", "Fira Mono", "Source Code Pro", Monaco, Consolas, "Lucida Console", monospace !important; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:99999; word-break: normal', 137 | 'num' => 'color:#BCD42A', 138 | 'const' => 'color: #4bb1b1;', 139 | 'str' => 'color:#BCD42A', 140 | 'note' => 'color:#ef7c61', 141 | 'ref' => 'color:#A0A0A0', 142 | 'public' => 'color:#FFFFFF', 143 | 'protected' => 'color:#FFFFFF', 144 | 'private' => 'color:#FFFFFF', 145 | 'meta' => 'color:#FFFFFF', 146 | 'key' => 'color:#BCD42A', 147 | 'index' => 'color:#ef7c61', 148 | ]; 149 | $this->htmlDumper->setStyles($styles); 150 | } 151 | 152 | return $this->htmlDumper; 153 | } 154 | 155 | /** 156 | * Format the given value into a human readable string. 157 | * 158 | * @param mixed $value 159 | * @return string 160 | */ 161 | public function dump($value) 162 | { 163 | $dumper = $this->getDumper(); 164 | 165 | if ($dumper) { 166 | // re-use the same DumpOutput instance, so it won't re-render the global styles/scripts on each dump. 167 | // exclude verbose information (e.g. exception stack traces) 168 | if (class_exists('Symfony\Component\VarDumper\Caster\Caster')) { 169 | $cloneVar = $this->getCloner()->cloneVar($value, Caster::EXCLUDE_VERBOSE); 170 | // Symfony VarDumper 2.6 Caster class dont exist. 171 | } else { 172 | $cloneVar = $this->getCloner()->cloneVar($value); 173 | } 174 | 175 | $dumper->dump( 176 | $cloneVar, 177 | $this->htmlDumperOutput 178 | ); 179 | 180 | $output = $this->htmlDumperOutput->getOutput(); 181 | $this->htmlDumperOutput->clear(); 182 | 183 | return $output; 184 | } 185 | 186 | return htmlspecialchars(print_r($value, true)); 187 | } 188 | 189 | /** 190 | * Format the args of the given Frame as a human readable html string 191 | * 192 | * @param Frame $frame 193 | * @return string the rendered html 194 | */ 195 | public function dumpArgs(Frame $frame) 196 | { 197 | // we support frame args only when the optional dumper is available 198 | if (!$this->getDumper()) { 199 | return ''; 200 | } 201 | 202 | $html = ''; 203 | $numFrames = count($frame->getArgs()); 204 | 205 | if ($numFrames > 0) { 206 | $html = '<ol class="linenums">'; 207 | foreach ($frame->getArgs() as $j => $frameArg) { 208 | $html .= '<li>'. $this->dump($frameArg) .'</li>'; 209 | } 210 | $html .= '</ol>'; 211 | } 212 | 213 | return $html; 214 | } 215 | 216 | /** 217 | * Convert a string to a slug version of itself 218 | * 219 | * @param string $original 220 | * @return string 221 | */ 222 | public function slug($original) 223 | { 224 | $slug = str_replace(" ", "-", $original); 225 | $slug = preg_replace('/[^\w\d\-\_]/i', '', $slug); 226 | return strtolower($slug); 227 | } 228 | 229 | /** 230 | * Given a template path, render it within its own scope. This 231 | * method also accepts an array of additional variables to be 232 | * passed to the template. 233 | * 234 | * @param string $template 235 | */ 236 | public function render($template, ?array $additionalVariables = null) 237 | { 238 | $variables = $this->getVariables(); 239 | 240 | // Pass the helper to the template: 241 | $variables["tpl"] = $this; 242 | 243 | if ($additionalVariables !== null) { 244 | $variables = array_replace($variables, $additionalVariables); 245 | } 246 | 247 | call_user_func(function () { 248 | extract(func_get_arg(1)); 249 | require func_get_arg(0); 250 | }, $template, $variables); 251 | } 252 | 253 | /** 254 | * Sets the variables to be passed to all templates rendered 255 | * by this template helper. 256 | */ 257 | public function setVariables(array $variables) 258 | { 259 | $this->variables = $variables; 260 | } 261 | 262 | /** 263 | * Sets a single template variable, by its name: 264 | * 265 | * @param string $variableName 266 | * @param mixed $variableValue 267 | */ 268 | public function setVariable($variableName, $variableValue) 269 | { 270 | $this->variables[$variableName] = $variableValue; 271 | } 272 | 273 | /** 274 | * Gets a single template variable, by its name, or 275 | * $defaultValue if the variable does not exist 276 | * 277 | * @param string $variableName 278 | * @param mixed $defaultValue 279 | * @return mixed 280 | */ 281 | public function getVariable($variableName, $defaultValue = null) 282 | { 283 | return isset($this->variables[$variableName]) ? 284 | $this->variables[$variableName] : $defaultValue; 285 | } 286 | 287 | /** 288 | * Unsets a single template variable, by its name 289 | * 290 | * @param string $variableName 291 | */ 292 | public function delVariable($variableName) 293 | { 294 | unset($this->variables[$variableName]); 295 | } 296 | 297 | /** 298 | * Returns all variables for this helper 299 | * 300 | * @return array 301 | */ 302 | public function getVariables() 303 | { 304 | return $this->variables; 305 | } 306 | 307 | /** 308 | * Set the cloner used for dumping variables. 309 | * 310 | * @param AbstractCloner $cloner 311 | */ 312 | public function setCloner($cloner) 313 | { 314 | $this->cloner = $cloner; 315 | } 316 | 317 | /** 318 | * Get the cloner used for dumping variables. 319 | * 320 | * @return AbstractCloner 321 | */ 322 | public function getCloner() 323 | { 324 | if (!$this->cloner) { 325 | $this->cloner = new VarCloner(); 326 | } 327 | return $this->cloner; 328 | } 329 | 330 | /** 331 | * Set the application root path. 332 | * 333 | * @param string $applicationRootPath 334 | */ 335 | public function setApplicationRootPath($applicationRootPath) 336 | { 337 | $this->applicationRootPath = $applicationRootPath; 338 | } 339 | 340 | /** 341 | * Return the application root path. 342 | * 343 | * @return string 344 | */ 345 | public function getApplicationRootPath() 346 | { 347 | return $this->applicationRootPath; 348 | } 349 | } 350 | --------------------------------------------------------------------------------