Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
12345 anikendra 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 to set as default
211
 * @return mixed void|string
212
 * @throws RouterException
213
 */
214
	public static function defaultRouteClass($routeClass = null) {
215
		if ($routeClass === null) {
216
			return self::$_routeClass;
217
		}
218
 
219
		self::$_routeClass = self::_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 (
231
			$routeClass !== 'CakeRoute' &&
232
			(!class_exists($routeClass) || !is_subclass_of($routeClass, 'CakeRoute'))
233
		) {
234
			throw new RouterException(__d('cake_dev', 'Route class not found, or route class is not a subclass of CakeRoute'));
235
		}
236
		return $routeClass;
237
	}
238
 
239
/**
240
 * Sets the Routing prefixes.
241
 *
242
 * @return void
243
 */
244
	protected static function _setPrefixes() {
245
		$routing = Configure::read('Routing');
246
		if (!empty($routing['prefixes'])) {
247
			self::$_prefixes = array_merge(self::$_prefixes, (array)$routing['prefixes']);
248
		}
249
	}
250
 
251
/**
252
 * Gets the named route elements for use in app/Config/routes.php
253
 *
254
 * @return array Named route elements
255
 * @see Router::$_namedExpressions
256
 */
257
	public static function getNamedExpressions() {
258
		return self::$_namedExpressions;
259
	}
260
 
261
/**
262
 * Resource map getter & setter.
263
 *
264
 * @param array $resourceMap Resource map
265
 * @return mixed
266
 * @see Router::$_resourceMap
267
 */
268
	public static function resourceMap($resourceMap = null) {
269
		if ($resourceMap === null) {
270
			return self::$_resourceMap;
271
		}
272
		self::$_resourceMap = $resourceMap;
273
	}
274
 
275
/**
276
 * Connects a new Route in the router.
277
 *
278
 * Routes are a way of connecting request URLs to objects in your application. At their core routes
279
 * are a set of regular expressions that are used to match requests to destinations.
280
 *
281
 * Examples:
282
 *
283
 * `Router::connect('/:controller/:action/*');`
284
 *
285
 * The first token ':controller' will be used as a controller name while the second is used as the action name.
286
 * the '/*' syntax makes this route greedy in that it will match requests like `/posts/index` as well as requests
287
 * like `/posts/edit/1/foo/bar`.
288
 *
289
 * `Router::connect('/home-page', array('controller' => 'pages', 'action' => 'display', 'home'));`
290
 *
291
 * The above shows the use of route parameter defaults, and providing routing parameters for a static route.
292
 *
293
 * {{{
294
 * Router::connect(
295
 *   '/:lang/:controller/:action/:id',
296
 *   array(),
297
 *   array('id' => '[0-9]+', 'lang' => '[a-z]{3}')
298
 * );
299
 * }}}
300
 *
301
 * Shows connecting a route with custom route parameters as well as providing patterns for those parameters.
302
 * Patterns for routing parameters do not need capturing groups, as one will be added for each route params.
303
 *
304
 * $defaults is merged with the results of parsing the request URL to form the final routing destination and its
305
 * parameters. This destination is expressed as an associative array by Router. See the output of {@link parse()}.
306
 *
307
 * $options offers four 'special' keys. `pass`, `named`, `persist` and `routeClass`
308
 * have special meaning in the $options array.
309
 *
310
 * - `pass` is used to define which of the routed parameters should be shifted into the pass array. Adding a
311
 *   parameter to pass will remove it from the regular route array. Ex. `'pass' => array('slug')`
312
 * - `persist` is used to define which route parameters should be automatically included when generating
313
 *   new URLs. You can override persistent parameters by redefining them in a URL or remove them by
314
 *   setting the parameter to `false`. Ex. `'persist' => array('lang')`
315
 * - `routeClass` is used to extend and change how individual routes parse requests and handle reverse routing,
316
 *   via a custom routing class. Ex. `'routeClass' => 'SlugRoute'`
317
 * - `named` is used to configure named parameters at the route level. This key uses the same options
318
 *   as Router::connectNamed()
319
 *
320
 * You can also add additional conditions for matching routes to the $defaults array.
321
 * The following conditions can be used:
322
 *
323
 * - `[type]` Only match requests for specific content types.
324
 * - `[method]` Only match requests with specific HTTP verbs.
325
 * - `[server]` Only match when $_SERVER['SERVER_NAME'] matches the given value.
326
 *
327
 * Example of using the `[method]` condition:
328
 *
329
 * `Router::connect('/tasks', array('controller' => 'tasks', 'action' => 'index', '[method]' => 'GET'));`
330
 *
331
 * The above route will only be matched for GET requests. POST requests will fail to match this route.
332
 *
333
 * @param string $route A string describing the template of the route
334
 * @param array $defaults An array describing the default route parameters. These parameters will be used by default
335
 *   and can supply routing parameters that are not dynamic. See above.
336
 * @param array $options An array matching the named elements in the route to regular expressions which that
337
 *   element should match. Also contains additional parameters such as which routed parameters should be
338
 *   shifted into the passed arguments, supplying patterns for routing parameters and supplying the name of a
339
 *   custom routing class.
340
 * @see routes
341
 * @see parse().
342
 * @return array Array of routes
343
 * @throws RouterException
344
 */
