CakePHP
  • Documentation
    • Book
    • API
    • Videos
    • Reporting Security Issues
    • Privacy Policy
    • Logos & Trademarks
  • Business Solutions
  • Swag
  • Road Trip
  • Team
  • Community
    • Community
    • Get Involved
    • Issues (GitHub)
    • Bakery
    • Featured Resources
    • Training
    • Meetups
    • My CakePHP
    • CakeFest
    • Newsletter
    • Linkedin
    • YouTube
    • Facebook
    • Twitter
    • Mastodon
    • Help & Support
    • Forum
    • Stack Overflow
    • Slack
    • Paid Support
CakePHP

C CakePHP 1.3 API

  • Overview
  • Tree
  • Deprecated
  • Version:
    • 1.3
      • 4.2
      • 4.1
      • 4.0
      • 3.9
      • 3.8
      • 3.7
      • 3.6
      • 3.5
      • 3.4
      • 3.3
      • 3.2
      • 3.1
      • 3.0
      • 2.10
      • 2.9
      • 2.8
      • 2.7
      • 2.6
      • 2.5
      • 2.4
      • 2.3
      • 2.2
      • 2.1
      • 2.0
      • 1.3
      • 1.2

Classes

  • AclBase
  • AclBehavior
  • AclComponent
  • AclNode
  • AclShell
  • Aco
  • AcoAction
  • AjaxHelper
  • ApcEngine
  • ApiShell
  • App
  • AppController
  • AppHelper
  • AppModel
  • Aro
  • AuthComponent
  • BakeShell
  • BakeTask
  • BehaviorCollection
  • Cache
  • CacheEngine
  • CacheHelper
  • CakeErrorController
  • CakeLog
  • CakeRoute
  • CakeSchema
  • CakeSession
  • CakeSocket
  • ClassRegistry
  • Component
  • Configure
  • ConnectionManager
  • ConsoleShell
  • ContainableBehavior
  • Controller
  • ControllerTask
  • CookieComponent
  • DataSource
  • DbAcl
  • DbConfigTask
  • DboMssql
  • DboMysql
  • DboMysqlBase
  • DboMysqli
  • DboOracle
  • DboPostgres
  • DboSource
  • DboSqlite
  • Debugger
  • EmailComponent
  • ErrorHandler
  • ExtractTask
  • File
  • FileEngine
  • FileLog
  • FixtureTask
  • Folder
  • FormHelper
  • Helper
  • HtmlHelper
  • HttpSocket
  • I18n
  • I18nModel
  • I18nShell
  • Inflector
  • IniAcl
  • JavascriptHelper
  • JqueryEngineHelper
  • JsBaseEngineHelper
  • JsHelper
  • L10n
  • MagicDb
  • MagicFileResource
  • MediaView
  • MemcacheEngine
  • Model
  • ModelBehavior
  • ModelTask
  • MootoolsEngineHelper
  • Multibyte
  • NumberHelper
  • Object
  • Overloadable
  • Overloadable2
  • PagesController
  • PaginatorHelper
  • Permission
  • PluginShortRoute
  • PluginTask
  • ProjectTask
  • PrototypeEngineHelper
  • RequestHandlerComponent
  • Router
  • RssHelper
  • Sanitize
  • Scaffold
  • ScaffoldView
  • SchemaShell
  • Security
  • SecurityComponent
  • SessionComponent
  • SessionHelper
  • Set
  • Shell
  • String
  • TemplateTask
  • TestSuiteShell
  • TestTask
  • TextHelper
  • ThemeView
  • TimeHelper
  • TranslateBehavior
  • TreeBehavior
  • Validation
  • View
  • ViewTask
  • XcacheEngine
  • Xml
  • XmlElement
  • XmlHelper
  • XmlManager
  • XmlNode
  • XmlTextNode

