~tsep-dev/tsep/0.9-beta

« back to all changes in this revision

Viewing changes to branches/symfony/cake/libs/router.php

  • Committer: geoffreyfishing
  • Date: 2011-01-11 23:46:12 UTC
  • Revision ID: svn-v4:ae0de26e-ed09-4cbe-9a20-e40b4c60ac6c::125
Created a symfony branch for future migration to symfony

Show diffs side-by-side

added added

removed removed

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