195 lines
9.2 KiB
PHP
195 lines
9.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/*
|
|
* This file is part of the TYPO3 CMS project.
|
|
*
|
|
* It is free software; you can redistribute it and/or modify it under
|
|
* the terms of the GNU General Public License, either version 2
|
|
* of the License, or any later version.
|
|
*
|
|
* For the full copyright and license information, please read the
|
|
* LICENSE.txt file that was distributed with this source code.
|
|
*
|
|
* The TYPO3 project - inspiring people to share!
|
|
*/
|
|
|
|
namespace TYPO3\CMS\Frontend\Middleware;
|
|
|
|
use Psr\Http\Message\ResponseInterface;
|
|
use Psr\Http\Message\ServerRequestInterface;
|
|
use Psr\Http\Server\MiddlewareInterface;
|
|
use Psr\Http\Server\RequestHandlerInterface;
|
|
use TYPO3\CMS\Core\Context\Context;
|
|
use TYPO3\CMS\Core\Context\DateTimeAspect;
|
|
use TYPO3\CMS\Core\Context\LanguageAspectFactory;
|
|
use TYPO3\CMS\Core\Context\VisibilityAspect;
|
|
use TYPO3\CMS\Core\Domain\DateTimeFactory;
|
|
use TYPO3\CMS\Core\Domain\Repository\PageRepository;
|
|
use TYPO3\CMS\Core\Routing\PageArguments;
|
|
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
|
use TYPO3\CMS\Core\Utility\RootlineUtility;
|
|
use TYPO3\CMS\Frontend\Aspect\PreviewAspect;
|
|
use TYPO3\CMS\Frontend\Controller\ErrorController;
|
|
use TYPO3\CMS\Frontend\Page\PageAccessFailureReasons;
|
|
|
|
/**
|
|
* Middleware for handling preview settings
|
|
* used when simulating / previewing pages or content through query params when
|
|
* previewing access or time restricted content via for example backend preview links
|
|
*/
|
|
readonly class PreviewSimulator implements MiddlewareInterface
|
|
{
|
|
public function __construct(
|
|
protected Context $context,
|
|
protected PageRepository $pageRepository,
|
|
) {}
|
|
|
|
/**
|
|
* Evaluates preview settings if a backend user is logged in
|
|
*
|
|
* @throws \Exception
|
|
*/
|
|
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
|
{
|
|
$isLoggedIn = $this->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 = <groupId> 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;
|
|
}
|
|
}
|