[+FEATURE] Extbase (MVC): Automatic target page determination
authorBastian Waidelich <bastian@typo3.org>
Thu, 23 Sep 2010 18:41:44 +0000 (18:41 +0000)
committerBastian Waidelich <bastian@typo3.org>
Thu, 23 Sep 2010 18:41:44 +0000 (18:41 +0000)
you can use the "pageUid" argument of the link.* and uri.* view helpers
to link to a different page. That is deprecated though as we won't have
the notion of "page uids" in v5. Instead the target page is now determined
automatically.
If the target page can't be determined because more than one active
plugin is capable of handling the action an exception will be thrown.
In that case you'll have to define the target page either by using the
pageUid argument or - preferably - by setting
plugin.tx_extensionname_pluginname.view.defaultPid to a fixed page uid.

Note: This feature still has to be documented!

Resolves: #9121

typo3/sysext/extbase/Classes/MVC/Web/Routing/UriBuilder.php
typo3/sysext/extbase/Classes/Utility/Extension.php
typo3/sysext/extbase/Tests/Utility/Extension_testcase.php

index 5402173..f95216d 100644 (file)
@@ -410,13 +410,19 @@ class Tx_Extbase_MVC_Web_Routing_UriBuilder {
                if ($extensionName === NULL) {
                        $extensionName = $this->request->getControllerExtensionName();
                }
+               if ($pluginName === NULL && TYPO3_MODE === 'FE') {
+                       $pluginName = Tx_Extbase_Utility_Extension::getPluginNameByAction($extensionName, $controllerArguments['controller'], $controllerArguments['action']);
+               }
                if ($pluginName === NULL) {
                        $pluginName = $this->request->getPluginName();
                }
+               $pluginSignature = strtolower($extensionName . '_' . $pluginName);
+               if ($this->targetPageUid === NULL && TYPO3_MODE === 'FE') {
+                       $this->targetPageUid = Tx_Extbase_Utility_Extension::getTargetPidByPluginSignature($pluginSignature);
+               }
                if ($this->format !== '') {
                        $controllerArguments['format'] = $this->format;
                }
-               $pluginSignature = strtolower($extensionName . '_' . $pluginName);
                $pluginNamespace = Tx_Extbase_Utility_Extension::getPluginNamespaceByPluginSignature($pluginSignature);
                $prefixedControllerArguments = array($pluginNamespace => $controllerArguments);
                $this->arguments = t3lib_div::array_merge_recursive_overrule($this->arguments, $prefixedControllerArguments);
index 461cc2e..043ec53 100644 (file)
@@ -95,6 +95,8 @@ class Tx_Extbase_Utility_Extension {
                templateRootPath =
                layoutRootPath =
                partialRootPath =
+                # the defaultPid can be an integer but defaults to the string "auto" which means that the target pid is determined automatically
+               defaultPid = auto
        }
 }';
                t3lib_extMgm::addTypoScript($extensionName, 'setup', '
@@ -480,5 +482,81 @@ tt_content.list.20.' . $pluginSignature . ' {
                return $frameworkConfiguration['view']['pluginNamespace'];
        }
 
+       /**
+        * Iterates through the global TypoScript configuration and returns the name of the plugin
+        * that matches specified extensionName, controllerName and actionName.
+        * If no matching plugin was found, NULL is returned.
+        * If more than one plugin matches, an Exception will be thrown
+        *
+        * @param string $extensionName name of the target extension (UpperCamelCase)
+        * @param string $controllerName name of the target controller (UpperCamelCase)
+        * @param string $actionName name of the target action (lowerCamelCase)
+        * @return string name of the target plugin (UpperCamelCase) or NULL if no matching plugin configuration was found
+        */
+       public static function getPluginNameByAction($extensionName, $controllerName, $actionName) {
+               if (!isset($GLOBALS['TSFE']->tmpl->setup['tt_content.']['list.']['20.']) || !is_array($GLOBALS['TSFE']->tmpl->setup['tt_content.']['list.']['20.'])) {
+                       return NULL;
+               }
+               $pluginNames = array();
+               foreach($GLOBALS['TSFE']->tmpl->setup['tt_content.']['list.']['20.'] as $pluginConfiguration) {
+                       if (!is_array($pluginConfiguration) || !isset($pluginConfiguration['switchableControllerActions.']) || !isset($pluginConfiguration['extensionName'])) {
+                               continue;
+                       }
+                       if (strtolower($extensionName) !== strtolower($pluginConfiguration['extensionName'])) {
+                               continue;
+                       }
+                       foreach($pluginConfiguration['switchableControllerActions.'] as $controller => $switchableControllerActions) {
+                               if (strtolower(rtrim($controller, '.')) !== strtolower($controllerName)) {
+                                       continue;
+                               }
+                               $actions = t3lib_div::trimExplode(',', $switchableControllerActions['actions']);
+                               if (in_array($actionName, $actions)) {
+                                       $pluginNames[] = $pluginConfiguration['pluginName'];
+                               }
+                       }
+               }
+               if (count($pluginNames) > 1) {
+                       throw new Tx_Extbase_Exception('There is more than one plugin that can handle this request (Extension: "' . $extensionName . '", Controller: "' . $controllerName . '", action: "' . $actionName . '"). Please specify "pluginName" argument' , 1280825466);
+               }
+               return count($pluginNames) > 0 ? $pluginNames[0] : NULL;
+       }
+
+       /**
+        * Determines the target page of the specified plugin.
+        * If plugin.tx_$pluginSignature.view.defaultPid is set, this value is used as target page id
+        * If defaultPid is set to "auto", a the target pid is determined by loading the tt_content record that contains this plugin
+        * If the page could not be determined, NULL is returned
+        * If defaultPid is "auto" and more than one page contains the specified plugin, an Exception is thrown
+        *
+        * @param string $pluginSignature Plugin signature: strtolower($extensionName) . '_' . strtolower($pluginName)
+        * @return integer uid of the target page or NULL if target page could not be determined
+        */
+       public static function getTargetPidByPluginSignature($pluginSignature) {
+               $configurationManager = Tx_Extbase_Dispatcher::getConfigurationManager();
+               if (!isset($configurationManager) || !isset($GLOBALS['TSFE']->tmpl->setup['tt_content.']['list.']['20.']) || !is_array($GLOBALS['TSFE']->tmpl->setup['tt_content.']['list.']['20.'])) {
+                       return NULL;
+               }
+               $pluginConfiguration = $GLOBALS['TSFE']->tmpl->setup['tt_content.']['list.']['20.'][$pluginSignature . '.'];
+               $frameworkConfiguration = $configurationManager->getFrameworkConfiguration($pluginConfiguration);
+               if (!isset($frameworkConfiguration['view']['defaultPid']) || empty($frameworkConfiguration['view']['defaultPid'])) {
+                       return NULL;
+               }
+               if ($frameworkConfiguration['view']['defaultPid'] === 'auto') {
+                       $pages = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
+                               'pid',
+                               'tt_content',
+                               'list_type=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($pluginSignature, 'tt_content') . $GLOBALS['TSFE']->sys_page->enableFields('tt_content'),
+                               '',
+                               '',
+                               2
+                       );
+                       if (count($pages) > 1) {
+                               throw new Tx_Extbase_Exception('There is more than one "' . $pluginSignature . '" plugin in the current page tree. Please remove one plugin or set the TypoScript configuration "plugin.' . $pluginSignature . '.view.defaultPid" to a fixed page id' , 1280773643);
+                       }
+                       return count($pages) > 0 ? $pages[0]['pid'] : NULL;
+               }
+               return (integer)$frameworkConfiguration['view']['defaultPid'];
+       }
 }
