Passed
Push — master ( a3e128...d0d3a9 )
by Alexander
03:09
created

PleasingPageHandler::getDefaultContext()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 7
c 0
b 0
f 0
nc 2
nop 0
dl 0
loc 11
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
		$context    = array_merge($this->getDefaultContext(), $this->tables);
158
		
159
		return [ 
160
			'class' => explode('\\', $supervisor->getExceptionName()),
161
			'stylesheet' => preg_replace('#[\r\n\t ]+#', ' ', $style),
162
			'javascript' => preg_replace('#[\r\n\t ]+#', ' ', $jscript),
163
			'header' => $this->getResource('views/partials/updown/header.php'),
164
			'footer' => $this->getResource('views/partials/updown/footer.php'),
165
			'info_exception' => $this->getResource('views/partials/info/info_exception.php'),
166
			'section_stack_exception' => $this->getResource('views/partials/section_stack_exception.php'),
167
			'section_frame' => $this->getResource('views/partials/section_frame.php'),
168
			'frame_description' => $this->getResource('views/partials/frames/frame_description.php'),
169
			'frame_list' => $this->getResource('views/partials/frames//frame_list.php'),
170
			'section_code' => $this->getResource('views/partials/section_code.php'),
171
			'code_source' => $this->getResource('views/partials/codes/code_source.php'),
172
			'request_info' => $this->getResource('views/partials/request_info.php'),
173
			'navigation' => $this->getResource('views/partials/details/navigation.php'),
174
			'section_detail_context' => $this->getResource('views/partials/details/section_detail_context.php'),
175
			'plain_exception' => Formatter::formatExceptionAsPlainText($this->getSupervisor()),
176
			'handler' => $this,
177
			'handlers' => $this->getDebug()->getHandlers(),
178
			'debug' => $this->getDebug(),
179
			'code' => $this->getExceptionCode(),
180
			'message' => $supervisor->getExceptionMessage(),
181
			'frames' => $this->getExceptionFrames(),
182
			'servers' => $this->getProcessTables($servers),
183
			'routes' => $this->getProcessTables($routing),
184
			'contexts' => $this->getProcessTables($context),
185
		];
186
	}
187
	
188
	/**
189
	 * The way in which the data sender (usually the server) can tell the recipient
190
	 * (the browser, in general) what type of data is being sent in this case, html format tagged.
191
	 * 
192
	 * @return string
193
	 */
194
	public function contentType(): string
195
	{
196
		return 'text/html;charset=UTF-8';
197
	}
198
199
	/**
200
	 * Gets the brand of project.
201
	 * 
202
	 * @return string
203
	 */
204
	public function getBrand(): string
205
	{
206
		return $this->brand;
207
	}
208
209
	/**
210
	 * Returns the default servers.
211
	 * 
212
	 * @return \Syscodes\Components\Contracts\Debug\Table[]
213
	 */
214
	protected function getDefaultServers()
215
	{
216
		$server = [
217
			'host' => $_SERVER['HTTP_HOST'], 
218
			'user-agent' => $_SERVER['HTTP_USER_AGENT'], 
219
			'accept' => $_SERVER['HTTP_ACCEPT'], 
220
			'accept-language' => $_SERVER['HTTP_ACCEPT_LANGUAGE'], 
221
			'accept-encoding' => $_SERVER['HTTP_ACCEPT_ENCODING'],
222
			'connection' => $_SERVER['HTTP_CONNECTION'],
223
			'upgrade-insecure-requests' => $_SERVER['HTTP_UPGRADE_INSECURE_REQUESTS'], 
224
			'sec-fetch-dest' => $_SERVER['HTTP_SEC_FETCH_DEST'],
225
			'sec-fetch-mode' => $_SERVER['HTTP_SEC_FETCH_MODE'],
226
			'sec-fetch-site' => $_SERVER['HTTP_SEC_FETCH_SITE'],
227
			'sec-fetch-user' => $_SERVER['HTTP_SEC_FETCH_USER'],
228
		];
229
230
		return [new ArrayTable($server)];
231
	}
232
233
	/**
234
	 * Returns the default routing.
235
	 * 
236
	 * @return \Syscodes\Components\Contracts\Debug\Table[]
237
	 */
238
	protected function getDefaultRouting()
