統計
| ブランチ: | リビジョン:

pictcode / lib / Cake / Routing / Router.php @ 0b1b8047

履歴 | 表示 | アノテート | ダウンロード (38.289 KB)

1
<?php
2
/**
3
 * Parses the request URL into controller, action, and parameters.
4
 *
5
 * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
6
 * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
7
 *
8
 * Licensed under The MIT License
9
 * For full copyright and license information, please see the LICENSE.txt
10
 * Redistributions of files must retain the above copyright notice.
11
 *
12
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
13
 * @link          http://cakephp.org CakePHP(tm) Project
14
 * @package       Cake.Routing
15
 * @since         CakePHP(tm) v 0.2.9
16
 * @license       http://www.opensource.org/licenses/mit-license.php MIT License
17
 */
18

    
19
App::uses('CakeRequest', 'Network');
20
App::uses('CakeRoute', 'Routing/Route');
21

    
22
/**
23
 * Parses the request URL into controller, action, and parameters. Uses the connected routes
24
 * to match the incoming URL string to parameters that will allow the request to be dispatched. Also
25
 * handles converting parameter lists into URL strings, using the connected routes. Routing allows you to decouple
26
 * the way the world interacts with your application (URLs) and the implementation (controllers and actions).
27
 *
28
 * ### Connecting routes
29
 *
30
 * Connecting routes is done using Router::connect(). When parsing incoming requests or reverse matching
31
 * parameters, routes are enumerated in the order they were connected. You can modify the order of connected
32
 * routes using Router::promote(). For more information on routes and how to connect them see Router::connect().
33
 *
34
 * ### Named parameters
35
 *
36
 * Named parameters allow you to embed key:value pairs into path segments. This allows you create hash
37
 * structures using URLs. You can define how named parameters work in your application using Router::connectNamed()
38
 *
39
 * @package       Cake.Routing
40
 */
41
class Router {
42

    
43
/**
44
 * Array of routes connected with Router::connect()
45
 *
46
 * @var array
47
 */
48
        public static $routes = array();
49

    
50
/**
51
 * Have routes been loaded
52
 *
53
 * @var bool
54
 */
55
        public static $initialized = false;
56

    
57
/**
58
 * Contains the base string that will be applied to all generated URLs
59
 * For example `https://example.com`
60
 *
61
 * @var string
62
 */
63
        protected static $_fullBaseUrl;
64

    
65
/**
66
 * List of action prefixes used in connected routes.
67
 * Includes admin prefix
68
 *
69
 * @var array
70
 */
71
        protected static $_prefixes = array();
72

    
73
/**
74
 * Directive for Router to parse out file extensions for mapping to Content-types.
75
 *
76
 * @var bool
77
 */
78
        protected static $_parseExtensions = false;
79

    
80
/**
81
 * List of valid extensions to parse from a URL. If null, any extension is allowed.
82
 *
83
 * @var array
84
 */
85
        protected static $_validExtensions = array();
86

    
87
/**
88
 * Regular expression for action names
89
 *
90
 * @var string
91
 */
92
        const ACTION = 'index|show|add|create|edit|update|remove|del|delete|view|item';
93

    
94
/**
95
 * Regular expression for years
96
 *
97
 * @var string
98
 */
99
        const YEAR = '[12][0-9]{3}';
100

    
101
/**
102
 * Regular expression for months
103
 *
104
 * @var string
105
 */
106
        const MONTH = '0[1-9]|1[012]';
107

    
108
/**
109
 * Regular expression for days
110
 *
111
 * @var string
112
 */
113
        const DAY = '0[1-9]|[12][0-9]|3[01]';
114

    
115
/**
116
 * Regular expression for auto increment IDs
117
 *
118
 * @var string
119
 */
120
        const ID = '[0-9]+';
121

    
122
/**
123
 * Regular expression for UUIDs
124
 *
125
 * @var string
126
 */
127
        const 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}';
128

    
129
/**
130
 * Named expressions
131
 *
132
 * @var array
133
 */
134
        protected static $_namedExpressions = array(
135
                'Action' => Router::ACTION,
136
                'Year' => Router::YEAR,
137
                'Month' => Router::MONTH,
138
                'Day' => Router::DAY,
139
                'ID' => Router::ID,
140
                'UUID' => Router::UUID
141
        );
142

    
143
/**
144
 * Stores all information necessary to decide what named arguments are parsed under what conditions.
145
 *
146
 * @var string
147
 */
148
        protected static $_namedConfig = array(
149
                'default' => array('page', 'fields', 'order', 'limit', 'recursive', 'sort', 'direction', 'step'),
150
                'greedyNamed' => true,
151
                'separator' => ':',
152
                'rules' => false,
153
        );
154

    
155
/**
156
 * The route matching the URL of the current request
157
 *
158
 * @var array
159
 */
160
        protected static $_currentRoute = array();
161

    
162
/**
163
 * Default HTTP request method => controller action map.
164
 *
165
 * @var array
166
 */
167
        protected static $_resourceMap = array(
168
                array('action' => 'index', 'method' => 'GET', 'id' => false),
169
                array('action' => 'view', 'method' => 'GET', 'id' => true),
170
                array('action' => 'add', 'method' => 'POST', 'id' => false),
171
                array('action' => 'edit', 'method' => 'PUT', 'id' => true),
172
                array('action' => 'delete', 'method' => 'DELETE', 'id' => true),
173
                array('action' => 'edit', 'method' => 'POST', 'id' => true)
174
        );
175

    
176
/**
177
 * List of resource-mapped controllers
178
 *
179
 * @var array
180
 */
181
        protected static $_resourceMapped = array();
182

    
183
/**
184
 * Maintains the request object stack for the current request.
185
 * This will contain more than one request object when requestAction is used.
186
 *
187
 * @var array
188
 */
189
        protected static $_requests = array();
