776868b4199eb437e1c33c19aca8b223eade1871
[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\Backend\Module\ModuleLoader;
18 use TYPO3\CMS\Backend\Utility\BackendUtility;
19 use TYPO3\CMS\Core\Imaging\Icon;
20 use TYPO3\CMS\Core\Imaging\IconFactory;
21 use TYPO3\CMS\Core\Imaging\IconRegistry;
22 use TYPO3\CMS\Core\Utility\GeneralUtility;
23
24 /**
25 * Repository for backend module menu
26 * compiles all data from $GLOBALS[TBE_MODULES]
27 */
28 class BackendModuleRepository implements \TYPO3\CMS\Core\SingletonInterface
29 {
30 /**
31 * @var \TYPO3\CMS\Backend\Module\ModuleStorage
32 */
33 protected $moduleStorage;
34
35 /**
36 * Constructs the module menu and gets the Singleton instance of the menu
37 */
38 public function __construct()
39 {
40 $this->moduleStorage = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Module\ModuleStorage::class);
41
42 $rawData = $this->getRawModuleMenuData();
43
44 $this->convertRawModuleDataToModuleMenuObject($rawData);
45 $this->createMenuEntriesForTbeModulesExt();
46 }
47
48 /**
49 * loads all module information in the module storage
50 *
51 * @param array $excludeGroupNames
52 * @return \SplObjectStorage
53 */
54 public function loadAllowedModules(array $excludeGroupNames = [])
55 {
56 if (empty($excludeGroupNames)) {
57 return $this->moduleStorage->getEntries();
58 }
59
60 $modules = new \SplObjectStorage();
61 foreach ($this->moduleStorage->getEntries() as $moduleGroup) {
62 if (!in_array($moduleGroup->getName(), $excludeGroupNames, true)) {
63 if ($moduleGroup->getChildren()->count() > 0) {
64 $modules->attach($moduleGroup);
65 }
66 }
67 }
68
69 return $modules;
70 }
71
72 /**
73 * @param string $groupName
74 * @return \SplObjectStorage|FALSE
75 **/
76 public function findByGroupName($groupName = '')
77 {
78 foreach ($this->moduleStorage->getEntries() as $moduleGroup) {
79 if ($moduleGroup->getName() === $groupName) {
80 return $moduleGroup;
81 }
82 }
83
84 return false;
85 }
86
87 /**
88 * Finds a module menu entry by name
89 *
90 * @param string $name
91 * @return \TYPO3\CMS\Backend\Domain\Model\Module\BackendModule|bool
92 */
93 public function findByModuleName($name)
94 {
95 $entries = $this->moduleStorage->getEntries();
96 $entry = $this->findByModuleNameInGivenEntries($name, $entries);
97 return $entry;
98 }
99
100 /**
101 * Finds a module menu entry by name in a given storage
102 *
103 * @param string $name
104 * @param \SplObjectStorage $entries
105 * @return \TYPO3\CMS\Backend\Domain\Model\Module\BackendModule|bool
106 */
107 public function findByModuleNameInGivenEntries($name, \SplObjectStorage $entries)
108 {
109 foreach ($entries as $entry) {
110 if ($entry->getName() === $name) {
111 return $entry;
112 }
113 $children = $entry->getChildren();
114 if (!empty($children)) {
115 $childRecord = $this->findByModuleNameInGivenEntries($name, $children);
116 if ($childRecord !== false) {
117 return $childRecord;
118 }
119 }
120 }
121 return false;
122 }
123
124 /**
125 * Creates the module menu object structure from the raw data array
126 *
127 * @param array $rawModuleData
128 */
129 protected function convertRawModuleDataToModuleMenuObject(array $rawModuleData)
130 {
131 foreach ($rawModuleData as $module) {
132 $entry = $this->createEntryFromRawData($module);
133 if (isset($module['subitems']) && !empty($module['subitems'])) {
134 foreach ($module['subitems'] as $subitem) {
135 $subEntry = $this->createEntryFromRawData($subitem);
136 $entry->addChild($subEntry);
137 }
138 }
139 $this->moduleStorage->attachEntry($entry);
140 }
141 }
142
143 /**
144 * Creates a menu entry object from an array
145 *
146 * @param array $module
147 * @return \TYPO3\CMS\Backend\Domain\Model\Module\BackendModule
148 */
149 protected function createEntryFromRawData(array $module)
150 {
151 /** @var $entry \TYPO3\CMS\Backend\Domain\Model\Module\BackendModule */
152 $entry = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Domain\Model\Module\BackendModule::class);
153 if (!empty($module['name']) && is_string($module['name'])) {
154 $entry->setName($module['name']);
155 }
156 if (!empty($module['title']) && is_string($module['title'])) {
157 $entry->setTitle($this->getLanguageService()->sL($module['title']));
158 }
159 if (!empty($module['onclick']) && is_string($module['onclick'])) {
160 $entry->setOnClick($module['onclick']);
161 }
162 if (!empty($module['link']) && is_string($module['link'])) {
163 $entry->setLink($module['link']);
164 } elseif (empty($module['link']) && !empty($module['path']) && is_string($module['path'])) {
165 $entry->setLink($module['path']);
166 }
167 if (!empty($module['description']) && is_string($module['description'])) {
168 $entry->setDescription($this->getLanguageService()->sL($module['description']));
169 }
170 if (!empty($module['icon'])) {
171 $entry->setIcon($module['icon']);
172 }
173 if (!empty($module['navigationComponentId']) && is_string($module['navigationComponentId'])) {
174 $entry->setNavigationComponentId($module['navigationComponentId']);
175 }
176 if (!empty($module['navigationFrameScript']) && is_string($module['navigationFrameScript'])) {
177 $entry->setNavigationFrameScript($module['navigationFrameScript']);
178 } elseif (!empty($module['parentNavigationFrameScript']) && is_string($module['parentNavigationFrameScript'])) {
179 $entry->setNavigationFrameScript($module['parentNavigationFrameScript']);
180 }
181 if (!empty($module['navigationFrameScriptParam']) && is_string($module['navigationFrameScriptParam'])) {
182 $entry->setNavigationFrameScriptParameters($module['navigationFrameScriptParam']);
183 }
184 return $entry;
185 }
186
187 /**
188 * Creates the "third level" menu entries (submodules for the info module for
189 * example) from the TBE_MODULES_EXT array
190 */
191 protected function createMenuEntriesForTbeModulesExt()
192 {
193 foreach ($GLOBALS['TBE_MODULES_EXT'] as $mainModule => $tbeModuleExt) {
194 list($main) = explode('_', $mainModule);
195 $mainEntry = $this->findByModuleName($main);
196 if ($mainEntry === false) {
197 continue;
198 }
199
200 $subEntries = $mainEntry->getChildren();
201 if (empty($subEntries)) {
202 continue;
203 }
204 $matchingSubEntry = $this->findByModuleName($mainModule);
205 if ($matchingSubEntry !== false) {
206 if (isset($tbeModuleExt['MOD_MENU']) && isset($tbeModuleExt['MOD_MENU']['function'])) {
207 foreach ($tbeModuleExt['MOD_MENU']['function'] as $subModule) {
208 $entry = $this->createEntryFromRawData($subModule);
209 $matchingSubEntry->addChild($entry);
210 }
211 }
212 }
213 }
214 }
215
216 /**
217 * Return language service instance
218 *
219 * @return \TYPO3\CMS\Core\Localization\LanguageService
220 */
221 protected function getLanguageService()
222 {
223 return $GLOBALS['LANG'];
224 }
225
226 /**
227 * loads the module menu from the moduleloader based on $GLOBALS['TBE_MODULES']
228 * and compiles an array with all the data needed for menu etc.
229 *
230 * @return array
231 */
232 public function getRawModuleMenuData()
233 {
234 // Loads the backend modules available for the logged in user.
235 /** @var ModuleLoader $moduleLoader */
236 $moduleLoader = GeneralUtility::makeInstance(ModuleLoader::class);
237 $moduleLoader->observeWorkspaces = true;
238 $moduleLoader->load($GLOBALS['TBE_MODULES']);
239 $loadedModules = $moduleLoader->modules;
240
241 $modules = [];
242
243 // Unset modules that are meant to be hidden from the menu.
244 $loadedModules = $this->removeHiddenModules($loadedModules);
245 $dummyScript = BackendUtility::getModuleUrl('dummy');
246 foreach ($loadedModules as $moduleName => $moduleData) {
247 $moduleLink = '';
248 if (!is_array($moduleData['sub'])) {
249 $moduleLink = $moduleData['script'];
250 }
251 $moduleLink = GeneralUtility::resolveBackPath($moduleLink);
252 $moduleLabels = $moduleLoader->getLabelsForModule($moduleName);
253 $moduleKey = 'modmenu_' . $moduleName;
254 $modules[$moduleKey] = [
255 'name' => $moduleName,
256 'title' => $moduleLabels['title'],
257 'onclick' => 'top.goToModule(' . GeneralUtility::quoteJSvalue($moduleName) . ');',
258 'icon' => $this->getModuleIcon($moduleKey, $moduleData),
259 'link' => $moduleLink,
260 'description' => $moduleLabels['shortdescription']
261 ];
262 if (!is_array($moduleData['sub']) && $moduleData['script'] !== $dummyScript) {
263 // Work around for modules with own main entry, but being self the only submodule
264 $modules[$moduleKey]['subitems'][$moduleKey] = [
265 'name' => $moduleName,
266 'title' => $moduleLabels['title'],
267 'onclick' => 'top.goToModule(' . GeneralUtility::quoteJSvalue($moduleName) . ');',
268 'icon' => $this->getModuleIcon($moduleKey, $moduleData),
269 'link' => $moduleLink,
270 'originalLink' => $moduleLink,
271 'description' => $moduleLabels['shortdescription'],
272 'navigationFrameScript' => null,
273 'navigationFrameScriptParam' => null,
274 'navigationComponentId' => null
275 ];
276 } elseif (is_array($moduleData['sub'])) {
277 foreach ($moduleData['sub'] as $submoduleName => $submoduleData) {
278 if (isset($submoduleData['script'])) {
279 $submoduleLink = GeneralUtility::resolveBackPath($submoduleData['script']);
280 } else {
281 $submoduleLink = BackendUtility::getModuleUrl($submoduleData['name']);
282 }
283 $submoduleKey = $moduleName . '_' . $submoduleName;
284 $submoduleLabels = $moduleLoader->getLabelsForModule($submoduleKey);
285 $submoduleDescription = $submoduleLabels['shortdescription'];
286 $originalLink = $submoduleLink;
287 $navigationFrameScript = $submoduleData['navFrameScript'];
288 $modules[$moduleKey]['subitems'][$submoduleKey] = [
289 'name' => $moduleName . '_' . $submoduleName,
290 'title' => $submoduleLabels['title'],
291 'onclick' => 'top.goToModule(' . GeneralUtility::quoteJSvalue($moduleName . '_' . $submoduleName) . ');',
292 'icon' => $this->getModuleIcon($moduleKey, $submoduleData),
293 'link' => $submoduleLink,
294 'originalLink' => $originalLink,
295 'description' => $submoduleDescription,
296 'navigationFrameScript' => $navigationFrameScript,
297 'navigationFrameScriptParam' => $submoduleData['navFrameScriptParam'],
298 'navigationComponentId' => $submoduleData['navigationComponentId']
299 ];
300 // if the main module has a navframe script, inherit to the submodule,
301 // but only if it is not disabled explicitly (option is set to FALSE)
302 if ($moduleData['navFrameScript'] && $submoduleData['inheritNavigationComponentFromMainModule'] !== false) {
303 $modules[$moduleKey]['subitems'][$submoduleKey]['parentNavigationFrameScript'] = $moduleData['navFrameScript'];
304 }
305 }
306 }
307 }
308 return $modules;
309 }
310
311 /**
312 * Reads User configuration from options.hideModules and removes
313 * modules accordingly.
314 *
315 * @param array $loadedModules
316 * @return array
317 */
318 protected function removeHiddenModules($loadedModules)
319 {
320 $hiddenModules = $GLOBALS['BE_USER']->getTSConfig('options.hideModules');
321
322 // Hide modules if set in userTS.
323 if (!empty($hiddenModules['value'])) {
324 $hiddenMainModules = explode(',', $hiddenModules['value']);
325 foreach ($hiddenMainModules as $hiddenMainModule) {
326 unset($loadedModules[trim($hiddenMainModule)]);
327 }
328 }
329
330 // Hide sub-modules if set in userTS.
331 if (!empty($hiddenModules['properties']) && is_array($hiddenModules['properties'])) {
332 foreach ($hiddenModules['properties'] as $mainModuleName => $subModules) {
333 $hiddenSubModules = explode(',', $subModules);
334 foreach ($hiddenSubModules as $hiddenSubModule) {
335 unset($loadedModules[$mainModuleName]['sub'][trim($hiddenSubModule)]);
336 }
337 }
338 }
339
340 return $loadedModules;
341 }
342
343 /**
344 * gets the module icon
345 *
346 * @param string $moduleKey Module key
347 * @param array $moduleData the compiled data associated with it
348 * @return string Icon data, either sprite or <img> tag
349 */
350 protected function getModuleIcon($moduleKey, $moduleData)
351 {
352 $iconIdentifier = !(empty($moduleData['iconIdentifier']))
353 ? $moduleData['iconIdentifier']
354 : 'module-icon-' . $moduleKey;
355 $iconRegistry = GeneralUtility::makeInstance(IconRegistry::class);
356 if ($iconRegistry->isRegistered($iconIdentifier)) {
357 $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
358 return $iconFactory->getIcon($iconIdentifier)->render();
359 }
360 return '';
361 }
362 }