summaryrefslogtreecommitdiff
path: root/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing
diff options
context:
space:
mode:
authorLudovic Pouzenc <ludovic@pouzenc.fr>2012-08-02 11:09:40 +0000
committerLudovic Pouzenc <ludovic@pouzenc.fr>2012-08-02 11:09:40 +0000
commit6dfd5d507d9863f987b30b0a5ab4268aea2ed875 (patch)
tree9c3de66b4aaa1e6bfa3c6442d807ec70bc479d11 /poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing
parentf4792ba1a7785220fef5adb1cb3e7ce8ac40152f (diff)
download2012-php-weave-6dfd5d507d9863f987b30b0a5ab4268aea2ed875.tar.gz
2012-php-weave-6dfd5d507d9863f987b30b0a5ab4268aea2ed875.tar.bz2
2012-php-weave-6dfd5d507d9863f987b30b0a5ab4268aea2ed875.zip
J'étais parti sur un download pourri de Cake. Les gars on abusé sur GitHub.
git-svn-id: file:///var/svn/2012-php-weave/trunk@7 d972a294-176a-4cf9-8ea1-fcd5b0c30f5c
Diffstat (limited to 'poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing')
-rw-r--r--poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Dispatcher.php281
-rw-r--r--poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/DispatcherFilter.php86
-rw-r--r--poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Filter/AssetDispatcher.php159
-rw-r--r--poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Filter/CacheDispatcher.php67
-rw-r--r--poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Route/CakeRoute.php532
-rw-r--r--poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Route/PluginShortRoute.php58
-rw-r--r--poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Route/RedirectRoute.php117
-rw-r--r--poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Router.php1141
8 files changed, 2441 insertions, 0 deletions
diff --git a/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Dispatcher.php b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Dispatcher.php
new file mode 100644
index 0000000..bf3e395
--- /dev/null
+++ b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Dispatcher.php
@@ -0,0 +1,281 @@
+<?php
+/**
+ * Dispatcher takes the URL information, parses it for parameters and
+ * tells the involved controllers what to do.
+ *
+ * This is the heart of Cake's operation.
+ *
+ * PHP 5
+ *
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link http://cakephp.org CakePHP(tm) Project
+ * @package Cake.Routing
+ * @since CakePHP(tm) v 0.2.9
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+
+App::uses('Router', 'Routing');
+App::uses('CakeRequest', 'Network');
+App::uses('CakeResponse', 'Network');
+App::uses('Controller', 'Controller');
+App::uses('Scaffold', 'Controller');
+App::uses('View', 'View');
+App::uses('Debugger', 'Utility');
+App::uses('CakeEvent', 'Event');
+App::uses('CakeEventManager', 'Event');
+App::uses('CakeEventListener', 'Event');
+
+/**
+ * Dispatcher converts Requests into controller actions. It uses the dispatched Request
+ * to locate and load the correct controller. If found, the requested action is called on
+ * the controller.
+ *
+ * @package Cake.Routing
+ */
+class Dispatcher implements CakeEventListener {
+
+/**
+ * Event manager, used to handle dispatcher filters
+ *
+ * @var CakeEventManager
+ */
+ protected $_eventManager;
+
+/**
+ * Constructor.
+ *
+ * @param string $base The base directory for the application. Writes `App.base` to Configure.
+ */
+ public function __construct($base = false) {
+ if ($base !== false) {
+ Configure::write('App.base', $base);
+ }
+ }
+
+/**
+ * Returns the CakeEventManager instance or creates one if none was
+ * creted. Attaches the default listeners and filters
+ *
+ * @return CakeEventManager
+ */
+ public function getEventManager() {
+ if (!$this->_eventManager) {
+ $this->_eventManager = new CakeEventManager();
+ $this->_eventManager->attach($this);
+ $this->_attachFilters($this->_eventManager);
+ }
+ return $this->_eventManager;
+ }
+
+/**
+ * Returns the list of events this object listents to.
+ *
+ * @return array
+ */
+ public function implementedEvents() {
+ return array('Dispatcher.beforeDispatch' => 'parseParams');
+ }
+
+/**
+ * Attaches all event listeners for this dispatcher instance. Loads the
+ * dispatcher filters from the configured locations.
+ *
+ * @param CakeEventManager $manager
+ * @return void
+ * @throws MissingDispatcherFilterException
+ */
+ protected function _attachFilters($manager) {
+ $filters = Configure::read('Dispatcher.filters');
+ if (empty($filters)) {
+ return;
+ }
+
+ foreach ($filters as $filter) {
+ if (is_string($filter)) {
+ $filter = array('callable' => $filter);
+ }
+ if (is_string($filter['callable'])) {
+ list($plugin, $callable) = pluginSplit($filter['callable'], true);
+ App::uses($callable, $plugin . 'Routing/Filter');
+ if (!class_exists($callable)) {
+ throw new MissingDispatcherFilterException($callable);
+ }
+ $manager->attach(new $callable);
+ } else {
+ $on = strtolower($filter['on']);
+ $options = array();
+ if (isset($filter['priority'])) {
+ $options = array('priority' => $filter['priority']);
+ }
+ $manager->attach($filter['callable'], 'Dispatcher.' . $on . 'Dispatch', $options);
+ }
+ }
+ }
+
+/**
+ * Dispatches and invokes given Request, handing over control to the involved controller. If the controller is set
+ * to autoRender, via Controller::$autoRender, then Dispatcher will render the view.
+ *
+ * Actions in CakePHP can be any public method on a controller, that is not declared in Controller. If you
+ * want controller methods to be public and in-accessible by URL, then prefix them with a `_`.
+ * For example `public function _loadPosts() { }` would not be accessible via URL. Private and protected methods
+ * are also not accessible via URL.
+ *
+ * If no controller of given name can be found, invoke() will throw an exception.
+ * If the controller is found, and the action is not found an exception will be thrown.
+ *
+ * @param CakeRequest $request Request object to dispatch.
+ * @param CakeResponse $response Response object to put the results of the dispatch into.
+ * @param array $additionalParams Settings array ("bare", "return") which is melded with the GET and POST params
+ * @return string|void if `$request['return']` is set then it returns response body, null otherwise
+ * @throws MissingControllerException When the controller is missing.
+ */
+ public function dispatch(CakeRequest $request, CakeResponse $response, $additionalParams = array()) {
+ $beforeEvent = new CakeEvent('Dispatcher.beforeDispatch', $this, compact('request', 'response', 'additionalParams'));
+ $this->getEventManager()->dispatch($beforeEvent);
+
+ $request = $beforeEvent->data['request'];
+ if ($beforeEvent->result instanceof CakeResponse) {
+ if (isset($request->params['return'])) {
+ return $response->body();
+ }
+ $response->send();
+ return;
+ }
+
+ $controller = $this->_getController($request, $response);
+
+ if (!($controller instanceof Controller)) {
+ throw new MissingControllerException(array(
+ 'class' => Inflector::camelize($request->params['controller']) . 'Controller',
+ 'plugin' => empty($request->params['plugin']) ? null : Inflector::camelize($request->params['plugin'])
+ ));
+ }
+
+ $response = $this->_invoke($controller, $request, $response);
+ if (isset($request->params['return'])) {
+ return $response->body();
+ }
+
+ $afterEvent = new CakeEvent('Dispatcher.afterDispatch', $this, compact('request', 'response'));
+ $this->getEventManager()->dispatch($afterEvent);
+ $afterEvent->data['response']->send();
+ }
+
+/**
+ * Initializes the components and models a controller will be using.
+ * Triggers the controller action, and invokes the rendering if Controller::$autoRender is true and echo's the output.
+ * Otherwise the return value of the controller action are returned.
+ *
+ * @param Controller $controller Controller to invoke
+ * @param CakeRequest $request The request object to invoke the controller for.
+ * @param CakeResponse $response The response object to receive the output
+ * @return CakeResponse te resulting response object
+ */
+ protected function _invoke(Controller $controller, CakeRequest $request, CakeResponse $response) {
+ $controller->constructClasses();
+ $controller->startupProcess();
+
+ $render = true;
+ $result = $controller->invokeAction($request);
+ if ($result instanceof CakeResponse) {
+ $render = false;
+ $response = $result;
+ }
+
+ if ($render && $controller->autoRender) {
+ $response = $controller->render();
+ } elseif ($response->body() === null) {
+ $response->body($result);
+ }
+ $controller->shutdownProcess();
+
+ return $response;
+ }
+
+/**
+ * Applies Routing and additionalParameters to the request to be dispatched.
+ * If Routes have not been loaded they will be loaded, and app/Config/routes.php will be run.
+ *
+ * @param CakeEvent $event containing the request, response and additional params
+ * @return void
+ */
+ public function parseParams($event) {
+ $request = $event->data['request'];
+ Router::setRequestInfo($request);
+ if (count(Router::$routes) == 0) {
+ $namedExpressions = Router::getNamedExpressions();
+ extract($namedExpressions);
+ $this->_loadRoutes();
+ }
+
+ $params = Router::parse($request->url);
+ $request->addParams($params);
+
+ if (!empty($event->data['additionalParams'])) {
+ $request->addParams($event->data['additionalParams']);
+ }
+ }
+
+/**
+ * Get controller to use, either plugin controller or application controller
+ *
+ * @param CakeRequest $request Request object
+ * @param CakeResponse $response Response for the controller.
+ * @return mixed name of controller if not loaded, or object if loaded
+ */
+ protected function _getController($request, $response) {
+ $ctrlClass = $this->_loadController($request);
+ if (!$ctrlClass) {
+ return false;
+ }
+ $reflection = new ReflectionClass($ctrlClass);
+ if ($reflection->isAbstract() || $reflection->isInterface()) {
+ return false;
+ }
+ return $reflection->newInstance($request, $response);
+ }
+
+/**
+ * Load controller and return controller classname
+ *
+ * @param CakeRequest $request
+ * @return string|bool Name of controller class name
+ */
+ protected function _loadController($request) {
+ $pluginName = $pluginPath = $controller = null;
+ if (!empty($request->params['plugin'])) {
+ $pluginName = $controller = Inflector::camelize($request->params['plugin']);
+ $pluginPath = $pluginName . '.';
+ }
+ if (!empty($request->params['controller'])) {
+ $controller = Inflector::camelize($request->params['controller']);
+ }
+ if ($pluginPath . $controller) {
+ $class = $controller . 'Controller';
+ App::uses('AppController', 'Controller');
+ App::uses($pluginName . 'AppController', $pluginPath . 'Controller');
+ App::uses($class, $pluginPath . 'Controller');
+ if (class_exists($class)) {
+ return $class;
+ }
+ }
+ return false;
+ }
+
+/**
+ * Loads route configuration
+ *
+ * @return void
+ */
+ protected function _loadRoutes() {
+ include APP . 'Config' . DS . 'routes.php';
+ }
+
+}
diff --git a/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/DispatcherFilter.php b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/DispatcherFilter.php
new file mode 100644
index 0000000..cf6f4fa
--- /dev/null
+++ b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/DispatcherFilter.php
@@ -0,0 +1,86 @@
+<?php
+/**
+ *
+ * PHP 5
+ *
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link http://cakephp.org CakePHP(tm) Project
+ * @package Cake.Routing
+ * @since CakePHP(tm) v 2.2
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+
+App::uses('CakeEventListener', 'Event');
+
+/**
+ * This abstract class represents a filter to be applied to a dispatcher cycle. It acts as as
+ * event listener with the ability to alter the request or response as needed before it is handled
+ * by a controller or after the response body has already been built.
+ *
+ * @package Cake.Routing
+ */
+abstract class DispatcherFilter implements CakeEventListener {
+
+/**
+ * Default priority for all methods in this filter
+ *
+ * @var int
+ **/
+ public $priority = 10;
+
+/**
+ * Returns the list of events this filter listens to.
+ * Dispatcher notifies 2 different events `Dispatcher.before` and `Dispatcher.after`.
+ * By default this class will attach `preDispatch` and `postDispatch` method respectively.
+ *
+ * Override this method at will to only listen to the events you are interested in.
+ *
+ * @return array
+ **/
+ public function implementedEvents() {
+ return array(
+ 'Dispatcher.beforeDispatch' => array('callable' => 'beforeDispatch', 'priority' => $this->priority),
+ 'Dispatcher.afterDispatch' => array('callable' => 'afterDispatch', 'priority' => $this->priority),
+ );
+ }
+
+/**
+ * Method called before the controller is instantiated and called to ser a request.
+ * If used with default priority, it will be called after the Router has parsed the
+ * url and set the routing params into the request object.
+ *
+ * If a CakeResponse object instance is returned, it will be served at the end of the
+ * event cycle, not calling any controller as a result. This will also have the effect of
+ * not calling the after event in the dispatcher.
+ *
+ * If false is returned, the event will be stopped and no more listeners will be notified.
+ * Alternatively you can call `$event->stopPropagation()` to acheive the same result.
+ *
+ * @param CakeEvent $event container object having the `request`, `response` and `additionalParams`
+ * keys in the data property.
+ * @return CakeResponse|boolean
+ **/
+ public function beforeDispatch($event) {
+ }
+
+/**
+ * Method called after the controller served a request and generated a response.
+ * It is posible to alter the response object at this point as it is not sent to the
+ * client yet.
+ *
+ * If false is returned, the event will be stopped and no more listeners will be notified.
+ * Alternatively you can call `$event->stopPropagation()` to acheive the same result.
+ *
+ * @param CakeEvent $event container object having the `request` and `response`
+ * keys in the data property.
+ * @return mixed boolean to stop the event dispatching or null to continue
+ **/
+ public function afterDispatch($event) {
+ }
+}
diff --git a/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Filter/AssetDispatcher.php b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Filter/AssetDispatcher.php
new file mode 100644
index 0000000..11840ba
--- /dev/null
+++ b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Filter/AssetDispatcher.php
@@ -0,0 +1,159 @@
+<?php
+/**
+ *
+ * PHP 5
+ *
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link http://cakephp.org CakePHP(tm) Project
+ * @package Cake.Routing
+ * @since CakePHP(tm) v 2.2
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+
+App::uses('DispatcherFilter', 'Routing');
+
+/**
+ * Filters a request and tests whether it is a file in the webroot folder or not and
+ * serves the file to the client if appropriate.
+ *
+ * @package Cake.Routing.Filter
+ */
+class AssetDispatcher extends DispatcherFilter {
+
+/**
+ * Default priority for all methods in this filter
+ * This filter should run before the request gets parsed by router
+ *
+ * @var int
+ **/
+ public $priority = 9;
+
+/**
+ * Checks if a requested asset exists and sends it to the browser
+ *
+ * @param CakeEvent $event containing the request and response object
+ * @return CakeResponse if the client is requesting a recognized asset, null otherwise
+ */
+ public function beforeDispatch($event) {
+ $url = $event->data['request']->url;
+ $response = $event->data['response'];
+
+ if (strpos($url, '..') !== false || strpos($url, '.') === false) {
+ return;
+ }
+
+ if ($result = $this->_filterAsset($event)) {
+ $event->stopPropagation();
+ return $result;
+ }
+
+ $pathSegments = explode('.', $url);
+ $ext = array_pop($pathSegments);
+ $parts = explode('/', $url);
+ $assetFile = null;
+
+ if ($parts[0] === 'theme') {
+ $themeName = $parts[1];
+ unset($parts[0], $parts[1]);
+ $fileFragment = urldecode(implode(DS, $parts));
+ $path = App::themePath($themeName) . 'webroot' . DS;
+ if (file_exists($path . $fileFragment)) {
+ $assetFile = $path . $fileFragment;
+ }
+ } else {
+ $plugin = Inflector::camelize($parts[0]);
+ if (CakePlugin::loaded($plugin)) {
+ unset($parts[0]);
+ $fileFragment = urldecode(implode(DS, $parts));
+ $pluginWebroot = CakePlugin::path($plugin) . 'webroot' . DS;
+ if (file_exists($pluginWebroot . $fileFragment)) {
+ $assetFile = $pluginWebroot . $fileFragment;
+ }
+ }
+ }
+
+ if ($assetFile !== null) {
+ $event->stopPropagation();
+ $response->modified(filemtime($assetFile));
+ if (!$response->checkNotModified($event->data['request'])) {
+ $this->_deliverAsset($response, $assetFile, $ext);
+ }
+ return $response;
+ }
+ }
+
+/**
+ * Checks if the client is requeting a filtered asset and runs the corresponding
+ * filter if any is configured
+ *
+ * @param CakeEvent $event containing the request and response object
+ * @return CakeResponse if the client is requesting a recognized asset, null otherwise
+ */
+ protected function _filterAsset($event) {
+ $url = $event->data['request']->url;
+ $response = $event->data['response'];
+ $filters = Configure::read('Asset.filter');
+ $isCss = (
+ strpos($url, 'ccss/') === 0 ||
+ preg_match('#^(theme/([^/]+)/ccss/)|(([^/]+)(?<!css)/ccss)/#i', $url)
+ );
+ $isJs = (
+ strpos($url, 'cjs/') === 0 ||
+ preg_match('#^/((theme/[^/]+)/cjs/)|(([^/]+)(?<!js)/cjs)/#i', $url)
+ );
+
+ if (($isCss && empty($filters['css'])) || ($isJs && empty($filters['js']))) {
+ $response->statusCode(404);
+ return $response;
+ } elseif ($isCss) {
+ include WWW_ROOT . DS . $filters['css'];
+ return $response;
+ } elseif ($isJs) {
+ include WWW_ROOT . DS . $filters['js'];
+ return $response;
+ }
+ }
+
+/**
+ * Sends an asset file to the client
+ *
+ * @param CakeResponse $response The response object to use.
+ * @param string $assetFile Path to the asset file in the file system
+ * @param string $ext The extension of the file to determine its mime type
+ * @return void
+ */
+ protected function _deliverAsset(CakeResponse $response, $assetFile, $ext) {
+ ob_start();
+ $compressionEnabled = Configure::read('Asset.compress') && $response->compress();
+ if ($response->type($ext) == $ext) {
+ $contentType = 'application/octet-stream';
+ $agent = env('HTTP_USER_AGENT');
+ if (preg_match('%Opera(/| )([0-9].[0-9]{1,2})%', $agent) || preg_match('/MSIE ([0-9].[0-9]{1,2})/', $agent)) {
+ $contentType = 'application/octetstream';
+ }
+ $response->type($contentType);
+ }
+ if (!$compressionEnabled) {
+ $response->header('Content-Length', filesize($assetFile));
+ }
+ $response->cache(filemtime($assetFile));
+ $response->send();
+ ob_clean();
+ if ($ext === 'css' || $ext === 'js') {
+ include $assetFile;
+ } else {
+ readfile($assetFile);
+ }
+
+ if ($compressionEnabled) {
+ ob_end_flush();
+ }
+ }
+
+} \ No newline at end of file
diff --git a/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Filter/CacheDispatcher.php b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Filter/CacheDispatcher.php
new file mode 100644
index 0000000..908d77a
--- /dev/null
+++ b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Filter/CacheDispatcher.php
@@ -0,0 +1,67 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link http://cakephp.org CakePHP(tm) Project
+ * @since CakePHP(tm) v 2.2
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+
+App::uses('DispatcherFilter', 'Routing');
+
+/**
+ * This filter will check wheter the response was previously cached in the file system
+ * and served it back to the client if appropriate.
+ *
+ * @package Cake.Routing.Filter
+ */
+class CacheDispatcher extends DispatcherFilter {
+
+/**
+ * Default priority for all methods in this filter
+ * This filter should run before the request gets parsed by router
+ *
+ * @var int
+ */
+ public $priority = 9;
+
+/**
+ * Checks whether the response was cached and set the body accordingly.
+ *
+ * @param CakeEvent $event containing the request and response object
+ * @return CakeResponse with cached content if found, null otherwise
+ */
+ public function beforeDispatch($event) {
+ if (Configure::read('Cache.check') !== true) {
+ return;
+ }
+
+ $path = $event->data['request']->here();
+ if ($path == '/') {
+ $path = 'home';
+ }
+ $path = strtolower(Inflector::slug($path));
+
+ $filename = CACHE . 'views' . DS . $path . '.php';
+
+ if (!file_exists($filename)) {
+ $filename = CACHE . 'views' . DS . $path . '_index.php';
+ }
+ if (file_exists($filename)) {
+ $controller = null;
+ $view = new View($controller);
+ $result = $view->renderCache($filename, microtime(true));
+ if ($result !== false) {
+ $event->stopPropagation();
+ $event->data['response']->body($result);
+ return $event->data['response'];
+ }
+ }
+ }
+
+}
diff --git a/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Route/CakeRoute.php b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Route/CakeRoute.php
new file mode 100644
index 0000000..8fab212
--- /dev/null
+++ b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Route/CakeRoute.php
@@ -0,0 +1,532 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link http://cakephp.org CakePHP(tm) Project
+ * @since CakePHP(tm) v 1.3
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+
+App::uses('Set', 'Utility');
+
+/**
+ * A single Route used by the Router to connect requests to
+ * parameter maps.
+ *
+ * Not normally created as a standalone. Use Router::connect() to create
+ * Routes for your application.
+ *
+ * @package Cake.Routing.Route
+ */
+class CakeRoute {
+
+/**
+ * An array of named segments in a Route.
+ * `/:controller/:action/:id` has 3 key elements
+ *
+ * @var array
+ */
+ public $keys = array();
+
+/**
+ * An array of additional parameters for the Route.
+ *
+ * @var array
+ */
+ public $options = array();
+
+/**
+ * Default parameters for a Route
+ *
+ * @var array
+ */
+ public $defaults = array();
+
+/**
+ * The routes template string.
+ *
+ * @var string
+ */
+ public $template = null;
+
+/**
+ * Is this route a greedy route? Greedy routes have a `/*` in their
+ * template
+ *
+ * @var string
+ */
+ protected $_greedy = false;
+
+/**
+ * The compiled route regular expression
+ *
+ * @var string
+ */
+ protected $_compiledRoute = null;
+
+/**
+ * HTTP header shortcut map. Used for evaluating header-based route expressions.
+ *
+ * @var array
+ */
+ protected $_headerMap = array(
+ 'type' => 'content_type',
+ 'method' => 'request_method',
+ 'server' => 'server_name'
+ );
+
+/**
+ * Constructor for a Route
+ *
+ * @param string $template Template string with parameter placeholders
+ * @param array $defaults Array of defaults for the route.
+ * @param array $options Array of additional options for the Route
+ */
+ public function __construct($template, $defaults = array(), $options = array()) {
+ $this->template = $template;
+ $this->defaults = (array)$defaults;
+ $this->options = (array)$options;
+ }
+
+/**
+ * Check if a Route has been compiled into a regular expression.
+ *
+ * @return boolean
+ */
+ public function compiled() {
+ return !empty($this->_compiledRoute);
+ }
+
+/**
+ * Compiles the route's regular expression. Modifies defaults property so all necessary keys are set
+ * and populates $this->names with the named routing elements.
+ *
+ * @return array Returns a string regular expression of the compiled route.
+ */
+ public function compile() {
+ if ($this->compiled()) {
+ return $this->_compiledRoute;
+ }
+ $this->_writeRoute();
+ return $this->_compiledRoute;
+ }
+
+/**
+ * Builds a route regular expression. Uses the template, defaults and options
+ * properties to compile a regular expression that can be used to parse request strings.
+ *
+ * @return void
+ */
+ protected function _writeRoute() {
+ if (empty($this->template) || ($this->template === '/')) {
+ $this->_compiledRoute = '#^/*$#';
+ $this->keys = array();
+ return;
+ }
+ $route = $this->template;
+ $names = $routeParams = array();
+ $parsed = preg_quote($this->template, '#');
+
+ preg_match_all('#:([A-Za-z0-9_-]+[A-Z0-9a-z])#', $route, $namedElements);
+ foreach ($namedElements[1] as $i => $name) {
+ $search = '\\' . $namedElements[0][$i];
+ if (isset($this->options[$name])) {
+ $option = null;
+ if ($name !== 'plugin' && array_key_exists($name, $this->defaults)) {
+ $option = '?';
+ }
+ $slashParam = '/\\' . $namedElements[0][$i];
+ if (strpos($parsed, $slashParam) !== false) {
+ $routeParams[$slashParam] = '(?:/(?P<' . $name . '>' . $this->options[$name] . ')' . $option . ')' . $option;
+ } else {
+ $routeParams[$search] = '(?:(?P<' . $name . '>' . $this->options[$name] . ')' . $option . ')' . $option;
+ }
+ } else {
+ $routeParams[$search] = '(?:(?P<' . $name . '>[^/]+))';
+ }
+ $names[] = $name;
+ }
+ if (preg_match('#\/\*\*$#', $route)) {
+ $parsed = preg_replace('#/\\\\\*\\\\\*$#', '(?:/(?P<_trailing_>.*))?', $parsed);
+ $this->_greedy = true;
+ } elseif (preg_match('#\/\*$#', $route)) {
+ $parsed = preg_replace('#/\\\\\*$#', '(?:/(?P<_args_>.*))?', $parsed);
+ $this->_greedy = true;
+ }
+ krsort($routeParams);
+ $parsed = str_replace(array_keys($routeParams), array_values($routeParams), $parsed);
+ $this->_compiledRoute = '#^' . $parsed . '[/]*$#';
+ $this->keys = $names;
+
+ //remove defaults that are also keys. They can cause match failures
+ foreach ($this->keys as $key) {
+ unset($this->defaults[$key]);
+ }
+ }
+
+/**
+ * Checks to see if the given URL can be parsed by this route.
+ * If the route can be parsed an array of parameters will be returned; if not
+ * false will be returned. String urls are parsed if they match a routes regular expression.
+ *
+ * @param string $url The url to attempt to parse.
+ * @return mixed Boolean false on failure, otherwise an array or parameters
+ */
+ public function parse($url) {
+ if (!$this->compiled()) {
+ $this->compile();
+ }
+ if (!preg_match($this->_compiledRoute, $url, $route)) {
+ return false;
+ }
+ foreach ($this->defaults as $key => $val) {
+ $key = (string)$key;
+ if ($key[0] === '[' && preg_match('/^\[(\w+)\]$/', $key, $header)) {
+ if (isset($this->_headerMap[$header[1]])) {
+ $header = $this->_headerMap[$header[1]];
+ } else {
+ $header = 'http_' . $header[1];
+ }
+ $header = strtoupper($header);
+
+ $val = (array)$val;
+ $h = false;
+
+ foreach ($val as $v) {
+ if (env($header) === $v) {
+ $h = true;
+ }
+ }
+ if (!$h) {
+ return false;
+ }
+ }
+ }
+ array_shift($route);
+ $count = count($this->keys);
+ for ($i = 0; $i <= $count; $i++) {
+ unset($route[$i]);
+ }
+ $route['pass'] = $route['named'] = array();
+
+ // Assign defaults, set passed args to pass
+ foreach ($this->defaults as $key => $value) {
+ if (isset($route[$key])) {
+ continue;
+ }
+ if (is_integer($key)) {
+ $route['pass'][] = $value;
+ continue;
+ }
+ $route[$key] = $value;
+ }
+
+ foreach ($this->keys as $key) {
+ if (isset($route[$key])) {
+ $route[$key] = rawurldecode($route[$key]);
+ }
+ }
+
+ if (isset($route['_args_'])) {
+ list($pass, $named) = $this->_parseArgs($route['_args_'], $route);
+ $route['pass'] = array_merge($route['pass'], $pass);
+ $route['named'] = $named;
+ unset($route['_args_']);
+ }
+
+ if (isset($route['_trailing_'])) {
+ $route['pass'][] = rawurldecode($route['_trailing_']);
+ unset($route['_trailing_']);
+ }
+
+ // restructure 'pass' key route params
+ if (isset($this->options['pass'])) {
+ $j = count($this->options['pass']);
+ while ($j--) {
+ if (isset($route[$this->options['pass'][$j]])) {
+ array_unshift($route['pass'], $route[$this->options['pass'][$j]]);
+ }
+ }
+ }
+ return $route;
+ }
+
+/**
+ * Parse passed and Named parameters into a list of passed args, and a hash of named parameters.
+ * The local and global configuration for named parameters will be used.
+ *
+ * @param string $args A string with the passed & named params. eg. /1/page:2
+ * @param string $context The current route context, which should contain controller/action keys.
+ * @return array Array of ($pass, $named)
+ */
+ protected function _parseArgs($args, $context) {
+ $pass = $named = array();
+ $args = explode('/', $args);
+
+ $namedConfig = Router::namedConfig();
+ $greedy = $namedConfig['greedyNamed'];
+ $rules = $namedConfig['rules'];
+ if (!empty($this->options['named'])) {
+ $greedy = isset($this->options['greedyNamed']) && $this->options['greedyNamed'] === true;
+ foreach ((array)$this->options['named'] as $key => $val) {
+ if (is_numeric($key)) {
+ $rules[$val] = true;
+ continue;
+ }
+ $rules[$key] = $val;
+ }
+ }
+
+ foreach ($args as $param) {
+ if (empty($param) && $param !== '0' && $param !== 0) {
+ continue;
+ }
+
+ $separatorIsPresent = strpos($param, $namedConfig['separator']) !== false;
+ if ((!isset($this->options['named']) || !empty($this->options['named'])) && $separatorIsPresent) {
+ list($key, $val) = explode($namedConfig['separator'], $param, 2);
+ $key = rawurldecode($key);
+ $val = rawurldecode($val);
+ $hasRule = isset($rules[$key]);
+ $passIt = (!$hasRule && !$greedy) || ($hasRule && !$this->_matchNamed($val, $rules[$key], $context));
+ if ($passIt) {
+ $pass[] = rawurldecode($param);
+ } else {
+ if (preg_match_all('/\[([A-Za-z0-9_-]+)?\]/', $key, $matches, PREG_SET_ORDER)) {
+ $matches = array_reverse($matches);
+ $parts = explode('[', $key);
+ $key = array_shift($parts);
+ $arr = $val;
+ foreach ($matches as $match) {
+ if (empty($match[1])) {
+ $arr = array($arr);
+ } else {
+ $arr = array(
+ $match[1] => $arr
+ );
+ }
+ }
+ $val = $arr;
+ }
+ $named = array_merge_recursive($named, array($key => $val));
+ }
+ } else {
+ $pass[] = rawurldecode($param);
+ }
+ }
+ return array($pass, $named);
+ }
+
+/**
+ * Return true if a given named $param's $val matches a given $rule depending on $context. Currently implemented
+ * rule types are controller, action and match that can be combined with each other.
+ *
+ * @param string $val The value of the named parameter
+ * @param array $rule The rule(s) to apply, can also be a match string
+ * @param string $context An array with additional context information (controller / action)
+ * @return boolean
+ */
+ protected function _matchNamed($val, $rule, $context) {
+ if ($rule === true || $rule === false) {
+ return $rule;
+ }
+ if (is_string($rule)) {
+ $rule = array('match' => $rule);
+ }
+ if (!is_array($rule)) {
+ return false;
+ }
+
+ $controllerMatches = (
+ !isset($rule['controller'], $context['controller']) ||
+ in_array($context['controller'], (array)$rule['controller'])
+ );
+ if (!$controllerMatches) {
+ return false;
+ }
+ $actionMatches = (
+ !isset($rule['action'], $context['action']) ||
+ in_array($context['action'], (array)$rule['action'])
+ );
+ if (!$actionMatches) {
+ return false;
+ }
+ return (!isset($rule['match']) || preg_match('/' . $rule['match'] . '/', $val));
+ }
+
+/**
+ * Apply persistent parameters to a url array. Persistent parameters are a special
+ * key used during route creation to force route parameters to persist when omitted from
+ * a url array.
+ *
+ * @param array $url The array to apply persistent parameters to.
+ * @param array $params An array of persistent values to replace persistent ones.
+ * @return array An array with persistent parameters applied.
+ */
+ public function persistParams($url, $params) {
+ foreach ($this->options['persist'] as $persistKey) {
+ if (array_key_exists($persistKey, $params) && !isset($url[$persistKey])) {
+ $url[$persistKey] = $params[$persistKey];
+ }
+ }
+ return $url;
+ }
+
+/**
+ * Attempt to match a url array. If the url matches the route parameters and settings, then
+ * return a generated string url. If the url doesn't match the route parameters, false will be returned.
+ * This method handles the reverse routing or conversion of url arrays into string urls.
+ *
+ * @param array $url An array of parameters to check matching with.
+ * @return mixed Either a string url for the parameters if they match or false.
+ */
+ public function match($url) {
+ if (!$this->compiled()) {
+ $this->compile();
+ }
+ $defaults = $this->defaults;
+
+ if (isset($defaults['prefix'])) {
+ $url['prefix'] = $defaults['prefix'];
+ }
+
+ //check that all the key names are in the url
+ $keyNames = array_flip($this->keys);
+ if (array_intersect_key($keyNames, $url) !== $keyNames) {
+ return false;
+ }
+
+ // Missing defaults is a fail.
+ if (array_diff_key($defaults, $url) !== array()) {
+ return false;
+ }
+
+ $namedConfig = Router::namedConfig();
+ $prefixes = Router::prefixes();
+ $greedyNamed = $namedConfig['greedyNamed'];
+ $allowedNamedParams = $namedConfig['rules'];
+
+ $named = $pass = array();
+
+ foreach ($url as $key => $value) {
+
+ // keys that exist in the defaults and have different values is a match failure.
+ $defaultExists = array_key_exists($key, $defaults);
+ if ($defaultExists && $defaults[$key] != $value) {
+ return false;
+ } elseif ($defaultExists) {
+ continue;
+ }
+
+ // If the key is a routed key, its not different yet.
+ if (array_key_exists($key, $keyNames)) {
+ continue;
+ }
+
+ // pull out passed args
+ $numeric = is_numeric($key);
+ if ($numeric && isset($defaults[$key]) && $defaults[$key] == $value) {
+ continue;
+ } elseif ($numeric) {
+ $pass[] = $value;
+ unset($url[$key]);
+ continue;
+ }
+
+ // pull out named params if named params are greedy or a rule exists.
+ if (
+ ($greedyNamed || isset($allowedNamedParams[$key])) &&
+ ($value !== false && $value !== null) &&
+ (!in_array($key, $prefixes))
+ ) {
+ $named[$key] = $value;
+ continue;
+ }
+
+ // keys that don't exist are different.
+ if (!$defaultExists && !empty($value)) {
+ return false;
+ }
+ }
+
+ //if a not a greedy route, no extra params are allowed.
+ if (!$this->_greedy && (!empty($pass) || !empty($named))) {
+ return false;
+ }
+
+ //check patterns for routed params
+ if (!empty($this->options)) {
+ foreach ($this->options as $key => $pattern) {
+ if (array_key_exists($key, $url) && !preg_match('#^' . $pattern . '$#', $url[$key])) {
+ return false;
+ }
+ }
+ }
+ return $this->_writeUrl(array_merge($url, compact('pass', 'named')));
+ }
+
+/**
+ * Converts a matching route array into a url string. Composes the string url using the template
+ * used to create the route.
+ *
+ * @param array $params The params to convert to a string url.
+ * @return string Composed route string.
+ */
+ protected function _writeUrl($params) {
+ if (isset($params['prefix'])) {
+ $prefixed = $params['prefix'] . '_';
+ }
+ if (isset($prefixed, $params['action']) && strpos($params['action'], $prefixed) === 0) {
+ $params['action'] = substr($params['action'], strlen($prefixed) * -1);
+ unset($params['prefix']);
+ }
+
+ if (is_array($params['pass'])) {
+ $params['pass'] = implode('/', array_map('rawurlencode', $params['pass']));
+ }
+
+ $namedConfig = Router::namedConfig();
+ $separator = $namedConfig['separator'];
+
+ if (!empty($params['named']) && is_array($params['named'])) {
+ $named = array();
+ foreach ($params['named'] as $key => $value) {
+ if (is_array($value)) {
+ $flat = Hash::flatten($value, '%5D%5B');
+ foreach ($flat as $namedKey => $namedValue) {
+ $named[] = $key . "%5B{$namedKey}%5D" . $separator . rawurlencode($namedValue);
+ }
+ } else {
+ $named[] = $key . $separator . rawurlencode($value);
+ }
+ }
+ $params['pass'] = $params['pass'] . '/' . implode('/', $named);
+ }
+ $out = $this->template;
+
+ $search = $replace = array();
+ foreach ($this->keys as $key) {
+ $string = null;
+ if (isset($params[$key])) {
+ $string = $params[$key];
+ } elseif (strpos($out, $key) != strlen($out) - strlen($key)) {
+ $key .= '/';
+ }
+ $search[] = ':' . $key;
+ $replace[] = $string;
+ }
+ $out = str_replace($search, $replace, $out);
+
+ if (strpos($this->template, '*')) {
+ $out = str_replace('*', $params['pass'], $out);
+ }
+ $out = str_replace('//', '/', $out);
+ return $out;
+ }
+
+}
diff --git a/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Route/PluginShortRoute.php b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Route/PluginShortRoute.php
new file mode 100644
index 0000000..2edd944
--- /dev/null
+++ b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Route/PluginShortRoute.php
@@ -0,0 +1,58 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link http://cakephp.org CakePHP(tm) Project
+ * @since CakePHP(tm) v 1.3
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+
+App::uses('CakeRoute', 'Routing/Route');
+
+/**
+ * Plugin short route, that copies the plugin param to the controller parameters
+ * It is used for supporting /:plugin routes.
+ *
+ * @package Cake.Routing.Route
+ */
+class PluginShortRoute extends CakeRoute {
+
+/**
+ * Parses a string url into an array. If a plugin key is found, it will be copied to the
+ * controller parameter
+ *
+ * @param string $url The url to parse
+ * @return mixed false on failure, or an array of request parameters
+ */
+ public function parse($url) {
+ $params = parent::parse($url);
+ if (!$params) {
+ return false;
+ }
+ $params['controller'] = $params['plugin'];
+ return $params;
+ }
+
+/**
+ * Reverse route plugin shortcut urls. If the plugin and controller
+ * are not the same the match is an auto fail.
+ *
+ * @param array $url Array of parameters to convert to a string.
+ * @return mixed either false or a string url.
+ */
+ public function match($url) {
+ if (isset($url['controller']) && isset($url['plugin']) && $url['plugin'] != $url['controller']) {
+ return false;
+ }
+ $this->defaults['controller'] = $url['controller'];
+ $result = parent::match($url);
+ unset($this->defaults['controller']);
+ return $result;
+ }
+
+}
diff --git a/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Route/RedirectRoute.php b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Route/RedirectRoute.php
new file mode 100644
index 0000000..bfb0f06
--- /dev/null
+++ b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Route/RedirectRoute.php
@@ -0,0 +1,117 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link http://cakephp.org CakePHP(tm) Project
+ * @package Cake.Routing.Route
+ * @since CakePHP(tm) v 2.0
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+
+App::uses('CakeResponse', 'Network');
+App::uses('CakeRoute', 'Routing/Route');
+
+/**
+ * Redirect route will perform an immediate redirect. Redirect routes
+ * are useful when you want to have Routing layer redirects occur in your
+ * application, for when URLs move.
+ *
+ * @package Cake.Routing.Route
+ */
+class RedirectRoute extends CakeRoute {
+
+/**
+ * A CakeResponse object
+ *
+ * @var CakeResponse
+ */
+ public $response = null;
+
+/**
+ * The location to redirect to. Either a string or a cake array url.
+ *
+ * @var mixed
+ */
+ public $redirect;
+
+/**
+ * Flag for disabling exit() when this route parses a url.
+ *
+ * @var boolean
+ */
+ public $stop = true;
+
+/**
+ * Constructor
+ *
+ * @param string $template Template string with parameter placeholders
+ * @param array $defaults Array of defaults for the route.
+ * @param array $options Array of additional options for the Route
+ */
+ public function __construct($template, $defaults = array(), $options = array()) {
+ parent::__construct($template, $defaults, $options);
+ $this->redirect = (array)$defaults;
+ }
+
+/**
+ * Parses a string url into an array. Parsed urls will result in an automatic
+ * redirection
+ *
+ * @param string $url The url to parse
+ * @return boolean False on failure
+ */
+ public function parse($url) {
+ $params = parent::parse($url);
+ if (!$params) {
+ return false;
+ }
+ if (!$this->response) {
+ $this->response = new CakeResponse();
+ }
+ $redirect = $this->redirect;
+ if (count($this->redirect) == 1 && !isset($this->redirect['controller'])) {
+ $redirect = $this->redirect[0];
+ }
+ if (isset($this->options['persist']) && is_array($redirect)) {
+ $redirect += array('named' => $params['named'], 'pass' => $params['pass'], 'url' => array());
+ $redirect = Router::reverse($redirect);
+ }
+ $status = 301;
+ if (isset($this->options['status']) && ($this->options['status'] >= 300 && $this->options['status'] < 400)) {
+ $status = $this->options['status'];
+ }
+ $this->response->header(array('Location' => Router::url($redirect, true)));
+ $this->response->statusCode($status);
+ $this->response->send();
+ $this->_stop();
+ }
+
+/**
+ * There is no reverse routing redirection routes
+ *
+ * @param array $url Array of parameters to convert to a string.
+ * @return mixed either false or a string url.
+ */
+ public function match($url) {
+ return false;
+ }
+
+/**
+ * Stop execution of the current script. Wraps exit() making
+ * testing easier.
+ *
+ * @param integer|string $status see http://php.net/exit for values
+ * @return void
+ */
+ protected function _stop($code = 0) {
+ if ($this->stop) {
+ exit($code);
+ }
+ }
+
+}
diff --git a/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Router.php b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Router.php
new file mode 100644
index 0000000..42a2937
--- /dev/null
+++ b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/Routing/Router.php
@@ -0,0 +1,1141 @@
+<?php
+/**
+ * Parses the request URL into controller, action, and parameters.
+ *
+ * PHP 5
+ *
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link http://cakephp.org CakePHP(tm) Project
+ * @package Cake.Routing
+ * @since CakePHP(tm) v 0.2.9
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+
+App::uses('CakeRequest', 'Network');
+App::uses('CakeRoute', 'Routing/Route');
+
+/**
+ * Parses the request URL into controller, action, and parameters. Uses the connected routes
+ * to match the incoming url string to parameters that will allow the request to be dispatched. Also
+ * handles converting parameter lists into url strings, using the connected routes. Routing allows you to decouple
+ * the way the world interacts with your application (urls) and the implementation (controllers and actions).
+ *
+ * ### Connecting routes
+ *
+ * Connecting routes is done using Router::connect(). When parsing incoming requests or reverse matching
+ * parameters, routes are enumerated in the order they were connected. You can modify the order of connected
+ * routes using Router::promote(). For more information on routes and how to connect them see Router::connect().
+ *
+ * ### Named parameters
+ *
+ * Named parameters allow you to embed key:value pairs into path segments. This allows you create hash
+ * structures using urls. You can define how named parameters work in your application using Router::connectNamed()
+ *
+ * @package Cake.Routing
+ */
+class Router {
+
+/**
+ * Array of routes connected with Router::connect()
+ *
+ * @var array
+ */
+ public static $routes = array();
+
+/**
+ * List of action prefixes used in connected routes.
+ * Includes admin prefix
+ *
+ * @var array
+ */
+ protected static $_prefixes = array();
+
+/**
+ * Directive for Router to parse out file extensions for mapping to Content-types.
+ *
+ * @var boolean
+ */
+ protected static $_parseExtensions = false;
+
+/**
+ * List of valid extensions to parse from a URL. If null, any extension is allowed.
+ *
+ * @var array
+ */
+ protected static $_validExtensions = array();
+
+/**
+ * 'Constant' regular expression definitions for named route elements
+ *
+ */
+ const ACTION = 'index|show|add|create|edit|update|remove|del|delete|view|item';
+ const YEAR = '[12][0-9]{3}';
+ const MONTH = '0[1-9]|1[012]';
+ const DAY = '0[1-9]|[12][0-9]|3[01]';
+ const ID = '[0-9]+';
+ const UUID = '[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}';
+
+/**
+ * Named expressions
+ *
+ * @var array
+ */
+ protected static $_namedExpressions = array(
+ 'Action' => Router::ACTION,
+ 'Year' => Router::YEAR,
+ 'Month' => Router::MONTH,
+ 'Day' => Router::DAY,
+ 'ID' => Router::ID,
+ 'UUID' => Router::UUID
+ );
+
+/**
+ * Stores all information necessary to decide what named arguments are parsed under what conditions.
+ *
+ * @var string
+ */
+ protected static $_namedConfig = array(
+ 'default' => array('page', 'fields', 'order', 'limit', 'recursive', 'sort', 'direction', 'step'),
+ 'greedyNamed' => true,
+ 'separator' => ':',
+ 'rules' => false,
+ );
+
+/**
+ * The route matching the URL of the current request
+ *
+ * @var array
+ */
+ protected static $_currentRoute = array();
+
+/**
+ * Default HTTP request method => controller action map.
+ *
+ * @var array
+ */
+ protected static $_resourceMap = array(
+ array('action' => 'index', 'method' => 'GET', 'id' => false),
+ array('action' => 'view', 'method' => 'GET', 'id' => true),
+ array('action' => 'add', 'method' => 'POST', 'id' => false),
+ array('action' => 'edit', 'method' => 'PUT', 'id' => true),
+ array('action' => 'delete', 'method' => 'DELETE', 'id' => true),
+ array('action' => 'edit', 'method' => 'POST', 'id' => true)
+ );
+
+/**
+ * List of resource-mapped controllers
+ *
+ * @var array
+ */
+ protected static $_resourceMapped = array();
+
+/**
+ * Maintains the request object stack for the current request.
+ * This will contain more than one request object when requestAction is used.
+ *
+ * @var array
+ */
+ protected static $_requests = array();
+
+/**
+ * Initial state is populated the first time reload() is called which is at the bottom
+ * of this file. This is a cheat as get_class_vars() returns the value of static vars even if they
+ * have changed.
+ *
+ * @var array
+ */
+ protected static $_initialState = array();
+
+/**
+ * Default route class to use
+ *
+ * @var string
+ */
+ protected static $_routeClass = 'CakeRoute';
+
+/**
+ * Set the default route class to use or return the current one
+ *
+ * @param string $routeClass to set as default
+ * @return mixed void|string
+ * @throws RouterException
+ */
+ public static function defaultRouteClass($routeClass = null) {
+ if (is_null($routeClass)) {
+ return self::$_routeClass;
+ }
+
+ self::$_routeClass = self::_validateRouteClass($routeClass);
+ }
+
+/**
+ * Validates that the passed route class exists and is a subclass of CakeRoute
+ *
+ * @param $routeClass
+ * @return string
+ * @throws RouterException
+ */
+ protected static function _validateRouteClass($routeClass) {
+ if (!class_exists($routeClass) || !is_subclass_of($routeClass, 'CakeRoute')) {
+ throw new RouterException(__d('cake_dev', 'Route classes must extend CakeRoute'));
+ }
+ return $routeClass;
+ }
+
+/**
+ * Sets the Routing prefixes.
+ *
+ * @return void
+ */
+ protected static function _setPrefixes() {
+ $routing = Configure::read('Routing');
+ if (!empty($routing['prefixes'])) {
+ self::$_prefixes = array_merge(self::$_prefixes, (array)$routing['prefixes']);
+ }
+ }
+
+/**
+ * Gets the named route elements for use in app/Config/routes.php
+ *
+ * @return array Named route elements
+ * @see Router::$_namedExpressions
+ */
+ public static function getNamedExpressions() {
+ return self::$_namedExpressions;
+ }
+
+/**
+ * Resource map getter & setter.
+ *
+ * @param array $resourceMap Resource map
+ * @return mixed
+ * @see Router::$_resourceMap
+ */
+ public static function resourceMap($resourceMap = null) {
+ if ($resourceMap === null) {
+ return self::$_resourceMap;
+ }
+ self::$_resourceMap = $resourceMap;
+ }
+
+/**
+ * Connects a new Route in the router.
+ *
+ * Routes are a way of connecting request urls to objects in your application. At their core routes
+ * are a set or regular expressions that are used to match requests to destinations.
+ *
+ * Examples:
+ *
+ * `Router::connect('/:controller/:action/*');`
+ *
+ * The first parameter will be used as a controller name while the second is used as the action name.
+ * the '/*' syntax makes this route greedy in that it will match requests like `/posts/index` as well as requests
+ * like `/posts/edit/1/foo/bar`.
+ *
+ * `Router::connect('/home-page', array('controller' => 'pages', 'action' => 'display', 'home'));`
+ *
+ * The above shows the use of route parameter defaults. And providing routing parameters for a static route.
+ *
+ * {{{
+ * Router::connect(
+ * '/:lang/:controller/:action/:id',
+ * array(),
+ * array('id' => '[0-9]+', 'lang' => '[a-z]{3}')
+ * );
+ * }}}
+ *
+ * Shows connecting a route with custom route parameters as well as providing patterns for those parameters.
+ * Patterns for routing parameters do not need capturing groups, as one will be added for each route params.
+ *
+ * $options offers four 'special' keys. `pass`, `named`, `persist` and `routeClass`
+ * have special meaning in the $options array.
+ *
+ * `pass` is used to define which of the routed parameters should be shifted into the pass array. Adding a
+ * parameter to pass will remove it from the regular route array. Ex. `'pass' => array('slug')`
+ *
+ * `persist` is used to define which route parameters should be automatically included when generating
+ * new urls. You can override persistent parameters by redefining them in a url or remove them by
+ * setting the parameter to `false`. Ex. `'persist' => array('lang')`
+ *
+ * `routeClass` is used to extend and change how individual routes parse requests and handle reverse routing,
+ * via a custom routing class. Ex. `'routeClass' => 'SlugRoute'`
+ *
+ * `named` is used to configure named parameters at the route level. This key uses the same options
+ * as Router::connectNamed()
+ *
+ * @param string $route A string describing the template of the route
+ * @param array $defaults An array describing the default route parameters. These parameters will be used by default
+ * and can supply routing parameters that are not dynamic. See above.
+ * @param array $options An array matching the named elements in the route to regular expressions which that
+ * element should match. Also contains additional parameters such as which routed parameters should be
+ * shifted into the passed arguments, supplying patterns for routing parameters and supplying the name of a
+ * custom routing class.
+ * @see routes
+ * @return array Array of routes
+ * @throws RouterException
+ */
+ public static function connect($route, $defaults = array(), $options = array()) {
+ foreach (self::$_prefixes as $prefix) {
+ if (isset($defaults[$prefix])) {
+ if ($defaults[$prefix]) {
+ $defaults['prefix'] = $prefix;
+ } else {
+ unset($defaults[$prefix]);
+ }
+ break;
+ }
+ }
+ if (isset($defaults['prefix'])) {
+ self::$_prefixes[] = $defaults['prefix'];
+ self::$_prefixes = array_keys(array_flip(self::$_prefixes));
+ }
+ $defaults += array('plugin' => null);
+ if (empty($options['action'])) {
+ $defaults += array('action' => 'index');
+ }
+ $routeClass = self::$_routeClass;
+ if (isset($options['routeClass'])) {
+ $routeClass = self::_validateRouteClass($options['routeClass']);
+ unset($options['routeClass']);
+ }
+ if ($routeClass == 'RedirectRoute' && isset($defaults['redirect'])) {
+ $defaults = $defaults['redirect'];
+ }
+ self::$routes[] = new $routeClass($route, $defaults, $options);
+ return self::$routes;
+ }
+
+/**
+ * Connects a new redirection Route in the router.
+ *
+ * Redirection routes are different from normal routes as they perform an actual
+ * header redirection if a match is found. The redirection can occur within your
+ * application or redirect to an outside location.
+ *
+ * Examples:
+ *
+ * `Router::redirect('/home/*', array('controller' => 'posts', 'action' => 'view', array('persist' => true)));`
+ *
+ * Redirects /home/* to /posts/view and passes the parameters to /posts/view. Using an array as the
+ * redirect destination allows you to use other routes to define where a url string should be redirected to.
+ *
+ * `Router::redirect('/posts/*', 'http://google.com', array('status' => 302));`
+ *
+ * Redirects /posts/* to http://google.com with a HTTP status of 302
+ *
+ * ### Options:
+ *
+ * - `status` Sets the HTTP status (default 301)
+ * - `persist` Passes the params to the redirected route, if it can. This is useful with greedy routes,
+ * routes that end in `*` are greedy. As you can remap urls and not loose any passed/named args.
+ *
+ * @param string $route A string describing the template of the route
+ * @param array $url A url to redirect to. Can be a string or a Cake array-based url
+ * @param array $options An array matching the named elements in the route to regular expressions which that
+ * element should match. Also contains additional parameters such as which routed parameters should be
+ * shifted into the passed arguments. As well as supplying patterns for routing parameters.
+ * @see routes
+ * @return array Array of routes
+ */
+ public static function redirect($route, $url, $options = array()) {
+ App::uses('RedirectRoute', 'Routing/Route');
+ $options['routeClass'] = 'RedirectRoute';
+ if (is_string($url)) {
+ $url = array('redirect' => $url);
+ }
+ return self::connect($route, $url, $options);
+ }
+
+/**
+ * Specifies what named parameters CakePHP should be parsing out of incoming urls. By default
+ * CakePHP will parse every named parameter out of incoming URLs. However, if you want to take more
+ * control over how named parameters are parsed you can use one of the following setups:
+ *
+ * Do not parse any named parameters:
+ *
+ * {{{ Router::connectNamed(false); }}}
+ *
+ * Parse only default parameters used for CakePHP's pagination:
+ *
+ * {{{ Router::connectNamed(false, array('default' => true)); }}}
+ *
+ * Parse only the page parameter if its value is a number:
+ *
+ * {{{ Router::connectNamed(array('page' => '[\d]+'), array('default' => false, 'greedy' => false)); }}}
+ *
+ * Parse only the page parameter no matter what.
+ *
+ * {{{ Router::connectNamed(array('page'), array('default' => false, 'greedy' => false)); }}}
+ *
+ * Parse only the page parameter if the current action is 'index'.
+ *
+ * {{{
+ * Router::connectNamed(
+ * array('page' => array('action' => 'index')),
+ * array('default' => false, 'greedy' => false)
+ * );
+ * }}}
+ *
+ * Parse only the page parameter if the current action is 'index' and the controller is 'pages'.
+ *
+ * {{{
+ * Router::connectNamed(
+ * array('page' => array('action' => 'index', 'controller' => 'pages')),
+ * array('default' => false, 'greedy' => false)
+ * );
+ * }}}
+ *
+ * ### Options
+ *
+ * - `greedy` Setting this to true will make Router parse all named params. Setting it to false will
+ * parse only the connected named params.
+ * - `default` Set this to true to merge in the default set of named parameters.
+ * - `reset` Set to true to clear existing rules and start fresh.
+ * - `separator` Change the string used to separate the key & value in a named parameter. Defaults to `:`
+ *
+ * @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.
+ * @param array $options Allows to control all settings: separator, greedy, reset, default
+ * @return array
+ */
+ public static function connectNamed($named, $options = array()) {
+ if (isset($options['separator'])) {
+ self::$_namedConfig['separator'] = $options['separator'];
+ unset($options['separator']);
+ }
+
+ if ($named === true || $named === false) {
+ $options = array_merge(array('default' => $named, 'reset' => true, 'greedy' => $named), $options);
+ $named = array();
+ } else {
+ $options = array_merge(array('default' => false, 'reset' => false, 'greedy' => true), $options);
+ }
+
+ if ($options['reset'] == true || self::$_namedConfig['rules'] === false) {
+ self::$_namedConfig['rules'] = array();
+ }
+
+ if ($options['default']) {
+ $named = array_merge($named, self::$_namedConfig['default']);
+ }
+
+ foreach ($named as $key => $val) {
+ if (is_numeric($key)) {
+ self::$_namedConfig['rules'][$val] = true;
+ } else {
+ self::$_namedConfig['rules'][$key] = $val;
+ }
+ }
+ self::$_namedConfig['greedyNamed'] = $options['greedy'];
+ return self::$_namedConfig;
+ }
+
+/**
+ * Gets the current named parameter configuration values.
+ *
+ * @return array
+ * @see Router::$_namedConfig
+ */
+ public static function namedConfig() {
+ return self::$_namedConfig;
+ }
+
+/**
+ * Creates REST resource routes for the given controller(s). When creating resource routes
+ * for a plugin, by default the prefix will be changed to the lower_underscore version of the plugin
+ * name. By providing a prefix you can override this behavior.
+ *
+ * ### Options:
+ *
+ * - 'id' - The regular expression fragment to use when matching IDs. By default, matches
+ * integer values and UUIDs.
+ * - 'prefix' - URL prefix to use for the generated routes. Defaults to '/'.
+ *
+ * @param string|array $controller A controller name or array of controller names (i.e. "Posts" or "ListItems")
+ * @param array $options Options to use when generating REST routes
+ * @return array Array of mapped resources
+ */
+ public static function mapResources($controller, $options = array()) {
+ $hasPrefix = isset($options['prefix']);
+ $options = array_merge(array(
+ 'prefix' => '/',
+ 'id' => self::ID . '|' . self::UUID
+ ), $options);
+
+ $prefix = $options['prefix'];
+
+ foreach ((array)$controller as $name) {
+ list($plugin, $name) = pluginSplit($name);
+ $urlName = Inflector::underscore($name);
+ $plugin = Inflector::underscore($plugin);
+ if ($plugin && !$hasPrefix) {
+ $prefix = '/' . $plugin . '/';
+ }
+
+ foreach (self::$_resourceMap as $params) {
+ $url = $prefix . $urlName . (($params['id']) ? '/:id' : '');
+
+ Router::connect($url,
+ array(
+ 'plugin' => $plugin,
+ 'controller' => $urlName,
+ 'action' => $params['action'],
+ '[method]' => $params['method']
+ ),
+ array('id' => $options['id'], 'pass' => array('id'))
+ );
+ }
+ self::$_resourceMapped[] = $urlName;
+ }
+ return self::$_resourceMapped;
+ }
+
+/**
+ * Returns the list of prefixes used in connected routes
+ *
+ * @return array A list of prefixes used in connected routes
+ */
+ public static function prefixes() {
+ return self::$_prefixes;
+ }
+
+/**
+ * Parses given URL string. Returns 'routing' parameters for that url.
+ *
+ * @param string $url URL to be parsed
+ * @return array Parsed elements from URL
+ */
+ public static function parse($url) {
+ $ext = null;
+ $out = array();
+
+ if ($url && strpos($url, '/') !== 0) {
+ $url = '/' . $url;
+ }
+ if (strpos($url, '?') !== false) {
+ $url = substr($url, 0, strpos($url, '?'));
+ }
+
+ extract(self::_parseExtension($url));
+
+ for ($i = 0, $len = count(self::$routes); $i < $len; $i++) {
+ $route =& self::$routes[$i];
+
+ if (($r = $route->parse($url)) !== false) {
+ self::$_currentRoute[] =& $route;
+ $out = $r;
+ break;
+ }
+ }
+ if (isset($out['prefix'])) {
+ $out['action'] = $out['prefix'] . '_' . $out['action'];
+ }
+
+ if (!empty($ext) && !isset($out['ext'])) {
+ $out['ext'] = $ext;
+ }
+ return $out;
+ }
+
+/**
+ * Parses a file extension out of a URL, if Router::parseExtensions() is enabled.
+ *
+ * @param string $url
+ * @return array Returns an array containing the altered URL and the parsed extension.
+ */
+ protected static function _parseExtension($url) {
+ $ext = null;
+
+ if (self::$_parseExtensions) {
+ if (preg_match('/\.[0-9a-zA-Z]*$/', $url, $match) === 1) {
+ $match = substr($match[0], 1);
+ if (empty(self::$_validExtensions)) {
+ $url = substr($url, 0, strpos($url, '.' . $match));
+ $ext = $match;
+ } else {
+ foreach (self::$_validExtensions as $name) {
+ if (strcasecmp($name, $match) === 0) {
+ $url = substr($url, 0, strpos($url, '.' . $name));
+ $ext = $match;
+ break;
+ }
+ }
+ }
+ }
+ }
+ return compact('ext', 'url');
+ }
+
+/**
+ * Takes parameter and path information back from the Dispatcher, sets these
+ * parameters as the current request parameters that are merged with url arrays
+ * created later in the request.
+ *
+ * Nested requests will create a stack of requests. You can remove requests using
+ * Router::popRequest(). This is done automatically when using Object::requestAction().
+ *
+ * Will accept either a CakeRequest object or an array of arrays. Support for
+ * accepting arrays may be removed in the future.
+ *
+ * @param CakeRequest|array $request Parameters and path information or a CakeRequest object.
+ * @return void
+ */
+ public static function setRequestInfo($request) {
+ if ($request instanceof CakeRequest) {
+ self::$_requests[] = $request;
+ } else {
+ $requestObj = new CakeRequest();
+ $request += array(array(), array());
+ $request[0] += array('controller' => false, 'action' => false, 'plugin' => null);
+ $requestObj->addParams($request[0])->addPaths($request[1]);
+ self::$_requests[] = $requestObj;
+ }
+ }
+
+/**
+ * Pops a request off of the request stack. Used when doing requestAction
+ *
+ * @return CakeRequest The request removed from the stack.
+ * @see Router::setRequestInfo()
+ * @see Object::requestAction()
+ */
+ public static function popRequest() {
+ return array_pop(self::$_requests);
+ }
+
+/**
+ * Get the either the current request object, or the first one.
+ *
+ * @param boolean $current Whether you want the request from the top of the stack or the first one.
+ * @return CakeRequest or null.
+ */
+ public static function getRequest($current = false) {
+ if ($current) {
+ $i = count(self::$_requests) - 1;
+ return isset(self::$_requests[$i]) ? self::$_requests[$i] : null;
+ }
+ return isset(self::$_requests[0]) ? self::$_requests[0] : null;
+ }
+
+/**
+ * Gets parameter information
+ *
+ * @param boolean $current Get current request parameter, useful when using requestAction
+ * @return array Parameter information
+ */
+ public static function getParams($current = false) {
+ if ($current) {
+ return self::$_requests[count(self::$_requests) - 1]->params;
+ }
+ if (isset(self::$_requests[0])) {
+ return self::$_requests[0]->params;
+ }
+ return array();
+ }
+
+/**
+ * Gets URL parameter by name
+ *
+ * @param string $name Parameter name
+ * @param boolean $current Current parameter, useful when using requestAction
+ * @return string Parameter value
+ */
+ public static function getParam($name = 'controller', $current = false) {
+ $params = Router::getParams($current);
+ if (isset($params[$name])) {
+ return $params[$name];
+ }
+ return null;
+ }
+
+/**
+ * Gets path information
+ *
+ * @param boolean $current Current parameter, useful when using requestAction
+ * @return array
+ */
+ public static function getPaths($current = false) {
+ if ($current) {
+ return self::$_requests[count(self::$_requests) - 1];
+ }
+ if (!isset(self::$_requests[0])) {
+ return array('base' => null);
+ }
+ return array('base' => self::$_requests[0]->base);
+ }
+
+/**
+ * Reloads default Router settings. Resets all class variables and
+ * removes all connected routes.
+ *
+ * @return void
+ */
+ public static function reload() {
+ if (empty(self::$_initialState)) {
+ self::$_initialState = get_class_vars('Router');
+ self::_setPrefixes();
+ return;
+ }
+ foreach (self::$_initialState as $key => $val) {
+ if ($key != '_initialState') {
+ self::${$key} = $val;
+ }
+ }
+ self::_setPrefixes();
+ }
+
+/**
+ * Promote a route (by default, the last one added) to the beginning of the list
+ *
+ * @param integer $which A zero-based array index representing the route to move. For example,
+ * if 3 routes have been added, the last route would be 2.
+ * @return boolean Returns false if no route exists at the position specified by $which.
+ */
+ public static function promote($which = null) {
+ if ($which === null) {
+ $which = count(self::$routes) - 1;
+ }
+ if (!isset(self::$routes[$which])) {
+ return false;
+ }
+ $route =& self::$routes[$which];
+ unset(self::$routes[$which]);
+ array_unshift(self::$routes, $route);
+ return true;
+ }
+
+/**
+ * Finds URL for specified action.
+ *
+ * Returns an URL pointing to a combination of controller and action. Param
+ * $url can be:
+ *
+ * - Empty - the method will find address to actual controller/action.
+ * - '/' - the method will find base URL of application.
+ * - A combination of controller/action - the method will find url for it.
+ *
+ * There are a few 'special' parameters that can change the final URL string that is generated
+ *
+ * - `base` - Set to false to remove the base path from the generated url. If your application
+ * is not in the root directory, this can be used to generate urls that are 'cake relative'.
+ * cake relative urls are required when using requestAction.
+ * - `?` - Takes an array of query string parameters
+ * - `#` - Allows you to set url hash fragments.
+ * - `full_base` - If true the `FULL_BASE_URL` constant will be prepended to generated urls.
+ *
+ * @param string|array $url Cake-relative URL, like "/products/edit/92" or "/presidents/elect/4"
+ * or an array specifying any of the following: 'controller', 'action',
+ * and/or 'plugin', in addition to named arguments (keyed array elements),
+ * and standard URL arguments (indexed array elements)
+ * @param bool|array $full If (bool) true, the full base URL will be prepended to the result.
+ * If an array accepts the following keys
+ * - escape - used when making urls embedded in html escapes query string '&'
+ * - full - if true the full base URL will be prepended.
+ * @return string Full translated URL with base path.
+ */
+ public static function url($url = null, $full = false) {
+ $params = array('plugin' => null, 'controller' => null, 'action' => 'index');
+
+ if (is_bool($full)) {
+ $escape = false;
+ } else {
+ extract($full + array('escape' => false, 'full' => false));
+ }
+
+ $path = array('base' => null);
+ if (!empty(self::$_requests)) {
+ $request = self::$_requests[count(self::$_requests) - 1];
+ $params = $request->params;
+ $path = array('base' => $request->base, 'here' => $request->here);
+ }
+
+ $base = $path['base'];
+ $extension = $output = $q = $frag = null;
+
+ if (empty($url)) {
+ $output = isset($path['here']) ? $path['here'] : '/';
+ if ($full && defined('FULL_BASE_URL')) {
+ $output = FULL_BASE_URL . $output;
+ }
+ return $output;
+ } elseif (is_array($url)) {
+ if (isset($url['base']) && $url['base'] === false) {
+ $base = null;
+ unset($url['base']);
+ }
+ if (isset($url['full_base']) && $url['full_base'] === true) {
+ $full = true;
+ unset($url['full_base']);
+ }
+ if (isset($url['?'])) {
+ $q = $url['?'];
+ unset($url['?']);
+ }
+ if (isset($url['#'])) {
+ $frag = '#' . $url['#'];
+ unset($url['#']);
+ }
+ if (isset($url['ext'])) {
+ $extension = '.' . $url['ext'];
+ unset($url['ext']);
+ }
+ if (empty($url['action'])) {
+ if (empty($url['controller']) || $params['controller'] === $url['controller']) {
+ $url['action'] = $params['action'];
+ } else {
+ $url['action'] = 'index';
+ }
+ }
+
+ $prefixExists = (array_intersect_key($url, array_flip(self::$_prefixes)));
+ foreach (self::$_prefixes as $prefix) {
+ if (!empty($params[$prefix]) && !$prefixExists) {
+ $url[$prefix] = true;
+ } elseif (isset($url[$prefix]) && !$url[$prefix]) {
+ unset($url[$prefix]);
+ }
+ if (isset($url[$prefix]) && strpos($url['action'], $prefix . '_') === 0) {
+ $url['action'] = substr($url['action'], strlen($prefix) + 1);
+ }
+ }
+
+ $url += array('controller' => $params['controller'], 'plugin' => $params['plugin']);
+
+ $match = false;
+
+ for ($i = 0, $len = count(self::$routes); $i < $len; $i++) {
+ $originalUrl = $url;
+
+ if (isset(self::$routes[$i]->options['persist'], $params)) {
+ $url = self::$routes[$i]->persistParams($url, $params);
+ }
+
+ if ($match = self::$routes[$i]->match($url)) {
+ $output = trim($match, '/');
+ break;
+ }
+ $url = $originalUrl;
+ }
+ if ($match === false) {
+ $output = self::_handleNoRoute($url);
+ }
+ } else {
+ if (
+ (strpos($url, '://') !== false ||
+ (strpos($url, 'javascript:') === 0) ||
+ (strpos($url, 'mailto:') === 0)) ||
+ (!strncmp($url, '#', 1))
+ ) {
+ return $url;
+ }
+ if (substr($url, 0, 1) === '/') {
+ $output = substr($url, 1);
+ } else {
+ foreach (self::$_prefixes as $prefix) {
+ if (isset($params[$prefix])) {
+ $output .= $prefix . '/';
+ break;
+ }
+ }
+ if (!empty($params['plugin']) && $params['plugin'] !== $params['controller']) {
+ $output .= Inflector::underscore($params['plugin']) . '/';
+ }
+ $output .= Inflector::underscore($params['controller']) . '/' . $url;
+ }
+ }
+ $protocol = preg_match('#^[a-z][a-z0-9+-.]*\://#i', $output);
+ if ($protocol === 0) {
+ $output = str_replace('//', '/', $base . '/' . $output);
+
+ if ($full && defined('FULL_BASE_URL')) {
+ $output = FULL_BASE_URL . $output;
+ }
+ if (!empty($extension)) {
+ $output = rtrim($output, '/');
+ }
+ }
+ return $output . $extension . self::queryString($q, array(), $escape) . $frag;
+ }
+
+/**
+ * A special fallback method that handles url arrays that cannot match
+ * any defined routes.
+ *
+ * @param array $url A url that didn't match any routes
+ * @return string A generated url for the array
+ * @see Router::url()
+ */
+ protected static function _handleNoRoute($url) {
+ $named = $args = array();
+ $skip = array_merge(
+ array('bare', 'action', 'controller', 'plugin', 'prefix'),
+ self::$_prefixes
+ );
+
+ $keys = array_values(array_diff(array_keys($url), $skip));
+ $count = count($keys);
+
+ // Remove this once parsed URL parameters can be inserted into 'pass'
+ for ($i = 0; $i < $count; $i++) {
+ $key = $keys[$i];
+ if (is_numeric($keys[$i])) {
+ $args[] = $url[$key];
+ } else {
+ $named[$key] = $url[$key];
+ }
+ }
+
+ list($args, $named) = array(Hash::filter($args), Hash::filter($named));
+ foreach (self::$_prefixes as $prefix) {
+ $prefixed = $prefix . '_';
+ if (!empty($url[$prefix]) && strpos($url['action'], $prefixed) === 0) {
+ $url['action'] = substr($url['action'], strlen($prefixed) * -1);
+ break;
+ }
+ }
+
+ if (empty($named) && empty($args) && (!isset($url['action']) || $url['action'] === 'index')) {
+ $url['action'] = null;
+ }
+
+ $urlOut = array_filter(array($url['controller'], $url['action']));
+
+ if (isset($url['plugin'])) {
+ array_unshift($urlOut, $url['plugin']);
+ }
+
+ foreach (self::$_prefixes as $prefix) {
+ if (isset($url[$prefix])) {
+ array_unshift($urlOut, $prefix);
+ break;
+ }
+ }
+ $output = implode('/', $urlOut);
+
+ if (!empty($args)) {
+ $output .= '/' . implode('/', array_map('rawurlencode', $args));
+ }
+
+ if (!empty($named)) {
+ foreach ($named as $name => $value) {
+ if (is_array($value)) {
+ $flattend = Hash::flatten($value, '][');
+ foreach ($flattend as $namedKey => $namedValue) {
+ $output .= '/' . $name . "[$namedKey]" . self::$_namedConfig['separator'] . rawurlencode($namedValue);
+ }
+ } else {
+ $output .= '/' . $name . self::$_namedConfig['separator'] . rawurlencode($value);
+ }
+ }
+ }
+ return $output;
+ }
+
+/**
+ * Generates a well-formed querystring from $q
+ *
+ * @param string|array $q Query string Either a string of already compiled query string arguments or
+ * an array of arguments to convert into a query string.
+ * @param array $extra Extra querystring parameters.
+ * @param boolean $escape Whether or not to use escaped &
+ * @return array
+ */
+ public static function queryString($q, $extra = array(), $escape = false) {
+ if (empty($q) && empty($extra)) {
+ return null;
+ }
+ $join = '&';
+ if ($escape === true) {
+ $join = '&amp;';
+ }
+ $out = '';
+
+ if (is_array($q)) {
+ $q = array_merge($q, $extra);
+ } else {
+ $out = $q;
+ $q = $extra;
+ }
+ $addition = http_build_query($q, null, $join);
+
+ if ($out && $addition && substr($out, strlen($join) * -1, strlen($join)) != $join) {
+ $out .= $join;
+ }
+
+ $out .= $addition;
+
+ if (isset($out[0]) && $out[0] != '?') {
+ $out = '?' . $out;
+ }
+ return $out;
+ }
+
+/**
+ * Reverses a parsed parameter array into a string. Works similarly to Router::url(), but
+ * Since parsed URL's contain additional 'pass' and 'named' as well as 'url.url' keys.
+ * Those keys need to be specially handled in order to reverse a params array into a string url.
+ *
+ * This will strip out 'autoRender', 'bare', 'requested', and 'return' param names as those
+ * are used for CakePHP internals and should not normally be part of an output url.
+ *
+ * @param CakeRequest|array $params The params array or CakeRequest object that needs to be reversed.
+ * @param boolean $full Set to true to include the full url including the protocol when reversing
+ * the url.
+ * @return string The string that is the reversed result of the array
+ */
+ public static function reverse($params, $full = false) {
+ if ($params instanceof CakeRequest) {
+ $url = $params->query;
+ $params = $params->params;
+ } else {
+ $url = $params['url'];
+ }
+ $pass = isset($params['pass']) ? $params['pass'] : array();
+ $named = isset($params['named']) ? $params['named'] : array();
+
+ unset(
+ $params['pass'], $params['named'], $params['paging'], $params['models'], $params['url'], $url['url'],
+ $params['autoRender'], $params['bare'], $params['requested'], $params['return'],
+ $params['_Token']
+ );
+ $params = array_merge($params, $pass, $named);
+ if (!empty($url)) {
+ $params['?'] = $url;
+ }
+ return Router::url($params, $full);
+ }
+
+/**
+ * Normalizes a URL for purposes of comparison. Will strip the base path off
+ * and replace any double /'s. It will not unify the casing and underscoring
+ * of the input value.
+ *
+ * @param array|string $url URL to normalize Either an array or a string url.
+ * @return string Normalized URL
+ */
+ public static function normalize($url = '/') {
+ if (is_array($url)) {
+ $url = Router::url($url);
+ }
+ if (preg_match('/^[a-z\-]+:\/\//', $url)) {
+ return $url;
+ }
+ $request = Router::getRequest();
+
+ if (!empty($request->base) && stristr($url, $request->base)) {
+ $url = preg_replace('/^' . preg_quote($request->base, '/') . '/', '', $url, 1);
+ }
+ $url = '/' . $url;
+
+ while (strpos($url, '//') !== false) {
+ $url = str_replace('//', '/', $url);
+ }
+ $url = preg_replace('/(?:(\/$))/', '', $url);
+
+ if (empty($url)) {
+ return '/';
+ }
+ return $url;
+ }
+
+/**
+ * Returns the route matching the current request URL.
+ *
+ * @return CakeRoute Matching route object.
+ */
+ public static function &requestRoute() {
+ return self::$_currentRoute[0];
+ }
+
+/**
+ * Returns the route matching the current request (useful for requestAction traces)
+ *
+ * @return CakeRoute Matching route object.
+ */
+ public static function &currentRoute() {
+ return self::$_currentRoute[count(self::$_currentRoute) - 1];
+ }
+
+/**
+ * Removes the plugin name from the base URL.
+ *
+ * @param string $base Base URL
+ * @param string $plugin Plugin name
+ * @return string base url with plugin name removed if present
+ */
+ public static function stripPlugin($base, $plugin = null) {
+ if ($plugin != null) {
+ $base = preg_replace('/(?:' . $plugin . ')/', '', $base);
+ $base = str_replace('//', '', $base);
+ $pos1 = strrpos($base, '/');
+ $char = strlen($base) - 1;
+
+ if ($pos1 === $char) {
+ $base = substr($base, 0, $char);
+ }
+ }
+ return $base;
+ }
+
+/**
+ * Instructs the router to parse out file extensions from the URL. For example,
+ * http://example.com/posts.rss would yield an file extension of "rss".
+ * The file extension itself is made available in the controller as
+ * `$this->params['ext']`, and is used by the RequestHandler component to
+ * automatically switch to alternate layouts and templates, and load helpers
+ * corresponding to the given content, i.e. RssHelper. Switching layouts and helpers
+ * requires that the chosen extension has a defined mime type in `CakeResponse`
+ *
+ * A list of valid extension can be passed to this method, i.e. Router::parseExtensions('rss', 'xml');
+ * If no parameters are given, anything after the first . (dot) after the last / in the URL will be
+ * parsed, excluding querystring parameters (i.e. ?q=...).
+ *
+ * @return void
+ * @see RequestHandler::startup()
+ */
+ public static function parseExtensions() {
+ self::$_parseExtensions = true;
+ if (func_num_args() > 0) {
+ self::setExtensions(func_get_args(), false);
+ }
+ }
+
+/**
+ * Get the list of extensions that can be parsed by Router.
+ * To initially set extensions use `Router::parseExtensions()`
+ * To add more see `setExtensions()`
+ *
+ * @return array Array of extensions Router is configured to parse.
+ */
+ public static function extensions() {
+ return self::$_validExtensions;
+ }
+
+/**
+ * Set/add valid extensions.
+ * To have the extensions parsed you still need to call `Router::parseExtensions()`
+ *
+ * @param array $extensions List of extensions to be added as valid extension
+ * @param boolean $merge Default true will merge extensions. Set to false to override current extensions
+ * @return array
+ */
+ public static function setExtensions($extensions, $merge = true) {
+ if (!is_array($extensions)) {
+ return self::$_validExtensions;
+ }
+ if (!$merge) {
+ return self::$_validExtensions = $extensions;
+ }
+ return self::$_validExtensions = array_merge(self::$_validExtensions, $extensions);
+ }
+
+}
+
+//Save the initial state
+Router::reload();