190

    
191
/**
192
 * Initial state is populated the first time reload() is called which is at the bottom
193
 * of this file. This is a cheat as get_class_vars() returns the value of static vars even if they
194
 * have changed.
195
 *
196
 * @var array
197
 */
198
        protected static $_initialState = array();
199

    
200
/**
201
 * Default route class to use
202
 *
203
 * @var string
204
 */
205
        protected static $_routeClass = 'CakeRoute';
206

    
207
/**
208
 * Set the default route class to use or return the current one
209
 *
210
 * @param string $routeClass The route class to set as default.
211
 * @return string|null The default route class.
212
 * @throws RouterException
213
 */
214
        public static function defaultRouteClass($routeClass = null) {
215
                if ($routeClass === null) {
216
                        return static::$_routeClass;
217
                }
218

    
219
                static::$_routeClass = static::_validateRouteClass($routeClass);
220
        }
221

    
222
/**
223
 * Validates that the passed route class exists and is a subclass of CakeRoute
224
 *
225
 * @param string $routeClass Route class name
226
 * @return string
227
 * @throws RouterException
228
 */
229
        protected static function _validateRouteClass($routeClass) {
230
                if ($routeClass !== 'CakeRoute' &&
231
                        (!class_exists($routeClass) || !is_subclass_of($routeClass, 'CakeRoute'))
232
                ) {
233
                        throw new RouterException(__d('cake_dev', 'Route class not found, or route class is not a subclass of CakeRoute'));
234
                }
235
                return $routeClass;
236
        }
237

    
238
/**
239
 * Sets the Routing prefixes.
240
 *
241
 * @return void
242
 */
243
        protected static function _setPrefixes() {
244
                $routing = Configure::read('Routing');
245
                if (!empty($routing['prefixes'])) {
246
                        static::$_prefixes = array_merge(static::$_prefixes, (array)$routing['prefixes']);
247
                }
248
        }
249

    
250
/**
251
 * Gets the named route elements for use in app/Config/routes.php
252
 *
253
 * @return array Named route elements
254
 * @see Router::$_namedExpressions
255
 */
256
        public static function getNamedExpressions() {
257
                return static::$_namedExpressions;
258
        }
259

    
260
/**
261
 * Resource map getter & setter.
262
 *
263
 * @param array $resourceMap Resource map
264
 * @return mixed
265
 * @see Router::$_resourceMap
266
 */
267
        public static function resourceMap($resourceMap = null) {
268
                if ($resourceMap === null) {
269
                        return static::$_resourceMap;
270
                }
271
                static::$_resourceMap = $resourceMap;
272
        }
273

    
274
/**
275
 * Connects a new Route in the router.
276
 *
277
 * Routes are a way of connecting request URLs to objects in your application. At their core routes
278
 * are a set of regular expressions that are used to match requests to destinations.
279
 *
280
 * Examples:
281
 *
282
 * `Router::connect('/:controller/:action/*');`
283
 *
284
 * The first token ':controller' will be used as a controller name while the second is used as the action name.
285
 * the '/*' syntax makes this route greedy in that it will match requests like `/posts/index` as well as requests
286
 * like `/posts/edit/1/foo/bar`.
287
 *
288
 * `Router::connect('/home-page', array('controller' => 'pages', 'action' => 'display', 'home'));`
289
 *
290
 * The above shows the use of route parameter defaults, and providing routing parameters for a static route.
291
 *
292
 * ```
293
 * Router::connect(
294
 *   '/:lang/:controller/:action/:id',
295
 *   array(),
296
 *   array('id' => '[0-9]+', 'lang' => '[a-z]{3}')
297
 * );
298
 * ```
299
 *
300
 * Shows connecting a route with custom route parameters as well as providing patterns for those parameters.
301
 * Patterns for routing parameters do not need capturing groups, as one will be added for each route params.
302
 *
303
 * $defaults is merged with the results of parsing the request URL to form the final routing destination and its
304
 * parameters. This destination is expressed as an associative array by Router. See the output of {@link parse()}.
305
 *
306
 * $options offers four 'special' keys. `pass`, `named`, `persist` and `routeClass`
307
 * have special meaning in the $options array.
308
 *
309
 * - `pass` is used to define which of the routed parameters should be shifted into the pass array. Adding a
310
 *   parameter to pass will remove it from the regular route array. Ex. `'pass' => array('slug')`
311
 * - `persist` is used to define which route parameters should be automatically included when generating
312
 *   new URLs. You can override persistent parameters by redefining them in a URL or remove them by
313
 *   setting the parameter to `false`. Ex. `'persist' => array('lang')`
314
 * - `routeClass` is used to extend and change how individual routes parse requests and handle reverse routing,
315
 *   via a custom routing class. Ex. `'routeClass' => 'SlugRoute'`
316
 * - `named` is used to configure named parameters at the route level. This key uses the same options
317
 *   as Router::connectNamed()
318
 *
319
 * You can also add additional conditions for matching routes to the $defaults array.
320
 * The following conditions can be used:
321
 *
322
 * - `[type]` Only match requests for specific content types.
323
 * - `[method]` Only match requests with specific HTTP verbs.
324
 * - `[server]` Only match when $_SERVER['SERVER_NAME'] matches the given value.
325
 *
326
 * Example of using the `[method]` condition:
327
 *
328
 * `Router::connect('/tasks', array('controller' => 'tasks', 'action' => 'index', '[method]' => 'GET'));`
329
 *
330
 * The above route will only be matched for GET requests. POST requests will fail to match this route.
331
 *
332
 * @param string $route A string describing the template of the route
333
 * @param array $defaults An array describing the default route parameters. These parameters will be used by default
334
 *   and can supply routing parameters that are not dynamic. See above.
335
 * @param array $options An array matching the named elements in the route to regular expressions which that
336
 *   element should match. Also contains additional parameters such as which routed parameters should be
337
 *   shifted into the passed arguments, supplying patterns for routing parameters and supplying the name of a
338
 *   custom routing class.
339
 * @see routes
340
 * @see parse().
341
 * @return array Array of routes
342
 * @throws RouterException
343
 */
