cfc721da6eb1c1a291b3fa1aad8c89f2fdeb18a0
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Dispatcher.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2009 Jochen Rau <jochen.rau@typoplanet.de>
6 * All rights reserved
7 *
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 *
17 * This script is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * This copyright notice MUST APPEAR in all copies of the script!
23 ***************************************************************/
24
25 /**
26 * Creates a request an dispatches it to the controller which was specified
27 * by TS Setup, Flexform and returns the content to the v4 framework.
28 *
29 * This class is the main entry point for extbase extensions.
30 *
31 * @package Extbase
32 * @version $ID:$
33 */
34 class Tx_Extbase_Dispatcher {
35
36 /**
37 * Back reference to the parent content object
38 * This has to be public as it is set directly from TYPO3
39 *
40 * @var tslib_cObj
41 */
42 public $cObj;
43
44 /**
45 * @var Tx_Extbase_Utility_ClassLoader
46 */
47 protected $classLoader;
48
49 /**
50 * @var Tx_Extbase_Configuration_AbstractConfigurationManager
51 */
52 protected static $configurationManager;
53
54 /**
55 * @var t3lib_cache_Manager
56 */
57 protected $cacheManager;
58
59 /**
60 * @var Tx_Extbase_Reflection_Service
61 */
62 protected static $reflectionService;
63
64 /**
65 * @var Tx_Extbase_Persistence_Manager
66 */
67 protected static $persistenceManager;
68
69 /**
70 * The configuration for the Extbase framework
71 * @var array
72 */
73 protected static $extbaseFrameworkConfiguration;
74
75
76 /**
77 * Constructs this Dispatcher and registers the autoloader
78 */
79 public function __construct() {
80 t3lib_cache::initializeCachingFramework();
81 $this->initializeClassLoader();
82 $this->initializeCache();
83 $this->initializeReflection();
84 }
85
86 /**
87 * Creates a request an dispatches it to a controller.
88 *
89 * @param string $content The content
90 * @param array $configuration The TS configuration array
91 * @return string $content The processed content
92 */
93 public function dispatch($content, $configuration) {
94 // FIXME Remove the next lines. These are only there to generate the ext_autoload.php file
95 //$extutil = new Tx_Extbase_Utility_Extension;
96 //$extutil->createAutoloadRegistryForExtension('extbase', t3lib_extMgm::extPath('extbase'));
97 //$extutil->createAutoloadRegistryForExtension('fluid', t3lib_extMgm::extPath('fluid'));
98
99 $this->timeTrackPush('Extbase is called.','');
100 $this->timeTrackPush('Extbase gets initialized.','');
101
102 if (!is_array($configuration)) {
103 t3lib_div::sysLog('Extbase was not able to dispatch the request. No configuration.', 'extbase', t3lib_div::SYSLOG_SEVERITY_ERROR);
104 return $content;
105 }
106
107 $this->initializeConfigurationManagerAndFrameworkConfiguration($configuration);
108
109 $requestBuilder = t3lib_div::makeInstance('Tx_Extbase_MVC_Web_RequestBuilder');
110 $request = $requestBuilder->initialize(self::$extbaseFrameworkConfiguration);
111 $request = $requestBuilder->build();
112 if (isset($this->cObj->data) && is_array($this->cObj->data)) {
113 // we need to check the above conditions as cObj is not available in Backend.
114 $request->setContentObjectData($this->cObj->data);
115 }
116 $response = t3lib_div::makeInstance('Tx_Extbase_MVC_Web_Response');
117
118 // Request hash service
119 $requestHashService = t3lib_div::makeInstance('Tx_Extbase_Security_Channel_RequestHashService'); // singleton
120 $requestHashService->verifyRequest($request);
121
122 $persistenceManager = self::getPersistenceManager();
123
124 $this->timeTrackPull();
125
126 $this->timeTrackPush('Extbase dispatches request.','');
127 $dispatchLoopCount = 0;
128 while (!$request->isDispatched()) {
129 if ($dispatchLoopCount++ > 99) throw new Tx_Extbase_MVC_Exception_InfiniteLoop('Could not ultimately dispatch the request after ' . $dispatchLoopCount . ' iterations.', 1217839467);
130 $controller = $this->getPreparedController($request);
131 try {
132 $controller->processRequest($request, $response);
133 } catch (Tx_Extbase_MVC_Exception_StopAction $ignoredException) {
134 }
135 }
136 $this->timeTrackPull();
137
138 $this->timeTrackPush('Extbase persists all changes.','');
139 $flashMessages = t3lib_div::makeInstance('Tx_Extbase_MVC_Controller_FlashMessages'); // singleton
140 $flashMessages->persist();
141 $persistenceManager->persistAll();
142 $this->timeTrackPull();
143
144 self::$reflectionService->shutdown();
145
146 if (substr($response->getStatus(), 0, 3) === '303') {
147 $response->sendHeaders();
148 exit;
149 }
150
151 if (count($response->getAdditionalHeaderData()) > 0) {
152 $GLOBALS['TSFE']->additionalHeaderData[$request->getControllerExtensionName()] = implode("\n", $response->getAdditionalHeaderData());
153 }
154 $this->timeTrackPull();
155 return $response->getContent();
156 }
157
158 /**
159 * Initializes the autoload mechanism of Extbase. This is supplement to the core autoloader.
160 *
161 * @return void
162 */
163 protected function initializeClassLoader() {
164 if (!class_exists('Tx_Extbase_Utility_ClassLoader')) {
165 require(t3lib_extmgm::extPath('extbase') . 'Classes/Utility/ClassLoader.php');
166 }
167
168 $classLoader = new Tx_Extbase_Utility_ClassLoader();
169 spl_autoload_register(array($classLoader, 'loadClass'));
170 }
171
172 /**
173 * Initializes the configuration manager and the Extbase settings
174 *
175 * @param $configuration The current incoming configuration
176 * @return void
177 */
178 protected function initializeConfigurationManagerAndFrameworkConfiguration($configuration) {
179 if (TYPO3_MODE === 'FE') {
180 self::$configurationManager = t3lib_div::makeInstance('Tx_Extbase_Configuration_FrontendConfigurationManager');
181 self::$configurationManager->setContentObject($this->cObj);
182 } else {
183 self::$configurationManager = t3lib_div::makeInstance('Tx_Extbase_Configuration_BackendConfigurationManager');
184 }
185 self::$extbaseFrameworkConfiguration = self::$configurationManager->getFrameworkConfiguration($configuration);
186 }
187
188 /**
189 * Initializes the cache framework
190 *
191 * @return void
192 */
193 protected function initializeCache() {
194 $this->cacheManager = $GLOBALS['typo3CacheManager'];
195 try {
196 $this->cacheManager->getCache('cache_extbase_reflection');
197 } catch (t3lib_cache_exception_NoSuchCache $exception) {
198 $GLOBALS['typo3CacheFactory']->create(
199 'cache_extbase_reflection',
200 't3lib_cache_frontend_VariableFrontend',
201 $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['cache_extbase_reflection']['backend'],
202 $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['cache_extbase_reflection']['options']
203 );
204 }
205 }
206
207 /**
208 * Initializes the Reflection Service
209 *
210 * @return void
211 */
212 protected function initializeReflection() {
213 self::$reflectionService = t3lib_div::makeInstance('Tx_Extbase_Reflection_Service');
214 self::$reflectionService->setCache($this->cacheManager->getCache('cache_extbase_reflection'));
215 if (!self::$reflectionService->isInitialized()) {
216 self::$reflectionService->initialize();
217 }
218 }
219
220 /**
221 * Builds and returns a controller
222 *
223 * @param Tx_Extbase_MVC_Web_Request $request
224 * @return Tx_Extbase_MVC_Controller_ControllerInterface The prepared controller
225 */
226 protected function getPreparedController(Tx_Extbase_MVC_Web_Request $request) {
227 $controllerObjectName = $request->getControllerObjectName();
228 $controller = t3lib_div::makeInstance($controllerObjectName);
229 if (!$controller instanceof Tx_Extbase_MVC_Controller_ControllerInterface) {
230 throw new Tx_Extbase_MVC_Exception_InvalidController('Invalid controller "' . $request->getControllerObjectName() . '". The controller must implement the Tx_Extbase_MVC_Controller_ControllerInterface.', 1202921619);
231 }
232 $propertyMapper = t3lib_div::makeInstance('Tx_Extbase_Property_Mapper');
233 $propertyMapper->injectReflectionService(self::$reflectionService);
234 $controller->injectPropertyMapper($propertyMapper);
235
236 $controller->injectSettings(is_array(self::$extbaseFrameworkConfiguration['settings']) ? self::$extbaseFrameworkConfiguration['settings'] : array());
237
238 $flashMessageContainer = t3lib_div::makeInstance('Tx_Extbase_MVC_Controller_FlashMessages'); // singleton
239 $flashMessageContainer->reset();
240 $controller->injectFlashMessageContainer($flashMessageContainer);
241
242 $objectManager = t3lib_div::makeInstance('Tx_Extbase_Object_Manager');
243 $validatorResolver = t3lib_div::makeInstance('Tx_Extbase_Validation_ValidatorResolver');
244 $validatorResolver->injectObjectManager($objectManager);
245 $validatorResolver->injectReflectionService(self::$reflectionService);
246 $controller->injectValidatorResolver($validatorResolver);
247 $controller->injectReflectionService(self::$reflectionService);
248 $controller->injectObjectManager($objectManager);
249 return $controller;
250 }
251
252 /**
253 * This function prepares and returns the Persistance Manager
254 *
255 * @return Tx_Extbase_Persistence_Manager A (singleton) instance of the Persistence Manager
256 */
257 public static function getPersistenceManager() {
258 if (self::$persistenceManager === NULL) {
259 $identityMap = t3lib_div::makeInstance('Tx_Extbase_Persistence_IdentityMap');
260 $persistenceSession = t3lib_div::makeInstance('Tx_Extbase_Persistence_Session'); // singleton
261
262 $dataMapper = t3lib_div::makeInstance('Tx_Extbase_Persistence_Mapper_DataMapper'); // singleton
263 $dataMapper->injectIdentityMap($identityMap);
264 $dataMapper->injectSession($persistenceSession);
265 $dataMapper->injectReflectionService(self::$reflectionService);
266 $dataMapper->injectDataMapFactory(t3lib_div::makeInstance('Tx_Extbase_Persistence_Mapper_DataMapFactory'));
267
268
269 $storageBackend = t3lib_div::makeInstance('Tx_Extbase_Persistence_Storage_Typo3DbBackend', $GLOBALS['TYPO3_DB']); // singleton
270 $storageBackend->injectDataMapper($dataMapper);
271
272 $qomFactory = t3lib_div::makeInstance('Tx_Extbase_Persistence_QOM_QueryObjectModelFactory', $storageBackend);
273
274 $dataMapper->setQomFactory($qomFactory);
275
276 $persistenceBackend = t3lib_div::makeInstance('Tx_Extbase_Persistence_Backend', $persistenceSession, $storageBackend); // singleton
277 $persistenceBackend->injectDataMapper($dataMapper);
278 $persistenceBackend->injectIdentityMap($identityMap);
279 $persistenceBackend->injectReflectionService(self::$reflectionService);
280 $persistenceBackend->injectQueryFactory(t3lib_div::makeInstance('Tx_Extbase_Persistence_QueryFactory'));
281 $persistenceBackend->injectQomFactory($qomFactory);
282
283 $objectManager = t3lib_div::makeInstance('Tx_Extbase_Object_Manager'); // singleton
284
285 $persistenceManager = t3lib_div::makeInstance('Tx_Extbase_Persistence_Manager'); // singleton
286 $persistenceManager->injectBackend($persistenceBackend);
287 $persistenceManager->injectSession($persistenceSession);
288 $persistenceManager->injectObjectManager($objectManager);
289
290 self::$persistenceManager = $persistenceManager;
291 }
292
293 return self::$persistenceManager;
294 }
295
296 /**
297 * This function returns the Configuration Manager. It is instanciated for
298 * each call to dispatch() only once.
299 *
300 * @return Tx_Extbase_Configuration_Manager An instance of the Configuration Manager
301 */
302 public static function getConfigurationManager() {
303 return self::$configurationManager;
304 }
305
306 /**
307 * This function returns the settings of Extbase
308 *
309 * @return array The settings
310 */
311 public static function getExtbaseFrameworkConfiguration() {
312 return self::$extbaseFrameworkConfiguration;
313 }
314
315 /**
316 * Calls an Extbase Backend module.
317 *
318 * @param string $module The name of the module
319 * @return void
320 */
321 public function callModule($module) {
322 if (isset($GLOBALS['TBE_MODULES'][$module])) {
323 $config = $GLOBALS['TBE_MODULES'][$module];
324
325 // Check permissions and exit if the user has no permission for entry
326 $GLOBALS['BE_USER']->modAccess($config, TRUE);
327 if (t3lib_div::_GP('id')) {
328 // Check page access
329 $id = t3lib_div::_GP('id');
330 $permClause = $GLOBALS['BE_USER']->getPagePermsClause(TRUE);
331 $access = is_array(t3lib_BEfunc::readPageAccess($id, $permClause));
332 if (!$access) {
333 t3lib_BEfunc::typo3PrintError('No Access', 'You don\'t have access to this page', 0);
334 }
335 }
336
337 // Resolve the controller/action to use
338 $controllerAction = $this->resolveControllerAction($module);
339
340 // As for SCbase modules, output of the controller/action pair should be echoed
341 echo $this->transfer($module, $controllerAction['controllerName'], $controllerAction['actionName']);
342 return TRUE;
343 } else {
344 return FALSE;
345 }
346 }
347
348 /**
349 * Resolves the controller and action to use for current call.
350 * This takes into account any function menu that has being called.
351 *
352 * @param string $module The name of the module
353 * @return array The controller/action pair to use for current call
354 */
355 protected function resolveControllerAction($module) {
356 $configuration = $GLOBALS['TBE_MODULES'][$module];
357 $fallbackControllerAction = $this->getFallbackControllerAction($configuration);
358
359 // Extract dispatcher settings from request
360 $argumentPrefix = strtolower('tx_' . $configuration['extensionName'] . '_' . $configuration['name']);
361 $dispatcherParameters = t3lib_div::_GPmerged($argumentPrefix);
362 $dispatcherControllerAction = $this->getDispatcherControllerAction($configuration, $dispatcherParameters);
363
364 // Extract module function settings from request
365 $moduleFunctionControllerAction = $this->getModuleFunctionControllerAction($module, $fallbackControllerAction['controllerName']);
366
367 // Dispatcher controller/action has precedence over default controller/action
368 $controllerAction = t3lib_div::array_merge_recursive_overrule($fallbackControllerAction, $dispatcherControllerAction, FALSE, FALSE);
369 // Module function controller/action has precedence
370 $controllerAction = t3lib_div::array_merge_recursive_overrule($controllerAction, $moduleFunctionControllerAction, FALSE, FALSE);
371
372 return $controllerAction;
373 }
374
375 /**
376 * Returns the fallback controller/action pair to be used when request does not contain
377 * any controller/action to be used or the provided parameters are not valid.
378 *
379 * @param array $configuration The module configuration
380 * @return array The controller/action pair
381 */
382 protected function getFallbackControllerAction($configuration) {
383 // Extract module settings from its registration in ext_tables.php
384 $controllers = array_keys($configuration['controllerActions']);
385 $defaultController = array_shift($controllers);
386 $actions = t3lib_div::trimExplode(',', $configuration['controllerActions'][$defaultController], TRUE);
387 $defaultAction = $actions[0];
388
389 return array(
390 'controllerName' => $defaultController,
391 'actionName' => $defaultAction,
392 );
393 }
394
395 /**
396 * Returns the controller/action pair that was specified by the request if it is valid,
397 * otherwise, will just return a blank controller/action pair meaning the default
398 * controller/action should be used instead.
399 *
400 * @param array $configuration The module configuration
401 * @param array $dispatcherParameters The dispatcher parameters
402 * @return array The controller/action pair
403 */
404 protected function getDispatcherControllerAction($configuration, $dispatcherParameters) {
405 $controllerAction = array(
406 'controllerName' => '',
407 'actionName' => '',
408 );
409
410 if (!isset($dispatcherParameters['controllerName'])) {
411 // Early return: should use fallback controller/action
412 return $controllerAction;
413 }
414
415 // Extract configured controllers from module's registration in ext_tables.php
416 $controllers = array_keys($configuration['controllerActions']);
417
418 $controller = $dispatcherParameters['controllerName'];
419 if (in_array($controller, $controllers)) {
420 // Update return value as selected controller is valid
421 $controllerAction['controllerName'] = $controller;
422 $actions = t3lib_div::trimExplode(',', $configuration['controllerActions'][$controller], TRUE);
423 if (isset($dispatcherParameters['actionName'])) {
424 // Extract configured actions for selected controllers
425 $action = $dispatcherParameters['actionName'];
426 if (in_array($action, $actions)) {
427 // Requested action is valid for selected controller
428 $controllerAction['actionName'] = $action;
429 } else {
430 // Use first action of selected controller as fallback action
431 $controllerAction['actionName'] = $actions[0];
432 }
433 } else {
434 // Use first action of selected controller as fallback action
435 $controllerAction['actionName'] = $actions[0];
436 }
437 }
438
439 return $controllerAction;
440 }
441
442 /**
443 * Returns the controller/action pair to use if a module function parameter is found
444 * in the request, otherwise, will just return a blank controller/action pair.
445 *
446 * @param string $module The name of the module
447 * @param string $defaultController The module's default controller
448 * @return array The controller/action pair
449 */
450 protected function getModuleFunctionControllerAction($module, $defaultController) {
451 $controllerAction = array(
452 'controllerName' => '',
453 'actionName' => '',
454 );
455
456 $set = t3lib_div::_GP('SET');
457 if (!$set) {
458 // Early return
459 return $controllerAction;
460 }
461
462 $moduleFunction = $set['function'];
463 $matches = array();
464 if (preg_match('/^(.*)->(.*)$/', $moduleFunction, $matches)) {
465 $controllerAction['controllerName'] = $matches[1];
466 $controllerAction['actionName'] = $matches[2];
467 } else {
468 // Support for external SCbase module function rendering
469 $functions = $GLOBALS['TBE_MODULES_EXT'][$module]['MOD_MENU']['function'];
470 if (isset($functions[$moduleFunction])) {
471 $controllerAction['controllerName'] = $defaultController;
472 $controllerAction['actionName'] = 'extObj';
473 }
474 }
475
476 return $controllerAction;
477 }
478
479 /**
480 * Transfers the request to an Extbase backend module, calling
481 * a given controller/action.
482 *
483 * @param string $module The name of the module
484 * @param string $controller The controller to use
485 * @param string $action The controller's action to execute
486 * @return string The module rendered view
487 */
488 protected function transfer($module, $controller, $action) {
489 $config = $GLOBALS['TBE_MODULES'][$module];
490
491 $extbaseConfiguration = array(
492 'userFunc' => 'tx_extbase_dispatcher->dispatch',
493 'pluginName' => $module,
494 'extensionName' => $config['extensionName'],
495 'controller' => $controller,
496 'action' => $action,
497 'switchableControllerActions.' => array(),
498 'settings' => '< module.tx_' . strtolower($config['extensionName']) . '.settings',
499 'persistence' => '< module.tx_' . strtolower($config['extensionName']) . '.persistence',
500 'view' => '< module.tx_' . strtolower($config['extensionName']) . '.view',
501 );
502
503 $i = 1;
504 foreach ($config['controllerActions'] as $controller => $actions) {
505 // Add an "extObj" action for the default controller to handle external
506 // SCbase modules which add function menu entries
507 if ($i == 1) {
508 $actions .= ',extObj';
509 }
510 $extbaseConfiguration['switchableControllerActions.'][$i++ . '.'] = array(
511 'controller' => $controller,
512 'actions' => $actions,
513 );
514 }
515
516 // BACK_PATH is the path from the typo3/ directory from within the
517 // directory containing the controller file. We are using mod.php dispatcher
518 // and thus we are already within typo3/ because we call typo3/mod.php
519 $GLOBALS['BACK_PATH'] = '';
520 return $this->dispatch('', $extbaseConfiguration);
521 }
522
523 /**
524 * Push some information to time tracking if in Frontend
525 *
526 * @param string $name
527 * @todo correct variable names
528 * @return void
529 */
530 protected function timeTrackPush($name, $param2) {
531 if (isset($GLOBALS['TT'])) {
532 $GLOBALS['TT']->push($name, $param2);
533 }
534 }
535
536 /**
537 * Time track pull
538 * @todo complete documentation of this method.
539 */
540 protected function timeTrackPull() {
541 if (isset($GLOBALS['TT'])) {
542 $GLOBALS['TT']->pull();
543 }
544 }
545
546 }
547 ?>