Request::get()   B
last analyzed

Complexity

Conditions 8
Paths 8

Size

Total Lines 21
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 72

Importance

Changes 0
Metric Value
eloc 13
dl 0
loc 21
ccs 0
cts 7
cp 0
rs 8.4444
c 0
b 0
f 0
cc 8
nc 8
nop 2
crap 72
1
<?php
2
/**
3
 * Request basic class.
4
 *
5
 * @package App
6
 *
7
 * @copyright YetiForce S.A.
8
 * @license   YetiForce Public License 6.5 (licenses/LicenseEN.txt or yetiforce.com)
9
 * @author    Mariusz Krzaczkowski <[email protected]>
10
 * @author    Radosław Skrzypczak <[email protected]>
11
 */
12
13
namespace App;
14
15
/**
16
 * Request basic class.
17
 */
18
class Request
19
{
20
	/**
21
	 * Raw request data.
22
	 *
23
	 * @var array
24
	 */
25
	protected $rawValues = [];
26
27
	/**
28
	 * Headers request.
29
	 *
30
	 * @var array
31
	 */
32
	protected $headers;
33
34
	/**
35
	 * Self instance.
36
	 *
37
	 * @var Request
38
	 */
39
	protected static $request;
40
41
	/**
42
	 * Purified request values for get.
43
	 *
44
	 * @var array
45
	 */
46
	protected $purifiedValuesByGet = [];
47
48
	/**
49
	 * Purified request values for type.
50
	 *
51
	 * @var array
52
	 */
53
	protected $purifiedValuesByType = [];
54
55
	/**
56
	 * Purified request values for integer.
57
	 *
58
	 * @var array
59
	 */
60
	protected $purifiedValuesByInteger = [];
61
62
	/**
63
	 * Purified request values for array.
64
	 *
65
	 * @var array
66
	 */
67
	protected $purifiedValuesByArray = [];
68
69
	/**
70
	 * Purified request values for exploded.
71
	 *
72
	 * @var array
73
	 */
74
	protected $purifiedValuesByExploded = [];
75
76
	/**
77
	 * Purified request values for multi dimension array.
78
	 *
79
	 * @var array
80
	 */
81
	protected $purifiedValuesByMultiDimension = [];
82
83
	/**
84
	 * Purified request values for date range.
85
	 *
86
	 * @var array
87
	 */
88
	protected $purifiedValuesByDateRange = [];
89
90
	/**
91
	 * Purified request values for date html.
92
	 *
93
	 * @var array
94
	 */
95
	protected $purifiedValuesByHtml = [];
96
	/**
97
	 * List of headings and sanitization methods.
98
	 *
99
	 * @var array
100 16
	 */
101
	public $headersPurifierMap = [
102 16
	];
103 16
104
	/**
105
	 * Constructor.
106 16
	 *
107
	 * @param array $rawValues
108
	 * @param bool  $overwrite
109
	 */
110
	public function __construct($rawValues, $overwrite = true)
111
	{
112
		$this->rawValues = $rawValues;
113
		if ($overwrite) {
114
			static::$request = $this;
115
		}
116 38
	}
117
118 38
	/**
119
	 * Function to get the value for a given key.
120
	 *
121 38
	 * @param string $key
122
	 * @param mixed  $value Default value
123
	 *
124 38
	 * @return mixed
125
	 */
126
	public function get($key, $value = '')
127
	{
128
		if (isset($this->purifiedValuesByGet[$key])) {
129
			return $this->purifiedValuesByGet[$key];
130
		}
131
		if (isset($this->rawValues[$key])) {
132
			$value = $this->rawValues[$key];
133
		} else {
134
			return $value;
135
		}
136
		if (\is_string($value) && (0 === strpos($value, '[') || 0 === strpos($value, '{'))) {
137
			$decodeValue = Json::decode($value);
138
			if (isset($decodeValue)) {
139
				$value = $decodeValue;
140
			}
141
		}
142
		if ($value) {
143
			$value = Purifier::purify($value);
144
		}
145
146
		return $this->purifiedValuesByGet[$key] = $value;
147
	}
148
149
	/**
150
	 * Purify by data type.
151
	 *
152
	 * Type list:
153
	 * Standard - only words
154 15
	 * 1 - only words
155
	 * Alnum - word and int
156 15
	 * 2 - word and int
157
	 *
158
	 * @param string     $key     Key name
159 15
	 * @param int|string $type    Data type that is only acceptable, default only words 'Standard'
160 15
	 * @param mixed      $convert
161
	 *
162
	 * @return bool|mixed
163
	 */
164
	public function getByType($key, $type = 'Standard', $convert = false)
165
	{
166
		if (isset($this->purifiedValuesByType[$key][$type])) {
167
			return $this->purifiedValuesByType[$key][$type];
168
		}
169
		if (isset($this->rawValues[$key])) {
170
			return $this->purifiedValuesByType[$key][$type] = Purifier::purifyByType($this->rawValues[$key], $type, $convert);
171
		}
172
		return false;
173
	}
174
175
	/**
176
	 * Function to get the boolean value for a given key.
177
	 *
178
	 * @param string $key
179
	 * @param bool   $defaultValue Default value
180
	 *
181
	 * @return bool
182
	 */
183
	public function getBoolean(string $key, bool $defaultValue = null)
184
	{
185
		$value = $this->get($key, $defaultValue);
186
		if (\is_bool($value)) {
187
			return $value;
188
		}
189
		return 0 === strcasecmp('true', (string) $value) || '1' === (string) $value;
190
	}
191
192
	/**
193
	 * Function to get the integer value for a given key.
194
	 *
195
	 * @param string $key
196
	 * @param int    $value
197
	 *
198
	 * @return int
199
	 */
200
	public function getInteger($key, $value = 0)
201
	{
202
		if (isset($this->purifiedValuesByInteger[$key])) {
203
			return $this->purifiedValuesByInteger[$key];
204
		}
205
		if (!isset($this->rawValues[$key])) {
206
			return $value;
207
		}
208
		if (false !== ($value = filter_var($this->rawValues[$key], FILTER_VALIDATE_INT))) {
209
			return $this->purifiedValuesByInteger[$key] = $value;
210
		}
211
212
		throw new \App\Exceptions\IllegalValue("ERR_NOT_ALLOWED_VALUE||$key||{$this->rawValues[$key]}", 406);
213
	}
214
215
	/**
216
	 * Function to get the array values for a given key.
217
	 *
218
	 * @param string      $key
219
	 * @param mixed       $type
220
	 * @param array       $value
221
	 * @param string|null $keyType
222
	 *
223
	 * @return array
224
	 */
225
	public function getArray($key, $type = false, $value = [], ?string $keyType = null)
226
	{
227
		if (isset($this->purifiedValuesByArray[$key])) {
228
			return $this->purifiedValuesByArray[$key];
229
		}
230
		if (isset($this->rawValues[$key])) {
231
			$value = $this->rawValues[$key];
232
			if (!$value) {
233
				return [];
234
			}
235
			if (\is_string($value) && (0 === strpos($value, '[') || 0 === strpos($value, '{'))) {
236
				$decodeValue = Json::decode($value);
237
				if (isset($decodeValue)) {
238
					$value = $decodeValue;
239
				} else {
240
					\App\Log::warning('Invalid data format, problem encountered while decoding JSON. Data should be in JSON format. Data: ' . $value);
241
				}
242
			}
243
			if ($value) {
244
				if (\is_array($value)) {
245
					$input = [];
246
					foreach ($value as $k => $v) {
247
						if (!\is_int($k)) {
248
							$k = $keyType ? Purifier::purifyByType($k, $keyType) : Purifier::purify($k);
249
						}
250
						$input[$k] = $type ? Purifier::purifyByType($v, $type) : Purifier::purify($v);
251
					}
252
					$value = $input;
253
				} else {
254
					$value = $type ? Purifier::purifyByType($value, $type) : Purifier::purify($value);
255
				}
256
			}
257
258
			return $this->purifiedValuesByArray[$key] = (array) $value;
259
		}
260
		return $value;
261
	}
262
263
	/**
264
	 * Function to get the exploded values for a given key.
265
	 *
266
	 * @param string      $key
267
	 * @param string      $delimiter
268
	 * @param bool|string $type
269
	 *
270
	 * @return array
271
	 */
272
	public function getExploded($key, $delimiter = ',', $type = false)
273
	{
274
		if (isset($this->purifiedValuesByExploded[$key])) {
275
			return $this->purifiedValuesByExploded[$key];
276
		}
277
		$value = [];
278
		if (isset($this->rawValues[$key])) {
279
			if ('' === $this->rawValues[$key]) {
280
				return $value;
281
			}
282 1
			$value = explode($delimiter, $this->rawValues[$key]);
283
			if ($value) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $value of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
284 1
				$value = $type ? Purifier::purifyByType($value, $type) : Purifier::purify($value);
0 ignored issues
show
Bug introduced by
$value of type string[] is incompatible with the type string expected by parameter $input of App\Purifier::purify(). ( Ignorable by Annotation )

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

284
				$value = $type ? Purifier::purifyByType($value, $type) : Purifier::purify(/** @scrutinizer ignore-type */ $value);
Loading history...
285 1
			}
286 1
287 1
			return $this->purifiedValuesByExploded[$key] = $value;
288
		}
289
290 1
		return $value;
291 1
	}