344
        public static function connect($route, $defaults = array(), $options = array()) {
345
                static::$initialized = true;
346

    
347
                foreach (static::$_prefixes as $prefix) {
348
                        if (isset($defaults[$prefix])) {
349
                                if ($defaults[$prefix]) {
350
                                        $defaults['prefix'] = $prefix;
351
                                } else {
352
                                        unset($defaults[$prefix]);
353
                                }
354
                                break;
355
                        }
356
                }
357
                if (isset($defaults['prefix']) && !in_array($defaults['prefix'], static::$_prefixes)) {
358
                        static::$_prefixes[] = $defaults['prefix'];
359
                }
360
                $defaults += array('plugin' => null);
361
                if (empty($options['action'])) {
362
                        $defaults += array('action' => 'index');
363
                }
364
                $routeClass = static::$_routeClass;
365
                if (isset($options['routeClass'])) {
366
                        if (strpos($options['routeClass'], '.') === false) {
367
                                $routeClass = $options['routeClass'];
368
                        } else {
369
                                list(, $routeClass) = pluginSplit($options['routeClass'], true);
370
                        }
371
                        $routeClass = static::_validateRouteClass($routeClass);
372
                        unset($options['routeClass']);
373
                }
374
                if ($routeClass === 'RedirectRoute' && isset($defaults['redirect'])) {
375
                        $defaults = $defaults['redirect'];
376
                }
377
                static::$routes[] = new $routeClass($route, $defaults, $options);
378
                return static::$routes;
379
        }
380

    
381
/**
382
 * Connects a new redirection Route in the router.
383
 *
384
 * Redirection routes are different from normal routes as they perform an actual
385
 * header redirection if a match is found. The redirection can occur within your
386
 * application or redirect to an outside location.
387
 *
388
 * Examples:
389
 *
390
 * `Router::redirect('/home/*', array('controller' => 'posts', 'action' => 'view'), array('persist' => true));`
391
 *
392
 * Redirects /home/* to /posts/view and passes the parameters to /posts/view. Using an array as the
393
 * redirect destination allows you to use other routes to define where a URL string should be redirected to.
394
 *
395
 * `Router::redirect('/posts/*', 'http://google.com', array('status' => 302));`
396
 *
397
 * Redirects /posts/* to http://google.com with a HTTP status of 302
398
 *
399
 * ### Options:
400
 *
401
 * - `status` Sets the HTTP status (default 301)
402
 * - `persist` Passes the params to the redirected route, if it can. This is useful with greedy routes,
403
 *   routes that end in `*` are greedy. As you can remap URLs and not loose any passed/named args.
404
 *
405
 * @param string $route A string describing the template of the route
406
 * @param array $url A URL to redirect to. Can be a string or a CakePHP array-based URL
407
 * @param array $options An array matching the named elements in the route to regular expressions which that
408
 *   element should match. Also contains additional parameters such as which routed parameters should be
409
 *   shifted into the passed arguments. As well as supplying patterns for routing parameters.
410
 * @see routes
411
 * @return array Array of routes
412
 */
413
        public static function redirect($route, $url, $options = array()) {
414
                App::uses('RedirectRoute', 'Routing/Route');
415
                $options['routeClass'] = 'RedirectRoute';
416
                if (is_string($url)) {
417
                        $url = array('redirect' => $url);
418
                }
419
                return static::connect($route, $url, $options);
420
        }
421

    
422
/**
423
 * Specifies what named parameters CakePHP should be parsing out of incoming URLs. By default
424
 * CakePHP will parse every named parameter out of incoming URLs. However, if you want to take more
425
 * control over how named parameters are parsed you can use one of the following setups:
426
 *
427
 * Do not parse any named parameters:
428
 *
429
 * ``` Router::connectNamed(false); ```
430
 *
431
 * Parse only default parameters used for CakePHP's pagination:
432
 *
433
 * ``` Router::connectNamed(false, array('default' => true)); ```
434
 *
435
 * Parse only the page parameter if its value is a number:
436
 *
437
 * ``` Router::connectNamed(array('page' => '[\d]+'), array('default' => false, 'greedy' => false)); ```
438
 *
439
 * Parse only the page parameter no matter what.
440
 *
441
 * ``` Router::connectNamed(array('page'), array('default' => false, 'greedy' => false)); ```
442
 *
443
 * Parse only the page parameter if the current action is 'index'.
444
 *
445
 * ```
446
 * Router::connectNamed(
447
 *    array('page' => array('action' => 'index')),
448
 *    array('default' => false, 'greedy' => false)
449
 * );
450
 * ```
451
 *
452
 * Parse only the page parameter if the current action is 'index' and the controller is 'pages'.
453
 *
454
 * ```
455
 * Router::connectNamed(
456
 *    array('page' => array('action' => 'index', 'controller' => 'pages')),
457
 *    array('default' => false, 'greedy' => false)
458
 * );
459
 * ```
460
 *
461
 * ### Options
462
 *
463
 * - `greedy` Setting this to true will make Router parse all named params. Setting it to false will
464
 *    parse only the connected named params.
465
 * - `default` Set this to true to merge in the default set of named parameters.
466
 * - `reset` Set to true to clear existing rules and start fresh.
467
 * - `separator` Change the string used to separate the key & value in a named parameter. Defaults to `:`
468
 *
469
 * @param array $named A list of named parameters. Key value pairs are accepted where values are
470
 *    either regex strings to match, or arrays as seen above.
471
 * @param array $options Allows to control all settings: separator, greedy, reset, default
472
 * @return array
473
 */
