Completed
Push — master ( ff226c...7e2a5d )
by
unknown
29:24 queued 14:04
created

BackendController::initializeToolbarItems()   A

Complexity

Conditions 6
Paths 5

Size

Total Lines 27
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 18
nc 5
nop 0
dl 0
loc 27
rs 9.0444
c 0
b 0
f 0
1
<?php
2
3
namespace TYPO3\CMS\Backend\Controller;
4
5
/*
6
 * This file is part of the TYPO3 CMS project.
7
 *
8
 * It is free software; you can redistribute it and/or modify it under
9
 * the terms of the GNU General Public License, either version 2
10
 * of the License, or any later version.
11
 *
12
 * For the full copyright and license information, please read the
13
 * LICENSE.txt file that was distributed with this source code.
14
 *
15
 * The TYPO3 project - inspiring people to share!
16
 */
17
18
use Psr\Http\Message\ResponseInterface;
19
use Psr\Http\Message\ServerRequestInterface;
20
use TYPO3\CMS\Backend\Domain\Repository\Module\BackendModuleRepository;
21
use TYPO3\CMS\Backend\Module\ModuleLoader;
22
use TYPO3\CMS\Backend\Routing\UriBuilder;
23
use TYPO3\CMS\Backend\Template\ModuleTemplate;
24
use TYPO3\CMS\Backend\Toolbar\ToolbarItemInterface;
25
use TYPO3\CMS\Backend\Utility\BackendUtility;
26
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
27
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
28
use TYPO3\CMS\Core\Http\HtmlResponse;
29
use TYPO3\CMS\Core\Http\JsonResponse;
30
use TYPO3\CMS\Core\Imaging\IconFactory;
31
use TYPO3\CMS\Core\Information\Typo3Version;
32
use TYPO3\CMS\Core\Localization\LanguageService;
33
use TYPO3\CMS\Core\Page\PageRenderer;
34
use TYPO3\CMS\Core\Type\Bitmask\Permission;
35
use TYPO3\CMS\Core\Type\File\ImageInfo;
36
use TYPO3\CMS\Core\Utility\GeneralUtility;
37
use TYPO3\CMS\Core\Utility\MathUtility;
38
use TYPO3\CMS\Core\Utility\PathUtility;
39
use TYPO3\CMS\Fluid\View\StandaloneView;
40
41
/**
42
 * Class for rendering the TYPO3 backend
43
 */