292 1
293
	/**
294
	 * Purify multi dimension array.
295 1
	 *
296
	 * @param mixed        $values
297 1
	 * @param array|string $template
298
	 *
299
	 * @throws \App\Exceptions\IllegalValue
300 1
	 *
301
	 * @return mixed
302 1
	 */
303
	private function purifyMultiDimensionArray($values, $template)
304
	{
305
		if (\is_array($template)) {
306
			foreach ($values as $firstKey => $value) {
307
				if (\is_array($value)) {
308
					if (1 === \count($template)) {
309
						$template = current($template);
310
					}
311
					foreach ($value as $secondKey => $val) {
312
						$tempTemplate = $template;
313
						if (isset($template[$firstKey])) {
314
							$tempTemplate = $template[$firstKey];
315 1
						}
316
						if (1 === \count($tempTemplate)) {
317
							$tempTemplate = current($tempTemplate);
318 1
						} elseif (!isset($tempTemplate[$secondKey])) {
319
							throw new Exceptions\IllegalValue("ERR_NOT_ALLOWED_VALUE||{$secondKey}", 406);
320
						} else {
321
							$tempTemplate = $tempTemplate[$secondKey];
322
						}
323
						$values[$firstKey][$secondKey] = $this->purifyMultiDimensionArray($val, $tempTemplate);
324
					}
325
				} else {
326
					if (\is_array($template) && 1 === \count($template)) {
327
						$values[$firstKey] = $this->purifyMultiDimensionArray($value, current($template));
328
					} elseif (isset($template[$firstKey])) {
329 1
						$values[$firstKey] = $this->purifyMultiDimensionArray($value, $template[$firstKey]);
330
					} else {
331 1
						throw new Exceptions\IllegalValue("ERR_NOT_ALLOWED_VALUE||{$firstKey}||" . print_r($template, true), 406);
0 ignored issues
show
Bug introduced by
Are you sure print_r($template, true) of type string|true can be used in concatenation? ( Ignorable by Annotation )

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

331
						throw new Exceptions\IllegalValue("ERR_NOT_ALLOWED_VALUE||{$firstKey}||" . /** @scrutinizer ignore-type */ print_r($template, true), 406);
Loading history...
332 1
					}
333
				}
334 1
			}
335 1
		} else {
336
			$values = empty($values) ? $values : ($template ? Purifier::purifyByType($values, $template) : Purifier::purify($values));
337
		}
338
		return $values;
339
	}
