Passed
Push — dependabot/composer/doctrine/d... ( 71e71a...50cb9e )
by
unknown
42:38 queued 27:52
created

Url::getDefaultActionForCurrentModule()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1.037

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 3
cp 0.6667
crap 1.037
rs 10
c 1
b 0
f 0
1
<?php
2
3
namespace Backend\Core\Engine;
4
5
use Backend\Core\Config;
6
use Backend\Core\Engine\Base\Config as BackendBaseConfig;
7
use Backend\Core\Engine\Model as BackendModel;
8
use Common\Exception\RedirectException;
9
use ForkCMS\App\KernelLoader;
10
use Symfony\Component\HttpFoundation\RedirectResponse;
11
use Symfony\Component\HttpFoundation\Request;
12
use Symfony\Component\HttpFoundation\Response;
13
use Symfony\Component\HttpKernel\KernelInterface;
14
use Backend\Core\Language\Language as BackendLanguage;
15
16
/**
17
 * This class will handle the incoming URL.
18
 */
19
class Url extends KernelLoader
20
{
21
    /**
22
     * The Symfony request object
23
     *
24
     * @var Request
25
     */
26
    private $request;
27
28
    /**
29
     * The current action
30
     *
31
     * @var string
32
     */
33
    protected $action;
34
35
    /**
36
     * The current module
37
     *
38
     * @var string
39
     */
40
    protected $module;
41
42
    /**
43
     * @param KernelInterface $kernel
44
     */
45 131
    public function __construct(KernelInterface $kernel)
46
    {
47 131
        parent::__construct($kernel);
48
49 131
        $this->getContainer()->set('url', $this);
50
51 131
        $this->processQueryString();
52 131
    }
53
54
    /**
55
     * Get the domain
56
     *
57
     * @return string The current domain (without www.)
58
     */
59
    public function getDomain(): string
60
    {
61
        // replace
62
        return str_replace('www.', '', BackendModel::getRequest()->getHttpHost());
63
    }
64
65
    /**
66
     * Get the full querystring
67
     *
68
     * @return string
69
     */
70 24
    public function getQueryString(): string
71
    {
72 24
        return trim((string) BackendModel::getRequest()->getRequestUri(), '/');
73
    }
74
75 131
    private function getLanguageFromUrl(): string
76
    {
77 131
        if (!array_key_exists(BackendModel::getRequest()->attributes->get('_locale'), BackendLanguage::getWorkingLanguages())) {
78 1
            $url = $this->getBaseUrlForLanguage($this->getContainer()->getParameter('site.default_language'));
79 1
            $url .= '/' . BackendModel::getRequest()->attributes->get('module') . '/' . BackendModel::getRequest()->attributes->get('action');
80
81 1
            if (BackendModel::getRequest()->getQueryString() !== null) {
82
                $url .= '?' . BackendModel::getRequest()->getQueryString();
83
            }
84
85 1
            $this->redirect($url);
86
        }
87
88 131
        return BackendModel::getRequest()->attributes->get('_locale');
89
    }
90
91 131
    private function getModuleFromRequest(): string
92
    {
93 131
        $module = BackendModel::getRequest()->attributes->get('module');
94 131
        if (empty($module)) {
95 1
            return 'Dashboard';
96
        }
97
98 131
        return \SpoonFilter::toCamelCase($module);
99
    }
100
101 131
    private function getActionFromRequest(string $module, string $language): string
102
    {
103 131
        $action = BackendModel::getRequest()->attributes->get('action');
104 131
        if (!empty($action)) {
105 131
            return \SpoonFilter::toCamelCase($action);
106
        }
107
108 67
        return $this->getDefaultActionForModule($module, $language);
109
    }
110
111 1
    final public function getDefaultActionForCurrentModule(string $language = null): string
112
    {
113 1
        return $this->getDefaultActionForModule($this->module, $language ?? $this->getLanguageFromUrl());
114
    }
115
116 67
    private function getDefaultActionForModule(string $module, string $language): string
117
    {
118
        // Check if we can load the config file
119 67
        $configClass = 'Backend\\Modules\\' . $module . '\\Config';
120 67
        if ($module === 'Core') {
121
            $configClass = Config::class;
122
        }
123
124 67
        if (!class_exists($configClass)) {
125
            if (BackendModel::getContainer()->getParameter('kernel.debug')) {
126
                throw new Exception('The config file for the module (' . $module . ') can\'t be found.');
127
            }
128
129
            $this->redirectWithQueryString(
130
                $language,
131
                '/error?type=action-not-allowed',
132
                Response::HTTP_TEMPORARY_REDIRECT
133
            );
134
        }
135
136
        /** @var BackendBaseConfig $config */
137 67
        $config = new $configClass($this->getKernel(), $module);
138
139 67
        return $config->getDefaultAction() ?? 'Index';
140
    }
141
142 131
    private function processQueryString(): void
143
    {
144 131
        if (BackendModel::getRequest()->attributes->get('_route') === 'backend_ajax') {
145
            $this->processAjaxRequest();
146
147
            return;
148
        }
149
150 131
        $language = $this->getLanguageFromUrl();
151 131
        $module = $this->getModuleFromRequest();
152 131
        $action = $this->getActionFromRequest($module, $language);
153
154 131
        $this->processRegularRequest($module, $action, $language);
155 131
    }
156
157
    private function splitUpForkData(array $forkData): array
158
    {
159
        $language = $forkData['language'] ?? '';
160
161
        if ($language === '') {
162
            $language = $this->getContainer()->getParameter('site.default_language');
163
        }
164
165
        return [
166
            $forkData['module'] ?? '',
167
            $forkData['action'] ?? '',
168
            $language,
169
        ];
170
    }
171
172
    private function getForkData(): array
173
    {
174
        if (BackendModel::getRequest()->request->has('fork')) {
175
            return $this->splitUpForkData((array) BackendModel::getRequest()->request->get('fork'));
176
        }
177
178
        if (BackendModel::getRequest()->query->has('fork')) {
179
            return $this->splitUpForkData((array) BackendModel::getRequest()->query->get('fork'));
180
        }
181
182
        return $this->splitUpForkData(BackendModel::getRequest()->query->all());
183
    }
184
185
    private function processAjaxRequest(): void
186
    {
187
        [$module, $action, $language] = $this->getForkData();
188
189
        $this->setAction($action, $module);
190
        BackendLanguage::setWorkingLanguage($language);
191
    }
192
193 131
    private function processRegularRequest(string $module, string $action, string $language): void
194
    {
195
        // the person isn't logged in? or the module doesn't require authentication
196 131
        if (!Authentication::isLoggedIn() && !Authentication::isAllowedModule($module)) {
197 24
            $this->redirectWithQueryString($language, '/authentication');
198
        }
199
200 131
        if (Authentication::isLoggedIn() && !Authentication::isAllowedModule($module)) {
201
            if ($module === 'Dashboard') {
202
                $this->redirectToFistAvailableLink(
203
                    $language,
204
                    $this->getContainer()->get('cache.backend_navigation')->get()
205
                );
206
            }
207
208
            $this->redirectWithQueryString(
209
                $language,
210
                '/error?type=module-not-allowed',
211
                Response::HTTP_TEMPORARY_REDIRECT
212
            );
213
        }
214
215 131
        if (!Authentication::isAllowedAction($action, $module)) {
216
            $this->redirectWithQueryString(
217
                $language,
218
                '/error?type=action-not-allowed',
219
                Response::HTTP_TEMPORARY_REDIRECT
220
            );
221
        }
222
223
        // set the working language, this is not the interface language
224 131
        BackendLanguage::setWorkingLanguage($language);
225
226 131
        $this->setLocale();
227 131
        $this->setModule($module);
228 131
        $this->setAction($action);
229 131
    }
230
231 131
    private function setLocale(): void
232
    {
233 131
        $defaultLocale = $this->get('fork.settings')->get('Core', 'default_interface_language');
234 131
        $locale = $this->getInterfaceLanguage();
235 131
        $possibleLocale = array_keys(BackendLanguage::getInterfaceLanguages());
236
237
        // set the default if the detected interface language is not enabled
238 131
        if (!in_array($locale, $possibleLocale, true)) {
239
            $locale = $defaultLocale;
240
        }
241
242 131
        BackendLanguage::setLocale($locale);
243 131
    }
244
245 131
    private function getInterfaceLanguage(): string
246
    {
247 131
        $default = $this->get('fork.settings')->get('Core', 'default_interface_language', SITE_DEFAULT_LANGUAGE);
248
249 131
        if (Authentication::getUser()->isAuthenticated()) {
250 40
            return Authentication::getUser()->getSetting('interface_language', $default);
251
        }
252
253 131
        if ($this->getContainer()->get('fork.cookie')->has('interface_language')) {
254
            // no authenticated user, but available from a cookie
255 71
            return $this->getContainer()->get('fork.cookie')->get('interface_language');
256
        }
257
258 131
        return $default;
259
    }
260
261 24
    private function getBaseUrlForLanguage(string $language): string
262
    {
263 24
        return '/' . NAMED_APPLICATION . '/' . $language;
264
    }
265
266 24
    private function redirectWithQueryString(string $language, string $url, int $code = Response::HTTP_FOUND): void
267
    {
268
        // add a / at the start if needed
269 24
        if (mb_strpos($url, '/') !== 0) {
270
            $url = '/' . $url;
271
        }
272
273 24
        $this->redirect($this->getBaseUrlForLanguage($language) . $this->addQueryStringToUrl($url), $code);
274
    }
275
276 24
    private function addQueryStringToUrl(string $url): string
277
    {
278 24
        $queryString = 'querystring=' . rawurlencode('/' . $this->getQueryString());
279
280 24
        if (mb_strpos($url, '?') !== false) {
281
            return $url . '&' . $queryString;
282
        }
283
284 24
        return $url . '?' . $queryString;
285
    }
286
287
    private function redirectToFistAvailableLink(string $language, array $navigation): void
288
    {
289
        foreach ($navigation as $navigationItem) {
290
            list($module, $action) = explode('/', $navigationItem['url']);
291
            $module = \SpoonFilter::toCamelCase($module);
292
            $action = \SpoonFilter::toCamelCase($action);
293
294
            if (Authentication::isAllowedModule($module) && Authentication::isAllowedAction($action, $module)) {
295
                $this->redirect(
296
                    $this->getBaseUrlForLanguage($language) . '/' . $navigationItem['url'],
297
                    Response::HTTP_TEMPORARY_REDIRECT
298
                );
299
            }
300
301
            if (array_key_exists('children', $navigationItem)) {
302
                $this->redirectToFistAvailableLink($language, $navigationItem['children']);
303
            }
304
        }
305
    }
306
307
    /**
308
     * Redirect to a given URL
309
     *
310
     * @param string $url The URL to redirect to.
311
     * @param int $code The redirect code, default is 302 which means this is a temporary redirect.
312
     *
313
     * @throws RedirectException
314
     */
315 131
    public function redirect(string $url, int $code = Response::HTTP_FOUND): void
316
    {
317 131
        throw new RedirectException('Redirect', new RedirectResponse($url, $code));
318
    }
319
320
    /**
321
     * Helper method to create a redirect to the error page of the backend
322
     *
323
     * @param string $type
324
     * @param int $code
325
     */
326
    public function redirectToErrorPage(string $type, int $code = Response::HTTP_BAD_REQUEST): void
327
    {
328
        $errorUrl = '/' . NAMED_APPLICATION . '/' . BackendModel::getRequest()->getLocale()
329
                    . '/error?type=' . $type;
330
331
        $this->get('url')->redirect($errorUrl, $code);
332
    }
333
334 131
    public function getAction(): string
335
    {
336 131
        return $this->action;
337
    }
338
339 131
    public function getModule(): string
340
    {
341 131
        if ($this->module === null) {
342
            throw new Exception('Module has not yet been set.');
343
        }
344
345 131
        return $this->module;
346
    }
347
348 131
    private function setAction(string $action, string $module = null): void
349
    {
350
        // set module
351 131
        if ($module !== null) {
352
            $this->setModule($module);
353
        }
354
355
        // is this action allowed?
356 131
        if (!Authentication::isAllowedAction($action, $this->getModule())) {
357
            // set correct headers
358
            header('HTTP/1.1 403 Forbidden');
359
360
            // throw exception
361
            throw new Exception('Action not allowed.');
362
        }
363
364
        // set property
365 131
        $this->action = \SpoonFilter::toCamelCase($action);
366 131
    }
367
368 131
    private function setModule(string $module): void
369
    {
370
        // is this module allowed?
371 131
        if (!Authentication::isAllowedModule($module)) {
372
            // set correct headers
373
            header('HTTP/1.1 403 Forbidden');
374
375
            // throw exception
376
            throw new Exception('Module not allowed.');
377
        }
378
379
        // set property
380 131
        $this->module = $module;
381 131
    }
382
}
383