Functions

  • mb_encode_mimeheader
  • mb_stripos
  • mb_stristr
  • mb_strlen
  • mb_strpos
  • mb_strrchr
  • mb_strrichr
  • mb_strripos
  • mb_strrpos
  • mb_strstr
  • mb_strtolower
  • mb_strtoupper
  • mb_substr
  • mb_substr_count
   1: <?php
   2: /**
   3:  * Parses the request URL into controller, action, and parameters.
   4:  *
   5:  * PHP versions 4 and 5
   6:  *
   7:  * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
   8:  * Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
   9:  *
  10:  * Licensed under The MIT License
  11:  * Redistributions of files must retain the above copyright notice.
  12:  *
  13:  * @copyright     Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
  14:  * @link          http://cakephp.org CakePHP(tm) Project
  15:  * @package       cake
  16:  * @subpackage    cake.cake.libs
  17:  * @since         CakePHP(tm) v 0.2.9
  18:  * @license       MIT License (http://www.opensource.org/licenses/mit-license.php)
  19:  */
  20: 
  21: /**
  22:  * Parses the request URL into controller, action, and parameters.
  23:  *
  24:  * @package       cake
  25:  * @subpackage    cake.cake.libs
  26:  */
  27: class Router {
  28: 
  29: /**
  30:  * Array of routes connected with Router::connect()
  31:  *
  32:  * @var array
  33:  * @access public
  34:  */
  35:     var $routes = array();
  36: 
  37: /**
  38:  * List of action prefixes used in connected routes.
  39:  * Includes admin prefix
  40:  *
  41:  * @var array
  42:  * @access private
  43:  */
  44:     var $__prefixes = array();
  45: 
  46: /**
  47:  * Directive for Router to parse out file extensions for mapping to Content-types.
  48:  *
  49:  * @var boolean
  50:  * @access private
  51:  */
  52:     var $__parseExtensions = false;
  53: 
  54: /**
  55:  * List of valid extensions to parse from a URL.  If null, any extension is allowed.
  56:  *
  57:  * @var array
  58:  * @access private
  59:  */
  60:     var $__validExtensions = null;
  61: 
  62: /**
  63:  * 'Constant' regular expression definitions for named route elements
  64:  *
  65:  * @var array
  66:  * @access private
  67:  */
  68:     var $__named = array(
  69:         'Action'    => 'index|show|add|create|edit|update|remove|del|delete|view|item',
  70:         'Year'      => '[12][0-9]{3}',
  71:         'Month'     => '0[1-9]|1[012]',
  72:         'Day'       => '0[1-9]|[12][0-9]|3[01]',
  73:         'ID'        => '[0-9]+',
  74:         '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}'
  75:     );
  76: 
  77: /**
  78:  * Stores all information necessary to decide what named arguments are parsed under what conditions.
  79:  *
  80:  * @var string
  81:  * @access public
  82:  */
  83:     var $named = array(
  84:         'default' => array('page', 'fields', 'order', 'limit', 'recursive', 'sort', 'direction', 'step'),
  85:         'greedy' => true,
  86:         'separator' => ':',
  87:         'rules' => false,
  88:     );
  89: 
  90: /**
  91:  * The route matching the URL of the current request
  92:  *
  93:  * @var array
  94:  * @access private
  95:  */
  96:     var $__currentRoute = array();
  97: 
  98: /**
  99:  * Default HTTP request method => controller action map.
 100:  *
 101:  * @var array
 102:  * @access private
 103:  */
 104:     var $__resourceMap = array(
 105:         array('action' => 'index',  'method' => 'GET',      'id' => false),
 106:         array('action' => 'view',   'method' => 'GET',      'id' => true),
 107:         array('action' => 'add',    'method' => 'POST',     'id' => false),
 108:         array('action' => 'edit',   'method' => 'PUT',      'id' => true),
 109:         array('action' => 'delete', 'method' => 'DELETE',   'id' => true),
 110:         array('action' => 'edit',   'method' => 'POST',     'id' => true)
 111:     );
 112: 
 113: /**
 114:  * List of resource-mapped controllers
 115:  *
 116:  * @var array
 117:  * @access private
 118:  */
 119:     var $__resourceMapped = array();
 120: 
 121: /**
 122:  * Maintains the parameter stack for the current request
 123:  *
 124:  * @var array
 125:  * @access private
 126:  */
 127:     var $__params = array();
 128: 
 129: /**
 130:  * Maintains the path stack for the current request
 131:  *
 132:  * @var array
 133:  * @access private
 134:  */
 135:     var $__paths = array();
 136: 
 137: /**
 138:  * Keeps Router state to determine if default routes have already been connected
 139:  *
 140:  * @var boolean
 141:  * @access private
 142:  */
 143:     var $__defaultsMapped = false;
 144: 
 145: /**
 146:  * Keeps track of whether the connection of default routes is enabled or disabled.
 147:  *
 148:  * @var boolean
 149:  * @access private
 150:  */
 151:     var $__connectDefaults = true;
 152: 
 153: /**
 154:  * Constructor for Router.
 155:  * Builds __prefixes
 156:  *
 157:  * @return void
 158:  */
 159:     function Router() {
 160:         $this->__setPrefixes();
 161:     }
 162: 
 163: /**
 164:  * Sets the Routing prefixes. Includes compatibility for existing Routing.admin
 165:  * configurations.
 166:  *
 167:  * @return void
 168:  * @access private
 169:  * @todo Remove support for Routing.admin in future versions.
 170:  */
 171:     function __setPrefixes() {
 172:         $routing = Configure::read('Routing');
 173:         if (!empty($routing['admin'])) {
 174:             $this->__prefixes[] = $routing['admin'];
 175:         }
 176:         if (!empty($routing['prefixes'])) {
 177:             $this->__prefixes = array_merge($this->__prefixes, (array)$routing['prefixes']);
 178:         }
 179:     }
 180: 
 181: /**
 182:  * Gets a reference to the Router object instance
 183:  *
 184:  * @return Router Instance of the Router.
 185:  * @access public
 186:  * @static
 187:  */
 188:     function &getInstance() {
 189:         static $instance = array();
 190: 
 191:         if (!$instance) {
 192:             $instance[0] =& new Router();
 193:         }
 194:         return $instance[0];
 195:     }
 196: 
 197: /**
 198:  * Gets the named route elements for use in app/config/routes.php
 199:  *
 200:  * @return array Named route elements
 201:  * @access public
 202:  * @see Router::$__named
 203:  * @static
 204:  */
 205:     function getNamedExpressions() {
 206:         $self =& Router::getInstance();
 207:         return $self->__named;
 208:     }
 209: 
 210: /**
 211:  * Connects a new Route in the router.
 212:  *
 213:  * Routes are a way of connecting request urls to objects in your application.  At their core routes
 214:  * are a set or regular expressions that are used to match requests to destinations.
 215:  *
 216:  * Examples:
 217:  *
 218:  * `Router::connect('/:controller/:action/*');`
 219:  *
 220:  * The first parameter will be used as a controller name while the second is used as the action name.
 221:  * the '/*' syntax makes this route greedy in that it will match requests like `/posts/index` as well as requests
 222:  * like `/posts/edit/1/foo/bar`.
 223:  *
 224:  * `Router::connect('/home-page', array('controller' => 'pages', 'action' => 'display', 'home'));`
 225:  *
 226:  * The above shows the use of route parameter defaults. And providing routing parameters for a static route.
 227:  *
 228:  * {{{
 229:  * Router::connect(
 230:  *   '/:lang/:controller/:action/:id',
 231:  *   array(),
 232:  *   array('id' => '[0-9]+', 'lang' => '[a-z]{3}')
 233:  * );
 234:  * }}}
 235:  *
 236:  * Shows connecting a route with custom route parameters as well as providing patterns for those parameters.
 237:  * Patterns for routing parameters do not need capturing groups, as one will be added for each route params.
 238:  *
 239:  * $options offers three 'special' keys. `pass`, `persist` and `routeClass` have special meaning in the $options array.
 240:  *
 241:  * `pass` is used to define which of the routed parameters should be shifted into the pass array.  Adding a
 242:  * parameter to pass will remove it from the regular route array. Ex. `'pass' => array('slug')`
 243:  *
 244:  * `persist` is used to define which route parameters should be automatically included when generating
 245:  * new urls. You can override persistent parameters by redefining them in a url or remove them by
 246:  * setting the parameter to `false`.  Ex. `'persist' => array('lang')`
 247:  *
 248:  * `routeClass` is used to extend and change how individual routes parse requests and handle reverse routing,
 249:  * via a custom routing class. Ex. `'routeClass' => 'SlugRoute'`
 250:  *
 251:  * @param string $route A string describing the template of the route
 252:  * @param array $defaults An array describing the default route parameters. These parameters will be used by default
 253:  *   and can supply routing parameters that are not dynamic. See above.
 254:  * @param array $options An array matching the named elements in the route to regular expressions which that
 255:  *   element should match.  Also contains additional parameters such as which routed parameters should be
 256:  *   shifted into the passed arguments, supplying patterns for routing parameters and supplying the name of a
 257:  *   custom routing class.
 258:  * @see routes
 259:  * @return array Array of routes
 260:  * @access public
 261:  * @static
 262:  */
 263:     function connect($route, $defaults = array(), $options = array()) {
 264:         $self =& Router::getInstance();
 265: 
 266:         foreach ($self->__prefixes as $prefix) {
 267:             if (isset($defaults[$prefix])) {
 268:                 $defaults['prefix'] = $prefix;
 269:                 break;
 270:             }
 271:         }
 272:         if (isset($defaults['prefix'])) {
 273:             $self->__prefixes[] = $defaults['prefix'];
 274:             $self->__prefixes = array_keys(array_flip($self->__prefixes));
 275:         }
 276:         $defaults += array('plugin' => null);
 277:         if (empty($options['action'])) {
 278:             $defaults += array('action' => 'index');
 279:         }
 280:         $routeClass = 'CakeRoute';
 281:         if (isset($options['routeClass'])) {
 282:             $routeClass = $options['routeClass'];
 283:             unset($options['routeClass']);
 284:         }
 285:         //TODO 2.0 refactor this to use a string class name, throw exception, and then construct.
 286:         $Route =& new $routeClass($route, $defaults, $options);
 287:         if ($routeClass !== 'CakeRoute' && !is_subclass_of($Route, 'CakeRoute')) {
 288:             trigger_error(__('Route classes must extend CakeRoute', true), E_USER_WARNING);
 289:             return false;
 290:         }
 291:         $self->routes[] =& $Route;
 292:         return $self->routes;
 293:     }
 294: 
 295: /**
 296:  * Specifies what named parameters CakePHP should be parsing. The most common setups are:
 297:  *
 298:  * Do not parse any named parameters:
 299:  *
 300:  * {{{ Router::connectNamed(false); }}}
 301:  *
 302:  * Parse only default parameters used for CakePHP's pagination:
 303:  *
 304:  * {{{ Router::connectNamed(false, array('default' => true)); }}}
 305:  *
 306:  * Parse only the page parameter if its value is a number:
 307:  *
 308:  * {{{ Router::connectNamed(array('page' => '[\d]+'), array('default' => false, 'greedy' => false)); }}}
 309:  *
 310:  * Parse only the page parameter no mater what.
 311:  *
 312:  * {{{ Router::connectNamed(array('page'), array('default' => false, 'greedy' => false)); }}}
 313:  *
 314:  * Parse only the page parameter if the current action is 'index'.
 315:  *
 316:  * {{{
 317:  * Router::connectNamed(
 318:  *    array('page' => array('action' => 'index')),
 319:  *    array('default' => false, 'greedy' => false)
 320:  * );
 321:  * }}}
 322:  *
 323:  * Parse only the page parameter if the current action is 'index' and the controller is 'pages'.
 324:  *
 325:  * {{{
 326:  * Router::connectNamed(
 327:  *    array('page' => array('action' => 'index', 'controller' => 'pages')),
 328:  *    array('default' => false, 'greedy' => false)
 329:  * );
 330:  * }}}
 331:  *
 332:  * @param array $named A list of named parameters. Key value pairs are accepted where values are
 333:  *    either regex strings to match, or arrays as seen above.
 334:  * @param array $options Allows to control all settings: separator, greedy, reset, default
 335:  * @return array
 336:  * @access public
 337:  * @static
 338:  */
 339:     function connectNamed($named, $options = array()) {
 340:         $self =& Router::getInstance();
 341: 
 342:         if (isset($options['argSeparator'])) {
 343:             $self->named['separator'] = $options['argSeparator'];
 344:             unset($options['argSeparator']);
 345:         }
 346: 
 347:         if ($named === true || $named === false) {
 348:             $options = array_merge(array('default' => $named, 'reset' => true, 'greedy' => $named), $options);
 349:             $named = array();
 350:         } else {
 351:             $options = array_merge(array('default' => false, 'reset' => false, 'greedy' => true), $options);
 352:         }
 353: 
 354:         if ($options['reset'] == true || $self->named['rules'] === false) {
 355:             $self->named['rules'] = array();
 356:         }
 357: 
 358:         if ($options['default']) {
 359:             $named = array_merge($named, $self->named['default']);
 360:         }
 361: 
 362:         foreach ($named as $key => $val) {
 363:             if (is_numeric($key)) {
 364:                 $self->named['rules'][$val] = true;
 365:             } else {
 366:                 $self->named['rules'][$key] = $val;
 367:             }
 368:         }
 369:         $self->named['greedy'] = $options['greedy'];
 370:         return $self->named;
 371:     }
 372: 
 373: /**
 374:  * Tell router to connect or not connect the default routes.
 375:  *
 376:  * If default routes are disabled all automatic route generation will be disabled
 377:  * and you will need to manually configure all the routes you want.
 378:  *
 379:  * @param boolean $connect Set to true or false depending on whether you want or don't want default routes.
 380:  * @return void
 381:  * @access public
 382:  * @static
 383:  */
 384:     function defaults($connect = true) {
 385:         $self =& Router::getInstance();
 386:         $self->__connectDefaults = $connect;
 387:     }
 388: 
 389: /**
 390:  * Creates REST resource routes for the given controller(s)
 391:  *
 392:  * ### Options:
 393:  *
 394:  * - 'id' - The regular expression fragment to use when matching IDs.  By default, matches
 395:  *    integer values and UUIDs.
 396:  * - 'prefix' - URL prefix to use for the generated routes.  Defaults to '/'.
 397:  *
 398:  * @param mixed $controller A controller name or array of controller names (i.e. "Posts" or "ListItems")
 399:  * @param array $options Options to use when generating REST routes
 400:  * @return void
 401:  * @access public
 402:  * @static
 403:  */
 404:     function mapResources($controller, $options = array()) {
 405:         $self =& Router::getInstance();
 406:         $options = array_merge(array('prefix' => '/', 'id' => $self->__named['ID'] . '|' . $self->__named['UUID']), $options);
 407:         $prefix = $options['prefix'];
 408: 
 409:         foreach ((array)$controller as $ctlName) {
 410:             $urlName = Inflector::underscore($ctlName);
 411: 
 412:             foreach ($self->__resourceMap as $params) {
 413:                 extract($params);
 414:                 $url = $prefix . $urlName . (($id) ? '/:id' : '');
 415: 
 416:                 Router::connect($url,
 417:                     array('controller' => $urlName, 'action' => $action, '[method]' => $params['method']),
 418:                     array('id' => $options['id'], 'pass' => array('id'))
 419:                 );
 420:             }
 421:             $self->__resourceMapped[] = $urlName;
 422:         }
 423:     }
 424: 
 425: /**
 426:  * Returns the list of prefixes used in connected routes
 427:  *
 428:  * @return array A list of prefixes used in connected routes
 429:  * @access public
 430:  * @static
 431:  */
 432:     function prefixes() {
 433:         $self =& Router::getInstance();
 434:         return $self->__prefixes;
 435:     }
 436: 
 437: /**
 438:  * Parses given URL and returns an array of controller, action and parameters
 439:  * taken from that URL.
 440:  *
 441:  * @param string $url URL to be parsed
 442:  * @return array Parsed elements from URL
 443:  * @access public
 444:  * @static
 445:  */
 446:     function parse($url) {
 447:         $self =& Router::getInstance();
 448:         if (!$self->__defaultsMapped && $self->__connectDefaults) {
 449:             $self->__connectDefaultRoutes();
 450:         }
 451:         $out = array(
 452:             'pass' => array(),
 453:             'named' => array(),
 454:         );
 455:         $r = $ext = null;
 456: 
 457:         if (ini_get('magic_quotes_gpc') === '1') {
 458:             $url = stripslashes_deep($url);
 459:         }
 460: 
 461:         if ($url && strpos($url, '/') !== 0) {
 462:             $url = '/' . $url;
 463:         }
 464:         if (strpos($url, '?') !== false) {
 465:             $url = substr($url, 0, strpos($url, '?'));
 466:         }
 467:         extract($self->__parseExtension($url));
 468: 
 469:         for ($i = 0, $len = count($self->routes); $i < $len; $i++) {
 470:             $route =& $self->routes[$i];
 471:             if (($r = $route->parse($url)) !== false) {
 472:                 $self->__currentRoute[] =& $route;
 473: 
 474:                 $params = $route->options;
 475:                 $argOptions = array();
 476: 
 477:                 if (array_key_exists('named', $params)) {
 478:                     $argOptions['named'] = $params['named'];
 479:                     unset($params['named']);
 480:                 }
 481:                 if (array_key_exists('greedy', $params)) {
 482:                     $argOptions['greedy'] = $params['greedy'];
 483:                     unset($params['greedy']);
 484:                 }
 485:                 $out = $r;
 486: 
 487:                 if (isset($out['_args_'])) {
 488:                     $argOptions['context'] = array('action' => $out['action'], 'controller' => $out['controller']);
 489:                     $parsedArgs = $self->getArgs($out['_args_'], $argOptions);
 490:                     $out['pass'] = array_merge($out['pass'], $parsedArgs['pass']);
 491:                     $out['named'] = $parsedArgs['named'];
 492:                     unset($out['_args_']);
 493:                 }
 494: 
 495:                 if (isset($params['pass'])) {
 496:                     $j = count($params['pass']);
 497:                     while($j--) {
 498:                         if (isset($out[$params['pass'][$j]])) {
 499:                             array_unshift($out['pass'], $out[$params['pass'][$j]]);
 500:                         }
 501:                     }
 502:                 }
 503:                 break;
 504:             }
 505:         }
 506: 
 507:         if (!empty($ext) && !isset($out['url']['ext'])) {
 508:             $out['url']['ext'] = $ext;
 509:         }
 510:         return $out;
 511:     }
 512: 
 513: /**
 514:  * Parses a file extension out of a URL, if Router::parseExtensions() is enabled.
 515:  *
 516:  * @param string $url
 517:  * @return array Returns an array containing the altered URL and the parsed extension.
 518:  * @access private
 519:  */
 520:     function __parseExtension($url) {
 521:         $ext = null;
 522: 
 523:         if ($this->__parseExtensions) {
 524:             if (preg_match('/\.[0-9a-zA-Z]*$/', $url, $match) === 1) {
 525:                 $match = substr($match[0], 1);
 526:                 if (empty($this->__validExtensions)) {
 527:                     $url = substr($url, 0, strpos($url, '.' . $match));
 528:                     $ext = $match;
 529:                 } else {
 530:                     foreach ($this->__validExtensions as $name) {
 531:                         if (strcasecmp($name, $match) === 0) {
 532:                             $url = substr($url, 0, strpos($url, '.' . $name));
 533:                             $ext = $match;
 534:                             break;
 535:                         }
 536:                     }
 537:                 }
 538:             }
 539:             if (empty($ext)) {
 540:                 $ext = 'html';
 541:             }
 542:         }
 543:         return compact('ext', 'url');
 544:     }
 545: 
 546: /**
 547:  * Connects the default, built-in routes, including prefix and plugin routes. The following routes are created
 548:  * in the order below:
 549:  *
 550:  * For each of the Routing.prefixes the following routes are created. Routes containing `:plugin` are only
 551:  * created when your application has one or more plugins.
 552:  *
 553:  * - `/:prefix/:plugin` a plugin shortcut route.
 554:  * - `/:prefix/:plugin/:action/*` a plugin shortcut route.
 555:  * - `/:prefix/:plugin/:controller`
 556:  * - `/:prefix/:plugin/:controller/:action/*`
 557:  * - `/:prefix/:controller`
 558:  * - `/:prefix/:controller/:action/*`
 559:  *
 560:  * If plugins are found in your application the following routes are created:
 561:  *
 562:  * - `/:plugin` a plugin shortcut route.
 563:  * - `/:plugin/:action/*` a plugin shortcut route.
 564:  * - `/:plugin/:controller`
 565:  * - `/:plugin/:controller/:action/*`
 566:  *
 567:  * And lastly the following catch-all routes are connected.
 568:  *
 569:  * - `/:controller'
 570:  * - `/:controller/:action/*'
 571:  *
 572:  * You can disable the connection of default routes with Router::defaults().
 573:  *
 574:  * @return void
 575:  * @access private
 576:  */
 577:     function __connectDefaultRoutes() {
 578:         if ($plugins = App::objects('plugin')) {
 579:             foreach ($plugins as $key => $value) {
 580:                 $plugins[$key] = Inflector::underscore($value);
 581:             }
 582:             $pluginPattern = implode('|', $plugins);
 583:             $match = array('plugin' => $pluginPattern);
 584:             $shortParams = array('routeClass' => 'PluginShortRoute', 'plugin' => $pluginPattern);
 585: 
 586:             foreach ($this->__prefixes as $prefix) {
 587:                 $params = array('prefix' => $prefix, $prefix => true);
 588:                 $indexParams = $params + array('action' => 'index');
 589:                 $this->connect("/{$prefix}/:plugin", $indexParams, $shortParams);
 590:                 $this->connect("/{$prefix}/:plugin/:controller", $indexParams, $match);
 591:                 $this->connect("/{$prefix}/:plugin/:controller/:action/*", $params, $match);
 592:             }
 593:             $this->connect('/:plugin', array('action' => 'index'), $shortParams);
 594:             $this->connect('/:plugin/:controller', array('action' => 'index'), $match);
 595:             $this->connect('/:plugin/:controller/:action/*', array(), $match);
 596:         }
 597: 
 598:         foreach ($this->__prefixes as $prefix) {
 599:             $params = array('prefix' => $prefix, $prefix => true);
 600:             $indexParams = $params + array('action' => 'index');
 601:             $this->connect("/{$prefix}/:controller", $indexParams);
 602:             $this->connect("/{$prefix}/:controller/:action/*", $params);
 603:         }
 604:         $this->connect('/:controller', array('action' => 'index'));
 605:         $this->connect('/:controller/:action/*');
 606: 
 607:         if ($this->named['rules'] === false) {
 608:             $this->connectNamed(true);
 609:         }
 610:         $this->__defaultsMapped = true;
 611:     }
 612: 
 613: /**
 614:  * Takes parameter and path information back from the Dispatcher, sets these
 615:  * parameters as the current request parameters that are merged with url arrays
 616:  * created later in the request.
 617:  *
 618:  * @param array $params Parameters and path information
 619:  * @return void
 620:  * @access public
 621:  * @static
 622:  */
 623:     function setRequestInfo($params) {
 624:         $self =& Router::getInstance();
 625:         $defaults = array('plugin' => null, 'controller' => null, 'action' => null);
 626:         $params[0] = array_merge($defaults, (array)$params[0]);
 627:         $params[1] = array_merge($defaults, (array)$params[1]);
 628:         list($self->__params[], $self->__paths[]) = $params;
 629: 
 630:         if (count($self->__paths)) {
 631:             if (isset($self->__paths[0]['namedArgs'])) {
 632:                 foreach ($self->__paths[0]['namedArgs'] as $arg => $value) {
 633:                     $self->named['rules'][$arg] = true;
 634:                 }
 635:             }
 636:         }
 637:     }
 638: 
 639: /**
 640:  * Gets parameter information
 641:  *
 642:  * @param boolean $current Get current request parameter, useful when using requestAction
 643:  * @return array Parameter information
 644:  * @access public
 645:  * @static
 646:  */
 647:     function getParams($current = false) {
 648:         $self =& Router::getInstance();
 649:         if ($current) {
 650:             return $self->__params[count($self->__params) - 1];
 651:         }
 652:         if (isset($self->__params[0])) {
 653:             return $self->__params[0];
 654:         }
 655:         return array();
 656:     }
 657: 
 658: /**
 659:  * Gets URL parameter by name
 660:  *
 661:  * @param string $name Parameter name
 662:  * @param boolean $current Current parameter, useful when using requestAction
 663:  * @return string Parameter value
 664:  * @access public
 665:  * @static
 666:  */
 667:     function getParam($name = 'controller', $current = false) {
 668:         $params = Router::getParams($current);
 669:         if (isset($params[$name])) {
 670:             return $params[$name];
 671:         }
 672:         return null;
 673:     }
 674: 
 675: /**
 676:  * Gets path information
 677:  *
 678:  * @param boolean $current Current parameter, useful when using requestAction
 679:  * @return array
 680:  * @access public
 681:  * @static
 682:  */
 683:     function getPaths($current = false) {
 684:         $self =& Router::getInstance();
 685:         if ($current) {
 686:             return $self->__paths[count($self->__paths) - 1];
 687:         }
 688:         if (!isset($self->__paths[0])) {
 689:             return array('base' => null);
 690:         }
 691:         return $self->__paths[0];
 692:     }
 693: 
 694: /**
 695:  * Reloads default Router settings.  Resets all class variables and
 696:  * removes all connected routes.
 697:  *
 698:  * @access public
 699:  * @return void
 700:  * @static
 701:  */
 702:     function reload() {
 703:         $self =& Router::getInstance();
 704:         foreach (get_class_vars('Router') as $key => $val) {
 705:             $self->{$key} = $val;
 706:         }
 707:         $self->__setPrefixes();
 708:     }
 709: 
 710: /**
 711:  * Promote a route (by default, the last one added) to the beginning of the list
 712:  *
 713:  * @param $which A zero-based array index representing the route to move. For example,
 714:  *    if 3 routes have been added, the last route would be 2.
 715:  * @return boolean Returns false if no route exists at the position specified by $which.
 716:  * @access public
 717:  * @static
 718:  */
 719:     function promote($which = null) {
 720:         $self =& Router::getInstance();
 721:         if ($which === null) {
 722:             $which = count($self->routes) - 1;
 723:         }
 724:         if (!isset($self->routes[$which])) {
 725:             return false;
 726:         }
 727:         $route =& $self->routes[$which];
 728:         unset($self->routes[$which]);
 729:         array_unshift($self->routes, $route);
 730:         return true;
 731:     }
 732: 
 733: /**
 734:  * Finds URL for specified action.
 735:  *
 736:  * Returns an URL pointing to a combination of controller and action. Param
 737:  * $url can be:
 738:  *
 739:  * - Empty - the method will find address to actual controller/action.
 740:  * - '/' - the method will find base URL of application.
 741:  * - A combination of controller/action - the method will find url for it.
 742:  *
 743:  * There are a few 'special' parameters that can change the final URL string that is generated
 744:  *
 745:  * - `base` - Set to false to remove the base path from the generated url. If your application
 746:  *   is not in the root directory, this can be used to generate urls that are 'cake relative'.
 747:  *   cake relative urls are required when using requestAction.
 748:  * - `?` - Takes an array of query string parameters
 749:  * - `#` - Allows you to set url hash fragments.
 750:  * - `full_base` - If true the `FULL_BASE_URL` constant will be prepended to generated urls.
 751:  *
 752:  * @param mixed $url Cake-relative URL, like "/products/edit/92" or "/presidents/elect/4"
 753:  *   or an array specifying any of the following: 'controller', 'action',
 754:  *   and/or 'plugin', in addition to named arguments (keyed array elements),
 755:  *   and standard URL arguments (indexed array elements)
 756:  * @param mixed $full If (bool) true, the full base URL will be prepended to the result.
 757:  *   If an array accepts the following keys
 758:  *    - escape - used when making urls embedded in html escapes query string '&'
 759:  *    - full - if true the full base URL will be prepended.
 760:  * @return string Full translated URL with base path.
 761:  * @access public
 762:  * @static
 763:  */
 764:     function url($url = null, $full = false) {
 765:         $self =& Router::getInstance();
 766:         $defaults = $params = array('plugin' => null, 'controller' => null, 'action' => 'index');
 767: 
 768:         if (is_bool($full)) {
 769:             $escape = false;
 770:         } else {
 771:             extract($full + array('escape' => false, 'full' => false));
 772:         }
 773: 
 774:         if (!empty($self->__params)) {
 775:             if (isset($this) && !isset($this->params['requested'])) {
 776:                 $params = $self->__params[0];
 777:             } else {
 778:                 $params = end($self->__params);
 779:             }
 780:         }
 781:         $path = array('base' => null);
 782: 
 783:         if (!empty($self->__paths)) {
 784:             if (isset($this) && !isset($this->params['requested'])) {
 785:                 $path = $self->__paths[0];
 786:             } else {
 787:                 $path = end($self->__paths);
 788:             }
 789:         }
 790:         $base = $path['base'];
 791:         $extension = $output = $mapped = $q = $frag = null;
 792: 
 793:         if (is_array($url)) {
 794:             if (isset($url['base']) && $url['base'] === false) {
 795:                 $base = null;
 796:                 unset($url['base']);
 797:             }
 798:             if (isset($url['full_base']) && $url['full_base'] === true) {
 799:                 $full = true;
 800:                 unset($url['full_base']);
 801:             }
 802:             if (isset($url['?'])) {
 803:                 $q = $url['?'];
 804:                 unset($url['?']);
 805:             }
 806:             if (isset($url['#'])) {
 807:                 $frag = '#' . urlencode($url['#']);
 808:                 unset($url['#']);
 809:             }
 810:             if (empty($url['action'])) {
 811:                 if (empty($url['controller']) || $params['controller'] === $url['controller']) {
 812:                     $url['action'] = $params['action'];
 813:                 } else {
 814:                     $url['action'] = 'index';
 815:                 }
 816:             }
 817: 
 818:             $prefixExists = (array_intersect_key($url, array_flip($self->__prefixes)));
 819:             foreach ($self->__prefixes as $prefix) {
 820:                 if (!empty($params[$prefix]) && !$prefixExists) {
 821:                     $url[$prefix] = true;
 822:                 } elseif (isset($url[$prefix]) && !$url[$prefix]) {
 823:                     unset($url[$prefix]);
 824:                 }
 825:                 if (isset($url[$prefix]) && strpos($url['action'], $prefix . '_') === 0) {
 826:                     $url['action'] = substr($url['action'], strlen($prefix) + 1);
 827:                 }
 828:             }
 829: 
 830:             $url += array('controller' => $params['controller'], 'plugin' => $params['plugin']);
 831: 
 832:             if (isset($url['ext'])) {
 833:                 $extension = '.' . $url['ext'];
 834:                 unset($url['ext']);
 835:             }
 836:             $match = false;
 837: 
 838:             for ($i = 0, $len = count($self->routes); $i < $len; $i++) {
 839:                 $originalUrl = $url;
 840: 
 841:                 if (isset($self->routes[$i]->options['persist'], $params)) {
 842:                     $url = $self->routes[$i]->persistParams($url, $params);
 843:                 }
 844: 
 845:                 if ($match = $self->routes[$i]->match($url)) {
 846:                     $output = trim($match, '/');
 847:                     break;
 848:                 }
 849:                 $url = $originalUrl;
 850:             }
 851:             if ($match === false) {
 852:                 $output = $self->_handleNoRoute($url);
 853:             }
 854:             $output = str_replace('//', '/', $base . '/' . $output);
 855:         } else {
 856:             if (((strpos($url, '://')) !== false || (strpos($url, 'javascript:') === 0) || (strpos($url, 'mailto:') === 0)) || (!strncmp($url, '#', 1))) {
 857:                 return $url;
 858:             }
 859:             if (empty($url)) {
 860:                 if (!isset($path['here'])) {
 861:                     $path['here'] = '/';
 862:                 }
 863:                 $output = $path['here'];
 864:             } elseif (substr($url, 0, 1) === '/') {
 865:                 $output = $base . $url;
 866:             } else {
 867:                 $output = $base . '/';
 868:                 foreach ($self->__prefixes as $prefix) {
 869:                     if (isset($params[$prefix])) {
 870:                         $output .= $prefix . '/';
 871:                         break;
 872:                     }
 873:                 }
 874:                 if (!empty($params['plugin']) && $params['plugin'] !== $params['controller']) {
 875:                     $output .= Inflector::underscore($params['plugin']) . '/';
 876:                 }
 877:                 $output .= Inflector::underscore($params['controller']) . '/' . $url;
 878:             }
 879:             $output = str_replace('//', '/', $output);
 880:         }
 881:         if ($full && defined('FULL_BASE_URL')) {
 882:             $output = FULL_BASE_URL . $output;
 883:         }
 884:         if (!empty($extension) && substr($output, -1) === '/') {
 885:             $output = substr($output, 0, -1);
 886:         }
 887: 
 888:         return $output . $extension . $self->queryString($q, array(), $escape) . $frag;
 889:     }
 890: 
 891: /**
 892:  * A special fallback method that handles url arrays that cannot match
 893:  * any defined routes.
 894:  *
 895:  * @param array $url A url that didn't match any routes
 896:  * @return string A generated url for the array
 897:  * @access protected
 898:  * @see Router::url()
 899:  */
 900:     function _handleNoRoute($url) {
 901:         $named = $args = array();
 902:         $skip = array_merge(
 903:             array('bare', 'action', 'controller', 'plugin', 'prefix'),
 904:             $this->__prefixes
 905:         );
 906: 
 907:         $keys = array_values(array_diff(array_keys($url), $skip));
 908:         $count = count($keys);
 909: 
 910:         // Remove this once parsed URL parameters can be inserted into 'pass'
 911:         for ($i = 0; $i < $count; $i++) {
 912:             if (is_numeric($keys[$i])) {
 913:                 $args[] = $url[$keys[$i]];
 914:             } else {
 915:                 $named[$keys[$i]] = $url[$keys[$i]];
 916:             }
 917:         }
 918: 
 919:         list($args, $named) = array(Set::filter($args, true), Set::filter($named, true));
 920:         foreach ($this->__prefixes as $prefix) {
 921:             if (!empty($url[$prefix])) {
 922:                 $url['action'] = str_replace($prefix . '_', '', $url['action']);
 923:                 break;
 924:             }
 925:         }
 926: 
 927:         if (empty($named) && empty($args) && (!isset($url['action']) || $url['action'] === 'index')) {
 928:             $url['action'] = null;
 929:         }
 930: 
 931:         $urlOut = array_filter(array($url['controller'], $url['action']));
 932: 
 933:         if (isset($url['plugin'])) {
 934:             array_unshift($urlOut, $url['plugin']);
 935:         }
 936: 
 937:         foreach ($this->__prefixes as $prefix) {
 938:             if (isset($url[$prefix])) {
 939:                 array_unshift($urlOut, $prefix);
 940:                 break;
 941:             }
 942:         }
 943:         $output = implode('/', $urlOut);
 944: 
 945:         if (!empty($args)) {
 946:             $output .= '/' . implode('/', $args);
 947:         }
 948: 
 949:         if (!empty($named)) {
 950:             foreach ($named as $name => $value) {
 951:                 if (is_array($value)) {
 952:                     $flattend = Set::flatten($value, '][');
 953:                     foreach ($flattend as $namedKey => $namedValue) {
 954:                         $output .= '/' . $name . "[$namedKey]" . $this->named['separator'] . $namedValue;
 955:                     }
 956:                 } else {
 957:                     $output .= '/' . $name . $this->named['separator'] . $value;
 958:                 }
 959:             }
 960:         }
 961:         return $output;
 962:     }
 963: 
 964: /**
 965:  * Takes an array of URL parameters and separates the ones that can be used as named arguments
 966:  *
 967:  * @param array $params Associative array of URL parameters.
 968:  * @param string $controller Name of controller being routed.  Used in scoping.
 969:  * @param string $action Name of action being routed.  Used in scoping.
 970:  * @return array
 971:  * @access public
 972:  * @static
 973:  */
 974:     function getNamedElements($params, $controller = null, $action = null) {
 975:         $self =& Router::getInstance();
 976:         $named = array();
 977: 
 978:         foreach ($params as $param => $val) {
 979:             if (isset($self->named['rules'][$param])) {
 980:                 $rule = $self->named['rules'][$param];
 981:                 if (Router::matchNamed($param, $val, $rule, compact('controller', 'action'))) {
 982:                     $named[$param] = $val;
 983:                     unset($params[$param]);
 984:                 }
 985:             }
 986:         }
 987:         return array($named, $params);
 988:     }
 989: 
 990: /**
 991:  * Return true if a given named $param's $val matches a given $rule depending on $context. Currently implemented
 992:  * rule types are controller, action and match that can be combined with each other.
 993:  *
 994:  * @param string $param The name of the named parameter
 995:  * @param string $val The value of the named parameter
 996:  * @param array $rule The rule(s) to apply, can also be a match string
 997:  * @param string $context An array with additional context information (controller / action)
 998:  * @return boolean
 999:  * @access public
1000:  * @static
1001:  */
1002:     function matchNamed($param, $val, $rule, $context = array()) {
1003:         if ($rule === true || $rule === false) {
1004:             return $rule;
1005:         }
1006:         if (is_string($rule)) {
1007:             $rule = array('match' => $rule);
1008:         }
1009:         if (!is_array($rule)) {
1010:             return false;
1011:         }
1012: 
1013:         $controllerMatches = !isset($rule['controller'], $context['controller']) || in_array($context['controller'], (array)$rule['controller']);
1014:         if (!$controllerMatches) {
1015:             return false;
1016:         }
1017:         $actionMatches = !isset($rule['action'], $context['action']) || in_array($context['action'], (array)$rule['action']);
1018:         if (!$actionMatches) {
1019:             return false;
1020:         }
1021:         return (!isset($rule['match']) || preg_match('/' . $rule['match'] . '/', $val));
1022:     }
1023: 
1024: /**
1025:  * Generates a well-formed querystring from $q
1026:  *
1027:  * @param mixed $q Query string
1028:  * @param array $extra Extra querystring parameters.
1029:  * @param bool $escape Whether or not to use escaped &
1030:  * @return array
1031:  * @access public
1032:  * @static
1033:  */
1034:     function queryString($q, $extra = array(), $escape = false) {
1035:         if (empty($q) && empty($extra)) {
1036:             return null;
1037:         }
1038:         $join = '&';
1039:         if ($escape === true) {
1040:             $join = '&amp;';
1041:         }
1042:         $out = '';
1043: 
1044:         if (is_array($q)) {
1045:             $q = array_merge($extra, $q);
1046:         } else {
1047:             $out = $q;
1048:             $q = $extra;
1049:         }
1050:         $out .= http_build_query($q, null, $join);
1051:         if (isset($out[0]) && $out[0] != '?') {
1052:             $out = '?' . $out;
1053:         }
1054:         return $out;
1055:     }
1056: 
1057: /**
1058:  * Reverses a parsed parameter array into a string. Works similarly to Router::url(), but
1059:  * Since parsed URL's contain additional 'pass' and 'named' as well as 'url.url' keys.
1060:  * Those keys need to be specially handled in order to reverse a params array into a string url.
1061:  *
1062:  * This will strip out 'autoRender', 'bare', 'requested', and 'return' param names as those
1063:  * are used for CakePHP internals and should not normally be part of an output url.
1064:  *
1065:  * @param array $param The params array that needs to be reversed.
1066:  * @return string The string that is the reversed result of the array
1067:  * @access public
1068:  * @static
1069:  */
1070:     function reverse($params) {
1071:         $pass = $params['pass'];
1072:         $named = $params['named'];
1073:         if (isset($params['url'])) {
1074:             $url = $params['url'];
1075:         } else {
1076:             $url = null;
1077:         }
1078:         unset(
1079:             $params['pass'], $params['named'], $params['paging'], $params['models'], $params['url'], $url['url'],
1080:             $params['autoRender'], $params['bare'], $params['requested'], $params['return']
1081:         );
1082:         $params = array_merge($params, $pass, $named);
1083:         if (!empty($url)) {
1084:             $params['?'] = $url;
1085:         }
1086:         return Router::url($params);
1087:     }
1088: 
1089: /**
1090:  * Normalizes a URL for purposes of comparison.  Will strip the base path off
1091:  * and replace any double /'s.  It will not unify the casing and underscoring
1092:  * of the input value.
1093:  *
1094:  * @param mixed $url URL to normalize Either an array or a string url.
1095:  * @return string Normalized URL
1096:  * @access public
1097:  * @static
1098:  */
1099:     function normalize($url = '/') {
1100:         if (is_array($url)) {
1101:             $url = Router::url($url);
1102:         } elseif (preg_match('/^[a-z\-]+:\/\//', $url)) {
1103:             return $url;
1104:         }
1105:         $paths = Router::getPaths();
1106: 
1107:         if (!empty($paths['base']) && stristr($url, $paths['base'])) {
1108:             $url = preg_replace('/^' . preg_quote($paths['base'], '/') . '/', '', $url, 1);
1109:         }
1110:         $url = '/' . $url;
1111: 
1112:         while (strpos($url, '//') !== false) {
1113:             $url = str_replace('//', '/', $url);
1114:         }
1115:         $url = preg_replace('/(?:(\/$))/', '', $url);
1116: 
1117:         if (empty($url)) {
1118:             return '/';
1119:         }
1120:         return $url;
1121:     }
1122: 
1123: /**
1124:  * Returns the route matching the current request URL.
1125:  *
1126:  * @return CakeRoute Matching route object.
1127:  * @access public
1128:  * @static
1129:  */
1130:     function &requestRoute() {
1131:         $self =& Router::getInstance();
1132:         return $self->__currentRoute[0];
1133:     }
1134: 
1135: /**
1136:  * Returns the route matching the current request (useful for requestAction traces)
1137:  *
1138:  * @return CakeRoute Matching route object.
1139:  * @access public
1140:  * @static
1141:  */
1142:     function &currentRoute() {
1143:         $self =& Router::getInstance();
1144:         return $self->__currentRoute[count($self->__currentRoute) - 1];
1145:     }
1146: 
1147: /**
1148:  * Removes the plugin name from the base URL.
1149:  *
1150:  * @param string $base Base URL
1151:  * @param string $plugin Plugin name
1152:  * @return base url with plugin name removed if present
1153:  * @access public
1154:  * @static
1155:  */
1156:     function stripPlugin($base, $plugin = null) {
1157:         if ($plugin != null) {
1158:             $base = preg_replace('/(?:' . $plugin . ')/', '', $base);
1159:             $base = str_replace('//', '', $base);
1160:             $pos1 = strrpos($base, '/');
1161:             $char = strlen($base) - 1;
1162: 
1163:             if ($pos1 === $char) {
1164:                 $base = substr($base, 0, $char);
1165:             }
1166:         }
1167:         return $base;
1168:     }
1169: 
1170: /**
1171:  * Instructs the router to parse out file extensions from the URL. For example,
1172:  * http://example.com/posts.rss would yield an file extension of "rss".
1173:  * The file extension itself is made available in the controller as
1174:  * $this->params['url']['ext'], and is used by the RequestHandler component to
1175:  * automatically switch to alternate layouts and templates, and load helpers
1176:  * corresponding to the given content, i.e. RssHelper.
1177:  *
1178:  * A list of valid extension can be passed to this method, i.e. Router::parseExtensions('rss', 'xml');
1179:  * If no parameters are given, anything after the first . (dot) after the last / in the URL will be
1180:  * parsed, excluding querystring parameters (i.e. ?q=...).
1181:  *
1182:  * @access public
1183:  * @return void
1184:  * @static
1185:  */
1186:     function parseExtensions() {
1187:         $self =& Router::getInstance();
1188:         $self->__parseExtensions = true;
1189:         if (func_num_args() > 0) {
1190:             $self->__validExtensions = func_get_args();
1191:         }
1192:     }
1193: 
1194: /**
1195:  * Takes a passed params and converts it to args
1196:  *
1197:  * @param array $params
1198:  * @return array Array containing passed and named parameters
1199:  * @access public
1200:  * @static
1201:  */
1202:     function getArgs($args, $options = array()) {
1203:         $self =& Router::getInstance();
1204:         $pass = $named = array();
1205:         $args = explode('/', $args);
1206: 
1207:         $greedy = isset($options['greedy']) ? $options['greedy'] : $self->named['greedy'];
1208:         $context = array();
1209:         if (isset($options['context'])) {
1210:             $context = $options['context'];
1211:         }
1212:         $rules = $self->named['rules'];
1213:         if (isset($options['named'])) {
1214:             $greedy = isset($options['greedy']) && $options['greedy'] === true;
1215:             foreach ((array)$options['named'] as $key => $val) {
1216:                 if (is_numeric($key)) {
1217:                     $rules[$val] = true;
1218:                     continue;
1219:                 }
1220:                 $rules[$key] = $val;
1221:             }
1222:         }
1223: 
1224:         foreach ($args as $param) {
1225:             if (empty($param) && $param !== '0' && $param !== 0) {
1226:                 continue;
1227:             }
1228: 
1229:             $separatorIsPresent = strpos($param, $self->named['separator']) !== false;
1230:             if ((!isset($options['named']) || !empty($options['named'])) && $separatorIsPresent) {
1231:                 list($key, $val) = explode($self->named['separator'], $param, 2);
1232:                 $hasRule = isset($rules[$key]);
1233:                 $passIt = (!$hasRule && !$greedy) || ($hasRule && !$self->matchNamed($key, $val, $rules[$key], $context));
1234:                 if ($passIt) {
1235:                     $pass[] = $param;
1236:                 } else {
1237:                     $named[$key] = $val;
1238:                 }
1239:             } else {
1240:                 $pass[] = $param;
1241:             }
1242:         }
1243:         return compact('pass', 'named');
1244:     }
1245: }
1246: 
1247: /**
1248:  * A single Route used by the Router to connect requests to
1249:  * parameter maps.
1250:  *
1251:  * Not normally created as a standalone.  Use Router::connect() to create
1252:  * Routes for your application.
1253:  *
1254:  * @package       cake
1255:  * @subpackage    cake.cake.libs
1256:  * @since 1.3.0
1257:  * @see Router::connect()
1258:  */
1259: class CakeRoute {
1260: 
1261: /**
1262:  * An array of named segments in a Route.
1263:  * `/:controller/:action/:id` has 3 key elements
1264:  *
1265:  * @var array
1266:  * @access public
1267:  */
1268:     var $keys = array();
1269: 
1270: /**
1271:  * An array of additional parameters for the Route.
1272:  *
1273:  * @var array
1274:  * @access public
1275:  */
1276:     var $options = array();
1277: 
1278: /**
1279:  * Default parameters for a Route
1280:  *
1281:  * @var array
1282:  * @access public
1283:  */
1284:     var $defaults = array();
1285: 
1286: /**
1287:  * The routes template string.
1288:  *
1289:  * @var string
1290:  * @access public
1291:  */
1292:     var $template = null;
1293: 
1294: /**
1295:  * Is this route a greedy route?  Greedy routes have a `/*` in their
1296:  * template
1297:  *
1298:  * @var string
1299:  * @access protected
1300:  */
1301:     var $_greedy = false;
1302: 
1303: /**
1304:  * The compiled route regular expresssion
1305:  *
1306:  * @var string
1307:  * @access protected
1308:  */
1309:     var $_compiledRoute = null;
1310: 
1311: /**
1312:  * HTTP header shortcut map.  Used for evaluating header-based route expressions.
1313:  *
1314:  * @var array
1315:  * @access private
1316:  */
1317:     var $__headerMap = array(
1318:         'type' => 'content_type',
1319:         'method' => 'request_method',
1320:         'server' => 'server_name'
1321:     );
1322: 
1323: /**
1324:  * Constructor for a Route
1325:  *
1326:  * @param string $template Template string with parameter placeholders
1327:  * @param array $defaults Array of defaults for the route.
1328:  * @param string $params Array of parameters and additional options for the Route
1329:  * @return void
1330:  * @access public
1331:  */
1332:     function CakeRoute($template, $defaults = array(), $options = array()) {
1333:         $this->template = $template;
1334:         $this->defaults = (array)$defaults;
1335:         $this->options = (array)$options;
1336:     }
1337: 
1338: /**
1339:  * Check if a Route has been compiled into a regular expression.
1340:  *
1341:  * @return boolean
1342:  * @access public
1343:  */
1344:     function compiled() {
1345:         return !empty($this->_compiledRoute);
1346:     }
1347: 
1348: /**
1349:  * Compiles the route's regular expression.  Modifies defaults property so all necessary keys are set
1350:  * and populates $this->names with the named routing elements.
1351:  *
1352:  * @return array Returns a string regular expression of the compiled route.
1353:  * @access public
1354:  */
1355:     function compile() {
1356:         if ($this->compiled()) {
1357:             return $this->_compiledRoute;
1358:         }
1359:         $this->_writeRoute();
1360:         return $this->_compiledRoute;
1361:     }
1362: 
1363: /**
1364:  * Builds a route regular expression.  Uses the template, defaults and options
1365:  * properties to compile a regular expression that can be used to parse request strings.
1366:  *
1367:  * @return void
1368:  * @access protected
1369:  */
1370:     function _writeRoute() {
1371:         if (empty($this->template) || ($this->template === '/')) {
1372:             $this->_compiledRoute = '#^/*$#';
1373:             $this->keys = array();
1374:             return;
1375:         }
1376:         $route = $this->template;
1377:         $names = $routeParams = array();
1378:         $parsed = preg_quote($this->template, '#');
1379:         $parsed = str_replace('\\-', '-', $parsed);
1380: 
1381:         preg_match_all('#:([A-Za-z0-9_-]+[A-Z0-9a-z])#', $parsed, $namedElements);
1382:         foreach ($namedElements[1] as $i => $name) {
1383:             $search = '\\' . $namedElements[0][$i];
1384:             if (isset($this->options[$name])) {
1385:                 $option = null;
1386:                 if ($name !== 'plugin' && array_key_exists($name, $this->defaults)) {
1387:                     $option = '?';
1388:                 }
1389:                 $slashParam = '/\\' . $namedElements[0][$i];
1390:                 if (strpos($parsed, $slashParam) !== false) {
1391:                     $routeParams[$slashParam] = '(?:/(' . $this->options[$name] . ')' . $option . ')' . $option;
1392:                 } else {
1393:                     $routeParams[$search] = '(?:(' . $this->options[$name] . ')' . $option . ')' . $option;
1394:                 }
1395:             } else {
1396:                 $routeParams[$search] = '(?:([^/]+))';
1397:             }
1398:             $names[] = $name;
1399:         }
1400:         if (preg_match('#\/\*$#', $route, $m)) {
1401:             $parsed = preg_replace('#/\\\\\*$#', '(?:/(?P<_args_>.*))?', $parsed);
1402:             $this->_greedy = true;
1403:         }
1404:         krsort($routeParams);
1405:         $parsed = str_replace(array_keys($routeParams), array_values($routeParams), $parsed);
1406:         $this->_compiledRoute = '#^' . $parsed . '[/]*$#';
1407:         $this->keys = $names;
1408:     }
1409: 
1410: /**
1411:  * Checks to see if the given URL can be parsed by this route.
1412:  * If the route can be parsed an array of parameters will be returned; if not,
1413:  * false will be returned. String urls are parsed if they match a routes regular expression.
1414:  *
1415:  * @param string $url The url to attempt to parse.
1416:  * @return mixed Boolean false on failure, otherwise an array or parameters
1417:  * @access public
1418:  */
1419:     function parse($url) {
1420:         if (!$this->compiled()) {
1421:             $this->compile();
1422:         }
1423:         if (!preg_match($this->_compiledRoute, $url, $parsed)) {
1424:             return false;
1425:         } else {
1426:             foreach ($this->defaults as $key => $val) {
1427:                 if ($key[0] === '[' && preg_match('/^\[(\w+)\]$/', $key, $header)) {
1428:                     if (isset($this->__headerMap[$header[1]])) {
1429:                         $header = $this->__headerMap[$header[1]];
1430:                     } else {
1431:                         $header = 'http_' . $header[1];
1432:                     }
1433: 
1434:                     $val = (array)$val;
1435:                     $h = false;
1436: 
1437:                     foreach ($val as $v) {
1438:                         if (env(strtoupper($header)) === $v) {
1439:                             $h = true;
1440:                         }
1441:                     }
1442:                     if (!$h) {
1443:                         return false;
1444:                     }
1445:                 }
1446:             }
1447:             array_shift($parsed);
1448:             $route = array();
1449:             foreach ($this->keys as $i => $key) {
1450:                 if (isset($parsed[$i])) {
1451:                     $route[$key] = $parsed[$i];
1452:                 }
1453:             }
1454:             $route['pass'] = $route['named'] = array();
1455:             $route += $this->defaults;
1456:             if (isset($parsed['_args_'])) {
1457:                 $route['_args_'] = $parsed['_args_'];
1458:             }
1459:             foreach ($route as $key => $value) {
1460:                 if (is_integer($key)) {
1461:                     $route['pass'][] = $value;
1462:                     unset($route[$key]);
1463:                 }
1464:             }
1465:             return $route;
1466:         }
1467:     }
1468: 
1469: /**
1470:  * Apply persistent parameters to a url array. Persistant parameters are a special
1471:  * key used during route creation to force route parameters to persist when omitted from
1472:  * a url array.
1473:  *
1474:  * @param array $url The array to apply persistent parameters to.
1475:  * @param array $params An array of persistent values to replace persistent ones.
1476:  * @return array An array with persistent parameters applied.
1477:  * @access public
1478:  */
1479:     function persistParams($url, $params) {
1480:         foreach ($this->options['persist'] as $persistKey) {
1481:             if (array_key_exists($persistKey, $params) && !isset($url[$persistKey])) {
1482:                 $url[$persistKey] = $params[$persistKey];
1483:             }
1484:         }
1485:         return $url;
1486:     }
1487: 
1488: /**
1489:  * Attempt to match a url array.  If the url matches the route parameters and settings, then
1490:  * return a generated string url.  If the url doesn't match the route parameters, false will be returned.
1491:  * This method handles the reverse routing or conversion of url arrays into string urls.
1492:  *
1493:  * @param array $url An array of parameters to check matching with.
1494:  * @return mixed Either a string url for the parameters if they match or false.
1495:  * @access public
1496:  */
1497:     function match($url) {
1498:         if (!$this->compiled()) {
1499:             $this->compile();
1500:         }
1501:         $defaults = $this->defaults;
1502: 
1503:         if (isset($defaults['prefix'])) {
1504:             $url['prefix'] = $defaults['prefix'];
1505:         }
1506: 
1507:         //check that all the key names are in the url
1508:         $keyNames = array_flip($this->keys);
1509:         if (array_intersect_key($keyNames, $url) != $keyNames) {
1510:             return false;
1511:         }
1512: 
1513:         $diffUnfiltered = Set::diff($url, $defaults);
1514:         $diff = array();
1515: 
1516:         foreach ($diffUnfiltered as $key => $var) {
1517:             if ($var === 0 || $var === '0' || !empty($var)) {
1518:                 $diff[$key] = $var;
1519:             }
1520:         }
1521: 
1522:         //if a not a greedy route, no extra params are allowed.
1523:         if (!$this->_greedy && array_diff_key($diff, $keyNames) != array()) {
1524:             return false;
1525:         }
1526: 
1527:         //remove defaults that are also keys. They can cause match failures
1528:         foreach ($this->keys as $key) {
1529:             unset($defaults[$key]);
1530:         }
1531:         $filteredDefaults = array_filter($defaults);
1532: 
1533:         //if the difference between the url diff and defaults contains keys from defaults its not a match
1534:         if (array_intersect_key($filteredDefaults, $diffUnfiltered) !== array()) {
1535:             return false;
1536:         }
1537: 
1538:         $passedArgsAndParams = array_diff_key($diff, $filteredDefaults, $keyNames);
1539:         list($named, $params) = Router::getNamedElements($passedArgsAndParams, $url['controller'], $url['action']);
1540: 
1541:         //remove any pass params, they have numeric indexes, skip any params that are in the defaults
1542:         $pass = array();
1543:         $i = 0;
1544:         while (isset($url[$i])) {
1545:             if (!isset($diff[$i])) {
1546:                 $i++;
1547:                 continue;
1548:             }
1549:             $pass[] = $url[$i];
1550:             unset($url[$i], $params[$i]);
1551:             $i++;
1552:         }
1553: 
1554:         //still some left over parameters that weren't named or passed args, bail.
1555:         if (!empty($params)) {
1556:             return false;
1557:         }
1558: 
1559:         //check patterns for routed params
1560:         if (!empty($this->options)) {
1561:             foreach ($this->options as $key => $pattern) {
1562:                 if (array_key_exists($key, $url) && !preg_match('#^' . $pattern . '$#', $url[$key])) {
1563:                     return false;
1564:                 }
1565:             }
1566:         }
1567:         return $this->_writeUrl(array_merge($url, compact('pass', 'named')));
1568:     }
1569: 
1570: /**
1571:  * Converts a matching route array into a url string. Composes the string url using the template
1572:  * used to create the route.
1573:  *
1574:  * @param array $params The params to convert to a string url.
1575:  * @return string Composed route string.
1576:  * @access protected
1577:  */
1578:     function _writeUrl($params) {
1579:         if (isset($params['prefix'], $params['action'])) {
1580:             $params['action'] = str_replace($params['prefix'] . '_', '', $params['action']);
1581:             unset($params['prefix']);
1582:         }
1583: 
1584:         if (is_array($params['pass'])) {
1585:             $params['pass'] = implode('/', $params['pass']);
1586:         }
1587: 
1588:         $instance =& Router::getInstance();
1589:         $separator = $instance->named['separator'];
1590: 
1591:         if (!empty($params['named']) && is_array($params['named'])) {
1592:             $named = array();
1593:             foreach ($params['named'] as $key => $value) {
1594:                 $named[] = $key . $separator . $value;
1595:             }
1596:             $params['pass'] = $params['pass'] . '/' . implode('/', $named);
1597:         }
1598:         $out = $this->template;
1599: 
1600:         $search = $replace = array();
1601:         foreach ($this->keys as $key) {
1602:             $string = null;
1603:             if (isset($params[$key])) {
1604:                 $string = $params[$key];
1605:             } elseif (strpos($out, $key) != strlen($out) - strlen($key)) {
1606:                 $key .= '/';
1607:             }
1608:             $search[] = ':' . $key;
1609:             $replace[] = $string;
1610:         }
1611:         $out = str_replace($search, $replace, $out);
1612: 
1613:         if (strpos($this->template, '*')) {
1614:             $out = str_replace('*', $params['pass'], $out);
1615:         }
1616:         $out = str_replace('//', '/', $out);
1617:         return $out;
1618:     }
1619: }
1620: 
1621: /**
1622:  * Plugin short route, that copies the plugin param to the controller parameters
1623:  * It is used for supporting /:plugin routes.
1624:  *
1625:  * @package       cake
1626:  * @subpackage    cake.cake.libs
1627:  */
1628: class PluginShortRoute extends CakeRoute {
1629: 
1630: /**
1631:  * Parses a string url into an array.  If a plugin key is found, it will be copied to the
1632:  * controller parameter
1633:  *
1634:  * @param string $url The url to parse
1635:  * @return mixed false on failure, or an array of request parameters
1636:  */
1637:     function parse($url) {
1638:         $params = parent::parse($url);
1639:         if (!$params) {
1640:             return false;
1641:         }
1642:         $params['controller'] = $params['plugin'];
1643:         return $params;
1644:     }
1645: 
1646: /**
1647:  * Reverse route plugin shortcut urls.  If the plugin and controller
1648:  * are not the same the match is an auto fail.
1649:  *
1650:  * @param array $url Array of parameters to convert to a string.
1651:  * @return mixed either false or a string url.
1652:  */
1653:     function match($url) {
1654:         if (isset($url['controller']) && isset($url['plugin']) && $url['plugin'] != $url['controller']) {
1655:             return false;
1656:         }
1657:         $this->defaults['controller'] = $url['controller'];
1658:         $result = parent::match($url);
1659:         unset($this->defaults['controller']);
1660:         return $result;
1661:     }
1662: }
1663: 
OpenHub
Rackspace
Rackspace
  • Business Solutions
  • Showcase
  • Documentation
  • Book
  • API
  • Videos
  • Reporting Security Issues
  • Privacy Policy
  • Logos & Trademarks
  • Community
  • Get Involved
  • Issues (GitHub)
  • Bakery
  • Featured Resources
  • Training
  • Meetups
  • My CakePHP
  • CakeFest
  • Newsletter
  • Linkedin
  • YouTube
  • Facebook
  • Twitter
  • Mastodon
  • Help & Support
  • Forum
  • Stack Overflow
  • Slack
  • Paid Support

Generated using CakePHP API Docs