44
class BackendController
45
{
46
    /**
47
     * @var string
48
     */
49
    protected $css = '';
50
51
    /**
52
     * @var array
53
     */
54
    protected $toolbarItems = [];
55
56
    /**
57
     * @var string
58
     */
59
    protected $templatePath = 'EXT:backend/Resources/Private/Templates/';
60
61
    /**
62
     * @var string
63
     */
64
    protected $partialPath = 'EXT:backend/Resources/Private/Partials/';
65
66
    /**
67
     * @var BackendModuleRepository
68
     */
69
    protected $backendModuleRepository;
70
71
    /**
72
     * @var PageRenderer
73
     */
74
    protected $pageRenderer;
75
76
    /**
77
     * @var IconFactory
78
     */
79
    protected $iconFactory;
80
81
    /**
82
     * @var Typo3Version
83
     */
84
    protected $typo3Version;
85
86
    /**
87
     * @var UriBuilder
88
     */
89
    protected $uriBuilder;
90
91
    /**
92
     * @var \SplObjectStorage
93
     */
94
    protected $moduleStorage;
95
96
    /**
97
     * Constructor
98
     */
99
    public function __construct()
100
    {
101
        $this->getLanguageService()->includeLLFile('EXT:core/Resources/Private/Language/locallang_misc.xlf');
102
        $this->backendModuleRepository = GeneralUtility::makeInstance(BackendModuleRepository::class);
103
        $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
104
        $this->uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
105
        $this->typo3Version = GeneralUtility::makeInstance(Typo3Version::class);
106
        $this->pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
107
        // Add default BE javascript
108
        $this->pageRenderer->addJsFile('EXT:backend/Resources/Public/JavaScript/md5.js');
109
        $this->pageRenderer->addJsFile('EXT:backend/Resources/Public/JavaScript/backend.js');
110
        $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/LoginRefresh', 'function(LoginRefresh) {
111
			LoginRefresh.setIntervalTime(' . MathUtility::forceIntegerInRange((int)$GLOBALS['TYPO3_CONF_VARS']['BE']['sessionTimeout'] - 60, 60) . ');
112
			LoginRefresh.setLoginFramesetUrl(' . GeneralUtility::quoteJSvalue((string)$this->uriBuilder->buildUriFromRoute('login_frameset')) . ');
113
			LoginRefresh.setLogoutUrl(' . GeneralUtility::quoteJSvalue((string)$this->uriBuilder->buildUriFromRoute('logout')) . ');
114
			LoginRefresh.initialize();
115
		}');
116
117
        $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/BroadcastService', 'function(service) { service.listen(); }');
118
        $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/ModuleMenu');
119
        $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Toolbar');
120
        $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Notification');
121
        $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Modal');
122
        $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/InfoWindow');
123
        $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
124
125
        // load the storage API and fill the UC into the PersistentStorage, so no additional AJAX call is needed
126
        $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Storage/Persistent', 'function(PersistentStorage) {
127
            PersistentStorage.load(' . json_encode($this->getBackendUser()->uc) . ');
128
        }');
129
130
        $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/DebugConsole');
131
132
        $this->pageRenderer->addInlineLanguageLabelFile('EXT:core/Resources/Private/Language/locallang_core.xlf');
133
        $this->pageRenderer->addInlineLanguageLabelFile('EXT:core/Resources/Private/Language/locallang_misc.xlf');
134
        $this->pageRenderer->addInlineLanguageLabelFile('EXT:backend/Resources/Private/Language/locallang_layout.xlf');
135
        $this->pageRenderer->addInlineLanguageLabelFile('EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf');
136
        $this->pageRenderer->addInlineLanguageLabelFile('EXT:core/Resources/Private/Language/debugger.xlf');
137
        $this->pageRenderer->addInlineLanguageLabelFile('EXT:core/Resources/Private/Language/wizard.xlf');
138
139
        $this->pageRenderer->addInlineSetting('ContextHelp', 'moduleUrl', (string)$this->uriBuilder->buildUriFromRoute('help_cshmanual'));
140
        $this->pageRenderer->addInlineSetting('ShowItem', 'moduleUrl', (string)$this->uriBuilder->buildUriFromRoute('show_item'));
141
        $this->pageRenderer->addInlineSetting('RecordHistory', 'moduleUrl', (string)$this->uriBuilder->buildUriFromRoute('record_history'));
142
        $this->pageRenderer->addInlineSetting('NewRecord', 'moduleUrl', (string)$this->uriBuilder->buildUriFromRoute('db_new'));
143
        $this->pageRenderer->addInlineSetting('FormEngine', 'moduleUrl', (string)$this->uriBuilder->buildUriFromRoute('record_edit'));
144
        $this->pageRenderer->addInlineSetting('RecordCommit', 'moduleUrl', (string)$this->uriBuilder->buildUriFromRoute('tce_db'));
145
        $this->pageRenderer->addInlineSetting('FileCommit', 'moduleUrl', (string)$this->uriBuilder->buildUriFromRoute('tce_file'));
146
        $this->pageRenderer->addInlineSetting('WebLayout', 'moduleUrl', (string)$this->uriBuilder->buildUriFromRoute('web_layout'));
147
148
        $this->initializeToolbarItems();
149
        $this->executeHook('constructPostProcess');
150
151
        $this->moduleStorage = $this->backendModuleRepository->loadAllowedModules(['user', 'help']);
152
    }
153
154
    /**
155
     * Initialize toolbar item objects
156
     *
157
     * @throws \RuntimeException
158
     */
159
    protected function initializeToolbarItems()
160
    {
161
        $toolbarItemInstances = [];
162
        foreach ($GLOBALS['TYPO3_CONF_VARS']['BE']['toolbarItems'] ?? [] as $className) {
163
            $toolbarItemInstance = GeneralUtility::makeInstance($className);
164
            if (!$toolbarItemInstance instanceof ToolbarItemInterface) {
165
                throw new \RuntimeException(
166
                    'class ' . $className . ' is registered as toolbar item but does not implement'
167
                        . ToolbarItemInterface::class,
168
                    1415958218
169
                );
170
            }
171
            $index = (int)$toolbarItemInstance->getIndex();
172
            if ($index < 0 || $index > 100) {
173
                throw new \RuntimeException(
174
                    'getIndex() must return an integer between 0 and 100',
175
                    1415968498
176
                );
177
            }
178
            // Find next free position in array
179
            while (array_key_exists($index, $toolbarItemInstances)) {
180
                $index++;
181
            }
182
            $toolbarItemInstances[$index] = $toolbarItemInstance;
183
        }
184
        ksort($toolbarItemInstances);
185
        $this->toolbarItems = $toolbarItemInstances;
186
    }
187
188
    /**
189
     * Main function generating the BE scaffolding
190
     *
191
     * @param ServerRequestInterface $request
192
     * @return ResponseInterface the response with the content
193
     */
194
    public function mainAction(ServerRequestInterface $request): ResponseInterface
195
    {
196
        $this->executeHook('renderPreProcess');
197
198
        // Prepare the scaffolding, at this point extension may still add javascript and css
199
        $moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
200
        $view = $moduleTemplate->getView();
201
        $view->setPartialRootPaths([GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Partials')]);
202
        $view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName($this->templatePath . 'Backend/Main.html'));
203
        $view->assign('moduleMenuCollapsed', $this->getCollapseStateOfMenu());
204
        $view->assign('moduleMenu', $this->generateModuleMenu());
205
        $view->assign('topbar', $this->renderTopbar());
206
        $view->assign('hasModules', count($this->moduleStorage) > 0);
207
208
        if (!empty($this->css)) {
209
            $this->pageRenderer->addCssInlineBlock('BackendInlineCSS', $this->css);
210
        }
211
        $this->generateJavascript($request);
212
213
        // Set document title
214
        $typo3Version = 'TYPO3 CMS ' . $this->typo3Version->getVersion();
215
        $title = $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] ? $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] . ' [' . $typo3Version . ']' : $typo3Version;
216
        $moduleTemplate->setTitle($title);
217
218
        // Renders the backend scaffolding
219
        $content = $moduleTemplate->renderContent();
220
        $this->executeHook('renderPostProcess', ['content' => &$content]);
221
        return new HtmlResponse($content);
222
    }