474
        public static function connectNamed($named, $options = array()) {
475
                if (isset($options['separator'])) {
476
                        static::$_namedConfig['separator'] = $options['separator'];
477
                        unset($options['separator']);
478
                }
479

    
480
                if ($named === true || $named === false) {
481
                        $options += array('default' => $named, 'reset' => true, 'greedy' => $named);
482
                        $named = array();
483
                } else {
484
                        $options += array('default' => false, 'reset' => false, 'greedy' => true);
485
                }
486

    
487
                if ($options['reset'] || static::$_namedConfig['rules'] === false) {
488
                        static::$_namedConfig['rules'] = array();
489
                }
490

    
491
                if ($options['default']) {
492
                        $named = array_merge($named, static::$_namedConfig['default']);
493
                }
494

    
495
                foreach ($named as $key => $val) {
496
                        if (is_numeric($key)) {
497
                                static::$_namedConfig['rules'][$val] = true;
498
                        } else {
499
                                static::$_namedConfig['rules'][$key] = $val;
500
                        }
501
                }
502
                static::$_namedConfig['greedyNamed'] = $options['greedy'];
503
                return static::$_namedConfig;
504
        }
505

    
506
/**
507
 * Gets the current named parameter configuration values.
508
 *
509
 * @return array
510
 * @see Router::$_namedConfig
511
 */
512
        public static function namedConfig() {
513
                return static::$_namedConfig;
514
        }
515

    
516
/**
517
 * Creates REST resource routes for the given controller(s). When creating resource routes
518
 * for a plugin, by default the prefix will be changed to the lower_underscore version of the plugin
519
 * name. By providing a prefix you can override this behavior.
520
 *
521
 * ### Options:
522
 *
523
 * - 'id' - The regular expression fragment to use when matching IDs. By default, matches
524
 *    integer values and UUIDs.
525
 * - 'prefix' - URL prefix to use for the generated routes. Defaults to '/'.
526
 *
527
 * @param string|array $controller A controller name or array of controller names (i.e. "Posts" or "ListItems")
528
 * @param array $options Options to use when generating REST routes
529
 * @return array Array of mapped resources
530
 */
531
        public static function mapResources($controller, $options = array()) {
532
                $hasPrefix = isset($options['prefix']);
533
                $options += array(
534
                        'connectOptions' => array(),
535
                        'prefix' => '/',
536
                        'id' => static::ID . '|' . static::UUID
537
                );
538

    
539
                $prefix = $options['prefix'];
540
                $connectOptions = $options['connectOptions'];
541
                unset($options['connectOptions']);
542
                if (strpos($prefix, '/') !== 0) {
543
                        $prefix = '/' . $prefix;
544
                }
545
                if (substr($prefix, -1) !== '/') {
546
                        $prefix .= '/';
547
                }
548

    
549
                foreach ((array)$controller as $name) {
550
                        list($plugin, $name) = pluginSplit($name);
551
                        $urlName = Inflector::underscore($name);
552
                        $plugin = Inflector::underscore($plugin);
553
                        if ($plugin && !$hasPrefix) {
554
                                $prefix = '/' . $plugin . '/';
555
                        }
556

    
557
                        foreach (static::$_resourceMap as $params) {
558
                                $url = $prefix . $urlName . (($params['id']) ? '/:id' : '');
559

    
560
                                Router::connect($url,
561
                                        array(
562
                                                'plugin' => $plugin,
563
                                                'controller' => $urlName,
564
                                                'action' => $params['action'],
565
                                                '[method]' => $params['method']
566
                                        ),
567
                                        array_merge(
568
                                                array('id' => $options['id'], 'pass' => array('id')),
569
                                                $connectOptions
570
                                        )
571
                                );
572
                        }
573
                        static::$_resourceMapped[] = $urlName;
574
                }
575
                return static::$_resourceMapped;
576
        }
577

    
578
/**
579
 * Returns the list of prefixes used in connected routes
580
 *
581
 * @return array A list of prefixes used in connected routes
582
 */
583
        public static function prefixes() {
584
                return static::$_prefixes;
585
        }
586

    
587
/**
588
 * Parses given URL string. Returns 'routing' parameters for that URL.
589
 *
590
 * @param string $url URL to be parsed
591
 * @return array Parsed elements from URL
592
 */
593
        public static function parse($url) {
594
                if (!static::$initialized) {
595
                        static::_loadRoutes();
596
                }
597

    
598
                $ext = null;
599
                $out = array();
600

    
601
                if (strlen($url) && strpos($url, '/') !== 0) {
602
                        $url = '/' . $url;
603
                }
604
                if (strpos($url, '?') !== false) {
605
                        list($url, $queryParameters) = explode('?', $url, 2);
606
                        parse_str($queryParameters, $queryParameters);
607
                }
608

    
609
                extract(static::_parseExtension($url));
610

    
611
                foreach (static::$routes as $route) {
612
                        if (($r = $route->parse($url)) !== false) {
613
                                static::$_currentRoute[] = $route;
614
                                $out = $r;
615
                                break;
616
                        }
617
                }
618
                if (isset($out['prefix'])) {
619
                        $out['action'] = $out['prefix'] . '_' . $out['action'];
620
                }
621

    
622
                if (!empty($ext) && !isset($out['ext'])) {
623
                        $out['ext'] = $ext;
624
                }
625

    
626
                if (!empty($queryParameters) && !isset($out['?'])) {
627
                        $out['?'] = $queryParameters;
628
                }
629
                return $out;
630
        }
631

    
632
/**
633
 * Parses a file extension out of a URL, if Router::parseExtensions() is enabled.
634
 *
635
 * @param string $url URL.
636
 * @return array Returns an array containing the altered URL and the parsed extension.
637
 */
