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