[TASK] Remove global option BACK_PATH
[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\Utility\BackendUtility;
18 use TYPO3\CMS\Core\Imaging\Icon;
19 use TYPO3\CMS\Core\Imaging\IconFactory;
20 use TYPO3\CMS\Core\Utility\GeneralUtility;
21
22 /**
23 * Repository for backend module menu
24 * compiles all data from $GLOBALS[TBE_MODULES]
25 */
26 class BackendModuleRepository implements \TYPO3\CMS\Core\SingletonInterface
27 {
28 /**
29 * @var \TYPO3\CMS\Backend\Module\ModuleStorage
30 */
31 protected $moduleStorage;
32
33 /**
34 * Constructs the module menu and gets the Singleton instance of the menu
35 */
36 public function __construct()
37 {
38 $this->moduleStorage = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Module\ModuleStorage::class);
39
40 $rawData = $this->getRawModuleMenuData();
41
42 $this->convertRawModuleDataToModuleMenuObject($rawData);
43 $this->createMenuEntriesForTbeModulesExt();
44 }
45
46 /**
47 * loads all module information in the module storage
48 *
49 * @param array $excludeGroupNames
50 * @return \SplObjectStorage
51 */
52 public function loadAllowedModules(array $excludeGroupNames = array())
53 {
54 if (empty($excludeGroupNames)) {
55 return $this->moduleStorage->getEntries();
56 }
57
58 $modules = new \SplObjectStorage();
59 foreach ($this->moduleStorage->getEntries() as $moduleGroup) {
60 if (!in_array($moduleGroup->getName(), $excludeGroupNames, true)) {
61 if ($moduleGroup->getChildren()->count() > 0) {
62 $modules->attach($moduleGroup);
63 }
64 }
65 }
66
67 return $modules;
68 }
69
70 /**
71 * @param string $groupName
72 * @return \SplObjectStorage|FALSE
73 **/
74 public function findByGroupName($groupName = '')
75 {
76 foreach ($this->moduleStorage->getEntries() as $moduleGroup) {
77 if ($moduleGroup->getName() === $groupName) {
78 return $moduleGroup;
79 }
80 }
81
82 return false;
83 }
84
85 /**
86 * Finds a module menu entry by name
87 *
88 * @param string $name
89 * @return \TYPO3\CMS\Backend\Domain\Model\Module\BackendModule|bool
90 */
91 public function findByModuleName($name)
92 {
93 $entries = $this->moduleStorage->getEntries();
94 $entry = $this->findByModuleNameInGivenEntries($name, $entries);
95 return $entry;
96 }
97
98 /**
99 * Finds a module menu entry by name in a given storage
100 *
101 * @param string $name
102 * @param \SplObjectStorage $entries
103 * @return \TYPO3\CMS\Backend\Domain\Model\Module\BackendModule|bool
104 */
105 public function findByModuleNameInGivenEntries($name, \SplObjectStorage $entries)
106 {
107 foreach ($entries as $entry) {
108 if ($entry->getName() === $name) {
109 return $entry;
110 }
111 $children = $entry->getChildren();
112 if (!empty($children)) {
113 $childRecord = $this->findByModuleNameInGivenEntries($name, $children);
114 if ($childRecord !== false) {
115 return $childRecord;
116 }
117 }
118 }
119 return false;
120 }
121
122 /**
123 * Creates the module menu object structure from the raw data array
124 *
125 * @param array $rawModuleData
126 * @return void
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($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 * @return void
191 */
192 protected function createMenuEntriesForTbeModulesExt()
193 {
194 foreach ($GLOBALS['TBE_MODULES_EXT'] as $mainModule => $tbeModuleExt) {
195 list($main) = explode('_', $mainModule);
196 $mainEntry = $this->findByModuleName($main);
197 if ($mainEntry === false) {
198 continue;
199 }
200
201 $subEntries = $mainEntry->getChildren();
202 if (empty($subEntries)) {
203 continue;
204 }
205 $matchingSubEntry = $this->findByModuleName($mainModule);
206 if ($matchingSubEntry !== false) {
207 if (isset($tbeModuleExt['MOD_MENU']) && isset($tbeModuleExt['MOD_MENU']['function'])) {
208 foreach ($tbeModuleExt['MOD_MENU']['function'] as $subModule) {
209 $entry = $this->createEntryFromRawData($subModule);
210 $matchingSubEntry->addChild($entry);
211 }
212 }
213 }
214 }
215 }
216
217 /**
218 * Return language service instance
219 *
220 * @return \TYPO3\CMS\Lang\LanguageService
221 */
222 protected function getLanguageService()
223 {
224 return $GLOBALS['LANG'];
225 }
226
227 /**
228 * loads the module menu from the moduleloader based on $GLOBALS['TBE_MODULES']
229 * and compiles an array with all the data needed for menu etc.
230 *
231 * @return array
232 */
233 public function getRawModuleMenuData()
234 {
235 // Loads the backend modules available for the logged in user.
236 $moduleLoader = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Module\ModuleLoader::class);
237 $moduleLoader->observeWorkspaces = true;
238 $moduleLoader->load($GLOBALS['TBE_MODULES']);
239 $loadedModules = $moduleLoader->modules;
240
241 $modules = array();
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 $moduleKey = 'modmenu_' . $moduleName;
253 $modules[$moduleKey] = array(
254 'name' => $moduleName,
255 'title' => $GLOBALS['LANG']->moduleLabels['tabs'][$moduleName . '_tab'],
256 'onclick' => 'top.goToModule(' . GeneralUtility::quoteJSvalue($moduleName) . ');',
257 'icon' => $this->getModuleIcon($moduleName . '_tab', $moduleData),
258 'link' => $moduleLink,
259 'description' => $GLOBALS['LANG']->moduleLabels['labels'][$moduleKey . 'label']
260 );
261 if (!is_array($moduleData['sub']) && $moduleData['script'] !== $dummyScript) {
262 // Work around for modules with own main entry, but being self the only submodule
263 $modules[$moduleKey]['subitems'][$moduleKey] = array(
264 'name' => $moduleName,
265 'title' => $GLOBALS['LANG']->moduleLabels['tabs'][$moduleName . '_tab'],
266 'onclick' => 'top.goToModule(' . GeneralUtility::quoteJSvalue($moduleName) . ');',
267 'icon' => $this->getModuleIcon($moduleName . '_tab', $moduleData),
268 'link' => $moduleLink,
269 'originalLink' => $moduleLink,
270 'description' => $GLOBALS['LANG']->moduleLabels['labels'][$moduleKey . 'label'],
271 'navigationFrameScript' => null,
272 'navigationFrameScriptParam' => null,
273 'navigationComponentId' => null
274 );
275 } elseif (is_array($moduleData['sub'])) {
276 foreach ($moduleData['sub'] as $submoduleName => $submoduleData) {
277 if (isset($submoduleData['script'])) {
278 $submoduleLink = GeneralUtility::resolveBackPath($submoduleData['script']);
279 } else {
280 $submoduleLink = BackendUtility::getModuleUrl($submoduleData['name']);
281 }
282 $submoduleKey = $moduleName . '_' . $submoduleName . '_tab';
283 $submoduleDescription = $GLOBALS['LANG']->moduleLabels['labels'][$submoduleKey . 'label'];
284 $originalLink = $submoduleLink;
285 $navigationFrameScript = $submoduleData['navFrameScript'];
286 $modules[$moduleKey]['subitems'][$submoduleKey] = array(
287 'name' => $moduleName . '_' . $submoduleName,
288 'title' => $GLOBALS['LANG']->moduleLabels['tabs'][$submoduleKey],
289 'onclick' => 'top.goToModule(' . GeneralUtility::quoteJSvalue($moduleName . '_' . $submoduleName) . ');',
290 'icon' => $this->getModuleIcon($submoduleKey, $submoduleData),
291 'link' => $submoduleLink,
292 'originalLink' => $originalLink,
293 'description' => $submoduleDescription,
294 'navigationFrameScript' => $navigationFrameScript,
295 'navigationFrameScriptParam' => $submoduleData['navFrameScriptParam'],
296 'navigationComponentId' => $submoduleData['navigationComponentId']
297 );
298 // if the main module has a navframe script, inherit to the submodule,
299 // but only if it is not disabled explicitly (option is set to FALSE)
300 if ($moduleData['navFrameScript'] && $submoduleData['inheritNavigationComponentFromMainModule'] !== false) {
301 $modules[$moduleKey]['subitems'][$submoduleKey]['parentNavigationFrameScript'] = $moduleData['navFrameScript'];
302 }
303 }
304 }
305 }
306 return $modules;
307 }
308
309 /**
310 * Reads User configuration from options.hideModules and removes
311 * modules accordingly.
312 *
313 * @param array $loadedModules
314 * @return array
315 */
316 protected function removeHiddenModules($loadedModules)
317 {
318 $hiddenModules = $GLOBALS['BE_USER']->getTSConfig('options.hideModules');
319
320 // Hide modules if set in userTS.
321 if (!empty($hiddenModules['value'])) {
322 $hiddenMainModules = explode(',', $hiddenModules['value']);
323 foreach ($hiddenMainModules as $hiddenMainModule) {
324 unset($loadedModules[trim($hiddenMainModule)]);
325 }
326 }
327
328 // Hide sub-modules if set in userTS.
329 if (!empty($hiddenModules['properties']) && is_array($hiddenModules['properties'])) {
330 foreach ($hiddenModules['properties'] as $mainModuleName => $subModules) {
331 $hiddenSubModules = explode(',', $subModules);
332 foreach ($hiddenSubModules as $hiddenSubModule) {
333 unset($loadedModules[$mainModuleName]['sub'][trim($hiddenSubModule)]);
334 }
335 }
336 }
337
338 return $loadedModules;
339 }
340
341 /**
342 * gets the module icon and its size
343 *
344 * @param string $moduleKey Module key
345 * @param array $moduleData the compiled data associated with it
346 * @return string Icon data, either sprite or <img> tag
347 */
348 protected function getModuleIcon($moduleKey, $moduleData)
349 {
350 $icon = '';
351
352 // add as a sprite icon
353 if (!empty($moduleData['iconIdentifier'])) {
354 $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
355 $icon = $iconFactory->getIcon($moduleData['iconIdentifier'])->render();
356 } elseif (!empty($GLOBALS['LANG']->moduleLabels['tabs_images'][$moduleKey])) {
357 $imageReference = $GLOBALS['LANG']->moduleLabels['tabs_images'][$moduleKey];
358 $iconFileRelative = $this->getModuleIconRelative($imageReference);
359 if (!empty($iconFileRelative)) {
360 $iconTitle = $GLOBALS['LANG']->moduleLabels['tabs'][$moduleKey];
361 $iconSizes = @getimagesize($this->getModuleIconAbsolute($imageReference));
362 $icon = '<img src="' . $iconFileRelative . '" ' . $iconSizes[3] . ' title="' . htmlspecialchars($iconTitle) . '" alt="' . htmlspecialchars($iconTitle) . '" />';
363 }
364 }
365 return $icon;
366 }
367
368 /**
369 * Returns the filename readable for the script from PATH_typo3.
370 * That means absolute names are just returned while relative names are
371 * prepended with the path pointing back to typo3/ dir
372 *
373 * @param string $iconFilename Icon filename
374 * @return string Icon filename with absolute path
375 * @see getModuleIconRelative()
376 */
377 protected function getModuleIconAbsolute($iconFilename)
378 {
379 if (!GeneralUtility::isAbsPath($iconFilename)) {
380 $iconFilename = PATH_typo3 . $iconFilename;
381 }
382 return $iconFilename;
383 }
384
385 /**
386 * Returns relative path to the icon filename for use in img-tags
387 *
388 * @param string $iconFilename Icon filename
389 * @return string Icon filename with relative path
390 * @see getModuleIconAbsolute()
391 */
392 protected function getModuleIconRelative($iconFilename)
393 {
394 if (GeneralUtility::isAbsPath($iconFilename)) {
395 $iconFilename = '../' . \TYPO3\CMS\Core\Utility\PathUtility::stripPathSitePrefix($iconFilename);
396 }
397 return $iconFilename;
398 }
399 }