BackendModuleRepository.php 15.4 KB
Newer Older
1
<?php
2

3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
7
8
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
9
 *
10
11
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
14
 * The TYPO3 project - inspiring people to share!
 */
Nicole Cordes's avatar
Nicole Cordes committed
15

16
17
namespace TYPO3\CMS\Backend\Domain\Repository\Module;

18
use TYPO3\CMS\Backend\Domain\Model\Module\BackendModule;
19
use TYPO3\CMS\Backend\Module\ModuleLoader;
20
use TYPO3\CMS\Backend\Module\ModuleStorage;
21
22
use TYPO3\CMS\Backend\Routing\UriBuilder;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
23
use TYPO3\CMS\Core\Imaging\IconFactory;
24
use TYPO3\CMS\Core\Imaging\IconRegistry;
25
use TYPO3\CMS\Core\Localization\LanguageService;
26
use TYPO3\CMS\Core\SingletonInterface;
Benni Mack's avatar
Benni Mack committed
27
28
use TYPO3\CMS\Core\Utility\GeneralUtility;

29
30
/**
 * Repository for backend module menu
Benni Mack's avatar
Benni Mack committed
31
 * compiles all data from $GLOBALS[TBE_MODULES]
32
 */
33
class BackendModuleRepository implements SingletonInterface
34
35
36
37
38
{
    /**
     * @var \TYPO3\CMS\Backend\Module\ModuleStorage
     */
    protected $moduleStorage;
39

40
41
42
43
44
    /**
     * Constructs the module menu and gets the Singleton instance of the menu
     */
    public function __construct()
    {
45
        $this->moduleStorage = GeneralUtility::makeInstance(ModuleStorage::class);
46

47
        $rawData = $this->getRawModuleMenuData();
48

49
50
51
        $this->convertRawModuleDataToModuleMenuObject($rawData);
        $this->createMenuEntriesForTbeModulesExt();
    }
52

53
54
55
56
57
58
    /**
     * loads all module information in the module storage
     *
     * @param array $excludeGroupNames
     * @return \SplObjectStorage
     */
59
    public function loadAllowedModules(array $excludeGroupNames = [])
60
61
62
63
    {
        if (empty($excludeGroupNames)) {
            return $this->moduleStorage->getEntries();
        }
Benni Mack's avatar
Benni Mack committed
64

65
66
67
        $modules = new \SplObjectStorage();
        foreach ($this->moduleStorage->getEntries() as $moduleGroup) {
            if (!in_array($moduleGroup->getName(), $excludeGroupNames, true)) {
68
                if ($moduleGroup->getChildren()->count() > 0 || $moduleGroup->isStandalone()) {
69
70
71
72
                    $modules->attach($moduleGroup);
                }
            }
        }
73

74
75
        return $modules;
    }
76

77
78
    /**
     * @param string $groupName
79
     * @return \SplObjectStorage|false
80
81
82
83
84
85
86
87
     **/
    public function findByGroupName($groupName = '')
    {
        foreach ($this->moduleStorage->getEntries() as $moduleGroup) {
            if ($moduleGroup->getName() === $groupName) {
                return $moduleGroup;
            }
        }
88

89
90
        return false;
    }
91

92
93
94
95
    /**
     * Finds a module menu entry by name
     *
     * @param string $name
96
     * @return \TYPO3\CMS\Backend\Domain\Model\Module\BackendModule|bool
97
98
99
100
101
102
103
     */
    public function findByModuleName($name)
    {
        $entries = $this->moduleStorage->getEntries();
        $entry = $this->findByModuleNameInGivenEntries($name, $entries);
        return $entry;
    }
104

105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
    /**
     * Finds a module menu entry by name in a given storage
     *
     * @param string $name
     * @param \SplObjectStorage $entries
     * @return \TYPO3\CMS\Backend\Domain\Model\Module\BackendModule|bool
     */
    public function findByModuleNameInGivenEntries($name, \SplObjectStorage $entries)
    {
        foreach ($entries as $entry) {
            if ($entry->getName() === $name) {
                return $entry;
            }
            $children = $entry->getChildren();
            if (!empty($children)) {
                $childRecord = $this->findByModuleNameInGivenEntries($name, $children);
                if ($childRecord !== false) {
                    return $childRecord;
                }
            }
        }
        return false;
    }
128

129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
    /**
     * Creates the module menu object structure from the raw data array
     *
     * @param array $rawModuleData
     */
    protected function convertRawModuleDataToModuleMenuObject(array $rawModuleData)
    {
        foreach ($rawModuleData as $module) {
            $entry = $this->createEntryFromRawData($module);
            if (isset($module['subitems']) && !empty($module['subitems'])) {
                foreach ($module['subitems'] as $subitem) {
                    $subEntry = $this->createEntryFromRawData($subitem);
                    $entry->addChild($subEntry);
                }
            }
            $this->moduleStorage->attachEntry($entry);
        }
    }
147

148
149
150
151
152
153
154
155
    /**
     * Creates a menu entry object from an array
     *
     * @param array $module
     * @return \TYPO3\CMS\Backend\Domain\Model\Module\BackendModule
     */
    protected function createEntryFromRawData(array $module)
    {
156
        /** @var \TYPO3\CMS\Backend\Domain\Model\Module\BackendModule $entry */
157
        $entry = GeneralUtility::makeInstance(BackendModule::class);
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
        if (!empty($module['name']) && is_string($module['name'])) {
            $entry->setName($module['name']);
        }
        if (!empty($module['title']) && is_string($module['title'])) {
            $entry->setTitle($this->getLanguageService()->sL($module['title']));
        }
        if (!empty($module['onclick']) && is_string($module['onclick'])) {
            $entry->setOnClick($module['onclick']);
        }
        if (!empty($module['link']) && is_string($module['link'])) {
            $entry->setLink($module['link']);
        } elseif (empty($module['link']) && !empty($module['path']) && is_string($module['path'])) {
            $entry->setLink($module['path']);
        }
        if (!empty($module['description']) && is_string($module['description'])) {
173
            $entry->setDescription($this->getLanguageService()->sL($module['description']));
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
        }
        if (!empty($module['icon'])) {
            $entry->setIcon($module['icon']);
        }
        if (!empty($module['navigationComponentId']) && is_string($module['navigationComponentId'])) {
            $entry->setNavigationComponentId($module['navigationComponentId']);
        }
        if (!empty($module['navigationFrameScript']) && is_string($module['navigationFrameScript'])) {
            $entry->setNavigationFrameScript($module['navigationFrameScript']);
        } elseif (!empty($module['parentNavigationFrameScript']) && is_string($module['parentNavigationFrameScript'])) {
            $entry->setNavigationFrameScript($module['parentNavigationFrameScript']);
        }
        if (!empty($module['navigationFrameScriptParam']) && is_string($module['navigationFrameScriptParam'])) {
            $entry->setNavigationFrameScriptParameters($module['navigationFrameScriptParam']);
        }
189
190
191
        if (!empty($module['standalone'])) {
            $entry->setStandalone((bool)$module['standalone']);
        }
192
193
        $moduleMenuState = json_decode($this->getBackendUser()->uc['modulemenu'] ?? '{}', true);
        $entry->setCollapsed(isset($moduleMenuState[$module['name']]));
194
195
        return $entry;
    }
Benni Mack's avatar
Benni Mack committed
196

197
198
199
200
201
202
    /**
     * Creates the "third level" menu entries (submodules for the info module for
     * example) from the TBE_MODULES_EXT array
     */
    protected function createMenuEntriesForTbeModulesExt()
    {
203
        foreach ($GLOBALS['TBE_MODULES_EXT'] ?? [] as $mainModule => $tbeModuleExt) {
204
            [$main] = explode('_', $mainModule);
205
206
207
208
            $mainEntry = $this->findByModuleName($main);
            if ($mainEntry === false) {
                continue;
            }
Benni Mack's avatar
Benni Mack committed
209

210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
            $subEntries = $mainEntry->getChildren();
            if (empty($subEntries)) {
                continue;
            }
            $matchingSubEntry = $this->findByModuleName($mainModule);
            if ($matchingSubEntry !== false) {
                if (isset($tbeModuleExt['MOD_MENU']) && isset($tbeModuleExt['MOD_MENU']['function'])) {
                    foreach ($tbeModuleExt['MOD_MENU']['function'] as $subModule) {
                        $entry = $this->createEntryFromRawData($subModule);
                        $matchingSubEntry->addChild($entry);
                    }
                }
            }
        }
    }
Benni Mack's avatar
Benni Mack committed
225

226
227
228
229
230
231
232
233
234
    /**
     * loads the module menu from the moduleloader based on $GLOBALS['TBE_MODULES']
     * and compiles an array with all the data needed for menu etc.
     *
     * @return array
     */
    public function getRawModuleMenuData()
    {
        // Loads the backend modules available for the logged in user.
235
236
        /** @var ModuleLoader $moduleLoader */
        $moduleLoader = GeneralUtility::makeInstance(ModuleLoader::class);
237
238
239
        $moduleLoader->observeWorkspaces = true;
        $moduleLoader->load($GLOBALS['TBE_MODULES']);
        $loadedModules = $moduleLoader->modules;
Benni Mack's avatar
Benni Mack committed
240

241
        $modules = [];
Benni Mack's avatar
Benni Mack committed
242

243
244
        // Unset modules that are meant to be hidden from the menu.
        $loadedModules = $this->removeHiddenModules($loadedModules);
245
        $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
246
        $dummyScript = (string)$uriBuilder->buildUriFromRoute('dummy');
247
248
        foreach ($loadedModules as $moduleName => $moduleData) {
            $moduleLink = '';
249
            if (!is_array($moduleData['sub'] ?? null)) {
250
251
252
                $moduleLink = $moduleData['script'];
            }
            $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
                'standalone' => (bool)($moduleData['standalone'] ?? false)
263
            ];
264
            if ((($moduleData['standalone'] ?? false) === false) && !is_array($moduleData['sub']) && $moduleData['script'] !== $dummyScript) {
265
                // Work around for modules with own main entry, but being self the only submodule
266
                $modules[$moduleKey]['subitems'][$moduleKey] = [
267
                    'name' => $moduleName,
268
                    'title' => $moduleLabels['title'],
269
                    'onclick' => 'top.goToModule(' . GeneralUtility::quoteJSvalue($moduleName) . ');',
270
                    'icon' => $this->getModuleIcon($moduleKey, $moduleData),
271
272
                    'link' => $moduleLink,
                    'originalLink' => $moduleLink,
273
                    'description' => $moduleLabels['shortdescription'],
274
275
276
                    'navigationFrameScript' => null,
                    'navigationFrameScriptParam' => null,
                    'navigationComponentId' => null
277
                ];
278
            } elseif (is_array($moduleData['sub'] ?? null)) {
279
280
281
282
                foreach ($moduleData['sub'] as $submoduleName => $submoduleData) {
                    if (isset($submoduleData['script'])) {
                        $submoduleLink = GeneralUtility::resolveBackPath($submoduleData['script']);
                    } else {
283
                        $submoduleLink = (string)$uriBuilder->buildUriFromRoute($submoduleData['name']);
284
                    }
285
286
287
                    $submoduleKey = $moduleName . '_' . $submoduleName;
                    $submoduleLabels = $moduleLoader->getLabelsForModule($submoduleKey);
                    $submoduleDescription = $submoduleLabels['shortdescription'];
288
                    $originalLink = $submoduleLink;
289
                    $navigationFrameScript = $submoduleData['navFrameScript'] ?? null;
290
                    $modules[$moduleKey]['subitems'][$submoduleKey] = [
291
                        'name' => $moduleName . '_' . $submoduleName,
292
                        'title' => $submoduleLabels['title'],
293
                        'onclick' => 'top.goToModule(' . GeneralUtility::quoteJSvalue($moduleName . '_' . $submoduleName) . ');',
294
                        'icon' => $this->getModuleIcon($moduleKey, $submoduleData),
295
296
297
298
                        'link' => $submoduleLink,
                        'originalLink' => $originalLink,
                        'description' => $submoduleDescription,
                        'navigationFrameScript' => $navigationFrameScript,
299
300
                        'navigationFrameScriptParam' => $submoduleData['navFrameScriptParam'] ?? null,
                        'navigationComponentId' => $submoduleData['navigationComponentId'] ?? null
301
                    ];
302
303
                    // if the main module has a navframe script, inherit to the submodule,
                    // but only if it is not disabled explicitly (option is set to FALSE)
304
                    if (($moduleData['navFrameScript'] ?? false) && $submoduleData['inheritNavigationComponentFromMainModule'] !== false) {
305
306
307
308
309
310
311
                        $modules[$moduleKey]['subitems'][$submoduleKey]['parentNavigationFrameScript'] = $moduleData['navFrameScript'];
                    }
                }
            }
        }
        return $modules;
    }
