Completed
Push — master ( 720717...80d8f6 )
by
unknown
26:45 queued 13:07
created

BackendModuleRepository::getLanguageService()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
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) {
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
        $moduleMenuState = json_decode($this->getBackendUser()->uc['modulemenu'] ?? '{}', true);
187
        $entry->setCollapsed(isset($moduleMenuState[$module['name']]));
188
        return $entry;
189
    }
190
191
    /**
192
     * Creates the "third level" menu entries (submodules for the info module for
193
     * example) from the TBE_MODULES_EXT array
194
     */
195
    protected function createMenuEntriesForTbeModulesExt()
196
    {
197
        foreach ($GLOBALS['TBE_MODULES_EXT'] ?? [] as $mainModule => $tbeModuleExt) {
198
            [$main] = explode('_', $mainModule);
199
            $mainEntry = $this->findByModuleName($main);
200
            if ($mainEntry === false) {
201
                continue;
202
            }
203
204
            $subEntries = $mainEntry->getChildren();
205
            if (empty($subEntries)) {
206
                continue;
207
            }
208
            $matchingSubEntry = $this->findByModuleName($mainModule);
209
            if ($matchingSubEntry !== false) {
210
                if (isset($tbeModuleExt['MOD_MENU']) && isset($tbeModuleExt['MOD_MENU']['function'])) {
211
                    foreach ($tbeModuleExt['MOD_MENU']['function'] as $subModule) {
212
                        $entry = $this->createEntryFromRawData($subModule);
213
                        $matchingSubEntry->addChild($entry);
214
                    }
215
                }
216
            }
217
        }
218
    }
219
220
    /**
221
     * loads the module menu from the moduleloader based on $GLOBALS['TBE_MODULES']
222
     * and compiles an array with all the data needed for menu etc.
223
     *
224
     * @return array
225
     */
226
    public function getRawModuleMenuData()
227
    {
228
        // Loads the backend modules available for the logged in user.
229
        /** @var ModuleLoader $moduleLoader */
230
        $moduleLoader = GeneralUtility::makeInstance(ModuleLoader::class);
231
        $moduleLoader->observeWorkspaces = true;
232
        $moduleLoader->load($GLOBALS['TBE_MODULES']);
233
        $loadedModules = $moduleLoader->modules;
234
235
        $modules = [];
236
237
        // Unset modules that are meant to be hidden from the menu.
238
        $loadedModules = $this->removeHiddenModules($loadedModules);
239
        $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
240
        $dummyScript = (string)$uriBuilder->buildUriFromRoute('dummy');
241
        foreach ($loadedModules as $moduleName => $moduleData) {
242
            $moduleLink = '';
243
            if (!is_array($moduleData['sub'])) {
244
                $moduleLink = $moduleData['script'];
245
            }
246
            $moduleLink = GeneralUtility::resolveBackPath($moduleLink);
247
            $moduleLabels = $moduleLoader->getLabelsForModule($moduleName);
248
            $moduleKey = 'modmenu_' . $moduleName;
249
            $modules[$moduleKey] = [
250
                'name' => $moduleName,
251
                'title' => $moduleLabels['title'],
252
                'onclick' => 'top.goToModule(' . GeneralUtility::quoteJSvalue($moduleName) . ');',
253
                'icon' => $this->getModuleIcon($moduleKey, $moduleData),
254
                'link' => $moduleLink,
255
                'description' => $moduleLabels['shortdescription']
256
            ];
257
            if (!is_array($moduleData['sub']) && $moduleData['script'] !== $dummyScript) {
258
                // Work around for modules with own main entry, but being self the only submodule
259
                $modules[$moduleKey]['subitems'][$moduleKey] = [
260
                    'name' => $moduleName,
261
                    'title' => $moduleLabels['title'],
262
                    'onclick' => 'top.goToModule(' . GeneralUtility::quoteJSvalue($moduleName) . ');',
263
                    'icon' => $this->getModuleIcon($moduleKey, $moduleData),
264
                    'link' => $moduleLink,
265
                    'originalLink' => $moduleLink,
266
                    'description' => $moduleLabels['shortdescription'],
267
                    'navigationFrameScript' => null,
268
                    'navigationFrameScriptParam' => null,
269
                    'navigationComponentId' => null
270
                ];
271
            } elseif (is_array($moduleData['sub'])) {
272
                foreach ($moduleData['sub'] as $submoduleName => $submoduleData) {
273
                    if (isset($submoduleData['script'])) {
274
                        $submoduleLink = GeneralUtility::resolveBackPath($submoduleData['script']);
275
                    } else {
276
                        $submoduleLink = (string)$uriBuilder->buildUriFromRoute($submoduleData['name']);
277
                    }
278
                    $submoduleKey = $moduleName . '_' . $submoduleName;
279
                    $submoduleLabels = $moduleLoader->getLabelsForModule($submoduleKey);
280
                    $submoduleDescription = $submoduleLabels['shortdescription'];
281
                    $originalLink = $submoduleLink;
282
                    $navigationFrameScript = $submoduleData['navFrameScript'];
283
                    $modules[$moduleKey]['subitems'][$submoduleKey] = [
284
                        'name' => $moduleName . '_' . $submoduleName,
285
                        'title' => $submoduleLabels['title'],
286
                        'onclick' => 'top.goToModule(' . GeneralUtility::quoteJSvalue($moduleName . '_' . $submoduleName) . ');',
287
                        'icon' => $this->getModuleIcon($moduleKey, $submoduleData),
288
                        'link' => $submoduleLink,
289
                        'originalLink' => $originalLink,
290
                        'description' => $submoduleDescription,
291
                        'navigationFrameScript' => $navigationFrameScript,
292
                        'navigationFrameScriptParam' => $submoduleData['navFrameScriptParam'],
293
                        'navigationComponentId' => $submoduleData['navigationComponentId']
294
                    ];
295
                    // if the main module has a navframe script, inherit to the submodule,
296
                    // but only if it is not disabled explicitly (option is set to FALSE)
297
                    if ($moduleData['navFrameScript'] && $submoduleData['inheritNavigationComponentFromMainModule'] !== false) {
298
                        $modules[$moduleKey]['subitems'][$submoduleKey]['parentNavigationFrameScript'] = $moduleData['navFrameScript'];
299
                    }
300
                }
301
            }
302
        }
303
        return $modules;
304
    }