638
        protected static function _parseExtension($url) {
639
                $ext = null;
640

    
641
                if (static::$_parseExtensions) {
642
                        if (preg_match('/\.[0-9a-zA-Z]*$/', $url, $match) === 1) {
643
                                $match = substr($match[0], 1);
644
                                if (empty(static::$_validExtensions)) {
645
                                        $url = substr($url, 0, strpos($url, '.' . $match));
646
                                        $ext = $match;
647
                                } else {
648
                                        foreach (static::$_validExtensions as $name) {
649
                                                if (strcasecmp($name, $match) === 0) {
650
                                                        $url = substr($url, 0, strpos($url, '.' . $name));
651
                                                        $ext = $match;
652
                                                        break;
653
                                                }
654
                                        }
655
                                }
656
                        }
657
                }
658
                return compact('ext', 'url');
659
        }
660

    
661
/**
662
 * Takes parameter and path information back from the Dispatcher, sets these
663
 * parameters as the current request parameters that are merged with URL arrays
664
 * created later in the request.
665
 *
666
 * Nested requests will create a stack of requests. You can remove requests using
667
 * Router::popRequest(). This is done automatically when using Object::requestAction().
668
 *
669
 * Will accept either a CakeRequest object or an array of arrays. Support for
670
 * accepting arrays may be removed in the future.
671
 *
672
 * @param CakeRequest|array $request Parameters and path information or a CakeRequest object.
673
 * @return void
674
 */
675
        public static function setRequestInfo($request) {
676
                if ($request instanceof CakeRequest) {
677
                        static::$_requests[] = $request;
678
                } else {
679
                        $requestObj = new CakeRequest();
680
                        $request += array(array(), array());
681
                        $request[0] += array('controller' => false, 'action' => false, 'plugin' => null);
682
                        $requestObj->addParams($request[0])->addPaths($request[1]);
683
                        static::$_requests[] = $requestObj;
684
                }
685
        }
686

    
687
/**
688
 * Pops a request off of the request stack. Used when doing requestAction
689
 *
690
 * @return CakeRequest The request removed from the stack.
691
 * @see Router::setRequestInfo()
692
 * @see Object::requestAction()
693
 */
694
        public static function popRequest() {
695
                return array_pop(static::$_requests);
696
        }
697

    
698
/**
699
 * Gets the current request object, or the first one.
700
 *
701
 * @param bool $current True to get the current request object, or false to get the first one.
702
 * @return CakeRequest|null Null if stack is empty.
703
 */
704
        public static function getRequest($current = false) {
705
                if ($current) {
706
                        $i = count(static::$_requests) - 1;
707
                        return isset(static::$_requests[$i]) ? static::$_requests[$i] : null;
708
                }
709
                return isset(static::$_requests[0]) ? static::$_requests[0] : null;
710
        }
711

    
712
/**
713
 * Gets parameter information
714
 *
715
 * @param bool $current Get current request parameter, useful when using requestAction
716
 * @return array Parameter information
717
 */
718
        public static function getParams($current = false) {
719
                if ($current && static::$_requests) {
720
                        return static::$_requests[count(static::$_requests) - 1]->params;
721
                }
722
                if (isset(static::$_requests[0])) {
723
                        return static::$_requests[0]->params;
724
                }
725
                return array();
726
        }
727

    
728
/**
729
 * Gets URL parameter by name
730
 *
731
 * @param string $name Parameter name
732
 * @param bool $current Current parameter, useful when using requestAction
733
 * @return string|null Parameter value
734
 */
735
        public static function getParam($name = 'controller', $current = false) {
736
                $params = Router::getParams($current);
737
                if (isset($params[$name])) {
738
                        return $params[$name];
739
                }
740
                return null;
741
        }
742

    
743
/**
744
 * Gets path information
745
 *
746
 * @param bool $current Current parameter, useful when using requestAction
747
 * @return array
748
 */
749
        public static function getPaths($current = false) {
750
                if ($current) {
751
                        return static::$_requests[count(static::$_requests) - 1];
752
                }
753
                if (!isset(static::$_requests[0])) {
754
                        return array('base' => null);
755
                }
756
                return array('base' => static::$_requests[0]->base);
757
        }
758

    
759
/**
760
 * Reloads default Router settings. Resets all class variables and
761
 * removes all connected routes.
762
 *
763
 * @return void
764
 */
765
        public static function reload() {
766
                if (empty(static::$_initialState)) {
767
                        static::$_initialState = get_class_vars('Router');
768
                        static::_setPrefixes();
769
                        return;
770
                }
771
                foreach (static::$_initialState as $key => $val) {
772
                        if ($key !== '_initialState') {
773
                                static::${$key} = $val;
774
                        }
775
                }
776
                static::_setPrefixes();
777
        }
778

    
779
/**
780
 * Promote a route (by default, the last one added) to the beginning of the list
781
 *
782
 * @param int $which A zero-based array index representing the route to move. For example,
783
 *    if 3 routes have been added, the last route would be 2.
784
 * @return bool Returns false if no route exists at the position specified by $which.
785
 */
786
        public static function promote($which = null) {
787
                if ($which === null) {
788
                        $which = count(static::$routes) - 1;
789
                }
790
                if (!isset(static::$routes[$which])) {
791
                        return false;
792
                }
793
                $route =& static::$routes[$which];
794
                unset(static::$routes[$which]);
795
                array_unshift(static::$routes, $route);
796
                return true;
797
        }