340
341
	/**
342
	 * Function to get multi dimension array.
343 1
	 *
344 1
	 * @param string $key
345
	 * @param array  $template
346
	 *
347 1
	 * @return array
348
	 */
349
	public function getMultiDimensionArray(string $key, array $template): array
350
	{
351
		$return = [];
352
		if (isset($this->purifiedValuesByMultiDimension[$key])) {
353
			$return = $this->purifiedValuesByMultiDimension[$key];
354
		} elseif (isset($this->rawValues[$key]) && ($value = $this->rawValues[$key])) {
355
			if (\is_string($value) && (0 === strpos($value, '[') || 0 === strpos($value, '{'))) {
356
				$decodeValue = Json::decode($value);
357
				if (null !== $decodeValue) {
358
					$value = $decodeValue;
359
				} else {
360
					Log::warning('Invalid data format, problem encountered while decoding JSON. Data should be in JSON format. Data: ' . $value);
361
				}
362
			}
363
			$value = (array) $this->purifyMultiDimensionArray($value, $template);
364
			$return = $this->purifiedValuesByMultiDimension[$key] = $value;
365
		}
366
367
		return $return;
368
	}
369
370
	/**
371
	 * Function to get the date range values for a given key.
372
	 *
373
	 * @param string $key request param like 'createdtime'
374
	 *
375
	 * @return array
376
	 */