239
	{
240
		$action = app('request')->route()->isControllerAction() 
0 ignored issues
show
Bug introduced by
The method route() does not exist on Syscodes\Components\Contracts\Core\Application. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

240
		$action = app('request')->/** @scrutinizer ignore-call */ route()->isControllerAction() 

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
241
		          ? app('request')->route()->parseControllerCallback()[0] 
242
		          : 'Closure';
243
244
		$index = match (true) {
245
			array_key_exists('web', app('router')->getMiddlewareGroups()) => 0,
0 ignored issues
show
Bug introduced by
The method getMiddlewareGroups() does not exist on Syscodes\Components\Contracts\Core\Application. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

245
			array_key_exists('web', app('router')->/** @scrutinizer ignore-call */ getMiddlewareGroups()) => 0,

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
246
			array_key_exists('api', app('router')->getMiddlewareGroups()) => 1,
247
		};
248
249
		$routing = [
250
			'Controller' => $action,
251
			'Middleware' => array_keys(app('router')->getMiddlewareGroups())[$index],
252
		];
253
254
		return [new ArrayTable($routing)];
255
	}
256
257
	/**
258
	 * Returns the default context data.
259
	 * 
260
	 * @return \Syscodes\Components\Contracts\Debug\Table[]
261
	 */
262
	protected function getDefaultContext()
263
	{
264
		$context = [
265
			'Php Version' => PHP_VERSION,
266
			'Lenevor Version' => app()->version(),
267
			'Lenevor Locale' => config('app.locale'),
268
			'App Debug' => (1 == env('APP_DEBUG') ? 'True' : 'False'),
269
			'App Env' => env('APP_ENV'),
270
		];
271
272
		return [new ArrayTable($context)];
273
	}
274
275
	/**
276
	 * Get the code of the exception that is currently being handled.
277
	 * 
278
	 * @return string
279
	 */
280
	protected function getExceptionCode()
281
	{
282
		$exception = $this->getException();
283
		$code      = $exception->getCode();
284
285
		if ($exception instanceof ErrorException) {
286
			$code = Misc::translateErrorCode($exception->getSeverity());
287
		}
288
289
		return (string) $code;
290
	}
291
292
	/**
293
	 * Get the stack trace frames of the exception that is currently being handled.
294
	 * 
295
	 * @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...
296
	 */
297
	protected function getExceptionFrames()
298
	{
299
		$frames = $this->getSupervisor()->getFrames();
300
		
301
		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...
302
	}
303
	
304
	/**
305
	 * Gets the page title web.
306
	 * 
307
	 * @return string
308
	 */
309
	public function getPageTitle(): string
310
	{
311
		return $this->pageTitle;
312
	}
313
314
	/**
315
	 * Processes an array of tables making sure everything is all right.
316
	 * 
317
	 * @param  \Syscodes\Components\Contracts\Debug\Table[]  $tables
318
	 * 
319
	 * @return array
320
	 */
321
	protected function getProcessTables(array $tables): array
322
	{
323
		$processTables = [];
324
325
		foreach ($tables as $table) {
326
			if ( ! $table instanceof Table) {
327
				continue;
328
			}
329
			
330
			$label = $table->getLabel();
331
332
			try {
333
				$data = (array) $table->getData();
334
335
				if ( ! (is_array($data) || $data instanceof Traversable)) {
336
					$data = [];
337
				}
338
			} catch (Exception $e) {
339
				$data = [];
340
			}
341
342
			$processTables[$label] = $data;
343
		}
344
345
		return $processTables;
346
	}
347
348
	/**
349
	 * Finds a resource, by its relative path, in all available search paths.
350
	 *
351
	 * @param  string  $resource
352
	 * 
353
	 * @return string
354
	 * 
355
	 * @throws \RuntimeException
356
	 */
357
	protected function getResource($resource)
358
	{
359
		if (isset($this->resourceCache[$resource])) {
360
			return $this->resourceCache[$resource];
361
		}
362
363
		foreach ($this->searchPaths as $path) {
364
			$fullPath = $path.DIRECTORY_SEPARATOR.$resource;
365
366
			if (is_file($fullPath)) {
367
				// Cache:
368
				$this->resourceCache[$resource] = $fullPath;
369
370
				return $fullPath;
371
			}
372
		}
373
374
		throw new RuntimeException( 
375
				"Could not find resource '{$resource}' in any resource paths.". 
376
				"(searched: ".join(", ", $this->searchPaths).")");
377
	}
378
	
