forked from jbrinley/WP-Router
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathWP_Router.class.php
294 lines (267 loc) · 6.56 KB
/
WP_Router.class.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
<?php
class WP_Router extends WP_Router_Utility {
const ROUTE_CACHE_OPTION = 'WP_Router_route_hash';
private $routes = array();
/**
* @var WP_Router The one instance of this singleton class
*/
private static $instance;
/**
* Exist!
*
* @static
* @return void
*/
public static function init() {
self::$instance = self::get_instance();
}
/****************************************************
* PUBLIC API
****************************************************/
/**
* Add a new route
*
* @param string $id
* @param array $properties
* @return null|WP_Route
*/
public function add_route( $id, array $properties ) {
if ( $route = $this->create_route($id, $properties) ) {
$this->routes[$id] = $route;
}
return $route;
}
/**
* Get a previously registered route
*
* @param string $id
* @return null|WP_Route
*/
public function get_route( $id ) {
if ( isset($this->routes[$id]) ) {
return $this->routes[$id];
} else {
return NULL;
}
}
/**
* Update each property included in $changes for the given route
*
* @param string $id
* @param array $changes
* @return void
*/
public function edit_route( $id, array $changes ) {
if ( !isset($this->routes[$id]) ) {
return;
}
foreach ( $changes as $key => $value ) {
if ( $key != 'id' ) {
try {
$this->routes[$id]->set($key, $value);
} catch ( Exception $e ) {
// Error setting the property. Failing silently
}
}
}
}
/**
* Get rid of the route with the given $id
*
* @param string $id
* @return void
*/
public function remove_route( $id ) {
if ( isset($this->routes[$id]) ) {
unset($this->routes[$id]);
}
}
/**
* Get the URL to access the given route with the given arguments
*
* @param string $route_id
* @param array $arguments
* @return string The url to the route, or the home URL if the route doesn't exist
*/
public function get_url( $route_id, $arguments = array() ) {
$route = $this->get_route($route_id);
if ( !$route ) {
return home_url();
} else {
return $route->url($arguments);
}
}
/****************************************************
* PLUMBING
****************************************************/
/*
* Singleton Design Pattern
* ------------------------------------------------- */
public static function get_instance() {
if ( !self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Hook into WordPress
*/
private function __construct() {
add_action('init', array($this, 'generate_routes'), 1000, 0);
add_action('parse_request', array($this, 'parse_request'), 10, 1);
add_filter('rewrite_rules_array', array($this, 'add_rewrite_rules'), 10, 1);
add_filter('query_vars', array($this, 'add_query_vars'), 10, 1);
}
private function __clone() {
// cannot be cloned
trigger_error(__CLASS__.' may not be cloned', E_USER_ERROR);
}
private function __sleep() {
// cannot be serialized
trigger_error(__CLASS__.' may not be serialized', E_USER_ERROR);
}
/**
* WordPress hook callbacks
* ------------------------------------------------- */
/**
* Announce to other plugins that it's time to create rules
* Action: init
*
* @uses do_action() Calls 'wp_router_generate_routes'
* @uses do_action() Calls 'wp_router_alter_routes'
* @return void
*/
public function generate_routes() {
do_action('wp_router_generate_routes', $this);
do_action('wp_router_alter_routes', $this);
$rules = $this->rewrite_rules();
if ( $this->hash($rules) != get_option(self::ROUTE_CACHE_OPTION) ) {
$this->flush_rewrite_rules();
}
}
/**
* Update WordPress's rewrite rules array with registered routes
* Filter: rewrite_rules_array
*
* @param array $rules
* @return array
*/
public function add_rewrite_rules( $rules ) {
$new_rules = $this->rewrite_rules();
update_option(self::ROUTE_CACHE_OPTION, $this->hash($new_rules));
return $new_rules + $rules;
}
/**
* Add all query vars from registered routes to WP's recognized query vars
*
* @param array $vars
* @return array
*/
public function add_query_vars( $vars ) {
$route_vars = $this->query_vars();
$vars = array_merge($vars, $route_vars);
return $vars;
}
/**
* If a callback is in order, call it.
* Action: parse_request
*
* @param WP $query
* @return
*/
public function parse_request( WP $query ) {
$this->redirect_placeholder($query);
if ( $id = $this->identify_route($query) ) {
$this->routes[$id]->execute($query);
}
}
/**
* Redirect the placeholder page back to the front page
*
* @param WP|WP_Query $query
*/
protected function redirect_placeholder( $query ) {
// we'll only get a 'wp_router_page' query var when visiting
// the page for a WP Router post, and there's only one of those
if ( !empty( $query->query_vars[WP_Router_Page::POST_TYPE]) ) {
wp_redirect( home_url(), 303 );
exit();
}
}
/**
* Identify the route based on the request's query variables
*
* @param WP|WP_Query $query
* @return string|NULL
*/
protected function identify_route( $query ) {
if ( !isset($query->query_vars[self::QUERY_VAR]) ) {
return NULL;
}
$id = $query->query_vars[self::QUERY_VAR];
if ( !isset($this->routes[$id]) || ! $this->routes[$id] instanceof WP_Route ) {
return NULL;
}
return $id;
}
/**
* Create a new WP_Route with the given id and properties
*
* protected so it can be mocked in testing
*
* @param string $id
* @param array $properties
* @return null|WP_Route
*/
protected function create_route( $id, array $properties ) {
try {
$route = new WP_Route($id, $properties);
} catch ( Exception $e ) {
// invalid route $properties
return NULL;
}
return $route;
}
/**
* Get the array of rewrite rules from all registered routes
*
* @return array
*/
protected function rewrite_rules() {
$rules = array();
foreach ( $this->routes as $id => $route ) {
$rules = array_merge($rules, $route->rewrite_rules());
}
return $rules;
}
/**
* Get an array of all query vars used by registered routes
*
* @return array
*/
protected function query_vars() {
$vars = array();
foreach ( $this->routes as $id => $route ) {
$vars = array_merge($vars, $route->get_query_vars());
}
$vars[] = self::QUERY_VAR;
return $vars;
}
/**
* Create a hash of the registered rewrite rules
*
* @param array $rules
* @return string
*/
protected function hash( $rules ) {
return md5(serialize($rules));
}
/**
* Tell WordPress to flush its rewrite rules
*
* @return void
*/
protected function flush_rewrite_rules() {
flush_rewrite_rules();
}
}