377
	public function getDateRange($key)
378
	{
379
		return $this->getByType($key, 'DateRangeUserFormat');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getByType(... 'DateRangeUserFormat') also could return the type boolean which is incompatible with the documented return type array.
Loading history...
380
	}
381
382
	/**
383
	 * Function to get html the value for a given key.
384
	 *
385
	 * @param string $key
386
	 * @param mixed  $value
387
	 *
388
	 * @return mixed
389
	 */
390
	public function getForHtml($key, $value = '')
391
	{
392
		if (isset($this->purifiedValuesByHtml[$key])) {
393
			return $this->purifiedValuesByHtml[$key];
394
		}
395
		if (isset($this->rawValues[$key])) {
396
			$value = $this->rawValues[$key];
397
		}
398
		if ($value) {
399
			$value = \App\Purifier::purifyHtml($value);
400
		}
401
402
		return $this->purifiedValuesByHtml[$key] = $value;
403
	}
404
405
	/**
406
	 * Function to get the value if its safe to use for SQL Query (column).
407
	 *
408
	 * @param string $key
409
	 * @param bool   $skipEmtpy
410
	 *
411
	 * @return string
412
	 */
413 1
	public function getForSql($key, $skipEmtpy = true)
414
	{
415 1
		return Purifier::purifySql($this->get($key), $skipEmtpy);
0 ignored issues
show
Bug Best Practice introduced by
The expression return App\Purifier::pur...>get($key), $skipEmtpy) also could return the type boolean which is incompatible with the documented return type string.
Loading history...
416
	}
417
418
	/**
419 1
	 * Function to get the request mode.
420
	 *
421
	 * @return string
422
	 */
423
	public function getMode()
424
	{
425
		return '' !== $this->getRaw('mode') ? $this->getByType('mode', 'Alnum') : '';
0 ignored issues
show
Bug Best Practice introduced by
The expression return '' !== $this->get...e('mode', 'Alnum') : '' also could return the type boolean which is incompatible with the documented return type string.
Loading history...
426
	}
427 1
428
	/**
429 1
	 * Get all data.
430
	 *
431
	 * @return array
432
	 */
433
	public function getAll()
434
	{
435
		foreach ($this->rawValues as $key => $value) {
436
			$this->get($key);
437
		}
438
439
		return $this->purifiedValuesByGet;
440
	}
441
442
	/**
443
	 * Get all raw data.
444
	 *
445
	 * @return array
446
	 */
447
	public function getAllRaw()
448
	{
449
		return $this->rawValues;
450
	}
451
452
	/**
453
	 * Get raw value.
454 1
	 *
455
	 * @param string $key
456 1
	 * @param mixed  $defaultValue
457
	 *
458
	 * @return mixed
459 1
	 */
460 1
	public function getRaw($key, $defaultValue = '')
461 1
	{
462 1
		if (isset($this->rawValues[$key])) {
463
			return $this->rawValues[$key];
464
		}
465
466
		return $defaultValue;
467
	}
468
469
	/**
470
	 * Get all headers.
471
	 *
472
	 * @return string[]
473
	 */
474 1
	public function getHeaders()
475
	{
476
		if (isset($this->headers)) {
477
			return $this->headers;
478
		}
479
		$data = array_change_key_case(getallheaders(), CASE_LOWER);
480
		foreach ($data as $key => &$value) {
481
			if ('' !== $value) {
482
				$value = isset($this->headersPurifierMap[$key]) ? Purifier::purifyByType($value, $this->headersPurifierMap[$key]) : Purifier::purify($value);
483
			}
484
		}
485
		return $this->headers = $data;
486
	}
487
488
	/**
489
	 * Get header for a given key.
490
	 *
491
	 * @param string $key
492
	 *
493
	 * @return string
494
	 */
495
	public function getHeader($key)
496
	{
497
		if (!isset($this->headers)) {
498
			$this->getHeaders();
499
		}
500
		return $this->headers[$key] ?? null;
501
	}
