Passed
Push — master ( f08e5d...a3e128 )
by Alexander
11:58
created

PleasingPageHandler::getDefaultRouting()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 7
rs 10
1
<?php 
2
3
/**
4
 * Lenevor Framework
5
 *
6
 * LICENSE
7
 *
8
 * This source file is subject to the new BSD license that is bundled
9
 * with this package in the file license.md.
10
 * It is also available through the world-wide-web at this URL:
11
 * https://lenevor.com/license
12
 * If you did not receive a copy of the license and are unable to
13
 * obtain it through the world-wide-web, please send an email
14
 * to [email protected] so we can send you a copy immediately.
15
 *
16
 * @package     Lenevor
17
 * @subpackage  Base
18
 * @link        https://lenevor.com
19
 * @copyright   Copyright (c) 2019 - 2023 Alexander Campo <[email protected]>
20
 * @license     https://opensource.org/licenses/BSD-3-Clause New BSD license or see https://lenevor.com/license or see /license.md
21
 */
22
23
namespace Syscodes\Components\Debug\Handlers;
24
25
use Exception;
26
use Traversable;
27
use ErrorException;
28
use RuntimeException;
29
use InvalidArgumentException;
30
use UnexpectedValueException;
31
use Syscodes\Components\Debug\Util\Misc;
32
use Syscodes\Components\Contracts\Debug\Table;
33
use Syscodes\Components\Debug\Util\ArrayTable;
34
use Syscodes\Components\Debug\Util\TemplateHandler;
35
use Syscodes\Components\Debug\FrameHandler\Formatter;  
36
37
/**
38
 * Generates exceptions in mode of graphic interface.
39
 */
