Mercurial > hg > Members > shoshi > webvirt
comparison cake/libs/router.php @ 0:261e66bd5a0c
hg init
author | Shoshi TAMAKI <shoshi@cr.ie.u-ryukyu.ac.jp> |
---|---|
date | Sun, 24 Jul 2011 21:08:31 +0900 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:261e66bd5a0c |
---|---|
1 <?php | |
2 /** | |
3 * Parses the request URL into controller, action, and parameters. | |
4 * | |
5 * PHP versions 4 and 5 | |
6 * | |
7 * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) | |
8 * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) | |
9 * | |
10 * Licensed under The MIT License | |
11 * Redistributions of files must retain the above copyright notice. | |
12 * | |
13 * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) | |
14 * @link http://cakephp.org CakePHP(tm) Project | |
15 * @package cake | |
16 * @subpackage cake.cake.libs | |
17 * @since CakePHP(tm) v 0.2.9 | |
18 * @license MIT License (http://www.opensource.org/licenses/mit-license.php) | |
19 */ | |
20 | |
21 /** | |
22 * Parses the request URL into controller, action, and parameters. | |
23 * | |
24 * @package cake | |
25 * @subpackage cake.cake.libs | |
26 */ | |
27 class Router { | |
28 | |
29 /** | |
30 * Array of routes connected with Router::connect() | |
31 * | |
32 * @var array | |
33 * @access public | |
34 */ | |
35 var $routes = array(); | |
36 | |
37 /** | |
38 * List of action prefixes used in connected routes. | |
39 * Includes admin prefix | |
40 * | |
41 * @var array | |
42 * @access private | |
43 */ | |
44 var $__prefixes = array(); | |
45 | |
46 /** | |
47 * Directive for Router to parse out file extensions for mapping to Content-types. | |
48 * | |
49 * @var boolean | |
50 * @access private | |
51 */ | |
52 var $__parseExtensions = false; | |
53 | |
54 /** | |
55 * List of valid extensions to parse from a URL. If null, any extension is allowed. | |
56 * | |
57 * @var array | |
58 * @access private | |
59 */ | |
60 var $__validExtensions = null; | |
61 | |
62 /** | |
63 * 'Constant' regular expression definitions for named route elements | |
64 * | |
65 * @var array | |
66 * @access private | |
67 */ | |
68 var $__named = array( | |
69 'Action' => 'index|show|add|create|edit|update|remove|del|delete|view|item', | |
70 'Year' => '[12][0-9]{3}', | |
71 'Month' => '0[1-9]|1[012]', | |
72 'Day' => '0[1-9]|[12][0-9]|3[01]', | |
73 'ID' => '[0-9]+', | |
74 'UUID' => '[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}' | |
75 ); | |
76 | |
77 /** | |
78 * Stores all information necessary to decide what named arguments are parsed under what conditions. | |
79 * | |
80 * @var string | |
81 * @access public | |
82 */ | |
83 var $named = array( | |
84 'default' => array('page', 'fields', 'order', 'limit', 'recursive', 'sort', 'direction', 'step'), | |
85 'greedy' => true, | |
86 'separator' => ':', | |
87 'rules' => false, | |
88 ); | |
89 | |
90 /** | |
91 * The route matching the URL of the current request | |
92 * | |
93 * @var array | |
94 * @access private | |
95 */ | |
96 var $__currentRoute = array(); | |
97 | |
98 /** | |
99 * Default HTTP request method => controller action map. | |
100 * | |
101 * @var array | |
102 * @access private | |
103 */ | |
104 var $__resourceMap = array( | |
105 array('action' => 'index', 'method' => 'GET', 'id' => false), | |
106 array('action' => 'view', 'method' => 'GET', 'id' => true), | |
107 array('action' => 'add', 'method' => 'POST', 'id' => false), | |
108 array('action' => 'edit', 'method' => 'PUT', 'id' => true), | |
109 array('action' => 'delete', 'method' => 'DELETE', 'id' => true), | |
110 array('action' => 'edit', 'method' => 'POST', 'id' => true) | |
111 ); | |
112 | |
113 /** | |
114 * List of resource-mapped controllers | |
115 * | |
116 * @var array | |
117 * @access private | |
118 */ | |
119 var $__resourceMapped = array(); | |
120 | |
121 /** | |
122 * Maintains the parameter stack for the current request | |
123 * | |
124 * @var array | |
125 * @access private | |
126 */ | |
127 var $__params = array(); | |
128 | |
129 /** | |
130 * Maintains the path stack for the current request | |
131 * | |
132 * @var array | |
133 * @access private | |
134 */ | |
135 var $__paths = array(); | |
136 | |
137 /** | |
138 * Keeps Router state to determine if default routes have already been connected | |
139 * | |
140 * @var boolean | |
141 * @access private | |
142 */ | |
143 var $__defaultsMapped = false; | |
144 | |
145 /** | |
146 * Keeps track of whether the connection of default routes is enabled or disabled. | |
147 * | |
148 * @var boolean | |
149 * @access private | |
150 */ | |
151 var $__connectDefaults = true; | |
152 | |
153 /** | |
154 * Constructor for Router. | |
155 * Builds __prefixes | |
156 * | |
157 * @return void | |
158 */ | |
159 function Router() { | |
160 $this->__setPrefixes(); | |
161 } | |
162 | |
163 /** | |
164 * Sets the Routing prefixes. Includes compatibility for existing Routing.admin | |
165 * configurations. | |
166 * | |
167 * @return void | |
168 * @access private | |
169 * @todo Remove support for Routing.admin in future versions. | |
170 */ | |
171 function __setPrefixes() { | |
172 $routing = Configure::read('Routing'); | |
173 if (!empty($routing['admin'])) { | |
174 $this->__prefixes[] = $routing['admin']; | |
175 } | |
176 if (!empty($routing['prefixes'])) { | |
177 $this->__prefixes = array_merge($this->__prefixes, (array)$routing['prefixes']); | |
178 } | |
179 } | |
180 | |
181 /** | |
182 * Gets a reference to the Router object instance | |
183 * | |
184 * @return Router Instance of the Router. | |
185 * @access public | |
186 * @static | |
187 */ | |
188 function &getInstance() { | |
189 static $instance = array(); | |
190 | |
191 if (!$instance) { | |
192 $instance[0] =& new Router(); | |
193 } | |
194 return $instance[0]; | |
195 } | |
196 | |
197 /** | |
198 * Gets the named route elements for use in app/config/routes.php | |
199 * | |
200 * @return array Named route elements | |
201 * @access public | |
202 * @see Router::$__named | |
203 * @static | |
204 */ | |
205 function getNamedExpressions() { | |
206 $self =& Router::getInstance(); | |
207 return $self->__named; | |
208 } | |
209 | |
210 /** | |
211 * Connects a new Route in the router. | |
212 * | |
213 * Routes are a way of connecting request urls to objects in your application. At their core routes | |
214 * are a set or regular expressions that are used to match requests to destinations. | |
215 * | |
216 * Examples: | |
217 * | |
218 * `Router::connect('/:controller/:action/*');` | |
219 * | |
220 * The first parameter will be used as a controller name while the second is used as the action name. | |
221 * the '/*' syntax makes this route greedy in that it will match requests like `/posts/index` as well as requests | |
222 * like `/posts/edit/1/foo/bar`. | |
223 * | |
224 * `Router::connect('/home-page', array('controller' => 'pages', 'action' => 'display', 'home'));` | |
225 * | |
226 * The above shows the use of route parameter defaults. And providing routing parameters for a static route. | |
227 * | |
228 * {{{ | |
229 * Router::connect( | |
230 * '/:lang/:controller/:action/:id', | |
231 * array(), | |
232 * array('id' => '[0-9]+', 'lang' => '[a-z]{3}') | |
233 * ); | |
234 * }}} | |
235 * | |
236 * Shows connecting a route with custom route parameters as well as providing patterns for those parameters. | |
237 * Patterns for routing parameters do not need capturing groups, as one will be added for each route params. | |
238 * | |
239 * $options offers three 'special' keys. `pass`, `persist` and `routeClass` have special meaning in the $options array. | |
240 * | |
241 * `pass` is used to define which of the routed parameters should be shifted into the pass array. Adding a | |
242 * parameter to pass will remove it from the regular route array. Ex. `'pass' => array('slug')` | |
243 * | |
244 * `persist` is used to define which route parameters should be automatically included when generating | |
245 * new urls. You can override persistent parameters by redefining them in a url or remove them by | |
246 * setting the parameter to `false`. Ex. `'persist' => array('lang')` | |
247 * | |
248 * `routeClass` is used to extend and change how individual routes parse requests and handle reverse routing, | |
249 * via a custom routing class. Ex. `'routeClass' => 'SlugRoute'` | |
250 * | |
251 * @param string $route A string describing the template of the route | |
252 * @param array $defaults An array describing the default route parameters. These parameters will be used by default | |
253 * and can supply routing parameters that are not dynamic. See above. | |
254 * @param array $options An array matching the named elements in the route to regular expressions which that | |
255 * element should match. Also contains additional parameters such as which routed parameters should be | |
256 * shifted into the passed arguments, supplying patterns for routing parameters and supplying the name of a | |
257 * custom routing class. | |
258 * @see routes | |
259 * @return array Array of routes | |
260 * @access public | |
261 * @static | |
262 */ | |
263 function connect($route, $defaults = array(), $options = array()) { | |
264 $self =& Router::getInstance(); | |
265 | |
266 foreach ($self->__prefixes as $prefix) { | |
267 if (isset($defaults[$prefix])) { | |
268 $defaults['prefix'] = $prefix; | |
269 break; | |
270 } | |
271 } | |
272 if (isset($defaults['prefix'])) { | |
273 $self->__prefixes[] = $defaults['prefix']; | |
274 $self->__prefixes = array_keys(array_flip($self->__prefixes)); | |
275 } | |
276 $defaults += array('plugin' => null); | |
277 if (empty($options['action'])) { | |
278 $defaults += array('action' => 'index'); | |
279 } | |
280 $routeClass = 'CakeRoute'; | |
281 if (isset($options['routeClass'])) { | |
282 $routeClass = $options['routeClass']; | |
283 unset($options['routeClass']); | |
284 } | |
285 //TODO 2.0 refactor this to use a string class name, throw exception, and then construct. | |
286 $Route =& new $routeClass($route, $defaults, $options); | |
287 if ($routeClass !== 'CakeRoute' && !is_subclass_of($Route, 'CakeRoute')) { | |
288 trigger_error(__('Route classes must extend CakeRoute', true), E_USER_WARNING); | |
289 return false; | |
290 } | |
291 $self->routes[] =& $Route; | |
292 return $self->routes; | |
293 } | |
294 | |
295 /** | |
296 * Specifies what named parameters CakePHP should be parsing. The most common setups are: | |
297 * | |
298 * Do not parse any named parameters: | |
299 * | |
300 * {{{ Router::connectNamed(false); }}} | |
301 * | |
302 * Parse only default parameters used for CakePHP's pagination: | |
303 * | |
304 * {{{ Router::connectNamed(false, array('default' => true)); }}} | |
305 * | |
306 * Parse only the page parameter if its value is a number: | |
307 * | |
308 * {{{ Router::connectNamed(array('page' => '[\d]+'), array('default' => false, 'greedy' => false)); }}} | |
309 * | |
310 * Parse only the page parameter no mater what. | |
311 * | |
312 * {{{ Router::connectNamed(array('page'), array('default' => false, 'greedy' => false)); }}} | |
313 * | |
314 * Parse only the page parameter if the current action is 'index'. | |
315 * | |
316 * {{{ | |
317 * Router::connectNamed( | |
318 * array('page' => array('action' => 'index')), | |
319 * array('default' => false, 'greedy' => false) | |
320 * ); | |
321 * }}} | |
322 * | |
323 * Parse only the page parameter if the current action is 'index' and the controller is 'pages'. | |
324 * | |
325 * {{{ | |
326 * Router::connectNamed( | |
327 * array('page' => array('action' => 'index', 'controller' => 'pages')), | |
328 * array('default' => false, 'greedy' => false) | |
329 * ); | |
330 * }}} | |
331 * | |
332 * @param array $named A list of named parameters. Key value pairs are accepted where values are | |
333 * either regex strings to match, or arrays as seen above. | |
334 * @param array $options Allows to control all settings: separator, greedy, reset, default | |
335 * @return array | |
336 * @access public | |
337 * @static | |
338 */ | |
339 function connectNamed($named, $options = array()) { | |
340 $self =& Router::getInstance(); | |
341 | |
342 if (isset($options['argSeparator'])) { | |
343 $self->named['separator'] = $options['argSeparator']; | |
344 unset($options['argSeparator']); | |
345 } | |
346 | |
347 if ($named === true || $named === false) { | |
348 $options = array_merge(array('default' => $named, 'reset' => true, 'greedy' => $named), $options); | |
349 $named = array(); | |
350 } else { | |
351 $options = array_merge(array('default' => false, 'reset' => false, 'greedy' => true), $options); | |
352 } | |
353 | |
354 if ($options['reset'] == true || $self->named['rules'] === false) { | |
355 $self->named['rules'] = array(); | |
356 } | |
357 | |
358 if ($options['default']) { | |
359 $named = array_merge($named, $self->named['default']); | |
360 } | |
361 | |
362 foreach ($named as $key => $val) { | |
363 if (is_numeric($key)) { | |
364 $self->named['rules'][$val] = true; | |
365 } else { | |
366 $self->named['rules'][$key] = $val; | |
367 } | |
368 } | |
369 $self->named['greedy'] = $options['greedy']; | |
370 return $self->named; | |
371 } | |
372 | |
373 /** | |
374 * Tell router to connect or not connect the default routes. | |
375 * | |
376 * If default routes are disabled all automatic route generation will be disabled | |
377 * and you will need to manually configure all the routes you want. | |
378 * | |
379 * @param boolean $connect Set to true or false depending on whether you want or don't want default routes. | |
380 * @return void | |
381 * @access public | |
382 * @static | |
383 */ | |
384 function defaults($connect = true) { | |
385 $self =& Router::getInstance(); | |
386 $self->__connectDefaults = $connect; | |
387 } | |
388 | |
389 /** | |
390 * Creates REST resource routes for the given controller(s) | |
391 * | |
392 * ### Options: | |
393 * | |
394 * - 'id' - The regular expression fragment to use when matching IDs. By default, matches | |
395 * integer values and UUIDs. | |
396 * - 'prefix' - URL prefix to use for the generated routes. Defaults to '/'. | |
397 * | |
398 * @param mixed $controller A controller name or array of controller names (i.e. "Posts" or "ListItems") | |
399 * @param array $options Options to use when generating REST routes | |
400 * @return void | |
401 * @access public | |
402 * @static | |
403 */ | |
404 function mapResources($controller, $options = array()) { | |
405 $self =& Router::getInstance(); | |
406 $options = array_merge(array('prefix' => '/', 'id' => $self->__named['ID'] . '|' . $self->__named['UUID']), $options); | |
407 $prefix = $options['prefix']; | |
408 | |
409 foreach ((array)$controller as $ctlName) { | |
410 $urlName = Inflector::underscore($ctlName); | |
411 | |
412 foreach ($self->__resourceMap as $params) { | |
413 extract($params); | |
414 $url = $prefix . $urlName . (($id) ? '/:id' : ''); | |
415 | |
416 Router::connect($url, | |
417 array('controller' => $urlName, 'action' => $action, '[method]' => $params['method']), | |
418 array('id' => $options['id'], 'pass' => array('id')) | |
419 ); | |
420 } | |
421 $self->__resourceMapped[] = $urlName; | |
422 } | |
423 } | |
424 | |
425 /** | |
426 * Returns the list of prefixes used in connected routes | |
427 * | |
428 * @return array A list of prefixes used in connected routes | |
429 * @access public | |
430 * @static | |
431 */ | |
432 function prefixes() { | |
433 $self =& Router::getInstance(); | |
434 return $self->__prefixes; | |
435 } | |
436 | |
437 /** | |
438 * Parses given URL and returns an array of controller, action and parameters | |
439 * taken from that URL. | |
440 * | |
441 * @param string $url URL to be parsed | |
442 * @return array Parsed elements from URL | |
443 * @access public | |
444 * @static | |
445 */ | |
446 function parse($url) { | |
447 $self =& Router::getInstance(); | |
448 if (!$self->__defaultsMapped && $self->__connectDefaults) { | |
449 $self->__connectDefaultRoutes(); | |
450 } | |
451 $out = array( | |
452 'pass' => array(), | |
453 'named' => array(), | |
454 ); | |
455 $r = $ext = null; | |
456 | |
457 if (ini_get('magic_quotes_gpc') === '1') { | |
458 $url = stripslashes_deep($url); | |
459 } | |
460 | |
461 if ($url && strpos($url, '/') !== 0) { | |
462 $url = '/' . $url; | |
463 } | |
464 if (strpos($url, '?') !== false) { | |
465 $url = substr($url, 0, strpos($url, '?')); | |
466 } | |
467 extract($self->__parseExtension($url)); | |
468 | |
469 for ($i = 0, $len = count($self->routes); $i < $len; $i++) { | |
470 $route =& $self->routes[$i]; | |
471 if (($r = $route->parse($url)) !== false) { | |
472 $self->__currentRoute[] =& $route; | |
473 | |
474 $params = $route->options; | |
475 $argOptions = array(); | |
476 | |
477 if (array_key_exists('named', $params)) { | |
478 $argOptions['named'] = $params['named']; | |
479 unset($params['named']); | |
480 } | |
481 if (array_key_exists('greedy', $params)) { | |
482 $argOptions['greedy'] = $params['greedy']; | |
483 unset($params['greedy']); | |
484 } | |
485 $out = $r; | |
486 | |
487 if (isset($out['_args_'])) { | |
488 $argOptions['context'] = array('action' => $out['action'], 'controller' => $out['controller']); | |
489 $parsedArgs = $self->getArgs($out['_args_'], $argOptions); | |
490 $out['pass'] = array_merge($out['pass'], $parsedArgs['pass']); | |
491 $out['named'] = $parsedArgs['named']; | |
492 unset($out['_args_']); | |
493 } | |
494 | |
495 if (isset($params['pass'])) { | |
496 $j = count($params['pass']); | |
497 while($j--) { | |
498 if (isset($out[$params['pass'][$j]])) { | |
499 array_unshift($out['pass'], $out[$params['pass'][$j]]); | |
500 } | |
501 } | |
502 } | |
503 break; | |
504 } | |
505 } | |
506 | |
507 if (!empty($ext) && !isset($out['url']['ext'])) { | |
508 $out['url']['ext'] = $ext; | |
509 } | |
510 return $out; | |
511 } | |
512 | |
513 /** | |
514 * Parses a file extension out of a URL, if Router::parseExtensions() is enabled. | |
515 * | |
516 * @param string $url | |
517 * @return array Returns an array containing the altered URL and the parsed extension. | |
518 * @access private | |
519 */ | |
520 function __parseExtension($url) { | |
521 $ext = null; | |
522 | |
523 if ($this->__parseExtensions) { | |
524 if (preg_match('/\.[0-9a-zA-Z]*$/', $url, $match) === 1) { | |
525 $match = substr($match[0], 1); | |
526 if (empty($this->__validExtensions)) { | |
527 $url = substr($url, 0, strpos($url, '.' . $match)); | |
528 $ext = $match; | |
529 } else { | |
530 foreach ($this->__validExtensions as $name) { | |
531 if (strcasecmp($name, $match) === 0) { | |
532 $url = substr($url, 0, strpos($url, '.' . $name)); | |
533 $ext = $match; | |
534 break; | |
535 } | |
536 } | |
537 } | |
538 } | |
539 if (empty($ext)) { | |
540 $ext = 'html'; | |
541 } | |
542 } | |
543 return compact('ext', 'url'); | |
544 } | |
545 | |
546 /** | |
547 * Connects the default, built-in routes, including prefix and plugin routes. The following routes are created | |
548 * in the order below: | |
549 * | |
550 * For each of the Routing.prefixes the following routes are created. Routes containing `:plugin` are only | |
551 * created when your application has one or more plugins. | |
552 * | |
553 * - `/:prefix/:plugin` a plugin shortcut route. | |
554 * - `/:prefix/:plugin/:action/*` a plugin shortcut route. | |
555 * - `/:prefix/:plugin/:controller` | |
556 * - `/:prefix/:plugin/:controller/:action/*` | |
557 * - `/:prefix/:controller` | |
558 * - `/:prefix/:controller/:action/*` | |
559 * | |
560 * If plugins are found in your application the following routes are created: | |
561 * | |
562 * - `/:plugin` a plugin shortcut route. | |
563 * - `/:plugin/:action/*` a plugin shortcut route. | |
564 * - `/:plugin/:controller` | |
565 * - `/:plugin/:controller/:action/*` | |
566 * | |
567 * And lastly the following catch-all routes are connected. | |
568 * | |
569 * - `/:controller' | |
570 * - `/:controller/:action/*' | |
571 * | |
572 * You can disable the connection of default routes with Router::defaults(). | |
573 * | |
574 * @return void | |
575 * @access private | |
576 */ | |
577 function __connectDefaultRoutes() { | |
578 if ($plugins = App::objects('plugin')) { | |
579 foreach ($plugins as $key => $value) { | |
580 $plugins[$key] = Inflector::underscore($value); | |
581 } | |
582 $pluginPattern = implode('|', $plugins); | |
583 $match = array('plugin' => $pluginPattern); | |
584 $shortParams = array('routeClass' => 'PluginShortRoute', 'plugin' => $pluginPattern); | |
585 | |
586 foreach ($this->__prefixes as $prefix) { | |
587 $params = array('prefix' => $prefix, $prefix => true); | |
588 $indexParams = $params + array('action' => 'index'); | |
589 $this->connect("/{$prefix}/:plugin", $indexParams, $shortParams); | |
590 $this->connect("/{$prefix}/:plugin/:controller", $indexParams, $match); | |
591 $this->connect("/{$prefix}/:plugin/:controller/:action/*", $params, $match); | |
592 } | |
593 $this->connect('/:plugin', array('action' => 'index'), $shortParams); | |
594 $this->connect('/:plugin/:controller', array('action' => 'index'), $match); | |
595 $this->connect('/:plugin/:controller/:action/*', array(), $match); | |
596 } | |
597 | |
598 foreach ($this->__prefixes as $prefix) { | |
599 $params = array('prefix' => $prefix, $prefix => true); | |
600 $indexParams = $params + array('action' => 'index'); | |
601 $this->connect("/{$prefix}/:controller", $indexParams); | |
602 $this->connect("/{$prefix}/:controller/:action/*", $params); | |
603 } | |
604 $this->connect('/:controller', array('action' => 'index')); | |
605 $this->connect('/:controller/:action/*'); | |
606 | |
607 if ($this->named['rules'] === false) { | |
608 $this->connectNamed(true); | |
609 } | |
610 $this->__defaultsMapped = true; | |
611 } | |
612 | |
613 /** | |
614 * Takes parameter and path information back from the Dispatcher, sets these | |
615 * parameters as the current request parameters that are merged with url arrays | |
616 * created later in the request. | |
617 * | |
618 * @param array $params Parameters and path information | |
619 * @return void | |
620 * @access public | |
621 * @static | |
622 */ | |
623 function setRequestInfo($params) { | |
624 $self =& Router::getInstance(); | |
625 $defaults = array('plugin' => null, 'controller' => null, 'action' => null); | |
626 $params[0] = array_merge($defaults, (array)$params[0]); | |
627 $params[1] = array_merge($defaults, (array)$params[1]); | |
628 list($self->__params[], $self->__paths[]) = $params; | |
629 | |
630 if (count($self->__paths)) { | |
631 if (isset($self->__paths[0]['namedArgs'])) { | |
632 foreach ($self->__paths[0]['namedArgs'] as $arg => $value) { | |
633 $self->named['rules'][$arg] = true; | |
634 } | |
635 } | |
636 } | |
637 } | |
638 | |
639 /** | |
640 * Gets parameter information | |
641 * | |
642 * @param boolean $current Get current request parameter, useful when using requestAction | |
643 * @return array Parameter information | |
644 * @access public | |
645 * @static | |
646 */ | |
647 function getParams($current = false) { | |
648 $self =& Router::getInstance(); | |
649 if ($current) { | |
650 return $self->__params[count($self->__params) - 1]; | |
651 } | |
652 if (isset($self->__params[0])) { | |
653 return $self->__params[0]; | |
654 } | |
655 return array(); | |
656 } | |
657 | |
658 /** | |
659 * Gets URL parameter by name | |
660 * | |
661 * @param string $name Parameter name | |
662 * @param boolean $current Current parameter, useful when using requestAction | |
663 * @return string Parameter value | |
664 * @access public | |
665 * @static | |
666 */ | |
667 function getParam($name = 'controller', $current = false) { | |
668 $params = Router::getParams($current); | |
669 if (isset($params[$name])) { | |
670 return $params[$name]; | |
671 } | |
672 return null; | |
673 } | |
674 | |
675 /** | |
676 * Gets path information | |
677 * | |
678 * @param boolean $current Current parameter, useful when using requestAction | |
679 * @return array | |
680 * @access public | |
681 * @static | |
682 */ | |
683 function getPaths($current = false) { | |
684 $self =& Router::getInstance(); | |
685 if ($current) { | |
686 return $self->__paths[count($self->__paths) - 1]; | |
687 } | |
688 if (!isset($self->__paths[0])) { | |
689 return array('base' => null); | |
690 } | |
691 return $self->__paths[0]; | |
692 } | |
693 | |
694 /** | |
695 * Reloads default Router settings. Resets all class variables and | |
696 * removes all connected routes. | |
697 * | |
698 * @access public | |
699 * @return void | |
700 * @static | |
701 */ | |
702 function reload() { | |
703 $self =& Router::getInstance(); | |
704 foreach (get_class_vars('Router') as $key => $val) { | |
705 $self->{$key} = $val; | |
706 } | |
707 $self->__setPrefixes(); | |
708 } | |
709 | |
710 /** | |
711 * Promote a route (by default, the last one added) to the beginning of the list | |
712 * | |
713 * @param $which A zero-based array index representing the route to move. For example, | |
714 * if 3 routes have been added, the last route would be 2. | |
715 * @return boolean Returns false if no route exists at the position specified by $which. | |
716 * @access public | |
717 * @static | |
718 */ | |
719 function promote($which = null) { | |
720 $self =& Router::getInstance(); | |
721 if ($which === null) { | |
722 $which = count($self->routes) - 1; | |
723 } | |
724 if (!isset($self->routes[$which])) { | |
725 return false; | |
726 } | |
727 $route =& $self->routes[$which]; | |
728 unset($self->routes[$which]); | |
729 array_unshift($self->routes, $route); | |
730 return true; | |
731 } | |
732 | |
733 /** | |
734 * Finds URL for specified action. | |
735 * | |
736 * Returns an URL pointing to a combination of controller and action. Param | |
737 * $url can be: | |
738 * | |
739 * - Empty - the method will find address to actual controller/action. | |
740 * - '/' - the method will find base URL of application. | |
741 * - A combination of controller/action - the method will find url for it. | |
742 * | |
743 * There are a few 'special' parameters that can change the final URL string that is generated | |
744 * | |
745 * - `base` - Set to false to remove the base path from the generated url. If your application | |
746 * is not in the root directory, this can be used to generate urls that are 'cake relative'. | |
747 * cake relative urls are required when using requestAction. | |
748 * - `?` - Takes an array of query string parameters | |
749 * - `#` - Allows you to set url hash fragments. | |
750 * - `full_base` - If true the `FULL_BASE_URL` constant will be prepended to generated urls. | |
751 * | |
752 * @param mixed $url Cake-relative URL, like "/products/edit/92" or "/presidents/elect/4" | |
753 * or an array specifying any of the following: 'controller', 'action', | |
754 * and/or 'plugin', in addition to named arguments (keyed array elements), | |
755 * and standard URL arguments (indexed array elements) | |
756 * @param mixed $full If (bool) true, the full base URL will be prepended to the result. | |
757 * If an array accepts the following keys | |
758 * - escape - used when making urls embedded in html escapes query string '&' | |
759 * - full - if true the full base URL will be prepended. | |
760 * @return string Full translated URL with base path. | |
761 * @access public | |
762 * @static | |
763 */ | |
764 function url($url = null, $full = false) { | |
765 $self =& Router::getInstance(); | |
766 $defaults = $params = array('plugin' => null, 'controller' => null, 'action' => 'index'); | |
767 | |
768 if (is_bool($full)) { | |
769 $escape = false; | |
770 } else { | |
771 extract($full + array('escape' => false, 'full' => false)); | |
772 } | |
773 | |
774 if (!empty($self->__params)) { | |
775 if (isset($this) && !isset($this->params['requested'])) { | |
776 $params = $self->__params[0]; | |
777 } else { | |
778 $params = end($self->__params); | |
779 } | |
780 } | |
781 $path = array('base' => null); | |
782 | |
783 if (!empty($self->__paths)) { | |
784 if (isset($this) && !isset($this->params['requested'])) { | |
785 $path = $self->__paths[0]; | |
786 } else { | |
787 $path = end($self->__paths); | |
788 } | |
789 } | |
790 $base = $path['base']; | |
791 $extension = $output = $mapped = $q = $frag = null; | |
792 | |
793 if (is_array($url)) { | |
794 if (isset($url['base']) && $url['base'] === false) { | |
795 $base = null; | |
796 unset($url['base']); | |
797 } | |
798 if (isset($url['full_base']) && $url['full_base'] === true) { | |
799 $full = true; | |
800 unset($url['full_base']); | |
801 } | |
802 if (isset($url['?'])) { | |
803 $q = $url['?']; | |
804 unset($url['?']); | |
805 } | |
806 if (isset($url['#'])) { | |
807 $frag = '#' . urlencode($url['#']); | |
808 unset($url['#']); | |
809 } | |
810 if (empty($url['action'])) { | |
811 if (empty($url['controller']) || $params['controller'] === $url['controller']) { | |
812 $url['action'] = $params['action']; | |
813 } else { | |
814 $url['action'] = 'index'; | |
815 } | |
816 } | |
817 | |
818 $prefixExists = (array_intersect_key($url, array_flip($self->__prefixes))); | |
819 foreach ($self->__prefixes as $prefix) { | |
820 if (!empty($params[$prefix]) && !$prefixExists) { | |
821 $url[$prefix] = true; | |
822 } elseif (isset($url[$prefix]) && !$url[$prefix]) { | |
823 unset($url[$prefix]); | |
824 } | |
825 if (isset($url[$prefix]) && strpos($url['action'], $prefix . '_') === 0) { | |
826 $url['action'] = substr($url['action'], strlen($prefix) + 1); | |
827 } | |
828 } | |
829 | |
830 $url += array('controller' => $params['controller'], 'plugin' => $params['plugin']); | |
831 | |
832 if (isset($url['ext'])) { | |
833 $extension = '.' . $url['ext']; | |
834 unset($url['ext']); | |
835 } | |
836 $match = false; | |
837 | |
838 for ($i = 0, $len = count($self->routes); $i < $len; $i++) { | |
839 $originalUrl = $url; | |
840 | |
841 if (isset($self->routes[$i]->options['persist'], $params)) { | |
842 $url = $self->routes[$i]->persistParams($url, $params); | |
843 } | |
844 | |
845 if ($match = $self->routes[$i]->match($url)) { | |
846 $output = trim($match, '/'); | |
847 break; | |
848 } | |
849 $url = $originalUrl; | |
850 } | |
851 if ($match === false) { | |
852 $output = $self->_handleNoRoute($url); | |
853 } | |
854 $output = str_replace('//', '/', $base . '/' . $output); | |
855 } else { | |
856 if (((strpos($url, '://')) || (strpos($url, 'javascript:') === 0) || (strpos($url, 'mailto:') === 0)) || (!strncmp($url, '#', 1))) { | |
857 return $url; | |
858 } | |
859 if (empty($url)) { | |
860 if (!isset($path['here'])) { | |
861 $path['here'] = '/'; | |
862 } | |
863 $output = $path['here']; | |
864 } elseif (substr($url, 0, 1) === '/') { | |
865 $output = $base . $url; | |
866 } else { | |
867 $output = $base . '/'; | |
868 foreach ($self->__prefixes as $prefix) { | |
869 if (isset($params[$prefix])) { | |
870 $output .= $prefix . '/'; | |
871 break; | |
872 } | |
873 } | |
874 if (!empty($params['plugin']) && $params['plugin'] !== $params['controller']) { | |
875 $output .= Inflector::underscore($params['plugin']) . '/'; | |
876 } | |
877 $output .= Inflector::underscore($params['controller']) . '/' . $url; | |
878 } | |
879 $output = str_replace('//', '/', $output); | |
880 } | |
881 if ($full && defined('FULL_BASE_URL')) { | |
882 $output = FULL_BASE_URL . $output; | |
883 } | |
884 if (!empty($extension) && substr($output, -1) === '/') { | |
885 $output = substr($output, 0, -1); | |
886 } | |
887 | |
888 return $output . $extension . $self->queryString($q, array(), $escape) . $frag; | |
889 } | |
890 | |
891 /** | |
892 * A special fallback method that handles url arrays that cannot match | |
893 * any defined routes. | |
894 * | |
895 * @param array $url A url that didn't match any routes | |
896 * @return string A generated url for the array | |
897 * @access protected | |
898 * @see Router::url() | |
899 */ | |
900 function _handleNoRoute($url) { | |
901 $named = $args = array(); | |
902 $skip = array_merge( | |
903 array('bare', 'action', 'controller', 'plugin', 'prefix'), | |
904 $this->__prefixes | |
905 ); | |
906 | |
907 $keys = array_values(array_diff(array_keys($url), $skip)); | |
908 $count = count($keys); | |
909 | |
910 // Remove this once parsed URL parameters can be inserted into 'pass' | |
911 for ($i = 0; $i < $count; $i++) { | |
912 if (is_numeric($keys[$i])) { | |
913 $args[] = $url[$keys[$i]]; | |
914 } else { | |
915 $named[$keys[$i]] = $url[$keys[$i]]; | |
916 } | |
917 } | |
918 | |
919 list($args, $named) = array(Set::filter($args, true), Set::filter($named, true)); | |
920 foreach ($this->__prefixes as $prefix) { | |
921 if (!empty($url[$prefix])) { | |
922 $url['action'] = str_replace($prefix . '_', '', $url['action']); | |
923 break; | |
924 } | |
925 } | |
926 | |
927 if (empty($named) && empty($args) && (!isset($url['action']) || $url['action'] === 'index')) { | |
928 $url['action'] = null; | |
929 } | |
930 | |
931 $urlOut = array_filter(array($url['controller'], $url['action'])); | |
932 | |
933 if (isset($url['plugin'])) { | |
934 array_unshift($urlOut, $url['plugin']); | |
935 } | |
936 | |
937 foreach ($this->__prefixes as $prefix) { | |
938 if (isset($url[$prefix])) { | |
939 array_unshift($urlOut, $prefix); | |
940 break; | |
941 } | |
942 } | |
943 $output = implode('/', $urlOut); | |
944 | |
945 if (!empty($args)) { | |
946 $output .= '/' . implode('/', $args); | |
947 } | |
948 | |
949 if (!empty($named)) { | |
950 foreach ($named as $name => $value) { | |
951 $output .= '/' . $name . $this->named['separator'] . $value; | |
952 } | |
953 } | |
954 return $output; | |
955 } | |
956 | |
957 /** | |
958 * Takes an array of URL parameters and separates the ones that can be used as named arguments | |
959 * | |
960 * @param array $params Associative array of URL parameters. | |
961 * @param string $controller Name of controller being routed. Used in scoping. | |
962 * @param string $action Name of action being routed. Used in scoping. | |
963 * @return array | |
964 * @access public | |
965 * @static | |
966 */ | |
967 function getNamedElements($params, $controller = null, $action = null) { | |
968 $self =& Router::getInstance(); | |
969 $named = array(); | |
970 | |
971 foreach ($params as $param => $val) { | |
972 if (isset($self->named['rules'][$param])) { | |
973 $rule = $self->named['rules'][$param]; | |
974 if (Router::matchNamed($param, $val, $rule, compact('controller', 'action'))) { | |
975 $named[$param] = $val; | |
976 unset($params[$param]); | |
977 } | |
978 } | |
979 } | |
980 return array($named, $params); | |
981 } | |
982 | |
983 /** | |
984 * Return true if a given named $param's $val matches a given $rule depending on $context. Currently implemented | |
985 * rule types are controller, action and match that can be combined with each other. | |
986 * | |
987 * @param string $param The name of the named parameter | |
988 * @param string $val The value of the named parameter | |
989 * @param array $rule The rule(s) to apply, can also be a match string | |
990 * @param string $context An array with additional context information (controller / action) | |
991 * @return boolean | |
992 * @access public | |
993 * @static | |
994 */ | |
995 function matchNamed($param, $val, $rule, $context = array()) { | |
996 if ($rule === true || $rule === false) { | |
997 return $rule; | |
998 } | |
999 if (is_string($rule)) { | |
1000 $rule = array('match' => $rule); | |
1001 } | |
1002 if (!is_array($rule)) { | |
1003 return false; | |
1004 } | |
1005 | |
1006 $controllerMatches = !isset($rule['controller'], $context['controller']) || in_array($context['controller'], (array)$rule['controller']); | |
1007 if (!$controllerMatches) { | |
1008 return false; | |
1009 } | |
1010 $actionMatches = !isset($rule['action'], $context['action']) || in_array($context['action'], (array)$rule['action']); | |
1011 if (!$actionMatches) { | |
1012 return false; | |
1013 } | |
1014 return (!isset($rule['match']) || preg_match('/' . $rule['match'] . '/', $val)); | |
1015 } | |
1016 | |
1017 /** | |
1018 * Generates a well-formed querystring from $q | |
1019 * | |
1020 * @param mixed $q Query string | |
1021 * @param array $extra Extra querystring parameters. | |
1022 * @param bool $escape Whether or not to use escaped & | |
1023 * @return array | |
1024 * @access public | |
1025 * @static | |
1026 */ | |
1027 function queryString($q, $extra = array(), $escape = false) { | |
1028 if (empty($q) && empty($extra)) { | |
1029 return null; | |
1030 } | |
1031 $join = '&'; | |
1032 if ($escape === true) { | |
1033 $join = '&'; | |
1034 } | |
1035 $out = ''; | |
1036 | |
1037 if (is_array($q)) { | |
1038 $q = array_merge($extra, $q); | |
1039 } else { | |
1040 $out = $q; | |
1041 $q = $extra; | |
1042 } | |
1043 $out .= http_build_query($q, null, $join); | |
1044 if (isset($out[0]) && $out[0] != '?') { | |
1045 $out = '?' . $out; | |
1046 } | |
1047 return $out; | |
1048 } | |
1049 | |
1050 /** | |
1051 * Reverses a parsed parameter array into a string. Works similarly to Router::url(), but | |
1052 * Since parsed URL's contain additional 'pass' and 'named' as well as 'url.url' keys. | |
1053 * Those keys need to be specially handled in order to reverse a params array into a string url. | |
1054 * | |
1055 * This will strip out 'autoRender', 'bare', 'requested', and 'return' param names as those | |
1056 * are used for CakePHP internals and should not normally be part of an output url. | |
1057 * | |
1058 * @param array $param The params array that needs to be reversed. | |
1059 * @return string The string that is the reversed result of the array | |
1060 * @access public | |
1061 * @static | |
1062 */ | |
1063 function reverse($params) { | |
1064 $pass = $params['pass']; | |
1065 $named = $params['named']; | |
1066 $url = $params['url']; | |
1067 unset( | |
1068 $params['pass'], $params['named'], $params['paging'], $params['models'], $params['url'], $url['url'], | |
1069 $params['autoRender'], $params['bare'], $params['requested'], $params['return'] | |
1070 ); | |
1071 $params = array_merge($params, $pass, $named); | |
1072 if (!empty($url)) { | |
1073 $params['?'] = $url; | |
1074 } | |
1075 return Router::url($params); | |
1076 } | |
1077 | |
1078 /** | |
1079 * Normalizes a URL for purposes of comparison. Will strip the base path off | |
1080 * and replace any double /'s. It will not unify the casing and underscoring | |
1081 * of the input value. | |
1082 * | |
1083 * @param mixed $url URL to normalize Either an array or a string url. | |
1084 * @return string Normalized URL | |
1085 * @access public | |
1086 * @static | |
1087 */ | |
1088 function normalize($url = '/') { | |
1089 if (is_array($url)) { | |
1090 $url = Router::url($url); | |
1091 } elseif (preg_match('/^[a-z\-]+:\/\//', $url)) { | |
1092 return $url; | |
1093 } | |
1094 $paths = Router::getPaths(); | |
1095 | |
1096 if (!empty($paths['base']) && stristr($url, $paths['base'])) { | |
1097 $url = preg_replace('/^' . preg_quote($paths['base'], '/') . '/', '', $url, 1); | |
1098 } | |
1099 $url = '/' . $url; | |
1100 | |
1101 while (strpos($url, '//') !== false) { | |
1102 $url = str_replace('//', '/', $url); | |
1103 } | |
1104 $url = preg_replace('/(?:(\/$))/', '', $url); | |
1105 | |
1106 if (empty($url)) { | |
1107 return '/'; | |
1108 } | |
1109 return $url; | |
1110 } | |
1111 | |
1112 /** | |
1113 * Returns the route matching the current request URL. | |
1114 * | |
1115 * @return CakeRoute Matching route object. | |
1116 * @access public | |
1117 * @static | |
1118 */ | |
1119 function &requestRoute() { | |
1120 $self =& Router::getInstance(); | |
1121 return $self->__currentRoute[0]; | |
1122 } | |
1123 | |
1124 /** | |
1125 * Returns the route matching the current request (useful for requestAction traces) | |
1126 * | |
1127 * @return CakeRoute Matching route object. | |
1128 * @access public | |
1129 * @static | |
1130 */ | |
1131 function ¤tRoute() { | |
1132 $self =& Router::getInstance(); | |
1133 return $self->__currentRoute[count($self->__currentRoute) - 1]; | |
1134 } | |
1135 | |
1136 /** | |
1137 * Removes the plugin name from the base URL. | |
1138 * | |
1139 * @param string $base Base URL | |
1140 * @param string $plugin Plugin name | |
1141 * @return base url with plugin name removed if present | |
1142 * @access public | |
1143 * @static | |
1144 */ | |
1145 function stripPlugin($base, $plugin = null) { | |
1146 if ($plugin != null) { | |
1147 $base = preg_replace('/(?:' . $plugin . ')/', '', $base); | |
1148 $base = str_replace('//', '', $base); | |
1149 $pos1 = strrpos($base, '/'); | |
1150 $char = strlen($base) - 1; | |
1151 | |
1152 if ($pos1 === $char) { | |
1153 $base = substr($base, 0, $char); | |
1154 } | |
1155 } | |
1156 return $base; | |
1157 } | |
1158 | |
1159 /** | |
1160 * Instructs the router to parse out file extensions from the URL. For example, | |
1161 * http://example.com/posts.rss would yield an file extension of "rss". | |
1162 * The file extension itself is made available in the controller as | |
1163 * $this->params['url']['ext'], and is used by the RequestHandler component to | |
1164 * automatically switch to alternate layouts and templates, and load helpers | |
1165 * corresponding to the given content, i.e. RssHelper. | |
1166 * | |
1167 * A list of valid extension can be passed to this method, i.e. Router::parseExtensions('rss', 'xml'); | |
1168 * If no parameters are given, anything after the first . (dot) after the last / in the URL will be | |
1169 * parsed, excluding querystring parameters (i.e. ?q=...). | |
1170 * | |
1171 * @access public | |
1172 * @return void | |
1173 * @static | |
1174 */ | |
1175 function parseExtensions() { | |
1176 $self =& Router::getInstance(); | |
1177 $self->__parseExtensions = true; | |
1178 if (func_num_args() > 0) { | |
1179 $self->__validExtensions = func_get_args(); | |
1180 } | |
1181 } | |
1182 | |
1183 /** | |
1184 * Takes a passed params and converts it to args | |
1185 * | |
1186 * @param array $params | |
1187 * @return array Array containing passed and named parameters | |
1188 * @access public | |
1189 * @static | |
1190 */ | |
1191 function getArgs($args, $options = array()) { | |
1192 $self =& Router::getInstance(); | |
1193 $pass = $named = array(); | |
1194 $args = explode('/', $args); | |
1195 | |
1196 $greedy = isset($options['greedy']) ? $options['greedy'] : $self->named['greedy']; | |
1197 $context = array(); | |
1198 if (isset($options['context'])) { | |
1199 $context = $options['context']; | |
1200 } | |
1201 $rules = $self->named['rules']; | |
1202 if (isset($options['named'])) { | |
1203 $greedy = isset($options['greedy']) && $options['greedy'] === true; | |
1204 foreach ((array)$options['named'] as $key => $val) { | |
1205 if (is_numeric($key)) { | |
1206 $rules[$val] = true; | |
1207 continue; | |
1208 } | |
1209 $rules[$key] = $val; | |
1210 } | |
1211 } | |
1212 | |
1213 foreach ($args as $param) { | |
1214 if (empty($param) && $param !== '0' && $param !== 0) { | |
1215 continue; | |
1216 } | |
1217 | |
1218 $separatorIsPresent = strpos($param, $self->named['separator']) !== false; | |
1219 if ((!isset($options['named']) || !empty($options['named'])) && $separatorIsPresent) { | |
1220 list($key, $val) = explode($self->named['separator'], $param, 2); | |
1221 $hasRule = isset($rules[$key]); | |
1222 $passIt = (!$hasRule && !$greedy) || ($hasRule && !$self->matchNamed($key, $val, $rules[$key], $context)); | |
1223 if ($passIt) { | |
1224 $pass[] = $param; | |
1225 } else { | |
1226 $named[$key] = $val; | |
1227 } | |
1228 } else { | |
1229 $pass[] = $param; | |
1230 } | |
1231 } | |
1232 return compact('pass', 'named'); | |
1233 } | |
1234 } | |
1235 | |
1236 /** | |
1237 * A single Route used by the Router to connect requests to | |
1238 * parameter maps. | |
1239 * | |
1240 * Not normally created as a standalone. Use Router::connect() to create | |
1241 * Routes for your application. | |
1242 * | |
1243 * @package cake.libs | |
1244 * @since 1.3.0 | |
1245 * @see Router::connect() | |
1246 */ | |
1247 class CakeRoute { | |
1248 | |
1249 /** | |
1250 * An array of named segments in a Route. | |
1251 * `/:controller/:action/:id` has 3 key elements | |
1252 * | |
1253 * @var array | |
1254 * @access public | |
1255 */ | |
1256 var $keys = array(); | |
1257 | |
1258 /** | |
1259 * An array of additional parameters for the Route. | |
1260 * | |
1261 * @var array | |
1262 * @access public | |
1263 */ | |
1264 var $options = array(); | |
1265 | |
1266 /** | |
1267 * Default parameters for a Route | |
1268 * | |
1269 * @var array | |
1270 * @access public | |
1271 */ | |
1272 var $defaults = array(); | |
1273 | |
1274 /** | |
1275 * The routes template string. | |
1276 * | |
1277 * @var string | |
1278 * @access public | |
1279 */ | |
1280 var $template = null; | |
1281 | |
1282 /** | |
1283 * Is this route a greedy route? Greedy routes have a `/*` in their | |
1284 * template | |
1285 * | |
1286 * @var string | |
1287 * @access protected | |
1288 */ | |
1289 var $_greedy = false; | |
1290 | |
1291 /** | |
1292 * The compiled route regular expresssion | |
1293 * | |
1294 * @var string | |
1295 * @access protected | |
1296 */ | |
1297 var $_compiledRoute = null; | |
1298 | |
1299 /** | |
1300 * HTTP header shortcut map. Used for evaluating header-based route expressions. | |
1301 * | |
1302 * @var array | |
1303 * @access private | |
1304 */ | |
1305 var $__headerMap = array( | |
1306 'type' => 'content_type', | |
1307 'method' => 'request_method', | |
1308 'server' => 'server_name' | |
1309 ); | |
1310 | |
1311 /** | |
1312 * Constructor for a Route | |
1313 * | |
1314 * @param string $template Template string with parameter placeholders | |
1315 * @param array $defaults Array of defaults for the route. | |
1316 * @param string $params Array of parameters and additional options for the Route | |
1317 * @return void | |
1318 * @access public | |
1319 */ | |
1320 function CakeRoute($template, $defaults = array(), $options = array()) { | |
1321 $this->template = $template; | |
1322 $this->defaults = (array)$defaults; | |
1323 $this->options = (array)$options; | |
1324 } | |
1325 | |
1326 /** | |
1327 * Check if a Route has been compiled into a regular expression. | |
1328 * | |
1329 * @return boolean | |
1330 * @access public | |
1331 */ | |
1332 function compiled() { | |
1333 return !empty($this->_compiledRoute); | |
1334 } | |
1335 | |
1336 /** | |
1337 * Compiles the route's regular expression. Modifies defaults property so all necessary keys are set | |
1338 * and populates $this->names with the named routing elements. | |
1339 * | |
1340 * @return array Returns a string regular expression of the compiled route. | |
1341 * @access public | |
1342 */ | |
1343 function compile() { | |
1344 if ($this->compiled()) { | |
1345 return $this->_compiledRoute; | |
1346 } | |
1347 $this->_writeRoute(); | |
1348 return $this->_compiledRoute; | |
1349 } | |
1350 | |
1351 /** | |
1352 * Builds a route regular expression. Uses the template, defaults and options | |
1353 * properties to compile a regular expression that can be used to parse request strings. | |
1354 * | |
1355 * @return void | |
1356 * @access protected | |
1357 */ | |
1358 function _writeRoute() { | |
1359 if (empty($this->template) || ($this->template === '/')) { | |
1360 $this->_compiledRoute = '#^/*$#'; | |
1361 $this->keys = array(); | |
1362 return; | |
1363 } | |
1364 $route = $this->template; | |
1365 $names = $routeParams = array(); | |
1366 $parsed = preg_quote($this->template, '#'); | |
1367 $parsed = str_replace('\\-', '-', $parsed); | |
1368 | |
1369 preg_match_all('#:([A-Za-z0-9_-]+[A-Z0-9a-z])#', $parsed, $namedElements); | |
1370 foreach ($namedElements[1] as $i => $name) { | |
1371 $search = '\\' . $namedElements[0][$i]; | |
1372 if (isset($this->options[$name])) { | |
1373 $option = null; | |
1374 if ($name !== 'plugin' && array_key_exists($name, $this->defaults)) { | |
1375 $option = '?'; | |
1376 } | |
1377 $slashParam = '/\\' . $namedElements[0][$i]; | |
1378 if (strpos($parsed, $slashParam) !== false) { | |
1379 $routeParams[$slashParam] = '(?:/(' . $this->options[$name] . ')' . $option . ')' . $option; | |
1380 } else { | |
1381 $routeParams[$search] = '(?:(' . $this->options[$name] . ')' . $option . ')' . $option; | |
1382 } | |
1383 } else { | |
1384 $routeParams[$search] = '(?:([^/]+))'; | |
1385 } | |
1386 $names[] = $name; | |
1387 } | |
1388 if (preg_match('#\/\*$#', $route, $m)) { | |
1389 $parsed = preg_replace('#/\\\\\*$#', '(?:/(?P<_args_>.*))?', $parsed); | |
1390 $this->_greedy = true; | |
1391 } | |
1392 krsort($routeParams); | |
1393 $parsed = str_replace(array_keys($routeParams), array_values($routeParams), $parsed); | |
1394 $this->_compiledRoute = '#^' . $parsed . '[/]*$#'; | |
1395 $this->keys = $names; | |
1396 } | |
1397 | |
1398 /** | |
1399 * Checks to see if the given URL can be parsed by this route. | |
1400 * If the route can be parsed an array of parameters will be returned; if not, | |
1401 * false will be returned. String urls are parsed if they match a routes regular expression. | |
1402 * | |
1403 * @param string $url The url to attempt to parse. | |
1404 * @return mixed Boolean false on failure, otherwise an array or parameters | |
1405 * @access public | |
1406 */ | |
1407 function parse($url) { | |
1408 if (!$this->compiled()) { | |
1409 $this->compile(); | |
1410 } | |
1411 if (!preg_match($this->_compiledRoute, $url, $parsed)) { | |
1412 return false; | |
1413 } else { | |
1414 foreach ($this->defaults as $key => $val) { | |
1415 if ($key[0] === '[' && preg_match('/^\[(\w+)\]$/', $key, $header)) { | |
1416 if (isset($this->__headerMap[$header[1]])) { | |
1417 $header = $this->__headerMap[$header[1]]; | |
1418 } else { | |
1419 $header = 'http_' . $header[1]; | |
1420 } | |
1421 | |
1422 $val = (array)$val; | |
1423 $h = false; | |
1424 | |
1425 foreach ($val as $v) { | |
1426 if (env(strtoupper($header)) === $v) { | |
1427 $h = true; | |
1428 } | |
1429 } | |
1430 if (!$h) { | |
1431 return false; | |
1432 } | |
1433 } | |
1434 } | |
1435 array_shift($parsed); | |
1436 $route = array(); | |
1437 foreach ($this->keys as $i => $key) { | |
1438 if (isset($parsed[$i])) { | |
1439 $route[$key] = $parsed[$i]; | |
1440 } | |
1441 } | |
1442 $route['pass'] = $route['named'] = array(); | |
1443 $route += $this->defaults; | |
1444 if (isset($parsed['_args_'])) { | |
1445 $route['_args_'] = $parsed['_args_']; | |
1446 } | |
1447 foreach ($route as $key => $value) { | |
1448 if (is_integer($key)) { | |
1449 $route['pass'][] = $value; | |
1450 unset($route[$key]); | |
1451 } | |
1452 } | |
1453 return $route; | |
1454 } | |
1455 } | |
1456 | |
1457 /** | |
1458 * Apply persistent parameters to a url array. Persistant parameters are a special | |
1459 * key used during route creation to force route parameters to persist when omitted from | |
1460 * a url array. | |
1461 * | |
1462 * @param array $url The array to apply persistent parameters to. | |
1463 * @param array $params An array of persistent values to replace persistent ones. | |
1464 * @return array An array with persistent parameters applied. | |
1465 * @access public | |
1466 */ | |
1467 function persistParams($url, $params) { | |
1468 foreach ($this->options['persist'] as $persistKey) { | |
1469 if (array_key_exists($persistKey, $params) && !isset($url[$persistKey])) { | |
1470 $url[$persistKey] = $params[$persistKey]; | |
1471 } | |
1472 } | |
1473 return $url; | |
1474 } | |
1475 | |
1476 /** | |
1477 * Attempt to match a url array. If the url matches the route parameters and settings, then | |
1478 * return a generated string url. If the url doesn't match the route parameters, false will be returned. | |
1479 * This method handles the reverse routing or conversion of url arrays into string urls. | |
1480 * | |
1481 * @param array $url An array of parameters to check matching with. | |
1482 * @return mixed Either a string url for the parameters if they match or false. | |
1483 * @access public | |
1484 */ | |
1485 function match($url) { | |
1486 if (!$this->compiled()) { | |
1487 $this->compile(); | |
1488 } | |
1489 $defaults = $this->defaults; | |
1490 | |
1491 if (isset($defaults['prefix'])) { | |
1492 $url['prefix'] = $defaults['prefix']; | |
1493 } | |
1494 | |
1495 //check that all the key names are in the url | |
1496 $keyNames = array_flip($this->keys); | |
1497 if (array_intersect_key($keyNames, $url) != $keyNames) { | |
1498 return false; | |
1499 } | |
1500 | |
1501 $diffUnfiltered = Set::diff($url, $defaults); | |
1502 $diff = array(); | |
1503 | |
1504 foreach ($diffUnfiltered as $key => $var) { | |
1505 if ($var === 0 || $var === '0' || !empty($var)) { | |
1506 $diff[$key] = $var; | |
1507 } | |
1508 } | |
1509 | |
1510 //if a not a greedy route, no extra params are allowed. | |
1511 if (!$this->_greedy && array_diff_key($diff, $keyNames) != array()) { | |
1512 return false; | |
1513 } | |
1514 | |
1515 //remove defaults that are also keys. They can cause match failures | |
1516 foreach ($this->keys as $key) { | |
1517 unset($defaults[$key]); | |
1518 } | |
1519 $filteredDefaults = array_filter($defaults); | |
1520 | |
1521 //if the difference between the url diff and defaults contains keys from defaults its not a match | |
1522 if (array_intersect_key($filteredDefaults, $diffUnfiltered) !== array()) { | |
1523 return false; | |
1524 } | |
1525 | |
1526 $passedArgsAndParams = array_diff_key($diff, $filteredDefaults, $keyNames); | |
1527 list($named, $params) = Router::getNamedElements($passedArgsAndParams, $url['controller'], $url['action']); | |
1528 | |
1529 //remove any pass params, they have numeric indexes, skip any params that are in the defaults | |
1530 $pass = array(); | |
1531 $i = 0; | |
1532 while (isset($url[$i])) { | |
1533 if (!isset($diff[$i])) { | |
1534 $i++; | |
1535 continue; | |
1536 } | |
1537 $pass[] = $url[$i]; | |
1538 unset($url[$i], $params[$i]); | |
1539 $i++; | |
1540 } | |
1541 | |
1542 //still some left over parameters that weren't named or passed args, bail. | |
1543 if (!empty($params)) { | |
1544 return false; | |
1545 } | |
1546 | |
1547 //check patterns for routed params | |
1548 if (!empty($this->options)) { | |
1549 foreach ($this->options as $key => $pattern) { | |
1550 if (array_key_exists($key, $url) && !preg_match('#^' . $pattern . '$#', $url[$key])) { | |
1551 return false; | |
1552 } | |
1553 } | |
1554 } | |
1555 return $this->_writeUrl(array_merge($url, compact('pass', 'named'))); | |
1556 } | |
1557 | |
1558 /** | |
1559 * Converts a matching route array into a url string. Composes the string url using the template | |
1560 * used to create the route. | |
1561 * | |
1562 * @param array $params The params to convert to a string url. | |
1563 * @return string Composed route string. | |
1564 * @access protected | |
1565 */ | |
1566 function _writeUrl($params) { | |
1567 if (isset($params['prefix'], $params['action'])) { | |
1568 $params['action'] = str_replace($params['prefix'] . '_', '', $params['action']); | |
1569 unset($params['prefix']); | |
1570 } | |
1571 | |
1572 if (is_array($params['pass'])) { | |
1573 $params['pass'] = implode('/', $params['pass']); | |
1574 } | |
1575 | |
1576 $instance =& Router::getInstance(); | |
1577 $separator = $instance->named['separator']; | |
1578 | |
1579 if (!empty($params['named']) && is_array($params['named'])) { | |
1580 $named = array(); | |
1581 foreach ($params['named'] as $key => $value) { | |
1582 $named[] = $key . $separator . $value; | |
1583 } | |
1584 $params['pass'] = $params['pass'] . '/' . implode('/', $named); | |
1585 } | |
1586 $out = $this->template; | |
1587 | |
1588 $search = $replace = array(); | |
1589 foreach ($this->keys as $key) { | |
1590 $string = null; | |
1591 if (isset($params[$key])) { | |
1592 $string = $params[$key]; | |
1593 } elseif (strpos($out, $key) != strlen($out) - strlen($key)) { | |
1594 $key .= '/'; | |
1595 } | |
1596 $search[] = ':' . $key; | |
1597 $replace[] = $string; | |
1598 } | |
1599 $out = str_replace($search, $replace, $out); | |
1600 | |
1601 if (strpos($this->template, '*')) { | |
1602 $out = str_replace('*', $params['pass'], $out); | |
1603 } | |
1604 $out = str_replace('//', '/', $out); | |
1605 return $out; | |
1606 } | |
1607 } | |
1608 | |
1609 /** | |
1610 * Plugin short route, that copies the plugin param to the controller parameters | |
1611 * It is used for supporting /:plugin routes. | |
1612 * | |
1613 * @package cake.libs | |
1614 */ | |
1615 class PluginShortRoute extends CakeRoute { | |
1616 | |
1617 /** | |
1618 * Parses a string url into an array. If a plugin key is found, it will be copied to the | |
1619 * controller parameter | |
1620 * | |
1621 * @param string $url The url to parse | |
1622 * @return mixed false on failure, or an array of request parameters | |
1623 */ | |
1624 function parse($url) { | |
1625 $params = parent::parse($url); | |
1626 if (!$params) { | |
1627 return false; | |
1628 } | |
1629 $params['controller'] = $params['plugin']; | |
1630 return $params; | |
1631 } | |
1632 | |
1633 /** | |
1634 * Reverse route plugin shortcut urls. If the plugin and controller | |
1635 * are not the same the match is an auto fail. | |
1636 * | |
1637 * @param array $url Array of parameters to convert to a string. | |
1638 * @return mixed either false or a string url. | |
1639 */ | |
1640 function match($url) { | |
1641 if (isset($url['controller']) && isset($url['plugin']) && $url['plugin'] != $url['controller']) { | |
1642 return false; | |
1643 } | |
1644 $this->defaults['controller'] = $url['controller']; | |
1645 $result = parent::match($url); | |
1646 unset($this->defaults['controller']); | |
1647 return $result; | |
1648 } | |
1649 } |