345
	public static function connect($route, $defaults = array(), $options = array()) {
346
		self::$initialized = true;
347
 
348
		foreach (self::$_prefixes as $prefix) {
349
			if (isset($defaults[$prefix])) {
350
				if ($defaults[$prefix]) {
351
					$defaults['prefix'] = $prefix;
352
				} else {
353
					unset($defaults[$prefix]);
354
				}
355
				break;
356
			}
357
		}
358
		if (isset($defaults['prefix'])) {
359
			self::$_prefixes[] = $defaults['prefix'];
360
			self::$_prefixes = array_keys(array_flip(self::$_prefixes));
361
		}
362
		$defaults += array('plugin' => null);
363
		if (empty($options['action'])) {
364
			$defaults += array('action' => 'index');
365
		}
366
		$routeClass = self::$_routeClass;
367
		if (isset($options['routeClass'])) {
368
			if (strpos($options['routeClass'], '.') === false) {
369
				$routeClass = $options['routeClass'];
370
			} else {
371
				list(, $routeClass) = pluginSplit($options['routeClass'], true);
372
			}
373
			$routeClass = self::_validateRouteClass($routeClass);
374
			unset($options['routeClass']);
375
		}
376
		if ($routeClass === 'RedirectRoute' && isset($defaults['redirect'])) {
377
			$defaults = $defaults['redirect'];
378
		}
379
		self::$routes[] = new $routeClass($route, $defaults, $options);
380
		return self::$routes;
381
	}
382
 
383
/**
384
 * Connects a new redirection Route in the router.
385
 *
386
 * Redirection routes are different from normal routes as they perform an actual
387
 * header redirection if a match is found. The redirection can occur within your
388
 * application or redirect to an outside location.
389
 *
390
 * Examples:
391
 *
392
 * `Router::redirect('/home/*', array('controller' => 'posts', 'action' => 'view'), array('persist' => true));`
393
 *
394
 * Redirects /home/* to /posts/view and passes the parameters to /posts/view. Using an array as the
395
 * redirect destination allows you to use other routes to define where a URL string should be redirected to.
396
 *
397
 * `Router::redirect('/posts/*', 'http://google.com', array('status' => 302));`
398
 *
399
 * Redirects /posts/* to http://google.com with a HTTP status of 302
400
 *
401
 * ### Options:
402
 *
403
 * - `status` Sets the HTTP status (default 301)
404
 * - `persist` Passes the params to the redirected route, if it can. This is useful with greedy routes,
405
 *   routes that end in `*` are greedy. As you can remap URLs and not loose any passed/named args.
406
 *
407
 * @param string $route A string describing the template of the route
408
 * @param array $url A URL to redirect to. Can be a string or a CakePHP array-based URL
409
 * @param array $options An array matching the named elements in the route to regular expressions which that
410
 *   element should match. Also contains additional parameters such as which routed parameters should be
411
 *   shifted into the passed arguments. As well as supplying patterns for routing parameters.
412
 * @see routes
413
 * @return array Array of routes
414
 */
415
	public static function redirect($route, $url, $options = array()) {
416
		App::uses('RedirectRoute', 'Routing/Route');
417
		$options['routeClass'] = 'RedirectRoute';
418
		if (is_string($url)) {
419
			$url = array('redirect' => $url);
420
		}
421
		return self::connect($route, $url, $options);
422
	}
423
 
424
/**
425
 * Specifies what named parameters CakePHP should be parsing out of incoming URLs. By default
426
 * CakePHP will parse every named parameter out of incoming URLs. However, if you want to take more
427
 * control over how named parameters are parsed you can use one of the following setups:
428
 *
429
 * Do not parse any named parameters:
430
 *
431
 * {{{ Router::connectNamed(false); }}}
432
 *
433
 * Parse only default parameters used for CakePHP's pagination:
434
 *
435
 * {{{ Router::connectNamed(false, array('default' => true)); }}}
436
 *
437
 * Parse only the page parameter if its value is a number:
438
 *
439
 * {{{ Router::connectNamed(array('page' => '[\d]+'), array('default' => false, 'greedy' => false)); }}}
440
 *
441
 * Parse only the page parameter no matter what.
442
 *
443
 * {{{ Router::connectNamed(array('page'), array('default' => false, 'greedy' => false)); }}}
444
 *
445
 * Parse only the page parameter if the current action is 'index'.
446
 *
447
 * {{{
448
 * Router::connectNamed(
449
 *    array('page' => array('action' => 'index')),
450
 *    array('default' => false, 'greedy' => false)
451
 * );
452
 * }}}
453
 *
454
 * Parse only the page parameter if the current action is 'index' and the controller is 'pages'.
455
 *
456
 * {{{
457
 * Router::connectNamed(
458
 *    array('page' => array('action' => 'index', 'controller' => 'pages')),
459
 *    array('default' => false, 'greedy' => false)
460
 * );
461
 * }}}
462
 *
463
 * ### Options
464
 *
465
 * - `greedy` Setting this to true will make Router parse all named params. Setting it to false will
466
 *    parse only the connected named params.
467
 * - `default` Set this to true to merge in the default set of named parameters.
468
 * - `reset` Set to true to clear existing rules and start fresh.
469
 * - `separator` Change the string used to separate the key & value in a named parameter. Defaults to `:`
470
 *
471
 * @param array $named A list of named parameters. Key value pairs are accepted where values are
472
 *    either regex strings to match, or arrays as seen above.
473
 * @param array $options Allows to control all settings: separator, greedy, reset, default
474
 * @return array
475
 */
