Router.php 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 0.2.9
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Routing;
  16. use Cake\Core\App;
  17. use Cake\Core\Configure;
  18. use Cake\Network\Request;
  19. use Cake\Routing\RouteBuilder;
  20. use Cake\Routing\RouteCollection;
  21. use Cake\Routing\Route\Route;
  22. use Cake\Utility\Inflector;
  23. /**
  24. * Parses the request URL into controller, action, and parameters. Uses the connected routes
  25. * to match the incoming URL string to parameters that will allow the request to be dispatched. Also
  26. * handles converting parameter lists into URL strings, using the connected routes. Routing allows you to decouple
  27. * the way the world interacts with your application (URLs) and the implementation (controllers and actions).
  28. *
  29. * ### Connecting routes
  30. *
  31. * Connecting routes is done using Router::connect(). When parsing incoming requests or reverse matching
  32. * parameters, routes are enumerated in the order they were connected. You can modify the order of connected
  33. * routes using Router::promote(). For more information on routes and how to connect them see Router::connect().
  34. *
  35. */
  36. class Router {
  37. /**
  38. * Have routes been loaded
  39. *
  40. * @var bool
  41. */
  42. public static $initialized = false;
  43. /**
  44. * Default route class.
  45. *
  46. * @var bool
  47. */
  48. protected static $_defaultRouteClass = 'Cake\Routing\Route\Route';
  49. /**
  50. * Contains the base string that will be applied to all generated URLs
  51. * For example `https://example.com`
  52. *
  53. * @var string
  54. */
  55. protected static $_fullBaseUrl;
  56. /**
  57. * Regular expression for action names
  58. *
  59. * @var string
  60. */
  61. const ACTION = 'index|show|add|create|edit|update|remove|del|delete|view|item';
  62. /**
  63. * Regular expression for years
  64. *
  65. * @var string
  66. */
  67. const YEAR = '[12][0-9]{3}';
  68. /**
  69. * Regular expression for months
  70. *
  71. * @var string
  72. */
  73. const MONTH = '0[1-9]|1[012]';
  74. /**
  75. * Regular expression for days
  76. *
  77. * @var string
  78. */
  79. const DAY = '0[1-9]|[12][0-9]|3[01]';
  80. /**
  81. * Regular expression for auto increment IDs
  82. *
  83. * @var string
  84. */
  85. const ID = '[0-9]+';
  86. /**
  87. * Regular expression for UUIDs
  88. *
  89. * @var string
  90. */
  91. 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}';
  92. protected static $_collection;
  93. /**
  94. * A hash of request context data.
  95. *
  96. * @var array
  97. */
  98. protected static $_requestContext = [];
  99. /**
  100. * Named expressions
  101. *
  102. * @var array
  103. */
  104. protected static $_namedExpressions = array(
  105. 'Action' => Router::ACTION,
  106. 'Year' => Router::YEAR,
  107. 'Month' => Router::MONTH,
  108. 'Day' => Router::DAY,
  109. 'ID' => Router::ID,
  110. 'UUID' => Router::UUID
  111. );
  112. /**
  113. * Maintains the request object stack for the current request.
  114. * This will contain more than one request object when requestAction is used.
  115. *
  116. * @var array
  117. */
  118. protected static $_requests = [];
  119. /**
  120. * Initial state is populated the first time reload() is called which is at the bottom
  121. * of this file. This is a cheat as get_class_vars() returns the value of static vars even if they
  122. * have changed.
  123. *
  124. * @var array
  125. */
  126. protected static $_initialState = [];
  127. /**
  128. * The stack of URL filters to apply against routing URLs before passing the
  129. * parameters to the route collection.
  130. *
  131. * @var array
  132. */
  133. protected static $_urlFilters = [];
  134. /**
  135. * Get or set default route class.
  136. *
  137. * @param string|null $routeClass Class name.
  138. * @return string|void
  139. */
  140. public static function defaultRouteClass($routeClass = null) {
  141. if ($routeClass == null) {
  142. return static::$_defaultRouteClass;
  143. }
  144. static::$_defaultRouteClass = $routeClass;
  145. }
  146. /**
  147. * Gets the named route patterns for use in config/routes.php
  148. *
  149. * @return array Named route elements
  150. * @see Router::$_namedExpressions
  151. */
  152. public static function getNamedExpressions() {
  153. return static::$_namedExpressions;
  154. }
  155. /**
  156. * Connects a new Route in the router.
  157. *
  158. * Compatibility proxy to \Cake\Routing\RouteBuilder::connect() in the `/` scope.
  159. *
  160. * @param string $route A string describing the template of the route
  161. * @param array $defaults An array describing the default route parameters. These parameters will be used by default
  162. * and can supply routing parameters that are not dynamic. See above.
  163. * @param array $options An array matching the named elements in the route to regular expressions which that
  164. * element should match. Also contains additional parameters such as which routed parameters should be
  165. * shifted into the passed arguments, supplying patterns for routing parameters and supplying the name of a
  166. * custom routing class.
  167. * @return void
  168. * @throws \Cake\Core\Exception\Exception
  169. * @see \Cake\Routing\RouteBuilder::connect()
  170. * @see \Cake\Routing\Router::scope()
  171. */
  172. public static function connect($route, $defaults = [], $options = []) {
  173. static::$initialized = true;
  174. static::scope('/', function($routes) use ($route, $defaults, $options) {
  175. $routes->connect($route, $defaults, $options);
  176. });
  177. }
  178. /**
  179. * Connects a new redirection Route in the router.
  180. *
  181. * Compatibility proxy to \Cake\Routing\RouteBuilder::redirect() in the `/` scope.
  182. *
  183. * @param string $route A string describing the template of the route
  184. * @param array $url An URL to redirect to. Can be a string or a Cake array-based URL
  185. * @param array $options An array matching the named elements in the route to regular expressions which that
  186. * element should match. Also contains additional parameters such as which routed parameters should be
  187. * shifted into the passed arguments. As well as supplying patterns for routing parameters.
  188. * @return array Array of routes
  189. * @see \Cake\Routing\RouteBuilder::redirect()
  190. */
  191. public static function redirect($route, $url, $options = []) {
  192. $options['routeClass'] = 'Cake\Routing\Route\RedirectRoute';
  193. if (is_string($url)) {
  194. $url = ['redirect' => $url];
  195. }
  196. return static::connect($route, $url, $options);
  197. }
  198. /**
  199. * Generate REST resource routes for the given controller(s).
  200. *
  201. * Compatibility proxy to \Cake\Routing\RouteBuilder::resources(). Additional, compatibility
  202. * around prefixes and plugins and prefixes is handled by this method.
  203. *
  204. * A quick way to generate a default routes to a set of REST resources (controller(s)).
  205. *
  206. * ### Usage
  207. *
  208. * Connect resource routes for an app controller:
  209. *
  210. * {{{
  211. * Router::mapResources('Posts');
  212. * }}}
  213. *
  214. * Connect resource routes for the Comment controller in the
  215. * Comments plugin:
  216. *
  217. * {{{
  218. * Router::mapResources('Comments.Comment');
  219. * }}}
  220. *
  221. * Plugins will create lower_case underscored resource routes. e.g
  222. * `/comments/comment`
  223. *
  224. * Connect resource routes for the Posts controller in the
  225. * Admin prefix:
  226. *
  227. * {{{
  228. * Router::mapResources('Posts', ['prefix' => 'admin']);
  229. * }}}
  230. *
  231. * Prefixes will create lower_case underscored resource routes. e.g
  232. * `/admin/posts`
  233. *
  234. * ### Options:
  235. *
  236. * - 'id' - The regular expression fragment to use when matching IDs. By default, matches
  237. * integer values and UUIDs.
  238. * - 'prefix' - Routing prefix to use for the generated routes. Defaults to ''.
  239. * Using this option will create prefixed routes, similar to using Routing.prefixes.
  240. *
  241. * @param string|array $controller A controller name or array of controller names (i.e. "Posts" or "ListItems")
  242. * @param array $options Options to use when generating REST routes
  243. * @return void
  244. */
  245. public static function mapResources($controller, $options = []) {
  246. foreach ((array)$controller as $name) {
  247. list($plugin, $name) = pluginSplit($name);
  248. $prefix = $pluginUrl = false;
  249. if (!empty($options['prefix'])) {
  250. $prefix = $options['prefix'];
  251. }
  252. if ($plugin) {
  253. $pluginUrl = Inflector::underscore($plugin);
  254. }
  255. $callback = function($routes) use ($name, $options) {
  256. $routes->resources($name, $options);
  257. };
  258. if ($plugin && $prefix) {
  259. $path = '/' . implode('/', [$prefix, $pluginUrl]);
  260. $params = ['prefix' => $prefix, 'plugin' => $plugin];
  261. return static::scope($path, $params, $callback);
  262. }
  263. if ($prefix) {
  264. return static::prefix($prefix, $callback);
  265. }
  266. if ($plugin) {
  267. return static::plugin($plugin, $callback);
  268. }
  269. return static::scope('/', $callback);
  270. }
  271. }
  272. /**
  273. * Parses given URL string. Returns 'routing' parameters for that URL.
  274. *
  275. * @param string $url URL to be parsed
  276. * @return array Parsed elements from URL
  277. * @throws \Cake\Routing\Exception\MissingRouteException When a route cannot be handled
  278. */
  279. public static function parse($url) {
  280. if (!static::$initialized) {
  281. static::_loadRoutes();
  282. }
  283. if (strpos($url, '/') !== 0) {
  284. $url = '/' . $url;
  285. }
  286. return static::$_collection->parse($url);
  287. }
  288. /**
  289. * Takes parameter and path information back from the Dispatcher, sets these
  290. * parameters as the current request parameters that are merged with URL arrays
  291. * created later in the request.
  292. *
  293. * Nested requests will create a stack of requests. You can remove requests using
  294. * Router::popRequest(). This is done automatically when using Object::requestAction().
  295. *
  296. * Will accept either a Cake\Network\Request object or an array of arrays. Support for
  297. * accepting arrays may be removed in the future.
  298. *
  299. * @param \Cake\Network\Request|array $request Parameters and path information or a Cake\Network\Request object.
  300. * @return void
  301. */
  302. public static function setRequestInfo($request) {
  303. if ($request instanceof Request) {
  304. static::pushRequest($request);
  305. } else {
  306. $requestData = $request;
  307. $requestData += array([], []);
  308. $requestData[0] += array(
  309. 'controller' => false,
  310. 'action' => false,
  311. 'plugin' => null
  312. );
  313. $request = new Request();
  314. $request->addParams($requestData[0])->addPaths($requestData[1]);
  315. static::pushRequest($request);
  316. }
  317. }
  318. /**
  319. * Push a request onto the request stack. Pushing a request
  320. * sets the request context used when generating URLs.
  321. *
  322. * @param \Cake\Network\Request $request Request instance.
  323. * @return void
  324. */
  325. public static function pushRequest(Request $request) {
  326. static::$_requests[] = $request;
  327. static::_setContext($request);
  328. }
  329. /**
  330. * Store the request context for a given request.
  331. *
  332. * @param \Cake\Network\Request $request The request instance.
  333. * @return void
  334. */
  335. protected static function _setContext($request) {
  336. static::$_requestContext = [
  337. '_base' => $request->base,
  338. '_port' => $request->port(),
  339. '_scheme' => $request->scheme(),
  340. '_host' => $request->host()
  341. ];
  342. }
  343. /**
  344. * Pops a request off of the request stack. Used when doing requestAction
  345. *
  346. * @return \Cake\Network\Request The request removed from the stack.
  347. * @see Router::pushRequest()
  348. * @see Object::requestAction()
  349. */
  350. public static function popRequest() {
  351. $removed = array_pop(static::$_requests);
  352. $last = end(static::$_requests);
  353. if ($last) {
  354. static::_setContext($last);
  355. reset(static::$_requests);
  356. }
  357. return $removed;
  358. }
  359. /**
  360. * Get the current request object, or the first one.
  361. *
  362. * @param bool $current True to get the current request, or false to get the first one.
  363. * @return \Cake\Network\Request|null.
  364. */
  365. public static function getRequest($current = false) {
  366. if ($current) {
  367. return end(static::$_requests);
  368. }
  369. return isset(static::$_requests[0]) ? static::$_requests[0] : null;
  370. }
  371. /**
  372. * Reloads default Router settings. Resets all class variables and
  373. * removes all connected routes.
  374. *
  375. * @return void
  376. */
  377. public static function reload() {
  378. if (empty(static::$_initialState)) {
  379. static::$_collection = new RouteCollection();
  380. static::$_initialState = get_class_vars(get_called_class());
  381. return;
  382. }
  383. foreach (static::$_initialState as $key => $val) {
  384. if ($key != '_initialState') {
  385. static::${$key} = $val;
  386. }
  387. }
  388. static::$_collection = new RouteCollection();
  389. }
  390. /**
  391. * Add a URL filter to Router.
  392. *
  393. * URL filter functions are applied to every array $url provided to
  394. * Router::url() before the URLs are sent to the route collection.
  395. *
  396. * Callback functions should expect the following parameters:
  397. *
  398. * - `$params` The URL params being processed.
  399. * - `$request` The current request.
  400. *
  401. * The URL filter function should *always* return the params even if unmodified.
  402. *
  403. * ### Usage
  404. *
  405. * URL filters allow you to easily implement features like persistent parameters.
  406. *
  407. * {{{
  408. * Router::addUrlFilter(function ($params, $request) {
  409. * if (isset($request->params['lang']) && !isset($params['lang']) {
  410. * $params['lang'] = $request->params['lang'];
  411. * }
  412. * return $params;
  413. * });
  414. * }}}
  415. *
  416. * @param callable $function The function to add
  417. * @return void
  418. */
  419. public static function addUrlFilter(callable $function) {
  420. static::$_urlFilters[] = $function;
  421. }
  422. /**
  423. * Applies all the connected URL filters to the URL.
  424. *
  425. * @param array $url The URL array being modified.
  426. * @return array The modified URL.
  427. * @see Router::url()
  428. * @see Router::addUrlFilter()
  429. */
  430. protected static function _applyUrlFilters($url) {
  431. $request = static::getRequest(true);
  432. foreach (static::$_urlFilters as $filter) {
  433. $url = $filter($url, $request);
  434. }
  435. return $url;
  436. }
  437. /**
  438. * Finds URL for specified action.
  439. *
  440. * Returns an URL pointing to a combination of controller and action.
  441. *
  442. * ### Usage
  443. *
  444. * - `Router::url('/posts/edit/1');` Returns the string with the base dir prepended.
  445. * This usage does not use reverser routing.
  446. * - `Router::url(['controller' => 'posts', 'action' => 'edit']);` Returns a URL
  447. * generated through reverse routing.
  448. * - `Router::url(['_name' => 'custom-name', ...]);` Returns a URL generated
  449. * through reverse routing. This form allows you to leverage named routes.
  450. *
  451. * There are a few 'special' parameters that can change the final URL string that is generated
  452. *
  453. * - `_base` - Set to false to remove the base path from the generated URL. If your application
  454. * is not in the root directory, this can be used to generate URLs that are 'cake relative'.
  455. * cake relative URLs are required when using requestAction.
  456. * - `_scheme` - Set to create links on different schemes like `webcal` or `ftp`. Defaults
  457. * to the current scheme.
  458. * - `_host` - Set the host to use for the link. Defaults to the current host.
  459. * - `_port` - Set the port if you need to create links on non-standard ports.
  460. * - `_full` - If true output of `Router::fullBaseUrl()` will be prepended to generated URLs.
  461. * - `#` - Allows you to set URL hash fragments.
  462. * - `_ssl` - Set to true to convert the generated URL to https, or false to force http.
  463. * - `_name` - Name of route. If you have setup named routes you can use this key
  464. * to specify it.
  465. *
  466. * @param string|array $url An array specifying any of the following:
  467. * 'controller', 'action', 'plugin' additionally, you can provide routed
  468. * elements or query string parameters. If string it can be name any valid url
  469. * string.
  470. * @param bool $full If true, the full base URL will be prepended to the result.
  471. * Default is false.
  472. * @return string Full translated URL with base path.
  473. * @throws \Cake\Core\Exception\Exception When the route name is not found
  474. */
  475. public static function url($url = null, $full = false) {
  476. if (!static::$initialized) {
  477. static::_loadRoutes();
  478. }
  479. $params = array(
  480. 'plugin' => null,
  481. 'controller' => null,
  482. 'action' => 'index',
  483. '_ext' => null
  484. );
  485. $here = $base = $output = $frag = null;
  486. $request = static::getRequest(true);
  487. if ($request) {
  488. $params = $request->params;
  489. $here = $request->here;
  490. $base = $request->base;
  491. }
  492. if (!isset($base)) {
  493. $base = Configure::read('App.base');
  494. }
  495. if (empty($url)) {
  496. $output = isset($here) ? $here : '/';
  497. if ($full) {
  498. $output = static::fullBaseUrl() . $base . $output;
  499. }
  500. return $output;
  501. } elseif (is_array($url)) {
  502. if (isset($url['_full']) && $url['_full'] === true) {
  503. $full = true;
  504. unset($url['_full']);
  505. }
  506. // Compatibility for older versions.
  507. if (isset($url['?'])) {
  508. $q = $url['?'];
  509. unset($url['?']);
  510. $url = array_merge($url, $q);
  511. }
  512. if (isset($url['#'])) {
  513. $frag = '#' . $url['#'];
  514. unset($url['#']);
  515. }
  516. if (isset($url['_ssl'])) {
  517. $url['_scheme'] = ($url['_ssl'] === true) ? 'https' : 'http';
  518. unset($url['_ssl']);
  519. }
  520. if (!isset($url['_name'])) {
  521. // Copy the current action if the controller is the current one.
  522. if (
  523. empty($url['action']) &&
  524. (empty($url['controller']) || $params['controller'] === $url['controller'])
  525. ) {
  526. $url['action'] = $params['action'];
  527. }
  528. // Keep the current prefix around if none set.
  529. if (isset($params['prefix']) && !isset($url['prefix'])) {
  530. $url['prefix'] = $params['prefix'];
  531. }
  532. $url += array(
  533. 'plugin' => $params['plugin'],
  534. 'controller' => $params['controller'],
  535. 'action' => 'index',
  536. '_ext' => $params['_ext']
  537. );
  538. }
  539. $url = static::_applyUrlFilters($url);
  540. $output = static::$_collection->match($url, static::$_requestContext);
  541. } else {
  542. $plainString = (
  543. strpos($url, 'javascript:') === 0 ||
  544. strpos($url, 'mailto:') === 0 ||
  545. strpos($url, 'tel:') === 0 ||
  546. strpos($url, 'sms:') === 0 ||
  547. strpos($url, '#') === 0 ||
  548. strpos($url, '?') === 0 ||
  549. strpos($url, '//') === 0 ||
  550. strpos($url, '://') !== false
  551. );
  552. if ($plainString) {
  553. return $url;
  554. }
  555. $output = $base . $url;
  556. }
  557. $protocol = preg_match('#^[a-z][a-z0-9+\-.]*\://#i', $output);
  558. if ($protocol === 0) {
  559. $output = str_replace('//', '/', '/' . $output);
  560. if ($full) {
  561. $output = static::fullBaseUrl() . $output;
  562. }
  563. }
  564. return $output . $frag;
  565. }
  566. /**
  567. * Sets the full base URL that will be used as a prefix for generating
  568. * fully qualified URLs for this application. If not parameters are passed,
  569. * the currently configured value is returned.
  570. *
  571. * ## Note:
  572. *
  573. * If you change the configuration value ``App.fullBaseUrl`` during runtime
  574. * and expect the router to produce links using the new setting, you are
  575. * required to call this method passing such value again.
  576. *
  577. * @param string $base the prefix for URLs generated containing the domain.
  578. * For example: ``http://example.com``
  579. * @return string
  580. */
  581. public static function fullBaseUrl($base = null) {
  582. if ($base !== null) {
  583. static::$_fullBaseUrl = $base;
  584. Configure::write('App.fullBaseUrl', $base);
  585. }
  586. if (empty(static::$_fullBaseUrl)) {
  587. static::$_fullBaseUrl = Configure::read('App.fullBaseUrl');
  588. }
  589. return static::$_fullBaseUrl;
  590. }
  591. /**
  592. * Reverses a parsed parameter array into a string.
  593. *
  594. * Works similarly to Router::url(), but since parsed URL's contain additional
  595. * 'pass' as well as 'url.url' keys. Those keys need to be specially
  596. * handled in order to reverse a params array into a string URL.
  597. *
  598. * This will strip out 'autoRender', 'bare', 'requested', and 'return' param names as those
  599. * are used for CakePHP internals and should not normally be part of an output URL.
  600. *
  601. * @param \Cake\Network\Request|array $params The params array or
  602. * Cake\Network\Request object that needs to be reversed.
  603. * @param bool $full Set to true to include the full URL including the
  604. * protocol when reversing the URL.
  605. * @return string The string that is the reversed result of the array
  606. */
  607. public static function reverse($params, $full = false) {
  608. $url = [];
  609. if ($params instanceof Request) {
  610. $url = $params->query;
  611. $params = $params->params;
  612. } elseif (isset($params['url'])) {
  613. $url = $params['url'];
  614. }
  615. $pass = isset($params['pass']) ? $params['pass'] : [];
  616. unset(
  617. $params['pass'], $params['paging'], $params['models'], $params['url'], $url['url'],
  618. $params['autoRender'], $params['bare'], $params['requested'], $params['return'],
  619. $params['_Token']
  620. );
  621. $params = array_merge($params, $pass);
  622. if (!empty($url)) {
  623. $params['?'] = $url;
  624. }
  625. return Router::url($params, $full);
  626. }
  627. /**
  628. * Normalizes an URL for purposes of comparison.
  629. *
  630. * Will strip the base path off and replace any double /'s.
  631. * It will not unify the casing and underscoring of the input value.
  632. *
  633. * @param array|string $url URL to normalize Either an array or a string URL.
  634. * @return string Normalized URL
  635. */
  636. public static function normalize($url = '/') {
  637. if (is_array($url)) {
  638. $url = Router::url($url);
  639. }
  640. if (preg_match('/^[a-z\-]+:\/\//', $url)) {
  641. return $url;
  642. }
  643. $request = Router::getRequest();
  644. if (!empty($request->base) && stristr($url, $request->base)) {
  645. $url = preg_replace('/^' . preg_quote($request->base, '/') . '/', '', $url, 1);
  646. }
  647. $url = '/' . $url;
  648. while (strpos($url, '//') !== false) {
  649. $url = str_replace('//', '/', $url);
  650. }
  651. $url = preg_replace('/(?:(\/$))/', '', $url);
  652. if (empty($url)) {
  653. return '/';
  654. }
  655. return $url;
  656. }
  657. /**
  658. * Deprecated method for backwards compatibility.
  659. *
  660. * @param string|array $extensions List of extensions to be added.
  661. * @param bool $merge Whether to merge with or override existing extensions.
  662. * Defaults to `true`.
  663. * @return array Extensions list.
  664. * @deprecated 3.0.0 Use Router::extensions() instead.
  665. */
  666. public static function parseExtensions($extensions = null, $merge = true) {
  667. trigger_error(
  668. 'Router::parseExtensions() is deprecated should use Router::extensions() instead.',
  669. E_USER_DEPRECATED
  670. );
  671. return static::extensions($extensions, $merge);
  672. }
  673. /**
  674. * Get/Set valid extensions. Instructs the router to parse out file extensions
  675. * from the URL. For example, http://example.com/posts.rss would yield a file
  676. * extension of "rss". The file extension itself is made available in the
  677. * controller as `$this->request->params['_ext']`, and is used by the RequestHandler
  678. * component to automatically switch to alternate layouts and templates, and
  679. * load helpers corresponding to the given content, i.e. RssHelper. Switching
  680. * layouts and helpers requires that the chosen extension has a defined mime type
  681. * in `Cake\Network\Response`.
  682. *
  683. * A string or an array of valid extensions can be passed to this method.
  684. * If called without any parameters it will return current list of set extensions.
  685. *
  686. * @param array|string $extensions List of extensions to be added.
  687. * @param bool $merge Whether to merge with or override existing extensions.
  688. * Defaults to `true`.
  689. * @return array Array of extensions Router is configured to parse.
  690. */
  691. public static function extensions($extensions = null, $merge = true) {
  692. $collection = static::$_collection;
  693. if ($extensions === null) {
  694. if (!static::$initialized) {
  695. static::_loadRoutes();
  696. }
  697. return $collection->extensions();
  698. }
  699. return $collection->extensions($extensions, $merge);
  700. }
  701. /**
  702. * Provides legacy support for named parameters on incoming URLs.
  703. *
  704. * Checks the passed parameters for elements containing `$options['separator']`
  705. * Those parameters are split and parsed as if they were old style named parameters.
  706. *
  707. * The parsed parameters will be moved from params['pass'] to params['named'].
  708. *
  709. * ### Options
  710. *
  711. * - `separator` The string to use as a separator. Defaults to `:`.
  712. *
  713. * @param \Cake\Network\Request $request The request object to modify.
  714. * @param array $options The array of options.
  715. * @return \Cake\Network\Request The modified request
  716. */
  717. public static function parseNamedParams(Request $request, array $options = []) {
  718. $options += array('separator' => ':');
  719. if (empty($request->params['pass'])) {
  720. $request->params['named'] = [];
  721. return $request;
  722. }
  723. $named = [];
  724. foreach ($request->params['pass'] as $key => $value) {
  725. if (strpos($value, $options['separator']) === false) {
  726. continue;
  727. }
  728. unset($request->params['pass'][$key]);
  729. list($key, $value) = explode($options['separator'], $value, 2);
  730. if (preg_match_all('/\[([A-Za-z0-9_-]+)?\]/', $key, $matches, PREG_SET_ORDER)) {
  731. $matches = array_reverse($matches);
  732. $parts = explode('[', $key);
  733. $key = array_shift($parts);
  734. $arr = $value;
  735. foreach ($matches as $match) {
  736. if (empty($match[1])) {
  737. $arr = array($arr);
  738. } else {
  739. $arr = array(
  740. $match[1] => $arr
  741. );
  742. }
  743. }
  744. $value = $arr;
  745. }
  746. $named = array_merge_recursive($named, array($key => $value));
  747. }
  748. $request->params['named'] = $named;
  749. return $request;
  750. }
  751. /**
  752. * Create a routing scope.
  753. *
  754. * Routing scopes allow you to keep your routes DRY and avoid repeating
  755. * common path prefixes, and or parameter sets.
  756. *
  757. * Scoped collections will be indexed by path for faster route parsing. If you
  758. * re-open or re-use a scope the connected routes will be merged with the
  759. * existing ones.
  760. *
  761. * ### Example
  762. *
  763. * {{{
  764. * Router::scope('/blog', ['plugin' => 'Blog'], function($routes) {
  765. * $routes->connect('/', ['controller' => 'Articles']);
  766. * });
  767. * }}}
  768. *
  769. * The above would result in a `/blog/` route being created, with both the
  770. * plugin & controller default parameters set.
  771. *
  772. * You can use Router::plugin() and Router::prefix() as shortcuts to creating
  773. * specific kinds of scopes.
  774. *
  775. * Routing scopes will inherit the globally set extensions configured with
  776. * Router::extensions(). You can also set valid extensions using
  777. * `$routes->extensions()` in your closure.
  778. *
  779. * @param string $path The path prefix for the scope. This path will be prepended
  780. * to all routes connected in the scoped collection.
  781. * @param array|callable $params An array of routing defaults to add to each connected route.
  782. * If you have no parameters, this argument can be a callable.
  783. * @param callable $callback The callback to invoke with the scoped collection.
  784. * @throws \InvalidArgumentException When an invalid callable is provided.
  785. * @return null|\Cake\Routing\RouteBuilder The route builder
  786. * was created/used.
  787. */
  788. public static function scope($path, $params = [], $callback = null) {
  789. $builder = new RouteBuilder(static::$_collection, '/', [], [
  790. 'routeClass' => static::defaultRouteClass(),
  791. 'extensions' => static::$_collection->extensions()
  792. ]);
  793. $builder->scope($path, $params, $callback);
  794. }
  795. /**
  796. * Create prefixed routes.
  797. *
  798. * This method creates a scoped route collection that includes
  799. * relevant prefix information.
  800. *
  801. * The path parameter is used to generate the routing parameter name.
  802. * For example a path of `admin` would result in `'prefix' => 'admin'` being
  803. * applied to all connected routes.
  804. *
  805. * You can re-open a prefix as many times as necessary, as well as nest prefixes.
  806. * Nested prefixes will result in prefix values like `admin/api` which translates
  807. * to the `Controller\Admin\Api\` namespace.
  808. *
  809. * @param string $name The prefix name to use.
  810. * @param callable $callback The callback to invoke that builds the prefixed routes.
  811. * @return void
  812. */
  813. public static function prefix($name, $callback) {
  814. $name = Inflector::underscore($name);
  815. $path = '/' . $name;
  816. static::scope($path, ['prefix' => $name], $callback);
  817. }
  818. /**
  819. * Add plugin routes.
  820. *
  821. * This method creates a scoped route collection that includes
  822. * relevant plugin information.
  823. *
  824. * The plugin name will be inflected to the underscore version to create
  825. * the routing path. If you want a custom path name, use the `path` option.
  826. *
  827. * Routes connected in the scoped collection will have the correct path segment
  828. * prepended, and have a matching plugin routing key set.
  829. *
  830. * @param string $name The plugin name to build routes for
  831. * @param array|callable $options Either the options to use, or a callback
  832. * @param callable $callback The callback to invoke that builds the plugin routes.
  833. * Only required when $options is defined
  834. * @return void
  835. */
  836. public static function plugin($name, $options = [], $callback = null) {
  837. if ($callback === null) {
  838. $callback = $options;
  839. $options = [];
  840. }
  841. $params = ['plugin' => $name];
  842. if (empty($options['path'])) {
  843. $options['path'] = '/' . Inflector::underscore($name);
  844. }
  845. static::scope($options['path'], $params, $callback);
  846. }
  847. /**
  848. * Get the route scopes and their connected routes.
  849. *
  850. * @return array
  851. */
  852. public static function routes() {
  853. return static::$_collection->routes();
  854. }
  855. /**
  856. * Loads route configuration
  857. *
  858. * @return void
  859. */
  860. protected static function _loadRoutes() {
  861. static::$initialized = true;
  862. include CONFIG . 'routes.php';
  863. }
  864. }
  865. //Save the initial state
  866. Router::reload();