PageRenderTime 52ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/core/Associates/Whoops/vendor/filp/whoops/src/Whoops/Handler/PrettyPageHandler.php

https://gitlab.com/fiesta-framework/Documentation
PHP | 576 lines | 282 code | 76 blank | 218 comment | 28 complexity | edf4d164c673989b7ede1f116f437f98 MD5 | raw file
  1. <?php
  2. /**
  3. * Whoops - php errors for cool kids
  4. * @author Filipe Dobreira <http://github.com/filp>
  5. */
  6. namespace Whoops\Handler;
  7. use InvalidArgumentException;
  8. use RuntimeException;
  9. use Whoops\Exception\Formatter;
  10. use Whoops\Util\Misc;
  11. use Whoops\Util\TemplateHelper;
  12. class PrettyPageHandler extends Handler
  13. {
  14. /**
  15. * Search paths to be scanned for resources, in the reverse
  16. * order they're declared.
  17. *
  18. * @var array
  19. */
  20. private $searchPaths = array();
  21. /**
  22. * Fast lookup cache for known resource locations.
  23. *
  24. * @var array
  25. */
  26. private $resourceCache = array();
  27. /**
  28. * The name of the custom css file.
  29. *
  30. * @var string
  31. */
  32. private $customCss = null;
  33. /**
  34. * @var array[]
  35. */
  36. private $extraTables = array();
  37. /**
  38. * @var bool
  39. */
  40. private $handleUnconditionally = false;
  41. /**
  42. * @var string
  43. */
  44. private $pageTitle = "Whoops! There was an error.";
  45. /**
  46. * A string identifier for a known IDE/text editor, or a closure
  47. * that resolves a string that can be used to open a given file
  48. * in an editor. If the string contains the special substrings
  49. * %file or %line, they will be replaced with the correct data.
  50. *
  51. * @example
  52. * "txmt://open?url=%file&line=%line"
  53. * @var mixed $editor
  54. */
  55. protected $editor;
  56. /**
  57. * A list of known editor strings
  58. * @var array
  59. */
  60. protected $editors = array(
  61. "sublime" => "subl://open?url=file://%file&line=%line",
  62. "textmate" => "txmt://open?url=file://%file&line=%line",
  63. "emacs" => "emacs://open?url=file://%file&line=%line",
  64. "macvim" => "mvim://open/?url=file://%file&line=%line",
  65. );
  66. /**
  67. * Constructor.
  68. */
  69. public function __construct()
  70. {
  71. if (ini_get('xdebug.file_link_format') || extension_loaded('xdebug')) {
  72. // Register editor using xdebug's file_link_format option.
  73. $this->editors['xdebug'] = function ($file, $line) {
  74. return str_replace(array('%f', '%l'), array($file, $line), ini_get('xdebug.file_link_format'));
  75. };
  76. }
  77. // Add the default, local resource search path:
  78. $this->searchPaths[] = __DIR__ . "/../Resources";
  79. }
  80. /**
  81. * @return int|null
  82. */
  83. public function handle()
  84. {
  85. if (!$this->handleUnconditionally()) {
  86. // Check conditions for outputting HTML:
  87. // @todo: Make this more robust
  88. if (php_sapi_name() === 'cli') {
  89. // Help users who have been relying on an internal test value
  90. // fix their code to the proper method
  91. if (isset($_ENV['whoops-test'])) {
  92. throw new \Exception(
  93. 'Use handleUnconditionally instead of whoops-test'
  94. .' environment variable'
  95. );
  96. }
  97. return Handler::DONE;
  98. }
  99. }
  100. // @todo: Make this more dynamic
  101. $helper = new TemplateHelper();
  102. $templateFile = $this->getResource("views/layout.html.php");
  103. $cssFile = $this->getResource("css/whoops.base.css");
  104. $zeptoFile = $this->getResource("js/zepto.min.js");
  105. $jsFile = $this->getResource("js/whoops.base.js");
  106. if ($this->customCss) {
  107. $customCssFile = $this->getResource($this->customCss);
  108. }
  109. $inspector = $this->getInspector();
  110. $frames = $inspector->getFrames();
  111. //
  112. $code = $inspector->getException()->getCode();
  113. if ($inspector->getException() instanceof \ErrorException) {
  114. $code = Misc::translateErrorCode($code);
  115. }
  116. //error_log("###".Formatter::formatExceptionPlain($inspector)."###");
  117. // List of variables that will be passed to the layout template.
  118. $vars = array(
  119. "page_title" => $this->getPageTitle(),
  120. // @todo: Asset compiler
  121. "stylesheet" => file_get_contents($cssFile),
  122. "zepto" => file_get_contents($zeptoFile),
  123. "javascript" => file_get_contents($jsFile),
  124. // Template paths:
  125. "header" => $this->getResource("views/header.html.php"),
  126. "frame_list" => $this->getResource("views/frame_list.html.php"),
  127. "frame_code" => $this->getResource("views/frame_code.html.php"),
  128. "env_details" => $this->getResource("views/env_details.html.php"),
  129. "title" => $this->getPageTitle(),
  130. "name" => explode("\\", $inspector->getExceptionName()),
  131. "message" => $inspector->getException()->getMessage(),
  132. "code" => $code,
  133. "plain_exception" => Formatter::formatExceptionPlain($inspector),
  134. "frames" => $frames,
  135. "has_frames" => !!count($frames),
  136. "handler" => $this,
  137. "handlers" => $this->getRun()->getHandlers(),
  138. "tables" => array(
  139. "Server/Request Data" => $_SERVER,
  140. "GET Data" => $_GET,
  141. "POST Data" => $_POST,
  142. "Files" => $_FILES,
  143. "Cookies" => $_COOKIE,
  144. "Session" => isset($_SESSION) ? $_SESSION : array(),
  145. "Environment Variables" => $_ENV,
  146. ),
  147. );
  148. if (isset($customCssFile)) {
  149. $vars["stylesheet"] .= file_get_contents($customCssFile);
  150. }
  151. // Add extra entries list of data tables:
  152. // @todo: Consolidate addDataTable and addDataTableCallback
  153. $extraTables = array_map(function ($table) {
  154. return $table instanceof \Closure ? $table() : $table;
  155. }, $this->getDataTables());
  156. $vars["tables"] = array_merge($extraTables, $vars["tables"]);
  157. $helper->setVariables($vars);
  158. $helper->render($templateFile);
  159. //
  160. return Handler::QUIT;
  161. }
  162. /**
  163. * Adds an entry to the list of tables displayed in the template.
  164. * The expected data is a simple associative array. Any nested arrays
  165. * will be flattened with print_r
  166. * @param string $label
  167. * @param array $data
  168. */
  169. public function addDataTable($label, array $data)
  170. {
  171. $this->extraTables[$label] = $data;
  172. }
  173. /**
  174. * Lazily adds an entry to the list of tables displayed in the table.
  175. * The supplied callback argument will be called when the error is rendered,
  176. * it should produce a simple associative array. Any nested arrays will
  177. * be flattened with print_r.
  178. *
  179. * @throws InvalidArgumentException If $callback is not callable
  180. * @param string $label
  181. * @param callable $callback Callable returning an associative array
  182. */
  183. public function addDataTableCallback($label, /* callable */ $callback)
  184. {
  185. if (!is_callable($callback)) {
  186. throw new InvalidArgumentException('Expecting callback argument to be callable');
  187. }
  188. $this->extraTables[$label] = function () use ($callback) {
  189. try {
  190. $result = call_user_func($callback);
  191. // Only return the result if it can be iterated over by foreach().
  192. return is_array($result) || $result instanceof \Traversable ? $result : array();
  193. } catch (\Exception $e) {
  194. // Don't allow failure to break the rendering of the original exception.
  195. return array();
  196. }
  197. };
  198. }
  199. /**
  200. * Returns all the extra data tables registered with this handler.
  201. * Optionally accepts a 'label' parameter, to only return the data
  202. * table under that label.
  203. * @param string|null $label
  204. * @return array[]|callable
  205. */
  206. public function getDataTables($label = null)
  207. {
  208. if ($label !== null) {
  209. return isset($this->extraTables[$label]) ?
  210. $this->extraTables[$label] : array();
  211. }
  212. return $this->extraTables;
  213. }
  214. /**
  215. * Allows to disable all attempts to dynamically decide whether to
  216. * handle or return prematurely.
  217. * Set this to ensure that the handler will perform no matter what.
  218. * @param bool|null $value
  219. * @return bool|null
  220. */
  221. public function handleUnconditionally($value = null)
  222. {
  223. if (func_num_args() == 0) {
  224. return $this->handleUnconditionally;
  225. }
  226. $this->handleUnconditionally = (bool) $value;
  227. }
  228. /**
  229. * Adds an editor resolver, identified by a string
  230. * name, and that may be a string path, or a callable
  231. * resolver. If the callable returns a string, it will
  232. * be set as the file reference's href attribute.
  233. *
  234. * @example
  235. * $run->addEditor('macvim', "mvim://open?url=file://%file&line=%line")
  236. * @example
  237. * $run->addEditor('remove-it', function($file, $line) {
  238. * unlink($file);
  239. * return "http://stackoverflow.com";
  240. * });
  241. * @param string $identifier
  242. * @param string $resolver
  243. */
  244. public function addEditor($identifier, $resolver)
  245. {
  246. $this->editors[$identifier] = $resolver;
  247. }
  248. /**
  249. * Set the editor to use to open referenced files, by a string
  250. * identifier, or a callable that will be executed for every
  251. * file reference, with a $file and $line argument, and should
  252. * return a string.
  253. *
  254. * @example
  255. * $run->setEditor(function($file, $line) { return "file:///{$file}"; });
  256. * @example
  257. * $run->setEditor('sublime');
  258. *
  259. * @throws InvalidArgumentException If invalid argument identifier provided
  260. * @param string|callable $editor
  261. */
  262. public function setEditor($editor)
  263. {
  264. if (!is_callable($editor) && !isset($this->editors[$editor])) {
  265. throw new InvalidArgumentException(
  266. "Unknown editor identifier: $editor. Known editors:" .
  267. implode(",", array_keys($this->editors))
  268. );
  269. }
  270. $this->editor = $editor;
  271. }
  272. /**
  273. * Given a string file path, and an integer file line,
  274. * executes the editor resolver and returns, if available,
  275. * a string that may be used as the href property for that
  276. * file reference.
  277. *
  278. * @throws InvalidArgumentException If editor resolver does not return a string
  279. * @param string $filePath
  280. * @param int $line
  281. * @return false|string
  282. */
  283. public function getEditorHref($filePath, $line)
  284. {
  285. if ($this->editor === null) {
  286. return false;
  287. }
  288. $editor = $this->editor;
  289. if (is_string($editor)) {
  290. $editor = $this->editors[$editor];
  291. }
  292. if (is_callable($editor)) {
  293. $editor = call_user_func($editor, $filePath, $line);
  294. }
  295. // Check that the editor is a string, and replace the
  296. // %line and %file placeholders:
  297. if (!is_string($editor)) {
  298. throw new InvalidArgumentException(
  299. __METHOD__ . " should always resolve to a string; got something else instead"
  300. );
  301. }
  302. $editor = str_replace("%line", rawurlencode($line), $editor);
  303. $editor = str_replace("%file", rawurlencode($filePath), $editor);
  304. return $editor;
  305. }
  306. /**
  307. * @param string $title
  308. * @return void
  309. */
  310. public function setPageTitle($title)
  311. {
  312. $this->pageTitle = (string) $title;
  313. }
  314. /**
  315. * @return string
  316. */
  317. public function getPageTitle()
  318. {
  319. return $this->pageTitle;
  320. }
  321. /**
  322. * Adds a path to the list of paths to be searched for
  323. * resources.
  324. *
  325. * @throws InvalidArgumnetException If $path is not a valid directory
  326. *
  327. * @param string $path
  328. * @return void
  329. */
  330. public function addResourcePath($path)
  331. {
  332. if (!is_dir($path)) {
  333. throw new InvalidArgumentException(
  334. "'$path' is not a valid directory"
  335. );
  336. }
  337. array_unshift($this->searchPaths, $path);
  338. }
  339. /**
  340. * Adds a custom css file to be loaded.
  341. *
  342. * @param string $name
  343. * @return void
  344. */
  345. public function addCustomCss($name)
  346. {
  347. $this->customCss = $name;
  348. }
  349. /**
  350. * @return array
  351. */
  352. public function getResourcePaths()
  353. {
  354. return $this->searchPaths;
  355. }
  356. /**
  357. * Finds a resource, by its relative path, in all available search paths.
  358. * The search is performed starting at the last search path, and all the
  359. * way back to the first, enabling a cascading-type system of overrides
  360. * for all resources.
  361. *
  362. * @throws RuntimeException If resource cannot be found in any of the available paths
  363. *
  364. * @param string $resource
  365. * @return string
  366. */
  367. protected function getResource($resource)
  368. {
  369. // If the resource was found before, we can speed things up
  370. // by caching its absolute, resolved path:
  371. if (isset($this->resourceCache[$resource])) {
  372. return $this->resourceCache[$resource];
  373. }
  374. // Search through available search paths, until we find the
  375. // resource we're after:
  376. foreach ($this->searchPaths as $path) {
  377. $fullPath = $path . "/$resource";
  378. if (is_file($fullPath)) {
  379. // Cache the result:
  380. $this->resourceCache[$resource] = $fullPath;
  381. return $fullPath;
  382. }
  383. }
  384. // If we got this far, nothing was found.
  385. throw new RuntimeException(
  386. "Could not find resource '$resource' in any resource paths."
  387. . "(searched: " . join(", ", $this->searchPaths). ")"
  388. );
  389. }
  390. /**
  391. * @deprecated
  392. *
  393. * @return string
  394. */
  395. public function getResourcesPath()
  396. {
  397. $allPaths = $this->getResourcePaths();
  398. // Compat: return only the first path added
  399. return end($allPaths) ?: null;
  400. }
  401. /**
  402. * @deprecated
  403. *
  404. * @param string $resourcesPath
  405. * @return void
  406. */
  407. public function setResourcesPath($resourcesPath)
  408. {
  409. $this->addResourcePath($resourcesPath);
  410. }
  411. /**
  412. * @return int|null
  413. */
  414. public function handleUserDebug($name2,$array)
  415. {
  416. if (!$this->handleUnconditionally()) {
  417. // Check conditions for outputting HTML:
  418. // @todo: Make this more robust
  419. if (php_sapi_name() === 'cli') {
  420. // Help users who have been relying on an internal test value
  421. // fix their code to the proper method
  422. if (isset($_ENV['whoops-test'])) {
  423. throw new \Exception(
  424. 'Use handleUnconditionally instead of whoops-test'
  425. .' environment variable'
  426. );
  427. }
  428. return Handler::DONE;
  429. }
  430. }
  431. // @todo: Make this more dynamic
  432. $helper = new TemplateHelper();
  433. $templateFile = $this->getResource("views/layout.html.php");
  434. $cssFile = $this->getResource("css/whoops.base.css");
  435. $zeptoFile = $this->getResource("js/zepto.min.js");
  436. $jsFile = $this->getResource("js/whoops.base.js");
  437. if ($this->customCss) {
  438. $customCssFile = $this->getResource($this->customCss);
  439. }
  440. //$inspector = $this->getInspector();
  441. $frames = array();
  442. $code = 1;
  443. //
  444. // List of variables that will be passed to the layout template.
  445. $vars = array(
  446. "page_title" => $this->getPageTitle(),
  447. // @todo: Asset compiler
  448. "stylesheet" => file_get_contents($cssFile),
  449. "zepto" => file_get_contents($zeptoFile),
  450. "javascript" => file_get_contents($jsFile),
  451. // Template paths:
  452. "header" => $this->getResource("views/header.html.php"),
  453. "frame_list" => $this->getResource("views/frame_list.html.php"),
  454. "frame_code" => $this->getResource("views/frame_code.html.php"),
  455. "env_details" => $this->getResource("views/env_details.html.php"),
  456. "title" => $this->getPageTitle(),
  457. "name" => array("Fiesta","Debug"),
  458. "message" => $name2,
  459. "code" => $code,
  460. "plain_exception" => "DebugStatus thrown with message 'hhhh'\n
  461. \n
  462. Stacktrace:\n
  463. #8 DebugStatus in C:\wamp\www\fiesta\core\Debug.php:10\n
  464. #7 Debug:stop in C:\wamp\www\fiesta\app\Routes.php:17\n
  465. #6 App:{closure} in <#unknown>:0\n
  466. #5 call_user_func_array in C:\wamp\www\fiesta\core\Access\Routes_2.php:405\n
  467. #4 Routes:runRoute in C:\wamp\www\fiesta\core\Access\Routes_2.php:303\n
  468. #3 Routes:exec in C:\wamp\www\fiesta\core\Access\Routes_2.php:246\n
  469. #2 Routes:run in C:\wamp\www\fiesta\core\Ini.php:147\n
  470. #1 App:run in C:\wamp\www\fiesta\bootstrap\start.php:28\n
  471. #0 require in C:\wamp\www\fiesta\public\index.php:13",
  472. "frames" => $frames,
  473. "has_frames" => !!count($frames),
  474. "handler" => $this,
  475. "handlers" => array($this),
  476. "tables" => array(
  477. "Selected parameters" => $array,
  478. "Server/Request Data" => $_SERVER,
  479. "GET Data" => $_GET,
  480. "POST Data" => $_POST,
  481. "Files" => $_FILES,
  482. "Cookies" => $_COOKIE,
  483. "Session" => isset($_SESSION) ? $_SESSION : array(),
  484. "Environment Variables" => $_ENV,
  485. ),
  486. );
  487. if (isset($customCssFile)) {
  488. $vars["stylesheet"] .= file_get_contents($customCssFile);
  489. }
  490. // Add extra entries list of data tables:
  491. // @todo: Consolidate addDataTable and addDataTableCallback
  492. $extraTables = array_map(function ($table) {
  493. return $table instanceof \Closure ? $table() : $table;
  494. }, $this->getDataTables());
  495. $vars["tables"] = array_merge($extraTables, $vars["tables"]);
  496. $helper->setVariables($vars);
  497. $helper->render($templateFile);
  498. return Handler::QUIT;
  499. }
  500. }