Completed
Push — master ( 0b8712...2f8bc7 )
by Mihail
04:21
created

Request::getController()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 1 Features 0
Metric Value
c 4
b 1
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
namespace Ffcms\Core\Network;
4
5
use Ffcms\Core\Helper\Type\Arr;
6
use Ffcms\Core\Helper\Type\Obj;
7
use Ffcms\Core\Helper\Type\Str;
8
use Symfony\Component\HttpFoundation\Request as FoundationRequest;
9
use Symfony\Component\HttpFoundation\RedirectResponse as Redirect;
10
use Ffcms\Core\App;
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->runMultiLanguage();
37
        $this->runPathBinding();
38
        $this->loadTrustedProxies();
39
    }
40
41
    /**
42
     * Sets the parameters for this request.
43
     *
44
     * This method also re-initializes all properties.
45
     *
46
     * @param array $query The GET parameters
47
     * @param array $request The POST parameters
48
     * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
49
     * @param array $cookies The COOKIE parameters
50
     * @param array $files The FILES parameters
51
     * @param array $server The SERVER parameters
52
     * @param string $content The raw body data
53
     *
54
     * @api
55
     */
56
    public function initialize(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null)
57
    {
58
        parent::initialize($query, $request, $attributes, $cookies, $files, $server, $content);
59
60
        $basePath = trim(App::$Properties->get('basePath'), '/');
61
        if ($basePath !== null && Str::length($basePath) > 0) {
62
            $basePath = '/' . $basePath;
63
        }
64
65
        if (!defined('env_no_uri') || env_no_uri === false) {
66
            $basePath .= '/' . strtolower(env_name);
67
        }
68
69
        // we never try to use path's without friendly url's
70
        $this->basePath = $this->baseUrl = $basePath;
71
    }
72
73
    /**
74
     * Build multi language pathway binding.
75
     */
76
    private function runMultiLanguage()
77
    {
78
        // multi-language is disabled, use default language
79
        if (App::$Properties->get('multiLanguage') !== true) {
80
            $this->language = App::$Properties->get('singleLanguage');
81
        } else {
82
            // maybe its a language domain alias?
83
            if (Obj::isArray(App::$Properties->get('languageDomainAlias'))) {
84
                /** @var array $domainAlias */
85
                $domainAlias = App::$Properties->get('languageDomainAlias');
86
                if (Obj::isArray($domainAlias) && !Str::likeEmpty($domainAlias[$this->getHost()])) {
87
                    $this->language = $domainAlias[$this->getHost()];
88
                }
89
            } else {
90
                // try to find language in pathway
91
                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...
92
                    if (Str::startsWith('/' . $lang, $this->getPathInfo())) {
93
                        $this->language = $lang;
94
                        $this->languageInPath = true;
95
                    }
96
                }
97
98
                // try to find in ?lang get
99
                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...
100
                    $this->language = $this->query->get('lang');
101
                }
102
103
                // language still not defined?!
104
                if ($this->language === null) {
105
                    $userLang = App::$Properties->get('singleLanguage');
106
                    $browserAccept = $this->getLanguages();
107
                    if (Obj::isArray($browserAccept) && count($browserAccept) > 0) {
108
                        foreach ($browserAccept as $bLang) {
109
                            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...
110
                                $userLang = $bLang;
111
                                break; // stop calculating, language is founded in priority
112
                            }
113
                        }
114
                    }
115
116
                    // parse query string
117
                    $queryString = null;
118
                    if (count($this->query->all()) > 0) {
119
                        $queryString = '?' . http_build_query($this->query->all());
120
                    }
121
122
                    // build response with redirect to language-based path
123
                    $response = new Redirect($this->getSchemeAndHttpHost() . $this->basePath . '/' . $userLang . $this->getPathInfo() . $queryString);
124
                    $response->send();
125
                    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...
126
                }
127
            }
128
        }
129
    }
130
131
    /**
132
     * Build static and dynamic path aliases for working set
133
     */
134
    private function runPathBinding()
135
    {
136
        // calculated depend of language
137
        $pathway = $this->getPathInfo();
138
        /** @var array $routing */
139
        $routing = App::$Properties->getAll('Routing');
140
141
        // try to work with static aliases
142 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...
143
            $pathway = $this->findStaticAliases($routing['Alias'][env_name], $pathway);
144
        }
145
146
        $this->setPathdata(explode('/', trim($pathway, '/')));
147
148
        // set default controller and action for undefined data
149
        if ($this->action == null) {
150
            $this->action = 'Index';
151
        }
152
153
        // empty or contains backslashes? set to main
154
        if ($this->controller == null || Str::contains('\\', $this->controller)) {
155
            $this->controller = 'Main';
156
        }
157
158
        // find callback injection in routing configs (calculated in App::run())
159 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...
160
            $this->findDynamicCallbacks($routing['Callback'][env_name], $this->controller);
161
        }
162
    }
163
164
    /**
165
     * Prepare static pathway aliasing for routing
166
     * @param array|null $map
167
     * @param string|null $pathway
168
     * @return string
169
     */
170
    private function findStaticAliases(array $map = null, $pathway = null)
171
    {
172
        if ($map === null) {
173
            return $pathway;
174
        }
175
176
        // current pathway is found as "old path" (or alias target). Make redirect to new pathway.
177
        if (Arr::in($pathway, $map)) {
178
            // find "new path" as binding uri slug
179
            $binding = array_search($pathway, $map, true);
180
            // build url to redirection
181
            $url = $this->getSchemeAndHttpHost() . $this->getBasePath() . '/';
182
            if (App::$Properties->get('multiLanguage')) {
183
                $url .= $this->language . '/';
184
            }
185
            $url .= ltrim($binding, '/');
186
187
            $redirect = new Redirect($url);
188
            $redirect->send();
189
            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...
190
        }
191
192
        // current pathway request is equal to path alias. Set alias to property.
193
        if (array_key_exists($pathway, $map)) {
194
            $pathway = $map[$pathway];
195
            $this->aliasPathTarget = $pathway;
196
        }
197
198
        return $pathway;
199
    }
