source: Dev/branches/cakephp/cake/dispatcher.php @ 127

Last change on this file since 127 was 126, checked in by fpvanagthoven, 14 years ago

Cakephp branch.

File size: 17.7 KB
Line 
1<?php
2/**
3 * Dispatcher takes the URL information, parses it for paramters and
4 * tells the involved controllers what to do.
5 *
6 * This is the heart of Cake's operation.
7 *
8 * PHP versions 4 and 5
9 *
10 * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
11 * Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
12 *
13 * Licensed under The MIT License
14 * Redistributions of files must retain the above copyright notice.
15 *
16 * @copyright     Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
17 * @link          http://cakephp.org CakePHP(tm) Project
18 * @package       cake
19 * @subpackage    cake.cake
20 * @since         CakePHP(tm) v 0.2.9
21 * @license       MIT License (http://www.opensource.org/licenses/mit-license.php)
22 */
23
24/**
25 * List of helpers to include
26 */
27App::import('Core', 'Router');
28App::import('Controller', 'Controller', false);
29
30/**
31 * Dispatcher translates URLs to controller-action-paramter triads.
32 *
33 * Dispatches the request, creating appropriate models and controllers.
34 *
35 * @package       cake
36 * @subpackage    cake.cake
37 */
38class Dispatcher extends Object {
39
40/**
41 * Base URL
42 *
43 * @var string
44 * @access public
45 */
46        var $base = false;
47
48/**
49 * webroot path
50 *
51 * @var string
52 * @access public
53 */
54        var $webroot = '/';
55
56/**
57 * Current URL
58 *
59 * @var string
60 * @access public
61 */
62        var $here = false;
63
64/**
65 * the params for this request
66 *
67 * @var string
68 * @access public
69 */
70        var $params = null;
71
72/**
73 * Constructor.
74 */
75        function __construct($url = null, $base = false) {
76                if ($base !== false) {
77                        Configure::write('App.base', $base);
78                }
79                if ($url !== null) {
80                        return $this->dispatch($url);
81                }
82        }
83
84/**
85 * Dispatches and invokes given URL, handing over control to the involved controllers, and then renders the
86 * results (if autoRender is set).
87 *
88 * If no controller of given name can be found, invoke() shows error messages in
89 * the form of Missing Controllers information. It does the same with Actions (methods of Controllers are called
90 * Actions).
91 *
92 * @param string $url URL information to work on
93 * @param array $additionalParams Settings array ("bare", "return") which is melded with the GET and POST params
94 * @return boolean Success
95 * @access public
96 */
97        function dispatch($url = null, $additionalParams = array()) {
98                if ($this->base === false) {
99                        $this->base = $this->baseUrl();
100                }
101
102                if (is_array($url)) {
103                        $url = $this->__extractParams($url, $additionalParams);
104                } else {
105                        if ($url) {
106                                $_GET['url'] = $url;
107                        }
108                        $url = $this->getUrl();
109                        $this->params = array_merge($this->parseParams($url), $additionalParams);
110                }
111                $this->here = $this->base . '/' . $url;
112
113                if ($this->asset($url) || $this->cached($url)) {
114                        return;
115                }
116                $controller =& $this->__getController();
117
118                if (!is_object($controller)) {
119                        Router::setRequestInfo(array($this->params, array('base' => $this->base, 'webroot' => $this->webroot)));
120                        return $this->cakeError('missingController', array(array(
121                                'className' => Inflector::camelize($this->params['controller']) . 'Controller',
122                                'webroot' => $this->webroot,
123                                'url' => $url,
124                                'base' => $this->base
125                        )));
126                }
127                $privateAction = $this->params['action'][0] === '_';
128                $prefixes = Router::prefixes();
129
130                if (!empty($prefixes)) {
131                        if (isset($this->params['prefix'])) {
132                                $this->params['action'] = $this->params['prefix'] . '_' . $this->params['action'];
133                        } elseif (strpos($this->params['action'], '_') > 0) {
134                                list($prefix, $action) = explode('_', $this->params['action']);
135                                $privateAction = in_array($prefix, $prefixes);
136                        }
137                }
138
139                Router::setRequestInfo(array(
140                        $this->params, array('base' => $this->base, 'here' => $this->here, 'webroot' => $this->webroot)
141                ));
142
143                if ($privateAction) {
144                        return $this->cakeError('privateAction', array(array(
145                                'className' => Inflector::camelize($this->params['controller'] . "Controller"),
146                                'action' => $this->params['action'],
147                                'webroot' => $this->webroot,
148                                'url' => $url,
149                                'base' => $this->base
150                        )));
151                }
152                $controller->base = $this->base;
153                $controller->here = $this->here;
154                $controller->webroot = $this->webroot;
155                $controller->plugin = isset($this->params['plugin']) ? $this->params['plugin'] : null;
156                $controller->params =& $this->params;
157                $controller->action =& $this->params['action'];
158                $controller->passedArgs = array_merge($this->params['pass'], $this->params['named']);
159
160                if (!empty($this->params['data'])) {
161                        $controller->data =& $this->params['data'];
162                } else {
163                        $controller->data = null;
164                }
165                if (isset($this->params['return']) && $this->params['return'] == 1) {
166                        $controller->autoRender = false;
167                }
168                if (!empty($this->params['bare'])) {
169                        $controller->autoLayout = false;
170                }
171                return $this->_invoke($controller, $this->params);
172        }
173
174/**
175 * Initializes the components and models a controller will be using.
176 * Triggers the controller action, and invokes the rendering if Controller::$autoRender is true and echo's the output.
177 * Otherwise the return value of the controller action are returned.
178 *
179 * @param object $controller Controller to invoke
180 * @param array $params Parameters with at least the 'action' to invoke
181 * @param boolean $missingAction Set to true if missing action should be rendered, false otherwise
182 * @return string Output as sent by controller
183 * @access protected
184 */
185        function _invoke(&$controller, $params) {
186                $controller->constructClasses();
187                $controller->startupProcess();
188
189                $methods = array_flip($controller->methods);
190
191                if (!isset($methods[strtolower($params['action'])])) {
192                        if ($controller->scaffold !== false) {
193                                App::import('Controller', 'Scaffold', false);
194                                return new Scaffold($controller, $params);
195                        }
196                        return $this->cakeError('missingAction', array(array(
197                                'className' => Inflector::camelize($params['controller']."Controller"),
198                                'action' => $params['action'],
199                                'webroot' => $this->webroot,
200                                'url' => $this->here,
201                                'base' => $this->base
202                        )));
203                }
204                $output = call_user_func_array(array(&$controller, $params['action']), $params['pass']);
205
206                if ($controller->autoRender) {
207                        $controller->output = $controller->render();
208                } elseif (empty($controller->output)) {
209                        $controller->output = $output;
210                }
211                $controller->shutdownProcess();
212
213                if (isset($params['return'])) {
214                        return $controller->output;
215                }
216                echo($controller->output);
217        }
218
219/**
220 * Sets the params when $url is passed as an array to Object::requestAction();
221 * Merges the $url and $additionalParams and creates a string url.
222 *
223 * @param array $url Array or request parameters
224 * @param array $additionalParams Array of additional parameters.
225 * @return string $url The generated url string.
226 * @access private
227 */
228        function __extractParams($url, $additionalParams = array()) {
229                $defaults = array('pass' => array(), 'named' => array(), 'form' => array());
230                $params = array_merge($defaults, $url, $additionalParams);
231                $this->params = $params;
232
233                $params += array('base' => false, 'url' => array());
234                return ltrim(Router::reverse($params), '/');
235        }
236
237/**
238 * Returns array of GET and POST parameters. GET parameters are taken from given URL.
239 *
240 * @param string $fromUrl URL to mine for parameter information.
241 * @return array Parameters found in POST and GET.
242 * @access public
243 */
244        function parseParams($fromUrl) {
245                $params = array();
246
247                if (isset($_POST)) {
248                        $params['form'] = $_POST;
249                        if (ini_get('magic_quotes_gpc') === '1') {
250                                $params['form'] = stripslashes_deep($params['form']);
251                        }
252                        if (env('HTTP_X_HTTP_METHOD_OVERRIDE')) {
253                                $params['form']['_method'] = env('HTTP_X_HTTP_METHOD_OVERRIDE');
254                        }
255                        if (isset($params['form']['_method'])) {
256                                if (!empty($_SERVER)) {
257                                        $_SERVER['REQUEST_METHOD'] = $params['form']['_method'];
258                                } else {
259                                        $_ENV['REQUEST_METHOD'] = $params['form']['_method'];
260                                }
261                                unset($params['form']['_method']);
262                        }
263                }
264                $namedExpressions = Router::getNamedExpressions();
265                extract($namedExpressions);
266                include CONFIGS . 'routes.php';
267                $params = array_merge(array('controller' => '', 'action' => ''), Router::parse($fromUrl), $params);
268
269                if (empty($params['action'])) {
270                        $params['action'] = 'index';
271                }
272                if (isset($params['form']['data'])) {
273                        $params['data'] = $params['form']['data'];
274                        unset($params['form']['data']);
275                }
276                if (isset($_GET)) {
277                        if (ini_get('magic_quotes_gpc') === '1') {
278                                $url = stripslashes_deep($_GET);
279                        } else {
280                                $url = $_GET;
281                        }
282                        if (isset($params['url'])) {
283                                $params['url'] = array_merge($params['url'], $url);
284                        } else {
285                                $params['url'] = $url;
286                        }
287                }
288
289                foreach ($_FILES as $name => $data) {
290                        if ($name != 'data') {
291                                $params['form'][$name] = $data;
292                        }
293                }
294
295                if (isset($_FILES['data'])) {
296                        foreach ($_FILES['data'] as $key => $data) {
297                                foreach ($data as $model => $fields) {
298                                        if (is_array($fields)) {
299                                                foreach ($fields as $field => $value) {
300                                                        if (is_array($value)) {
301                                                                foreach ($value as $k => $v) {
302                                                                        $params['data'][$model][$field][$k][$key] = $v;
303                                                                }
304                                                        } else {
305                                                                $params['data'][$model][$field][$key] = $value;
306                                                        }
307                                                }
308                                        } else {
309                                                $params['data'][$model][$key] = $fields;
310                                        }
311                                }
312                        }
313                }
314                return $params;
315        }
316
317/**
318 * Returns a base URL and sets the proper webroot
319 *
320 * @return string Base URL
321 * @access public
322 */
323        function baseUrl() {
324                $dir = $webroot = null;
325                $config = Configure::read('App');
326                extract($config);
327
328                if (!$base) {
329                        $base = $this->base;
330                }
331                if ($base !== false) {
332                        $this->webroot = $base . '/';
333                        return $this->base = $base;
334                }
335                if (!$baseUrl) {
336                        $replace = array('<', '>', '*', '\'', '"');
337                        $base = str_replace($replace, '', dirname(env('PHP_SELF')));
338
339                        if ($webroot === 'webroot' && $webroot === basename($base)) {
340                                $base = dirname($base);
341                        }
342                        if ($dir === 'app' && $dir === basename($base)) {
343                                $base = dirname($base);
344                        }
345
346                        if ($base === DS || $base === '.') {
347                                $base = '';
348                        }
349
350                        $this->webroot = $base .'/';
351                        return $base;
352                }
353
354                $file = '/' . basename($baseUrl);
355                $base = dirname($baseUrl);
356
357                if ($base === DS || $base === '.') {
358                        $base = '';
359                }
360                $this->webroot = $base . '/';
361
362                $docRoot = realpath(env('DOCUMENT_ROOT'));
363                $docRootContainsWebroot = strpos($docRoot, $dir . '/' . $webroot);
364
365                if (!empty($base) || !$docRootContainsWebroot) {
366                        if (strpos($this->webroot, $dir) === false) {
367                                $this->webroot .= $dir . '/' ;
368                        }
369                        if (strpos($this->webroot, $webroot) === false) {
370                                $this->webroot .= $webroot . '/';
371                        }
372                }
373                return $base . $file;
374        }
375
376/**
377 * Get controller to use, either plugin controller or application controller
378 *
379 * @param array $params Array of parameters
380 * @return mixed name of controller if not loaded, or object if loaded
381 * @access private
382 */
383        function &__getController() {
384                $controller = false;
385                $ctrlClass = $this->__loadController($this->params);
386                if (!$ctrlClass) {
387                        return $controller;
388                }
389                $ctrlClass .= 'Controller';
390                if (class_exists($ctrlClass)) {
391                        $controller =& new $ctrlClass();
392                }
393                return $controller;
394        }
395
396/**
397 * Load controller and return controller classname
398 *
399 * @param array $params Array of parameters
400 * @return string|bool Name of controller class name
401 * @access private
402 */
403        function __loadController($params) {
404                $pluginName = $pluginPath = $controller = null;
405                if (!empty($params['plugin'])) {
406                        $pluginName = $controller = Inflector::camelize($params['plugin']);
407                        $pluginPath = $pluginName . '.';
408                }
409                if (!empty($params['controller'])) {
410                        $controller = Inflector::camelize($params['controller']);
411                }
412                if ($pluginPath . $controller) {
413                        if (App::import('Controller', $pluginPath . $controller)) {
414                                return $controller;
415                        }
416                }
417                return false;
418        }
419
420/**
421 * Returns the REQUEST_URI from the server environment, or, failing that,
422 * constructs a new one, using the PHP_SELF constant and other variables.
423 *
424 * @return string URI
425 * @access public
426 */
427        function uri() {
428                foreach (array('HTTP_X_REWRITE_URL', 'REQUEST_URI', 'argv') as $var) {
429                        if ($uri = env($var)) {
430                                if ($var == 'argv') {
431                                        $uri = $uri[0];
432                                }
433                                break;
434                        }
435                }
436                $base = preg_replace('/^\//', '', '' . Configure::read('App.baseUrl'));
437
438                if ($base) {
439                        $uri = preg_replace('/^(?:\/)?(?:' . preg_quote($base, '/') . ')?(?:url=)?/', '', $uri);
440                }
441                if (PHP_SAPI == 'isapi') {
442                        $uri = preg_replace('/^(?:\/)?(?:\/)?(?:\?)?(?:url=)?/', '', $uri);
443                }
444                if (!empty($uri)) {
445                        if (key($_GET) && strpos(key($_GET), '?') !== false) {
446                                unset($_GET[key($_GET)]);
447                        }
448                        $uri = explode('?', $uri, 2);
449
450                        if (isset($uri[1])) {
451                                parse_str($uri[1], $_GET);
452                        }
453                        $uri = $uri[0];
454                } else {
455                        $uri = env('QUERY_STRING');
456                }
457                if (is_string($uri) && strpos($uri, 'index.php') !== false) {
458                        list(, $uri) = explode('index.php', $uri, 2);
459                }
460                if (empty($uri) || $uri == '/' || $uri == '//') {
461                        return '';
462                }
463                return str_replace('//', '/', '/' . $uri);
464        }
465
466/**
467 * Returns and sets the $_GET[url] derived from the REQUEST_URI
468 *
469 * @param string $uri Request URI
470 * @param string $base Base path
471 * @return string URL
472 * @access public
473 */
474        function getUrl($uri = null, $base = null) {
475                if (empty($_GET['url'])) {
476                        if ($uri == null) {
477                                $uri = $this->uri();
478                        }
479                        if ($base == null) {
480                                $base = $this->base;
481                        }
482                        $url = null;
483                        $tmpUri = preg_replace('/^(?:\?)?(?:\/)?/', '', $uri);
484                        $baseDir = preg_replace('/^\//', '', dirname($base)) . '/';
485
486                        if ($tmpUri === '/' || $tmpUri == $baseDir || $tmpUri == $base) {
487                                $url = $_GET['url'] = '/';
488                        } else {
489                                if ($base && strpos($uri, $base) === 0) {
490                                        $elements = explode($base, $uri, 2);
491                                } elseif (preg_match('/^[\/\?\/|\/\?|\?\/]/', $uri)) {
492                                        $elements = array(1 => preg_replace('/^[\/\?\/|\/\?|\?\/]/', '', $uri));
493                                } else {
494                                        $elements = array();
495                                }
496
497                                if (!empty($elements[1])) {
498                                        $_GET['url'] = $elements[1];
499                                        $url = $elements[1];
500                                } else {
501                                        $url = $_GET['url'] = '/';
502                                }
503
504                                if (strpos($url, '/') === 0 && $url != '/') {
505                                        $url = $_GET['url'] = substr($url, 1);
506                                }
507                        }
508                } else {
509                        $url = $_GET['url'];
510                }
511                if ($url{0} == '/') {
512                        $url = substr($url, 1);
513                }
514                return $url;
515        }
516
517/**
518 * Outputs cached dispatch view cache
519 *
520 * @param string $url Requested URL
521 * @access public
522 */
523        function cached($url) {
524                if (Configure::read('Cache.check') === true) {
525                        $path = $this->here;
526                        if ($this->here == '/') {
527                                $path = 'home';
528                        }
529                        $path = strtolower(Inflector::slug($path));
530
531                        $filename = CACHE . 'views' . DS . $path . '.php';
532
533                        if (!file_exists($filename)) {
534                                $filename = CACHE . 'views' . DS . $path . '_index.php';
535                        }
536
537                        if (file_exists($filename)) {
538                                if (!class_exists('View')) {
539                                        App::import('View', 'View', false);
540                                }
541                                $controller = null;
542                                $view =& new View($controller);
543                                $return = $view->renderCache($filename, getMicrotime());
544                                if (!$return) {
545                                        ClassRegistry::removeObject('view');
546                                }
547                                return $return;
548                        }
549                }
550                return false;
551        }
552
553/**
554 * Checks if a requested asset exists and sends it to the browser
555 *
556 * @param $url string $url Requested URL
557 * @return boolean True on success if the asset file was found and sent
558 * @access public
559 */
560        function asset($url) {
561                if (strpos($url, '..') !== false || strpos($url, '.') === false) {
562                        return false;
563                }
564                $filters = Configure::read('Asset.filter');
565                $isCss = (
566                        strpos($url, 'ccss/') === 0 ||
567                        preg_match('#^(theme/([^/]+)/ccss/)|(([^/]+)(?<!css)/ccss)/#i', $url)
568                );
569                $isJs = (
570                        strpos($url, 'cjs/') === 0 ||
571                        preg_match('#^/((theme/[^/]+)/cjs/)|(([^/]+)(?<!js)/cjs)/#i', $url)
572                );
573
574                if (($isCss && empty($filters['css'])) || ($isJs && empty($filters['js']))) {
575                        header('HTTP/1.1 404 Not Found');
576                        return $this->_stop();
577                } elseif ($isCss) {
578                        include WWW_ROOT . DS . $filters['css'];
579                        $this->_stop();
580                } elseif ($isJs) {
581                        include WWW_ROOT . DS . $filters['js'];
582                        $this->_stop();
583                }
584                $controller = null;
585                $ext = array_pop(explode('.', $url));
586                $parts = explode('/', $url);
587                $assetFile = null;
588
589                if ($parts[0] === 'theme') {
590                        $themeName = $parts[1];
591                        unset($parts[0], $parts[1]);
592                        $fileFragment = implode(DS, $parts);
593                        $path = App::themePath($themeName) . 'webroot' . DS;
594                        if (file_exists($path . $fileFragment)) {
595                                $assetFile = $path . $fileFragment;
596                        }
597                } else {
598                        $plugin = $parts[0];
599                        unset($parts[0]);
600                        $fileFragment = implode(DS, $parts);
601                        $pluginWebroot = App::pluginPath($plugin) . 'webroot' . DS;
602                        if (file_exists($pluginWebroot . $fileFragment)) {
603                                $assetFile = $pluginWebroot . $fileFragment;
604                        }
605                }
606
607                if ($assetFile !== null) {
608                        $this->_deliverAsset($assetFile, $ext);
609                        return true;
610                }
611                return false;
612        }
613
614/**
615 * Sends an asset file to the client
616 *
617 * @param string $assetFile Path to the asset file in the file system
618 * @param string $ext The extension of the file to determine its mime type
619 * @return void
620 * @access protected
621 */
622        function _deliverAsset($assetFile, $ext) {
623                $ob = @ini_get("zlib.output_compression") !== '1' && extension_loaded("zlib") && (strpos(env('HTTP_ACCEPT_ENCODING'), 'gzip') !== false);
624                $compressionEnabled = $ob && Configure::read('Asset.compress');
625                if ($compressionEnabled) {
626                        ob_start();
627                        ob_start('ob_gzhandler');
628                }
629
630                App::import('View', 'Media');
631                $controller = null;
632                $Media = new MediaView($controller);
633                if (isset($Media->mimeType[$ext])) {
634                        $contentType = $Media->mimeType[$ext];
635                } else {
636                        $contentType = 'application/octet-stream';
637                        $agent = env('HTTP_USER_AGENT');
638                        if (preg_match('%Opera(/| )([0-9].[0-9]{1,2})%', $agent) || preg_match('/MSIE ([0-9].[0-9]{1,2})/', $agent)) {
639                                $contentType = 'application/octetstream';
640                        }
641                }
642
643                header("Date: " . date("D, j M Y G:i:s ", filemtime($assetFile)) . 'GMT');
644                header('Content-type: ' . $contentType);
645                header("Expires: " . gmdate("D, j M Y H:i:s", time() + DAY) . " GMT");
646                header("Cache-Control: cache");
647                header("Pragma: cache");
648
649                if ($ext === 'css' || $ext === 'js') {
650                        include($assetFile);
651                } else {
652                        if ($compressionEnabled) {
653                                ob_clean();
654                        }
655                        readfile($assetFile);
656                }
657
658                if ($compressionEnabled) {
659                        ob_end_flush();
660                }
661        }
662}
Note: See TracBrowser for help on using the repository browser.