c862669d5eeb0756fd467a60f96e4d67b4a7ae7b
[Packages/TYPO3.CMS.git] / typo3 / sysext / version / Classes / Hook / PreviewHook.php
1 <?php
2 namespace TYPO3\CMS\Version\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
17 use TYPO3\CMS\Backend\FrontendBackendUserAuthentication;
18 use TYPO3\CMS\Core\Utility\GeneralUtility;
19 use TYPO3\CMS\Core\Utility\MathUtility;
20
21 /**
22 * Hook for checking if the preview mode is activated
23 * preview mode = show a page of a workspace without having to log in
24 */
25 class PreviewHook implements \TYPO3\CMS\Core\SingletonInterface
26 {
27 /**
28 * the GET parameter to be used
29 *
30 * @var string
31 */
32 protected $previewKey = 'ADMCMD_prev';
33
34 /**
35 * instance of the \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController object
36 *
37 * @var \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController
38 */
39 protected $tsfeObj;
40
41 /**
42 * preview configuration
43 *
44 * @var array
45 */
46 protected $previewConfiguration = false;
47
48 /**
49 * hook to check if the preview is activated
50 * right now, this hook is called at the end of "$TSFE->connectToDB"
51 *
52 * @param array $params (not needed right now)
53 * @param \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController $pObj
54 * @return void
55 */
56 public function checkForPreview($params, &$pObj)
57 {
58 $this->tsfeObj = $pObj;
59 $this->previewConfiguration = $this->getPreviewConfiguration();
60 if (is_array($this->previewConfiguration)) {
61 // In case of a keyword-authenticated preview,
62 // re-initialize the TSFE object:
63 // because the GET variables are taken from the preview
64 // configuration
65 $this->tsfeObj = GeneralUtility::makeInstance(
66 \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController::class,
67 $GLOBALS['TYPO3_CONF_VARS'],
68 GeneralUtility::_GP('id'),
69 GeneralUtility::_GP('type'),
70 GeneralUtility::_GP('no_cache'),
71 GeneralUtility::_GP('cHash'),
72 GeneralUtility::_GP('jumpurl'),
73 GeneralUtility::_GP('MP'),
74 GeneralUtility::_GP('RDCT')
75 );
76 $GLOBALS['TSFE'] = $this->tsfeObj;
77 // Configuration after initialization of TSFE object.
78 // Basically this unsets the BE cookie if any and forces
79 // the BE user set according to the preview configuration.
80 // @previouslyknownas TSFE->ADMCMD_preview_postInit
81 // Clear cookies:
82 unset($_COOKIE['be_typo_user']);
83 }
84 }
85
86 /**
87 * hook after the regular BE user has been initialized
88 * if there is no BE user login, but a preview configuration
89 * the BE user of the preview configuration gets initialized
90 *
91 * @param array $params holding the BE_USER object
92 * @param \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController $pObj
93 * @return void
94 */
95 public function initializePreviewUser(&$params, &$pObj)
96 {
97 if ((is_null($params['BE_USER']) || $params['BE_USER'] === false)
98 && $this->previewConfiguration !== false
99 && $this->previewConfiguration['BEUSER_uid'] > 0
100 ) {
101 // New backend user object
102 $BE_USER = GeneralUtility::makeInstance(FrontendBackendUserAuthentication::class);
103 $BE_USER->userTS_dontGetCached = 1;
104 $BE_USER->setBeUserByUid($this->previewConfiguration['BEUSER_uid']);
105 $BE_USER->unpack_uc('');
106 if ($BE_USER->user['uid']) {
107 $BE_USER->fetchGroupData();
108 $pObj->beUserLogin = true;
109 } else {
110 $BE_USER = null;
111 $pObj->beUserLogin = false;
112 }
113 $params['BE_USER'] = $BE_USER;
114 }
115 // if there is a valid BE user, and the full workspace should be
116 // previewed, the workspacePreview option shouldbe set
117 $workspaceUid = $this->previewConfiguration['fullWorkspace'];
118 if ($pObj->beUserLogin
119 && is_object($params['BE_USER'])
120 && MathUtility::canBeInterpretedAsInteger($workspaceUid)
121 ) {
122 if ($workspaceUid == 0
123 || $workspaceUid >= -1
124 && $params['BE_USER']->checkWorkspace($workspaceUid)
125 && $params['BE_USER']->isInWebMount($pObj->id)
126 ) {
127 // Check Access to workspace. Live (0) is OK to preview for all.
128 $pObj->workspacePreview = (int)$workspaceUid;
129 } else {
130 // No preview, will default to "Live" at the moment
131 $pObj->workspacePreview = -99;
132 }
133 }
134 }
135
136 /**
137 * Looking for an ADMCMD_prev code, looks it up if found and returns configuration data.
138 * Background: From the backend a request to the frontend to show a page, possibly with
139 * workspace preview can be "recorded" and associated with a keyword.
140 * When the frontend is requested with this keyword the associated request parameters are
141 * restored from the database AND the backend user is loaded - only for that request.
142 * The main point is that a special URL valid for a limited time,
143 * eg. http://localhost/typo3site/index.php?ADMCMD_prev=035d9bf938bd23cb657735f68a8cedbf will
144 * open up for a preview that doesn't require login. Thus it's useful for sending in an email
145 * to someone without backend account.
146 * This can also be used to generate previews of hidden pages, start/endtimes, usergroups and
147 * those other settings from the Admin Panel - just not implemented yet.
148 *
149 * @throws \Exception
150 * @return array Preview configuration array from sys_preview record.
151 */
152 public function getPreviewConfiguration()
153 {
154 $inputCode = $this->getPreviewInputCode();
155 // If inputcode is available, look up the settings
156 if ($inputCode) {
157 // "log out"
158 if ($inputCode == 'LOGOUT') {
159 setcookie($this->previewKey, '', 0, GeneralUtility::getIndpEnv('TYPO3_SITE_PATH'));
160 if ($this->tsfeObj->TYPO3_CONF_VARS['FE']['workspacePreviewLogoutTemplate']) {
161 $templateFile = PATH_site . $this->tsfeObj->TYPO3_CONF_VARS['FE']['workspacePreviewLogoutTemplate'];
162 if (@is_file($templateFile)) {
163 $message = GeneralUtility::getUrl(PATH_site . $this->tsfeObj->TYPO3_CONF_VARS['FE']['workspacePreviewLogoutTemplate']);
164 } else {
165 $message = '<strong>ERROR!</strong><br>Template File "'
166 . $this->tsfeObj->TYPO3_CONF_VARS['FE']['workspacePreviewLogoutTemplate']
167 . '" configured with $TYPO3_CONF_VARS["FE"]["workspacePreviewLogoutTemplate"] not found. Please contact webmaster about this problem.';
168 }
169 } else {
170 $message = 'You logged out from Workspace preview mode. Click this link to <a href="%1$s">go back to the website</a>';
171 }
172 $returnUrl = GeneralUtility::sanitizeLocalUrl(GeneralUtility::_GET('returnUrl'));
173 die(sprintf($message, htmlspecialchars(preg_replace('/\\&?' . $this->previewKey . '=[[:alnum:]]+/', '', $returnUrl))));
174 }
175 // Look for keyword configuration record:
176 $where = 'keyword=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($inputCode, 'sys_preview') . ' AND endtime>' . $GLOBALS['EXEC_TIME'];
177 $previewData = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow('*', 'sys_preview', $where);
178 // Get: Backend login status, Frontend login status
179 // - Make sure to remove fe/be cookies (temporarily);
180 // BE already done in ADMCMD_preview_postInit()
181 if (is_array($previewData)) {
182 if (empty(GeneralUtility::_POST())) {
183 // Unserialize configuration:
184 $previewConfig = unserialize($previewData['config']);
185 // For full workspace preview we only ADD a get variable
186 // to set the preview of the workspace - so all other Get
187 // vars are accepted. Hope this is not a security problem.
188 // Still posting is not allowed and even if a backend user
189 // get initialized it shouldn't lead to situations where
190 // users can use those credentials.
191 if ($previewConfig['fullWorkspace']) {
192 // Set the workspace preview value:
193 GeneralUtility::_GETset($previewConfig['fullWorkspace'], 'ADMCMD_previewWS');
194 // If ADMCMD_prev is set the $inputCode value cannot come
195 // from a cookie and we set that cookie here. Next time it will
196 // be found from the cookie if ADMCMD_prev is not set again...
197 if (GeneralUtility::_GP($this->previewKey)) {
198 // Lifetime is 1 hour, does it matter much?
199 // Requires the user to click the link from their email again if it expires.
200 SetCookie($this->previewKey, GeneralUtility::_GP($this->previewKey), 0, GeneralUtility::getIndpEnv('TYPO3_SITE_PATH'));
201 }
202 return $previewConfig;
203 } elseif (GeneralUtility::getIndpEnv('TYPO3_SITE_URL') . 'index.php?' . $this->previewKey . '=' . $inputCode === GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL')) {
204 // Set GET variables
205 $GET_VARS = '';
206 parse_str($previewConfig['getVars'], $GET_VARS);
207 GeneralUtility::_GETset($GET_VARS);
208 // Return preview keyword configuration
209 return $previewConfig;
210 } else {
211 // This check is to prevent people from setting additional
212 // GET vars via realurl or other URL path based ways of passing parameters.
213 throw new \Exception(htmlspecialchars('Request URL did not match "'
214 . GeneralUtility::getIndpEnv('TYPO3_SITE_URL') . 'index.php?' . $this->previewKey . '='
215 . $inputCode . '"', 1294585190));
216 }
217 } else {
218 throw new \Exception('POST requests are incompatible with keyword preview.', 1294585191);
219 }
220 } else {
221 throw new \Exception('ADMCMD command could not be executed! (No keyword configuration found)', 1294585192);
222 }
223 }
224 return false;
225 }
226
227 /**
228 * returns the input code value from the admin command variable
229 *
230 * @return string Input code
231 */
232 protected function getPreviewInputCode()
233 {
234 $inputCode = GeneralUtility::_GP($this->previewKey);
235 // If no inputcode and a cookie is set, load input code from cookie:
236 if (!$inputCode && $_COOKIE[$this->previewKey]) {
237 $inputCode = $_COOKIE[$this->previewKey];
238 }
239 return $inputCode;
240 }
241
242 /**
243 * Set preview keyword, eg:
244 * $previewUrl = GeneralUtility::getIndpEnv('TYPO3_SITE_URL').'index.php?ADMCMD_prev='.$this->compilePreviewKeyword('id='.$pageId.'&L='.$language.'&ADMCMD_view=1&ADMCMD_editIcons=1&ADMCMD_previewWS='.$this->workspace, $GLOBALS['BE_USER']->user['uid'], 120);
245 *
246 * @todo for sys_preview:
247 * - Add a comment which can be shown to previewer in frontend in some way (plus maybe ability to write back, take other action?)
248 * - Add possibility for the preview keyword to work in the backend as well: So it becomes a quick way to a certain action of sorts?
249 *
250 * @param string $getVarsStr Get variables to preview, eg. 'id=1150&L=0&ADMCMD_view=1&ADMCMD_editIcons=1&ADMCMD_previewWS=8'
251 * @param string $backendUserUid 32 byte MD5 hash keyword for the URL: "?ADMCMD_prev=[keyword]
252 * @param int $ttl Time-To-Live for keyword
253 * @param int|NULL $fullWorkspace Which workspace to preview. Workspace UID, -1 or >0. If set, the getVars is ignored in the frontend, so that string can be empty
254 * @return string Returns keyword to use in URL for ADMCMD_prev=
255 */
256 public function compilePreviewKeyword($getVarsStr, $backendUserUid, $ttl = 172800, $fullWorkspace = null)
257 {
258 $fieldData = array(
259 'keyword' => md5(uniqid(microtime(), true)),
260 'tstamp' => $GLOBALS['EXEC_TIME'],
261 'endtime' => $GLOBALS['EXEC_TIME'] + $ttl,
262 'config' => serialize(array(
263 'fullWorkspace' => $fullWorkspace,
264 'getVars' => $getVarsStr,
265 'BEUSER_uid' => $backendUserUid
266 ))
267 );
268 $GLOBALS['TYPO3_DB']->exec_INSERTquery('sys_preview', $fieldData);
269 return $fieldData['keyword'];
270 }
271
272 /**
273 * easy function to just return the number of hours
274 * a preview link is valid, based on the TSconfig value "options.workspaces.previewLinkTTLHours"
275 * by default, it's 48hs
276 *
277 * @return int The hours as a number
278 */
279 public function getPreviewLinkLifetime()
280 {
281 $ttlHours = (int)$GLOBALS['BE_USER']->getTSConfigVal('options.workspaces.previewLinkTTLHours');
282 return $ttlHours ? $ttlHours : 24 * 2;
283 }
284 }