379
	/**
380
	 * Given an exception and status code will display the error to the client.
381
	 * 
382
	 * @return int|null
383
	 */
384
	public function handle()
385
	{	
386
		$templatePath = $this->getResource('views/debug.layout.php');
387
388
		$vars = $this->collectionVars();
389
		
390
		if (empty($vars['message'])) $vars['message'] = __('exception.noMessage');
391
		
392
		$this->template->setVariables($vars);
393
		$this->template->render($templatePath);
394
		
395
		return MainHandler::QUIT;
396
	}
397
398
	/**
399
	 * Set the editor to use to open referenced files, by a string identifier or callable
400
	 * that will be executed for every file reference. Should return a string.
401
	 * 
402
	 * @example  $debug->setEditor(function($file, $line) { return "file:///{$file}"; });
403
	 * @example  $debug->setEditor('vscode');
404
	 * 
405
	 * @param  string  $editor
406
	 * 
407
	 * @return void
408
	 * 
409
	 * @throws \InvalidArgumentException
410
	 */
411
	public function setEditor($editor)
412
	{
413
		if ( ! is_callable($editor) && ! isset($this->editors[$editor])) {
414
			throw new InvalidArgumentException("Unknown editor identifier: [{$editor}]. Known editors: " .
415
				implode(', ', array_keys($this->editors))
416
			);
417
		}
418
419
		$this->editor = $editor;
420
	}
421
422
	/**
423
	 * Given a string file path, and an integer file line,
424
	 * executes the editor resolver and returns.
425
	 * 
426
	 * @param  string  $file
427
	 * @param  int	   $line
428
	 * 
429
	 * @return string|bool
430
	 * 
431
	 * @throws \UnexpectedValueException
432
	 */
433
	public function getEditorAtHref($file, $line)
434
	{
435
		$editor = $this->getEditor($file, $line);
436
437
		if (empty($editor))	{
438
			return false;
439
		}
440
441
		if ( ! isset($editor['url']) || ! is_string($editor['url'])) {
442
			throw new UnexpectedValueException(__METHOD__.'should always resolve to a string or a valid editor array');
443
		}
444
445
		$editor['url'] = str_replace("%file", rawurldecode($file), $editor['url']);
446
		$editor['url'] = str_replace("%line", rawurldecode($line), $editor['url']);
447
448
		return $editor['url'];
449
	}
450
451
	/**
452
	 * The editor must be a valid callable function/closure.
453
	 * 
454
	 * @param  string  $file
455
	 * @param  int	   $line
456
	 * 
457
	 * @return array
458
	 */
459
	protected function getEditor($file, $line): array
460
	{
461
		if ( ! $this->editor || ( ! is_string($this->editor) && ! is_callable($this->editor))) {
462
			return [];
463
		}
464
465
		if (is_string($this->editor) && isset($this->editors[$this->editor]) && ! is_callable($this->editors[$this->editor])) {
466
			return ['url' => $this->editors[$this->editor]];
467
		}
468
469
		if (is_callable($this->editor) || (isset($this->editors[$this->editor]) && is_callable($this->editors[$this->editor]))) {
470
			if (is_callable($this->editor)) {
471
				$callback = call_user_func($this->editor, $file, $line);
472
			} else {
473
				$callback = call_user_func($this->editors[$this->editor], $file, $line);
474
			}
475
476
			if (empty($callback)) {
477
				return [];
478
			}
479
480
			if (is_string($callback)) {
481
				return ['url' => $callback];
482
			}
483
			
484
			return ['url' => isset($callback['url']) ? $callback['url'] : $callback];
485
		}
486
		
487
		return [];
488
	}
489
490
	/**
491
	 * Registered the editor.
492
	 * 
493
	 * @return string
494
	 */
495
	public function getEditorcode(): string
496
	{
497
		return $this->editor;
498
	}
499
	
500
	/**
501
	 * Sets the brand of project.
502
	 * 
503
	 * @param  string  $brand
504
	 * 
505
	 * @return void
506
	 */
507
	public function setBrand($brand): void
508
	{
509
		$this->brand = (string) $brand;
510
	}
511
	
512
	/**
513
	 * Sets the page title web.
514
	 * 
515
	 * @param  string  $title
516
	 * 
517
	 * @return void
518
	 */
519
	public function setPageTitle($title): void
520
	{
521
		$this->pageTitle = (string) $title;
522
	}
523
}