40
class PleasingPageHandler extends MainHandler
41
{
42
	/**
43
	 * The brand main of handler.
44
	 * 
45
	 * @var string $brand
46
	 */
47
	protected $brand = 'Lenevor Debug';
48
49
	/**
50
	 * A string identifier for a known IDE/text editor, or a closure
51
	 * that resolves a string that can be used to open a given file
52
	 * in an editor.
53
	 * 
54
	 * @var mixed $editor
55
	 */
56
	protected $editor;
57
58
	/**
59
	 * A list of known editor strings.
60
	 * 
61
	 * @var array $editors
62
	 */
63
	protected $editors = [
64
		"vscode"   => "vscode://file/%file:%line",
65
		"sublime"  => "subl://open?url=file://%file&line=%line",
66
		"phpstorm" => "phpstorm://open?file://%file&line=%line",
67
		"textmate" => "txmt://open?url=file://%file&line=%line",
68
		"atom"     => "atom://core/open/file?filename=%file&line=%line",
69
	];
70
	
71
	/**
72
	 * The page title main of handler.
73
	 * 
74
	 * @var string $pageTitle
75
	 */
76
	protected $pageTitle = 'Lenevor Debug! There was an error';
77
	
78
	/**
79
	 * Fast lookup cache for known resource locations.
80
	 * 
81
	 * @var array $resourceCache
82
	 */
83
	protected $resourceCache = [];
84
	
85
	/**
86
	 * The path to the directory containing the html error template directories.
87
	 * 
88
	 * @var array $searchPaths
89
	 */
90
	protected $searchPaths = [];
91
92
	/**
93
	 * Gets the table of data.
94
	 * 
95
	 * @var array $tables
96
	 */
97
	protected $tables = [];
98
	
99
	/**
100
	 * The template handler system.
101
	 * 
102
	 * @var string|object $template
103
	 */
104
	protected $template;	
105
	
106
	/**
107
	 * Constructor. The PleasingPageHandler class.
108
	 * 
109
	 * @return void
110
	 */
111
	public function __construct()
112
	{
113
		$this->template      = new TemplateHandler;
114
		$this->searchPaths[] = dirname(__DIR__).DIRECTORY_SEPARATOR.'Resources';
115
	}
116
117
	/**
118
	 * Adds an editor resolver, identified by a string name, and that may be a 
119
	 * string path, or a callable resolver.
120
	 * 
121
	 * @param  string            $identifier
122
	 * @param  string|\Callable  $resolver
0 ignored issues
show
Bug introduced by
The type Callable was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
123
	 * 
124
	 * @return void
125
	 */
126
	public function addEditor($identifier, $resolver): void
127
	{
128
		$this->editors[$identifier] = $resolver;
129
	}
130
131
	/**
132
	 * Adds an entry to the list of tables displayed in the template.
133
	 * The expected data is a simple associative array. Any nested arrays
134
	 * will be flattened with print_r.
135
	 * 
136
	 * @param  \Syscodes\Components\Contracts\Debug\Table  $table
137
	 * 
138
	 * @return void
139
	 */
140
	public function addTables(Table $table): void
141
	{
142
		$this->tables[] = $table;
143
	}
144
	
145
	/**
146
	 * Gathers the variables that will be made available to the view.
147
	 * 
148
	 * @return  array
149
	 */
150
	protected function collectionVars(): array
151
	{
152
		$supervisor = $this->getSupervisor();
153
		$style      = file_get_contents($this->getResource('css/debug.base.css'));
154
		$jscript    = file_get_contents($this->getResource('js/debug.base.js'));
155
		$servers    = array_merge($this->getDefaultServers(), $this->tables);
156
		$routing    = array_merge($this->getDefaultRouting(), $this->tables);
157
		
158
		return [ 
159
			'class' => explode('\\', $supervisor->getExceptionName()),
160
			'stylesheet' => preg_replace('#[\r\n\t ]+#', ' ', $style),
161
			'javascript' => preg_replace('#[\r\n\t ]+#', ' ', $jscript),
162
			'header' => $this->getResource('views/partials/updown/header.php'),
163
			'footer' => $this->getResource('views/partials/updown/footer.php'),
164
			'info_exception' => $this->getResource('views/partials/info/info_exception.php'),
165
			'section_stack_exception' => $this->getResource('views/partials/section_stack_exception.php'),
166
			'section_frame' => $this->getResource('views/partials/section_frame.php'),
167
			'frame_description' => $this->getResource('views/partials/frames/frame_description.php'),
168
			'frame_list' => $this->getResource('views/partials/frames//frame_list.php'),
169
			'section_code' => $this->getResource('views/partials/section_code.php'),
170
			'code_source' => $this->getResource('views/partials/codes/code_source.php'),
171
			'request_info' => $this->getResource('views/partials/request_info.php'),
172
			'navigation' => $this->getResource('views/partials/details/navigation.php'),
173
			'section_detail_context' => $this->getResource('views/partials/details/section_detail_context.php'),
174
			'plain_exception' => Formatter::formatExceptionAsPlainText($this->getSupervisor()),
175
			'handler' => $this,
176
			'handlers' => $this->getDebug()->getHandlers(),
177
			'debug' => $this->getDebug(),
178
			'code' => $this->getExceptionCode(),
179
			'message' => $supervisor->getExceptionMessage(),
180
			'frames' => $this->getExceptionFrames(),
181
			'servers' => $this->getProcessTables($servers),
182
			'routes' => $this->getProcessTables($routing),
183
		];
184
	}
185
	
186
	/**
187
	 * The way in which the data sender (usually the server) can tell the recipient
188
	 * (the browser, in general) what type of data is being sent in this case, html format tagged.
189
	 * 
190
	 * @return string
191
	 */
192
	public function contentType(): string
193
	{
194
		return 'text/html;charset=UTF-8';
195
	}
196
197
	/**
198
	 * Gets the brand of project.
199
	 * 
200
	 * @return string
201
	 */
202
	public function getBrand(): string
203
	{
204
		return $this->brand;
205
	}
206
207
	/**
208
	 * Returns the default servers.
209
	 * 
210
	 * @return \Syscodes\Components\Contracts\Debug\Table[]
211
	 */
212
	protected function getDefaultServers()
213
	{
214
		$server = [
215
			'host' => $_SERVER['HTTP_HOST'], 
216
			'user-agent' => $_SERVER['HTTP_USER_AGENT'], 
217
			'accept' => $_SERVER['HTTP_ACCEPT'], 
218
			'accept-language' => $_SERVER['HTTP_ACCEPT_LANGUAGE'], 
219
			'accept-encoding' => $_SERVER['HTTP_ACCEPT_ENCODING'],
220
		    'connection' => $_SERVER['HTTP_CONNECTION'],
221
			'upgrade-insecure-requests' => $_SERVER['HTTP_UPGRADE_INSECURE_REQUESTS'], 
222
			'sec-fetch-dest' => $_SERVER['HTTP_SEC_FETCH_DEST'],
223
			'sec-fetch-mode' => $_SERVER['HTTP_SEC_FETCH_MODE'],
224
			'sec-fetch-site' => $_SERVER['HTTP_SEC_FETCH_SITE'],
225
			'sec-fetch-user' => $_SERVER['HTTP_SEC_FETCH_USER'],
226
		];
227
228
		return [new ArrayTable($server)];
229
	}
230
231
	/**
232
	 * Returns the default routing.
233
	 * 
234
	 * @return \Syscodes\Components\Contracts\Debug\Table[]
235
	 */
236
	protected function getDefaultRouting()
237
	{
238
		$routing = [
239
			'middleware' => config('routes.routes'),
240
		];
241
242
		return [new ArrayTable($routing)];
243
	}
244
245
	/**
246
	 * Get the code of the exception that is currently being handled.
247
	 * 
248
	 * @return string
249
	 */
250
	protected function getExceptionCode()
251
	{
252
		$exception = $this->getException();
253
		$code      = $exception->getCode();
254
255
		if ($exception instanceof ErrorException) {
256
			$code = Misc::translateErrorCode($exception->getSeverity());
257
		}
258
259
		return (string) $code;
260
	}
261
262
	/**
263
	 * Get the stack trace frames of the exception that is currently being handled.
264
	 * 
265
	 * @return \Syscodes\Components\Debug\Engine\Supervisor;
0 ignored issues
show
Bug introduced by
The type Syscodes\Components\Debug\Engine\Supervisor was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
266
	 */
267
	protected function getExceptionFrames()
268
	{
269
		$frames = $this->getSupervisor()->getFrames();
270
		
271
		return $frames;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $frames returns the type array which is incompatible with the documented return type Syscodes\Components\Debug\Engine\Supervisor.
Loading history...
272
	}
273
	
274
	/**
275
	 * Gets the page title web.
276
	 * 
277
	 * @return string
278
	 */
279
	public function getPageTitle(): string
280
	{
281
		return $this->pageTitle;
282
	}
283
284
	/**
285
	 * Processes an array of tables making sure everything is all right.
286
	 * 
287
	 * @param  \Syscodes\Components\Contracts\Debug\Table[]  $tables
288
	 * 
289
	 * @return array
290
	 */
291
	protected function getProcessTables(array $tables): array
292
	{
293
		$processTables = [];
294
295
		foreach ($tables as $table) {
296
			if ( ! $table instanceof Table) {
297
				continue;
298
			}
299
			
300
			$label = $table->getLabel();
301
302
			try {
303
				$data = (array) $table->getData();
304
305
				if ( ! (is_array($data) || $data instanceof Traversable)) {
306
					$data = [];
307
				}
308
			} catch (Exception $e) {
309
				$data = [];
310
			}
311
312
			$processTables[$label] = $data;
313
		}
314
315
		return $processTables;
316
	}
317
318
	/**
319
	 * Finds a resource, by its relative path, in all available search paths.
320
	 *
321
	 * @param  string  $resource
322
	 * 
323
	 * @return string
324
	 * 
325
	 * @throws \RuntimeException
326
	 */
327
	protected function getResource($resource)
328
	{
329
		if (isset($this->resourceCache[$resource])) {
330
			return $this->resourceCache[$resource];
331
		}
332
333
		foreach ($this->searchPaths as $path) {
334
			$fullPath = $path.DIRECTORY_SEPARATOR.$resource;
335
336
			if (is_file($fullPath)) {
337
				// Cache:
338
				$this->resourceCache[$resource] = $fullPath;
339
340
				return $fullPath;
341
			}
342
		}
343
344
		throw new RuntimeException( 
345
				"Could not find resource '{$resource}' in any resource paths.". 
346
				"(searched: ".join(", ", $this->searchPaths).")");
347
	}
348
	
349
	/**
350
	 * Given an exception and status code will display the error to the client.
351
	 * 
352
	 * @return int|null
353
	 */
354
	public function handle()
355
	{	
356
		$templatePath = $this->getResource('views/debug.layout.php');
357
358
		$vars = $this->collectionVars();
359
		
360
		if (empty($vars['message'])) $vars['message'] = __('exception.noMessage');
361
		
362
		$this->template->setVariables($vars);
363
		$this->template->render($templatePath);
364
		
365
		return MainHandler::QUIT;
366
	}
367
368
	/**
369
	 * Set the editor to use to open referenced files, by a string identifier or callable
370
	 * that will be executed for every file reference. Should return a string.
371
	 * 
372
	 * @example  $debug->setEditor(function($file, $line) { return "file:///{$file}"; });
373
	 * @example  $debug->setEditor('vscode');
374
	 * 
375
	 * @param  string  $editor
376
	 * 
377
	 * @return void
378
	 * 
379
	 * @throws \InvalidArgumentException
380
	 */
381
	public function setEditor($editor)
382
	{
383
		if ( ! is_callable($editor) && ! isset($this->editors[$editor])) {
384
			throw new InvalidArgumentException("Unknown editor identifier: [{$editor}]. Known editors: " .
385
				implode(', ', array_keys($this->editors))
386
			);
387
		}
388
389
		$this->editor = $editor;
390
	}
391
392
	/**
393
	 * Given a string file path, and an integer file line,
394
	 * executes the editor resolver and returns.
395
	 * 
396
	 * @param  string  $file
397
	 * @param  int	   $line
398
	 * 
399
	 * @return string|bool
400
	 * 
401
	 * @throws \UnexpectedValueException
402
	 */
403
	public function getEditorAtHref($file, $line)
404
	{
405
		$editor = $this->getEditor($file, $line);
406
407
		if (empty($editor))	{
408
			return false;
409
		}
410
411
		if ( ! isset($editor['url']) || ! is_string($editor['url'])) {
412
			throw new UnexpectedValueException(__METHOD__.'should always resolve to a string or a valid editor array');
413
		}
414
415
		$editor['url'] = str_replace("%file", rawurldecode($file), $editor['url']);
416
		$editor['url'] = str_replace("%line", rawurldecode($line), $editor['url']);
417
418
		return $editor['url'];
419
	}
420
421
	/**
422
	 * The editor must be a valid callable function/closure.
423
	 * 
424
	 * @param  string  $file
425
	 * @param  int	   $line
426
	 * 
427
	 * @return array
428
	 */
429
	protected function getEditor($file, $line): array
430
	{
431
		if ( ! $this->editor || ( ! is_string($this->editor) && ! is_callable($this->editor))) {
432
			return [];
433
		}
434
435
		if (is_string($this->editor) && isset($this->editors[$this->editor]) && ! is_callable($this->editors[$this->editor])) {
436
			return ['url' => $this->editors[$this->editor]];
437
		}
438
439
		if (is_callable($this->editor) || (isset($this->editors[$this->editor]) && is_callable($this->editors[$this->editor]))) {
440
			if (is_callable($this->editor)) {
441
				$callback = call_user_func($this->editor, $file, $line);
442
			} else {
443
				$callback = call_user_func($this->editors[$this->editor], $file, $line);
444
			}
445
446
			if (empty($callback)) {
447
				return [];
448
			}
449
450
			if (is_string($callback)) {
451
				return ['url' => $callback];
452
			}
453
			
454
			return ['url' => isset($callback['url']) ? $callback['url'] : $callback];
455
		}
456
		
457
		return [];
458
	}
459
460
	/**
461
	 * Registered the editor.
462
	 * 
463
	 * @return string
464
	 */
465
	public function getEditorcode(): string
466
	{
467
		return $this->editor;
468
	}
469
	
470
	/**
471
	 * Sets the brand of project.
472
	 * 
473
	 * @param  string  $brand
474
	 * 
475
	 * @return void
476
	 */
477
	public function setBrand($brand): void
478
	{
479
		$this->brand = (string) $brand;
480
	}
481
	
482
	/**
483
	 * Sets the page title web.
484
	 * 
485
	 * @param  string  $title
486
	 * 
487
	 * @return void
488
	 */
489
	public function setPageTitle($title): void
490
	{
491
		$this->pageTitle = (string) $title;
492
	}
493
}