798

    
799
/**
800
 * Finds URL for specified action.
801
 *
802
 * Returns a URL pointing to a combination of controller and action. Param
803
 * $url can be:
804
 *
805
 * - Empty - the method will find address to actual controller/action.
806
 * - '/' - the method will find base URL of application.
807
 * - A combination of controller/action - the method will find URL for it.
808
 *
809
 * There are a few 'special' parameters that can change the final URL string that is generated
810
 *
811
 * - `base` - Set to false to remove the base path from the generated URL. If your application
812
 *   is not in the root directory, this can be used to generate URLs that are 'cake relative'.
813
 *   cake relative URLs are required when using requestAction.
814
 * - `?` - Takes an array of query string parameters
815
 * - `#` - Allows you to set URL hash fragments.
816
 * - `full_base` - If true the `Router::fullBaseUrl()` value will be prepended to generated URLs.
817
 *
818
 * @param string|array $url Cake-relative URL, like "/products/edit/92" or "/presidents/elect/4"
819
 *   or an array specifying any of the following: 'controller', 'action',
820
 *   and/or 'plugin', in addition to named arguments (keyed array elements),
821
 *   and standard URL arguments (indexed array elements)
822
 * @param bool|array $full If (bool) true, the full base URL will be prepended to the result.
823
 *   If an array accepts the following keys
824
 *    - escape - used when making URLs embedded in html escapes query string '&'
825
 *    - full - if true the full base URL will be prepended.
826
 * @return string Full translated URL with base path.
827
 */
828
        public static function url($url = null, $full = false) {
829
                if (!static::$initialized) {
830
                        static::_loadRoutes();
831
                }
832

    
833
                $params = array('plugin' => null, 'controller' => null, 'action' => 'index');
834

    
835
                if (is_bool($full)) {
836
                        $escape = false;
837
                } else {
838
                        extract($full + array('escape' => false, 'full' => false));
839
                }
840

    
841
                $path = array('base' => null);
842
                if (!empty(static::$_requests)) {
843
                        $request = static::$_requests[count(static::$_requests) - 1];
844
                        $params = $request->params;
845
                        $path = array('base' => $request->base, 'here' => $request->here);
846
                }
847
                if (empty($path['base'])) {
848
                        $path['base'] = Configure::read('App.base');
849
                }
850

    
851
                $base = $path['base'];
852
                $extension = $output = $q = $frag = null;
853

    
854
                if (empty($url)) {
855
                        $output = isset($path['here']) ? $path['here'] : '/';
856
                        if ($full) {
857
                                $output = static::fullBaseUrl() . $output;
858
                        }
859
                        return $output;
860
                } elseif (is_array($url)) {
861
                        if (isset($url['base']) && $url['base'] === false) {
862
                                $base = null;
863
                                unset($url['base']);
864
                        }
865
                        if (isset($url['full_base']) && $url['full_base'] === true) {
866
                                $full = true;
867
                                unset($url['full_base']);
868
                        }
869
                        if (isset($url['?'])) {
870
                                $q = $url['?'];
871
                                unset($url['?']);
872
                        }
873
                        if (isset($url['#'])) {
874
                                $frag = '#' . $url['#'];
875
                                unset($url['#']);
876
                        }
877
                        if (isset($url['ext'])) {
878
                                $extension = '.' . $url['ext'];
879
                                unset($url['ext']);
880
                        }
881
                        if (empty($url['action'])) {
882
                                if (empty($url['controller']) || $params['controller'] === $url['controller']) {
883
                                        $url['action'] = $params['action'];
884
                                } else {
885
                                        $url['action'] = 'index';
886
                                }
887
                        }
888

    
889
                        $prefixExists = (array_intersect_key($url, array_flip(static::$_prefixes)));
890
                        foreach (static::$_prefixes as $prefix) {
891
                                if (!empty($params[$prefix]) && !$prefixExists) {
892
                                        $url[$prefix] = true;
893
                                } elseif (isset($url[$prefix]) && !$url[$prefix]) {
894
                                        unset($url[$prefix]);
895
                                }
896
                                if (isset($url[$prefix]) && strpos($url['action'], $prefix . '_') === 0) {
897
                                        $url['action'] = substr($url['action'], strlen($prefix) + 1);
898
                                }
899
                        }
900

    
901
                        $url += array('controller' => $params['controller'], 'plugin' => $params['plugin']);
902

    
903
                        $match = false;
904

    
905
                        foreach (static::$routes as $route) {
906
                                $originalUrl = $url;
907

    
908
                                $url = $route->persistParams($url, $params);
909

    
910
                                if ($match = $route->match($url)) {
911
                                        $output = trim($match, '/');
912
                                        break;
913
                                }
914
                                $url = $originalUrl;
915
                        }
916
                        if ($match === false) {
917
                                $output = static::_handleNoRoute($url);
918
                        }
919
                } else {
920
                        if (preg_match('/^([a-z][a-z0-9.+\-]+:|:?\/\/|[#?])/i', $url)) {
921
                                return $url;
922
                        }
923
                        if (substr($url, 0, 1) === '/') {
924
                                $output = substr($url, 1);
925
                        } else {
926
                                foreach (static::$_prefixes as $prefix) {
927
                                        if (isset($params[$prefix])) {
928
                                                $output .= $prefix . '/';
929
                                                break;
930
                                        }
931
                                }
932
                                if (!empty($params['plugin']) && $params['plugin'] !== $params['controller']) {
933
                                        $output .= Inflector::underscore($params['plugin']) . '/';
934
                                }
935
                                $output .= Inflector::underscore($params['controller']) . '/' . $url;
936
                        }
937
                }
938
                $protocol = preg_match('#^[a-z][a-z0-9+\-.]*\://#i', $output);
939
                if ($protocol === 0) {
940
                        $output = str_replace('//', '/', $base . '/' . $output);
941

    
942
                        if ($full) {
943
                                $output = static::fullBaseUrl() . $output;
944
                        }
945
                        if (!empty($extension)) {
946
                                $output = rtrim($output, '/');
947
                        }
948
                }
949
                return $output . $extension . static::queryString($q, array(), $escape) . $frag;
950
        }
951

    
952
/**
953
 * Sets the full base URL that will be used as a prefix for generating
954
 * fully qualified URLs for this application. If no parameters are passed,
955
 * the currently configured value is returned.
956
 *
957
 * ## Note:
958
 *
959
 * If you change the configuration value ``App.fullBaseUrl`` during runtime
960
 * and expect the router to produce links using the new setting, you are
961
 * required to call this method passing such value again.
962
 *
963
 * @param string $base the prefix for URLs generated containing the domain.
964
 * For example: ``http://example.com``
965
 * @return string
966
 */
