Passed
Push — master ( 6e510b...88282a )
by Alexander
05:33 queued 01:38
created

PleasingPageHandler::getTheme()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
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 Handler
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
		"netbeans" => "netbeans://open/?f=%file:%line",
66
		"idea"     => "idea://open?file=%file&line=%line",
67
		"sublime"  => "subl://open?url=file://%file&line=%line",
68
		"phpstorm" => "phpstorm://open?file://%file&line=%line",
69
		"textmate" => "txmt://open?url=file://%file&line=%line",
70
		"emacs"    => "emacs://open?url=file://%file&line=%line",
71
        "macvim"   => "mvim://open/?url=file://%file&line=%line",
72
		"atom"     => "atom://core/open/file?filename=%file&line=%line",
73
	];
74
	
75
	/**
76
	 * The page title main of handler.
77
	 * 
78
	 * @var string $pageTitle
79
	 */
80
	protected $pageTitle = 'Lenevor Debug! There was an error';
81
	
82
	/**
83
	 * Fast lookup cache for known resource locations.
84
	 * 
85
	 * @var array $resourceCache
86
	 */
87
	protected $resourceCache = [];
88
	
89
	/**
90
	 * The path to the directory containing the html error template directories.
91
	 * 
92
	 * @var array $searchPaths
93
	 */
94
	protected $searchPaths = [];
95
96
	/**
97
	 * Gets the table of data.
98
	 * 
99
	 * @var array $tables
100
	 */
101
	protected $tables = [];
102
103
	/**
104
	 * Gets the theme default.
105
	 * 
106
	 * @var string $theme
107
	 */
108
	protected $theme;
109
	
110
	/**
111
	 * The template handler system.
112
	 * 
113
	 * @var string|object $template
114
	 */
115
	protected $template;	
116
	
117
	/**
118
	 * Constructor. The PleasingPageHandler class.
119
	 * 
120
	 * @return void
121
	 */
122
	public function __construct()
123
	{
124
		$this->template      = new TemplateHandler;
125
		$this->searchPaths[] = dirname(dirname(__DIR__)).DIRECTORY_SEPARATOR.'Resources';
126
	}
127
128
	/**
129
	 * Adds an editor resolver, identified by a string name, and that may be a 
130
	 * string path, or a callable resolver.
131
	 * 
132
	 * @param  string            $identifier
133
	 * @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...
134
	 * 
135
	 * @return void
136
	 */
137
	public function addEditor($identifier, $resolver): void
138
	{
139
		$this->editors[$identifier] = $resolver;
140
	}
141
142
	/**
143
	 * Adds an entry to the list of tables displayed in the template.
144
	 * The expected data is a simple associative array. Any nested arrays
145
	 * will be flattened with print_r.
146
	 * 
147
	 * @param  \Syscodes\Components\Contracts\Debug\Table  $table
148
	 * 
149
	 * @return void
150
	 */
151
	public function addTables(Table $table): void
152
	{
153
		$this->tables[] = $table;
154
	}
155
	
156
	/**
157
	 * Gathers the variables that will be made available to the view.
158
	 * 
159
	 * @return  array
160
	 */
161
	protected function collectionVars(): array