476
	public static function connectNamed($named, $options = array()) {
477
		if (isset($options['separator'])) {
478
			self::$_namedConfig['separator'] = $options['separator'];
479
			unset($options['separator']);
480
		}
481
 
482
		if ($named === true || $named === false) {
483
			$options += array('default' => $named, 'reset' => true, 'greedy' => $named);
484
			$named = array();
485
		} else {
486
			$options += array('default' => false, 'reset' => false, 'greedy' => true);
487
		}
488
 
489
		if ($options['reset'] || self::$_namedConfig['rules'] === false) {
490
			self::$_namedConfig['rules'] = array();
491
		}
492
 
493
		if ($options['default']) {
494
			$named = array_merge($named, self::$_namedConfig['default']);
495
		}
496
 
497
		foreach ($named as $key => $val) {
498
			if (is_numeric($key)) {
499
				self::$_namedConfig['rules'][$val] = true;
500
			} else {
501
				self::$_namedConfig['rules'][$key] = $val;
502
			}
503
		}
504
		self::$_namedConfig['greedyNamed'] = $options['greedy'];
505
		return self::$_namedConfig;
506
	}
507
 
508
/**
509
 * Gets the current named parameter configuration values.
510
 *
511
 * @return array
512
 * @see Router::$_namedConfig
513
 */
514
	public static function namedConfig() {
515
		return self::$_namedConfig;
516
	}
517
 
518
/**
519
 * Creates REST resource routes for the given controller(s). When creating resource routes
520
 * for a plugin, by default the prefix will be changed to the lower_underscore version of the plugin
521
 * name. By providing a prefix you can override this behavior.
522
 *
523
 * ### Options:
524
 *
525
 * - 'id' - The regular expression fragment to use when matching IDs. By default, matches
526
 *    integer values and UUIDs.
527
 * - 'prefix' - URL prefix to use for the generated routes. Defaults to '/'.
528
 *
529
 * @param string|array $controller A controller name or array of controller names (i.e. "Posts" or "ListItems")
530
 * @param array $options Options to use when generating REST routes
531
 * @return array Array of mapped resources
532
 */
533
	public static function mapResources($controller, $options = array()) {
534
		$hasPrefix = isset($options['prefix']);
535
		$options += array(
536
			'connectOptions' => array(),
537
			'prefix' => '/',
538
			'id' => self::ID . '|' . self::UUID
539
		);
540
 
541
		$prefix = $options['prefix'];
542
		$connectOptions = $options['connectOptions'];
543
		unset($options['connectOptions']);
544
		if (strpos($prefix, '/') !== 0) {
545
			$prefix = '/' . $prefix;
546
		}
547
		if (substr($prefix, -1) !== '/') {
548
			$prefix .= '/';
549
		}
550
 
551
		foreach ((array)$controller as $name) {
552
			list($plugin, $name) = pluginSplit($name);
553
			$urlName = Inflector::underscore($name);
554
			$plugin = Inflector::underscore($plugin);
555
			if ($plugin && !$hasPrefix) {
556
				$prefix = '/' . $plugin . '/';
557
			}
558
 
559
			foreach (self::$_resourceMap as $params) {
560
				$url = $prefix . $urlName . (($params['id']) ? '/:id' : '');
561
 
562
				Router::connect($url,
563
					array(
564
						'plugin' => $plugin,
565
						'controller' => $urlName,
566
						'action' => $params['action'],
567
						'[method]' => $params['method']
568
					),
569
					array_merge(
570
						array('id' => $options['id'], 'pass' => array('id')),
571
						$connectOptions
572
					)
573
				);
574
			}
575
			self::$_resourceMapped[] = $urlName;
576
		}
577
		return self::$_resourceMapped;
578
	}
579
 
580
/**
581
 * Returns the list of prefixes used in connected routes
582
 *
583
 * @return array A list of prefixes used in connected routes
584
 */
585
	public static function prefixes() {
586
		return self::$_prefixes;
587
	}
588
 
589
/**
590
 * Parses given URL string. Returns 'routing' parameters for that URL.
591
 *
592
 * @param string $url URL to be parsed
593
 * @return array Parsed elements from URL
594
 */