223
224
    /**
225
     * Renders the topbar, containing the backend logo, sitename etc.
226
     *
227
     * @return string
228
     */
229
    protected function renderTopbar()
230
    {
231
        $view = $this->getFluidTemplateObject($this->partialPath . 'Backend/Topbar.html');
232
233
        // Extension Configuration to find the TYPO3 logo in the left corner
234
        $extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('backend');
235
        $logoPath = '';
236
        if (!empty($extConf['backendLogo'])) {
237
            $customBackendLogo = GeneralUtility::getFileAbsFileName(ltrim($extConf['backendLogo'], '/'));
238
            if (!empty($customBackendLogo)) {
239
                $logoPath = $customBackendLogo;
240
            }
241
        }
242
        // if no custom logo was set or the path is invalid, use the original one
243
        if (empty($logoPath) || !file_exists($logoPath)) {
244
            $logoPath = GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Public/Images/typo3_logo_orange.svg');
245
            $logoWidth = 22;
246
            $logoHeight = 22;
247
        } else {
248
            // set width/height for custom logo
249
            $imageInfo = GeneralUtility::makeInstance(ImageInfo::class, $logoPath);
250
            $logoWidth = $imageInfo->getWidth() ?? '22';
251
            $logoHeight = $imageInfo->getHeight() ?? '22';
252
253
            // High-resolution?
254
            if (strpos($logoPath, '@2x.') !== false) {
255
                $logoWidth /= 2;
256
                $logoHeight /= 2;
257
            }
258
        }
259
260
        $view->assign('hasModules', count($this->moduleStorage) > 0);
261
        $view->assign('modulesHaveNavigationComponent', $this->backendModuleRepository->modulesHaveNavigationComponent());
262
        $view->assign('logoUrl', PathUtility::getAbsoluteWebPath($logoPath));
263
        $view->assign('logoWidth', $logoWidth);
264
        $view->assign('logoHeight', $logoHeight);
265
        $view->assign('applicationVersion', $this->typo3Version->getVersion());
266
        $view->assign('siteName', $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename']);
267
        $view->assign('toolbar', $this->renderToolbar());
268
269
        return $view->render();
270
    }
271
272
    /**
273
     * Renders the items in the top toolbar
274
     *
275
     * @return string top toolbar elements as HTML
276
     */
277
    protected function renderToolbar()
278
    {
279
        $toolbar = [];
280
        foreach ($this->toolbarItems as $toolbarItem) {
281
            /** @var ToolbarItemInterface $toolbarItem */
282
            if ($toolbarItem->checkAccess()) {
283
                $hasDropDown = (bool)$toolbarItem->hasDropDown();
284
                $additionalAttributes = (array)$toolbarItem->getAdditionalAttributes();
285
286
                $liAttributes = [];
287
288
                // Merge class: Add dropdown class if hasDropDown, add classes from additional attributes
289
                $classes = [];
290
                $classes[] = 'toolbar-item';
291
                $classes[] = 't3js-toolbar-item';
292
                if (isset($additionalAttributes['class'])) {
293
                    $classes[] = $additionalAttributes['class'];
294
                    unset($additionalAttributes['class']);
295
                }
296
                $liAttributes['class'] = implode(' ', $classes);
297
298
                // Add further attributes
299
                foreach ($additionalAttributes as $name => $value) {
300
                    $liAttributes[$name] = $value;
301
                }
302
303
                // Create a unique id from class name
304
                $fullyQualifiedClassName = \get_class($toolbarItem);
305
                $className = GeneralUtility::underscoredToLowerCamelCase($fullyQualifiedClassName);
306
                $className = GeneralUtility::camelCaseToLowerCaseUnderscored($className);
307
                $className = str_replace(['_', '\\'], '-', $className);
308
                $liAttributes['id'] = $className;
309
310
                // Create data attribute identifier
311
                $shortName = substr($fullyQualifiedClassName, strrpos($fullyQualifiedClassName, '\\') + 1);
312
                $dataToolbarIdentifier = GeneralUtility::camelCaseToLowerCaseUnderscored($shortName);
313
                $dataToolbarIdentifier = str_replace('_', '-', $dataToolbarIdentifier);
314
                $liAttributes['data-toolbar-identifier'] = $dataToolbarIdentifier;
315
316
                $toolbar[] = '<li ' . GeneralUtility::implodeAttributes($liAttributes, true) . '>';
317
318
                if ($hasDropDown) {
319
                    $toolbar[] = '<a href="#" class="toolbar-item-link dropdown-toggle" data-toggle="dropdown">';
320
                    $toolbar[] = $toolbarItem->getItem();
321
                    $toolbar[] = '</a>';
322
                    $toolbar[] = '<div class="dropdown-menu" role="menu">';
323
                    $toolbar[] = $toolbarItem->getDropDown();
324
                    $toolbar[] = '</div>';
325
                } else {
326
                    $toolbar[] = $toolbarItem->getItem();
327
                }
328
                $toolbar[] = '</li>';
329
            }
330
        }
331
        return implode(LF, $toolbar);
332
    }
333
334
    /**
335
     * Generates the JavaScript code for the backend.
336
     *
337
     * @param ServerRequestInterface $request
338
     */
339
    protected function generateJavascript(ServerRequestInterface $request)
340
    {
341
        $beUser = $this->getBackendUser();
342
        // Needed for FormEngine manipulation (date picker)
343
        $dateFormat = ($GLOBALS['TYPO3_CONF_VARS']['SYS']['USdateFormat'] ? ['MM-DD-YYYY', 'HH:mm MM-DD-YYYY'] : ['DD-MM-YYYY', 'HH:mm DD-MM-YYYY']);
344
        $this->pageRenderer->addInlineSetting('DateTimePicker', 'DateFormat', $dateFormat);
345
346
        // If another page module was specified, replace the default Page module with the new one
347
        $newPageModule = trim($beUser->getTSConfig()['options.']['overridePageModule'] ?? '');
348
        $pageModule = BackendUtility::isModuleSetInTBE_MODULES($newPageModule) ? $newPageModule : 'web_layout';
349
        $pageModuleUrl = '';
350
        if (!$beUser->check('modules', $pageModule)) {
351
            $pageModule = '';
352
        } else {
353
            $pageModuleUrl = (string)$this->uriBuilder->buildUriFromRoute($pageModule);
354
        }
355
        $t3Configuration = [
356
            'username' => htmlspecialchars($beUser->user['username']),
357
            'pageModule' => $pageModule,
358
            'pageModuleUrl' => $pageModuleUrl,
359
            'inWorkspace' => $beUser->workspace !== 0,
360
            'showRefreshLoginPopup' => (bool)($GLOBALS['TYPO3_CONF_VARS']['BE']['showRefreshLoginPopup'] ?? false)
361
        ];
362
363
        $this->pageRenderer->addJsInlineCode(
364
            'BackendConfiguration',
365
            '
366
        TYPO3.configuration = ' . json_encode($t3Configuration) . ';
367
        /**
368
         * Frameset Module object
369
         *
370
         * Used in main modules with a frameset for submodules to keep the ID between modules
371
         * Typically that is set by something like this in a Web>* sub module:
372
         *		if (top.fsMod) top.fsMod.recentIds["web"] = "\'.(int)$this->id.\'";
373
         * 		if (top.fsMod) top.fsMod.recentIds["file"] = "...(file reference/string)...";
374
         */
375
        var fsMod = {
376
            recentIds: [],					// used by frameset modules to track the most recent used id for list frame.
377
            navFrameHighlightedID: [],		// used by navigation frames to track which row id was highlighted last time
378
            currentBank: "0"
379
        };
380
381
        top.goToModule = function(modName, cMR_flag, addGetVars) {
382
            TYPO3.ModuleMenu.App.showModule(modName, addGetVars);
383
        }
384
        ' . $this->setStartupModule($request)
385
          . $this->handlePageEditing($request),
386
            false
387
        );
388
    }
389
390
    /**
391
     * Checking if the "&edit" variable was sent so we can open it for editing the page.
392
     */
393
    protected function handlePageEditing(ServerRequestInterface $request): string
394
    {
395
        $beUser = $this->getBackendUser();
396
        $userTsConfig = $this->getBackendUser()->getTSConfig();
397
        // EDIT page
398
        $editId = preg_replace('/[^[:alnum:]_]/', '', $request->getQueryParams()['edit'] ?? '');
399
        if ($editId) {
400
            // Looking up the page to edit, checking permissions:
401
            $where = ' AND (' . $beUser->getPagePermsClause(Permission::PAGE_EDIT) . ' OR ' . $beUser->getPagePermsClause(Permission::CONTENT_EDIT) . ')';
402
            $editRecord = null;
403
            if (MathUtility::canBeInterpretedAsInteger($editId)) {
404
                $editRecord = BackendUtility::getRecordWSOL('pages', $editId, '*', $where);
405
            }
406
            // If the page was accessible, then let the user edit it.
407
            if (is_array($editRecord) && $beUser->isInWebMount($editRecord)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $beUser->isInWebMount($editRecord) of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
408
                // Checking page edit parameter:
409
                if (!($userTsConfig['options.']['bookmark_onEditId_dontSetPageTree'] ?? false)) {
410
                    $bookmarkKeepExpanded = (bool)($userTsConfig['options.']['bookmark_onEditId_keepExistingExpanded'] ?? false);
411
                    // Expanding page tree:
412
                    BackendUtility::openPageTree((int)$editRecord['pid'], !$bookmarkKeepExpanded);
413
                }
414
                // Setting JS code to open editing:
415
                return '
416
		// Load page to edit:
417
	window.setTimeout("top.loadEditId(' . (int)$editRecord['uid'] . ');", 500);
418
			';
419
            }
420
            return '
421
            // Warning about page editing:
422
            require(["TYPO3/CMS/Backend/Modal", "TYPO3/CMS/Backend/Severity"], function(Modal, Severity) {
423
                Modal.show("", ' . GeneralUtility::quoteJSvalue(sprintf($this->getLanguageService()->getLL('noEditPage'), $editId)) . ', Severity.notice, [{
424
                    text: ' . GeneralUtility::quoteJSvalue($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:close')) . ',
425
                    active: true,
426
                    btnClass: "btn-info",
427
                    name: "cancel",
428
                    trigger: function () {
429
                        Modal.currentModal.trigger("modal-dismiss");
430
                    }
431
                }])
432
            });';
433
        }