162
	{
163
		$supervisor = $this->getSupervisor();
164
		$style      = file_get_contents($this->getResource('compiled/css/debug.base.css'));
165
		$jscript    = file_get_contents($this->getResource('compiled/js/debug.base.js'));
166
		$servers    = array_merge($this->getDefaultServers(), $this->tables);
167
		$routing    = array_merge($this->getDefaultRouting(), $this->tables);
168
		$databases  = array_merge($this->getDefaultDatabase(), $this->tables);
169
		$context    = array_merge($this->getDefaultContext(), $this->tables);
170
		
171
		return [ 
172
			'class' => explode('\\', $supervisor->getExceptionName()),
173
			'stylesheet' => preg_replace('#[\r\n\t ]+#', ' ', $style),
174
			'javascript' => preg_replace('#[\r\n\t ]+#', ' ', $jscript),
175
			'header' => $this->getResource('views/partials/updown/header.php'),
176
			'footer' => $this->getResource('views/partials/updown/footer.php'),
177
			'info_exception' => $this->getResource('views/partials/info/info_exception.php'),
178
			'section_stack_exception' => $this->getResource('views/partials/section_stack_exception.php'),
179
			'section_frame' => $this->getResource('views/partials/section_frame.php'),
180
			'frame_description' => $this->getResource('views/partials/frames/frame_description.php'),
181
			'frame_list' => $this->getResource('views/partials/frames/frame_list.php'),
182
			'section_code' => $this->getResource('views/partials/section_code.php'),
183
			'code_source' => $this->getResource('views/partials/codes/code_source.php'),
184
			'request_info' => $this->getResource('views/partials/request_info.php'),
185
			'navigation' => $this->getResource('views/components/navBar.php'),
186
			'settings' => $this->getResource('views/components/settingsDropdown.php'),
187
			'section_detail_context' => $this->getResource('views/partials/details/section_detail_context.php'),
188
			'plain_exception' => Formatter::formatExceptionAsPlainText($this->getSupervisor()),
189
			'handler' => $this,
190
			'handlers' => $this->getDebug()->getHandlers(),
191
			'debug' => $this->getDebug(),
192
			'code' => $this->getExceptionCode(),
193
			'message' => $supervisor->getExceptionMessage(),
194
			'frames' => $this->getExceptionFrames(),
195
			'servers' => $this->getProcessTables($servers),
196
			'routes' => $this->getProcessTables($routing),
197
			'databases' => $this->getProcessTables($databases),
198
			'contexts' => $this->getProcessTables($context),
199
		];
200
	}
201
	
202
	/**
203
	 * The way in which the data sender (usually the server) can tell the recipient
204
	 * (the browser, in general) what type of data is being sent in this case, html format tagged.
205
	 * 
206
	 * @return string
207
	 */
208
	public function contentType(): string
209
	{
210
		return 'text/html;charset=UTF-8';
211
	}
212
213
	/**
214
	 * Gets the brand of project.
215
	 * 
216
	 * @return string
217
	 */
218
	public function getBrand(): string
219
	{
220
		return $this->brand;
221
	}
222
223
	/**
224
	 * Returns the default servers.
225
	 * 
226
	 * @return \Syscodes\Components\Contracts\Debug\Table[]
227
	 */
228
	protected function getDefaultServers()
229
	{
230
		$server = [
231
			'host' => $_SERVER['HTTP_HOST'], 
232
			'user-agent' => $_SERVER['HTTP_USER_AGENT'], 
233
			'accept' => $_SERVER['HTTP_ACCEPT'], 
234
			'accept-language' => $_SERVER['HTTP_ACCEPT_LANGUAGE'], 
235
			'accept-encoding' => $_SERVER['HTTP_ACCEPT_ENCODING'],
236
			'connection' => $_SERVER['HTTP_CONNECTION'],
237
			'upgrade-insecure-requests' => $_SERVER['HTTP_UPGRADE_INSECURE_REQUESTS'], 
238
			'sec-fetch-dest' => $_SERVER['HTTP_SEC_FETCH_DEST'],
239
			'sec-fetch-mode' => $_SERVER['HTTP_SEC_FETCH_MODE'],
240
			'sec-fetch-site' => $_SERVER['HTTP_SEC_FETCH_SITE'],
241
			'sec-fetch-user' => $_SERVER['HTTP_SEC_FETCH_USER'],
242
		];
243
244
		return [new ArrayTable($server)];
245
	}
246
247
	/**
248
	 * Returns the default routing.
249
	 * 
250
	 * @return \Syscodes\Components\Contracts\Debug\Table[]
251
	 */
252
	protected function getDefaultRouting()
253
	{
254
		$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

254
		$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...
255
		          ? app('request')->route()->parseControllerCallback()[0] 
256
		          : 'Closure';
257
258
		$index = match (true) {
259
			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

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

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
538
	}
539
	
540
	/**
541
	 * Sets the brand of project.
542
	 * 
543
	 * @param  string  $brand
544
	 * 
545
	 * @return void
546
	 */
547
	public function setBrand(string $brand): void
548
	{
549
		$this->brand = (string) $brand;
550
	}
551
	
552
	/**
553
	 * Sets the page title web.
554
	 * 
555
	 * @param  string  $title
556
	 * 
557
	 * @return void
558
	 */
559
	public function setPageTitle(string $title): void
560
	{
561
		$this->pageTitle = (string) $title;
562
	}
563
564
	/**
565
	 * Set the theme manually.
566
	 * 
567
	 * @param  string  $theme
568
	 * 
569
	 * @return void
570
	 */
571
	public function setTheme(string $theme): void
572
	{
573
		$this->theme = (string) $theme;
574
	}
575
}