Passed
Branch master (cbf361)
by Mihail
04:19
created

Request::findStaticAliases()   B

Complexity

Conditions 5
Paths 3

Size

Total Lines 29
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 15
nc 3
nop 2
dl 0
loc 29
rs 8.439
c 0
b 0
f 0
1
<?php
2
3
namespace Ffcms\Core\Network;
4
5
use Ffcms\Core\App;
6
use Ffcms\Core\Helper\Type\Any;
7
use Ffcms\Core\Helper\Type\Arr;
8
use Ffcms\Core\Helper\Type\Obj;
9
use Ffcms\Core\Helper\Type\Str;
10
use Symfony\Component\HttpFoundation\RedirectResponse as Redirect;
11
use Symfony\Component\HttpFoundation\Request as FoundationRequest;
12
13
/**
14
 * Class Request. Classic implementation of httpfoundation.request with smooth additions and changes which allow
15
 * working as well as in ffcms.
16
 * @package Ffcms\Core\Network
17
 */
18
class Request extends FoundationRequest
19
{
20
    protected $language;
21
    protected $languageInPath = false;
22
23
    // special variable for route aliasing
24
    protected $aliasPathTarget;
25
    // special variable for route callback binding
26
    protected $callbackClass;
27
28
    // fast access for controller building
29
    protected $controller;
30
    protected $action;
31
    protected $argumentId;
32
    protected $argumentAdd;
33
34
    public function __construct(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null)
35
    {
36
        parent::__construct($query, $request, $attributes, $cookies, $files, $server, $content);
37
        $this->findRedirect();
38
        $this->runMultiLanguage();
39
        $this->runPathBinding();
40
        $this->loadTrustedProxies();
41
    }
42
43
    /**
44
     * Sets the parameters for this request.
45
     *
46
     * This method also re-initializes all properties.
47
     *
48
     * @param array $query The GET parameters
49
     * @param array $request The POST parameters
50
     * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
51
     * @param array $cookies The COOKIE parameters
52
     * @param array $files The FILES parameters
53
     * @param array $server The SERVER parameters
54
     * @param string $content The raw body data
55
     *
56
     * @api
57
     */
58
    public function initialize(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null)
59
    {
60
        parent::initialize($query, $request, $attributes, $cookies, $files, $server, $content);
61
62
        $basePath = trim(App::$Properties->get('basePath'), '/');
0 ignored issues
show
Bug introduced by
It seems like Ffcms\Core\App::Properties->get('basePath') can also be of type false; however, parameter $str of trim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

62
        $basePath = trim(/** @scrutinizer ignore-type */ App::$Properties->get('basePath'), '/');
Loading history...
63
        if ($basePath !== null && Str::length($basePath) > 0) {
64
            $basePath = '/' . $basePath;
65
        }
66
67
        if (!defined('env_no_uri') || env_no_uri === false) {
0 ignored issues
show
Bug introduced by
The constant Ffcms\Core\Network\env_no_uri was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
68
            $basePath .= '/' . strtolower(env_name);
0 ignored issues
show
Bug introduced by
The constant Ffcms\Core\Network\env_name was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
69
        }
70
71
        // we never try to use path's without friendly url's
72
        $this->basePath = $this->baseUrl = $basePath;
73
    }
74
75
    /**
76
     * Build multi language pathway binding.
77
     */
78
    private function runMultiLanguage()
79
    {
80
        // multi-language is disabled, use default language
81
        if (App::$Properties->get('multiLanguage') !== true) {
82
            $this->language = App::$Properties->get('singleLanguage');
83
        } else {
84
            // maybe its a language domain alias?
85
            if (Any::isArray(App::$Properties->get('languageDomainAlias'))) {
86
                /** @var array $domainAlias */
87
                $domainAlias = App::$Properties->get('languageDomainAlias');
88
                if (Any::isArray($domainAlias) && !Str::likeEmpty($domainAlias[$this->getHost()])) {
89
                    $this->language = $domainAlias[$this->getHost()];
90
                }
91
            } else {
92
                // try to find language in pathway
93
                foreach (App::$Properties->get('languages') as $lang) {
94
                    if (Str::startsWith('/' . $lang, $this->getPathInfo())) {
95
                        $this->language = $lang;
96
                        $this->languageInPath = true;
97
                    }
98
                }
99
100
                // try to find in ?lang get
101
                if ($this->language === null && Arr::in($this->query->get('lang'), App::$Properties->get('languages'))) {
0 ignored issues
show
Bug introduced by
It seems like Ffcms\Core\App::Properties->get('languages') can also be of type false; however, parameter $haystack of Ffcms\Core\Helper\Type\Arr::in() does only seem to accept null|array, maybe add an additional type check? ( Ignorable by Annotation )

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

101
                if ($this->language === null && Arr::in($this->query->get('lang'), /** @scrutinizer ignore-type */ App::$Properties->get('languages'))) {
Loading history...
102
                    $this->language = $this->query->get('lang');
103
                }
104
105
                // language still not defined?!
106
                if ($this->language === null) {
107
                    $userLang = App::$Properties->get('singleLanguage');
108
                    $browserAccept = $this->getLanguages();
109
                    if (Any::isArray($browserAccept) && count($browserAccept) > 0) {
110
                        foreach ($browserAccept as $bLang) {
111
                            if (Arr::in($bLang, App::$Properties->get('languages'))) {
112
                                $userLang = $bLang;
113
                                break; // stop calculating, language is founded in priority
114
                            }
115
                        }
116
                    }
117
118
                    // parse query string
119
                    $queryString = null;
120
                    if (count($this->query->all()) > 0) {
121
                        $queryString = '?' . http_build_query($this->query->all());
122
                    }
123
124
                    // build response with redirect to language-based path
125
                    $response = new Redirect($this->getSchemeAndHttpHost() . $this->basePath . '/' . $userLang . $this->getPathInfo() . $queryString);
126
                    $response->send();
127
                    exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
128
                }
129
            }
130
        }
131
    }
132
133
    /**
134
     * Build static and dynamic path aliases for working set
135
     */
136
    private function runPathBinding()
137
    {
138
        // calculated depend of language
139
        $pathway = $this->getPathInfo();
140
        /** @var array $routing */
141
        $routing = App::$Properties->getAll('Routing');
142
143
        // try to work with static aliases
144
        if (Any::isArray($routing) && isset($routing['Alias'], $routing['Alias'][env_name])) {
0 ignored issues
show
Bug introduced by
The constant Ffcms\Core\Network\env_name was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
145
            $pathway = $this->findStaticAliases($routing['Alias'][env_name], $pathway);
146
        }
147
148
        $this->setPathdata(explode('/', trim($pathway, '/')));
149
150
        // set default controller and action for undefined data
151
        if (!$this->action) {
152
            $this->action = 'Index';
153
        }
154
155
        // empty or contains backslashes? set to main
156
        if (!$this->controller || Str::contains('\\', $this->controller)) {
157
            $this->controller = 'Main';
158
        }
159
160
        // find callback injection in routing configs (calculated in App::run())
161
        if (Any::isArray($routing) && isset($routing['Callback'], $routing['Callback'][env_name])) {
162
            $this->findDynamicCallbacks($routing['Callback'][env_name], $this->controller);
163
        }
164
    }
165
166
    /**
167
     * Check if current url in redirect map
168
     */
169
    private function findRedirect()
170
    {
171
        // calculated depend of language
172
        $pathway = $this->getPathInfo();
173
        /** @var array $routing */
174
        $routing = App::$Properties->getAll('Routing');
175
176
        if (!Any::isArray($routing) || !isset($routing['Redirect']) || !Any::isArray($routing['Redirect'])) {
177
            return;
178
        }
179
180
        // check if source uri is key in redirect target map
181
        if (array_key_exists($pathway, $routing['Redirect'])) {
182
            $target = $this->getSchemeAndHttpHost(); // . $this->getBasePath() . '/' . rtrim($routing['Redirect'][$pathway], '/');
183
            if ($this->getBasePath() !== null && !Str::likeEmpty($this->getBasePath())) {
184
                $target .= '/' . $this->getBasePath();
185
            }
186
            $target .= rtrim($routing['Redirect'][$pathway], '/');
187
            $redirect = new Redirect($target);
188
            $redirect->send();
189
            exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
190
        }
191
    }
192
193
    /**
194
     * Prepare static pathway aliasing for routing
195
     * @param array|null $map
196
     * @param string|null $pathway
197
     * @return string
198
     */
199
    private function findStaticAliases(array $map = null, $pathway = null)
200
    {
201
        if ($map === null) {
202
            return $pathway;
203
        }
204
205
        // current pathway is found as "old path" (or alias target). Make redirect to new pathway.
206
        if (Arr::in($pathway, $map)) {
207
            // find "new path" as binding uri slug
208
            $binding = array_search($pathway, $map, true);
209
            // build url to redirection
210
            $url = $this->getSchemeAndHttpHost() . $this->getBasePath() . '/';
211
            if (App::$Properties->get('multiLanguage')) {
212
                $url .= $this->language . '/';
213
            }
214
            $url .= ltrim($binding, '/');
0 ignored issues
show
Bug introduced by
It seems like $binding can also be of type false; however, parameter $str of ltrim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

214
            $url .= ltrim(/** @scrutinizer ignore-type */ $binding, '/');
Loading history...
215
216
            $redirect = new Redirect($url);
217
            $redirect->send();
218
            exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
219
        }
220
221
        // current pathway request is equal to path alias. Set alias to property.
222
        if (array_key_exists($pathway, $map)) {
223
            $pathway = $map[$pathway];
224
            $this->aliasPathTarget = $pathway;
225
        }
226
227
        return $pathway;
228
    }
229
230
    /**
231
     * Prepare dynamic callback data for routing
232
     * @param array|null $map
233
     * @param string|null $controller
234
     */
235
    private function findDynamicCallbacks(array $map = null, ?string $controller = null)
236
    {
237
        if ($map === null) {
238
            return;
239
        }
240
241
        // try to find global callback for this controller slug
242
        if (array_key_exists($controller, $map)) {
243
            $class = (string)$map[$controller];
244
            if (!Str::likeEmpty($class)) {
245
                $this->callbackClass = $class;
246
            }
247
        }
248
    }
249
250
    /**
251
     * Set trusted proxies from configs
252
     */
253
    private function loadTrustedProxies()
254
    {
255
        $proxies = App::$Properties->get('trustedProxy');
256
        if ($proxies === null || Str::likeEmpty($proxies)) {
0 ignored issues
show
Bug introduced by
It seems like $proxies can also be of type false; however, parameter $string of Ffcms\Core\Helper\Type\Str::likeEmpty() does only seem to accept null|string, maybe add an additional type check? ( Ignorable by Annotation )

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

256
        if ($proxies === null || Str::likeEmpty(/** @scrutinizer ignore-type */ $proxies)) {
Loading history...
257
            return;
258
        }
259
260
        $pList = explode(',', $proxies);
0 ignored issues
show
Bug introduced by
It seems like $proxies can also be of type false; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

260
        $pList = explode(',', /** @scrutinizer ignore-type */ $proxies);
Loading history...
261
        $resultList = [];
262
        foreach ($pList as $proxy) {
263
            $resultList[] = trim($proxy);
264
        }
265
        self::setTrustedProxies($resultList);
266
    }
267
268
    /**
269
     * Working with path array data
270
     * @param array|null $pathArray
271
     */
272
    private function setPathdata(?array $pathArray = null)
273
    {
274
        if (!Any::isArray($pathArray) || count($pathArray) < 1) {
275
            return;
276
        }
277
278
        // check if array length is more then 4 basic elements and slice it recursive
279
        if (count($pathArray) > 4) {
280
            $this->setPathdata(array_slice($pathArray, 0, 4));
0 ignored issues
show
Bug introduced by
It seems like $pathArray can also be of type null; however, parameter $array of array_slice() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

280
            $this->setPathdata(array_slice(/** @scrutinizer ignore-type */ $pathArray, 0, 4));
Loading history...
281
            return;
282
        }
283
284
        // Switch path array as reverse without break point! Caution: drugs inside!
285
        switch (count($pathArray)) {
286
            case 4:
287
                $this->argumentAdd = $pathArray[3];
288
                // no break
289
            case 3:
290
                $this->argumentId = $pathArray[2];
291
                // no break
292
            case 2:
293
                $this->action = ucfirst(Str::lowerCase($pathArray[1]));
294
                // no break
295
            case 1:
296
                $this->controller = ucfirst(Str::lowerCase($pathArray[0]));
297
                break;
298
        }
299
    }
300
301
    /**
302
     * Get pathway as string
303
     * @return string
304
     */
305
    public function getPathInfo()
306
    {
307
        $route = $this->languageInPath ? Str::sub(parent::getPathInfo(), Str::length($this->language) + 1) : parent::getPathInfo();
308
        if (!Str::startsWith('/', $route)) {
309
            $route = '/' . $route;
310
        }
311
        return $route;
312
    }
313
314
    /**
315
     * Check if language used in path
316
     * @return bool
317
     */
318
    public function languageInPath()
319
    {
320
        return $this->languageInPath;
321
    }
322
323
    /**
324
     * Get current language
325
     * @return string|null
326
     */
327
    public function getLanguage(): ?string
328
    {
329
        return $this->language;
330
    }
331
332
    /**
333
     * Set current language
334
     * @param string $lang
335
     * @return bool
336
     */
337
    public function setLanguage($lang): bool
338
    {
339
        if (Arr::in($lang, App::$Properties->get('languages'))) {
0 ignored issues
show
Bug introduced by
It seems like Ffcms\Core\App::Properties->get('languages') can also be of type false; however, parameter $haystack of Ffcms\Core\Helper\Type\Arr::in() does only seem to accept null|array, maybe add an additional type check? ( Ignorable by Annotation )

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

339
        if (Arr::in($lang, /** @scrutinizer ignore-type */ App::$Properties->get('languages'))) {
Loading history...
340
            $this->language = $lang;
341
            return true;
342
        }
343
344
        return false;
345
    }
346
347
    /**
348
     * Get current controller name
349
     * @return string
350
     */
351
    public function getController(): ?string
352
    {
353
        return $this->controller;
354
    }
355
356
    /**
357
     * Get current controller action() name
358
     * @return string
359
     */
360
    public function getAction(): ?string
361
    {
362
        return $this->action;
363
    }
364
365
    /**
366
     * Get current $id argument for controller action
367
     * @return string|null
368
     */
369
    public function getID(): ?string
370
    {
371
        return urldecode($this->argumentId);
372
    }
373
374
    /**
375
     * Set current controller name
376
     * @param string $name
377
     */
378
    public function setController($name): void
379
    {
380
        $this->controller = $name;
381
    }
382
383
    /**
384
     * Set current action value
385
     * @param string $name
386
     */
387
    public function setAction($name): void
388
    {
389
        $this->action = $name;
390
    }
391
392
    /**
393
     * Set current id argument value
394
     * @param mixed $name
395
     */
396
    public function setId($name): void
397
    {
398
        $this->argumentId = $name;
399
    }
400
401
    /**
402
     * Set current add argument value
403
     * @param mixed $name
404
     */
405
    public function setAdd($name): void
406
    {
407
        $this->argumentAdd = $name;
408
    }
409
410
    /**
411
     * Get current $add argument for controller action
412
     * @return string|null
413
     */
414
    public function getAdd(): ?string
415
    {
416
        return urldecode($this->argumentAdd);
417
    }
418
419
    /**
420
     * Get callback class alias if exist
421
     * @return null|string
422
     */
423
    public function getCallbackAlias(): ?string
424
    {
425
        return $this->callbackClass;
426
    }
427
428
    /**
429
     * Get static alias of pathway if exist
430
     * @return null|string
431
     */
432
    public function getStaticAlias(): ?string
433
    {
434
        return $this->aliasPathTarget;
435
    }
436
437
    /**
438
     * Check if current request is aliased by dynamic or static rule
439
     * @return bool
440
     */
441
    public function isPathInjected(): bool
442
    {
443
        return $this->callbackClass !== null || $this->aliasPathTarget !== null;
444
    }
445
446
    /**
447
     * Get pathway without current controller/action path
448
     * @return string
449
     */
450
    public function getPathWithoutControllerAction(): ?string
451
    {
452
        $path = trim($this->getPathInfo(), '/');
453
        if ($this->aliasPathTarget !== null) {
454
            $path = trim($this->aliasPathTarget, '/');
455
        }
456
457
        $pathArray = explode('/', $path);
458
        if ($pathArray[0] === Str::lowerCase($this->getController())) {
459
            // unset controller
460
            array_shift($pathArray);
461
            if ($pathArray[0] === Str::lowerCase($this->getAction())) {
462
                // unset action
463
                array_shift($pathArray);
464
            }
465
        }
466
        return trim(implode('/', $pathArray), '/');
467
    }
468
469
    /**
470
     * Get current full request URI
471
     * @return string
472
     */
473
    public function getFullUrl(): string
474
    {
475
        return $this->getSchemeAndHttpHost() . $this->getRequestUri();
476
    }
477
478
    /**
479
     * Get base path from current environment without basePath of subdirectories
480
     * @return string
481
     */
482
    public function getInterfaceSlug(): string
483
    {
484
        $path = $this->getBasePath();
485
        $subDir = App::$Properties->get('basePath');
486
        if ($subDir !== '/') {
487
            $offset = (int)Str::length($subDir);
0 ignored issues
show
Bug introduced by
It seems like $subDir can also be of type false; however, parameter $string of Ffcms\Core\Helper\Type\Str::length() does only seem to accept null|string, maybe add an additional type check? ( Ignorable by Annotation )

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

487
            $offset = (int)Str::length(/** @scrutinizer ignore-type */ $subDir);
Loading history...
488
            $path = Str::sub($path, --$offset);
489
        }
490
        return $path;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $path could return the type null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
491
    }
492
}
493