434
        return '';
435
    }
436
437
    /**
438
     * Sets the startup module from either GETvars module and modParams or user configuration.
439
     *
440
     * @param ServerRequestInterface $request
441
     * @return string the JavaScript code for the startup module
442
     */
443
    protected function setStartupModule(ServerRequestInterface $request)
444
    {
445
        $startModule = preg_replace('/[^[:alnum:]_]/', '', $request->getQueryParams()['module'] ?? '');
446
        $startModuleParameters = '';
447
        if (!$startModule) {
448
            $beUser = $this->getBackendUser();
449
            // start module on first login, will be removed once used the first time
450
            if (isset($beUser->uc['startModuleOnFirstLogin'])) {
451
                $startModule = $beUser->uc['startModuleOnFirstLogin'];
452
                unset($beUser->uc['startModuleOnFirstLogin']);
453
                $beUser->writeUC();
454
            } elseif ($beUser->uc['startModule']) {
455
                $startModule = $beUser->uc['startModule'];
456
            } else {
457
                $startModule = $this->determineFirstAvailableBackendModule();
458
            }
459
460
            // check if the start module has additional parameters, so a redirect to a specific
461
            // action is possible
462
            if (strpos($startModule, '->') !== false) {
463
                [$startModule, $startModuleParameters] = explode('->', $startModule, 2);
464
            }
465
        }
466
467
        $moduleParameters = $request->getQueryParams()['modParams'] ?? '';
468
        // if no GET parameters are set, check if there are parameters given from the UC
469
        if (!$moduleParameters && $startModuleParameters) {
470
            $moduleParameters = $startModuleParameters;
471
        }
472
473
        if ($startModule) {
474
            return '
475
					// start in module:
476
				top.startInModule = [' . GeneralUtility::quoteJSvalue($startModule) . ', ' . GeneralUtility::quoteJSvalue($moduleParameters) . '];
477
			';
478
        }
479
        return '';
480
    }
