Router.php 32 KB

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