router.php

Go to the documentation of this file.
00001 <?php
00002 /* SVN FILE: $Id: router.php 8258 2009-07-28 09:49:27Z DarkAngelBGE $ */
00003 /**
00004  * Parses the request URL into controller, action, and parameters.
00005  *
00006  * Long description for file
00007  *
00008  * PHP versions 4 and 5
00009  *
00010  * CakePHP(tm) :  Rapid Development Framework (http://www.cakephp.org)
00011  * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
00012  *
00013  * Licensed under The MIT License
00014  * Redistributions of files must retain the above copyright notice.
00015  *
00016  * @filesource
00017  * @copyright     Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
00018  * @link          http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
00019  * @package       cake
00020  * @subpackage    cake.cake.libs
00021  * @since         CakePHP(tm) v 0.2.9
00022  * @version       $Revision: 8258 $
00023  * @modifiedby    $LastChangedBy: DarkAngelBGE $
00024  * @lastmodified  $Date: 2009-07-28 05:49:27 -0400 (Tue, 28 Jul 2009) $
00025  * @license       http://www.opensource.org/licenses/mit-license.php The MIT License
00026  */
00027 /**
00028  * Included libraries.
00029  *
00030  */
00031 if (!class_exists('Object')) {
00032     App::import('Core', 'Object');
00033 }
00034 /**
00035  * Parses the request URL into controller, action, and parameters.
00036  *
00037  * @package       cake
00038  * @subpackage    cake.cake.libs
00039  */
00040 class Router extends Object {
00041 /**
00042  * Array of routes
00043  *
00044  * @var array
00045  * @access public
00046  */
00047     var $routes = array();
00048 /**
00049  * Caches admin setting from Configure class
00050  *
00051  * @var array
00052  * @access private
00053  */
00054     var $__admin = null;
00055 /**
00056  * List of action prefixes used in connected routes
00057  *
00058  * @var array
00059  * @access private
00060  */
00061     var $__prefixes = array();
00062 /**
00063  * Directive for Router to parse out file extensions for mapping to Content-types.
00064  *
00065  * @var boolean
00066  * @access private
00067  */
00068     var $__parseExtensions = false;
00069 /**
00070  * List of valid extensions to parse from a URL.  If null, any extension is allowed.
00071  *
00072  * @var array
00073  * @access private
00074  */
00075     var $__validExtensions = null;
00076 /**
00077  * 'Constant' regular expression definitions for named route elements
00078  *
00079  * @var array
00080  * @access private
00081  */
00082     var $__named = array(
00083         'Action'    => 'index|show|add|create|edit|update|remove|del|delete|view|item',
00084         'Year'      => '[12][0-9]{3}',
00085         'Month'     => '0[1-9]|1[012]',
00086         'Day'       => '0[1-9]|[12][0-9]|3[01]',
00087         'ID'        => '[0-9]+',
00088         '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}'
00089     );
00090 /**
00091  * Stores all information necessary to decide what named arguments are parsed under what conditions.
00092  *
00093  * @var string
00094  * @access public
00095  */
00096     var $named = array(
00097         'default' => array('page', 'fields', 'order', 'limit', 'recursive', 'sort', 'direction', 'step'),
00098         'greedy' => true,
00099         'separator' => ':',
00100         'rules' => false,
00101     );
00102 /**
00103  * The route matching the URL of the current request
00104  *
00105  * @var array
00106  * @access private
00107  */
00108     var $__currentRoute = array();
00109 /**
00110  * HTTP header shortcut map.  Used for evaluating header-based route expressions.
00111  *
00112  * @var array
00113  * @access private
00114  */
00115     var $__headerMap = array(
00116         'type'      => 'content_type',
00117         'method'    => 'request_method',
00118         'server'    => 'server_name'
00119     );
00120 /**
00121  * Default HTTP request method => controller action map.
00122  *
00123  * @var array
00124  * @access private
00125  */
00126     var $__resourceMap = array(
00127         array('action' => 'index',  'method' => 'GET',      'id' => false),
00128         array('action' => 'view',   'method' => 'GET',      'id' => true),
00129         array('action' => 'add',    'method' => 'POST',     'id' => false),
00130         array('action' => 'edit',   'method' => 'PUT',      'id' => true),
00131         array('action' => 'delete', 'method' => 'DELETE',   'id' => true),
00132         array('action' => 'edit',   'method' => 'POST',     'id' => true)
00133     );
00134 /**
00135  * List of resource-mapped controllers
00136  *
00137  * @var array
00138  * @access private
00139  */
00140     var $__resourceMapped = array();
00141 /**
00142  * Maintains the parameter stack for the current request
00143  *
00144  * @var array
00145  * @access private
00146  */
00147     var $__params = array();
00148 /**
00149  * Maintains the path stack for the current request
00150  *
00151  * @var array
00152  * @access private
00153  */
00154     var $__paths = array();
00155 /**
00156  * Keeps Router state to determine if default routes have already been connected
00157  *
00158  * @var boolean
00159  * @access private
00160  */
00161     var $__defaultsMapped = false;
00162 /**
00163  * Gets a reference to the Router object instance
00164  *
00165  * @return object Object instance
00166  * @access public
00167  * @static
00168  */
00169     function &getInstance() {
00170         static $instance = array();
00171 
00172         if (!$instance) {
00173             $instance[0] =& new Router();
00174             $instance[0]->__admin = Configure::read('Routing.admin');
00175         }
00176         return $instance[0];
00177     }
00178 /**
00179  * Gets the named route elements for use in app/config/routes.php
00180  *
00181  * @return array Named route elements
00182  * @access public
00183  * @see Router::$__named
00184  * @static
00185  */
00186     function getNamedExpressions() {
00187         $_this =& Router::getInstance();
00188         return $_this->__named;
00189     }
00190 /**
00191  * Returns this object's routes array. Returns false if there are no routes available.
00192  *
00193  * @param string $route         An empty string, or a route string "/"
00194  * @param array $default        NULL or an array describing the default route
00195  * @param array $params         An array matching the named elements in the route to regular expressions which that element should match.
00196  * @see routes
00197  * @return array            Array of routes
00198  * @access public
00199  * @static
00200  */
00201     function connect($route, $default = array(), $params = array()) {
00202         $_this =& Router::getInstance();
00203 
00204         if (!isset($default['action'])) {
00205             $default['action'] = 'index';
00206         }
00207         if (isset($default[$_this->__admin])) {
00208             $default['prefix'] = $_this->__admin;
00209         }
00210         if (isset($default['prefix'])) {
00211             $_this->__prefixes[] = $default['prefix'];
00212             $_this->__prefixes = array_keys(array_flip($_this->__prefixes));
00213         }
00214         $_this->routes[] = array($route, $default, $params);
00215         return $_this->routes;
00216     }
00217 /**
00218  * Specifies what named parameters CakePHP should be parsing. The most common setups are:
00219  *
00220  * Do not parse any named parameters:
00221  * {{{ Router::connectNamed(false); }}}
00222  *
00223  * Parse only default parameters used for CakePHP's pagination:
00224  * {{{ Router::connectNamed(false, array('default' => true)); }}}
00225  *
00226  * Parse only the page parameter if its value is a number:
00227  * {{{ Router::connectNamed(array('page' => '[\d]+'), array('default' => false, 'greedy' => false)); }}}
00228  *
00229  * Parse only the page parameter no mater what.
00230  * {{{ Router::connectNamed(array('page'), array('default' => false, 'greedy' => false)); }}}
00231  *
00232  * Parse only the page parameter if the current action is 'index'.
00233  * {{{ Router::connectNamed(array('page' => array('action' => 'index')), array('default' => false, 'greedy' => false)); }}}
00234  *
00235  * Parse only the page parameter if the current action is 'index' and the controller is 'pages'.
00236  * {{{ Router::connectNamed(array('page' => array('action' => 'index', 'controller' => 'pages')), array('default' => false, 'greedy' => false)); }}}
00237  *
00238  * @param array $named A list of named parameters. Key value pairs are accepted where values are either regex strings to match, or arrays as seen above.
00239  * @param array $options Allows to control all settings: separator, greedy, reset, default
00240  * @return array
00241  * @access public
00242  * @static
00243  */
00244     function connectNamed($named, $options = array()) {
00245         $_this =& Router::getInstance();
00246 
00247         if (isset($options['argSeparator'])) {
00248             $_this->named['separator'] = $options['argSeparator'];
00249             unset($options['argSeparator']);
00250         }
00251 
00252         if ($named === true || $named === false) {
00253             $options = array_merge(array('default' => $named, 'reset' => true, 'greedy' => $named), $options);
00254             $named = array();
00255         }
00256         $options = array_merge(array('default' => false, 'reset' => false, 'greedy' => true), $options);
00257 
00258         if ($options['reset'] == true || $_this->named['rules'] === false) {
00259             $_this->named['rules'] = array();
00260         }
00261 
00262         if ($options['default']) {
00263             $named = array_merge($named, $_this->named['default']);
00264         }
00265 
00266         foreach ($named as $key => $val) {
00267             if (is_numeric($key)) {
00268                 $_this->named['rules'][$val] = true;
00269             } else {
00270                 $_this->named['rules'][$key] = $val;
00271             }
00272         }
00273         $_this->named['greedy'] = $options['greedy'];
00274         return $_this->named;
00275     }
00276 /**
00277  * Creates REST resource routes for the given controller(s)
00278  *
00279  * Options:
00280  *
00281  * - 'id' - The regular expression fragment to use when matching IDs.  By default, matches
00282  *    integer values and UUIDs.
00283  * - 'prefix' - URL prefix to use for the generated routes.  Defaults to '/'.
00284  *
00285  * @param mixed $controller A controller name or array of controller names (i.e. "Posts" or "ListItems")
00286  * @param array $options Options to use when generating REST routes
00287  * @return void
00288  * @access public
00289  * @static
00290  */
00291     function mapResources($controller, $options = array()) {
00292         $_this =& Router::getInstance();
00293         $options = array_merge(array('prefix' => '/', 'id' => $_this->__named['ID'] . '|' . $_this->__named['UUID']), $options);
00294         $prefix = $options['prefix'];
00295 
00296         foreach ((array)$controller as $ctlName) {
00297             $urlName = Inflector::underscore($ctlName);
00298 
00299             foreach ($_this->__resourceMap as $params) {
00300                 extract($params);
00301                 $url = $prefix . $urlName . (($id) ? '/:id' : '');
00302 
00303                 Router::connect($url,
00304                     array('controller' => $urlName, 'action' => $action, '[method]' => $params['method']),
00305                     array('id' => $options['id'], 'pass' => array('id'))
00306                 );
00307             }
00308             $_this->__resourceMapped[] = $urlName;
00309         }
00310     }
00311 /**
00312  * Builds a route regular expression
00313  *
00314  * @param string $route An empty string, or a route string "/"
00315  * @param array $default NULL or an array describing the default route
00316  * @param array $params An array matching the named elements in the route to regular expressions which that element should match.
00317  * @return array
00318  * @see routes
00319  * @access public
00320  * @static
00321  */
00322     function writeRoute($route, $default, $params) {
00323         if (empty($route) || ($route === '/')) {
00324             return array('/^[\/]*$/', array());
00325         }
00326         $names = array();
00327         $elements = explode('/', $route);
00328 
00329         foreach ($elements as $element) {
00330             if (empty($element)) {
00331                 continue;
00332             }
00333             $q = null;
00334             $element = trim($element);
00335             $namedParam = strpos($element, ':') !== false;
00336 
00337             if ($namedParam && preg_match('/^:([^:]+)$/', $element, $r)) {
00338                 if (isset($params[$r[1]])) {
00339                     if ($r[1] != 'plugin' && array_key_exists($r[1], $default)) {
00340                         $q = '?';
00341                     }
00342                     $parsed[] = '(?:/(' . $params[$r[1]] . ')' . $q . ')' . $q;
00343                 } else {
00344                     $parsed[] = '(?:/([^\/]+))?';
00345                 }
00346                 $names[] = $r[1];
00347             } elseif ($element === '*') {
00348                 $parsed[] = '(?:/(.*))?';
00349             } else if ($namedParam && preg_match_all('/(?!\\\\):([a-z_0-9]+)/i', $element, $matches)) {
00350                 $matchCount = count($matches[1]);
00351 
00352                 foreach ($matches[1] as $i => $name) {
00353                     $pos = strpos($element, ':' . $name);
00354                     $before = substr($element, 0, $pos);
00355                     $element = substr($element, $pos + strlen($name) + 1);
00356                     $after = null;
00357 
00358                     if ($i + 1 === $matchCount && $element) {
00359                         $after = preg_quote($element);
00360                     }
00361 
00362                     if ($i === 0) {
00363                         $before = '/' . $before;
00364                     }
00365                     $before = preg_quote($before, '#');
00366 
00367                     if (isset($params[$name])) {
00368                         if (isset($default[$name]) && $name != 'plugin') {
00369                             $q = '?';
00370                         }
00371                         $parsed[] = '(?:' . $before . '(' . $params[$name] . ')' . $q . $after . ')' . $q;
00372                     } else {
00373                         $parsed[] = '(?:' . $before . '([^\/]+)' . $after . ')?';
00374                     }
00375                     $names[] = $name;
00376                 }
00377             } else {
00378                 $parsed[] = '/' . $element;
00379             }
00380         }
00381         return array('#^' . join('', $parsed) . '[\/]*$#', $names);
00382     }
00383 /**
00384  * Returns the list of prefixes used in connected routes
00385  *
00386  * @return array A list of prefixes used in connected routes
00387  * @access public
00388  * @static
00389  */
00390     function prefixes() {
00391         $_this =& Router::getInstance();
00392         return $_this->__prefixes;
00393     }
00394 /**
00395  * Parses given URL and returns an array of controllers, action and parameters
00396  * taken from that URL.
00397  *
00398  * @param string $url URL to be parsed
00399  * @return array Parsed elements from URL
00400  * @access public
00401  * @static
00402  */
00403     function parse($url) {
00404         $_this =& Router::getInstance();
00405         if (!$_this->__defaultsMapped) {
00406             $_this->__connectDefaultRoutes();
00407         }
00408         $out = array('pass' => array(), 'named' => array());
00409         $r = $ext = null;
00410 
00411         if (ini_get('magic_quotes_gpc') === '1') {
00412             $url = stripslashes_deep($url);
00413         }
00414 
00415         if ($url && strpos($url, '/') !== 0) {
00416             $url = '/' . $url;
00417         }
00418         if (strpos($url, '?') !== false) {
00419             $url = substr($url, 0, strpos($url, '?'));
00420         }
00421         extract($_this->__parseExtension($url));
00422 
00423         foreach ($_this->routes as $i => $route) {
00424             if (count($route) === 3) {
00425                 $route = $_this->compile($i);
00426             }
00427 
00428             if (($r = $_this->__matchRoute($route, $url)) !== false) {
00429                 $_this->__currentRoute[] = $route;
00430                 list($route, $regexp, $names, $defaults, $params) = $route;
00431                 $argOptions = array();
00432 
00433                 if (array_key_exists('named', $params)) {
00434                     $argOptions['named'] = $params['named'];
00435                     unset($params['named']);
00436                 }
00437                 if (array_key_exists('greedy', $params)) {
00438                     $argOptions['greedy'] = $params['greedy'];
00439                     unset($params['greedy']);
00440                 }
00441                 array_shift($r);
00442 
00443                 foreach ($names as $name) {
00444                     $out[$name] = null;
00445                 }
00446                 if (is_array($defaults)) {
00447                     foreach ($defaults as $name => $value) {
00448                         if (preg_match('#[a-zA-Z_\-]#i', $name)) {
00449                             $out[$name] = $value;
00450                         } else {
00451                             $out['pass'][] = $value;
00452                         }
00453                     }
00454                 }
00455 
00456                 foreach ($r as $key => $found) {
00457                     if (empty($found) && $found != 0) {
00458                         continue;
00459                     }
00460 
00461                     if (isset($names[$key])) {
00462                         $out[$names[$key]] = $_this->stripEscape($found);
00463                     } else {
00464                         $argOptions['context'] = array('action' => $out['action'], 'controller' => $out['controller']);
00465                         extract($_this->getArgs($found, $argOptions));
00466                         $out['pass'] = array_merge($out['pass'], $pass);
00467                         $out['named'] = $named;
00468                     }
00469                 }
00470 
00471                 if (isset($params['pass'])) {
00472                     for ($j = count($params['pass']) - 1; $j > -1; $j--) {
00473                         if (isset($out[$params['pass'][$j]])) {
00474                             array_unshift($out['pass'], $out[$params['pass'][$j]]);
00475                         }
00476                     }
00477                 }
00478                 break;
00479             }
00480         }
00481 
00482         if (!empty($ext)) {
00483             $out['url']['ext'] = $ext;
00484         }
00485         return $out;
00486     }
00487 /**
00488  * Checks to see if the given URL matches the given route
00489  *
00490  * @param array $route
00491  * @param string $url
00492  * @return mixed Boolean false on failure, otherwise array
00493  * @access private
00494  */
00495     function __matchRoute($route, $url) {
00496         list($route, $regexp, $names, $defaults) = $route;
00497 
00498         if (!preg_match($regexp, $url, $r)) {
00499             return false;
00500         } else {
00501             foreach ($defaults as $key => $val) {
00502                 if ($key{0} === '[' && preg_match('/^\[(\w+)\]$/', $key, $header)) {
00503                     if (isset($this->__headerMap[$header[1]])) {
00504                         $header = $this->__headerMap[$header[1]];
00505                     } else {
00506                         $header = 'http_' . $header[1];
00507                     }
00508 
00509                     $val = (array)$val;
00510                     $h = false;
00511 
00512                     foreach ($val as $v) {
00513                         if (env(strtoupper($header)) === $v) {
00514                             $h = true;
00515                         }
00516                     }
00517                     if (!$h) {
00518                         return false;
00519                     }
00520                 }
00521             }
00522         }
00523         return $r;
00524     }
00525 /**
00526  * Compiles a route by numeric key and returns the compiled expression, replacing
00527  * the existing uncompiled route.  Do not call statically.
00528  *
00529  * @param integer $i
00530  * @return array Returns an array containing the compiled route
00531  * @access public
00532  */
00533     function compile($i) {
00534         $route = $this->routes[$i];
00535 
00536         list($pattern, $names) = $this->writeRoute($route[0], $route[1], $route[2]);
00537         $this->routes[$i] = array(
00538             $route[0], $pattern, $names,
00539             array_merge(array('plugin' => null, 'controller' => null), (array)$route[1]),
00540             $route[2]
00541         );
00542         return $this->routes[$i];
00543     }
00544 /**
00545  * Parses a file extension out of a URL, if Router::parseExtensions() is enabled.
00546  *
00547  * @param string $url
00548  * @return array Returns an array containing the altered URL and the parsed extension.
00549  * @access private
00550  */
00551     function __parseExtension($url) {
00552         $ext = null;
00553 
00554         if ($this->__parseExtensions) {
00555             if (preg_match('/\.[0-9a-zA-Z]*$/', $url, $match) === 1) {
00556                 $match = substr($match[0], 1);
00557                 if (empty($this->__validExtensions)) {
00558                     $url = substr($url, 0, strpos($url, '.' . $match));
00559                     $ext = $match;
00560                 } else {
00561                     foreach ($this->__validExtensions as $name) {
00562                         if (strcasecmp($name, $match) === 0) {
00563                             $url = substr($url, 0, strpos($url, '.' . $name));
00564                             $ext = $match;
00565                         }
00566                     }
00567                 }
00568             }
00569             if (empty($ext)) {
00570                 $ext = 'html';
00571             }
00572         }
00573         return compact('ext', 'url');
00574     }
00575 /**
00576  * Connects the default, built-in routes, including admin routes, and (deprecated) web services
00577  * routes.
00578  *
00579  * @return void
00580  * @access private
00581  */
00582     function __connectDefaultRoutes() {
00583         if ($this->__defaultsMapped) {
00584             return;
00585         }
00586 
00587         if ($this->__admin) {
00588             $params = array('prefix' => $this->__admin, $this->__admin => true);
00589         }
00590 
00591         if ($plugins = Configure::listObjects('plugin')) {
00592             foreach ($plugins as $key => $value) {
00593                 $plugins[$key] = Inflector::underscore($value);
00594             }
00595 
00596             $match = array('plugin' => implode('|', $plugins));
00597             $this->connect('/:plugin/:controller/:action/*', array(), $match);
00598 
00599             if ($this->__admin) {
00600                 $this->connect("/{$this->__admin}/:plugin/:controller", $params, $match);
00601                 $this->connect("/{$this->__admin}/:plugin/:controller/:action/*", $params, $match);
00602             }
00603         }
00604 
00605         if ($this->__admin) {
00606             $this->connect("/{$this->__admin}/:controller", $params);
00607             $this->connect("/{$this->__admin}/:controller/:action/*", $params);
00608         }
00609         $this->connect('/:controller', array('action' => 'index'));
00610         $this->connect('/:controller/:action/*');
00611 
00612         if ($this->named['rules'] === false) {
00613             $this->connectNamed(true);
00614         }
00615         $this->__defaultsMapped = true;
00616     }
00617 /**
00618  * Takes parameter and path information back from the Dispatcher
00619  *
00620  * @param array $params Parameters and path information
00621  * @return void
00622  * @access public
00623  * @static
00624  */
00625     function setRequestInfo($params) {
00626         $_this =& Router::getInstance();
00627         $defaults = array('plugin' => null, 'controller' => null, 'action' => null);
00628         $params[0] = array_merge($defaults, (array)$params[0]);
00629         $params[1] = array_merge($defaults, (array)$params[1]);
00630         list($_this->__params[], $_this->__paths[]) = $params;
00631 
00632         if (count($_this->__paths)) {
00633             if (isset($_this->__paths[0]['namedArgs'])) {
00634                 foreach ($_this->__paths[0]['namedArgs'] as $arg => $value) {
00635                     $_this->named['rules'][$arg] = true;
00636                 }
00637             }
00638         }
00639     }
00640 /**
00641  * Gets parameter information
00642  *
00643  * @param boolean $current Get current parameter (true)
00644  * @return array Parameter information
00645  * @access public
00646  * @static
00647  */
00648     function getParams($current = false) {
00649         $_this =& Router::getInstance();
00650         if ($current) {
00651             return $_this->__params[count($_this->__params) - 1];
00652         }
00653         if (isset($_this->__params[0])) {
00654             return $_this->__params[0];
00655         }
00656         return array();
00657     }
00658 /**
00659  * Gets URL parameter by name
00660  *
00661  * @param string $name Parameter name
00662  * @param boolean $current Current parameter
00663  * @return string Parameter value
00664  * @access public
00665  * @static
00666  */
00667     function getParam($name = 'controller', $current = false) {
00668         $params = Router::getParams($current);
00669         if (isset($params[$name])) {
00670             return $params[$name];
00671         }
00672         return null;
00673     }
00674 /**
00675  * Gets path information
00676  *
00677  * @param boolean $current Current parameter
00678  * @return array
00679  * @access public
00680  * @static
00681  */
00682     function getPaths($current = false) {
00683         $_this =& Router::getInstance();
00684         if ($current) {
00685             return $_this->__paths[count($_this->__paths) - 1];
00686         }
00687         if (!isset($_this->__paths[0])) {
00688             return array('base' => null);
00689         }
00690         return $_this->__paths[0];
00691     }
00692 /**
00693  * Reloads default Router settings
00694  *
00695  * @access public
00696  * @return void
00697  * @static
00698  */
00699     function reload() {
00700         $_this =& Router::getInstance();
00701         foreach (get_class_vars('Router') as $key => $val) {
00702             $_this->{$key} = $val;
00703         }
00704         $_this->__admin = Configure::read('Routing.admin');
00705     }
00706 /**
00707  * Promote a route (by default, the last one added) to the beginning of the list
00708  *
00709  * @param $which A zero-based array index representing the route to move. For example,
00710  *               if 3 routes have been added, the last route would be 2.
00711  * @return boolean Retuns false if no route exists at the position specified by $which.
00712  * @access public
00713  * @static
00714  */
00715     function promote($which = null) {
00716         $_this =& Router::getInstance();
00717         if ($which === null) {
00718             $which = count($_this->routes) - 1;
00719         }
00720         if (!isset($_this->routes[$which])) {
00721             return false;
00722         }
00723         $route = $_this->routes[$which];
00724         unset($_this->routes[$which]);
00725         array_unshift($_this->routes, $route);
00726         return true;
00727     }
00728 /**
00729  * Finds URL for specified action.
00730  *
00731  * Returns an URL pointing to a combination of controller and action. Param
00732  * $url can be:
00733  *
00734  * - Empty - the method will find adress to actuall controller/action.
00735  * - '/' - the method will find base URL of application.
00736  * - A combination of controller/action - the method will find url for it.
00737  *
00738  * @param mixed $url Cake-relative URL, like "/products/edit/92" or "/presidents/elect/4"
00739  *   or an array specifying any of the following: 'controller', 'action',
00740  *   and/or 'plugin', in addition to named arguments (keyed array elements),
00741  *   and standard URL arguments (indexed array elements)
00742  * @param mixed $full If (bool) true, the full base URL will be prepended to the result.
00743  *   If an array accepts the following keys
00744  *    - escape - used when making urls embedded in html escapes query string '&'
00745  *    - full - if true the full base URL will be prepended.
00746  * @return string Full translated URL with base path.
00747  * @access public
00748  * @static
00749  */
00750     function url($url = null, $full = false) {
00751         $_this =& Router::getInstance();
00752         $defaults = $params = array('plugin' => null, 'controller' => null, 'action' => 'index');
00753 
00754         if (is_bool($full)) {
00755             $escape = false;
00756         } else {
00757             extract(array_merge(array('escape' => false, 'full' => false), $full));
00758         }
00759 
00760         if (!empty($_this->__params)) {
00761             if (isset($this) && !isset($this->params['requested'])) {
00762                 $params = $_this->__params[0];
00763             } else {
00764                 $params = end($_this->__params);
00765             }
00766         }
00767         $path = array('base' => null);
00768 
00769         if (!empty($_this->__paths)) {
00770             if (isset($this) && !isset($this->params['requested'])) {
00771                 $path = $_this->__paths[0];
00772             } else {
00773                 $path = end($_this->__paths);
00774             }
00775         }
00776         $base = $path['base'];
00777         $extension = $output = $mapped = $q = $frag = null;
00778 
00779         if (is_array($url)) {
00780             if (isset($url['base']) && $url['base'] === false) {
00781                 $base = null;
00782                 unset($url['base']);
00783             }
00784             if (isset($url['full_base']) && $url['full_base'] === true) {
00785                 $full = true;
00786                 unset($url['full_base']);
00787             }
00788             if (isset($url['?'])) {
00789                 $q = $url['?'];
00790                 unset($url['?']);
00791             }
00792             if (isset($url['#'])) {
00793                 $frag = '#' . urlencode($url['#']);
00794                 unset($url['#']);
00795             }
00796             if (empty($url['action'])) {
00797                 if (empty($url['controller']) || $params['controller'] === $url['controller']) {
00798                     $url['action'] = $params['action'];
00799                 } else {
00800                     $url['action'] = 'index';
00801                 }
00802             }
00803             if ($_this->__admin) {
00804                 if (!isset($url[$_this->__admin]) && !empty($params[$_this->__admin])) {
00805                     $url[$_this->__admin] = true;
00806                 } elseif ($_this->__admin && isset($url[$_this->__admin]) && !$url[$_this->__admin]) {
00807                     unset($url[$_this->__admin]);
00808                 }
00809             }
00810             $plugin = false;
00811 
00812             if (array_key_exists('plugin', $url)) {
00813                 $plugin = $url['plugin'];
00814             }
00815 
00816             $url = array_merge(array('controller' => $params['controller'], 'plugin' => $params['plugin']), Set::filter($url, true));
00817 
00818             if ($plugin !== false) {
00819                 $url['plugin'] = $plugin;
00820             }
00821 
00822             if (isset($url['ext'])) {
00823                 $extension = '.' . $url['ext'];
00824                 unset($url['ext']);
00825             }
00826             $match = false;
00827 
00828             foreach ($_this->routes as $i => $route) {
00829                 if (count($route) === 3) {
00830                     $route = $_this->compile($i);
00831                 }
00832                 $originalUrl = $url;
00833 
00834                 if (isset($route[4]['persist'], $_this->__params[0])) {
00835                     $url = array_merge(array_intersect_key($params, Set::combine($route[4]['persist'], '/')), $url);
00836                 }
00837                 if ($match = $_this->mapRouteElements($route, $url)) {
00838                     $output = trim($match, '/');
00839                     $url = array();
00840                     break;
00841                 }
00842                 $url = $originalUrl;
00843             }
00844 
00845             $named = $args = array();
00846             $skip = array(
00847                 'bare', 'action', 'controller', 'plugin', 'ext', '?', '#', 'prefix', $_this->__admin
00848             );
00849 
00850             $keys = array_values(array_diff(array_keys($url), $skip));
00851             $count = count($keys);
00852 
00853             // Remove this once parsed URL parameters can be inserted into 'pass'
00854             for ($i = 0; $i < $count; $i++) {
00855                 if ($i === 0 && is_numeric($keys[$i]) && in_array('id', $keys)) {
00856                     $args[0] = $url[$keys[$i]];
00857                 } elseif (is_numeric($keys[$i]) || $keys[$i] === 'id') {
00858                     $args[] = $url[$keys[$i]];
00859                 } else {
00860                     $named[$keys[$i]] = $url[$keys[$i]];
00861                 }
00862             }
00863 
00864             if ($match === false) {
00865                 list($args, $named)  = array(Set::filter($args, true), Set::filter($named));
00866                 if (!empty($url[$_this->__admin])) {
00867                     $url['action'] = str_replace($_this->__admin . '_', '', $url['action']);
00868                 }
00869 
00870                 if (empty($named) && empty($args) && (!isset($url['action']) || $url['action'] === 'index')) {
00871                     $url['action'] = null;
00872                 }
00873 
00874                 $urlOut = Set::filter(array($url['controller'], $url['action']));
00875 
00876                 if (isset($url['plugin']) && $url['plugin'] != $url['controller']) {
00877                     array_unshift($urlOut, $url['plugin']);
00878                 }
00879 
00880                 if ($_this->__admin && isset($url[$_this->__admin])) {
00881                     array_unshift($urlOut, $_this->__admin);
00882                 }
00883                 $output = join('/', $urlOut) . '/';
00884             }
00885 
00886             if (!empty($args)) {
00887                 $args = join('/', $args);
00888                 if ($output{strlen($output) - 1} != '/') {
00889                     $args = '/'. $args;
00890                 }
00891                 $output .= $args;
00892             }
00893 
00894             if (!empty($named)) {
00895                 foreach ($named as $name => $value) {
00896                     $output .= '/' . $name . $_this->named['separator'] . $value;
00897                 }
00898             }
00899             $output = str_replace('//', '/', $base . '/' . $output);
00900         } else {
00901             if (((strpos($url, '://')) || (strpos($url, 'javascript:') === 0) || (strpos($url, 'mailto:') === 0)) || (!strncmp($url, '#', 1))) {
00902                 return $url;
00903             }
00904             if (empty($url)) {
00905                 if (!isset($path['here'])) {
00906                     $path['here'] = '/';
00907                 }
00908                 $output = $path['here'];
00909             } elseif (substr($url, 0, 1) === '/') {
00910                 $output = $base . $url;
00911             } else {
00912                 $output = $base . '/';
00913                 if ($_this->__admin && isset($params[$_this->__admin])) {
00914                     $output .= $_this->__admin . '/';
00915                 }
00916                 if (!empty($params['plugin']) && $params['plugin'] !== $params['controller']) {
00917                     $output .= Inflector::underscore($params['plugin']) . '/';
00918                 }
00919                 $output .= Inflector::underscore($params['controller']) . '/' . $url;
00920             }
00921             $output = str_replace('//', '/', $output);
00922         }
00923         if ($full && defined('FULL_BASE_URL')) {
00924             $output = FULL_BASE_URL . $output;
00925         }
00926         if (!empty($extension) && substr($output, -1) === '/') {
00927             $output = substr($output, 0, -1);
00928         }
00929 
00930         return $output . $extension . $_this->queryString($q, array(), $escape) . $frag;
00931     }
00932 /**
00933  * Maps a URL array onto a route and returns the string result, or false if no match
00934  *
00935  * @param array $route Route Route
00936  * @param array $url URL URL to map
00937  * @return mixed Result (as string) or false if no match
00938  * @access public
00939  * @static
00940  */
00941     function mapRouteElements($route, $url) {
00942         if (isset($route[3]['prefix'])) {
00943             $prefix = $route[3]['prefix'];
00944             unset($route[3]['prefix']);
00945         }
00946 
00947         $pass = array();
00948         $defaults = $route[3];
00949         $routeParams = $route[2];
00950         $params = Set::diff($url, $defaults);
00951         $urlInv = array_combine(array_values($url), array_keys($url));
00952 
00953         $i = 0;
00954         while (isset($defaults[$i])) {
00955             if (isset($urlInv[$defaults[$i]])) {
00956                 if (!in_array($defaults[$i], $url) && is_int($urlInv[$defaults[$i]])) {
00957                     return false;
00958                 }
00959                 unset($urlInv[$defaults[$i]], $defaults[$i]);
00960             } else {
00961                 return false;
00962             }
00963             $i++;
00964         }
00965 
00966         foreach ($params as $key => $value) {
00967             if (is_int($key)) {
00968                 $pass[] = $value;
00969                 unset($params[$key]);
00970             }
00971         }
00972         list($named, $params) = Router::getNamedElements($params);
00973 
00974         if (!strpos($route[0], '*') && (!empty($pass) || !empty($named))) {
00975             return false;
00976         }
00977 
00978         $urlKeys = array_keys($url);
00979         $paramsKeys = array_keys($params);
00980         $defaultsKeys = array_keys($defaults);
00981 
00982         if (!empty($params)) {
00983             if (array_diff($paramsKeys, $routeParams) != array()) {
00984                 return false;
00985             }
00986             $required = array_values(array_diff($routeParams, $urlKeys));
00987             $reqCount = count($required);
00988 
00989             for ($i = 0; $i < $reqCount; $i++) {
00990                 if (array_key_exists($required[$i], $defaults) && $defaults[$required[$i]] === null) {
00991                     unset($required[$i]);
00992                 }
00993             }
00994         }
00995         $isFilled = true;
00996 
00997         if (!empty($routeParams)) {
00998             $filled = array_intersect_key($url, array_combine($routeParams, array_keys($routeParams)));
00999             $isFilled = (array_diff($routeParams, array_keys($filled)) === array());
01000             if (!$isFilled && empty($params)) {
01001                 return false;
01002             }
01003         }
01004 
01005         if (empty($params)) {
01006             return Router::__mapRoute($route, array_merge($url, compact('pass', 'named', 'prefix')));
01007         } elseif (!empty($routeParams) && !empty($route[3])) {
01008 
01009             if (!empty($required)) {
01010                 return false;
01011             }
01012             foreach ($params as $key => $val) {
01013                 if ((!isset($url[$key]) || $url[$key] != $val) || (!isset($defaults[$key]) || $defaults[$key] != $val) && !in_array($key, $routeParams)) {
01014                     if (!isset($defaults[$key])) {
01015                         continue;
01016                     }
01017                     return false;
01018                 }
01019             }
01020         } else {
01021             if (empty($required) && $defaults['plugin'] === $url['plugin'] && $defaults['controller'] === $url['controller'] && $defaults['action'] === $url['action']) {
01022                 return Router::__mapRoute($route, array_merge($url, compact('pass', 'named', 'prefix')));
01023             }
01024             return false;
01025         }
01026 
01027         if (!empty($route[4])) {
01028             foreach ($route[4] as $key => $reg) {
01029                 if (array_key_exists($key, $url) && !preg_match('#' . $reg . '#', $url[$key])) {
01030                     return false;
01031                 }
01032             }
01033         }
01034         return Router::__mapRoute($route, array_merge($filled, compact('pass', 'named', 'prefix')));
01035     }
01036 /**
01037  * Merges URL parameters into a route string
01038  *
01039  * @param array $route Route
01040  * @param array $params Parameters
01041  * @return string Merged URL with parameters
01042  * @access private
01043  */
01044     function __mapRoute($route, $params = array()) {
01045         if (isset($params['plugin']) && isset($params['controller']) && $params['plugin'] === $params['controller']) {
01046             unset($params['controller']);
01047         }
01048 
01049         if (isset($params['prefix']) && isset($params['action'])) {
01050             $params['action'] = str_replace($params['prefix'] . '_', '', $params['action']);
01051             unset($params['prefix']);
01052         }
01053 
01054         if (isset($params['pass']) && is_array($params['pass'])) {
01055             $params['pass'] = implode('/', Set::filter($params['pass'], true));
01056         } elseif (!isset($params['pass'])) {
01057             $params['pass'] = '';
01058         }
01059 
01060         if (isset($params['named'])) {
01061             if (is_array($params['named'])) {
01062                 $count = count($params['named']);
01063                 $keys = array_keys($params['named']);
01064                 $named = array();
01065 
01066                 for ($i = 0; $i < $count; $i++) {
01067                     $named[] = $keys[$i] . $this->named['separator'] . $params['named'][$keys[$i]];
01068                 }
01069                 $params['named'] = join('/', $named);
01070             }
01071             $params['pass'] = str_replace('//', '/', $params['pass'] . '/' . $params['named']);
01072         }
01073         $out = $route[0];
01074 
01075         foreach ($route[2] as $key) {
01076             $string = null;
01077             if (isset($params[$key])) {
01078                 $string = $params[$key];
01079                 unset($params[$key]);
01080             } elseif (strpos($out, $key) != strlen($out) - strlen($key)) {
01081                 $key = $key . '/';
01082             }
01083             $out = str_replace(':' . $key, $string, $out);
01084         }
01085 
01086         if (strpos($route[0], '*')) {
01087             $out = str_replace('*', $params['pass'], $out);
01088         }
01089 
01090         return $out;
01091     }
01092 /**
01093  * Takes an array of URL parameters and separates the ones that can be used as named arguments
01094  *
01095  * @param array $params         Associative array of URL parameters.
01096  * @param string $controller    Name of controller being routed.  Used in scoping.
01097  * @param string $action        Name of action being routed.  Used in scoping.
01098  * @return array
01099  * @access public
01100  * @static
01101  */
01102     function getNamedElements($params, $controller = null, $action = null) {
01103         $_this =& Router::getInstance();
01104         $named = array();
01105 
01106         foreach ($params as $param => $val) {
01107             if (isset($_this->named['rules'][$param])) {
01108                 $rule = $_this->named['rules'][$param];
01109                 if (Router::matchNamed($param, $val, $rule, compact('controller', 'action'))) {
01110                     $named[$param] = $val;
01111                     unset($params[$param]);
01112                 }
01113             }
01114         }
01115         return array($named, $params);
01116     }
01117 /**
01118  * Return true if a given named $param's $val matches a given $rule depending on $context. Currently implemented
01119  * rule types are controller, action and match that can be combined with each other.
01120  *
01121  * @param string $param The name of the named parameter
01122  * @param string $val The value of the named parameter
01123  * @param array $rule The rule(s) to apply, can also be a match string
01124  * @param string $context An array with additional context information (controller / action)
01125  * @return boolean
01126  * @access public
01127  */
01128     function matchNamed($param, $val, $rule, $context = array()) {
01129         if ($rule === true || $rule === false) {
01130             return $rule;
01131         }
01132         if (is_string($rule)) {
01133             $rule = array('match' => $rule);
01134         }
01135         if (!is_array($rule)) {
01136             return false;
01137         }
01138 
01139         $controllerMatches = !isset($rule['controller'], $context['controller']) || in_array($context['controller'], (array)$rule['controller']);
01140         if (!$controllerMatches) {
01141             return false;
01142         }
01143         $actionMatches = !isset($rule['action'], $context['action']) || in_array($context['action'], (array)$rule['action']);
01144         if (!$actionMatches) {
01145             return false;
01146         }
01147         $valueMatches = !isset($rule['match']) || preg_match(sprintf('/%s/', $rule['match']), $val);
01148         return $valueMatches;
01149     }
01150 /**
01151  * Generates a well-formed querystring from $q
01152  *
01153  * @param mixed $q Query string
01154  * @param array $extra Extra querystring parameters.
01155  * @param bool $escape Whether or not to use escaped &
01156  * @return array
01157  * @access public
01158  * @static
01159  */
01160     function queryString($q, $extra = array(), $escape = false) {
01161         if (empty($q) && empty($extra)) {
01162             return null;
01163         }
01164         $join = '&';
01165         if ($escape === true) {
01166             $join = '&amp;';
01167         }
01168         $out = '';
01169 
01170         if (is_array($q)) {
01171             $q = array_merge($extra, $q);
01172         } else {
01173             $out = $q;
01174             $q = $extra;
01175         }
01176         $out .= http_build_query($q, null, $join);
01177         if (isset($out[0]) && $out[0] != '?') {
01178             $out = '?' . $out;
01179         }
01180         return $out;
01181     }
01182 /**
01183  * Normalizes a URL for purposes of comparison
01184  *
01185  * @param mixed $url URL to normalize
01186  * @return string Normalized URL
01187  * @access public
01188  */
01189     function normalize($url = '/') {
01190         if (is_array($url)) {
01191             $url = Router::url($url);
01192         } elseif (preg_match('/^[a-z\-]+:\/\//', $url)) {
01193             return $url;
01194         }
01195         $paths = Router::getPaths();
01196 
01197         if (!empty($paths['base']) && stristr($url, $paths['base'])) {
01198             $url = preg_replace('/^' . preg_quote($paths['base'], '/') . '/', '', $url, 1);
01199         }
01200         $url = '/' . $url;
01201 
01202         while (strpos($url, '//') !== false) {
01203             $url = str_replace('//', '/', $url);
01204         }
01205         $url = preg_replace('/(?:(\/$))/', '', $url);
01206 
01207         if (empty($url)) {
01208             return '/';
01209         }
01210         return $url;
01211     }
01212 /**
01213  * Returns the route matching the current request URL.
01214  *
01215  * @return array Matching route
01216  * @access public
01217  * @static
01218  */
01219     function requestRoute() {
01220         $_this =& Router::getInstance();
01221         return $_this->__currentRoute[0];
01222     }
01223 /**
01224  * Returns the route matching the current request (useful for requestAction traces)
01225  *
01226  * @return array Matching route
01227  * @access public
01228  * @static
01229  */
01230     function currentRoute() {
01231         $_this =& Router::getInstance();
01232         return $_this->__currentRoute[count($_this->__currentRoute) - 1];
01233     }
01234 /**
01235  * Removes the plugin name from the base URL.
01236  *
01237  * @param string $base Base URL
01238  * @param string $plugin Plugin name
01239  * @return base url with plugin name removed if present
01240  * @access public
01241  * @static
01242  */
01243     function stripPlugin($base, $plugin = null) {
01244         if ($plugin != null) {
01245             $base = preg_replace('/(?:' . $plugin . ')/', '', $base);
01246             $base = str_replace('//', '', $base);
01247             $pos1 = strrpos($base, '/');
01248             $char = strlen($base) - 1;
01249 
01250             if ($pos1 === $char) {
01251                 $base = substr($base, 0, $char);
01252             }
01253         }
01254         return $base;
01255     }
01256 /**
01257  * Strip escape characters from parameter values.
01258  *
01259  * @param mixed $param Either an array, or a string
01260  * @return mixed Array or string escaped
01261  * @access public
01262  * @static
01263  */
01264     function stripEscape($param) {
01265         $_this =& Router::getInstance();
01266         if (!is_array($param) || empty($param)) {
01267             if (is_bool($param)) {
01268                 return $param;
01269             }
01270 
01271             return preg_replace('/^(?:[\\t ]*(?:-!)+)/', '', $param);
01272         }
01273 
01274         foreach ($param as $key => $value) {
01275             if (is_string($value)) {
01276                 $return[$key] = preg_replace('/^(?:[\\t ]*(?:-!)+)/', '', $value);
01277             } else {
01278                 foreach ($value as $array => $string) {
01279                     $return[$key][$array] = $_this->stripEscape($string);
01280                 }
01281             }
01282         }
01283         return $return;
01284     }
01285 /**
01286  * Instructs the router to parse out file extensions from the URL. For example,
01287  * http://example.com/posts.rss would yield an file extension of "rss".
01288  * The file extension itself is made available in the controller as
01289  * $this->params['url']['ext'], and is used by the RequestHandler component to
01290  * automatically switch to alternate layouts and templates, and load helpers
01291  * corresponding to the given content, i.e. RssHelper.
01292  *
01293  * A list of valid extension can be passed to this method, i.e. Router::parseExtensions('rss', 'xml');
01294  * If no parameters are given, anything after the first . (dot) after the last / in the URL will be
01295  * parsed, excluding querystring parameters (i.e. ?q=...).
01296  *
01297  * @access public
01298  * @return void
01299  * @static
01300  */
01301     function parseExtensions() {
01302         $_this =& Router::getInstance();
01303         $_this->__parseExtensions = true;
01304         if (func_num_args() > 0) {
01305             $_this->__validExtensions = func_get_args();
01306         }
01307     }
01308 /**
01309  * Takes an passed params and converts it to args
01310  *
01311  * @param array $params
01312  * @return array Array containing passed and named parameters
01313  * @access public
01314  * @static
01315  */
01316     function getArgs($args, $options = array()) {
01317         $_this =& Router::getInstance();
01318         $pass = $named = array();
01319         $args = explode('/', $args);
01320 
01321         $greedy = $_this->named['greedy'];
01322         if (isset($options['greedy'])) {
01323             $greedy = $options['greedy'];
01324         }
01325         $context = array();
01326         if (isset($options['context'])) {
01327             $context = $options['context'];
01328         }
01329         $rules = $_this->named['rules'];
01330         if (isset($options['named'])) {
01331             $greedy = isset($options['greedy']) && $options['greedy'] === true;
01332             foreach ((array)$options['named'] as $key => $val) {
01333                 if (is_numeric($key)) {
01334                     $rules[$val] = true;
01335                     continue;
01336                 }
01337                 $rules[$key] = $val;
01338             }
01339         }
01340 
01341         foreach ($args as $param) {
01342             if (empty($param) && $param !== '0' && $param !== 0) {
01343                 continue;
01344             }
01345             $param = $_this->stripEscape($param);
01346 
01347             $separatorIsPresent = strpos($param, $_this->named['separator']) !== false;
01348             if ((!isset($options['named']) || !empty($options['named'])) && $separatorIsPresent) {
01349                 list($key, $val) = explode($_this->named['separator'], $param, 2);
01350                 $hasRule = isset($rules[$key]);
01351                 $passIt = (!$hasRule && !$greedy) || ($hasRule && !Router::matchNamed($key, $val, $rules[$key], $context));
01352                 if ($passIt) {
01353                     $pass[] = $param;
01354                 } else {
01355                     $named[$key] = $val;
01356                 }
01357             } else {
01358                 $pass[] = $param;
01359             }
01360         }
01361         return compact('pass', 'named');
01362     }
01363 }
01364 ?>

Generated on Sun Nov 22 00:30:54 2009 for CakePHP 1.2.x.x (v1.2.4.8284) by doxygen 1.4.7