481
482
    protected function determineFirstAvailableBackendModule(): string
483
    {
484
        $loadModules = GeneralUtility::makeInstance(ModuleLoader::class);
485
        $loadModules->observeWorkspaces = true;
486
        $loadModules->load($GLOBALS['TBE_MODULES']);
487
488
        foreach ($loadModules->modules as $mainMod => $modData) {
489
            $hasSubmodules = !empty($modData['sub']) && is_array($modData['sub']);
490
            $isStandalone = $modData['standalone'] ?? false;
491
            if ($isStandalone) {
492
                return $modData['name'];
493
            }
494
495
            if ($hasSubmodules) {
496
                $firstSubmodule = reset($modData['sub']);
497
                return $firstSubmodule['name'];
498
            }
499
        }
500
501
        return '';
502
    }
503
504
    /**
505
     * Adds a css snippet to the backend
506
     *
507
     * @param string $css Css snippet
508
     * @throws \InvalidArgumentException
509
     */
510
    public function addCss($css)
511
    {
512
        if (!is_string($css)) {
0 ignored issues
show
introduced by
The condition is_string($css) is always true.
Loading history...
513
            throw new \InvalidArgumentException('parameter $css must be of type string', 1195129642);
514
        }
515
        $this->css .= $css;
516
    }
