Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

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