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