context->getPropertyFromAspect('backend.user', 'isLoggedIn', false); $isOfflineWorkspace = $this->context->getPropertyFromAspect('workspace', 'isOffline', false); // When previewing a workspace with the preview link, the PreviewUserAuthentication is NOT marked as // "isLoggedIn" as it does not have a valid user ID. For this reason, we also check if the Workspace is offline. See WorkspacePreview middleware if ($isLoggedIn || $isOfflineWorkspace) { $pageArguments = $request->getAttribute('routing', null); if (!$pageArguments instanceof PageArguments) { return GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction( $request, 'Page Arguments could not be resolved', ['code' => PageAccessFailureReasons::INVALID_PAGE_ARGUMENTS] ); } $visibilityAspect = $this->context->getAspect('visibility'); // The preview flag is set if the current page turns out to be hidden $showHiddenPages = $this->checkIfPageIsHidden($pageArguments->getPageId(), $request); $rootlineRequiresPreviewFlag = $this->checkIfRootlineRequiresPreview($pageArguments->getPageId()); $simulatingDate = $this->simulateDate($request); $simulatingGroup = $this->simulateUserGroup($request); $showHiddenRecords = $visibilityAspect->includeHidden(); $isPreview = $simulatingDate || $simulatingGroup || $showHiddenRecords || $showHiddenPages || $isOfflineWorkspace || $rootlineRequiresPreviewFlag; if ($this->context->hasAspect('frontend.preview')) { /** @var PreviewAspect $previewAspect */ $previewAspect = $this->context->getAspect('frontend.preview'); $isPreview = $previewAspect->isPreview() || $isPreview; } $this->context->setAspect('frontend.preview', new PreviewAspect($isPreview)); if ($showHiddenPages || $rootlineRequiresPreviewFlag) { $newAspect = new VisibilityAspect(true, $visibilityAspect->includeHiddenContent(), $visibilityAspect->includeDeletedRecords(), $visibilityAspect->includeScheduledRecords()); $this->context->setAspect('visibility', $newAspect); } } return $handler->handle($request); } /** * Evaluate if the "extendToSubpages" flag was set on any of the previous ancestor pages, * but be sure to not check for the current page itself. */ protected function checkIfRootlineRequiresPreview(int $pageId): bool { $rootlineUtility = GeneralUtility::makeInstance(RootlineUtility::class, $pageId, '', $this->context); $groupRestricted = false; $timeRestricted = false; $hidden = false; try { $rootLine = $rootlineUtility->get(); // Remove the current page from the rootline array_shift($rootLine); foreach ($rootLine as $page) { // Skip root node and pages which do not define extendToSubpages if ((int)($page['uid'] ?? 0) === 0 || !(bool)($page['extendToSubpages'] ?? false)) { continue; } $groupRestricted = (bool)(string)($page['fe_group'] ?? ''); $timeRestricted = (int)($page['starttime'] ?? 0) || (int)($page['endtime'] ?? 0); $hidden = (int)($page['hidden'] ?? 0); // Stop as soon as a page in the rootline has extendToSubpages set break; } } catch (\Exception) { // if the rootline cannot be resolved (404 because of delete placeholder in workspaces for example) // we do not want to fail here but rather continue handling the request to trigger the middleware 404 handling } return $groupRestricted || $timeRestricted || $hidden; } /** * Checks if the page is hidden in the active workspace + language setup. */ protected function checkIfPageIsHidden(int $pageId, ServerRequestInterface $request): bool { $site = $request->getAttribute('site', null); // always check both the page in the requested language and the page in the default language, as due to the // overlay handling, a hidden default page will require setting the preview flag to allow previewing of the // translation $languageAspectFromRequest = LanguageAspectFactory::createFromSiteLanguage($request->getAttribute('language', $site->getDefaultLanguage())); $pageIsHidden = $this->pageRepository->checkIfPageIsHidden($pageId, $languageAspectFromRequest); if ($languageAspectFromRequest->getId() > 0) { $pageIsHidden = $pageIsHidden || $this->pageRepository->checkIfPageIsHidden( $pageId, LanguageAspectFactory::createFromSiteLanguage($site->getDefaultLanguage()) ); } return $pageIsHidden; } /** * Simulate dates for preview functionality * When previewing a time restricted page from the backend, the parameter ADMCMD_simTime it added containing * a timestamp with the time to preview. The globals 'SIM_EXEC_TIME' and 'SIM_ACCESS_TIME' and the 'DateTimeAspect' * are used to simulate rendering at that point in time. * Ideally the global access is removed in future versions. * This functionality needs to be loaded after BackendAuthenticator as it is only relevant for * logged in backend users and needs to be done before any page resolving starts. */ protected function simulateDate(ServerRequestInterface $request): bool { $queryTime = (int)($request->getQueryParams()['ADMCMD_simTime'] ?? 0); if ($queryTime === 0) { return false; } $GLOBALS['SIM_EXEC_TIME'] = $queryTime; $GLOBALS['SIM_ACCESS_TIME'] = $queryTime - $queryTime % 60; $this->context->setAspect('date', new DateTimeAspect(DateTimeFactory::createFromTimestamp($queryTime))); return true; } /** * Simulate user group for preview functionality. When previewing a page with a user group restriction, * the parameter ADMCMD_simUser = will be added to the preview url. Simulation happens. * This functionality needs to be loaded after BackendAuthenticator as it is only relevant for * logged in backend users and needs to be done before any page resolving starts. */ protected function simulateUserGroup(ServerRequestInterface $request): bool { $simulateUserGroup = (int)($request->getQueryParams()['ADMCMD_simUser'] ?? 0); if (!$simulateUserGroup) { return false; } $frontendUser = $request->getAttribute('frontend.user'); $frontendUser->user[$frontendUser->usergroup_column] = (string)$simulateUserGroup; $frontendUser->userGroups[$simulateUserGroup] = [ 'uid' => $simulateUserGroup, 'title' => '_PREVIEW_', ]; // let's fake having a user with that group, too $frontendUser->user[$frontendUser->userid_column] = PHP_INT_MAX; // Set this option so the is_online timestamp is not updated in updateOnlineTimestamp() $frontendUser->user['is_online'] = $this->context->getPropertyFromAspect('date', 'timestamp'); $this->context->setAspect('frontend.user', $frontendUser->createUserAspect()); return true; } }