200
201
    /**
202
     * Prepare dynamic callback data for routing
203
     * @param array|null $map
204
     * @param string|null $controller
205
     */
206
    private function findDynamicCallbacks(array $map = null, $controller = null)
207
    {
208
        if ($map === null) {
209
            return;
210
        }
211
212
        // try to find global callback for this controller slug
213
        if (array_key_exists($controller, $map)) {
214
            $class = (string)$map[$controller];
215
            if (!Str::likeEmpty($class)) {
216
                $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...
217
            }
218
        }
219
    }
220
221
    /**
222
     * Set trusted proxies from configs
223
     */
224
    private function loadTrustedProxies()
225
    {
226
        $proxies = App::$Properties->get('trustedProxy');
227
        if ($proxies === null || Str::likeEmpty($proxies)) {
228
            return;
229
        }
230
231
        $pList = explode(',', $proxies);
232
        $resultList = [];
233
        foreach ($pList as $proxy) {
234
            $resultList[] = trim($proxy);
235
        }
236
        self::setTrustedProxies($resultList);
237
    }
238
239
    /**
240
     * Working with path array data
241
     * @param array|null $pathArray
242
     */
243
    private function setPathdata(array $pathArray = null)
244
    {
245
        if (!Obj::isArray($pathArray) || count($pathArray) < 1) {
246
            return;
247
        }
248
249
        /**
250
         * Switch path array as reverse without break point! Caution: drugs inside!
251
         */
252
        switch (count($pathArray)) {
253
            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...
254
                $this->argumentAdd = Str::lowerCase($pathArray[3]);
255
            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...
256
                $this->argumentId = Str::lowerCase($pathArray[2]);
257
            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...
258
                $this->action = ucfirst(Str::lowerCase($pathArray[1]));
259
            case 1:
260
                $this->controller = ucfirst(Str::lowerCase($pathArray[0]));
261
                break;
262
        }
263
    }
264
265
    /**
266
     * Get pathway as string
267
     * @return string
268
     */
269
    public function getPathInfo()
270
    {
271
        $route = $this->languageInPath ? Str::sub(parent::getPathInfo(), Str::length($this->language) + 1) : parent::getPathInfo();
272
        if (!Str::startsWith('/', $route)) {
273
            $route = '/' . $route;
274
        }
275
        return $route;
276
    }
277
278
    public function languageInPath()
279
    {
280
        return $this->languageInPath;
281
    }
282
283
    /**
284
     * Get current language
285
     * @return string|null
286
     */
287
    public function getLanguage()
288
    {
289
        return $this->language;
290
    }
291
292
    /**
293
     * Get current controller name
294
     * @return string
295
     */
296
    public function getController()
297
    {
298
        return $this->controller;
299
    }
300
301
    /**
302
     * Get current controller action() name
303
     * @return string
304
     */
305
    public function getAction()
306
    {
307
        return $this->action;
308
    }
309
310
    /**
311
     * Get current $id argument for controller action
312
     * @return string|null
313
     */
314
    public function getID()
315
    {
316
        return urldecode($this->argumentId);
317
    }
318
319
    /**
320
     * Get current $add argument for controller action
321
     * @return string|null
322
     */
323
    public function getAdd()
324
    {
325
        return urldecode($this->argumentAdd);
326
    }
327
328
    /**
329
     * Get callback class alias if exist
330
     * @return bool|string
331
     */
332
    public function getCallbackAlias()
333
    {
334
        return $this->callbackClass;
335
    }
336
337
    /**
338
     * Get static alias of pathway if exist
339
     * @return bool
340
     */
341
    public function getStaticAlias()
342
    {
343
        return $this->aliasPathTarget;
344
    }
345
346
    /**
347
     * Check if current request is aliased by dynamic or static rule
348
     * @return bool
349
     */
350
    public function isPathInjected()
351
    {
352
        return $this->callbackClass !== false || $this->aliasPathTarget !== false;
353
    }
354
355
    /**
356
     * Get pathway without current controller/action path
357
     * @return string
358
     */
359
    public function getPathWithoutControllerAction()
360
    {
361
        $path = trim($this->getPathInfo(), '/');
362
        if ($this->aliasPathTarget !== false) {
363
            $path = trim($this->aliasPathTarget, '/');
364
        }
365
        $pathArray = explode('/', $path);
366
        if ($pathArray[0] === Str::lowerCase($this->getController())) {
367
            // unset controller
368
            array_shift($pathArray);
369
            if ($pathArray[0] === Str::lowerCase($this->getAction())) {
370
                // unset action
371
                array_shift($pathArray);
372
            }
373
        }
374
        return trim(implode('/', $pathArray), '/');
375
    }
376
377
    /**
378
     * Get current full request URI
379
     * @return string
380
     */
381
    public function getFullUrl()
382
    {
383
        return $this->getSchemeAndHttpHost() . $this->getRequestUri();
384
    }
385
386
    /**
387
     * Get base path from current environment without basePath of subdirectories
388
     * @return string
389
     */
390
    public function getInterfaceSlug()
391
    {
392
        $path = $this->getBasePath();
393
        $subDir = App::$Properties->get('basePath');
394
        if ($subDir !== '/') {
395
            $offset = (int)Str::length($subDir);
396
            $path = Str::sub($path, --$offset);
397
        }
398
        return $path;
399
    }
400
401
}