[FEATURE] Add module menu object to the TYPO3 backend
authorSusanne Moog <typo3@susannemoog.de>
Sun, 17 Jun 2012 12:14:00 +0000 (14:14 +0200)
committerChristian Kuhn <lolli@schwarzbu.ch>
Sun, 24 Jun 2012 12:58:04 +0000 (14:58 +0200)
The backend at the moment uses a combination of the
GLOBAL variables TBE_MODULES and TBE_MODULES_EXT to
build the main menus of the backend (the module menu
on the left and the submodule menus of the backend
modules - look at info for an example).

To be able to easily change the representation of
these menus and to offer a clean API for rendering
menus the menu is refactored into an object structure
with n levels. This patch migrates the old GLOBALS
to a 3 level menu object. This means that you can use
the module menu object in your backend modules to
render your submenu.

This patch just adds the object structure and
transformation. It does not change any handling of
menus in the core itself. These changes can be done
piece by piece after this patch.

Change-Id: I1f7f9f5fcf83274840acef6cbeb267ea4ca460c3
Resolves: #38138
Releases: 6.0
Reviewed-on: http://review.typo3.org/12146
Reviewed-by: Christian Kuhn
Tested-by: Christian Kuhn
t3lib/core_autoload.php
tests/Unit/typo3/Classes/Utility/BackendModuleUtilityTest.php [new file with mode: 0644]
typo3/classes/Bootstrap.php
typo3/classes/Domain/Model/BackendModule.php [new file with mode: 0644]
typo3/classes/Domain/Repository/BackendModuleRepository.php [new file with mode: 0644]
typo3/classes/ModuleStorage.php [new file with mode: 0644]
typo3/classes/Utility/BackendModuleUtility.php [new file with mode: 0644]
typo3/init.php

index e057d8b..69b243b 100644 (file)
@@ -345,6 +345,11 @@ $typo3Classes = array(
        'typo3backend' => PATH_typo3 . 'backend.php',
        'typo3logo' => PATH_typo3 . 'classes/class.typo3logo.php',
        'webpagetree' => PATH_typo3 . 'class.webpagetree.php',
+       'typo3_modulestorage' => PATH_typo3 . 'classes/ModuleStorage.php',
+       'typo3_domain_model_backendmodule' => PATH_typo3 . 'classes/Domain/Model/BackendModule.php',
+       'typo3_domain_repository_backendmodulerepository' => PATH_typo3 . 'classes/Domain/Repository/BackendModuleRepository.php',
+       'typo3_utility_backendmoduleutility' => PATH_typo3 . 'classes/Utility/BackendModuleUtility.php',
+       'modulemenu' => PATH_typo3 . 'classes/class.modulemenu.php',
 );
 
 $tslibClasses = require(PATH_typo3 . 'sysext/cms/ext_autoload.php');