517
518
    /**
519
     * Executes defined hooks functions for the given identifier.
520
     *
521
     * These hook identifiers are valid:
522
     * + constructPostProcess
523
     * + renderPreProcess
524
     * + renderPostProcess
525
     *
526
     * @param string $identifier Specific hook identifier
527
     * @param array $hookConfiguration Additional configuration passed to hook functions
528
     */
529
    protected function executeHook($identifier, array $hookConfiguration = [])
530
    {
531
        $options = &$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/backend.php'];
532
        foreach ($options[$identifier] ?? [] as $hookFunction) {
533
            GeneralUtility::callUserFunction($hookFunction, $hookConfiguration, $this);
534
        }
535
    }
536
537
    /**
538
     * loads all modules from the repository
539
     * and renders it with a template
540
     *
541
     * @return string
542
     */
543
    protected function generateModuleMenu()
544
    {
545
        $view = $this->getFluidTemplateObject($this->templatePath . 'ModuleMenu/Main.html');
546
        $view->assign('modules', $this->moduleStorage);
547
        return $view->render();
548
    }
549
550
    protected function getCollapseStateOfMenu(): bool
551
    {
552
        $uc = json_decode(json_encode($this->getBackendUser()->uc), true);
553
        $collapseState = $uc['BackendComponents']['States']['typo3-module-menu']['collapsed'] ?? false;
554
555
        return $collapseState === true || $collapseState === 'true';
556
    }
