[TASK] Cleanup navigation frame module registration
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Domain / Repository / Module / BackendModuleRepository.php
1 <?php
2 namespace TYPO3\CMS\Backend\Domain\Repository\Module;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Utility\GeneralUtility;
18 use TYPO3\CMS\Backend\Utility\BackendUtility;
19
20 /**
21 * Repository for backend module menu
22 * compiles all data from $GLOBALS[TBE_MODULES]
23 */
24 class BackendModuleRepository implements \TYPO3\CMS\Core\SingletonInterface {
25
26 /**
27 * @var \TYPO3\CMS\Backend\Module\ModuleStorage
28 */
29 protected $moduleStorage;
30
31 /**
32 * Constructs the module menu and gets the Singleton instance of the menu
33 */
34 public function __construct() {
35 $this->moduleStorage = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Module\ModuleStorage::class);
36
37 $rawData = $this->getRawModuleMenuData();
38
39 $this->convertRawModuleDataToModuleMenuObject($rawData);
40 $this->createMenuEntriesForTbeModulesExt();
41 }
42
43 /**
44 * loads all module information in the module storage
45 *
46 * @param array $excludeGroupNames
47 * @return \SplObjectStorage
48 */
49 public function loadAllowedModules(array $excludeGroupNames = array()) {
50 if (empty($excludeGroupNames)) {
51 return $this->moduleStorage->getEntries();
52 }
53
54 $modules = new \SplObjectStorage();
55 foreach ($this->moduleStorage->getEntries() as $moduleGroup) {
56 if (!in_array($moduleGroup->getName(), $excludeGroupNames, TRUE)) {
57 if ($moduleGroup->getChildren()->count() > 0) {
58 $modules->attach($moduleGroup);
59 }
60 }
61 }
62
63 return $modules;
64 }
65
66 /**
67 * @param string $groupName
68 * @return \SplObjectStorage|FALSE
69 **/
70 public function findByGroupName($groupName = '') {
71 foreach ($this->moduleStorage->getEntries() as $moduleGroup) {
72 if ($moduleGroup->getName() === $groupName) {
73 return $moduleGroup;
74 }
75 }
76
77 return FALSE;
78 }
79
80 /**
81 * Finds a module menu entry by name
82 *
83 * @param string $name
84 * @return \TYPO3\CMS\Backend\Domain\Model\Module\BackendModule|boolean
85 */
86 public function findByModuleName($name) {
87 $entries = $this->moduleStorage->getEntries();
88 $entry = $this->findByModuleNameInGivenEntries($name, $entries);
89 return $entry;
90 }
91
92 /**
93 * Finds a module menu entry by name in a given storage
94 *
95 * @param string $name
96 * @param \SplObjectStorage $entries
97 * @return \TYPO3\CMS\Backend\Domain\Model\Module\BackendModule|bool
98 */
99 public function findByModuleNameInGivenEntries($name, \SplObjectStorage $entries) {
100 foreach ($entries as $entry) {
101 if ($entry->getName() === $name) {
102 return $entry;
103 }
104 $children = $entry->getChildren();
105 if (!empty($children)) {
106 $childRecord = $this->findByModuleNameInGivenEntries($name, $children);
107 if ($childRecord !== FALSE) {
108 return $childRecord;
109 }
110 }
111 }
112 return FALSE;
113 }
114
115 /**
116 * Creates the module menu object structure from the raw data array
117 *
118 * @param array $rawModuleData
119 * @return void
120 */
121 protected function convertRawModuleDataToModuleMenuObject(array $rawModuleData) {
122 foreach ($rawModuleData as $module) {
123 $entry = $this->createEntryFromRawData($module);
124 if (isset($module['subitems']) && !empty($module['subitems'])) {
125 foreach ($module['subitems'] as $subitem) {
126 $subEntry = $this->createEntryFromRawData($subitem);
127 $entry->addChild($subEntry);
128 }
129 }
130 $this->moduleStorage->attachEntry($entry);
131 }
132 }
133
134 /**
135 * Creates a menu entry object from an array
136 *
137 * @param array $module
138 * @return \TYPO3\CMS\Backend\Domain\Model\Module\BackendModule
139 */
140 protected function createEntryFromRawData(array $module) {
141 /** @var $entry \TYPO3\CMS\Backend\Domain\Model\Module\BackendModule */
142 $entry = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Domain\Model\Module\BackendModule::class);
143 if (!empty($module['name']) && is_string($module['name'])) {
144 $entry->setName($module['name']);
145 }
146 if (!empty($module['title']) && is_string($module['title'])) {
147 $entry->setTitle($this->getLanguageService()->sL($module['title']));
148 }
149 if (!empty($module['onclick']) && is_string($module['onclick'])) {
150 $entry->setOnClick($module['onclick']);
151 }
152 if (!empty($module['link']) && is_string($module['link'])) {
153 $entry->setLink($module['link']);
154 } elseif (empty($module['link']) && !empty($module['path']) && is_string($module['path'])) {
155 $entry->setLink($module['path']);
156 }
157 if (!empty($module['description']) && is_string($module['description'])) {
158 $entry->setDescription($module['description']);
159 }
160 if (!empty($module['icon'])) {
161 $entry->setIcon($module['icon']);
162 }
163 if (!empty($module['navigationComponentId']) && is_string($module['navigationComponentId'])) {
164 $entry->setNavigationComponentId($module['navigationComponentId']);
165 }
166 if (!empty($module['navigationFrameScript']) && is_string($module['navigationFrameScript'])) {
167 $entry->setNavigationFrameScript($module['navigationFrameScript']);
168 } elseif (!empty($module['parentNavigationFrameScript']) && is_string($module['parentNavigationFrameScript'])) {
169 $entry->setNavigationFrameScript($module['parentNavigationFrameScript']);
170 }
171 if (!empty($module['navigationFrameScriptParam']) && is_string($module['navigationFrameScriptParam'])) {
172 $entry->setNavigationFrameScriptParameters($module['navigationFrameScriptParam']);
173 }
174 return $entry;
175 }
176
177 /**
178 * Creates the "third level" menu entries (submodules for the info module for
179 * example) from the TBE_MODULES_EXT array
180 *
181 * @return void
182 */
183 protected function createMenuEntriesForTbeModulesExt() {
184 foreach ($GLOBALS['TBE_MODULES_EXT'] as $mainModule => $tbeModuleExt) {
185 list($main) = explode('_', $mainModule);
186 $mainEntry = $this->findByModuleName($main);
187 if ($mainEntry === FALSE) {
188 continue;
189 }
190
191 $subEntries = $mainEntry->getChildren();
192 if (empty($subEntries)) {
193 continue;
194 }
195 $matchingSubEntry = $this->findByModuleName($mainModule);
196 if ($matchingSubEntry !== FALSE) {
197 if (isset($tbeModuleExt['MOD_MENU']) && isset($tbeModuleExt['MOD_MENU']['function'])) {
198 foreach ($tbeModuleExt['MOD_MENU']['function'] as $subModule) {
199 $entry = $this->createEntryFromRawData($subModule);
200 $matchingSubEntry->addChild($entry);
201 }
202 }
203 }
204 }
205 }
206
207 /**
208 * Return language service instance
209 *
210 * @return \TYPO3\CMS\Lang\LanguageService
211 */
212 protected function getLanguageService() {
213 return $GLOBALS['LANG'];
214 }
215
216 /**
217 * loads the module menu from the moduleloader based on $GLOBALS['TBE_MODULES']
218 * and compiles an array with all the data needed for menu etc.
219 *
220 * @return array
221 */
222 public function getRawModuleMenuData() {
223 // Loads the backend modules available for the logged in user.
224 $moduleLoader = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Module\ModuleLoader::class);
225 $moduleLoader->observeWorkspaces = TRUE;
226 $moduleLoader->load($GLOBALS['TBE_MODULES']);
227 $loadedModules = $moduleLoader->modules;
228
229 $modules = array();
230
231 // Unset modules that are meant to be hidden from the menu.
232 $loadedModules = $this->removeHiddenModules($loadedModules);
233 $dummyScript = BackendUtility::getModuleUrl('dummy');
234 foreach ($loadedModules as $moduleName => $moduleData) {
235 $moduleLink = '';
236 if (!is_array($moduleData['sub'])) {
237 $moduleLink = $moduleData['script'];
238 }
239 $moduleLink = GeneralUtility::resolveBackPath($moduleLink);
240 $moduleKey = 'modmenu_' . $moduleName;
241 $modules[$moduleKey] = array(
242 'name' => $moduleName,
243 'title' => $GLOBALS['LANG']->moduleLabels['tabs'][$moduleName . '_tab'],
244 'onclick' => 'top.goToModule(' . GeneralUtility::quoteJSvalue($moduleName) . ');',
245 'icon' => $this->getModuleIcon($moduleName, $moduleData),
246 'link' => $moduleLink,
247 'description' => $GLOBALS['LANG']->moduleLabels['labels'][$moduleKey . 'label']
248 );
249 if (!is_array($moduleData['sub']) && $moduleData['script'] !== $dummyScript) {
250 // Work around for modules with own main entry, but being self the only submodule
251 $modules[$moduleKey]['subitems'][$moduleKey] = array(
252 'name' => $moduleName,
253 'title' => $GLOBALS['LANG']->moduleLabels['tabs'][$moduleName . '_tab'],
254 'onclick' => 'top.goToModule(' . GeneralUtility::quoteJSvalue($moduleName) . ');',
255 'icon' => $this->getModuleIcon($moduleName . '_tab', $moduleData),
256 'link' => $moduleLink,
257 'originalLink' => $moduleLink,
258 'description' => $GLOBALS['LANG']->moduleLabels['labels'][$moduleKey . 'label'],
259 'navigationFrameScript' => NULL,
260 'navigationFrameScriptParam' => NULL,
261 'navigationComponentId' => NULL
262 );
263 } elseif (is_array($moduleData['sub'])) {
264 foreach ($moduleData['sub'] as $submoduleName => $submoduleData) {
265 if (isset($submoduleData['script'])) {
266 $submoduleLink = GeneralUtility::resolveBackPath($submoduleData['script']);
267 } else {
268 $submoduleLink = BackendUtility::getModuleUrl($submoduleData['name']);
269 }
270 $submoduleKey = $moduleName . '_' . $submoduleName . '_tab';
271 $submoduleDescription = $GLOBALS['LANG']->moduleLabels['labels'][$submoduleKey . 'label'];
272 $originalLink = $submoduleLink;
273 $navigationFrameScript = $submoduleData['navFrameScript'];
274 $modules[$moduleKey]['subitems'][$submoduleKey] = array(
275 'name' => $moduleName . '_' . $submoduleName,
276 'title' => $GLOBALS['LANG']->moduleLabels['tabs'][$submoduleKey],
277 'onclick' => 'top.goToModule(' . GeneralUtility::quoteJSvalue($moduleName . '_' . $submoduleName) . ');',
278 'icon' => $this->getModuleIcon($submoduleKey, $submoduleData),
279 'link' => $submoduleLink,
280 'originalLink' => $originalLink,
281 'description' => $submoduleDescription,
282 'navigationFrameScript' => $navigationFrameScript,
283 'navigationFrameScriptParam' => $submoduleData['navFrameScriptParam'],
284 'navigationComponentId' => $submoduleData['navigationComponentId']
285 );
286 // if the main module has a navframe script, inherit to the submodule,
287 // but only if it is not disabled explicitly (option is set to FALSE)
288 if ($moduleData['navFrameScript'] && $submoduleData['inheritNavigationComponentFromMainModule'] !== FALSE) {
289 $modules[$moduleKey]['subitems'][$submoduleKey]['parentNavigationFrameScript'] = $moduleData['navFrameScript'];
290 }
291 }
292 }
293 }
294 return $modules;
295 }
296
297 /**
298 * Reads User configuration from options.hideModules and removes
299 * modules accordingly.
300 *
301 * @param array $loadedModules
302 * @return array
303 */
304 protected function removeHiddenModules($loadedModules) {
305 $hiddenModules = $GLOBALS['BE_USER']->getTSConfig('options.hideModules');
306
307 // Hide modules if set in userTS.
308 if (!empty($hiddenModules['value'])) {
309 $hiddenMainModules = explode(',', $hiddenModules['value']);
310 foreach ($hiddenMainModules as $hiddenMainModule) {
311 unset($loadedModules[trim($hiddenMainModule)]);
312 }
313 }
314
315 // Hide sub-modules if set in userTS.
316 if (!empty($hiddenModules['properties']) && is_array($hiddenModules['properties'])) {
317 foreach ($hiddenModules['properties'] as $mainModuleName => $subModules) {
318 $hiddenSubModules = explode(',', $subModules);
319 foreach ($hiddenSubModules as $hiddenSubModule) {
320 unset($loadedModules[$mainModuleName]['sub'][trim($hiddenSubModule)]);
321 }
322 }
323 }
324
325 return $loadedModules;
326 }
327
328 /**
329 * gets the module icon and its size
330 *
331 * @param string $moduleKey Module key
332 * @param array $moduleData the compiled data associated with it
333 * @return string Icon data, either sprite or <img> tag
334 */
335 protected function getModuleIcon($moduleKey, $moduleData) {
336 $icon = '';
337
338 // add as a sprite icon
339 if ($moduleData['configuration']['icon']) {
340 $icon = \TYPO3\CMS\Backend\Utility\IconUtility::getSpriteIcon($moduleData['configuration']['icon'], array('tagName' => 'i'));
341 } elseif (!empty($GLOBALS['LANG']->moduleLabels['tabs_images'][$moduleKey])) {
342 $imageReference = $GLOBALS['LANG']->moduleLabels['tabs_images'][$moduleKey];
343 $iconFileRelative = $this->getModuleIconRelative($imageReference);
344 if (!empty($iconFileRelative)) {
345 $iconTitle = $GLOBALS['LANG']->moduleLabels['tabs'][$moduleKey];
346 $iconSizes = @getimagesize($this->getModuleIconAbsolute($imageReference));
347 $icon = '<img src="' . $iconFileRelative . '" ' . $iconSizes[3] . ' title="' . htmlspecialchars($iconTitle) . '" alt="' . htmlspecialchars($iconTitle) . '" />';
348 }
349 }
350 return $icon;
351 }
352
353 /**
354 * Returns the filename readable for the script from PATH_typo3.
355 * That means absolute names are just returned while relative names are
356 * prepended with the path pointing back to typo3/ dir
357 *
358 * @param string $iconFilename Icon filename
359 * @return string Icon filename with absolute path
360 * @see getModuleIconRelative()
361 */
362 protected function getModuleIconAbsolute($iconFilename) {
363 if (!GeneralUtility::isAbsPath($iconFilename)) {
364 $iconFilename = $GLOBALS['BACK_PATH'] . $iconFilename;
365 }
366 return $iconFilename;
367 }
368
369 /**
370 * Returns relative path to the icon filename for use in img-tags
371 *
372 * @param string $iconFilename Icon filename
373 * @return string Icon filename with relative path
374 * @see getModuleIconAbsolute()
375 */
376 protected function getModuleIconRelative($iconFilename) {
377 if (GeneralUtility::isAbsPath($iconFilename)) {
378 $iconFilename = '../' . \TYPO3\CMS\Core\Utility\PathUtility::stripPathSitePrefix($iconFilename);
379 }
380 return $iconFilename;
381 }
382
383 }