595
	public static function parse($url) {
596
		if (!self::$initialized) {
597
			self::_loadRoutes();
598
		}
599
 
600
		$ext = null;
601
		$out = array();
602
 
603
		if (strlen($url) && strpos($url, '/') !== 0) {
604
			$url = '/' . $url;
605
		}
606
		if (strpos($url, '?') !== false) {
607
			list($url, $queryParameters) = explode('?', $url, 2);
608
			parse_str($queryParameters, $queryParameters);
609
		}
610
 
611
		extract(self::_parseExtension($url));
612
 
613
		for ($i = 0, $len = count(self::$routes); $i < $len; $i++) {
614
			$route =& self::$routes[$i];
615
 
616
			if (($r = $route->parse($url)) !== false) {
617
				self::$_currentRoute[] =& $route;
618
				$out = $r;
619
				break;
620
			}
621
		}
622
		if (isset($out['prefix'])) {
623
			$out['action'] = $out['prefix'] . '_' . $out['action'];
624
		}
625
 
626
		if (!empty($ext) && !isset($out['ext'])) {
627
			$out['ext'] = $ext;
628
		}
629
 
630
		if (!empty($queryParameters) && !isset($out['?'])) {
631
			$out['?'] = $queryParameters;
632
		}
633
		return $out;
634
	}
635
 
636
/**
637
 * Parses a file extension out of a URL, if Router::parseExtensions() is enabled.
638
 *
639
 * @param string $url URL.
640
 * @return array Returns an array containing the altered URL and the parsed extension.
641
 */
642
	protected static function _parseExtension($url) {
643
		$ext = null;
644
 
645
		if (self::$_parseExtensions) {
646
			if (preg_match('/\.[0-9a-zA-Z]*$/', $url, $match) === 1) {
647
				$match = substr($match[0], 1);
648
				if (empty(self::$_validExtensions)) {
649
					$url = substr($url, 0, strpos($url, '.' . $match));
650
					$ext = $match;
651
				} else {
652
					foreach (self::$_validExtensions as $name) {
653
						if (strcasecmp($name, $match) === 0) {
654
							$url = substr($url, 0, strpos($url, '.' . $name));
655
							$ext = $match;
656
							break;
657
						}
658
					}
659
				}
660
			}
661
		}
662
		return compact('ext', 'url');
663
	}
664
 
665
/**
666
 * Takes parameter and path information back from the Dispatcher, sets these
667
 * parameters as the current request parameters that are merged with URL arrays
668
 * created later in the request.
669
 *
670
 * Nested requests will create a stack of requests. You can remove requests using
671
 * Router::popRequest(). This is done automatically when using Object::requestAction().
672
 *
673
 * Will accept either a CakeRequest object or an array of arrays. Support for
674
 * accepting arrays may be removed in the future.
675
 *
676
 * @param CakeRequest|array $request Parameters and path information or a CakeRequest object.
677
 * @return void
678
 */
679
	public static function setRequestInfo($request) {
680
		if ($request instanceof CakeRequest) {
681
			self::$_requests[] = $request;
682
		} else {
683
			$requestObj = new CakeRequest();
684
			$request += array(array(), array());
685
			$request[0] += array('controller' => false, 'action' => false, 'plugin' => null);
686
			$requestObj->addParams($request[0])->addPaths($request[1]);
687
			self::$_requests[] = $requestObj;
688
		}
689
	}
690
 
691
/**
692
 * Pops a request off of the request stack. Used when doing requestAction
693
 *
694
 * @return CakeRequest The request removed from the stack.
695
 * @see Router::setRequestInfo()
696
 * @see Object::requestAction()
697
 */
698
	public static function popRequest() {
699
		return array_pop(self::$_requests);
700
	}
701
 
702
/**
703
 * Gets the current request object, or the first one.
704
 *
705
 * @param bool $current True to get the current request object, or false to get the first one.
706
 * @return CakeRequest|null Null if stack is empty.
707
 */
708
	public static function getRequest($current = false) {
709
		if ($current) {
710
			$i = count(self::$_requests) - 1;
711
			return isset(self::$_requests[$i]) ? self::$_requests[$i] : null;
712
		}
713
		return isset(self::$_requests[0]) ? self::$_requests[0] : null;
714
	}
715
 
716
/**
717
 * Gets parameter information
718
 *
719
 * @param bool $current Get current request parameter, useful when using requestAction
720
 * @return array Parameter information
721
 */
722
	public static function getParams($current = false) {
723
		if ($current && self::$_requests) {
724
			return self::$_requests[count(self::$_requests) - 1]->params;
725
		}
726
		if (isset(self::$_requests[0])) {
727
			return self::$_requests[0]->params;
728
		}
729
		return array();
730
	}
731
 
732
/**
733
 * Gets URL parameter by name
734
 *
735
 * @param string $name Parameter name
736
 * @param bool $current Current parameter, useful when using requestAction
737
 * @return string Parameter value
738
 */