diff --git a/tests/Unit/typo3/Classes/Utility/BackendModuleUtilityTest.php b/tests/Unit/typo3/Classes/Utility/BackendModuleUtilityTest.php
new file mode 100644 (file)
index 0000000..df2a878
--- /dev/null
@@ -0,0 +1,103 @@
+<?php
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2012 Susanne Moog <typo3@susannemoog.de>
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+/**
+ * Test class for module menu utilities
+ *
+ * @author Susanne Moog <typo3@susannemoog.de>
+ * @package TYPO3
+ * @subpackage tests
+ */
+class Typo3_Utility_BackendModuleUtilityTest extends Tx_PhpUnit_TestCase {
+
+       /**
+        * @var Typo3_BackendModule_Utility
+        */
+       protected $moduleMenuUtility;
+
+       /**
+        * Helper function to call protected or private methods
+        *
+        * @param string $className The className
+        * @param string $name the name of the method to call
+        * @param mixed $argument The argument for the method call (only one in this test class needed)
+        * @return mixed
+        */
+       public function callInaccessibleMethod($className, $name, $argument) {
+               $class = new \ReflectionClass($className);
+               $object = new $className;
+               $method = $class->getMethod($name);
+               $method->setAccessible(TRUE);
+               return $method->invoke($object, $argument);
+       }
+
+       /**
+        * @test
+        */
+       public function createEntryFromRawDataGeneratesMenuEntry() {
+               $entry = $this->callInaccessibleMethod('Typo3_Utility_BackendModuleUtility', 'createEntryFromRawData', array());
+               $this->assertInstanceOf('Typo3_Domain_Model_BackendModule', $entry);
+       }
+
+       /**
+        * @test
+        */
+       public function createEntryFromRawDataSetsPropertiesInEntryObject() {
+               $rawModule = array(
+                       'name' => 'nameTest',
+                       'title' => 'titleTest',
+                       'onclick' => 'onclickTest',
+                       'icon' => array(
+                               'test' => '123'
+                       ),
+                       'link' => 'linkTest',
+                       'description' => 'descriptionTest',
+                       'navigationComponentId' => 'navigationComponentIdTest'
+               );
+               $entry = $this->callInaccessibleMethod('Typo3_Utility_BackendModuleUtility', 'createEntryFromRawData', $rawModule);
+               $this->assertEquals('nameTest', $entry->getName());
+               $this->assertEquals('titleTest', $entry->getTitle());
+               $this->assertEquals('linkTest', $entry->getLink());
+               $this->assertEquals('onclickTest', $entry->getOnClick());
+               $this->assertEquals('navigationComponentIdTest', $entry->getNavigationComponentId());
+               $this->assertEquals('descriptionTest', $entry->getDescription());
+               $this->assertEquals(array('test' => '123'), $entry->getIcon());
+       }
+
+       /**
+        * @test
+        */
+       public function createEntryFromRawDataSetsLinkIfPathIsGivenInEntryObject() {
+               $rawModule = array(
+                       'path' => 'pathTest',
+               );
+               $entry = $this->callInaccessibleMethod('Typo3_Utility_BackendModuleUtility', 'createEntryFromRawData', $rawModule);
+               $this->assertEquals('pathTest', $entry->getLink());
+       }
+}
+
+?>
\ No newline at end of file
index 32480af..a478f87 100644 (file)
@@ -1271,6 +1271,19 @@ class Typo3_Bootstrap {
        }
 
        /**
+        * Initialize module menu object
+        *
+        * @return Typo3_Bootstrap
+        */
+       public function initializeModuleMenuObject() {
+                       /** @var $moduleMenuUtility Typo3_BackendModule_Utility */
+               $moduleMenuUtility = t3lib_div::makeInstance('Typo3_Utility_BackendModuleUtility');
+               $moduleMenuUtility->createModuleMenu();
+
+               return $this;
+       }
+
+       /**
         * Things that should be performed to shut down the framework.
         * This method is called in all important scripts for a clean
         * shut down of the system.
diff --git a/typo3/classes/Domain/Model/BackendModule.php b/typo3/classes/Domain/Model/BackendModule.php
new file mode 100644 (file)
index 0000000..d867b45
--- /dev/null
@@ -0,0 +1,204 @@
+<?php
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2012 - Susanne Moog <typo3@susannemoog.de>
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+/**
+ * Model for menu entries
+ *
+ * @author Susanne Moog <typo3@susannemoog.de>
+ * @package TYPO3
+ * @subpackage core
+ */
+class Typo3_Domain_Model_BackendModule {
+
+       /**
+        * @var string $title
+        */
+       protected $title = '';
+
+       /**
+        * @var string $name
+        */
+       protected $name = '';
+
+       /**
+        * @var array $icon
+        */
+       protected $icon = array();
+
+       /**
+        * @var string $link
+        */
+       protected $link = '';
+
+       /**
+        * @var string $onClick
+        */
+       protected $onClick = '';
+
+       /**
+        * @var string $description
+        */
+       protected $description = '';
+
+       /**
+        * @var string $navigationComponentId
+        */
+       protected $navigationComponentId = '';
+
+       /**
+        * @var SplObjectStorage $children
+        */
+       protected $children;
+
+       /**
+        * construct
+        */
+       public function __construct() {
+               $this->children = new SplObjectStorage();
+       }
+
+       /**
+        * @param \SplObjectStorage $children
+        */
+       public function setChildren($children) {
+               $this->children = $children;
+       }
+
+       /**
+        * @return \SplObjectStorage
+        */
+       public function getChildren() {
+               return $this->children;
+       }
+
+       /**
+        * @param Typo3_BackendModule $child
+        */
+       public function addChild(Typo3_Domain_Model_BackendModule $child) {
+               $this->children->attach($child);
+       }
+
+       /**
+        * @param array $icon
+        */
+       public function setIcon(array $icon) {
+               $this->icon = $icon;
+       }
+
+       /**
+        * @return array
+        */
+       public function getIcon() {
+               return $this->icon;
+       }
+
+       /**
+        * @param string $name
+        */
+       public function setName($name) {
+               $this->name = $name;
+       }
+
+       /**
+        * @return string
+        */
+       public function getName() {
+               return $this->name;
+       }
+
+       /**
+        * @param string $title
+        */
+       public function setTitle($title) {
+               $this->title = $title;
+       }
+
+       /**
+        * @return string
+        */
+       public function getTitle() {
+               return $this->title;
+       }
+
+       /**
+        * @param string $link
+        */
+       public function setLink($link) {
+               $this->link = $link;
+       }
+
+       /**
+        * @return string
+        */
+       public function getLink() {
+               return $this->link;
+       }
+
+       /**
+        * @param string $description
+        */
+       public function setDescription($description) {
+               $this->description = $description;
+       }
+
+       /**
+        * @return string
+        */
+       public function getDescription() {
+               return $this->description;
+       }
+
+       /**
+        * @param string $navigationComponentId
+        */
+       public function setNavigationComponentId($navigationComponentId) {
+               $this->navigationComponentId = $navigationComponentId;
+       }
+
+       /**
+        * @return string
+        */
+       public function getNavigationComponentId() {
+               return $this->navigationComponentId;
+       }
+
+       /**
+        * @param string $onClick
+        */
+       public function setOnClick($onClick) {
+               $this->onClick = $onClick;
+       }
+
+       /**
+        * @return string
+        */
+       public function getOnClick() {
+               return $this->onClick;
+       }
+}
+
+?>
\ No newline at end of file
diff --git a/typo3/classes/Domain/Repository/BackendModuleRepository.php b/typo3/classes/Domain/Repository/BackendModuleRepository.php
new file mode 100644 (file)
index 0000000..c57cf2f
--- /dev/null
@@ -0,0 +1,85 @@
+<?php
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2012 - Susanne Moog <typo3@susannemoog.de>
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+/**
+ * Repository for backend module menu
+ *
+ * @author Susanne Moog <typo3@susannemoog.de>
+ * @package TYPO3
+ * @subpackage core
+ */
+class Typo3_Domain_Repository_BackendModuleRepository implements t3lib_Singleton {
+
+       /**
+        * @var Typo3_ModuleStorage $moduleMenu
+        */
+       protected $moduleMenu;
+
+       /**
+        * Constructs the module menu and gets the Singleton instance of the menu
+        */
+       public function __construct() {
+               $this->moduleMenu = t3lib_div::makeInstance('Typo3_ModuleStorage');
+       }
+
+       /**
+        * Finds a module menu entry by name
+        *
+        * @param string $name
+        * @return Typo3_Domain_Model_BackendModule|boolean
+        */
+       public function findByModuleName($name) {
+               $entries = $this->moduleMenu->getEntries();
+               $entry = $this->findByModuleNameInGivenEntries($name, $entries);
+               return $entry;
+       }
+
+       /**
+        * Finds a module menu entry by name in a given storage
+        *
+        * @param string $name
+        * @param SplObjectStorage $entries
+        * @return Typo3_Domain_Model_BackendModule|bool
+        */
+       public function findByModuleNameInGivenEntries($name, SplObjectStorage $entries) {
+               foreach ($entries as $entry) {
+                       if ($entry->getName() === $name) {
+                               return $entry;
+                       }
+                       $children = $entry->getChildren();
+                       if (count($children) > 0) {
+                               $childRecord = $this->findByModuleNameInGivenEntries($name, $children);
+                               if ($childRecord !== FALSE) {
+                                       return $childRecord;
+                               }
+                       }
+               }
+               return FALSE;
+       }
+}
+
+?>
\ No newline at end of file
diff --git a/typo3/classes/ModuleStorage.php b/typo3/classes/ModuleStorage.php
new file mode 100644 (file)
index 0000000..c08e623
--- /dev/null
@@ -0,0 +1,71 @@
+<?php
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2012 - Susanne Moog <typo3@susannemoog.de>
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+/**
+ * Model for the module storage
+ *
+ * @author Susanne Moog <typo3@susannemoog.de>
+ * @package TYPO3
+ * @subpackage core
+ */
+class Typo3_ModuleStorage implements t3lib_Singleton {
+
+       /**
+        * @var SplObjectStorage
+        */
+       protected $entries;
+
+       /**
+        * construct
+        */
+       public function __construct() {
+               $this->entries = new SplObjectStorage();
+       }
+
+       /**
+        * @param \SplObjectStorage $entries
+        */
+       public function setEntries($entries) {
+               $this->entries = $entries;
+       }
+
+       /**
+        * @return \SplObjectStorage
+        */
+       public function getEntries() {
+               return $this->entries;
+       }
+
+       /**
+        * @param Typo3_Domain_Model_BackendModule $entry
+        */
+       public function attachEntry(Typo3_Domain_Model_BackendModule $entry) {
+               $this->entries->attach($entry);
+       }
+}
+
+?>
\ No newline at end of file
diff --git a/typo3/classes/Utility/BackendModuleUtility.php b/typo3/classes/Utility/BackendModuleUtility.php
new file mode 100644 (file)
index 0000000..7a09237
--- /dev/null
@@ -0,0 +1,156 @@
+<?php
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2012 - Susanne Moog <typo3@susannemoog.de>
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+/**
+ * Class with utility functions for module menu
+ *
+ * @author Susanne Moog <typo3@susannemoog.de>
+ * @package TYPO3
+ * @subpackage core
+ */
+class Typo3_Utility_BackendModuleUtility {
+
+       /**
+        * @var Typo3_ModuleStorage
+        */
+       protected $moduleMenu;
+
+       /**
+        * @var Typo3_Repository_BackendModuleRepository
+        */
+       protected $moduleMenuRepository;
+
+       /**
+        * Constructor
+        */
+       public function __construct() {
+               $this->moduleMenu = t3lib_div::makeInstance('Typo3_ModuleStorage');
+               $this->moduleMenuRepository = t3lib_div::makeInstance('Typo3_Domain_Repository_BackendModuleRepository');
+       }
+
+       /**
+        * This method creates the module menu if necessary
+        * afterwards you only need an instance of Typo3_ModuleStorage
+        * to get the menu
+        *
+        * @return void
+        */
+       public function createModuleMenu() {
+               if (count($this->moduleMenu->getEntries()) === 0) {
+                       /** @var $moduleMenu ModuleMenu */
+                       $moduleMenu = t3lib_div::makeInstance('ModuleMenu');
+                       $rawData = $moduleMenu->getRawModuleData();
+                       $this->convertRawModuleDataToModuleMenuObject($rawData);
+                       $this->createMenuEntriesForTbeModulesExt();
+               }
+       }
+
+       /**
+        * Creates the module menu object structure from the raw data array
+        *
+        * @see class.modulemenu.php getRawModuleData()
+        * @param array $rawModuleData
+        * @return void
+        */
+       protected function convertRawModuleDataToModuleMenuObject(array $rawModuleData) {
+               foreach ($rawModuleData as $module) {
+                       $entry = $this->createEntryFromRawData($module);
+                       if (isset($module['subitems']) && count($module['subitems']) > 0) {
+                               foreach ($module['subitems'] as $subitem) {
+                                       $subEntry = $this->createEntryFromRawData($subitem);
+                                       $entry->addChild($subEntry);
+                               }
+                       }
+                       $this->moduleMenu->attachEntry($entry);
+               }
+       }
+
+       /**
+        * Creates a menu entry object from an array
+        *
+        * @param array $module
+        * @return Typo3_BackendModule
+        */
+       protected function createEntryFromRawData(array $module) {
+               /** @var $entry Typo3_BackendModule */
+               $entry = t3lib_div::makeInstance('Typo3_Domain_Model_BackendModule');
+               if (!empty($module['name']) && is_string($module['name'])) {
+                       $entry->setName($module['name']);
+               }
+               if (!empty($module['title']) && is_string($module['title'])) {
+                       $entry->setTitle($GLOBALS['LANG']->sL($module['title']));
+               }
+               if (!empty($module['onclick']) && is_string($module['onclick'])) {
+                       $entry->setOnClick($module['onclick']);
+               }
+               if (!empty($module['link']) && is_string($module['link'])) {
+                       $entry->setLink($module['link']);
+               }
+               if (empty($module['link']) && !empty($module['path']) && is_string($module['path'])) {
+                       $entry->setLink($module['path']);
+               }
+               if (!empty($module['description']) && is_string($module['description'])) {
+                       $entry->setDescription($module['description']);
+               }
+               if (!empty($module['icon']) && is_array($module['icon'])) {
+                       $entry->setIcon($module['icon']);
+               }
+               if (!empty($module['navigationComponentId']) && is_string($module['navigationComponentId'])) {
+                       $entry->setNavigationComponentId($module['navigationComponentId']);
+               }
+               return $entry;
+       }
+
+       /**
+        * Creates the "third level" menu entries (submodules for the info module for example)
+        * from the TBE_MODULES_EXT array
+        *
+        * @return void
+        */
+       protected function createMenuEntriesForTbeModulesExt() {
+               foreach ($GLOBALS['TBE_MODULES_EXT'] as $mainModule => $tbeModuleExt) {
+                       list($main) = explode('_', $mainModule);
+                       $mainEntry = $this->moduleMenuRepository->findByModuleName($main);
+                       if ($mainEntry !== FALSE) {
+                               $subEntries = $mainEntry->getChildren();
+                               if (count($subEntries) > 0) {
+                                       $matchingSubEntry = $this->moduleMenuRepository->findByModuleName($mainModule);
+                                       if ($matchingSubEntry !== FALSE) {
+                                               if (array_key_exists('MOD_MENU', $tbeModuleExt) && array_key_exists('function', $tbeModuleExt['MOD_MENU'])) {
+                                                       foreach ($tbeModuleExt['MOD_MENU']['function'] as $subModule) {
+                                                               $entry = $this->createEntryFromRawData($subModule);
+                                                               $matchingSubEntry->addChild($entry);
+                                                       }
+                                               }
+                                       }
+                               }
+                       }
+               }
+       }
+}
+
+?>
\ No newline at end of file
index 5c62983..8ee9cc2 100644 (file)
@@ -110,8 +110,8 @@ Typo3_Bootstrap::getInstance()
        ->initializeBackendUser()
        ->initializeBackendUserMounts()
        ->initializeLanguageObject()
+       ->initializeModuleMenuObject()
        ->initializeBackendTemplate()
        ->endOutputBufferingAndCleanPreviousOutput()
        ->initializeOutputCompression();
-
 ?>
\ No newline at end of file