ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
PrettyPageHandler.php
Go to the documentation of this file.
1 <?php
7 namespace Whoops\Handler;
8 
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 }
$path
Definition: aliased.php:25
$_COOKIE['client_id']
Definition: server.php:9
getExceptionFrames()
Get the stack trace frames of the exception that is currently being handled.
Handler outputing plaintext error messages.
if((!isset($_SERVER['DOCUMENT_ROOT'])) OR(empty($_SERVER['DOCUMENT_ROOT']))) $_SERVER['DOCUMENT_ROOT']
$_SESSION["AccountId"]
$result
static translateErrorCode($error_code)
Translate ErrorException code into the represented constant.
Definition: Misc.php:48
$_GET["client_id"]
$code
Definition: example_050.php:99
blacklist($superGlobalName, $key)
blacklist a sensitive value within one of the superglobal arrays.
Exposes useful tools for working with/in templates.
Whoops - php errors for cool kids.
masked(array $superGlobal, $superGlobalName)
Checks all values within the given superGlobal array.
addResourcePath($path)
Adds a path to the list of paths to be searched for resources.
getExceptionCode()
Get the code of the exception that is currently being handled.
addEditor($identifier, $resolver)
handleUnconditionally($value=null)
Allows to disable all attempts to dynamically decide whether to handle or return prematurely.
getDataTables($label=null)
Returns all the extra data tables registered with this handler.
addDataTableCallback($label, $callback)
Lazily adds an entry to the list of tables displayed in the table.
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.
setApplicationPaths($applicationPaths)
Set the application paths.
static formatExceptionPlain(Inspector $inspector)
Definition: Formatter.php:49
$values
getResource($resource)
Finds a resource, by its relative path, in all available search paths.
setApplicationRootPath($applicationRootPath)
Set the application root path.
getEditorHref($filePath, $line)
Given a string file path, and an integer file line, executes the editor resolver and returns...
const QUIT
The Handler has handled the Throwable in some way, and wishes to quit/stop execution.
Definition: Handler.php:31
Abstract implementation of a Handler.
Definition: Handler.php:15
getApplicationPaths()
Return the application paths.
addCustomCss($name)
Adds a custom css file to be loaded.
if(empty($password)) $table
Definition: pwgen.php:24
getEditor($filePath, $line)
Given a boolean if the editor link should act as an Ajax request.
$key
Definition: croninfo.php:18
$_POST["username"]
$data
Definition: bench.php:6