502
503
	/**
504
	 * Get request method.
505
	 *
506
	 * @throws \App\Exceptions\AppException
507
	 *
508
	 * @return string
509
	 */
510
	public static function getRequestMethod()
511
	{
512
		$method = $_SERVER['REQUEST_METHOD'];
513
		if ('POST' === $method && isset($_SERVER['HTTP_X_HTTP_METHOD'])) {
514
			if ('DELETE' === $_SERVER['HTTP_X_HTTP_METHOD']) {
515
				$method = 'DELETE';
516
			} elseif ('PUT' === $_SERVER['HTTP_X_HTTP_METHOD']) {
517
				$method = 'PUT';
518
			} else {
519
				throw new \App\Exceptions\AppException('Unexpected Header');
520
			}
521
		}
522
		return strtoupper($method);
523
	}
524 2
525
	/**
526 2
	 * Get server and execution environment information.
527 2
	 *
528
	 * @param string $key
529
	 * @param mixed  $default
530
	 *
531
	 * @return bool
532
	 */
533
	public function getServer($key, $default = false)
534
	{
535
		if (!isset($_SERVER[$key])) {
536
			return $default;
537
		}
538
		return Purifier::purifyByType($_SERVER[$key], 'Text');
539
	}
540
541
	/**
542
	 * Get module name.
543
	 *
544
	 * @param bool $raw
545
	 *
546
	 * @return string
547
	 */
548
	public function getModule($raw = true)
549
	{
550
		$moduleName = $this->getByType('module', \App\Purifier::ALNUM);
551
		if (!$raw && !$this->isEmpty('parent', true) && 'Settings' === ($parentModule = $this->getByType('parent', \App\Purifier::ALNUM))) {
552
			$moduleName = "$parentModule:$moduleName";
553
		}
554
		return $moduleName;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $moduleName also could return the type boolean which is incompatible with the documented return type string.
Loading history...
555
	}
556
557 1
	/**
558
	 * Check for existence of key.
559 1
	 *
560
	 * @param string $key
561
	 *
562
	 * @return bool
563
	 */
564
	public function has($key)
565
	{
566
		return isset($this->rawValues[$key]);
567
	}
568
569
	/**
570 1
	 * Function to check if the key is empty.
571
	 *
572 1
	 * @param string $key
573
	 * @param bool   $emptyFunction
574
	 *
575
	 * @return bool
576 1
	 */
577
	public function isEmpty($key, $emptyFunction = false)
578
	{
579
		if ($emptyFunction) {
580
			return empty($this->rawValues[$key]);
581
		}
582
		return !isset($this->rawValues[$key]) || '' === $this->rawValues[$key];
583
	}
584
585
	/**
586
	 * Function to set the value for a given key.
587 15
	 *
588
	 * @param string $key
589 15
	 * @param mixed  $value
590 15
	 * @param bool   $onlyRaw
591
	 *
592 15
	 * @return $this
593
	 */
594
	public function set($key, $value, bool $onlyRaw = false): self
595
	{
596
		if ($onlyRaw) {
597
			$this->rawValues[$key] = $value;
598
		} else {
599
			$this->rawValues[$key] = $this->purifiedValuesByGet[$key] = $this->purifiedValuesByInteger[$key] = $this->purifiedValuesByHtml[$key] = $value;
600
			$this->purifiedValuesByType[$key] = [];
601
		}
602
		return $this;
603
	}
604
605
	/**
606
	 * Function to remove the value for a given key.
607
	 *
608
	 * @param string $key
609
	 */
610
	public function delete($key)
611
	{
612
		if (isset($this->purifiedValuesByGet[$key])) {
613
			unset($this->purifiedValuesByGet[$key]);
614
		}
615
		if (isset($this->purifiedValuesByInteger[$key])) {
616
			unset($this->purifiedValuesByInteger[$key]);
617
		}
618
		if (isset($this->purifiedValuesByType[$key])) {
619
			unset($this->purifiedValuesByType[$key]);
620
		}
621
		if (isset($this->purifiedValuesByHtml[$key])) {
622
			unset($this->purifiedValuesByHtml[$key]);
623
		}
624
		if (isset($this->purifiedValuesByArray[$key])) {
625
			unset($this->purifiedValuesByArray[$key]);
626
		}
627
		if (isset($this->purifiedValuesByDateRange[$key])) {
628
			unset($this->purifiedValuesByDateRange[$key]);
629
		}
630
		if (isset($this->purifiedValuesByExploded[$key])) {
631
			unset($this->purifiedValuesByExploded[$key]);
632
		}
633
		if (isset($this->purifiedValuesByMultiDimension[$key])) {
634
			unset($this->purifiedValuesByMultiDimension[$key]);
635
		}
636
		if (isset($this->rawValues[$key])) {
637
			unset($this->rawValues[$key]);
638
		}
639
	}
