ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
PrettyPageHandler.php
Go to the documentation of this file.
1<?php
7namespace Whoops\Handler;
8
9use InvalidArgumentException;
10use RuntimeException;
11use Symfony\Component\VarDumper\Cloner\AbstractCloner;
12use Symfony\Component\VarDumper\Cloner\VarCloner;
13use UnexpectedValueException;
17
19{
26 private $searchPaths = [];
27
33 private $resourceCache = [];
34
40 private $customCss = null;
41
45 private $extraTables = [];
46
50 private $handleUnconditionally = false;
51
55 private $pageTitle = "Whoops! There was an error.";
56
61
65 private $blacklist = [
66 '_GET' => [],
67 '_POST' => [],
68 '_FILES' => [],
69 '_COOKIE' => [],
70 '_SESSION' => [],
71 '_SERVER' => [],
72 '_ENV' => [],
73 ];
74
85 protected $editor;
86
91 protected $editors = [
92 "sublime" => "subl://open?url=file://%file&line=%line",
93 "textmate" => "txmt://open?url=file://%file&line=%line",
94 "emacs" => "emacs://open?url=file://%file&line=%line",
95 "macvim" => "mvim://open/?url=file://%file&line=%line",
96 "phpstorm" => "phpstorm://open?file=%file&line=%line",
97 "idea" => "idea://open?file=%file&line=%line",
98 "vscode" => "vscode://file/%file:%line",
99 ];
100
105
109 public function __construct()
110 {
111 if (ini_get('xdebug.file_link_format') || extension_loaded('xdebug')) {
112 // Register editor using xdebug's file_link_format option.
113 $this->editors['xdebug'] = function ($file, $line) {
114 return str_replace(['%f', '%l'], [$file, $line], ini_get('xdebug.file_link_format'));
115 };
116 }
117
118 // Add the default, local resource search path:
119 $this->searchPaths[] = __DIR__ . "/../Resources";
120
121 // blacklist php provided auth based values
122 $this->blacklist('_SERVER', 'PHP_AUTH_PW');
123
124 $this->templateHelper = new TemplateHelper();
125
126 if (class_exists('Symfony\Component\VarDumper\Cloner\VarCloner')) {
127 $cloner = new VarCloner();
128 // Only dump object internals if a custom caster exists.
129 $cloner->addCasters(['*' => function ($obj, $a, $stub, $isNested, $filter = 0) {
130 $class = $stub->class;
131 $classes = [$class => $class] + class_parents($class) + class_implements($class);
132
133 foreach ($classes as $class) {
134 if (isset(AbstractCloner::$defaultCasters[$class])) {
135 return $a;
136 }
137 }
138
139 // Remove all internals
140 return [];
141 }]);
142 $this->templateHelper->setCloner($cloner);
143 }
144 }
145
149 public function handle()
150 {
151 if (!$this->handleUnconditionally()) {
152 // Check conditions for outputting HTML:
153 // @todo: Make this more robust
154 if (PHP_SAPI === 'cli') {
155 // Help users who have been relying on an internal test value
156 // fix their code to the proper method
157 if (isset($_ENV['whoops-test'])) {
158 throw new \Exception(
159 'Use handleUnconditionally instead of whoops-test'
160 .' environment variable'
161 );
162 }
163
164 return Handler::DONE;
165 }
166 }
167
168 $templateFile = $this->getResource("views/layout.html.php");
169 $cssFile = $this->getResource("css/whoops.base.css");
170 $zeptoFile = $this->getResource("js/zepto.min.js");
171 $prettifyFile = $this->getResource("js/prettify.min.js");
172 $clipboard = $this->getResource("js/clipboard.min.js");
173 $jsFile = $this->getResource("js/whoops.base.js");
174
175 if ($this->customCss) {
176 $customCssFile = $this->getResource($this->customCss);
177 }
178
179 $inspector = $this->getInspector();
180 $frames = $this->getExceptionFrames();
181 $code = $this->getExceptionCode();
182
183 // List of variables that will be passed to the layout template.
184 $vars = [
185 "page_title" => $this->getPageTitle(),
186
187 // @todo: Asset compiler
188 "stylesheet" => file_get_contents($cssFile),
189 "zepto" => file_get_contents($zeptoFile),
190 "prettify" => file_get_contents($prettifyFile),
191 "clipboard" => file_get_contents($clipboard),
192 "javascript" => file_get_contents($jsFile),
193
194 // Template paths:
195 "header" => $this->getResource("views/header.html.php"),
196 "header_outer" => $this->getResource("views/header_outer.html.php"),
197 "frame_list" => $this->getResource("views/frame_list.html.php"),
198 "frames_description" => $this->getResource("views/frames_description.html.php"),
199 "frames_container" => $this->getResource("views/frames_container.html.php"),
200 "panel_details" => $this->getResource("views/panel_details.html.php"),
201 "panel_details_outer" => $this->getResource("views/panel_details_outer.html.php"),
202 "panel_left" => $this->getResource("views/panel_left.html.php"),
203 "panel_left_outer" => $this->getResource("views/panel_left_outer.html.php"),
204 "frame_code" => $this->getResource("views/frame_code.html.php"),
205 "env_details" => $this->getResource("views/env_details.html.php"),
206
207 "title" => $this->getPageTitle(),
208 "name" => explode("\\", $inspector->getExceptionName()),
209 "message" => $inspector->getExceptionMessage(),
210 "docref_url" => $inspector->getExceptionDocrefUrl(),
211 "code" => $code,
212 "plain_exception" => Formatter::formatExceptionPlain($inspector),
213 "frames" => $frames,
214 "has_frames" => !!count($frames),
215 "handler" => $this,
216 "handlers" => $this->getRun()->getHandlers(),
217
218 "active_frames_tab" => count($frames) && $frames->offsetGet(0)->isApplication() ? 'application' : 'all',
219 "has_frames_tabs" => $this->getApplicationPaths(),
220
221 "tables" => [
222 "GET Data" => $this->masked($_GET, '_GET'),
223 "POST Data" => $this->masked($_POST, '_POST'),
224 "Files" => isset($_FILES) ? $this->masked($_FILES, '_FILES') : [],
225 "Cookies" => $this->masked($_COOKIE, '_COOKIE'),
226 "Session" => isset($_SESSION) ? $this->masked($_SESSION, '_SESSION') : [],
227 "Server/Request Data" => $this->masked($_SERVER, '_SERVER'),
228 "Environment Variables" => $this->masked($_ENV, '_ENV'),
229 ],
230 ];
231
232 if (isset($customCssFile)) {
233 $vars["stylesheet"] .= file_get_contents($customCssFile);
234 }
235
236 // Add extra entries list of data tables:
237 // @todo: Consolidate addDataTable and addDataTableCallback
238 $extraTables = array_map(function ($table) use ($inspector) {
239 return $table instanceof \Closure ? $table($inspector) : $table;
240 }, $this->getDataTables());
241 $vars["tables"] = array_merge($extraTables, $vars["tables"]);
242
243 $plainTextHandler = new PlainTextHandler();
244 $plainTextHandler->setException($this->getException());
245 $plainTextHandler->setInspector($this->getInspector());
246 $vars["preface"] = "<!--\n\n\n" . $this->templateHelper->escape($plainTextHandler->generateResponse()) . "\n\n\n\n\n\n\n\n\n\n\n-->";
247
248 $this->templateHelper->setVariables($vars);
249 $this->templateHelper->render($templateFile);
250
251 return Handler::QUIT;
252 }
253
259 protected function getExceptionFrames()
260 {
261 $frames = $this->getInspector()->getFrames();
262
263 if ($this->getApplicationPaths()) {
264 foreach ($frames as $frame) {
265 foreach ($this->getApplicationPaths() as $path) {
266 if (strpos($frame->getFile(), $path) === 0) {
267 $frame->setApplication(true);
268 break;
269 }
270 }
271 }
272 }
273
274 return $frames;
275 }
276
282 protected function getExceptionCode()
283 {
284 $exception = $this->getException();
285
286 $code = $exception->getCode();
287 if ($exception instanceof \ErrorException) {
288 // ErrorExceptions wrap the php-error types within the 'severity' property
289 $code = Misc::translateErrorCode($exception->getSeverity());
290 }
291
292 return (string) $code;
293 }
294
298 public function contentType()
299 {
300 return 'text/html';
301 }
302
310 public function addDataTable($label, array $data)
311 {
312 $this->extraTables[$label] = $data;
313 }
314
325 public function addDataTableCallback($label, /* callable */ $callback)
326 {
327 if (!is_callable($callback)) {
328 throw new InvalidArgumentException('Expecting callback argument to be callable');
329 }
330
331 $this->extraTables[$label] = function (\Whoops\Exception\Inspector $inspector = null) use ($callback) {
332 try {
333 $result = call_user_func($callback, $inspector);
334
335 // Only return the result if it can be iterated over by foreach().
336 return is_array($result) || $result instanceof \Traversable ? $result : [];
337 } catch (\Exception $e) {
338 // Don't allow failure to break the rendering of the original exception.
339 return [];
340 }
341 };
342 }
343
351 public function getDataTables($label = null)
352 {
353 if ($label !== null) {
354 return isset($this->extraTables[$label]) ?
355 $this->extraTables[$label] : [];
356 }
357
358 return $this->extraTables;
359 }
360
368 public function handleUnconditionally($value = null)
369 {
370 if (func_num_args() == 0) {
372 }
373
374 $this->handleUnconditionally = (bool) $value;
375 }
376
393 public function addEditor($identifier, $resolver)
394 {
395 $this->editors[$identifier] = $resolver;
396 }
397
412 public function setEditor($editor)
413 {
414 if (!is_callable($editor) && !isset($this->editors[$editor])) {
415 throw new InvalidArgumentException(
416 "Unknown editor identifier: $editor. Known editors:" .
417 implode(",", array_keys($this->editors))
418 );
419 }
420
421 $this->editor = $editor;
422 }
423
435 public function getEditorHref($filePath, $line)
436 {
437 $editor = $this->getEditor($filePath, $line);
438
439 if (empty($editor)) {
440 return false;
441 }
442
443 // Check that the editor is a string, and replace the
444 // %line and %file placeholders:
445 if (!isset($editor['url']) || !is_string($editor['url'])) {
446 throw new UnexpectedValueException(
447 __METHOD__ . " should always resolve to a string or a valid editor array; got something else instead."
448 );
449 }
450
451 $editor['url'] = str_replace("%line", rawurlencode($line), $editor['url']);
452 $editor['url'] = str_replace("%file", rawurlencode($filePath), $editor['url']);
453
454 return $editor['url'];
455 }
456
467 public function getEditorAjax($filePath, $line)
468 {
469 $editor = $this->getEditor($filePath, $line);
470
471 // Check that the ajax is a bool
472 if (!isset($editor['ajax']) || !is_bool($editor['ajax'])) {
473 throw new UnexpectedValueException(
474 __METHOD__ . " should always resolve to a bool; got something else instead."
475 );
476 }
477 return $editor['ajax'];
478 }
479
489 protected function getEditor($filePath, $line)
490 {
491 if (!$this->editor || (!is_string($this->editor) && !is_callable($this->editor))) {
492 return [];
493 }
494
495 if (is_string($this->editor) && isset($this->editors[$this->editor]) && !is_callable($this->editors[$this->editor])) {
496 return [
497 'ajax' => false,
498 'url' => $this->editors[$this->editor],
499 ];
500 }
501
502 if (is_callable($this->editor) || (isset($this->editors[$this->editor]) && is_callable($this->editors[$this->editor]))) {
503 if (is_callable($this->editor)) {
504 $callback = call_user_func($this->editor, $filePath, $line);
505 } else {
506 $callback = call_user_func($this->editors[$this->editor], $filePath, $line);
507 }
508
509 if (is_string($callback)) {
510 return [
511 'ajax' => false,
512 'url' => $callback,
513 ];
514 }
515
516 return [
517 'ajax' => isset($callback['ajax']) ? $callback['ajax'] : false,
518 'url' => isset($callback['url']) ? $callback['url'] : $callback,
519 ];
520 }
521
522 return [];
523 }
524
529 public function setPageTitle($title)
530 {
531 $this->pageTitle = (string) $title;
532 }
533
537 public function getPageTitle()
538 {
539 return $this->pageTitle;
540 }
541
551 public function addResourcePath($path)
552 {
553 if (!is_dir($path)) {
554 throw new InvalidArgumentException(
555 "'$path' is not a valid directory"
556 );
557 }
558
559 array_unshift($this->searchPaths, $path);
560 }
561
568 public function addCustomCss($name)
569 {
570 $this->customCss = $name;
571 }
572
576 public function getResourcePaths()
577 {
578 return $this->searchPaths;
579 }
580
592 protected function getResource($resource)
593 {
594 // If the resource was found before, we can speed things up
595 // by caching its absolute, resolved path:
596 if (isset($this->resourceCache[$resource])) {
597 return $this->resourceCache[$resource];
598 }
599
600 // Search through available search paths, until we find the
601 // resource we're after:
602 foreach ($this->searchPaths as $path) {
603 $fullPath = $path . "/$resource";
604
605 if (is_file($fullPath)) {
606 // Cache the result:
607 $this->resourceCache[$resource] = $fullPath;
608 return $fullPath;
609 }
610 }
611
612 // If we got this far, nothing was found.
613 throw new RuntimeException(
614 "Could not find resource '$resource' in any resource paths."
615 . "(searched: " . join(", ", $this->searchPaths). ")"
616 );
617 }
618
624 public function getResourcesPath()
625 {
626 $allPaths = $this->getResourcePaths();
627
628 // Compat: return only the first path added
629 return end($allPaths) ?: null;
630 }
631
638 public function setResourcesPath($resourcesPath)
639 {
640 $this->addResourcePath($resourcesPath);
641 }
642
648 public function getApplicationPaths()
649 {
651 }
652
659 {
660 $this->applicationPaths = $applicationPaths;
661 }
662
668 public function setApplicationRootPath($applicationRootPath)
669 {
670 $this->templateHelper->setApplicationRootPath($applicationRootPath);
671 }
672
679 public function blacklist($superGlobalName, $key)
680 {
681 $this->blacklist[$superGlobalName][] = $key;
682 }
683
694 private function masked(array $superGlobal, $superGlobalName)
695 {
696 $blacklisted = $this->blacklist[$superGlobalName];
697
698 $values = $superGlobal;
699 foreach ($blacklisted as $key) {
700 if (isset($superGlobal[$key])) {
701 $values[$key] = str_repeat('*', strlen($superGlobal[$key]));
702 }
703 }
704 return $values;
705 }
706}
$result
$_COOKIE['client_id']
Definition: server.php:9
$path
Definition: aliased.php:25
$_GET["client_id"]
$_POST["username"]
$_SESSION["AccountId"]
An exception for terminatinating execution or to throw for unit testing.
Wraps ErrorException; mostly used for typing (at least now) to easily cleanup the stack trace of redu...
static formatExceptionPlain(Inspector $inspector)
Definition: Formatter.php:49
Abstract implementation of a Handler.
Definition: Handler.php:16
const QUIT
The Handler has handled the Throwable in some way, and wishes to quit/stop execution.
Definition: Handler.php:31
Handler outputing plaintext error messages.
getEditorHref($filePath, $line)
Given a string file path, and an integer file line, executes the editor resolver and returns,...
addEditor($identifier, $resolver)
blacklist($superGlobalName, $key)
blacklist a sensitive value within one of the superglobal arrays.
setApplicationPaths($applicationPaths)
Set the application paths.
getEditor($filePath, $line)
Given a boolean if the editor link should act as an Ajax request.
addDataTableCallback($label, $callback)
Lazily adds an entry to the list of tables displayed in the table.
handleUnconditionally($value=null)
Allows to disable all attempts to dynamically decide whether to handle or return prematurely.
addResourcePath($path)
Adds a path to the list of paths to be searched for resources.
setApplicationRootPath($applicationRootPath)
Set the application root path.
masked(array $superGlobal, $superGlobalName)
Checks all values within the given superGlobal array.
addCustomCss($name)
Adds a custom css file to be loaded.
getEditorAjax($filePath, $line)
Given a boolean if the editor link should act as an Ajax request.
addDataTable($label, array $data)
Adds an entry to the list of tables displayed in the template.
getExceptionCode()
Get the code of the exception that is currently being handled.
getDataTables($label=null)
Returns all the extra data tables registered with this handler.
getExceptionFrames()
Get the stack trace frames of the exception that is currently being handled.
getApplicationPaths()
Return the application paths.
getResource($resource)
Finds a resource, by its relative path, in all available search paths.
static translateErrorCode($error_code)
Translate ErrorException code into the represented constant.
Definition: Misc.php:48
Exposes useful tools for working with/in templates.
$key
Definition: croninfo.php:18
$code
Definition: example_050.php:99
Whoops - php errors for cool kids.
if(empty($password)) $table
Definition: pwgen.php:24
if((!isset($_SERVER['DOCUMENT_ROOT'])) OR(empty($_SERVER['DOCUMENT_ROOT']))) $_SERVER['DOCUMENT_ROOT']
$values
$data
Definition: bench.php:6