‪TYPO3CMS  ‪main
PreviewSimulator.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
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 
19 
20 use Psr\Http\Message\ResponseInterface;
21 use Psr\Http\Message\ServerRequestInterface;
22 use Psr\Http\Server\MiddlewareInterface;
23 use Psr\Http\Server\RequestHandlerInterface;
35 
41 class ‪PreviewSimulator implements MiddlewareInterface
42 {
43  public function ‪__construct(protected readonly ‪Context $context) {}
44 
50  public function ‪process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
51  {
52  $isLoggedIn = $this->context->getPropertyFromAspect('backend.user', 'isLoggedIn', false);
53  $isOfflineWorkspace = $this->context->getPropertyFromAspect('workspace', 'isOffline', false);
54  // When previewing a workspace with the preview link, the PreviewUserAuthentication is NOT marked as
55  // "isLoggedIn" as it does not have a valid user ID. For this reason, we also check if the Workspace is offline. See WorkspacePreview middleware
56  if ($isLoggedIn || $isOfflineWorkspace) {
57  $pageArguments = $request->getAttribute('routing', null);
58  if (!$pageArguments instanceof ‪PageArguments) {
59  return GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
60  $request,
61  'Page Arguments could not be resolved',
63  );
64  }
65  if ($this->context->hasAspect('visibility')) {
66  $visibilityAspect = $this->context->getAspect('visibility');
67  } else {
68  $visibilityAspect = GeneralUtility::makeInstance(VisibilityAspect::class);
69  }
70  // The preview flag is set if the current page turns out to be hidden
71  $showHiddenPages = $this->‪checkIfPageIsHidden($pageArguments->getPageId(), $request);
72  $rootlineRequiresPreviewFlag = $this->‪checkIfRootlineRequiresPreview($pageArguments->getPageId());
73  $simulatingDate = $this->‪simulateDate($request);
74  $simulatingGroup = $this->‪simulateUserGroup($request);
75  $showHiddenRecords = $visibilityAspect->includeHidden();
76  $isPreview = $simulatingDate || $simulatingGroup || $showHiddenRecords || $showHiddenPages || $isOfflineWorkspace || $rootlineRequiresPreviewFlag;
77  if ($this->context->hasAspect('frontend.preview')) {
79  $previewAspect = $this->context->getAspect('frontend.preview');
80  $isPreview = $previewAspect->isPreview() || $isPreview;
81  }
82  $previewAspect = GeneralUtility::makeInstance(PreviewAspect::class, $isPreview);
83  $this->context->setAspect('frontend.preview', $previewAspect);
84 
85  if ($showHiddenPages || $rootlineRequiresPreviewFlag) {
86  $newAspect = GeneralUtility::makeInstance(VisibilityAspect::class, true, $visibilityAspect->includeHiddenContent(), $visibilityAspect->includeDeletedRecords());
87  $this->context->setAspect('visibility', $newAspect);
88  }
89  }
90 
91  return $handler->handle($request);
92  }
93 
94  protected function ‪checkIfRootlineRequiresPreview(int $pageId): bool
95  {
96  $rootlineUtility = GeneralUtility::makeInstance(RootlineUtility::class, $pageId, '', $this->context);
97  $pageRepository = GeneralUtility::makeInstance(PageRepository::class, $this->context);
98  $groupRestricted = false;
99  $timeRestricted = false;
100  $hidden = false;
101  try {
102  $rootLine = $rootlineUtility->get();
103  $pageInfo = $pageRepository->getPage_noCheck($pageId);
104  // Only check rootline if the current page has not set extendToSubpages itself
105  // @see \TYPO3\CMS\Backend\Routing\PreviewUriBuilder::class
106  if (!(bool)($pageInfo['extendToSubpages'] ?? false)) {
107  // remove the current page from the rootline
108  array_shift($rootLine);
109  foreach ($rootLine as $page) {
110  // Skip root node and pages which do not define extendToSubpages
111  if ((int)($page['uid'] ?? 0) === 0 || !(bool)($page['extendToSubpages'] ?? false)) {
112  continue;
113  }
114  $groupRestricted = (bool)(string)($page['fe_group'] ?? '');
115  $timeRestricted = (int)($page['starttime'] ?? 0) || (int)($page['endtime'] ?? 0);
116  $hidden = (int)($page['hidden'] ?? 0);
117  // Stop as soon as a page in the rootline has extendToSubpages set
118  break;
119  }
120  }
121 
122  } catch (\‪Exception) {
123  // if the rootline cannot be resolved (404 because of delete placeholder in workspaces for example)
124  // we do not want to fail here but rather continue handling the request to trigger the TSFE 404 handling
125  }
126  return $groupRestricted || $timeRestricted || $hidden;
127  }
128 
132  protected function ‪checkIfPageIsHidden(int $pageId, ServerRequestInterface $request): bool
133  {
134  $pageRepository = GeneralUtility::makeInstance(PageRepository::class, $this->context);
135  $site = $request->getAttribute('site', null);
136  // always check both the page in the requested language and the page in the default language, as due to the
137  // overlay handling, a hidden default page will require setting the preview flag to allow previewing of the
138  // translation
139  $languageAspectFromRequest = ‪LanguageAspectFactory::createFromSiteLanguage($request->getAttribute('language', $site->getDefaultLanguage()));
140  $pageIsHidden = $pageRepository->checkIfPageIsHidden($pageId, $languageAspectFromRequest);
141 
142  if ($languageAspectFromRequest->getId() > 0) {
143  $pageIsHidden = $pageIsHidden || $pageRepository->checkIfPageIsHidden(
144  $pageId,
145  ‪LanguageAspectFactory::createFromSiteLanguage($site->getDefaultLanguage())
146  );
147  }
148  return $pageIsHidden;
149  }
150 
160  protected function ‪simulateDate(ServerRequestInterface $request): bool
161  {
162  $queryTime = (int)($request->getQueryParams()['ADMCMD_simTime'] ?? 0);
163  if ($queryTime === 0) {
164  return false;
165  }
166 
167  ‪$GLOBALS['SIM_EXEC_TIME'] = $queryTime;
168  ‪$GLOBALS['SIM_ACCESS_TIME'] = $queryTime - $queryTime % 60;
169  $this->context->setAspect(
170  'date',
171  GeneralUtility::makeInstance(
172  DateTimeAspect::class,
173  (new \DateTimeImmutable())->setTimestamp($queryTime)
174  )
175  );
176  return true;
177  }
178 
185  protected function ‪simulateUserGroup(ServerRequestInterface $request): bool
186  {
187  $simulateUserGroup = (int)($request->getQueryParams()['ADMCMD_simUser'] ?? 0);
188  if (!$simulateUserGroup) {
189  return false;
190  }
191  $frontendUser = $request->getAttribute('frontend.user');
192  $frontendUser->user[$frontendUser->usergroup_column] = $simulateUserGroup;
193  $frontendUser->userGroups[$simulateUserGroup] = [
194  'uid' => $simulateUserGroup,
195  'title' => '_PREVIEW_',
196  ];
197  // let's fake having a user with that group, too
198  $frontendUser->user[$frontendUser->userid_column] = PHP_INT_MAX;
199  // Set this option so the is_online timestamp is not updated in updateOnlineTimestamp()
200  $frontendUser->user['is_online'] = $this->context->getPropertyFromAspect('date', 'timestamp');
201  $this->context->setAspect('frontend.user', $frontendUser->createUserAspect());
202  return true;
203  }
204 }
‪TYPO3\CMS\Core\Routing\PageArguments
Definition: PageArguments.php:26
‪TYPO3\CMS\Core\Context\VisibilityAspect
Definition: VisibilityAspect.php:31
‪TYPO3\CMS\Core\Context\LanguageAspectFactory
Definition: LanguageAspectFactory.php:27
‪TYPO3\CMS\Frontend\Middleware\PreviewSimulator\__construct
‪__construct(protected readonly Context $context)
Definition: PreviewSimulator.php:43
‪TYPO3\CMS\Frontend\Page\PageAccessFailureReasons\INVALID_PAGE_ARGUMENTS
‪const INVALID_PAGE_ARGUMENTS
Definition: PageAccessFailureReasons.php:37
‪TYPO3\CMS\Frontend\Middleware\PreviewSimulator\checkIfPageIsHidden
‪checkIfPageIsHidden(int $pageId, ServerRequestInterface $request)
Definition: PreviewSimulator.php:132
‪TYPO3\CMS\Core\Context\LanguageAspectFactory\createFromSiteLanguage
‪static createFromSiteLanguage(SiteLanguage $language)
Definition: LanguageAspectFactory.php:31
‪TYPO3\CMS\Core\Utility\RootlineUtility
Definition: RootlineUtility.php:40
‪TYPO3\CMS\Frontend\Controller\ErrorController
Definition: ErrorController.php:38
‪TYPO3\CMS\Core\Context\Context
Definition: Context.php:54
‪TYPO3\CMS\Frontend\Middleware\PreviewSimulator\process
‪process(ServerRequestInterface $request, RequestHandlerInterface $handler)
Definition: PreviewSimulator.php:50
‪TYPO3\CMS\Frontend\Middleware
Definition: BackendUserAuthenticator.php:18
‪TYPO3\CMS\Frontend\Middleware\PreviewSimulator\simulateDate
‪simulateDate(ServerRequestInterface $request)
Definition: PreviewSimulator.php:160
‪TYPO3\CMS\Frontend\Exception
Definition: Exception.php:23
‪TYPO3\CMS\Frontend\Middleware\PreviewSimulator\checkIfRootlineRequiresPreview
‪checkIfRootlineRequiresPreview(int $pageId)
Definition: PreviewSimulator.php:94
‪TYPO3\CMS\Frontend\Middleware\PreviewSimulator
Definition: PreviewSimulator.php:42
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Domain\Repository\PageRepository
Definition: PageRepository.php:69
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Frontend\Aspect\PreviewAspect
Definition: PreviewAspect.php:30
‪TYPO3\CMS\Frontend\Middleware\PreviewSimulator\simulateUserGroup
‪simulateUserGroup(ServerRequestInterface $request)
Definition: PreviewSimulator.php:185
‪TYPO3\CMS\Core\Context\DateTimeAspect
Definition: DateTimeAspect.php:35
‪TYPO3\CMS\Frontend\Page\PageAccessFailureReasons
Definition: PageAccessFailureReasons.php:25