TYPO3CMS  8
 All Classes Namespaces Files Functions Variables Pages
PreviewHook.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Workspaces\Hook;
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
24 
30 {
36  protected $previewKey = 'ADMCMD_prev';
37 
43  protected $tsfeObj;
44 
50  protected $previewConfiguration = false;
51 
58  protected $forceReadPermissions = false;
59 
68  public function checkForPreview($params, &$pObj)
69  {
70  $this->tsfeObj = $pObj;
71  $this->previewConfiguration = $this->getPreviewConfiguration();
72  if (is_array($this->previewConfiguration)) {
73  // In case of a keyword-authenticated preview,
74  // re-initialize the TSFE object:
75  // because the GET variables are taken from the preview
76  // configuration
77  $this->tsfeObj = GeneralUtility::makeInstance(
78  \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController::class,
79  null,
80  GeneralUtility::_GP('id'),
81  GeneralUtility::_GP('type'),
82  GeneralUtility::_GP('no_cache'),
83  GeneralUtility::_GP('cHash'),
84  null,
85  GeneralUtility::_GP('MP'),
86  GeneralUtility::_GP('RDCT')
87  );
88  $GLOBALS['TSFE'] = $this->tsfeObj;
89  // Configuration after initialization of TSFE object.
90  // Basically this unsets the BE cookie if any and forces
91  // the BE user set according to the preview configuration.
92  // @previouslyknownas TSFE->ADMCMD_preview_postInit
93  // Clear cookies:
94  unset($_COOKIE['be_typo_user']);
95  }
96  }
97 
107  public function initializePreviewUser(&$params, &$pObj)
108  {
109  // if there is a valid BE user, and the full workspace should be previewed, the workspacePreview option should be set
110  $workspaceUid = $this->previewConfiguration['fullWorkspace'];
111  $workspaceRecord = null;
112  if ((is_null($params['BE_USER']) || $params['BE_USER'] === false) && $this->previewConfiguration !== false && $this->previewConfiguration['BEUSER_uid'] > 0) {
113  // First initialize a temp user object and resolve usergroup information
115  $tempBackendUser = $this->createFrontendBackendUser();
116  $tempBackendUser->userTS_dontGetCached = 1;
117  $tempBackendUser->setBeUserByUid($this->previewConfiguration['BEUSER_uid']);
118  if ($tempBackendUser->user['uid']) {
119  $tempBackendUser->unpack_uc();
120  $tempBackendUser->fetchGroupData();
121  // Handle degradation of admin users
122  if ($tempBackendUser->isAdmin()) {
123  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
124  ->getQueryBuilderForTable('sys_workspace');
125 
126  $queryBuilder->getRestrictions()
127  ->removeAll()
128  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
129  ->add(GeneralUtility::makeInstance(RootLevelRestriction::class));
130 
131  $workspaceRecord = $queryBuilder
132  ->select('uid', 'adminusers', 'reviewers', 'members', 'db_mountpoints')
133  ->from('sys_workspace')
134  ->where(
135  $queryBuilder->expr()->eq(
136  'uid',
137  $queryBuilder->createNamedParameter($workspaceUid, \PDO::PARAM_INT)
138  )
139  )
140  ->execute()
141  ->fetch();
142 
143  // Either use configured workspace mount or current page id, if admin user does not have any page mounts
144  if (empty($tempBackendUser->groupData['webmounts'])) {
145  $tempBackendUser->groupData['webmounts'] = !empty($workspaceRecord['db_mountpoints']) ? $workspaceRecord['db_mountpoints'] : $pObj->id;
146  }
147  // Force add degraded admin user as member of this workspace
148  $workspaceRecord['members'] = 'be_users_' . $this->previewConfiguration['BEUSER_uid'];
149  // Force read permission for degraded admin user
150  $this->forceReadPermissions = true;
151  }
152  // Store only needed information in the real simulate backend
153  $BE_USER = $this->createFrontendBackendUser();
154  $BE_USER->userTS_dontGetCached = 1;
155  $BE_USER->user = $tempBackendUser->user;
156  $BE_USER->user['admin'] = 0;
157  $BE_USER->groupData['webmounts'] = $tempBackendUser->groupData['webmounts'];
158  $BE_USER->groupList = $tempBackendUser->groupList;
159  $BE_USER->userGroups = $tempBackendUser->userGroups;
160  $BE_USER->userGroupsUID = $tempBackendUser->userGroupsUID;
161  $pObj->beUserLogin = true;
162  } else {
163  $BE_USER = null;
164  $pObj->beUserLogin = false;
165  }
166  unset($tempBackendUser);
167  $params['BE_USER'] = $BE_USER;
168  }
169  if ($pObj->beUserLogin
170  && is_object($params['BE_USER'])
172  ) {
173  if ($workspaceUid == 0
174  || $workspaceUid >= -1
175  && $params['BE_USER']->checkWorkspace($workspaceRecord ?: $workspaceUid)
176  && $params['BE_USER']->isInWebMount($pObj->id)
177  ) {
178  // Check Access to workspace. Live (0) is OK to preview for all.
179  $pObj->workspacePreview = (int)$workspaceUid;
180  } else {
181  // No preview, will default to "Live" at the moment
182  $pObj->workspacePreview = -99;
183  }
184  }
185  }
186 
196  public function overridePagePermissionClause(array $parameters)
197  {
198  $clause = $parameters['currentClause'];
199  if ($parameters['perms'] & 1 && $this->forceReadPermissions) {
200  $clause = ' 1=1';
201  }
202  return $clause;
203  }
204 
214  public function overridePermissionCalculation(array $parameters)
215  {
216  $permissions = $parameters['outputPermissions'];
217  if (!($permissions & Permission::PAGE_SHOW) && $this->forceReadPermissions) {
218  $permissions |= Permission::PAGE_SHOW;
219  }
220  return $permissions;
221  }
222 
239  public function getPreviewConfiguration()
240  {
241  $inputCode = $this->getPreviewInputCode();
242  // If input code is available and shall not be ignored, look up the settings
243  if ($inputCode && $inputCode !== 'IGNORE') {
244  // "log out"
245  if ($inputCode == 'LOGOUT') {
246  setcookie($this->previewKey, '', 0, GeneralUtility::getIndpEnv('TYPO3_SITE_PATH'));
247  if ($GLOBALS['TYPO3_CONF_VARS']['FE']['workspacePreviewLogoutTemplate']) {
248  $templateFile = PATH_site . $GLOBALS['TYPO3_CONF_VARS']['FE']['workspacePreviewLogoutTemplate'];
249  if (@is_file($templateFile)) {
250  $message = file_get_contents($templateFile);
251  } else {
252  $message = '<strong>ERROR!</strong><br>Template File "'
253  . $GLOBALS['TYPO3_CONF_VARS']['FE']['workspacePreviewLogoutTemplate']
254  . '" configured with $TYPO3_CONF_VARS["FE"]["workspacePreviewLogoutTemplate"] not found. Please contact webmaster about this problem.';
255  }
256  } else {
257  $message = 'You logged out from Workspace preview mode. Click this link to <a href="%1$s">go back to the website</a>';
258  }
259  $returnUrl = GeneralUtility::sanitizeLocalUrl(GeneralUtility::_GET('returnUrl'));
260  die(sprintf($message, htmlspecialchars(preg_replace('/\\&?' . $this->previewKey . '=[[:alnum:]]+/', '', $returnUrl))));
261  }
262  // Look for keyword configuration record:
263  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
264  ->getQueryBuilderForTable('sys_preview');
265 
266  $previewData = $queryBuilder
267  ->select('*')
268  ->from('sys_preview')
269  ->where(
270  $queryBuilder->expr()->eq(
271  'keyword',
272  $queryBuilder->createNamedParameter($inputCode, \PDO::PARAM_STR)
273  ),
274  $queryBuilder->expr()->gt(
275  'endtime',
276  $queryBuilder->createNamedParameter($GLOBALS['EXEC_TIME'], \PDO::PARAM_INT)
277  )
278  )
279  ->setMaxResults(1)
280  ->execute()
281  ->fetch();
282 
283  // Get: Backend login status, Frontend login status
284  // - Make sure to remove fe/be cookies (temporarily);
285  // BE already done in ADMCMD_preview_postInit()
286  if (is_array($previewData)) {
287  if (empty(GeneralUtility::_POST())) {
288  // Unserialize configuration:
289  $previewConfig = unserialize($previewData['config']);
290  // For full workspace preview we only ADD a get variable
291  // to set the preview of the workspace - so all other Get
292  // vars are accepted. Hope this is not a security problem.
293  // Still posting is not allowed and even if a backend user
294  // get initialized it shouldn't lead to situations where
295  // users can use those credentials.
296  if ($previewConfig['fullWorkspace']) {
297  // Set the workspace preview value:
298  GeneralUtility::_GETset($previewConfig['fullWorkspace'], 'ADMCMD_previewWS');
299  // If ADMCMD_prev is set the $inputCode value cannot come
300  // from a cookie and we set that cookie here. Next time it will
301  // be found from the cookie if ADMCMD_prev is not set again...
302  if (GeneralUtility::_GP($this->previewKey)) {
303  // Lifetime is 1 hour, does it matter much?
304  // Requires the user to click the link from their email again if it expires.
305  setcookie($this->previewKey, GeneralUtility::_GP($this->previewKey), 0, GeneralUtility::getIndpEnv('TYPO3_SITE_PATH'));
306  }
307  return $previewConfig;
308  } elseif (GeneralUtility::getIndpEnv('TYPO3_SITE_URL') . 'index.php?' . $this->previewKey . '=' . $inputCode === GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL')) {
309  // Set GET variables
310  $GET_VARS = '';
311  parse_str($previewConfig['getVars'], $GET_VARS);
312  GeneralUtility::_GETset($GET_VARS);
313  // Return preview keyword configuration
314  return $previewConfig;
315  } else {
316  // This check is to prevent people from setting additional
317  // GET vars via realurl or other URL path based ways of passing parameters.
318  throw new \Exception(htmlspecialchars('Request URL did not match "'
319  . GeneralUtility::getIndpEnv('TYPO3_SITE_URL') . 'index.php?' . $this->previewKey . '='
320  . $inputCode . '"', 1294585190));
321  }
322  } else {
323  throw new \Exception('POST requests are incompatible with keyword preview.', 1294585191);
324  }
325  } else {
326  throw new \Exception('ADMCMD command could not be executed! (No keyword configuration found)', 1294585192);
327  }
328  }
329  return false;
330  }
331 
337  protected function getPreviewInputCode()
338  {
339  $inputCode = GeneralUtility::_GP($this->previewKey);
340  // If no inputcode and a cookie is set, load input code from cookie:
341  if (!$inputCode && $_COOKIE[$this->previewKey]) {
342  $inputCode = $_COOKIE[$this->previewKey];
343  }
344  return $inputCode;
345  }
346 
361  public function compilePreviewKeyword($getVarsStr, $backendUserUid, $ttl = 172800, $fullWorkspace = null)
362  {
363  $fieldData = [
364  'keyword' => md5(uniqid(microtime(), true)),
365  'tstamp' => $GLOBALS['EXEC_TIME'],
366  'endtime' => $GLOBALS['EXEC_TIME'] + $ttl,
367  'config' => serialize([
368  'fullWorkspace' => $fullWorkspace,
369  'getVars' => $getVarsStr,
370  'BEUSER_uid' => $backendUserUid
371  ])
372  ];
373  GeneralUtility::makeInstance(ConnectionPool::class)
374  ->getConnectionForTable('sys_preview')
375  ->insert(
376  'sys_preview',
377  $fieldData
378  );
379 
380  return $fieldData['keyword'];
381  }
382 
390  public function getPreviewLinkLifetime()
391  {
392  $ttlHours = (int)$GLOBALS['BE_USER']->getTSConfigVal('options.workspaces.previewLinkTTLHours');
393  return $ttlHours ? $ttlHours : 24 * 2;
394  }
395 
399  protected function createFrontendBackendUser()
400  {
402  FrontendBackendUserAuthentication::class
403  );
404  }
405 }
overridePermissionCalculation(array $parameters)
static _GETset($inputGet, $key= '')
if(TYPO3_MODE=== 'BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
static makeInstance($className,...$constructorArguments)
overridePagePermissionClause(array $parameters)
compilePreviewKeyword($getVarsStr, $backendUserUid, $ttl=172800, $fullWorkspace=null)