739
	public static function getParam($name = 'controller', $current = false) {
740
		$params = Router::getParams($current);
741
		if (isset($params[$name])) {
742
			return $params[$name];
743
		}
744
		return null;
745
	}
746
 
747
/**
748
 * Gets path information
749
 *
750
 * @param bool $current Current parameter, useful when using requestAction
751
 * @return array
752
 */
753
	public static function getPaths($current = false) {
754
		if ($current) {
755
			return self::$_requests[count(self::$_requests) - 1];
756
		}
757
		if (!isset(self::$_requests[0])) {
758
			return array('base' => null);
759
		}
760
		return array('base' => self::$_requests[0]->base);
761
	}
762
 
763
/**
764
 * Reloads default Router settings. Resets all class variables and
765
 * removes all connected routes.
766
 *
767
 * @return void
768
 */
769
	public static function reload() {
770
		if (empty(self::$_initialState)) {
771
			self::$_initialState = get_class_vars('Router');
772
			self::_setPrefixes();
773
			return;
774
		}
775
		foreach (self::$_initialState as $key => $val) {
776
			if ($key !== '_initialState') {
777
				self::${$key} = $val;
778
			}
779
		}
780
		self::_setPrefixes();
781
	}
782
 
783
/**
784
 * Promote a route (by default, the last one added) to the beginning of the list
785
 *
786
 * @param int $which A zero-based array index representing the route to move. For example,
787
 *    if 3 routes have been added, the last route would be 2.
788
 * @return bool Returns false if no route exists at the position specified by $which.
789
 */
790
	public static function promote($which = null) {
791
		if ($which === null) {
792
			$which = count(self::$routes) - 1;
793
		}
794
		if (!isset(self::$routes[$which])) {
795
			return false;
796
		}
797
		$route =& self::$routes[$which];
798
		unset(self::$routes[$which]);
799
		array_unshift(self::$routes, $route);
800
		return true;
801
	}
802
 
803
/**
804
 * Finds URL for specified action.
805
 *
806
 * Returns a URL pointing to a combination of controller and action. Param
807
 * $url can be:
808
 *
809
 * - Empty - the method will find address to actual controller/action.
810
 * - '/' - the method will find base URL of application.
811
 * - A combination of controller/action - the method will find URL for it.
812
 *
813
 * There are a few 'special' parameters that can change the final URL string that is generated
814
 *
815
 * - `base` - Set to false to remove the base path from the generated URL. If your application
816
 *   is not in the root directory, this can be used to generate URLs that are 'cake relative'.
817
 *   cake relative URLs are required when using requestAction.
818
 * - `?` - Takes an array of query string parameters
819
 * - `#` - Allows you to set URL hash fragments.
820
 * - `full_base` - If true the `Router::fullBaseUrl()` value will be prepended to generated URLs.
821
 *
822
 * @param string|array $url Cake-relative URL, like "/products/edit/92" or "/presidents/elect/4"
823
 *   or an array specifying any of the following: 'controller', 'action',
824
 *   and/or 'plugin', in addition to named arguments (keyed array elements),
825
 *   and standard URL arguments (indexed array elements)
826
 * @param bool|array $full If (bool) true, the full base URL will be prepended to the result.
827
 *   If an array accepts the following keys
828
 *    - escape - used when making URLs embedded in html escapes query string '&'
829
 *    - full - if true the full base URL will be prepended.
830
 * @return string Full translated URL with base path.
831
 */