Benni Mack's avatar
Benni Mack committed
312

313
314
315
316
317
318
319
320
321
322
323
324
    public function modulesHaveNavigationComponent(): bool
    {
        /** @var BackendModule $module */
        foreach ($this->moduleStorage->getEntries() as $module) {
            if ($module->getNavigationComponentId() !== '') {
                return true;
            }
        }

        return false;
    }

325
326
327
328
329
330
331
332
333
    /**
     * Reads User configuration from options.hideModules and removes
     * modules accordingly.
     *
     * @param array $loadedModules
     * @return array
     */
    protected function removeHiddenModules($loadedModules)
    {
334
        $userTsConfig = $this->getBackendUser()->getTSConfig();
Benni Mack's avatar
Benni Mack committed
335

336
        // Hide modules if set in userTS.
337
338
339
        $hiddenMainModules = GeneralUtility::trimExplode(',', $userTsConfig['options.']['hideModules'] ?? '', true);
        foreach ($hiddenMainModules as $hiddenMainModule) {
            unset($loadedModules[$hiddenMainModule]);
340
        }
Benni Mack's avatar
Benni Mack committed
341

342
        // Hide sub-modules if set in userTS.
343
344
345
346
        $hiddenModules = $userTsConfig['options.']['hideModules.'] ?? [];
        if (is_array($hiddenModules)) {
            foreach ($hiddenModules as $mainModuleName => $subModules) {
                $hiddenSubModules = GeneralUtility::trimExplode(',', $subModules, true);
347
                foreach ($hiddenSubModules as $hiddenSubModule) {
348
                    unset($loadedModules[$mainModuleName]['sub'][$hiddenSubModule]);
349
350
351
                }
            }
        }
Benni Mack's avatar
Benni Mack committed
352

353
354
        return $loadedModules;
    }
Benni Mack's avatar
Benni Mack committed
355

356
    /**
357
     * gets the module icon
358
359
360
361
362
363
364
     *
     * @param string $moduleKey Module key
     * @param array $moduleData the compiled data associated with it
     * @return string Icon data, either sprite or <img> tag
     */
    protected function getModuleIcon($moduleKey, $moduleData)
    {
365
        $iconIdentifier = !empty($moduleData['iconIdentifier'])
366
367
            ? $moduleData['iconIdentifier']
            : 'module-icon-' . $moduleKey;
368
369
        $iconRegistry = GeneralUtility::makeInstance(IconRegistry::class);
        if ($iconRegistry->isRegistered($iconIdentifier)) {
370
            $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
371
            return $iconFactory->getIcon($iconIdentifier)->render('inline');
372
        }
373
        return '';
374
    }
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392

    /**
     * Return language service instance
     *
     * @return LanguageService
     */
    protected function getLanguageService()
    {
        return $GLOBALS['LANG'];
    }

    /**
     * @return BackendUserAuthentication
     */
    protected function getBackendUser(): BackendUserAuthentication
    {
        return $GLOBALS['BE_USER'];
    }
393
}