305
306
    public function modulesHaveNavigationComponent(): bool
307
    {
308
        /** @var BackendModule $module */
309
        foreach ($this->moduleStorage->getEntries() as $module) {
310
            if ($module->getNavigationComponentId() !== '') {
311
                return true;
312
            }
313
        }
314
315
        return false;
316
    }
317
318
    /**
319
     * Reads User configuration from options.hideModules and removes
320
     * modules accordingly.
321
     *
322
     * @param array $loadedModules
323
     * @return array
324
     */
325
    protected function removeHiddenModules($loadedModules)
326
    {
327
        $userTsConfig = $this->getBackendUser()->getTSConfig();
328
329
        // Hide modules if set in userTS.
330
        $hiddenMainModules = GeneralUtility::trimExplode(',', $userTsConfig['options.']['hideModules'] ?? '', true);
331
        foreach ($hiddenMainModules as $hiddenMainModule) {
332
            unset($loadedModules[$hiddenMainModule]);
333
        }
334
335
        // Hide sub-modules if set in userTS.
336
        $hiddenModules = $userTsConfig['options.']['hideModules.'] ?? [];
337
        if (is_array($hiddenModules)) {
338
            foreach ($hiddenModules as $mainModuleName => $subModules) {
339
                $hiddenSubModules = GeneralUtility::trimExplode(',', $subModules, true);
340
                foreach ($hiddenSubModules as $hiddenSubModule) {
341
                    unset($loadedModules[$mainModuleName]['sub'][$hiddenSubModule]);
342
                }
343
            }
344
        }
345
346
        return $loadedModules;
347
    }
348
349
    /**
350
     * gets the module icon
351
     *
352
     * @param string $moduleKey Module key
353
     * @param array $moduleData the compiled data associated with it
354
     * @return string Icon data, either sprite or <img> tag
355
     */
356
    protected function getModuleIcon($moduleKey, $moduleData)
357
    {
358
        $iconIdentifier = !empty($moduleData['iconIdentifier'])
359
            ? $moduleData['iconIdentifier']
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
360
            : 'module-icon-' . $moduleKey;
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
361
        $iconRegistry = GeneralUtility::makeInstance(IconRegistry::class);
362
        if ($iconRegistry->isRegistered($iconIdentifier)) {
363
            $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
364
            return $iconFactory->getIcon($iconIdentifier)->render('inline');
365
        }
366
        return '';
367
    }
368
369
    /**
370
     * Return language service instance
371
     *
372
     * @return LanguageService
373
     */
374
    protected function getLanguageService()
375
    {
376
        return $GLOBALS['LANG'];
377
    }
378
379
    /**
380
     * @return BackendUserAuthentication
381
     */
382
    protected function getBackendUser(): BackendUserAuthentication
383
    {
384
        return $GLOBALS['BE_USER'];
385
    }
386
}
387