967
        public static function fullBaseUrl($base = null) {
968
                if ($base !== null) {
969
                        static::$_fullBaseUrl = $base;
970
                        Configure::write('App.fullBaseUrl', $base);
971
                }
972
                if (empty(static::$_fullBaseUrl)) {
973
                        static::$_fullBaseUrl = Configure::read('App.fullBaseUrl');
974
                }
975
                return static::$_fullBaseUrl;
976
        }
977

    
978
/**
979
 * A special fallback method that handles URL arrays that cannot match
980
 * any defined routes.
981
 *
982
 * @param array $url A URL that didn't match any routes
983
 * @return string A generated URL for the array
984
 * @see Router::url()
985
 */
986
        protected static function _handleNoRoute($url) {
987
                $named = $args = array();
988
                $skip = array_merge(
989
                        array('bare', 'action', 'controller', 'plugin', 'prefix'),
990
                        static::$_prefixes
991
                );
992

    
993
                $keys = array_values(array_diff(array_keys($url), $skip));
994

    
995
                // Remove this once parsed URL parameters can be inserted into 'pass'
996
                foreach ($keys as $key) {
997
                        if (is_numeric($key)) {
998
                                $args[] = $url[$key];
999
                        } else {
1000
                                $named[$key] = $url[$key];
1001
                        }
1002
                }
1003

    
1004
                list($args, $named) = array(Hash::filter($args), Hash::filter($named));
1005
                foreach (static::$_prefixes as $prefix) {
1006
                        $prefixed = $prefix . '_';
1007
                        if (!empty($url[$prefix]) && strpos($url['action'], $prefixed) === 0) {
1008
                                $url['action'] = substr($url['action'], strlen($prefixed) * -1);
1009
                                break;
1010
                        }
1011
                }
1012

    
1013
                if (empty($named) && empty($args) && (!isset($url['action']) || $url['action'] === 'index')) {
1014
                        $url['action'] = null;
1015
                }
1016

    
1017
                $urlOut = array_filter(array($url['controller'], $url['action']));
1018

    
1019
                if (isset($url['plugin'])) {
1020
                        array_unshift($urlOut, $url['plugin']);
1021
                }
1022

    
1023
                foreach (static::$_prefixes as $prefix) {
1024
                        if (isset($url[$prefix])) {
1025
                                array_unshift($urlOut, $prefix);
1026
                                break;
1027
                        }
1028
                }
1029
                $output = implode('/', $urlOut);
1030

    
1031
                if (!empty($args)) {
1032
                        $output .= '/' . implode('/', array_map('rawurlencode', $args));
1033
                }
1034

    
1035
                if (!empty($named)) {
1036
                        foreach ($named as $name => $value) {
1037
                                if (is_array($value)) {
1038
                                        $flattend = Hash::flatten($value, '%5D%5B');
1039
                                        foreach ($flattend as $namedKey => $namedValue) {
1040
                                                $output .= '/' . $name . "%5B{$namedKey}%5D" . static::$_namedConfig['separator'] . rawurlencode($namedValue);
1041
                                        }
1042
                                } else {
1043
                                        $output .= '/' . $name . static::$_namedConfig['separator'] . rawurlencode($value);
1044
                                }
1045
                        }
1046
                }
1047
                return $output;
1048
        }
1049

    
1050
/**
1051
 * Generates a well-formed querystring from $q
1052
 *
1053
 * @param string|array $q Query string Either a string of already compiled query string arguments or
1054
 *    an array of arguments to convert into a query string.
1055
 * @param array $extra Extra querystring parameters.
1056
 * @param bool $escape Whether or not to use escaped &
1057
 * @return array
1058
 */
1059
        public static function queryString($q, $extra = array(), $escape = false) {
1060
                if (empty($q) && empty($extra)) {
1061
                        return null;
1062
                }
1063
                $join = '&';
1064
                if ($escape === true) {
1065
                        $join = '&amp;';
1066
                }
1067
                $out = '';
1068

    
1069
                if (is_array($q)) {
1070
                        $q = array_merge($q, $extra);
1071
                } else {
1072
                        $out = $q;
1073
                        $q = $extra;
1074
                }
1075
                $addition = http_build_query($q, null, $join);
1076

    
1077
                if ($out && $addition && substr($out, strlen($join) * -1, strlen($join)) !== $join) {
1078
                        $out .= $join;
1079
                }
1080

    
1081
                $out .= $addition;
1082

    
1083
                if (isset($out[0]) && $out[0] !== '?') {
1084
                        $out = '?' . $out;
1085
                }
1086
                return $out;
1087
        }
1088

    
1089
/**
1090
 * Reverses a parsed parameter array into a string.
1091
 *
1092
 * Works similarly to Router::url(), but since parsed URL's contain additional
1093
 * 'pass' and 'named' as well as 'url.url' keys. Those keys need to be specially
1094
 * handled in order to reverse a params array into a string URL.
1095
 *
1096
 * This will strip out 'autoRender', 'bare', 'requested', and 'return' param names as those
1097
 * are used for CakePHP internals and should not normally be part of an output URL.
1098
 *
1099
 * @param CakeRequest|array $params The params array or CakeRequest object that needs to be reversed.
1100
 * @param bool $full Set to true to include the full URL including the protocol when reversing
1101
 *     the URL.
1102
 * @return string The string that is the reversed result of the array
1103
 */