832
	public static function url($url = null, $full = false) {
833
		if (!self::$initialized) {
834
			self::_loadRoutes();
835
		}
836
 
837
		$params = array('plugin' => null, 'controller' => null, 'action' => 'index');
838
 
839
		if (is_bool($full)) {
840
			$escape = false;
841
		} else {
842
			extract($full + array('escape' => false, 'full' => false));
843
		}
844
 
845
		$path = array('base' => null);
846
		if (!empty(self::$_requests)) {
847
			$request = self::$_requests[count(self::$_requests) - 1];
848
			$params = $request->params;
849
			$path = array('base' => $request->base, 'here' => $request->here);
850
		}
851
		if (empty($path['base'])) {
852
			$path['base'] = Configure::read('App.base');
853
		}
854
 
855
		$base = $path['base'];
856
		$extension = $output = $q = $frag = null;
857
 
858
		if (empty($url)) {
859
			$output = isset($path['here']) ? $path['here'] : '/';
860
			if ($full) {
861
				$output = self::fullBaseUrl() . $output;
862
			}
863
			return $output;
864
		} elseif (is_array($url)) {
865
			if (isset($url['base']) && $url['base'] === false) {
866
				$base = null;
867
				unset($url['base']);
868
			}
869
			if (isset($url['full_base']) && $url['full_base'] === true) {
870
				$full = true;
871
				unset($url['full_base']);
872
			}
873
			if (isset($url['?'])) {
874
				$q = $url['?'];
875
				unset($url['?']);
876
			}
877
			if (isset($url['#'])) {
878
				$frag = '#' . $url['#'];
879
				unset($url['#']);
880
			}
881
			if (isset($url['ext'])) {
882
				$extension = '.' . $url['ext'];
883
				unset($url['ext']);
884
			}
885
			if (empty($url['action'])) {
886
				if (empty($url['controller']) || $params['controller'] === $url['controller']) {
887
					$url['action'] = $params['action'];
888
				} else {
889
					$url['action'] = 'index';
890
				}
891
			}
892
 
893
			$prefixExists = (array_intersect_key($url, array_flip(self::$_prefixes)));
894
			foreach (self::$_prefixes as $prefix) {
895
				if (!empty($params[$prefix]) && !$prefixExists) {
896
					$url[$prefix] = true;
897
				} elseif (isset($url[$prefix]) && !$url[$prefix]) {
898
					unset($url[$prefix]);
899
				}
900
				if (isset($url[$prefix]) && strpos($url['action'], $prefix . '_') === 0) {
901
					$url['action'] = substr($url['action'], strlen($prefix) + 1);
902
				}
903
			}
904
 
905
			$url += array('controller' => $params['controller'], 'plugin' => $params['plugin']);
906
 
907
			$match = false;
908
 
909
			for ($i = 0, $len = count(self::$routes); $i < $len; $i++) {
910
				$originalUrl = $url;
911
 
912
				$url = self::$routes[$i]->persistParams($url, $params);
913
 
914
				if ($match = self::$routes[$i]->match($url)) {
915
					$output = trim($match, '/');
916
					break;
917
				}
918
				$url = $originalUrl;
919
			}
920
			if ($match === false) {
921
				$output = self::_handleNoRoute($url);
922
			}
923
		} else {
924
			if (preg_match('/^([a-z][a-z0-9.+\-]+:|:?\/\/|[#?])/i', $url)) {
925
				return $url;
926
			}
927
			if (substr($url, 0, 1) === '/') {
928
				$output = substr($url, 1);
929
			} else {
930
				foreach (self::$_prefixes as $prefix) {
931
					if (isset($params[$prefix])) {
932
						$output .= $prefix . '/';
933
						break;
934
					}
935
				}
936
				if (!empty($params['plugin']) && $params['plugin'] !== $params['controller']) {
937
					$output .= Inflector::underscore($params['plugin']) . '/';
938
				}
939
				$output .= Inflector::underscore($params['controller']) . '/' . $url;
940
			}
941
		}
942
		$protocol = preg_match('#^[a-z][a-z0-9+\-.]*\://#i', $output);
943
		if ($protocol === 0) {
944
			$output = str_replace('//', '/', $base . '/' . $output);
945
 
946
			if ($full) {
947
				$output = self::fullBaseUrl() . $output;
948
			}
949
			if (!empty($extension)) {
950
				$output = rtrim($output, '/');
951
			}
952
		}
953
		return $output . $extension . self::queryString($q, array(), $escape) . $frag;
954
	}
955
 
956
/**
957
 * Sets the full base URL that will be used as a prefix for generating
958
 * fully qualified URLs for this application. If no parameters are passed,
959
 * the currently configured value is returned.
960
 *
961
 * ## Note:
962
 *
963
 * If you change the configuration value ``App.fullBaseUrl`` during runtime
964
 * and expect the router to produce links using the new setting, you are
965
 * required to call this method passing such value again.
966
 *
967
 * @param string $base the prefix for URLs generated containing the domain.
968
 * For example: ``http://example.com``
969
 * @return string
970
 */
971
	public static function fullBaseUrl($base = null) {
972
		if ($base !== null) {
973
			self::$_fullBaseUrl = $base;
974
			Configure::write('App.fullBaseUrl', $base);
975
		}
976
		if (empty(self::$_fullBaseUrl)) {
977
			self::$_fullBaseUrl = Configure::read('App.fullBaseUrl');
978
		}
979
		return self::$_fullBaseUrl;
980
	}
981
 
982
/**
983
 * A special fallback method that handles URL arrays that cannot match
984
 * any defined routes.
985
 *
986
 * @param array $url A URL that didn't match any routes
987
 * @return string A generated URL for the array
988
 * @see Router::url()
989
 */
