Passed
Push — master ( ecde40...94c840 )
by Lucien
01:50
created

Router::addRoute()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 2
dl 0
loc 3
rs 10
1
<?php
2
3
namespace TwinDigital\WPTools\Router;
4
5
/**
6
 * Class Router
7
 * @package TwinDigital\WPTools\Router
8
 */
9
class Router
10
{
11
12
    /**
13
     * @var self
14
     */
15
    private static $instance;
16
17
    /**
18
     * @var \TwinDigital\WPTools\Router\Route
19
     */
20
    private $matchedRoute = null;
21
22
    /**
23
     * @var Route[]
24
     */
25
    private $routes = [];
26
27
    /**
28
     * @var string
29
     */
30
    private $routeVariable = 'td_wptools_route';
31
32
    /**
33
     * Make instance or return existing one.
34
     * @return self
35
     */
36
    public static function init(): self
37
    {
38
        if (!self::$instance instanceof self) {
0 ignored issues
show
introduced by
self::instance is always a sub-type of self.
Loading history...
39
            self::$instance = new self();
40
        }
41
42
        return self::$instance;
43
    }
44
45
    public function __construct()
46
    {
47
        $this->hooks();
48
    }
49
50
    private function hooks()
51
    {
52
        add_action('init', [$this, 'registerRoutes']);
53
        add_action('parse_request', [$this, 'matchRequest']);
54
        add_action('template_include', [$this, 'loadRouteTemplate']);
55
        add_action('parse_request', [$this, 'callRouteHook']);
56
    }
57
58
    public function registerRoutes()
59
    {
60
        $routes = apply_filters($this->routeVariable . '_routes', $this->routes);
61
        foreach ($routes as $name => $route) {
62
            $this->addRoute($name, $route);
63
        }
64
        $this->compile();
65
66
        $hash = md5(serialize($this->routes));
67
68
        if ($hash != get_option($this->routeVariable . '_hash')) {
69
            flush_rewrite_rules();
70
            update_option($this->routeVariable . '_hash', $hash);
71
        }
72
    }
73
74
    /**
75
     * @param \WP $environment
76
     * @return void
77
     */
78
    public function matchRequest(\WP $environment)
0 ignored issues
show
Bug introduced by
The type WP was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
79
    {
80
        $matchedRoute = $this->match($environment->query_vars);
81
82
        if ($matchedRoute instanceof Route) {
83
            $this->matchedRoute = $matchedRoute;
84
        }
85
86
        if (
87
            $matchedRoute instanceof \WP_Error
0 ignored issues
show
Bug introduced by
The type WP_Error was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
88
            && in_array('route_not_found', $matchedRoute->get_error_codes())
0 ignored issues
show
Bug introduced by
The method get_error_codes() does not exist on TwinDigital\WPTools\Router\Route. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

88
            && in_array('route_not_found', $matchedRoute->/** @scrutinizer ignore-call */ get_error_codes())

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
89
        ) {
90
            wp_die($matchedRoute, 'Route Not Found', ['response' => 404]);
91
        }
92
    }
93
94
    /**
95
     * @param $template
96
     * @return string
97
     */
98
    public function loadRouteTemplate($template)
99
    {
100
        if (!$this->matchedRoute instanceof Route || !$this->matchedRoute->hasTemplate()) {
0 ignored issues
show
introduced by
$this->matchedRoute is always a sub-type of TwinDigital\WPTools\Router\Route.
Loading history...
101
            return $template;
102
        }
103
104
        $routeTemplate = get_query_template($this->matchedRoute->getTemplate());
105
106
        if (!empty($routeTemplate)) {
107
            $template = $routeTemplate;
108
        }
109
110
        return $template;
111
    }
112
113
    public function callRouteHook()
114
    {
115
        if (!$this->matchedRoute instanceof Route || !$this->matchedRoute->hasHook()) {
0 ignored issues
show
introduced by
$this->matchedRoute is always a sub-type of TwinDigital\WPTools\Router\Route.
Loading history...
116
            return;
117
        }
118
        do_action($this->matchedRoute->getHook());
119
    }
120
121
    /**
122
     * @param string $name
123
     * @param Route  $route
124
     * @return void
125
     */
126
    public function addRoute(string $name, Route $route)
127
    {
128
        $this->routes[$name] = $route;
129
    }
130
131
    public function compile()
132
    {
133
        add_rewrite_tag('%' . $this->routeVariable . '%', '(.+)');
134
135
        foreach ($this->routes as $name => $route) {
136
            $this->addRule($name, $route);
137
        }
138
    }
139
140
    /**
141
     * Adds a new WordPress rewrite rule for the given Route.
142
     *
143
     * @param string $name
144
     * @param Route  $route
145
     * @param string $position
146
     */
147
    private function addRule($name, Route $route, $position = 'top')
148
    {
149
        add_rewrite_rule(
150
            $this->generateRouteRegex($route),
151
            'index.php?' . $this->routeVariable . '=' . $name,
152
            $position
153
        );
154
    }
155
156
    /**
157
     * Generates the regex for the WordPress rewrite API for the given route.
158
     *
159
     * @param Route $route
160
     *
161
     * @return string
162
     */
163
    private function generateRouteRegex(Route $route)
164
    {
165
        return '^' . ltrim(trim($route->getPath()), '/') . '$';
166
    }
167
168
    /**
169
     * Tries to find a matching route using the given query variables. Returns the matching route
170
     * or a WP_Error.
171
     *
172
     * @param array $queryVariables
173
     *
174
     * @return Route|\WP_Error
175
     */
176
    public function match(array $queryVariables)
177
    {
178
        if (empty($queryVariables[$this->routeVariable])) {
179
            return new \WP_Error('missing_route_variable');
180
        }
181
182
        $routeName = $queryVariables[$this->routeVariable];
183
184
        if (!isset($this->routes[$routeName])) {
185
            return new \WP_Error('route_not_found');
186
        }
187
188
        return $this->routes[$routeName];
189
    }
190
191
    /**
192
     * @return string
193
     */
194
    public function getRouteVariable(): string
195
    {
196
        return $this->routeVariable;
197
    }
198
199
    public function deleteRoute(string $name)
200
    {
201
        if (array_key_exists($name, $this->routes)) {
202
            unset($this->routes[$name]);
203
        }
204
    }
205
}
206