Completed
Push — master ( 9154e6...0b8712 )
by Mihail
03:12
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->runPathAliasing();
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 runPathAliasing()
135
    {
136
        // calculated depend of language
137
        $pathway = $this->getPathInfo();
138
        $routing = App::$Properties->getAll('Routing');
139
140
        // try to find static routing alias
141
        /** @var bool|array $aliasMap */
142
        $aliasMap = false;
143 View Code Duplication
        if (isset($routing['Alias']) && isset($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
            $aliasMap = $routing['Alias'][env_name];
145
        }
146
147
        if (Obj::isArray($aliasMap) && array_key_exists($pathway, $aliasMap)) {
148
            $pathway = $aliasMap[$pathway];
149
            $this->aliasPathTarget = $pathway;
150
        }
151
152
        // check if pathway is the same with target and redirect to source from static routing
153
        if (Obj::isArray($aliasMap) && Arr::in($this->getPathInfo(), $aliasMap)) {
154
            $source = array_search($this->getPathInfo(), $aliasMap);
155
            $targetUri = $this->getSchemeAndHttpHost() . $this->getBasePath() . '/';
156
            if (App::$Properties->get('multiLanguage') === true) {
157
                $targetUri .= $this->language . '/';
158
            }
159
            $targetUri .= ltrim($source, '/');
160
            $response = new Redirect($targetUri);
161
            $response->send();
162
            exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method runPathAliasing() 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...
163
        }
164
165
        // define data from pathway
166
        $this->setPathdata(explode('/', trim($pathway, '/')));
167
168
        if ($this->action == null) { // can be null or string(0)""
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% 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...
169
            $this->action = 'Index';
170
        }
171
172
        // empty or contains backslashes? set to main
173
        if ($this->controller == null || Str::contains('\\', $this->controller)) {
174
            $this->controller = 'Main';
175
        }
176
177
        $callbackMap = false;
178 View Code Duplication
        if (isset($routing['Callback']) && isset($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...
179
            $callbackMap = $routing['Callback'][env_name];
180
        }
181
182
        // check is dynamic callback map exist
183
        if (isset($callbackMap[$this->controller])) {
184
            $callbackClass = $callbackMap[$this->controller];
185
            // check if rule for current controller is exist
186
            if ($callbackClass !== null) {
187
                $this->callbackClass = $callbackClass;
188
            }
189
        }
190
    }
191
192
    /**
193
     * Set trusted proxies from configs
194
     */
195
    private function loadTrustedProxies()
196
    {
197
        $proxies = App::$Properties->get('trustedProxy');
198
        if ($proxies === null || Str::likeEmpty($proxies)) {
199
            return;
200
        }
201
202
        $pList = explode(',', $proxies);
203
        $resultList = [];
204
        foreach ($pList as $proxy) {
205
            $resultList[] = trim($proxy);
206
        }
207
        $this->setTrustedProxies($resultList);
208
    }
209
210
    /**
211
     * Working with path array data
212
     * @param array|null $pathArray
213
     */
214
    private function setPathdata(array $pathArray = null)
215
    {
216
        if (!Obj::isArray($pathArray) || count($pathArray) < 1) {
217
            return;
218
        }
219
220
        /**
221
         * Switch path array as reverse without break point! Caution: drugs inside!
222
         */
223
        switch (count($pathArray)) {
224
            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...
225
                $this->argumentAdd = Str::lowerCase($pathArray[3]);
226
            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...
227
                $this->argumentId = Str::lowerCase($pathArray[2]);
228
            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...
229
                $this->action = ucfirst(Str::lowerCase($pathArray[1]));
230
            case 1:
231
                $this->controller = ucfirst(Str::lowerCase($pathArray[0]));
232
                break;
233
        }
234
235
        return;
236
    }
237
238
    /**
239
     * Get pathway as string
240
     * @return string
241
     */
242
    public function getPathInfo()
243
    {
244
        $route = $this->languageInPath ? Str::sub(parent::getPathInfo(), Str::length($this->language) + 1) : parent::getPathInfo();
245
        if (!Str::startsWith('/', $route)) {
246
            $route = '/' . $route;
247
        }
248
        return $route;
249
    }
250
251
    public function languageInPath()
252
    {
253
        return $this->languageInPath;
254
    }
255
256
    /**
257
     * Get current language
258
     * @return string|null
259
     */
260
    public function getLanguage()
261
    {
262
        return $this->language;
263
    }
264
265
    /**
266
     * Get current controller name
267
     * @return string
268
     */
269
    public function getController()
270
    {
271
        return $this->controller;
272
    }
273
274
    /**
275
     * Get current controller action() name
276
     * @return string
277
     */
278
    public function getAction()
279
    {
280
        return $this->action;
281
    }
282
283
    /**
284
     * Get current $id argument for controller action
285
     * @return string|null
286
     */
287
    public function getID()
288
    {
289
        return urldecode($this->argumentId);
290
    }
291
292
    /**
293
     * Get current $add argument for controller action
294
     * @return string|null
295
     */
296
    public function getAdd()
297
    {
298
        return urldecode($this->argumentAdd);
299
    }
300
301
    /**
302
     * Get callback class alias if exist
303
     * @return bool|string
304
     */
305
    public function getCallbackAlias()
306
    {
307
        return $this->callbackClass;
308
    }
309
310
    /**
311
     * Get static alias of pathway if exist
312
     * @return bool
313
     */
314
    public function getStaticAlias()
315
    {
316
        return $this->aliasPathTarget;
317
    }
318
319
    /**
320
     * Check if current request is aliased by dynamic or static rule
321
     * @return bool
322
     */
323
    public function isPathInjected()
324
    {
325
        return $this->callbackClass !== false || $this->aliasPathTarget !== false;
326
    }
327
328
    /**
329
     * Get pathway without current controller/action path
330
     * @return string
331
     */
332
    public function getPathWithoutControllerAction()
333
    {
334
        $path = trim($this->getPathInfo(), '/');
335
        if ($this->aliasPathTarget !== false) {
336
            $path = trim($this->aliasPathTarget, '/');
337
        }
338
        $pathArray = explode('/', $path);
339
        if ($pathArray[0] === Str::lowerCase($this->getController())) {
340
            // unset controller
341
            array_shift($pathArray);
342
            if ($pathArray[0] === Str::lowerCase($this->getAction())) {
343
                // unset action
344
                array_shift($pathArray);
345
            }
346
        }
347
        return trim(implode('/', $pathArray), '/');
348
    }
349
350
    /**
351
     * Get current full request URI
352
     * @return string
353
     */
354
    public function getFullUrl()
355
    {
356
        return $this->getSchemeAndHttpHost() . $this->getRequestUri();
357
    }
358
359
    /**
360
     * Get base path from current environment without basePath of subdirectories
361
     * @return string
362
     */
363
    public function getInterfaceSlug()
364
    {
365
        $path = $this->getBasePath();
366
        $subDir = App::$Properties->get('basePath');
367
        if ($subDir !== '/') {
368
            $offset = (int)Str::length($subDir);
369
            $path = Str::sub($path, --$offset);
370
        }
371
        return $path;
372
    }
373
374
}