+
 ?>
\ No newline at end of file
index 9b31971..bb3a806 100644 (file)
@@ -67,11 +67,11 @@ class Tx_Extbase_Utility_Extension_testcase extends tx_phpunit_testcase {
                                        'field' => 'layout'),
                                        0 => '< plugin.tt_news'
                                ),
-                       'someextension_someplugin' => 'USER',
-                       'someextension_someplugin.' => array(
+                       'extensionname_someplugin' => 'USER',
+                       'extensionname_someplugin.' => array(
                                'userFunc' => 'tx_extbase_dispatcher->dispatch',
                                'pluginName' => 'SomePlugin',
-                               'extensionName' => 'SomeExtension',
+                               'extensionName' => 'ExtensionName',
                                'controller' => 'ControllerName',
                                'action' => 'index',
                                'switchableControllerActions.' => array(
@@ -105,7 +105,7 @@ class Tx_Extbase_Utility_Extension_testcase extends tx_phpunit_testcase {
                                'controller' => 'ControllerName',
                                'action' => 'index',
                                'switchableControllerActions.' => array(
-                                       'FirstController.' => array(
+                                       'ControllerName.' => array(
                                                'actions' => 'otherAction,thirdAction',
                                        ),
                                ),
@@ -170,6 +170,8 @@ plugin.tx_myextension {
                templateRootPath =
                layoutRootPath =
                partialRootPath =
+                # the defaultPid can be an integer but defaults to the string "auto" which means that the target pid is determined automatically
+               defaultPid = auto
        }
 }', $defaultTypoScript);
        }
@@ -393,6 +395,126 @@ tt_content.list.20.myextension_pi1 {', $staticTypoScript);
                $actualResult = Tx_Extbase_Utility_Extension::getPluginNamespaceByPluginSignature('somePluginSignature');
                $this->assertEquals($expectedResult, $actualResult);
        }
+
+       /**
+        * DataProvider for getPluginNameByActionTests()
+        *
+        * @return array
+        */
+       public function getPluginNameByActionDataProvider() {
+               return array(
+                       array('ExtensionName', 'ControllerName', 'someNonExistingAction', NULL),
+                       array('ExtensionName', 'ControllerName', 'index', 'SomePlugin'),
+                       array('ExtensionName', 'ControllerName', 'thirdAction', 'ThirdPlugin'),
+                       array('eXtEnSiOnNaMe', 'cOnTrOlLeRnAmE', 'thirdAction', 'ThirdPlugin'),
+                       array('eXtEnSiOnNaMe', 'cOnTrOlLeRnAmE', 'ThIrDaCtIoN', NULL),
+                       array('SomeOtherExtensionName', 'ControllerName', 'otherAction', 'SecondPlugin'),
+               );
+       }
+
+       /**
+        * @test
+        * @dataProvider getPluginNameByActionDataProvider
+        */
+       public function getPluginNameByActionTests($extensionName, $controllerName, $actionName, $expectedResult) {
+               $actualResult = Tx_Extbase_Utility_Extension::getPluginNameByAction($extensionName, $controllerName, $actionName);
+               $this->assertEquals($expectedResult, $actualResult, 'Failing for $extensionName: "' . $extensionName . '", $controllerName: "' . $controllerName . '", $actionName: "' . $actionName . '" - ');
+       }
+
+       /**
+        * @test
+        * @expectedException Tx_Extbase_Exception
+        */
+       public function getPluginNameByActionThrowsExceptionIfMoreThanOnePluginMatches() {
+               Tx_Extbase_Utility_Extension::getPluginNameByAction('ExtensionName', 'ControllerName', 'otherAction');
+       }
+
+       /**
+        * @test
+        */
+       public function getTargetPidByPluginSignatureReturnsNullIfConfigurationManagerIsNotInitialized() {
+               $this->assertNull(Tx_Extbase_Utility_Extension::getTargetPidByPluginSignature('plugin_signature'));
+       }
+
+       /**
+        * @test
+        */
+       public function getTargetPidByPluginSignatureReturnsNullIfDefaultPidIsNotConfigured() {
+               $dispatcher = new Tx_Extbase_Tests_Fixtures_Dispatcher();
+               $mockConfigurationManager = $this->getMock('Tx_Extbase_Configuration_AbstractConfigurationManager', array('getContextSpecificFrameworkConfiguration', 'loadTypoScriptSetup', 'getFrameworkConfiguration'));
+               $mockConfigurationManager->expects($this->once())->method('getFrameworkConfiguration')->with($GLOBALS['TSFE']->tmpl->setup['tt_content.']['list.']['20.']['extensionname_someplugin.'])->will($this->returnValue(array()));
+               $dispatcher->setConfigurationManager($mockConfigurationManager);
+               $this->assertNull(Tx_Extbase_Utility_Extension::getTargetPidByPluginSignature('extensionname_someplugin'));
+       }
+
+       /**
+        * @test
+        */
+       public function getTargetPidByPluginSignatureReturnsTheConfiguredDefaultPid() {
+               $dispatcher = new Tx_Extbase_Tests_Fixtures_Dispatcher();
+               $mockConfigurationManager = $this->getMock('Tx_Extbase_Configuration_AbstractConfigurationManager', array('getContextSpecificFrameworkConfiguration', 'loadTypoScriptSetup', 'getFrameworkConfiguration'));
+               $mockConfigurationManager->expects($this->once())->method('getFrameworkConfiguration')->with($GLOBALS['TSFE']->tmpl->setup['tt_content.']['list.']['20.']['extensionname_someplugin.'])->will($this->returnValue(array('view' => array('defaultPid' => '123'))));
+               $dispatcher->setConfigurationManager($mockConfigurationManager);
+               $expectedResult = 123;
+               $actualResult = Tx_Extbase_Utility_Extension::getTargetPidByPluginSignature('extensionname_someplugin');
+               $this->assertEquals($expectedResult, $actualResult);
+       }
+
+       /**
+        * @test
+        */
+       public function getTargetPidByPluginSignatureDeterminesTheTargetPidIfDefaultPidIsAuto() {
+               $dispatcher = new Tx_Extbase_Tests_Fixtures_Dispatcher();
+               $mockConfigurationManager = $this->getMock('Tx_Extbase_Configuration_AbstractConfigurationManager', array('getContextSpecificFrameworkConfiguration', 'loadTypoScriptSetup', 'getFrameworkConfiguration'));
+               $mockConfigurationManager->expects($this->once())->method('getFrameworkConfiguration')->with($GLOBALS['TSFE']->tmpl->setup['tt_content.']['list.']['20.']['extensionname_someplugin.'])->will($this->returnValue(array('view' => array('defaultPid' => 'auto'))));
+               $dispatcher->setConfigurationManager($mockConfigurationManager);
+               $pluginSignature = 'extensionname_someplugin';
+               $GLOBALS['TSFE']->sys_page = $this->getMock('t3lib_pageSelect', array('enableFields'));
+               $GLOBALS['TSFE']->sys_page->expects($this->once())->method('enableFields')->with('tt_content')->will($this->returnValue(' AND enable_fields'));
+               $GLOBALS['TYPO3_DB']->expects($this->once())->method('fullQuoteStr')->with($pluginSignature, 'tt_content')->will($this->returnValue('"pluginSignature"'));
+               $GLOBALS['TYPO3_DB']->expects($this->once())->method('exec_SELECTgetRows')->with(
+                       'pid',
+                       'tt_content',
+                       'list_type="pluginSignature" AND enable_fields',
+                       '',
+                       ''
+               )->will($this->returnValue(array(array('pid' => '321'))));
+               $expectedResult = 321;
+               $actualResult = Tx_Extbase_Utility_Extension::getTargetPidByPluginSignature($pluginSignature);
+               $this->assertEquals($expectedResult, $actualResult);
+       }
+
+       /**
+        * @test
+        */
+       public function getTargetPidByPluginSignatureReturnsNullIfTargetPidCouldNotBeDetermined() {
+               $dispatcher = new Tx_Extbase_Tests_Fixtures_Dispatcher();
+               $mockConfigurationManager = $this->getMock('Tx_Extbase_Configuration_AbstractConfigurationManager', array('getContextSpecificFrameworkConfiguration', 'loadTypoScriptSetup', 'getFrameworkConfiguration'));
+               $mockConfigurationManager->expects($this->once())->method('getFrameworkConfiguration')->with($GLOBALS['TSFE']->tmpl->setup['tt_content.']['list.']['20.']['extensionname_someplugin.'])->will($this->returnValue(array('view' => array('defaultPid' => 'auto'))));
+               $dispatcher->setConfigurationManager($mockConfigurationManager);
+               $GLOBALS['TSFE']->sys_page = $this->getMock('t3lib_pageSelect', array('enableFields'));
+               $GLOBALS['TSFE']->sys_page->expects($this->once())->method('enableFields')->will($this->returnValue(' AND enable_fields'));
+               $GLOBALS['TYPO3_DB']->expects($this->once())->method('fullQuoteStr')->will($this->returnValue('"pluginSignature"'));
+               $GLOBALS['TYPO3_DB']->expects($this->once())->method('exec_SELECTgetRows')->will($this->returnValue(array()));
+               $this->assertNull(Tx_Extbase_Utility_Extension::getTargetPidByPluginSignature('extensionname_someplugin'));
+       }
+
+       /**
+        * @test
+        * @expectedException Tx_Extbase_Exception
+        */
+       public function getTargetPidByPluginSignatureThrowsExceptionIfMoreThanOneTargetPidsWereFound() {
+               $dispatcher = new Tx_Extbase_Tests_Fixtures_Dispatcher();
+               $mockConfigurationManager = $this->getMock('Tx_Extbase_Configuration_AbstractConfigurationManager', array('getContextSpecificFrameworkConfiguration', 'loadTypoScriptSetup', 'getFrameworkConfiguration'));
+               $mockConfigurationManager->expects($this->once())->method('getFrameworkConfiguration')->with($GLOBALS['TSFE']->tmpl->setup['tt_content.']['list.']['20.']['extensionname_someplugin.'])->will($this->returnValue(array('view' => array('defaultPid' => 'auto'))));
+               $dispatcher->setConfigurationManager($mockConfigurationManager);
+               $GLOBALS['TSFE']->sys_page = $this->getMock('t3lib_pageSelect', array('enableFields'));
+               $GLOBALS['TSFE']->sys_page->expects($this->once())->method('enableFields')->will($this->returnValue(' AND enable_fields'));
+               $GLOBALS['TYPO3_DB']->expects($this->once())->method('fullQuoteStr')->will($this->returnValue('"pluginSignature"'));
+               $GLOBALS['TYPO3_DB']->expects($this->once())->method('exec_SELECTgetRows')->will($this->returnValue(array(array('pid' => 123), array('pid' => 124))));
+               Tx_Extbase_Utility_Extension::getTargetPidByPluginSignature('extensionname_someplugin');
+       }
+
 }
 
 ?>
\ No newline at end of file