1104
        public static function reverse($params, $full = false) {
1105
                if ($params instanceof CakeRequest) {
1106
                        $url = $params->query;
1107
                        $params = $params->params;
1108
                } else {
1109
                        $url = $params['url'];
1110
                }
1111
                $pass = isset($params['pass']) ? $params['pass'] : array();
1112
                $named = isset($params['named']) ? $params['named'] : array();
1113

    
1114
                unset(
1115
                        $params['pass'], $params['named'], $params['paging'], $params['models'], $params['url'], $url['url'],
1116
                        $params['autoRender'], $params['bare'], $params['requested'], $params['return'],
1117
                        $params['_Token']
1118
                );
1119
                $params = array_merge($params, $pass, $named);
1120
                if (!empty($url)) {
1121
                        $params['?'] = $url;
1122
                }
1123
                return Router::url($params, $full);
1124
        }
1125

    
1126
/**
1127
 * Normalizes a URL for purposes of comparison.
1128
 *
1129
 * Will strip the base path off and replace any double /'s.
1130
 * It will not unify the casing and underscoring of the input value.
1131
 *
1132
 * @param array|string $url URL to normalize Either an array or a string URL.
1133
 * @return string Normalized URL
1134
 */
1135
        public static function normalize($url = '/') {
1136
                if (is_array($url)) {
1137
                        $url = Router::url($url);
1138
                }
1139
                if (preg_match('/^[a-z\-]+:\/\//', $url)) {
1140
                        return $url;
1141
                }
1142
                $request = Router::getRequest();
1143

    
1144
                if (!empty($request->base) && stristr($url, $request->base)) {
1145
                        $url = preg_replace('/^' . preg_quote($request->base, '/') . '/', '', $url, 1);
1146
                }
1147
                $url = '/' . $url;
1148

    
1149
                while (strpos($url, '//') !== false) {
1150
                        $url = str_replace('//', '/', $url);
1151
                }
1152
                $url = preg_replace('/(?:(\/$))/', '', $url);
1153

    
1154
                if (empty($url)) {
1155
                        return '/';
1156
                }
1157
                return $url;
1158
        }
1159

    
1160
/**
1161
 * Returns the route matching the current request URL.
1162
 *
1163
 * @return CakeRoute Matching route object.
1164
 */
1165
        public static function requestRoute() {
1166
                return static::$_currentRoute[0];
1167
        }
1168

    
1169
/**
1170
 * Returns the route matching the current request (useful for requestAction traces)
1171
 *
1172
 * @return CakeRoute Matching route object.
1173
 */
1174
        public static function currentRoute() {
1175
                $count = count(static::$_currentRoute) - 1;
1176
                return ($count >= 0) ? static::$_currentRoute[$count] : false;
1177
        }
1178

    
1179
/**
1180
 * Removes the plugin name from the base URL.
1181
 *
1182
 * @param string $base Base URL
1183
 * @param string $plugin Plugin name
1184
 * @return string base URL with plugin name removed if present
1185
 */
1186
        public static function stripPlugin($base, $plugin = null) {
1187
                if ($plugin) {
1188
                        $base = preg_replace('/(?:' . $plugin . ')/', '', $base);
1189
                        $base = str_replace('//', '', $base);
1190
                        $pos1 = strrpos($base, '/');
1191
                        $char = strlen($base) - 1;
1192

    
1193
                        if ($pos1 === $char) {
1194
                                $base = substr($base, 0, $char);
1195
                        }
1196
                }
1197
                return $base;
1198
        }
1199

    
1200
/**
1201
 * Instructs the router to parse out file extensions from the URL.
1202
 *
1203
 * For example, http://example.com/posts.rss would yield a file extension of "rss".
1204
 * The file extension itself is made available in the controller as
1205
 * `$this->params['ext']`, and is used by the RequestHandler component to
1206
 * automatically switch to alternate layouts and templates, and load helpers
1207
 * corresponding to the given content, i.e. RssHelper. Switching layouts and helpers
1208
 * requires that the chosen extension has a defined mime type in `CakeResponse`
1209
 *
1210
 * A list of valid extension can be passed to this method, i.e. Router::parseExtensions('rss', 'xml');
1211
 * If no parameters are given, anything after the first . (dot) after the last / in the URL will be
1212
 * parsed, excluding querystring parameters (i.e. ?q=...).
1213
 *
1214
 * @return void
1215
 * @see RequestHandler::startup()
1216
 */
1217
        public static function parseExtensions() {
1218
                static::$_parseExtensions = true;
1219
                if (func_num_args() > 0) {
1220
                        static::setExtensions(func_get_args(), false);
1221
                }
1222
        }
1223

    
1224
/**
1225
 * Get the list of extensions that can be parsed by Router.
1226
 *
1227
 * To initially set extensions use `Router::parseExtensions()`
1228
 * To add more see `setExtensions()`
1229
 *
1230
 * @return array Array of extensions Router is configured to parse.
1231
 */
1232
        public static function extensions() {
1233
                if (!static::$initialized) {
1234
                        static::_loadRoutes();
1235
                }
1236

    
1237
                return static::$_validExtensions;
1238
        }
1239

    
1240
/**
1241
 * Set/add valid extensions.
1242
 *
1243
 * To have the extensions parsed you still need to call `Router::parseExtensions()`
1244
 *
1245
 * @param array $extensions List of extensions to be added as valid extension
1246
 * @param bool $merge Default true will merge extensions. Set to false to override current extensions
1247
 * @return array
1248
 */
1249
        public static function setExtensions($extensions, $merge = true) {
1250
                if (!is_array($extensions)) {
1251
                        return static::$_validExtensions;
1252
                }
1253
                if (!$merge) {
1254
                        return static::$_validExtensions = $extensions;
1255
                }
1256
                return static::$_validExtensions = array_merge(static::$_validExtensions, $extensions);
1257
        }
1258

    
1259
/**
1260
 * Loads route configuration
1261
 *
1262
 * @return void
1263
 */
1264
        protected static function _loadRoutes() {
1265
                static::$initialized = true;
1266
                include APP . 'Config' . DS . 'routes.php';
1267
        }
1268

    
1269
}
1270

    
1271
//Save the initial state
1272
Router::reload();