Router.php 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (https://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. (https://cakefoundation.org)
  11. * @link https://cakephp.org CakePHP(tm) Project
  12. * @since 0.2.9
  13. * @license https://opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Routing;
  16. use Cake\Core\Configure;
  17. use Cake\Http\ServerRequest;
  18. use Cake\Routing\Exception\MissingRouteException;
  19. use Cake\Utility\Inflector;
  20. use Psr\Http\Message\ServerRequestInterface;
  21. use RuntimeException;
  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. For more information on routes and
  32. * how to connect them see Router::connect().
  33. */
  34. class Router
  35. {
  36. /**
  37. * Have routes been loaded
  38. *
  39. * @var bool
  40. * @deprecated 3.5.0 Routes will be loaded via the Application::routes() hook in 4.0.0
  41. */
  42. public static $initialized = false;
  43. /**
  44. * Default route class.
  45. *
  46. * @var string
  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. /**
  93. * The route collection routes would be added to.
  94. *
  95. * @var \Cake\Routing\RouteCollection
  96. */
  97. protected static $_collection;
  98. /**
  99. * A hash of request context data.
  100. *
  101. * @var array
  102. */
  103. protected static $_requestContext = [];
  104. /**
  105. * Named expressions
  106. *
  107. * @var array
  108. */
  109. protected static $_namedExpressions = [
  110. 'Action' => Router::ACTION,
  111. 'Year' => Router::YEAR,
  112. 'Month' => Router::MONTH,
  113. 'Day' => Router::DAY,
  114. 'ID' => Router::ID,
  115. 'UUID' => Router::UUID
  116. ];
  117. /**
  118. * Maintains the request object stack for the current request.
  119. * This will contain more than one request object when requestAction is used.
  120. *
  121. * @var array
  122. */
  123. protected static $_requests = [];
  124. /**
  125. * Initial state is populated the first time reload() is called which is at the bottom
  126. * of this file. This is a cheat as get_class_vars() returns the value of static vars even if they
  127. * have changed.
  128. *
  129. * @var array
  130. */
  131. protected static $_initialState = [];
  132. /**
  133. * The stack of URL filters to apply against routing URLs before passing the
  134. * parameters to the route collection.
  135. *
  136. * @var callable[]
  137. */
  138. protected static $_urlFilters = [];
  139. /**
  140. * Default extensions defined with Router::extensions()
  141. *
  142. * @var array
  143. */
  144. protected static $_defaultExtensions = [];
  145. /**
  146. * Get or set default route class.
  147. *
  148. * @param string|null $routeClass Class name.
  149. * @return string|null
  150. */
  151. public static function defaultRouteClass($routeClass = null)
  152. {
  153. if ($routeClass === null) {
  154. return static::$_defaultRouteClass;
  155. }
  156. static::$_defaultRouteClass = $routeClass;
  157. }
  158. /**
  159. * Gets the named route patterns for use in config/routes.php
  160. *
  161. * @return array Named route elements
  162. * @see \Cake\Routing\Router::$_namedExpressions
  163. */
  164. public static function getNamedExpressions()
  165. {
  166. return static::$_namedExpressions;
  167. }
  168. /**
  169. * Connects a new Route in the router.
  170. *
  171. * Compatibility proxy to \Cake\Routing\RouteBuilder::connect() in the `/` scope.
  172. *
  173. * @param string $route A string describing the template of the route
  174. * @param array|string $defaults An array describing the default route parameters. These parameters will be used by default
  175. * and can supply routing parameters that are not dynamic. See above.
  176. * @param array $options An array matching the named elements in the route to regular expressions which that
  177. * element should match. Also contains additional parameters such as which routed parameters should be
  178. * shifted into the passed arguments, supplying patterns for routing parameters and supplying the name of a
  179. * custom routing class.
  180. * @return void
  181. * @throws \Cake\Core\Exception\Exception
  182. * @see \Cake\Routing\RouteBuilder::connect()
  183. * @see \Cake\Routing\Router::scope()
  184. */
  185. public static function connect($route, $defaults = [], $options = [])
  186. {
  187. static::$initialized = true;
  188. static::scope('/', function ($routes) use ($route, $defaults, $options) {
  189. $routes->connect($route, $defaults, $options);
  190. });
  191. }
  192. /**
  193. * Connects a new redirection Route in the router.
  194. *
  195. * Compatibility proxy to \Cake\Routing\RouteBuilder::redirect() in the `/` scope.
  196. *
  197. * @param string $route A string describing the template of the route
  198. * @param array $url A URL to redirect to. Can be a string or a Cake array-based URL
  199. * @param array $options An array matching the named elements in the route to regular expressions which that
  200. * element should match. Also contains additional parameters such as which routed parameters should be
  201. * shifted into the passed arguments. As well as supplying patterns for routing parameters.
  202. * @return void
  203. * @see \Cake\Routing\RouteBuilder::redirect()
  204. * @deprecated 3.3.0 Use Router::scope() and RouteBuilder::redirect() instead.
  205. */
  206. public static function redirect($route, $url, $options = [])
  207. {
  208. deprecationWarning(
  209. 'Router::redirect() is deprecated. ' .
  210. 'Use Router::scope() and RouteBuilder::redirect() instead.'
  211. );
  212. if (is_string($url)) {
  213. $url = ['redirect' => $url];
  214. }
  215. if (!isset($options['routeClass'])) {
  216. $options['routeClass'] = 'Cake\Routing\Route\RedirectRoute';
  217. }
  218. static::connect($route, $url, $options);
  219. }
  220. /**
  221. * Generate REST resource routes for the given controller(s).
  222. *
  223. * Compatibility proxy to \Cake\Routing\RouteBuilder::resources(). Additional, compatibility
  224. * around prefixes and plugins and prefixes is handled by this method.
  225. *
  226. * A quick way to generate a default routes to a set of REST resources (controller(s)).
  227. *
  228. * ### Usage
  229. *
  230. * Connect resource routes for an app controller:
  231. *
  232. * ```
  233. * Router::mapResources('Posts');
  234. * ```
  235. *
  236. * Connect resource routes for the Comment controller in the
  237. * Comments plugin:
  238. *
  239. * ```
  240. * Router::mapResources('Comments.Comment');
  241. * ```
  242. *
  243. * Plugins will create lower_case underscored resource routes. e.g
  244. * `/comments/comment`
  245. *
  246. * Connect resource routes for the Posts controller in the
  247. * Admin prefix:
  248. *
  249. * ```
  250. * Router::mapResources('Posts', ['prefix' => 'admin']);
  251. * ```
  252. *
  253. * Prefixes will create lower_case underscored resource routes. e.g
  254. * `/admin/posts`
  255. *
  256. * ### Options:
  257. *
  258. * - 'id' - The regular expression fragment to use when matching IDs. By default, matches
  259. * integer values and UUIDs.
  260. * - 'prefix' - Routing prefix to use for the generated routes. Defaults to ''.
  261. * Using this option will create prefixed routes, similar to using Routing.prefixes.
  262. * - 'only' - Only connect the specific list of actions.
  263. * - 'actions' - Override the method names used for connecting actions.
  264. * - 'map' - Additional resource routes that should be connected. If you define 'only' and 'map',
  265. * make sure that your mapped methods are also in the 'only' list.
  266. * - 'path' - Change the path so it doesn't match the resource name. E.g ArticlesController
  267. * is available at `/posts`
  268. *
  269. * @param string|array $controller A controller name or array of controller names (i.e. "Posts" or "ListItems")
  270. * @param array $options Options to use when generating REST routes
  271. * @see \Cake\Routing\RouteBuilder::resources()
  272. * @deprecated 3.3.0 Use Router::scope() and RouteBuilder::resources() instead.
  273. * @return void
  274. */
  275. public static function mapResources($controller, $options = [])
  276. {
  277. deprecationWarning(
  278. 'Router::mapResources() is deprecated. ' .
  279. 'Use Router::scope() and RouteBuilder::resources() instead.'
  280. );
  281. foreach ((array)$controller as $name) {
  282. list($plugin, $name) = pluginSplit($name);
  283. $prefix = $pluginUrl = false;
  284. if (!empty($options['prefix'])) {
  285. $prefix = $options['prefix'];
  286. unset($options['prefix']);
  287. }
  288. if ($plugin) {
  289. $pluginUrl = Inflector::underscore($plugin);
  290. }
  291. $callback = function ($routes) use ($name, $options) {
  292. $routes->resources($name, $options);
  293. };
  294. if ($plugin && $prefix) {
  295. $path = '/' . implode('/', [$prefix, $pluginUrl]);
  296. $params = ['prefix' => $prefix, 'plugin' => $plugin];
  297. static::scope($path, $params, $callback);
  298. return;
  299. }
  300. if ($prefix) {
  301. static::prefix($prefix, $callback);
  302. return;
  303. }
  304. if ($plugin) {
  305. static::plugin($plugin, $callback);
  306. return;
  307. }
  308. static::scope('/', $callback);
  309. return;
  310. }
  311. }
  312. /**
  313. * Parses given URL string. Returns 'routing' parameters for that URL.
  314. *
  315. * @param string $url URL to be parsed.
  316. * @param string $method The HTTP method being used.
  317. * @return array Parsed elements from URL.
  318. * @throws \Cake\Routing\Exception\MissingRouteException When a route cannot be handled
  319. * @deprecated 3.4.0 Use Router::parseRequest() instead.
  320. */
  321. public static function parse($url, $method = '')
  322. {
  323. deprecationWarning(
  324. 'Router::parse() is deprecated. ' .
  325. 'Use Router::parseRequest() instead. This will require adopting the Http\Server library.'
  326. );
  327. if (!static::$initialized) {
  328. static::_loadRoutes();
  329. }
  330. if (strpos($url, '/') !== 0) {
  331. $url = '/' . $url;
  332. }
  333. return static::$_collection->parse($url, $method);
  334. }
  335. /**
  336. * Get the routing parameters for the request is possible.
  337. *
  338. * @param \Psr\Http\Message\ServerRequestInterface $request The request to parse request data from.
  339. * @return array Parsed elements from URL.
  340. * @throws \Cake\Routing\Exception\MissingRouteException When a route cannot be handled
  341. */
  342. public static function parseRequest(ServerRequestInterface $request)
  343. {
  344. if (!static::$initialized) {
  345. static::_loadRoutes();
  346. }
  347. return static::$_collection->parseRequest($request);
  348. }
  349. /**
  350. * Takes parameter and path information back from the Dispatcher, sets these
  351. * parameters as the current request parameters that are merged with URL arrays
  352. * created later in the request.
  353. *
  354. * Nested requests will create a stack of requests. You can remove requests using
  355. * Router::popRequest(). This is done automatically when using Object::requestAction().
  356. *
  357. * Will accept either a Cake\Http\ServerRequest object or an array of arrays. Support for
  358. * accepting arrays may be removed in the future.
  359. *
  360. * @param \Cake\Http\ServerRequest|array $request Parameters and path information or a Cake\Http\ServerRequest object.
  361. * @return void
  362. * @deprecatd 3.6.0 Support for arrays will be removed in 4.0.0
  363. */
  364. public static function setRequestInfo($request)
  365. {
  366. if ($request instanceof ServerRequest) {
  367. static::pushRequest($request);
  368. } else {
  369. deprecationWarning(
  370. 'Passing an array into Router::setRequestInfo() is deprecated. ' .
  371. 'Pass an instance of ServerRequest instead.'
  372. );
  373. $requestData = $request;
  374. $requestData += [[], []];
  375. $requestData[0] += [
  376. 'controller' => false,
  377. 'action' => false,
  378. 'plugin' => null
  379. ];
  380. $request = new ServerRequest([
  381. 'params' => $requestData[0],
  382. 'url' => isset($requestData[1]['here']) ? $requestData[1]['here'] : '/',
  383. 'base' => isset($requestData[1]['base']) ? $requestData[1]['base'] : '',
  384. 'webroot' => isset($requestData[1]['webroot']) ? $requestData[1]['webroot'] : '/',
  385. ]);
  386. static::pushRequest($request);
  387. }
  388. }
  389. /**
  390. * Push a request onto the request stack. Pushing a request
  391. * sets the request context used when generating URLs.
  392. *
  393. * @param \Cake\Http\ServerRequest $request Request instance.
  394. * @return void
  395. */
  396. public static function pushRequest(ServerRequest $request)
  397. {
  398. static::$_requests[] = $request;
  399. static::setRequestContext($request);
  400. }
  401. /**
  402. * Store the request context for a given request.
  403. *
  404. * @param \Psr\Http\Message\ServerRequestInterface $request The request instance.
  405. * @return void
  406. * @throws InvalidArgumentException When parameter is an incorrect type.
  407. */
  408. public static function setRequestContext(ServerRequestInterface $request)
  409. {
  410. $uri = $request->getUri();
  411. static::$_requestContext = [
  412. '_base' => $request->getAttribute('base'),
  413. '_port' => $uri->getPort(),
  414. '_scheme' => $uri->getScheme(),
  415. '_host' => $uri->getHost(),
  416. ];
  417. }
  418. /**
  419. * Pops a request off of the request stack. Used when doing requestAction
  420. *
  421. * @return \Cake\Http\ServerRequest The request removed from the stack.
  422. * @see \Cake\Routing\Router::pushRequest()
  423. * @see \Cake\Routing\RequestActionTrait::requestAction()
  424. */
  425. public static function popRequest()
  426. {
  427. $removed = array_pop(static::$_requests);
  428. $last = end(static::$_requests);
  429. if ($last) {
  430. static::setRequestContext($last);
  431. reset(static::$_requests);
  432. }
  433. return $removed;
  434. }
  435. /**
  436. * Get the current request object, or the first one.
  437. *
  438. * @param bool $current True to get the current request, or false to get the first one.
  439. * @return \Cake\Http\ServerRequest|null
  440. */
  441. public static function getRequest($current = false)
  442. {
  443. if ($current) {
  444. $request = end(static::$_requests);
  445. return $request ?: null;
  446. }
  447. return isset(static::$_requests[0]) ? static::$_requests[0] : null;
  448. }
  449. /**
  450. * Reloads default Router settings. Resets all class variables and
  451. * removes all connected routes.
  452. *
  453. * @return void
  454. */
  455. public static function reload()
  456. {
  457. if (empty(static::$_initialState)) {
  458. static::$_collection = new RouteCollection();
  459. static::$_initialState = get_class_vars(get_called_class());
  460. return;
  461. }
  462. foreach (static::$_initialState as $key => $val) {
  463. if ($key !== '_initialState') {
  464. static::${$key} = $val;
  465. }
  466. }
  467. static::$_collection = new RouteCollection();
  468. }
  469. /**
  470. * Add a URL filter to Router.
  471. *
  472. * URL filter functions are applied to every array $url provided to
  473. * Router::url() before the URLs are sent to the route collection.
  474. *
  475. * Callback functions should expect the following parameters:
  476. *
  477. * - `$params` The URL params being processed.
  478. * - `$request` The current request.
  479. *
  480. * The URL filter function should *always* return the params even if unmodified.
  481. *
  482. * ### Usage
  483. *
  484. * URL filters allow you to easily implement features like persistent parameters.
  485. *
  486. * ```
  487. * Router::addUrlFilter(function ($params, $request) {
  488. * if ($request->getParam('lang') && !isset($params['lang'])) {
  489. * $params['lang'] = $request->getParam('lang');
  490. * }
  491. * return $params;
  492. * });
  493. * ```
  494. *
  495. * @param callable $function The function to add
  496. * @return void
  497. */
  498. public static function addUrlFilter(callable $function)
  499. {
  500. static::$_urlFilters[] = $function;
  501. }
  502. /**
  503. * Applies all the connected URL filters to the URL.
  504. *
  505. * @param array $url The URL array being modified.
  506. * @return array The modified URL.
  507. * @see \Cake\Routing\Router::url()
  508. * @see \Cake\Routing\Router::addUrlFilter()
  509. */
  510. protected static function _applyUrlFilters($url)
  511. {
  512. $request = static::getRequest(true);
  513. foreach (static::$_urlFilters as $filter) {
  514. $url = $filter($url, $request);
  515. }
  516. return $url;
  517. }
  518. /**
  519. * Finds URL for specified action.
  520. *
  521. * Returns a URL pointing to a combination of controller and action.
  522. *
  523. * ### Usage
  524. *
  525. * - `Router::url('/posts/edit/1');` Returns the string with the base dir prepended.
  526. * This usage does not use reverser routing.
  527. * - `Router::url(['controller' => 'posts', 'action' => 'edit']);` Returns a URL
  528. * generated through reverse routing.
  529. * - `Router::url(['_name' => 'custom-name', ...]);` Returns a URL generated
  530. * through reverse routing. This form allows you to leverage named routes.
  531. *
  532. * There are a few 'special' parameters that can change the final URL string that is generated
  533. *
  534. * - `_base` - Set to false to remove the base path from the generated URL. If your application
  535. * is not in the root directory, this can be used to generate URLs that are 'cake relative'.
  536. * cake relative URLs are required when using requestAction.
  537. * - `_scheme` - Set to create links on different schemes like `webcal` or `ftp`. Defaults
  538. * to the current scheme.
  539. * - `_host` - Set the host to use for the link. Defaults to the current host.
  540. * - `_port` - Set the port if you need to create links on non-standard ports.
  541. * - `_full` - If true output of `Router::fullBaseUrl()` will be prepended to generated URLs.
  542. * - `#` - Allows you to set URL hash fragments.
  543. * - `_ssl` - Set to true to convert the generated URL to https, or false to force http.
  544. * - `_name` - Name of route. If you have setup named routes you can use this key
  545. * to specify it.
  546. *
  547. * @param string|array|null $url An array specifying any of the following:
  548. * 'controller', 'action', 'plugin' additionally, you can provide routed
  549. * elements or query string parameters. If string it can be name any valid url
  550. * string.
  551. * @param bool $full If true, the full base URL will be prepended to the result.
  552. * Default is false.
  553. * @return string Full translated URL with base path.
  554. * @throws \Cake\Core\Exception\Exception When the route name is not found
  555. */
  556. public static function url($url = null, $full = false)
  557. {
  558. if (!static::$initialized) {
  559. static::_loadRoutes();
  560. }
  561. $params = [
  562. 'plugin' => null,
  563. 'controller' => null,
  564. 'action' => 'index',
  565. '_ext' => null,
  566. ];
  567. $here = $base = $output = $frag = null;
  568. // In 4.x this should be replaced with state injected via setRequestContext
  569. $request = static::getRequest(true);
  570. if ($request) {
  571. $params = $request->getAttribute('params');
  572. $here = $request->getRequestTarget();
  573. $base = $request->getAttribute('base');
  574. } else {
  575. $base = Configure::read('App.base');
  576. if (isset(static::$_requestContext['_base'])) {
  577. $base = static::$_requestContext['_base'];
  578. }
  579. }
  580. if (empty($url)) {
  581. $output = $base . (isset($here) ? $here : '/');
  582. if ($full) {
  583. $output = static::fullBaseUrl() . $output;
  584. }
  585. return $output;
  586. }
  587. if (is_array($url)) {
  588. if (isset($url['_ssl'])) {
  589. $url['_scheme'] = ($url['_ssl'] === true) ? 'https' : 'http';
  590. }
  591. if (isset($url['_full']) && $url['_full'] === true) {
  592. $full = true;
  593. }
  594. if (isset($url['#'])) {
  595. $frag = '#' . $url['#'];
  596. }
  597. unset($url['_ssl'], $url['_full'], $url['#']);
  598. $url = static::_applyUrlFilters($url);
  599. if (!isset($url['_name'])) {
  600. // Copy the current action if the controller is the current one.
  601. if (empty($url['action']) &&
  602. (empty($url['controller']) || $params['controller'] === $url['controller'])
  603. ) {
  604. $url['action'] = $params['action'];
  605. }
  606. // Keep the current prefix around if none set.
  607. if (isset($params['prefix']) && !isset($url['prefix'])) {
  608. $url['prefix'] = $params['prefix'];
  609. }
  610. $url += [
  611. 'plugin' => $params['plugin'],
  612. 'controller' => $params['controller'],
  613. 'action' => 'index',
  614. '_ext' => null
  615. ];
  616. }
  617. // If a full URL is requested with a scheme the host should default
  618. // to App.fullBaseUrl to avoid corrupt URLs
  619. if ($full && isset($url['_scheme']) && !isset($url['_host'])) {
  620. $url['_host'] = parse_url(static::fullBaseUrl(), PHP_URL_HOST);
  621. }
  622. $output = static::$_collection->match($url, static::$_requestContext + ['params' => $params]);
  623. } else {
  624. $plainString = (
  625. strpos($url, 'javascript:') === 0 ||
  626. strpos($url, 'mailto:') === 0 ||
  627. strpos($url, 'tel:') === 0 ||
  628. strpos($url, 'sms:') === 0 ||
  629. strpos($url, '#') === 0 ||
  630. strpos($url, '?') === 0 ||
  631. strpos($url, '//') === 0 ||
  632. strpos($url, '://') !== false
  633. );
  634. if ($plainString) {
  635. return $url;
  636. }
  637. $output = $base . $url;
  638. }
  639. $protocol = preg_match('#^[a-z][a-z0-9+\-.]*\://#i', $output);
  640. if ($protocol === 0) {
  641. $output = str_replace('//', '/', '/' . $output);
  642. if ($full) {
  643. $output = static::fullBaseUrl() . $output;
  644. }
  645. }
  646. return $output . $frag;
  647. }
  648. /**
  649. * Finds URL for specified action.
  650. *
  651. * Returns a bool if the url exists
  652. *
  653. * ### Usage
  654. *
  655. * @see Router::url()
  656. *
  657. * @param string|array|null $url An array specifying any of the following:
  658. * 'controller', 'action', 'plugin' additionally, you can provide routed
  659. * elements or query string parameters. If string it can be name any valid url
  660. * string.
  661. * @param bool $full If true, the full base URL will be prepended to the result.
  662. * Default is false.
  663. * @return bool
  664. */
  665. public static function routeExists($url = null, $full = false)
  666. {
  667. try {
  668. $route = static::url($url, $full);
  669. return true;
  670. } catch (MissingRouteException $e) {
  671. return false;
  672. }
  673. }
  674. /**
  675. * Sets the full base URL that will be used as a prefix for generating
  676. * fully qualified URLs for this application. If not parameters are passed,
  677. * the currently configured value is returned.
  678. *
  679. * ### Note:
  680. *
  681. * If you change the configuration value `App.fullBaseUrl` during runtime
  682. * and expect the router to produce links using the new setting, you are
  683. * required to call this method passing such value again.
  684. *
  685. * @param string|null $base the prefix for URLs generated containing the domain.
  686. * For example: `http://example.com`
  687. * @return string
  688. */
  689. public static function fullBaseUrl($base = null)
  690. {
  691. if ($base !== null) {
  692. static::$_fullBaseUrl = $base;
  693. Configure::write('App.fullBaseUrl', $base);
  694. }
  695. if (empty(static::$_fullBaseUrl)) {
  696. static::$_fullBaseUrl = Configure::read('App.fullBaseUrl');
  697. }
  698. return static::$_fullBaseUrl;
  699. }
  700. /**
  701. * Reverses a parsed parameter array into an array.
  702. *
  703. * Works similarly to Router::url(), but since parsed URL's contain additional
  704. * 'pass' as well as 'url.url' keys. Those keys need to be specially
  705. * handled in order to reverse a params array into a string URL.
  706. *
  707. * This will strip out 'autoRender', 'bare', 'requested', and 'return' param names as those
  708. * are used for CakePHP internals and should not normally be part of an output URL.
  709. *
  710. * @param \Cake\Http\ServerRequest|array $params The params array or
  711. * Cake\Http\ServerRequest object that needs to be reversed.
  712. * @return array The URL array ready to be used for redirect or HTML link.
  713. */
  714. public static function reverseToArray($params)
  715. {
  716. $url = [];
  717. if ($params instanceof ServerRequest) {
  718. $url = $params->getQueryParams();
  719. $params = $params->getAttribute('params');
  720. } elseif (isset($params['url'])) {
  721. $url = $params['url'];
  722. }
  723. $pass = isset($params['pass']) ? $params['pass'] : [];
  724. unset(
  725. $params['pass'],
  726. $params['paging'],
  727. $params['models'],
  728. $params['url'],
  729. $url['url'],
  730. $params['autoRender'],
  731. $params['bare'],
  732. $params['requested'],
  733. $params['return'],
  734. $params['_Token'],
  735. $params['_matchedRoute'],
  736. $params['_name']
  737. );
  738. $params = array_merge($params, $pass);
  739. if (!empty($url)) {
  740. $params['?'] = $url;
  741. }
  742. return $params;
  743. }
  744. /**
  745. * Reverses a parsed parameter array into a string.
  746. *
  747. * Works similarly to Router::url(), but since parsed URL's contain additional
  748. * 'pass' as well as 'url.url' keys. Those keys need to be specially
  749. * handled in order to reverse a params array into a string URL.
  750. *
  751. * This will strip out 'autoRender', 'bare', 'requested', and 'return' param names as those
  752. * are used for CakePHP internals and should not normally be part of an output URL.
  753. *
  754. * @param \Cake\Http\ServerRequest|array $params The params array or
  755. * Cake\Network\Request object that needs to be reversed.
  756. * @param bool $full Set to true to include the full URL including the
  757. * protocol when reversing the URL.
  758. * @return string The string that is the reversed result of the array
  759. */
  760. public static function reverse($params, $full = false)
  761. {
  762. $params = static::reverseToArray($params);
  763. return static::url($params, $full);
  764. }
  765. /**
  766. * Normalizes a URL for purposes of comparison.
  767. *
  768. * Will strip the base path off and replace any double /'s.
  769. * It will not unify the casing and underscoring of the input value.
  770. *
  771. * @param array|string $url URL to normalize Either an array or a string URL.
  772. * @return string Normalized URL
  773. */
  774. public static function normalize($url = '/')
  775. {
  776. if (is_array($url)) {
  777. $url = static::url($url);
  778. }
  779. if (preg_match('/^[a-z\-]+:\/\//', $url)) {
  780. return $url;
  781. }
  782. $request = static::getRequest();
  783. if ($request) {
  784. $base = $request->getAttribute('base');
  785. if (strlen($base) && stristr($url, $base)) {
  786. $url = preg_replace('/^' . preg_quote($base, '/') . '/', '', $url, 1);
  787. }
  788. }
  789. $url = '/' . $url;
  790. while (strpos($url, '//') !== false) {
  791. $url = str_replace('//', '/', $url);
  792. }
  793. $url = preg_replace('/(?:(\/$))/', '', $url);
  794. if (empty($url)) {
  795. return '/';
  796. }
  797. return $url;
  798. }
  799. /**
  800. * Get or set valid extensions for all routes connected later.
  801. *
  802. * Instructs the router to parse out file extensions
  803. * from the URL. For example, http://example.com/posts.rss would yield a file
  804. * extension of "rss". The file extension itself is made available in the
  805. * controller as `$this->request->getParam('_ext')`, and is used by the RequestHandler
  806. * component to automatically switch to alternate layouts and templates, and
  807. * load helpers corresponding to the given content, i.e. RssHelper. Switching
  808. * layouts and helpers requires that the chosen extension has a defined mime type
  809. * in `Cake\Http\Response`.
  810. *
  811. * A string or an array of valid extensions can be passed to this method.
  812. * If called without any parameters it will return current list of set extensions.
  813. *
  814. * @param array|string|null $extensions List of extensions to be added.
  815. * @param bool $merge Whether to merge with or override existing extensions.
  816. * Defaults to `true`.
  817. * @return array Array of extensions Router is configured to parse.
  818. */
  819. public static function extensions($extensions = null, $merge = true)
  820. {
  821. $collection = static::$_collection;
  822. if ($extensions === null) {
  823. if (!static::$initialized) {
  824. static::_loadRoutes();
  825. }
  826. return array_unique(array_merge(static::$_defaultExtensions, $collection->getExtensions()));
  827. }
  828. $extensions = (array)$extensions;
  829. if ($merge) {
  830. $extensions = array_unique(array_merge(static::$_defaultExtensions, $extensions));
  831. }
  832. return static::$_defaultExtensions = $extensions;
  833. }
  834. /**
  835. * Provides legacy support for named parameters on incoming URLs.
  836. *
  837. * Checks the passed parameters for elements containing `$options['separator']`
  838. * Those parameters are split and parsed as if they were old style named parameters.
  839. *
  840. * The parsed parameters will be moved from params['pass'] to params['named'].
  841. *
  842. * ### Options
  843. *
  844. * - `separator` The string to use as a separator. Defaults to `:`.
  845. *
  846. * @param \Cake\Http\ServerRequest $request The request object to modify.
  847. * @param array $options The array of options.
  848. * @return \Cake\Http\ServerRequest The modified request
  849. * @deprecated 3.3.0 Named parameter backwards compatibility will be removed in 4.0.
  850. */
  851. public static function parseNamedParams(ServerRequest $request, array $options = [])
  852. {
  853. deprecationWarning(
  854. 'Router::parseNamedParams() is deprecated. ' .
  855. '2.x backwards compatible named parameter support will be removed in 4.0'
  856. );
  857. $options += ['separator' => ':'];
  858. if (!$request->getParam('pass')) {
  859. return $request->withParam('named', []);
  860. }
  861. $named = [];
  862. $pass = $request->getParam('pass');
  863. foreach ((array)$pass as $key => $value) {
  864. if (strpos($value, $options['separator']) === false) {
  865. continue;
  866. }
  867. unset($pass[$key]);
  868. list($key, $value) = explode($options['separator'], $value, 2);
  869. if (preg_match_all('/\[([A-Za-z0-9_-]+)?\]/', $key, $matches, PREG_SET_ORDER)) {
  870. $matches = array_reverse($matches);
  871. $parts = explode('[', $key);
  872. $key = array_shift($parts);
  873. $arr = $value;
  874. foreach ($matches as $match) {
  875. if (empty($match[1])) {
  876. $arr = [$arr];
  877. } else {
  878. $arr = [
  879. $match[1] => $arr
  880. ];
  881. }
  882. }
  883. $value = $arr;
  884. }
  885. $named = array_merge_recursive($named, [$key => $value]);
  886. }
  887. return $request
  888. ->withParam('pass', $pass)
  889. ->withParam('named', $named);
  890. }
  891. /**
  892. * Create a RouteBuilder for the provided path.
  893. *
  894. * @param string $path The path to set the builder to.
  895. * @param array $options The options for the builder
  896. * @return \Cake\Routing\RouteBuilder
  897. */
  898. public static function createRouteBuilder($path, array $options = [])
  899. {
  900. $defaults = [
  901. 'routeClass' => static::defaultRouteClass(),
  902. 'extensions' => static::$_defaultExtensions,
  903. ];
  904. $options += $defaults;
  905. return new RouteBuilder(static::$_collection, $path, [], [
  906. 'routeClass' => $options['routeClass'],
  907. 'extensions' => $options['extensions'],
  908. ]);
  909. }
  910. /**
  911. * Create a routing scope.
  912. *
  913. * Routing scopes allow you to keep your routes DRY and avoid repeating
  914. * common path prefixes, and or parameter sets.
  915. *
  916. * Scoped collections will be indexed by path for faster route parsing. If you
  917. * re-open or re-use a scope the connected routes will be merged with the
  918. * existing ones.
  919. *
  920. * ### Options
  921. *
  922. * The `$params` array allows you to define options for the routing scope.
  923. * The options listed below *are not* available to be used as routing defaults
  924. *
  925. * - `routeClass` The route class to use in this scope. Defaults to
  926. * `Router::defaultRouteClass()`
  927. * - `extensions` The extensions to enable in this scope. Defaults to the globally
  928. * enabled extensions set with `Router::extensions()`
  929. *
  930. * ### Example
  931. *
  932. * ```
  933. * Router::scope('/blog', ['plugin' => 'Blog'], function ($routes) {
  934. * $routes->connect('/', ['controller' => 'Articles']);
  935. * });
  936. * ```
  937. *
  938. * The above would result in a `/blog/` route being created, with both the
  939. * plugin & controller default parameters set.
  940. *
  941. * You can use `Router::plugin()` and `Router::prefix()` as shortcuts to creating
  942. * specific kinds of scopes.
  943. *
  944. * @param string $path The path prefix for the scope. This path will be prepended
  945. * to all routes connected in the scoped collection.
  946. * @param array|callable $params An array of routing defaults to add to each connected route.
  947. * If you have no parameters, this argument can be a callable.
  948. * @param callable|null $callback The callback to invoke with the scoped collection.
  949. * @throws \InvalidArgumentException When an invalid callable is provided.
  950. * @return void
  951. */
  952. public static function scope($path, $params = [], $callback = null)
  953. {
  954. $options = [];
  955. if (is_array($params)) {
  956. $options = $params;
  957. unset($params['routeClass'], $params['extensions']);
  958. }
  959. $builder = static::createRouteBuilder('/', $options);
  960. $builder->scope($path, $params, $callback);
  961. }
  962. /**
  963. * Create prefixed routes.
  964. *
  965. * This method creates a scoped route collection that includes
  966. * relevant prefix information.
  967. *
  968. * The path parameter is used to generate the routing parameter name.
  969. * For example a path of `admin` would result in `'prefix' => 'admin'` being
  970. * applied to all connected routes.
  971. *
  972. * The prefix name will be inflected to the underscore version to create
  973. * the routing path. If you want a custom path name, use the `path` option.
  974. *
  975. * You can re-open a prefix as many times as necessary, as well as nest prefixes.
  976. * Nested prefixes will result in prefix values like `admin/api` which translates
  977. * to the `Controller\Admin\Api\` namespace.
  978. *
  979. * @param string $name The prefix name to use.
  980. * @param array|callable $params An array of routing defaults to add to each connected route.
  981. * If you have no parameters, this argument can be a callable.
  982. * @param callable|null $callback The callback to invoke that builds the prefixed routes.
  983. * @return void
  984. */
  985. public static function prefix($name, $params = [], $callback = null)
  986. {
  987. if ($callback === null) {
  988. $callback = $params;
  989. $params = [];
  990. }
  991. $name = Inflector::underscore($name);
  992. if (empty($params['path'])) {
  993. $path = '/' . $name;
  994. } else {
  995. $path = $params['path'];
  996. unset($params['path']);
  997. }
  998. $params = array_merge($params, ['prefix' => $name]);
  999. static::scope($path, $params, $callback);
  1000. }
  1001. /**
  1002. * Add plugin routes.
  1003. *
  1004. * This method creates a scoped route collection that includes
  1005. * relevant plugin information.
  1006. *
  1007. * The plugin name will be inflected to the underscore version to create
  1008. * the routing path. If you want a custom path name, use the `path` option.
  1009. *
  1010. * Routes connected in the scoped collection will have the correct path segment
  1011. * prepended, and have a matching plugin routing key set.
  1012. *
  1013. * @param string $name The plugin name to build routes for
  1014. * @param array|callable $options Either the options to use, or a callback
  1015. * @param callable|null $callback The callback to invoke that builds the plugin routes.
  1016. * Only required when $options is defined
  1017. * @return void
  1018. */
  1019. public static function plugin($name, $options = [], $callback = null)
  1020. {
  1021. if ($callback === null) {
  1022. $callback = $options;
  1023. $options = [];
  1024. }
  1025. $params = ['plugin' => $name];
  1026. if (empty($options['path'])) {
  1027. $options['path'] = '/' . Inflector::underscore($name);
  1028. }
  1029. if (isset($options['_namePrefix'])) {
  1030. $params['_namePrefix'] = $options['_namePrefix'];
  1031. }
  1032. static::scope($options['path'], $params, $callback);
  1033. }
  1034. /**
  1035. * Get the route scopes and their connected routes.
  1036. *
  1037. * @return \Cake\Routing\Route\Route[]
  1038. */
  1039. public static function routes()
  1040. {
  1041. if (!static::$initialized) {
  1042. static::_loadRoutes();
  1043. }
  1044. return static::$_collection->routes();
  1045. }
  1046. /**
  1047. * Get the RouteCollection inside the Router
  1048. *
  1049. * @return \Cake\Routing\RouteCollection
  1050. */
  1051. public static function getRouteCollection()
  1052. {
  1053. return static::$_collection;
  1054. }
  1055. /**
  1056. * Set the RouteCollection inside the Router
  1057. *
  1058. * @param RouteCollection $routeCollection route collection
  1059. * @return void
  1060. */
  1061. public static function setRouteCollection($routeCollection)
  1062. {
  1063. static::$_collection = $routeCollection;
  1064. static::$initialized = true;
  1065. }
  1066. /**
  1067. * Loads route configuration
  1068. *
  1069. * @deprecated 3.5.0 Routes will be loaded via the Application::routes() hook in 4.0.0
  1070. * @return void
  1071. */
  1072. protected static function _loadRoutes()
  1073. {
  1074. static::$initialized = true;
  1075. include CONFIG . 'routes.php';
  1076. }
  1077. }