Completed
Push — master ( 9a305e...37b087 )
by Mihail
01:56
created

Request::setLanguage()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 1
1
<?php
2
3
namespace Ffcms\Core\Network;
4
5
use Ffcms\Core\App;
6
use Ffcms\Core\Helper\Type\Arr;
7
use Ffcms\Core\Helper\Type\Obj;
8
use Ffcms\Core\Helper\Type\Str;
9
use Symfony\Component\HttpFoundation\RedirectResponse as Redirect;
10
use Symfony\Component\HttpFoundation\Request as FoundationRequest;
11
12
/**
13
 * Class Request. Classic implementation of httpfoundation.request with smooth additions and changes which allow
14
 * working as well as in ffcms.
15
 * @package Ffcms\Core\Network
16
 */
17
class Request extends FoundationRequest
18
{
19
    protected $language;
20
    protected $languageInPath = false;
21
22
    // special variable for route aliasing
23
    protected $aliasPathTarget = false;
24
    // special variable for route callback binding
25
    protected $callbackClass = false;
26
27
    // fast access for controller building
28
    protected $controller;
29
    protected $action;
30
    protected $argumentId;
31
    protected $argumentAdd;
32
33
    public function __construct(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null)
34
    {
35
        parent::__construct($query, $request, $attributes, $cookies, $files, $server, $content);
36
        $this->findRedirect();
37
        $this->runMultiLanguage();
38
        $this->runPathBinding();
39
        $this->loadTrustedProxies();
40
    }
41
42
    /**
43
     * Sets the parameters for this request.
44
     *
45
     * This method also re-initializes all properties.
46
     *
47
     * @param array $query The GET parameters
48
     * @param array $request The POST parameters
49
     * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
50
     * @param array $cookies The COOKIE parameters
51
     * @param array $files The FILES parameters
52
     * @param array $server The SERVER parameters
53
     * @param string $content The raw body data
54
     *
55
     * @api
56
     */
57
    public function initialize(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null)
58
    {
59
        parent::initialize($query, $request, $attributes, $cookies, $files, $server, $content);
60
61
        $basePath = trim(App::$Properties->get('basePath'), '/');
62
        if ($basePath !== null && Str::length($basePath) > 0) {
63
            $basePath = '/' . $basePath;
64
        }
65
66
        if (!defined('env_no_uri') || env_no_uri === false) {
67
            $basePath .= '/' . strtolower(env_name);
68
        }
69
70
        // we never try to use path's without friendly url's
71
        $this->basePath = $this->baseUrl = $basePath;
72
    }
73
74
    /**
75
     * Build multi language pathway binding.
76
     */
77
    private function runMultiLanguage()
78
    {
79
        // multi-language is disabled, use default language
80
        if (App::$Properties->get('multiLanguage') !== true) {
81
            $this->language = App::$Properties->get('singleLanguage');
82
        } else {
83
            // maybe its a language domain alias?
84
            if (Obj::isArray(App::$Properties->get('languageDomainAlias'))) {
85
                /** @var array $domainAlias */
86
                $domainAlias = App::$Properties->get('languageDomainAlias');
87
                if (Obj::isArray($domainAlias) && !Str::likeEmpty($domainAlias[$this->getHost()])) {
88
                    $this->language = $domainAlias[$this->getHost()];
89
                }
90
            } else {
91
                // try to find language in pathway
92
                foreach (App::$Properties->get('languages') as $lang) {
0 ignored issues
show
Bug introduced by
The expression \Ffcms\Core\App::$Properties->get('languages') of type boolean is not traversable.
Loading history...
93
                    if (Str::startsWith('/' . $lang, $this->getPathInfo())) {
94
                        $this->language = $lang;
95
                        $this->languageInPath = true;
96
                    }
97
                }
98
99
                // try to find in ?lang get
100
                if ($this->language === null && Arr::in($this->query->get('lang'), App::$Properties->get('languages'))) {
0 ignored issues
show
Documentation introduced by
\Ffcms\Core\App::$Properties->get('languages') is of type boolean, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
101
                    $this->language = $this->query->get('lang');
102
                }
103
104
                // language still not defined?!
105
                if ($this->language === null) {
106
                    $userLang = App::$Properties->get('singleLanguage');
107
                    $browserAccept = $this->getLanguages();
108
                    if (Obj::isArray($browserAccept) && count($browserAccept) > 0) {
109
                        foreach ($browserAccept as $bLang) {
110
                            if (Arr::in($bLang, App::$Properties->get('languages'))) {
0 ignored issues
show
Documentation introduced by
\Ffcms\Core\App::$Properties->get('languages') is of type boolean, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
111
                                $userLang = $bLang;
112
                                break; // stop calculating, language is founded in priority
113
                            }
114
                        }
115
                    }
116
117
                    // parse query string
118
                    $queryString = null;
119
                    if (count($this->query->all()) > 0) {
120
                        $queryString = '?' . http_build_query($this->query->all());
121
                    }
122
123
                    // build response with redirect to language-based path
124
                    $response = new Redirect($this->getSchemeAndHttpHost() . $this->basePath . '/' . $userLang . $this->getPathInfo() . $queryString);
125
                    $response->send();
126
                    exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method runMultiLanguage() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
127
                }
128
            }
129
        }
130
    }
131
132
    /**
133
     * Build static and dynamic path aliases for working set
134
     */
135
    private function runPathBinding()
136
    {
137
        // calculated depend of language
138
        $pathway = $this->getPathInfo();
139
        /** @var array $routing */
140
        $routing = App::$Properties->getAll('Routing');
141
142
        // try to work with static aliases
143 View Code Duplication
        if (Obj::isArray($routing) && isset($routing['Alias'], $routing['Alias'][env_name])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
144
            $pathway = $this->findStaticAliases($routing['Alias'][env_name], $pathway);
145
        }
146
147
        $this->setPathdata(explode('/', trim($pathway, '/')));
148
149
        // set default controller and action for undefined data
150
        if ($this->action == null) {
151
            $this->action = 'Index';
152
        }
153
154
        // empty or contains backslashes? set to main
155
        if ($this->controller == null || Str::contains('\\', $this->controller)) {
156
            $this->controller = 'Main';
157
        }
158
159
        // find callback injection in routing configs (calculated in App::run())
160 View Code Duplication
        if (Obj::isArray($routing) && isset($routing['Callback'], $routing['Callback'][env_name])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
161
            $this->findDynamicCallbacks($routing['Callback'][env_name], $this->controller);
162
        }
163
    }
164
165
    /**
166
     * Check if current url in redirect map
167
     */
168
    private function findRedirect()
169
    {
170
        // calculated depend of language
171
        $pathway = $this->getPathInfo();
172
        /** @var array $routing */
173
        $routing = App::$Properties->getAll('Routing');
174
175
        if (!Obj::isArray($routing) || !isset($routing['Redirect']) || !Obj::isArray($routing['Redirect'])) {
176
            return;
177
        }
178
179
        // check if source uri is key in redirect target map
180
        if (array_key_exists($pathway, $routing['Redirect'])) {
181
            $target = $this->getSchemeAndHttpHost(); // . $this->getBasePath() . '/' . rtrim($routing['Redirect'][$pathway], '/');
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
182
            if ($this->getBasePath() !== null && !Str::likeEmpty($this->getBasePath())) {
183
                $target .= '/' . $this->getBasePath();
184
            }
185
            $target .= rtrim($routing['Redirect'][$pathway], '/');
186
            $redirect = new Redirect($target);
187
            $redirect->send();
188
            exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method findRedirect() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
189
        }
190
    }
191
192
    /**
193
     * Prepare static pathway aliasing for routing
194
     * @param array|null $map
195
     * @param string|null $pathway
196
     * @return string
197
     */
198
    private function findStaticAliases(array $map = null, $pathway = null)
199
    {
200
        if ($map === null) {
201
            return $pathway;
202
        }
203
204
        // current pathway is found as "old path" (or alias target). Make redirect to new pathway.
205
        if (Arr::in($pathway, $map)) {
206
            // find "new path" as binding uri slug
207
            $binding = array_search($pathway, $map, true);
208
            // build url to redirection
209
            $url = $this->getSchemeAndHttpHost() . $this->getBasePath() . '/';
210
            if (App::$Properties->get('multiLanguage')) {
211
                $url .= $this->language . '/';
212
            }
213
            $url .= ltrim($binding, '/');
214
215
            $redirect = new Redirect($url);
216
            $redirect->send();
217
            exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method findStaticAliases() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
218
        }
219
220
        // current pathway request is equal to path alias. Set alias to property.
221
        if (array_key_exists($pathway, $map)) {
222
            $pathway = $map[$pathway];
223
            $this->aliasPathTarget = $pathway;
224
        }
225
226
        return $pathway;
227
    }
228
229
    /**
230
     * Prepare dynamic callback data for routing
231
     * @param array|null $map
232
     * @param string|null $controller
233
     */
234
    private function findDynamicCallbacks(array $map = null, $controller = null)
235
    {
236
        if ($map === null) {
237
            return;
238
        }
239
240
        // try to find global callback for this controller slug
241
        if (array_key_exists($controller, $map)) {
242
            $class = (string)$map[$controller];
243
            if (!Str::likeEmpty($class)) {
244
                $this->callbackClass = $class;
0 ignored issues
show
Documentation Bug introduced by
The property $callbackClass was declared of type boolean, but $class is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
245
            }
246
        }
247
    }
248
249
    /**
250
     * Set trusted proxies from configs
251
     */
252
    private function loadTrustedProxies()
253
    {
254
        $proxies = App::$Properties->get('trustedProxy');
255
        if ($proxies === null || Str::likeEmpty($proxies)) {
256
            return;
257
        }
258
259
        $pList = explode(',', $proxies);
260
        $resultList = [];
261
        foreach ($pList as $proxy) {
262
            $resultList[] = trim($proxy);
263
        }
264
        self::setTrustedProxies($resultList);
265
    }
266
267
    /**
268
     * Working with path array data
269
     * @param array|null $pathArray
270
     */
271
    private function setPathdata(array $pathArray = null)
272
    {
273
        if (!Obj::isArray($pathArray) || count($pathArray) < 1) {
274
            return;
275
        }
276
277
        // check if array length is more then 4 basic elements and slice it recursive
278
        if (count($pathArray) > 4) {
279
            $this->setPathdata(array_slice($pathArray, 0, 4));
280
            return;
281
        }
282
283
        // Switch path array as reverse without break point! Caution: drugs inside!
284
        switch (count($pathArray)) {
285
            case 4:
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
286
                $this->argumentAdd = $pathArray[3];
287
            case 3:
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
288
                $this->argumentId = $pathArray[2];
289
            case 2:
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
290
                $this->action = ucfirst(Str::lowerCase($pathArray[1]));
291
            case 1:
292
                $this->controller = ucfirst(Str::lowerCase($pathArray[0]));
293
                break;
294
        }
295
    }
296
297
    /**
298
     * Get pathway as string
299
     * @return string
300
     */
301
    public function getPathInfo()
302
    {
303
        $route = $this->languageInPath ? Str::sub(parent::getPathInfo(), Str::length($this->language) + 1) : parent::getPathInfo();
304
        if (!Str::startsWith('/', $route)) {
305
            $route = '/' . $route;
306
        }
307
        return $route;
308
    }
309
310
    public function languageInPath()
311
    {
312
        return $this->languageInPath;
313
    }
314
315
    /**
316
     * Get current language
317
     * @return string|null
318
     */
319
    public function getLanguage()
320
    {
321
        return $this->language;
322
    }
323
324
    /**
325
     * Set current language
326
     * @param string $lang
327
     * @return bool
328
     */
329
    public function setLanguage($lang)
330
    {
331
        if (Arr::in($lang, App::$Properties->get('languages'))) {
332
            $this->language = $lang;
333
            return true;
334
        }
335
336
        return false;
337
    }
338
339
    /**
340
     * Get current controller name
341
     * @return string
342
     */
343
    public function getController()
344
    {
345
        return $this->controller;
346
    }
347
348
    /**
349
     * Get current controller action() name
350
     * @return string
351
     */
352
    public function getAction()
353
    {
354
        return $this->action;
355
    }
356
357
    /**
358
     * Get current $id argument for controller action
359
     * @return string|null
360
     */
361
    public function getID()
362
    {
363
        return urldecode($this->argumentId);
364
    }
365
366
    /**
367
     * Set current controller name
368
     * @param string $name
369
     */
370
    public function setController($name)
371
    {
372
        $this->controller = $name;
373
    }
374
375
    /**
376
     * Set current action value
377
     * @param string $name
378
     */
379
    public function setAction($name)
380
    {
381
        $this->action = $name;
382
    }
383
384
    /**
385
     * Set current id argument value
386
     * @param mixed $name
387
     */
388
    public function setId($name)
389
    {
390
        $this->argumentId = $name;
391
    }
392
393
    /**
394
     * Set current add argument value
395
     * @param mixed $name
396
     */
397
    public function setAdd($name)
398
    {
399
        $this->argumentAdd = $name;
400
    }
401
402
    /**
403
     * Get current $add argument for controller action
404
     * @return string|null
405
     */
406
    public function getAdd()
407
    {
408
        return urldecode($this->argumentAdd);
409
    }
410
411
    /**
412
     * Get callback class alias if exist
413
     * @return bool|string
414
     */
415
    public function getCallbackAlias()
416
    {
417
        return $this->callbackClass;
418
    }
419
420
    /**
421
     * Get static alias of pathway if exist
422
     * @return bool
423
     */
424
    public function getStaticAlias()
425
    {
426
        return $this->aliasPathTarget;
427
    }
428
429
    /**
430
     * Check if current request is aliased by dynamic or static rule
431
     * @return bool
432
     */
433
    public function isPathInjected()
434
    {
435
        return $this->callbackClass !== false || $this->aliasPathTarget !== false;
436
    }
437
438
    /**
439
     * Get pathway without current controller/action path
440
     * @return string
441
     */
442
    public function getPathWithoutControllerAction()
443
    {
444
        $path = trim($this->getPathInfo(), '/');
445
        if ($this->aliasPathTarget !== false) {
446
            $path = trim($this->aliasPathTarget, '/');
447
        }
448
        $pathArray = explode('/', $path);
449
        if ($pathArray[0] === Str::lowerCase($this->getController())) {
450
            // unset controller
451
            array_shift($pathArray);
452
            if ($pathArray[0] === Str::lowerCase($this->getAction())) {
453
                // unset action
454
                array_shift($pathArray);
455
            }
456
        }
457
        return trim(implode('/', $pathArray), '/');
458
    }
459
460
    /**
461
     * Get current full request URI
462
     * @return string
463
     */
464
    public function getFullUrl()
465
    {
466
        return $this->getSchemeAndHttpHost() . $this->getRequestUri();
467
    }
468
469
    /**
470
     * Get base path from current environment without basePath of subdirectories
471
     * @return string
472
     */
473
    public function getInterfaceSlug()
474
    {
475
        $path = $this->getBasePath();
476
        $subDir = App::$Properties->get('basePath');
477
        if ($subDir !== '/') {
478
            $offset = (int)Str::length($subDir);
479
            $path = Str::sub($path, --$offset);
480
        }
481
        return $path;
482
    }
483
484
}