Router   A
last analyzed

Complexity

Total Complexity 30

Size/Duplication

Total Lines 198
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 57
c 2
b 0
f 0
dl 0
loc 198
rs 10
wmc 30

14 Methods

Rating   Name   Duplication   Size   Complexity  
A init() 0 7 2
A matchRequest() 0 13 4
A __construct() 0 3 1
A registerRoutes() 0 13 3
A hooks() 0 6 1
A addRule() 0 6 1
A compile() 0 6 2
A generateRouteRegex() 0 3 1
A match() 0 13 3
A getRouteVariable() 0 3 1
A addRoute() 0 3 1
A deleteRoute() 0 4 2
A callRouteHook() 0 6 3
A loadRouteTemplate() 0 17 5
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|null
14
     */
15
    private static $instance;
16
17
    /**
18
     * @var \TwinDigital\WPTools\Router\Route|null
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) {
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)
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
88
            && in_array('route_not_found', $matchedRoute->get_error_codes())
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()) {
101
            return $template;
102
        }
103
104
        if (file_exists($this->matchedRoute->getTemplate())) {
105
            return $this->matchedRoute->getTemplate();
106
        }
107
108
        $routeTemplate = get_query_template($this->matchedRoute->getTemplate());
109
110
        if (!empty($routeTemplate)) {
111
            $template = $routeTemplate;
112
        }
113
114
        return $template;
115
    }
116
117
    public function callRouteHook()
118
    {
119
        if (!$this->matchedRoute instanceof Route || !$this->matchedRoute->hasHook()) {
120
            return;
121
        }
122
        do_action($this->matchedRoute->getHook());
123
    }
124
125
    /**
126
     * @param string $name
127
     * @param Route  $route
128
     * @return void
129
     */
130
    public function addRoute(string $name, Route $route)
131
    {
132
        $this->routes[$name] = $route;
133
    }
134
135
    public function compile()
136
    {
137
        add_rewrite_tag('%' . $this->routeVariable . '%', '(.+)');
138
139
        foreach ($this->routes as $name => $route) {
140
            $this->addRule($name, $route);
141
        }
142
    }
143
144
    /**
145
     * Adds a new WordPress rewrite rule for the given Route.
146
     *
147
     * @param string $name
148
     * @param Route  $route
149
     * @param string $position
150
     */
151
    private function addRule($name, Route $route, $position = 'top')
152
    {
153
        add_rewrite_rule(
154
            $this->generateRouteRegex($route),
155
            'index.php?' . $this->routeVariable . '=' . $name,
156
            $position
157
        );
158
    }
159
160
    /**
161
     * Generates the regex for the WordPress rewrite API for the given route.
162
     *
163
     * @param Route $route
164
     *
165
     * @return string
166
     */
167
    private function generateRouteRegex(Route $route)
168
    {
169
        return '^' . ltrim(trim($route->getPath()), '/') . '$';
170
    }
171
172
    /**
173
     * Tries to find a matching route using the given query variables. Returns the matching route
174
     * or a WP_Error.
175
     *
176
     * @param array $queryVariables
177
     *
178
     * @return Route|\WP_Error
179
     */
180
    public function match(array $queryVariables)
181
    {
182
        if (empty($queryVariables[$this->routeVariable])) {
183
            return new \WP_Error('missing_route_variable');
184
        }
185
186
        $routeName = $queryVariables[$this->routeVariable];
187
188
        if (!isset($this->routes[$routeName])) {
189
            return new \WP_Error('route_not_found');
190
        }
191
192
        return $this->routes[$routeName];
193
    }
194
195
    /**
196
     * @return string
197
     */
198
    public function getRouteVariable(): string
199
    {
200
        return $this->routeVariable;
201
    }
202
203
    public function deleteRoute(string $name)
204
    {
205
        if (array_key_exists($name, $this->routes)) {
206
            unset($this->routes[$name]);
207
        }
208
    }
209
}
210