557
558
    /**
559
     * Returns the Module menu for the AJAX request
560
     *
561
     * @return ResponseInterface
562
     */
563
    public function getModuleMenu(): ResponseInterface
564
    {
565
        return new JsonResponse(['menu' => $this->generateModuleMenu()]);
566
    }
567
568
    /**
569
     * Returns the toolbar for the AJAX request
570
     *
571
     * @return ResponseInterface
572
     */
573
    public function getTopbar(): ResponseInterface
574
    {
575
        return new JsonResponse(['topbar' => $this->renderTopbar()]);
576
    }
577
578
    /**
579
     * returns a new standalone view, shorthand function
580
     *
581
     * @param string $templatePathAndFileName optional the path to set the template path and filename
582
     * @return \TYPO3\CMS\Fluid\View\StandaloneView
583
     */
584
    protected function getFluidTemplateObject($templatePathAndFileName = null)
585
    {
586
        $view = GeneralUtility::makeInstance(StandaloneView::class);
587
        $view->setPartialRootPaths([GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Partials')]);
588
        if ($templatePathAndFileName) {
589
            $view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName($templatePathAndFileName));
590
        }
591
        return $view;
592
    }
593
594
    /**
595
     * Returns LanguageService
596
     *
597
     * @return LanguageService
598
     */
599
    protected function getLanguageService()
600
    {
601
        return $GLOBALS['LANG'];
602
    }
603
604
    /**
605
     * Returns the current BE user.
606
     *
607
     * @return BackendUserAuthentication
608
     */
609
    protected function getBackendUser()
610
    {
611
        return $GLOBALS['BE_USER'];
612
    }
613
}
614