Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
93.62% covered (success)
93.62%
44 / 47
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
WebRouter
93.62% covered (success)
93.62%
44 / 47
75.00% covered (warning)
75.00%
3 / 4
29.22
0.00% covered (danger)
0.00%
0 / 1
 importFromFile
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 clear
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 add
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 route
90.91% covered (success)
90.91%
30 / 33
0.00% covered (danger)
0.00%
0 / 1
22.36
1<?php
2/**
3 * Jingga
4 *
5 * PHP Version 8.1
6 *
7 * @package   phpOMS\Router
8 * @copyright Dennis Eichhorn
9 * @license   OMS License 2.0
10 * @version   1.0.0
11 * @link      https://jingga.app
12 */
13declare(strict_types=1);
14
15namespace phpOMS\Router;
16
17use phpOMS\Account\Account;
18
19/**
20 * Router class for web routes.
21 *
22 * @package phpOMS\Router
23 * @license OMS License 2.0
24 * @link    https://jingga.app
25 * @since   1.0.0
26 */
27final class WebRouter implements RouterInterface
28{
29    /**
30     * Routes.
31     *
32     * @var array<string, array>
33     * @since 1.0.0
34     */
35    private array $routes = [];
36
37    /**
38     * Add routes from file.
39     *
40     * Files need to return a php array of the following structure (see PermissionHandlingTrait):
41     * return [
42     *      '{REGEX_PATH}' => [
43     *          'dest' => '{DESTINATION_NAMESPACE:method}', // use :: for static functions
44     *          'verb' => RouteVerb::{VERB},
45     *          'csrf' => true,
46     *          'permission' => [ // optional
47     *              'module' => '{NAME}',
48     *              'type' => PermissionType::{TYPE},
49     *              'category' => PermissionCategory::{STATE},
50     *          ],
51     *          // define different destination for different verb
52     *      ],
53     *      // define another regex path, destination, permission here
54     * ];
55     *
56     * @param string $path Route file path
57     *
58     * @return bool
59     *
60     * @since 1.0.0
61     */
62    public function importFromFile(string $path) : bool
63    {
64        if (!\is_file($path)) {
65            return false;
66        }
67
68        /** @noinspection PhpIncludeInspection */
69        $this->routes += include $path;
70
71        return true;
72    }
73
74    /**
75     * Clear routes
76     *
77     * @return void
78     * @since 1.0.0
79     */
80    public function clear() : void
81    {
82        $this->routes = [];
83    }
84
85    /**
86     * {@inheritdoc}
87     */
88    public function add(
89        string $route,
90        mixed $destination,
91        int $verb = RouteVerb::GET,
92        bool $csrf = false,
93        array $validation = [],
94        string $dataPattern = ''
95    ) : void
96    {
97        if (!isset($this->routes[$route])) {
98            $this->routes[$route] = [];
99        }
100
101        $this->routes[$route][] = [
102            'dest'       => $destination,
103            'verb'       => $verb,
104            'csrf'       => $csrf,
105            'validation' => empty($validation) ? null : $validation,
106            'pattern'    => empty($dataPattern) ? null : $dataPattern,
107        ];
108    }
109
110    /**
111     * {@inheritdoc}
112     */
113    public function route(
114        string $uri,
115        string $csrf = null,
116        int $verb = RouteVerb::GET,
117        int $app = null,
118        int $unitId = null,
119        Account $account = null,
120        array $data = null
121    ) : array
122    {
123        $bound = [];
124        foreach ($this->routes as $route => $destination) {
125            if (!((bool) \preg_match('~^' . $route . '$~', $uri))) {
126                continue;
127            }
128
129            foreach ($destination as $d) {
130                if ($d['verb'] === RouteVerb::ANY
131                    || $verb === RouteVerb::ANY
132                    || ($verb & $d['verb']) === $verb
133                ) {
134                    // if csrf is required but not set
135                    if (isset($d['csrf']) && $d['csrf'] && $csrf === null) {
136                        return ['dest' => RouteStatus::INVALID_CSRF];
137                    }
138
139                    // if permission check is invalid
140                    if (isset($d['permission']) && !empty($d['permission'])
141                        && ($account === null || $account->getId() === 0)
142                    ) {
143                        return ['dest' => RouteStatus::NOT_LOGGED_IN];
144                    } elseif (isset($d['permission']) && !empty($d['permission'])
145                        && !($account?->hasPermission(
146                                $d['permission']['type'] ?? 0,
147                                $d['permission']['unit'] ?? $unitId,
148                                $app,
149                                $d['permission']['module'] ?? null,
150                                $d['permission']['category'] ?? null
151                            )
152                        )
153                    ) {
154                        return ['dest' => RouteStatus::INVALID_PERMISSIONS];
155                    }
156
157                    // if validation check is invalid
158                    if (isset($d['validation'])) {
159                        foreach ($d['validation'] as $name => $validation) {
160                            if (!isset($data[$name]) || \preg_match($validation, $data[$name]) !== 1) {
161                                return ['dest' => RouteStatus::INVALID_DATA];
162                            }
163                        }
164                    }
165
166                    $temp = ['dest' => $d['dest']];
167
168                    // fill data
169                    if (isset($d['pattern'])) {
170                        \preg_match($d['pattern'], $uri, $matches);
171
172                        $temp['data'] = $matches;
173                    }
174
175                    $bound[] = $temp;
176                }
177            }
178        }
179
180        return $bound;
181    }
182}