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