83962e3facd4ad05a9227d1df511ba944c56d13d
[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 }
83
84 /**
85 * Creates a request an dispatches it to a controller.
86 *
87 * @param string $content The content
88 * @param array $configuration The TS configuration array
89 * @return string $content The processed content
90 */
91 public function dispatch($content, $configuration) {
92 // FIXME Remove the next lines. These are only there to generate the ext_autoload.php file
93 //$extutil = new Tx_Extbase_Utility_Extension;
94 //$extutil->createAutoloadRegistryForExtension('extbase', t3lib_extMgm::extPath('extbase'));
95 //$extutil->createAutoloadRegistryForExtension('fluid', t3lib_extMgm::extPath('fluid'));
96
97 if (!is_array($configuration)) {
98 t3lib_div::sysLog('Extbase was not able to dispatch the request. No configuration.', 'extbase', t3lib_div::SYSLOG_SEVERITY_ERROR);
99 return $content;
100 }
101 $this->initializeConfigurationManagerAndFrameworkConfiguration($configuration);
102
103 $requestBuilder = t3lib_div::makeInstance('Tx_Extbase_MVC_Web_RequestBuilder');
104 $request = $requestBuilder->initialize($configuration);
105 $request = $requestBuilder->build();
106 $response = t3lib_div::makeInstance('Tx_Extbase_MVC_Web_Response');
107
108 $this->initializeCache();
109 $this->initializeReflection();
110
111 // Request hash service
112 $requestHashService = t3lib_div::makeInstance('Tx_Extbase_Security_Channel_RequestHashService'); // singleton
113 $requestHashService->verifyRequest($request);
114
115 $persistenceManager = self::getPersistenceManager();
116
117 $dispatchLoopCount = 0;
118 while (!$request->isDispatched()) {
119 if ($dispatchLoopCount++ > 99) throw new Tx_Extbase_MVC_Exception_InfiniteLoop('Could not ultimately dispatch the request after ' . $dispatchLoopCount . ' iterations.', 1217839467);
120 $controller = $this->getPreparedController($request);
121 try {
122 $controller->processRequest($request, $response);
123 } catch (Tx_Extbase_MVC_Exception_StopAction $ignoredException) {
124 }
125 }
126
127 $flashMessages = t3lib_div::makeInstance('Tx_Extbase_MVC_Controller_FlashMessages'); // singleton
128 $flashMessages->persist();
129
130 $persistenceManager->persistAll();
131 self::$reflectionService->shutdown();
132 if (count($response->getAdditionalHeaderData()) > 0) {
133 $GLOBALS['TSFE']->additionalHeaderData[$request->getControllerExtensionName()] = implode("\n", $response->getAdditionalHeaderData());
134 }
135 $response->sendHeaders();
136 return $response->getContent();
137 }
138
139 /**
140 * Initializes the autoload mechanism of Extbase. This is supplement to the core autoloader.
141 *
142 * @return void
143 */
144 protected function initializeClassLoader() {
145 if (!class_exists('Tx_Extbase_Utility_ClassLoader')) {
146 require(t3lib_extmgm::extPath('extbase') . 'Classes/Utility/ClassLoader.php');
147 }
148
149 $classLoader = new Tx_Extbase_Utility_ClassLoader();
150 spl_autoload_register(array($classLoader, 'loadClass'));
151 }
152
153 /**
154 * Initializes the configuration manager and the Extbase settings
155 *
156 * @param $configuration The current incoming configuration
157 * @return void
158 */
159 protected function initializeConfigurationManagerAndFrameworkConfiguration($configuration) {
160 $configurationSources = array();
161 $configurationSources[] = t3lib_div::makeInstance('Tx_Extbase_Configuration_Source_TypoScriptSource');
162 if (TYPO3_MODE === 'FE') {
163 if (!empty($this->cObj->data['pi_flexform'])) {
164 $configurationSource = t3lib_div::makeInstance('Tx_Extbase_Configuration_Source_FlexFormSource');
165 $configurationSource->setFlexFormContent($this->cObj->data['pi_flexform']);
166 $configurationSources[] = $configurationSource;
167 }
168 self::$configurationManager = t3lib_div::makeInstance('Tx_Extbase_Configuration_FrontendConfigurationManager', $configurationSources);
169 self::$configurationManager->setContentObject($this->cObj);
170 } else {
171 self::$configurationManager = t3lib_div::makeInstance('Tx_Extbase_Configuration_BackendConfigurationManager', $configurationSources);
172 }
173 self::$extbaseFrameworkConfiguration = self::$configurationManager->getFrameworkConfiguration($configuration);
174 }
175
176 /**
177 * Initializes the cache framework
178 *
179 * @return void
180 */
181 public function initializeCache() {
182 $this->cacheManager = $GLOBALS['typo3CacheManager'];
183 try {
184 $this->cacheManager->getCache('cache_extbase_reflection');
185 } catch (t3lib_cache_exception_NoSuchCache $exception) {
186 $GLOBALS['typo3CacheFactory']->create(
187 'cache_extbase_reflection',
188 't3lib_cache_frontend_VariableFrontend',
189 $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['cache_extbase_reflection']['backend'],
190 $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['cache_extbase_reflection']['options']
191 );
192 }
193 }
194
195 /**
196 * Initializes the Reflection Service
197 *
198 * @return void
199 * @see initialize()
200 */
201 public function initializeReflection() {
202 self::$reflectionService = t3lib_div::makeInstance('Tx_Extbase_Reflection_Service');
203 self::$reflectionService->setCache($this->cacheManager->getCache('cache_extbase_reflection'));
204 self::$reflectionService->initialize($availableClassNames);
205 }
206
207 /**
208 * Builds and returns a controller
209 *
210 * @param Tx_Extbase_MVC_Web_Request $request
211 * @return Tx_Extbase_MVC_Controller_ControllerInterface The prepared controller
212 */
213 protected function getPreparedController(Tx_Extbase_MVC_Web_Request $request) {
214 $controllerObjectName = $request->getControllerObjectName();
215 $controller = t3lib_div::makeInstance($controllerObjectName);
216 if (!$controller instanceof Tx_Extbase_MVC_Controller_ControllerInterface) {
217 throw new Tx_Extbase_MVC_Exception_InvalidController('Invalid controller "' . $request->getControllerObjectName() . '". The controller must implement the Tx_Extbase_MVC_Controller_ControllerInterface.', 1202921619);
218 }
219 $propertyMapper = t3lib_div::makeInstance('Tx_Extbase_Property_Mapper');
220 $propertyMapper->injectReflectionService(self::$reflectionService);
221 $controller->injectPropertyMapper($propertyMapper);
222 $controller->injectSettings(self::$configurationManager->getSettings($request->getControllerExtensionName()));
223
224 $flashMessages = t3lib_div::makeInstance('Tx_Extbase_MVC_Controller_FlashMessages'); // singleton
225 $flashMessages->reset();
226 $controller->injectFlashMessages($flashMessages);
227
228 $objectManager = t3lib_div::makeInstance('Tx_Extbase_Object_Manager');
229 $validatorResolver = t3lib_div::makeInstance('Tx_Extbase_Validation_ValidatorResolver');
230 $validatorResolver->injectObjectManager($objectManager);
231 $validatorResolver->injectReflectionService(self::$reflectionService);
232 $controller->injectValidatorResolver($validatorResolver);
233 $controller->injectReflectionService(self::$reflectionService);
234 $controller->injectObjectManager($objectManager);
235 return $controller;
236 }
237
238 /**
239 * This function prepares and returns the Persistance Manager
240 *
241 * @return Tx_Extbase_Persistence_Manager A (singleton) instance of the Persistence Manager
242 */
243 public static function getPersistenceManager() {
244 if (self::$persistenceManager === NULL) {
245 $dataMapper = t3lib_div::makeInstance('Tx_Extbase_Persistence_Mapper_DataMapper'); // singleton
246 $dataMapper->injectReflectionService(self::$reflectionService);
247
248 $storageBackend = t3lib_div::makeInstance('Tx_Extbase_Persistence_Storage_Typo3DbBackend', $GLOBALS['TYPO3_DB']); // singleton
249 $storageBackend->injectDataMapper($dataMapper);
250
251 $qomFactory = t3lib_div::makeInstance('Tx_Extbase_Persistence_QOM_QueryObjectModelFactory', $storageBackend, $dataMapper);
252
253 $persistenceSession = t3lib_div::makeInstance('Tx_Extbase_Persistence_Session'); // singleton
254
255 $persistenceBackend = t3lib_div::makeInstance('Tx_Extbase_Persistence_Backend', $persistenceSession, $storageBackend); // singleton
256 $persistenceBackend->injectDataMapper($dataMapper);
257 $persistenceBackend->injectIdentityMap(t3lib_div::makeInstance('Tx_Extbase_Persistence_IdentityMap'));
258 $persistenceBackend->injectQueryFactory(t3lib_div::makeInstance('Tx_Extbase_Persistence_QueryFactory'));
259 $persistenceBackend->injectQOMFactory($qomFactory);
260 $persistenceBackend->injectValueFactory(t3lib_div::makeInstance('Tx_Extbase_Persistence_ValueFactory'));
261
262 $objectManager = t3lib_div::makeInstance('Tx_Extbase_Object_Manager'); // singleton
263
264 $persistenceManager = t3lib_div::makeInstance('Tx_Extbase_Persistence_Manager'); // singleton
265 $persistenceManager->injectBackend($persistenceBackend);
266 $persistenceManager->injectSession($persistenceSession);
267 $persistenceManager->injectObjectManager($objectManager);
268
269 self::$persistenceManager = $persistenceManager;
270 }
271
272 return self::$persistenceManager;
273 }
274
275 /**
276 * This function returns the Configuration Manager. It is instanciated for
277 * each call to dispatch() only once.
278 *
279 * @return Tx_Extbase_Configuration_Manager An instance of the Configuration Manager
280 */
281 public static function getConfigurationManager() {
282 return self::$configurationManager;
283 }
284
285 /**
286 * This function returns the settings of Extbase
287 *
288 * @return array The settings
289 */
290 public static function getExtbaseFrameworkConfiguration() {
291 return self::$extbaseFrameworkConfiguration;
292 }
293
294 /**
295 * Calls an Extbase Backend module.
296 *
297 * @param string $module The name of the module
298 * @return void
299 */
300 public function callModule($module) {
301 if (isset($GLOBALS['TBE_MODULES'][$module])) {
302 $config = $GLOBALS['TBE_MODULES'][$module];
303
304 // Check permissions and exit if the user has no permission for entry
305 $GLOBALS['BE_USER']->modAccess($config, TRUE);
306 if (t3lib_div::_GP('id')) {
307 // Check page access
308 $id = t3lib_div::_GP('id');
309 $permClause = $GLOBALS['BE_USER']->getPagePermsClause(TRUE);
310 $access = is_array(t3lib_BEfunc::readPageAccess($id, $permClause));
311 if (!$access) {
312 t3lib_BEfunc::typo3PrintError('No Access', 'You don\'t have access to this page', 0);
313 }
314 }
315
316 // Resolve the controller/action to use
317 $controllerAction = $this->resolveControllerAction($module);
318
319 // As for SCbase modules, output of the controller/action pair should be echoed
320 echo $this->transfer($module, $controllerAction['controllerName'], $controllerAction['actionName']);
321 return TRUE;
322 } else {
323 return FALSE;
324 }
325 }
326
327 /**
328 * Resolves the controller and action to use for current call.
329 * This takes into account any function menu that has being called.
330 *
331 * @param string $module The name of the module
332 * @return array The controller/action pair to use for current call
333 */
334 protected function resolveControllerAction($module) {
335 $configuration = $GLOBALS['TBE_MODULES'][$module];
336 $fallbackControllerAction = $this->getFallbackControllerAction($configuration);
337
338 // Extract dispatcher settings from request
339 $argumentPrefix = strtolower('tx_' . $configuration['extensionName'] . '_' . $configuration['name']);
340 $dispatcherParameters = t3lib_div::_GPmerged($argumentPrefix);
341 $dispatcherControllerAction = $this->getDispatcherControllerAction($configuration, $dispatcherParameters);
342
343 // Extract module function settings from request
344 $moduleFunctionControllerAction = $this->getModuleFunctionControllerAction($module, $fallbackControllerAction['controllerName']);
345
346 // Dispatcher controller/action has precedence over default controller/action
347 $controllerAction = t3lib_div::array_merge_recursive_overrule($fallbackControllerAction, $dispatcherControllerAction, FALSE, FALSE);
348 // Module function controller/action has precedence
349 $controllerAction = t3lib_div::array_merge_recursive_overrule($controllerAction, $moduleFunctionControllerAction, FALSE, FALSE);
350
351 return $controllerAction;
352 }
353
354 /**
355 * Returns the fallback controller/action pair to be used when request does not contain
356 * any controller/action to be used or the provided parameters are not valid.
357 *
358 * @param array $configuration The module configuration
359 * @return array The controller/action pair
360 */
361 protected function getFallbackControllerAction($configuration) {
362 // Extract module settings from its registration in ext_tables.php
363 $controllers = array_keys($configuration['controllerActions']);
364 $defaultController = array_shift($controllers);
365 $actions = t3lib_div::trimExplode(',', $configuration['controllerActions'][$defaultController], TRUE);
366 $defaultAction = $actions[0];
367
368 return array(
369 'controllerName' => $defaultController,
370 'actionName' => $defaultAction,
371 );
372 }
373
374 /**
375 * Returns the controller/action pair that was specified by the request if it is valid,
376 * otherwise, will just return a blank controller/action pair meaning the default
377 * controller/action should be used instead.
378 *
379 * @param array $configuration The module configuration
380 * @param array $dispatcherParameters The dispatcher parameters
381 * @return array The controller/action pair
382 */
383 protected function getDispatcherControllerAction($configuration, $dispatcherParameters) {
384 $controllerAction = array(
385 'controllerName' => '',
386 'actionName' => '',
387 );
388
389 if (!isset($dispatcherParameters['controllerName'])) {
390 // Early return: should use fallback controller/action
391 return $controllerAction;
392 }
393
394 // Extract configured controllers from module's registration in ext_tables.php
395 $controllers = array_keys($configuration['controllerActions']);
396
397 $controller = $dispatcherParameters['controllerName'];
398 if (in_array($controller, $controllers)) {
399 // Update return value as selected controller is valid
400 $controllerAction['controllerName'] = $controller;
401 $actions = t3lib_div::trimExplode(',', $configuration['controllerActions'][$controller], TRUE);
402 if (isset($dispatcherParameters['actionName'])) {
403 // Extract configured actions for selected controllers
404 $action = $dispatcherParameters['actionName'];
405 if (in_array($action, $actions)) {
406 // Requested action is valid for selected controller
407 $controllerAction['actionName'] = $action;
408 } else {
409 // Use first action of selected controller as fallback action
410 $controllerAction['actionName'] = $actions[0];
411 }
412 } else {
413 // Use first action of selected controller as fallback action
414 $controllerAction['actionName'] = $actions[0];
415 }
416 }
417
418 return $controllerAction;
419 }
420
421 /**
422 * Returns the controller/action pair to use if a module function parameter is found
423 * in the request, otherwise, will just return a blank controller/action pair.
424 *
425 * @param string $module The name of the module
426 * @param string $defaultController The module's default controller
427 * @return array The controller/action pair
428 */
429 protected function getModuleFunctionControllerAction($module, $defaultController) {
430 $controllerAction = array(
431 'controllerName' => '',
432 'actionName' => '',
433 );
434
435 $set = t3lib_div::_GP('SET');
436 if (!$set) {
437 // Early return
438 return $controllerAction;
439 }
440
441 $moduleFunction = $set['function'];
442 $matches = array();
443 if (preg_match('/^(.*)->(.*)$/', $moduleFunction, $matches)) {
444 $controllerAction['controllerName'] = $matches[1];
445 $controllerAction['actionName'] = $matches[2];
446 } else {
447 // Support for external SCbase module function rendering
448 $functions = $GLOBALS['TBE_MODULES_EXT'][$module]['MOD_MENU']['function'];
449 if (isset($functions[$moduleFunction])) {
450 $controllerAction['controllerName'] = $defaultController;
451 $controllerAction['actionName'] = 'extObj';
452 }
453 }
454
455 return $controllerAction;
456 }
457
458 /**
459 * Transfers the request to an Extbase backend module, calling
460 * a given controller/action.
461 *
462 * @param string $module The name of the module
463 * @param string $controller The controller to use
464 * @param string $action The controller's action to execute
465 * @return string The module rendered view
466 */
467 protected function transfer($module, $controller, $action) {
468 $config = $GLOBALS['TBE_MODULES'][$module];
469
470 $extbaseConfiguration = array(
471 'userFunc' => 'tx_extbase_dispatcher->dispatch',
472 'pluginName' => $module,
473 'extensionName' => $config['extensionName'],
474 'controller' => $controller,
475 'action' => $action,
476 'switchableControllerActions.' => array(),
477 'settings' => '< module.tx_' . strtolower($config['extensionName']) . '.settings',
478 'persistence' => '< module.tx_' . strtolower($config['extensionName']) . '.persistence',
479 'view' => '< module.tx_' . strtolower($config['extensionName']) . '.view',
480 );
481
482 $i = 1;
483 foreach ($config['controllerActions'] as $controller => $actions) {
484 // Add an "extObj" action for the default controller to handle external
485 // SCbase modules which add function menu entries
486 if ($i == 1) {
487 $actions .= ',extObj';
488 }
489 $extbaseConfiguration['switchableControllerActions.'][$i++ . '.'] = array(
490 'controller' => $controller,
491 'actions' => $actions,
492 );
493 }
494
495 // BACK_PATH is the path from the typo3/ directory from within the
496 // directory containing the controller file. We are using mod.php dispatcher
497 // and thus we are already within typo3/ because we call typo3/mod.php
498 $GLOBALS['BACK_PATH'] = '';
499 return $this->dispatch('', $extbaseConfiguration);
500 }
501
502 }
503 ?>