990
	protected static function _handleNoRoute($url) {
991
		$named = $args = array();
992
		$skip = array_merge(
993
			array('bare', 'action', 'controller', 'plugin', 'prefix'),
994
			self::$_prefixes
995
		);
996
 
997
		$keys = array_values(array_diff(array_keys($url), $skip));
998
		$count = count($keys);
999
 
1000
		// Remove this once parsed URL parameters can be inserted into 'pass'
1001
		for ($i = 0; $i < $count; $i++) {
1002
			$key = $keys[$i];
1003
			if (is_numeric($keys[$i])) {
1004
				$args[] = $url[$key];
1005
			} else {
1006
				$named[$key] = $url[$key];
1007
			}
1008
		}
1009
 
1010
		list($args, $named) = array(Hash::filter($args), Hash::filter($named));
1011
		foreach (self::$_prefixes as $prefix) {
1012
			$prefixed = $prefix . '_';
1013
			if (!empty($url[$prefix]) && strpos($url['action'], $prefixed) === 0) {
1014
				$url['action'] = substr($url['action'], strlen($prefixed) * -1);
1015
				break;
1016
			}
1017
		}
1018
 
1019
		if (empty($named) && empty($args) && (!isset($url['action']) || $url['action'] === 'index')) {
1020
			$url['action'] = null;
1021
		}
1022
 
1023
		$urlOut = array_filter(array($url['controller'], $url['action']));
1024
 
1025
		if (isset($url['plugin'])) {
1026
			array_unshift($urlOut, $url['plugin']);
1027
		}
1028
 
1029
		foreach (self::$_prefixes as $prefix) {
1030
			if (isset($url[$prefix])) {
1031
				array_unshift($urlOut, $prefix);
1032
				break;
1033
			}
1034
		}
1035
		$output = implode('/', $urlOut);
1036
 
1037
		if (!empty($args)) {
1038
			$output .= '/' . implode('/', array_map('rawurlencode', $args));
1039
		}
1040
 
1041
		if (!empty($named)) {
1042
			foreach ($named as $name => $value) {
1043
				if (is_array($value)) {
1044
					$flattend = Hash::flatten($value, '%5D%5B');
1045
					foreach ($flattend as $namedKey => $namedValue) {
1046
						$output .= '/' . $name . "%5B{$namedKey}%5D" . self::$_namedConfig['separator'] . rawurlencode($namedValue);
1047
					}
1048
				} else {
1049
					$output .= '/' . $name . self::$_namedConfig['separator'] . rawurlencode($value);
1050
				}
1051
			}
1052
		}
1053
		return $output;
1054
	}
1055
 
1056
/**
1057
 * Generates a well-formed querystring from $q
1058
 *
1059
 * @param string|array $q Query string Either a string of already compiled query string arguments or
1060
 *    an array of arguments to convert into a query string.
1061
 * @param array $extra Extra querystring parameters.
1062
 * @param bool $escape Whether or not to use escaped &
1063
 * @return array
1064
 */
1065
	public static function queryString($q, $extra = array(), $escape = false) {
1066
		if (empty($q) && empty($extra)) {
1067
			return null;
1068
		}
1069
		$join = '&';
1070
		if ($escape === true) {
1071
			$join = '&amp;';
1072
		}
1073
		$out = '';
1074
 
1075
		if (is_array($q)) {
1076
			$q = array_merge($q, $extra);
1077
		} else {
1078
			$out = $q;
1079
			$q = $extra;
1080
		}
1081
		$addition = http_build_query($q, null, $join);
1082
 
1083
		if ($out && $addition && substr($out, strlen($join) * -1, strlen($join)) !== $join) {
1084
			$out .= $join;
1085
		}
1086
 
1087
		$out .= $addition;
1088
 
1089
		if (isset($out[0]) && $out[0] !== '?') {
1090
			$out = '?' . $out;
1091
		}
1092
		return $out;
1093
	}
1094
 
1095
/**
1096
 * Reverses a parsed parameter array into a string.
1097
 *
1098
 * Works similarly to Router::url(), but since parsed URL's contain additional
1099
 * 'pass' and 'named' as well as 'url.url' keys. Those keys need to be specially
1100
 * handled in order to reverse a params array into a string URL.
1101
 *
1102
 * This will strip out 'autoRender', 'bare', 'requested', and 'return' param names as those
1103
 * are used for CakePHP internals and should not normally be part of an output URL.
1104
 *
1105
 * @param CakeRequest|array $params The params array or CakeRequest object that needs to be reversed.
1106
 * @param bool $full Set to true to include the full URL including the protocol when reversing
1107
 *     the URL.
1108
 * @return string The string that is the reversed result of the array
1109
 */
1110
	public static function reverse($params, $full = false) {
1111
		if ($params instanceof CakeRequest) {
1112
			$url = $params->query;
1113
			$params = $params->params;
1114
		} else {
1115
			$url = $params['url'];
1116
		}
1117
		$pass = isset($params['pass']) ? $params['pass'] : array();
1118
		$named = isset($params['named']) ? $params['named'] : array();
1119
 
1120
		unset(
1121
			$params['pass'], $params['named'], $params['paging'], $params['models'], $params['url'], $url['url'],
1122
			$params['autoRender'], $params['bare'], $params['requested'], $params['return'],
1123
			$params['_Token']
1124
		);
1125
		$params = array_merge($params, $pass, $named);
1126
		if (!empty($url)) {
1127
			$params['?'] = $url;
1128
		}
1129
		return Router::url($params, $full);
1130
	}
1131
 
1132
/**
1133
 * Normalizes a URL for purposes of comparison.
1134
 *
1135
 * Will strip the base path off and replace any double /'s.
1136
 * It will not unify the casing and underscoring of the input value.
1137
 *
1138
 * @param array|string $url URL to normalize Either an array or a string URL.
1139
 * @return string Normalized URL
1140
 */