640
641
	/**
642
	 * Get all request keys.
643
	 *
644
	 * @return array
645
	 */
646
	public function getKeys()
647
	{
648
		return array_keys($this->rawValues);
649
	}
650
651
	/**
652
	 * Function to check if the ajax request.
653
	 *
654
	 * @return bool
655
	 */
656
	public function isAjax()
657
	{
658
		if (!empty($_SERVER['HTTP_X_PJAX']) && true === $_SERVER['HTTP_X_PJAX']) {
659
			return true;
660
		}
661
		if (!empty($_SERVER['HTTP_X_REQUESTED_WITH'])) {
662
			return true;
663
		}
664
		return false;
665
	}
666
667
	/**
668
	 * Is json.
669
	 *
670
	 * @return bool
671
	 */
672
	public function isJSON()
673
	{
674
		return false !== strpos($this->getHeader('accept'), 'application/json');
675
	}
676
677
	/**
678
	 * Validating read access request.
679
	 *
680
	 * @throws \App\Exceptions\Csrf
681
	 */
682
	public function validateReadAccess()
683
	{
684
		// Referer check if present - to over come && Check for user post authentication.
685
		if (\Config\Security::$verifyRefererHeader && isset($_SERVER['HTTP_REFERER']) && \App\User::getCurrentUserId() && 'Install' !== $this->get('module')) {
686
			$allowed = array_merge(\Config\Security::$allowedFrameDomains, \Config\Security::$allowedFormDomains);
687
			$allowed[] = \App\Config::main('site_URL');
688
			$throw = true;
689
			foreach ($allowed as $value) {
690
				if (0 === stripos($_SERVER['HTTP_REFERER'], $value)) {
691
					$throw = false;
692
				}
693
			}
694
			if ($throw) {
695
				throw new \App\Exceptions\Csrf('Illegal request');
696
			}
697
		}
698
	}
699
700
	/**
701
	 * Validating write access request.
702
	 *
703
	 * @param bool $skipRequestTypeCheck
704
	 *
705
	 * @throws \App\Exceptions\Csrf
706 3
	 */
707
	public function validateWriteAccess($skipRequestTypeCheck = false)
708 3
	{
709
		if (!$skipRequestTypeCheck && 'POST' !== $_SERVER['REQUEST_METHOD']) {
710
			throw new \App\Exceptions\Csrf('Invalid request - validate Write Access', 403);
711
		}
712 3
		$this->validateReadAccess();
713
		if (\App\Config::security('csrfActive')) {
714
			\CsrfMagic\Csrf::check();
715
		}
716
	}
717
718
	/**
719
	 * Static instance initialization.
720
	 *
721
	 * @param array|bool $request
722
	 *
723
	 * @return Request
724
	 */
725 41
	public static function init($request = false)
726
	{
727 41
		if (!static::$request) {
728
			static::$request = new self($request ?: $_REQUEST);
729
		}
730 41
		return static::$request;
731 41
	}
732
733
	/**
734 41
	 * Support static methods, all functions must start with "_".
735
	 *
736
	 * @param string     $name
737 41
	 * @param array|null $arguments
738 41
	 *
739 41
	 * @throws \App\Exceptions\AppException
740
	 *
741
	 * @return mixed
742
	 */
743
	public static function __callStatic($name, $arguments = null)
744
	{
745
		if (!static::$request) {
746
			static::init();
747
		}
748
		$function = ltrim($name, '_');
749
		if (!method_exists(static::$request, $function)) {
750
			throw new \App\Exceptions\AppException('Method not found');
751
		}
752
		if (empty($arguments)) {
753
			return static::$request->{$function}();
754
		}
755
		$first = array_shift($arguments);
756
		if (empty($arguments)) {
757
			return static::$request->{$function}($first);
758
		}
759
		return static::$request->{$function}($first, $arguments[0]);
760
	}
761
}
762