Passed
Push — master ( 741733...88fc2b )
by
unknown
14:08 queued 26s
created

findByModuleNameInGivenEntries()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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