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
SocketRouter
93.62% covered (success)
93.62%
44 / 47
75.00% covered (warning)
75.00%
3 / 4
30.23
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
23.40
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 socket 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 SocketRouter 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     *          'permission' => [ // optional
45     *              'module' => '{NAME}',
46     *              'type' => PermissionType::{TYPE},
47     *              'category' => PermissionCategory::{STATE},
48     *          ],
49     *          // define different destination for different verb
50     *      ],
51     *      // define another regex path, destination, permission here
52     * ];
53     *
54     * @param string $path Route file path
55     *
56     * @return bool
57     *
58     * @since 1.0.0
59     */
60    public function importFromFile(string $path) : bool
61    {
62        if (!\is_file($path)) {
63            return false;
64        }
65
66        /** @noinspection PhpIncludeInspection */
67        $this->routes += include $path;
68
69        return true;
70    }
71
72    /**
73     * Clear routes
74     *
75     * @return void
76     * @since 1.0.0
77     */
78    public function clear() : void
79    {
80        $this->routes = [];
81    }
82
83    /**
84     * {@inheritdoc}
85     */
86    public function add(
87        string $route,
88        mixed $destination,
89        int $verb = RouteVerb::GET,
90        bool $csrf = false,
91        array $validation = [],
92        string $dataPattern = ''
93    ) : void
94    {
95        if (!isset($this->routes[$route])) {
96            $this->routes[$route] = [];
97        }
98
99        $this->routes[$route][] = [
100            'dest'       => $destination,
101            'verb'       => $verb,
102            'csrf'       => $csrf,
103            'validation' => empty($validation) ? null : $validation,
104            'pattern'    => empty($dataPattern) ? null : $dataPattern,
105        ];
106    }
107
108    /**
109     * {@inheritdoc}
110     */
111    public function route(
112        string $uri,
113        string $csrf = null,
114        int $verb = RouteVerb::GET,
115        int $app = null,
116        int $unitId = null,
117        Account $account = null,
118        array $data = null
119    ) : array
120    {
121        $bound = [];
122        foreach ($this->routes as $route => $destination) {
123            if (!((bool) \preg_match('~^' . $route . '$~', $uri))) {
124                continue;
125            }
126
127            foreach ($destination as $d) {
128                if ((!isset($d['verb']) || $d['verb'] === RouteVerb::ANY)
129                    || $verb === RouteVerb::ANY
130                    || ($verb & $d['verb']) === $verb
131                ) {
132                    // if csrf is required but not set
133                    if (isset($d['csrf']) && $d['csrf'] && $csrf === null) {
134                        return ['dest' => RouteStatus::INVALID_CSRF];
135                    }
136
137                    // if permission check is invalid
138                    if (isset($d['permission']) && !empty($d['permission'])
139                        && ($account === null || $account->getId() === 0)
140                    ) {
141                        return ['dest' => RouteStatus::NOT_LOGGED_IN];
142                    } elseif (isset($d['permission']) && !empty($d['permission'])
143                        && !($account?->hasPermission(
144                                $d['permission']['type'] ?? 0,
145                                $d['permission']['unit'] ?? $unitId,
146                                $app,
147                                $d['permission']['module'] ?? null,
148                                $d['permission']['category'] ?? null
149                            )
150                        )
151                    ) {
152                        return ['dest' => RouteStatus::INVALID_PERMISSIONS];
153                    }
154
155                    // if validation check is invalid
156                    if (isset($d['validation'])) {
157                        foreach ($d['validation'] as $name => $pattern) {
158                            if (!isset($data[$name]) || \preg_match($pattern, $data[$name]) !== 1) {
159                                return ['dest' => RouteStatus::INVALID_DATA];
160                            }
161                        }
162                    }
163
164                    $temp = ['dest' => $d['dest']];
165
166                    // fill data
167                    if (isset($d['pattern'])) {
168                        \preg_match($d['pattern'], $uri, $matches);
169
170                        $temp['data'] = $matches;
171                    }
172
173                    $bound[] = $temp;
174                }
175            }
176        }
177
178        return $bound;
179    }
180}