1141
	public static function normalize($url = '/') {
1142
		if (is_array($url)) {
1143
			$url = Router::url($url);
1144
		}
1145
		if (preg_match('/^[a-z\-]+:\/\//', $url)) {
1146
			return $url;
1147
		}
1148
		$request = Router::getRequest();
1149
 
1150
		if (!empty($request->base) && stristr($url, $request->base)) {
1151
			$url = preg_replace('/^' . preg_quote($request->base, '/') . '/', '', $url, 1);
1152
		}
1153
		$url = '/' . $url;
1154
 
1155
		while (strpos($url, '//') !== false) {
1156
			$url = str_replace('//', '/', $url);
1157
		}
1158
		$url = preg_replace('/(?:(\/$))/', '', $url);
1159
 
1160
		if (empty($url)) {
1161
			return '/';
1162
		}
1163
		return $url;
1164
	}
1165
 
1166
/**
1167
 * Returns the route matching the current request URL.
1168
 *
1169
 * @return CakeRoute Matching route object.
1170
 */
1171
	public static function requestRoute() {
1172
		return self::$_currentRoute[0];
1173
	}
1174
 
1175
/**
1176
 * Returns the route matching the current request (useful for requestAction traces)
1177
 *
1178
 * @return CakeRoute Matching route object.
1179
 */
1180
	public static function currentRoute() {
1181
		$count = count(self::$_currentRoute) - 1;
1182
		return ($count >= 0) ? self::$_currentRoute[$count] : false;
1183
	}
1184
 
1185
/**
1186
 * Removes the plugin name from the base URL.
1187
 *
1188
 * @param string $base Base URL
1189
 * @param string $plugin Plugin name
1190
 * @return string base URL with plugin name removed if present
1191
 */
1192
	public static function stripPlugin($base, $plugin = null) {
1193
		if ($plugin) {
1194
			$base = preg_replace('/(?:' . $plugin . ')/', '', $base);
1195
			$base = str_replace('//', '', $base);
1196
			$pos1 = strrpos($base, '/');
1197
			$char = strlen($base) - 1;
1198
 
1199
			if ($pos1 === $char) {
1200
				$base = substr($base, 0, $char);
1201
			}
1202
		}
1203
		return $base;
1204
	}
1205
 
1206
/**
1207
 * Instructs the router to parse out file extensions from the URL.
1208
 *
1209
 * For example, http://example.com/posts.rss would yield an file extension of "rss".
1210
 * The file extension itself is made available in the controller as
1211
 * `$this->params['ext']`, and is used by the RequestHandler component to
1212
 * automatically switch to alternate layouts and templates, and load helpers
1213
 * corresponding to the given content, i.e. RssHelper. Switching layouts and helpers
1214
 * requires that the chosen extension has a defined mime type in `CakeResponse`
1215
 *
1216
 * A list of valid extension can be passed to this method, i.e. Router::parseExtensions('rss', 'xml');
1217
 * If no parameters are given, anything after the first . (dot) after the last / in the URL will be
1218
 * parsed, excluding querystring parameters (i.e. ?q=...).
1219
 *
1220
 * @return void
1221
 * @see RequestHandler::startup()
1222
 */
1223
	public static function parseExtensions() {
1224
		self::$_parseExtensions = true;
1225
		if (func_num_args() > 0) {
1226
			self::setExtensions(func_get_args(), false);
1227
		}
1228
	}
1229
 
1230
/**
1231
 * Get the list of extensions that can be parsed by Router.
1232
 *
1233
 * To initially set extensions use `Router::parseExtensions()`
1234
 * To add more see `setExtensions()`
1235
 *
1236
 * @return array Array of extensions Router is configured to parse.
1237
 */
1238
	public static function extensions() {
1239
		if (!self::$initialized) {
1240
			self::_loadRoutes();
1241
		}
1242
 
1243
		return self::$_validExtensions;
1244
	}
1245
 
1246
/**
1247
 * Set/add valid extensions.
1248
 *
1249
 * To have the extensions parsed you still need to call `Router::parseExtensions()`
1250
 *
1251
 * @param array $extensions List of extensions to be added as valid extension
1252
 * @param bool $merge Default true will merge extensions. Set to false to override current extensions
1253
 * @return array
1254
 */
1255
	public static function setExtensions($extensions, $merge = true) {
1256
		if (!is_array($extensions)) {
1257
			return self::$_validExtensions;
1258
		}
1259
		if (!$merge) {
1260
			return self::$_validExtensions = $extensions;
1261
		}
1262
		return self::$_validExtensions = array_merge(self::$_validExtensions, $extensions);
1263
	}
1264
 
1265
/**
1266
 * Loads route configuration
1267
 *
1268
 * @return void
1269
 */
1270
	protected static function _loadRoutes() {
1271
		self::$initialized = true;
1272
		include APP . 'Config' . DS . 'routes.php';
1273
	}
1274
 
1275
}
1276
 
1277
//Save the initial state
1278
Router::reload();