getBackendUser(); $pageRenderer = $this->pageRenderer; // apply nonce hint for elements that are shown in a modal $pageRenderer->setApplyNonceHint(true); $this->setUpBasicPageRendererForBackend($pageRenderer, $this->extensionConfiguration, $request, $this->getLanguageService()); $javaScriptRenderer = $pageRenderer->getJavaScriptRenderer(); $javaScriptRenderer->addGlobalAssignment(['window' => [ 'name' => 'typo3-backend', // reset window name to a standardized value 'opener' => null, // remove any previously set opener value ]]); $javaScriptRenderer->addJavaScriptModuleInstruction( JavaScriptModuleInstruction::create('@typo3/backend/login-refresh.js') ->invoke('initialize', [ 'intervalTime' => MathUtility::forceIntegerInRange((int)$GLOBALS['TYPO3_CONF_VARS']['BE']['sessionTimeout'] - 60, 60), 'requestTokenUrl' => (string)$this->uriBuilder->buildUriFromRoute('login_request_token'), 'loginFramesetUrl' => (string)$this->uriBuilder->buildUriFromRoute('login_frameset'), 'logoutUrl' => (string)$this->uriBuilder->buildUriFromRoute('logout'), ]) ); $javaScriptRenderer->addJavaScriptModuleInstruction( JavaScriptModuleInstruction::create('@typo3/backend/broadcast-service.js')->invoke('listen') ); $javaScriptRenderer->addJavaScriptModuleInstruction( JavaScriptModuleInstruction::create('@typo3/backend/hotkeys/negotiator.js') ); $javaScriptRenderer->addJavaScriptModuleInstruction( JavaScriptModuleInstruction::create('@typo3/backend/hotkeys.js') ); $javaScriptRenderer->addJavaScriptModuleInstruction( JavaScriptModuleInstruction::create('@typo3/backend/user-settings-manager.js') ); // load the storage API and fill the UC into the PersistentStorage, so no additional AJAX call is needed $javaScriptRenderer->addJavaScriptModuleInstruction( JavaScriptModuleInstruction::create('@typo3/backend/storage/persistent.js') ->invoke('load', $backendUser->uc) ); // Initialize bookmark store with server data if bookmarks are enabled if ($this->bookmarkService->isEnabled()) { $javaScriptRenderer->addJavaScriptModuleInstruction( JavaScriptModuleInstruction::create('@typo3/backend/bookmark/bookmark-store.js') ->invoke('initialize', $this->bookmarkService->getBookmarks(), $this->bookmarkService->getGroups()) ); } $javaScriptRenderer->addGlobalAssignment([ 'TYPO3' => [ 'configuration' => [ 'username' => htmlspecialchars($backendUser->user['username']), 'showRefreshLoginPopup' => (bool)($GLOBALS['TYPO3_CONF_VARS']['BE']['showRefreshLoginPopup'] ?? false), ], ], ]); $javaScriptRenderer->includeAllImports(); // @todo: This loads a ton of labels into JS. This should be reviewed what is really needed. // This could happen when the localization API gets an overhaul. $pageRenderer->addInlineLanguageLabelFile('EXT:core/Resources/Private/Language/locallang_core.xlf'); $pageRenderer->addInlineLanguageLabelFile('EXT:core/Resources/Private/Language/locallang_misc.xlf'); $pageRenderer->addInlineLanguageLabelFile('EXT:backend/Resources/Private/Language/locallang_layout.xlf'); $pageRenderer->addInlineLanguageLabelFile('EXT:backend/Resources/Private/Language/locallang_settingseditor.xlf'); $pageRenderer->addInlineLanguageLabelFile('EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf'); // @todo: We can not put this into the template since PageRendererViewHelper does not deal with namespace in addInlineSettings argument $pageRenderer->addInlineSetting('ShowItem', 'moduleUrl', (string)$this->uriBuilder->buildUriFromRoute('show_item')); $pageRenderer->addInlineSetting('Resource', 'thumbnailUrl', (string)$this->uriBuilder->buildUriFromRoute('resource_request_thumbnail')); $pageRenderer->addInlineSetting('RecordHistory', 'moduleUrl', (string)$this->uriBuilder->buildUriFromRoute('record_history')); $pageRenderer->addInlineSetting('NewRecord', 'moduleUrl', (string)$this->uriBuilder->buildUriFromRoute('db_new')); $pageRenderer->addInlineSetting('FormEngine', 'moduleUrl', (string)$this->uriBuilder->buildUriFromRoute('record_edit')); $pageRenderer->addInlineSetting('RecordCommit', 'moduleUrl', (string)$this->uriBuilder->buildUriFromRoute('tce_db')); $pageRenderer->addInlineSetting('FileCommit', 'moduleUrl', (string)$this->uriBuilder->buildUriFromRoute('tce_file')); $pageRenderer->addInlineSetting('Clipboard', 'moduleUrl', (string)$this->uriBuilder->buildUriFromRoute('clipboard_process')); $pageRenderer->addInlineSetting('Wizards', 'elementBrowserUrl', (string)$this->uriBuilder->buildUriFromRoute('wizard_element_browser')); // Needed for FormEngine manipulation (date picker) and DateTime components $pageRenderer->addInlineSetting(null, 'DateConfiguration', $this->dateConfigurationFactory->getConfiguration('javascript')); $typo3Version = 'TYPO3 CMS ' . $this->typo3Version->getVersion(); $title = $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] ? $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] . ' [' . $typo3Version . ']' : $typo3Version; $pageRenderer->setTitle($title); $sidebarContext = new SidebarComponentContext($request, $backendUser); $sidebar = $this->sidebarFactory->create($sidebarContext); $view = $this->viewFactory->create($request); $this->assignTopbarDetailsToView($request, $view, $sidebar); $startupModule = $this->getStartupModule($request); $noModuleAccess = $startupModule[0] === null && empty($this->moduleProvider->getModulesForModuleMenu($backendUser)); $view->assignMultiple([ 'startupModule' => $startupModule, 'noModuleAccess' => $noModuleAccess, 'workspaceAccessDenied' => $noModuleAccess && $backendUser->workspace === -99, 'entryPoint' => $this->backendEntryPointResolver->getPathFromRequest($request), 'stateTracker' => (string)$this->uriBuilder->buildUriFromRoute('state-tracker'), 'sitename' => $title, 'sitenameFirstInBackendTitle' => ($backendUser->uc['backendTitleFormat'] ?? '') === 'sitenameFirst', 'sidebar' => $sidebar->render(), ]); $this->eventDispatcher->dispatch(new BeforeBackendPageRenderEvent($view, $javaScriptRenderer, $pageRenderer)); $content = $view->render('Backend/Main'); $content = $this->eventDispatcher->dispatch(new AfterBackendPageRenderEvent($content, $view))->getContent(); $pageRenderer->addBodyContent('' . $content); return $pageRenderer->renderResponse($request); } /** * Returns the main module menu as json encoded HTML string. Used when * "update signals" request a menu reload, e.g. when an extension is loaded * that brings new main modules. */ public function getModuleMenu(ServerRequestInterface $request): ResponseInterface { $sidebarContext = new SidebarComponentContext($request, $this->getBackendUser()); $component = $this->sidebarFactory->create($sidebarContext)->getComponentByIdentifier('module-menu'); return new JsonResponse(['menu' => $component?->getResult($sidebarContext)->html]); } /** * Returns the toolbar as json encoded HTML string. Used when * "update signals" request a toolbar reload, e.g. when an extension is loaded. */ public function getTopbar(ServerRequestInterface $request): ResponseInterface { $sidebar = $this->sidebarFactory->create(new SidebarComponentContext($request, $this->getBackendUser())); $view = $this->viewFactory->create($request); $this->assignTopbarDetailsToView($request, $view, $sidebar); return new JsonResponse(['topbar' => $view->render('Backend/Topbar')]); } /** * Renders the topbar, containing the backend logo, sitename etc. */ protected function assignTopbarDetailsToView(ServerRequestInterface $request, ViewInterface $view, Sidebar $sidebar): void { // Extension Configuration to find the TYPO3 logo in the left corner $extConf = $this->extensionConfiguration->get('backend'); $logoPath = ''; $logoUrl = ''; $logoWidth = 22; $logoHeight = 22; if (!empty($extConf['backendLogo'])) { $configuredLogo = ltrim($extConf['backendLogo'], '/'); $customBackendLogo = GeneralUtility::getFileAbsFileName($configuredLogo); if ($customBackendLogo !== '' && file_exists($customBackendLogo)) { $logoPath = $customBackendLogo; $logoUrl = (string)PathUtility::getSystemResourceUri($configuredLogo, $request); // set width/height for custom logo $imageInfo = GeneralUtility::makeInstance(ImageInfo::class, $logoPath); $logoWidth = $imageInfo->getWidth() ?: $logoWidth; $logoHeight = $imageInfo->getHeight() ?: $logoHeight; // High-resolution? if (str_contains($logoPath, '@2x.')) { $logoWidth /= 2; $logoHeight /= 2; } } } // if no custom logo was set or the path is invalid, use the original one if ($logoPath === '') { $logoUrl = (string)PathUtility::getSystemResourceUri('EXT:backend/Resources/Public/Images/typo3_logo_orange.svg', $request); } $view->assign('sidebar', $sidebar); $view->assign('logoUrl', $logoUrl); $view->assign('logoWidth', $logoWidth); $view->assign('logoHeight', $logoHeight); $view->assign('applicationVersion', $this->typo3Version->getVersion()); $view->assign('siteName', $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename']); $view->assign('toolbarItems', $this->getToolbarItems($request)); $view->assign('isImpersonated', $this->getBackendUser()->getOriginalUserIdWhenInSwitchUserMode() !== null); } /** * @return ToolbarItemInterface[] */ protected function getToolbarItems(ServerRequestInterface $request): array { return array_map(static function (ToolbarItemInterface $toolbarItem) use ($request): ToolbarItemInterface { if ($toolbarItem instanceof RequestAwareToolbarItemInterface) { $toolbarItem->setRequest($request); } return $toolbarItem; }, array_filter( $this->toolbarItemsRegistry->getToolbarItems(), static fn(ToolbarItemInterface $toolbarItem): bool => $toolbarItem->checkAccess() )); } /** * Sets the startup module from either "redirect" GET parameters or user configuration. * * @return array{?string, ?string} */ protected function getStartupModule(ServerRequestInterface $request): array { $startModule = null; $startModuleIdentifier = null; $inaccessibleRedirectModule = null; $moduleParameters = []; try { $redirect = RouteRedirect::createFromRequest($request); if ($redirect !== null && $request->getMethod() === 'GET') { // Only redirect to existing non-ajax routes with no restriction to a specific method $router = GeneralUtility::makeInstance(Router::class); $redirect->resolve($router); $module = $router->getRoute($redirect->getName())?->getOption('module'); if ($module instanceof ModuleInterface === false || $this->moduleProvider->accessGranted($module->getIdentifier(), $this->getBackendUser()) ) { // Only add start module from request in case user has access or it's a no module route, // e.g. to FormEngine where permissions are checked by the corresponding component. // Access might temporarily be blocked. e.g. due to being in a workspace. $startModuleIdentifier = $redirect->getName(); $moduleParameters = $redirect->getParameters(); } elseif ($this->moduleProvider->isModuleRegistered($module->getIdentifier())) { // A redirect is set, however, the user is not allowed to access the module. // Store the requested module to later inform the user about the forced redirect. $inaccessibleRedirectModule = $this->moduleProvider->getModule($module->getIdentifier()); } } } finally { // No valid redirect, check for the start module if (!$startModuleIdentifier) { $backendUser = $this->getBackendUser(); // start module on first login, will be removed once used the first time if (isset($backendUser->uc['startModuleOnFirstLogin'])) { $startModuleIdentifier = $backendUser->uc['startModuleOnFirstLogin']; unset($backendUser->uc['startModuleOnFirstLogin']); $backendUser->writeUC(); } elseif (isset($backendUser->uc['startModule']) && $this->moduleProvider->accessGranted($backendUser->uc['startModule'], $backendUser)) { $startModuleIdentifier = $backendUser->uc['startModule']; } elseif ($firstAccessibleModule = $this->moduleProvider->getFirstAccessibleModule($backendUser)) { $startModuleIdentifier = $firstAccessibleModule->getIdentifier(); } // check if the start module has additional parameters, so a redirect to a specific // action is possible if (is_string($startModuleIdentifier) && str_contains($startModuleIdentifier, '->')) { [$startModuleIdentifier, $startModuleParameters] = explode('->', $startModuleIdentifier, 2); // if no GET parameters are set, check if there are parameters given from the UC if (!$moduleParameters && $startModuleParameters) { $moduleParameters = $startModuleParameters; } } } } if ($startModuleIdentifier) { if ($this->moduleProvider->isModuleRegistered($startModuleIdentifier)) { // startModuleIdentifier may be an alias, resolve original module $startModule = $this->moduleProvider->getModule($startModuleIdentifier, $this->getBackendUser()); $startModuleIdentifier = $startModule?->getIdentifier(); } if (is_array($moduleParameters)) { $parameters = $moduleParameters; } else { $parameters = []; parse_str($moduleParameters, $parameters); } try { $deepLink = $this->uriBuilder->buildUriFromRoute($startModuleIdentifier, $parameters); if ($startModule !== null && $inaccessibleRedirectModule !== null) { $this->enqueueRedirectMessage($inaccessibleRedirectModule, $startModule); } return [$startModuleIdentifier, (string)$deepLink]; } catch (RouteNotFoundException $e) { // It might be, that the user does not have access to the // $startModule, e.g. for modules with workspace restrictions. } } return [null, null]; } protected function enqueueRedirectMessage(ModuleInterface $requestedModule, ModuleInterface $redirectedModule): void { $languageService = $this->getLanguageService(); $this->flashMessageService ->getMessageQueueByIdentifier(FlashMessageQueue::NOTIFICATION_QUEUE) ->enqueue( new FlashMessage( sprintf( $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang.xlf:module.noAccess.message'), $languageService->sL($redirectedModule->getTitle()), $languageService->sL($requestedModule->getTitle()) ), $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang.xlf:module.noAccess.title'), ContextualFeedbackSeverity::INFO, true ) ); } protected function getBackendUser(): BackendUserAuthentication { return $GLOBALS['BE_USER']; } protected function getLanguageService(): LanguageService { return $GLOBALS['LANG']; } }