de99e2ca55210988e03d372bcd43358509cfc57c
[Packages/TYPO3.CMS.git] / typo3 / sysext / felogin / Classes / Controller / FrontendLoginController.php
1 <?php
2 namespace TYPO3\CMS\Felogin\Controller;
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 * Plugin 'Website User Login' for the 'felogin' extension.
21 *
22 * @author Steffen Kamper <info@sk-typo3.de>
23 */
24 class FrontendLoginController extends \TYPO3\CMS\Frontend\Plugin\AbstractPlugin {
25
26 /**
27 * Same as class name
28 *
29 * @var string
30 */
31 public $prefixId = 'tx_felogin_pi1';
32
33 /**
34 * Path to this script relative to the extension dir.
35 *
36 * @var string
37 * @TODO This is still set to the "old" class location since the locallang.xlf file in the same dir is loaded by pi_loadLL
38 */
39 public $scriptRelPath = 'pi1/class.tx_felogin_pi1.php';
40
41 /**
42 * The extension key.
43 *
44 * @var string
45 */
46 public $extKey = 'felogin';
47
48 /**
49 * @var bool
50 */
51 public $pi_checkCHash = FALSE;
52
53 /**
54 * @var bool
55 */
56 public $pi_USER_INT_obj = TRUE;
57
58 /**
59 * Is user logged in?
60 *
61 * @var bool
62 */
63 protected $userIsLoggedIn;
64
65 /**
66 * Holds the template for FE rendering
67 *
68 * @var string
69 */
70 protected $template;
71
72 /**
73 * Upload directory, used for flexform template files
74 *
75 * @var string
76 */
77 protected $uploadDir;
78
79 /**
80 * URL for the redirect
81 *
82 * @var string
83 */
84 protected $redirectUrl;
85
86 /**
87 * Flag for disable the redirect
88 *
89 * @var bool
90 */
91 protected $noRedirect = FALSE;
92
93 /**
94 * Logintype (given as GPvar), possible: login, logout
95 *
96 * @var string
97 */
98 protected $logintype;
99
100 /**
101 * The main method of the plugin
102 *
103 * @param string $content The PlugIn content
104 * @param array $conf The PlugIn configuration
105 * @return string The content that is displayed on the website
106 */
107 public function main($content, $conf) {
108 // Loading TypoScript array into object variable:
109 $this->conf = $conf;
110 $this->uploadDir = 'uploads/tx_felogin/';
111 // Loading default pivars
112 $this->pi_setPiVarDefaults();
113 // Loading language-labels
114 $this->pi_loadLL();
115 // Init FlexForm configuration for plugin:
116 $this->pi_initPIflexForm();
117 $this->mergeflexFormValuesIntoConf();
118 // Get storage PIDs:
119 if ($this->conf['storagePid']) {
120 if ((int)$this->conf['recursive']) {
121 $this->spid = $this->pi_getPidList($this->conf['storagePid'], (int)$this->conf['recursive']);
122 } else {
123 $this->spid = $this->conf['storagePid'];
124 }
125 } else {
126 $pids = $GLOBALS['TSFE']->getStorageSiterootPids();
127 $this->spid = $pids['_STORAGE_PID'];
128 }
129 // GPvars:
130 $this->logintype = GeneralUtility::_GP('logintype');
131 $this->referer = $this->validateRedirectUrl(GeneralUtility::_GP('referer'));
132 $this->noRedirect = $this->piVars['noredirect'] || $this->conf['redirectDisable'];
133 // If config.typolinkLinkAccessRestrictedPages is set, the var is return_url
134 $returnUrl = GeneralUtility::_GP('return_url');
135 if ($returnUrl) {
136 $this->redirectUrl = $returnUrl;
137 } else {
138 $this->redirectUrl = GeneralUtility::_GP('redirect_url');
139 }
140 $this->redirectUrl = $this->validateRedirectUrl($this->redirectUrl);
141 // Get Template
142 $templateFile = $this->conf['templateFile'] ?: 'EXT:felogin/template.html';
143 $this->template = $this->cObj->fileResource($templateFile);
144 // Is user logged in?
145 $this->userIsLoggedIn = $GLOBALS['TSFE']->loginUser;
146 // Redirect
147 if ($this->conf['redirectMode'] && !$this->conf['redirectDisable'] && !$this->noRedirect) {
148 $redirectUrl = $this->processRedirect();
149 if (count($redirectUrl)) {
150 $this->redirectUrl = $this->conf['redirectFirstMethod'] ? array_shift($redirectUrl) : array_pop($redirectUrl);
151 } else {
152 $this->redirectUrl = '';
153 }
154 }
155 // What to display
156 $content = '';
157 if ($this->piVars['forgot'] && $this->conf['showForgotPasswordLink']) {
158 $content .= $this->showForgot();
159 } elseif ($this->piVars['forgothash']) {
160 $content .= $this->changePassword();
161 } else {
162 if ($this->userIsLoggedIn && !$this->logintype) {
163 $content .= $this->showLogout();
164 } else {
165 $content .= $this->showLogin();
166 }
167 }
168 // Process the redirect
169 if (($this->logintype === 'login' || $this->logintype === 'logout') && $this->redirectUrl && !$this->noRedirect) {
170 if (!$GLOBALS['TSFE']->fe_user->isCookieSet() && $this->userIsLoggedIn) {
171 $content .= $this->cObj->stdWrap($this->pi_getLL('cookie_warning', '', TRUE), $this->conf['cookieWarning_stdWrap.']);
172 } else {
173 // Add hook for extra processing before redirect
174 if (
175 isset($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['beforeRedirect']) &&
176 is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['beforeRedirect'])
177 ) {
178 $_params = array(
179 'loginType' => $this->logintype,
180 'redirectUrl' => &$this->redirectUrl
181 );
182 foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['beforeRedirect'] as $_funcRef) {
183 if ($_funcRef) {
184 GeneralUtility::callUserFunction($_funcRef, $_params, $this);
185 }
186 }
187 }
188 \TYPO3\CMS\Core\Utility\HttpUtility::redirect($this->redirectUrl);
189 }
190 }
191 // Adds hook for processing of extra item markers / special
192 if (
193 isset($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['postProcContent'])
194 && is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['postProcContent'])
195 ) {
196 $_params = array(
197 'content' => $content
198 );
199 foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['postProcContent'] as $_funcRef) {
200 $content = GeneralUtility::callUserFunction($_funcRef, $_params, $this);
201 }
202 }
203 return $this->conf['wrapContentInBaseClass'] ? $this->pi_wrapInBaseClass($content) : $content;
204 }
205
206 /**
207 * Shows the forgot password form
208 *
209 * @return string Content
210 */
211 protected function showForgot() {
212 $subpart = $this->cObj->getSubpart($this->template, '###TEMPLATE_FORGOT###');
213 $subpartArray = ($linkpartArray = array());
214 $postData = GeneralUtility::_POST($this->prefixId);
215 if ($postData['forgot_email']) {
216 // Get hashes for compare
217 $postedHash = $postData['forgot_hash'];
218 $hashData = $GLOBALS['TSFE']->fe_user->getKey('ses', 'forgot_hash');
219 if ($postedHash === $hashData['forgot_hash']) {
220 $row = FALSE;
221 // Look for user record
222 $data = $GLOBALS['TYPO3_DB']->fullQuoteStr($this->piVars['forgot_email'], 'fe_users');
223 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
224 'uid, username, password, email',
225 'fe_users',
226 '(email=' . $data . ' OR username=' . $data . ') AND pid IN (' . $GLOBALS['TYPO3_DB']->cleanIntList($this->spid) . ') ' . $this->cObj->enableFields('fe_users')
227 );
228 if ($GLOBALS['TYPO3_DB']->sql_num_rows($res)) {
229 $row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
230 }
231 $error = NULL;
232 if ($row) {
233 // Generate an email with the hashed link
234 $error = $this->generateAndSendHash($row);
235 } elseif ($this->conf['exposeNonexistentUserInForgotPasswordDialog']) {
236 $error = $this->pi_getLL('ll_forgot_reset_message_error');
237 }
238 // Generate message
239 if ($error) {
240 $markerArray['###STATUS_MESSAGE###'] = $this->cObj->stdWrap($error, $this->conf['forgotErrorMessage_stdWrap.']);
241 } else {
242 $markerArray['###STATUS_MESSAGE###'] = $this->cObj->stdWrap(
243 $this->pi_getLL('ll_forgot_reset_message_emailSent', '', TRUE),
244 $this->conf['forgotResetMessageEmailSentMessage_stdWrap.']
245 );
246 }
247 $subpartArray['###FORGOT_FORM###'] = '';
248 } else {
249 // Wrong email
250 $markerArray['###STATUS_MESSAGE###'] = $this->getDisplayText('forgot_reset_message', $this->conf['forgotMessage_stdWrap.']);
251 $markerArray['###BACKLINK_LOGIN###'] = '';
252 }
253 } else {
254 $markerArray['###STATUS_MESSAGE###'] = $this->getDisplayText('forgot_reset_message', $this->conf['forgotMessage_stdWrap.']);
255 $markerArray['###BACKLINK_LOGIN###'] = '';
256 }
257 $markerArray['###BACKLINK_LOGIN###'] = $this->getPageLink($this->pi_getLL('ll_forgot_header_backToLogin', '', TRUE), array());
258 $markerArray['###STATUS_HEADER###'] = $this->getDisplayText('forgot_header', $this->conf['forgotHeader_stdWrap.']);
259 $markerArray['###LEGEND###'] = $this->pi_getLL('legend', $this->pi_getLL('reset_password', '', TRUE), TRUE);
260 $markerArray['###ACTION_URI###'] = $this->getPageLink('', array($this->prefixId . '[forgot]' => 1), TRUE);
261 $markerArray['###EMAIL_LABEL###'] = $this->pi_getLL('your_email', '', TRUE);
262 $markerArray['###FORGOT_PASSWORD_ENTEREMAIL###'] = $this->pi_getLL('forgot_password_enterEmail', '', TRUE);
263 $markerArray['###FORGOT_EMAIL###'] = $this->prefixId . '[forgot_email]';
264 $markerArray['###SEND_PASSWORD###'] = $this->pi_getLL('reset_password', '', TRUE);
265 $markerArray['###DATA_LABEL###'] = $this->pi_getLL('ll_enter_your_data', '', TRUE);
266 $markerArray = array_merge($markerArray, $this->getUserFieldMarkers());
267 // Generate hash
268 $hash = md5($this->generatePassword(3));
269 $markerArray['###FORGOTHASH###'] = $hash;
270 // Set hash in feuser session
271 $GLOBALS['TSFE']->fe_user->setKey('ses', 'forgot_hash', array('forgot_hash' => $hash));
272 return $this->cObj->substituteMarkerArrayCached($subpart, $markerArray, $subpartArray, $linkpartArray);
273 }
274
275 /**
276 * This function checks the hash from link and checks the validity. If it's valid it shows the form for
277 * changing the password and process the change of password after submit, if not valid it returns the error message
278 *
279 * @return string The content.
280 */
281 protected function changePassword() {
282 $subpartArray = ($linkpartArray = array());
283 $done = FALSE;
284 $minLength = (int)$this->conf['newPasswordMinLength'] ?: 6;
285 $subpart = $this->cObj->getSubpart($this->template, '###TEMPLATE_CHANGEPASSWORD###');
286 $markerArray['###STATUS_HEADER###'] = $this->getDisplayText('change_password_header', $this->conf['changePasswordHeader_stdWrap.']);
287 $markerArray['###STATUS_MESSAGE###'] = sprintf($this->getDisplayText(
288 'change_password_message',
289 $this->conf['changePasswordMessage_stdWrap.']
290 ), $minLength);
291
292 $markerArray['###BACKLINK_LOGIN###'] = '';
293 $uid = $this->piVars['user'];
294 $piHash = $this->piVars['forgothash'];
295 $hash = explode('|', $piHash);
296 if ((int)$uid === 0) {
297 $markerArray['###STATUS_MESSAGE###'] = $this->getDisplayText(
298 'change_password_notvalid_message',
299 $this->conf['changePasswordNotValidMessage_stdWrap.']
300 );
301 $subpartArray['###CHANGEPASSWORD_FORM###'] = '';
302 } else {
303 $user = $this->pi_getRecord('fe_users', (int)$uid);
304 $userHash = $user['felogin_forgotHash'];
305 $compareHash = explode('|', $userHash);
306 if (!$compareHash || !$compareHash[1] || $compareHash[0] < time() || $hash[0] != $compareHash[0] || md5($hash[1]) != $compareHash[1]) {
307 $markerArray['###STATUS_MESSAGE###'] = $this->getDisplayText(
308 'change_password_notvalid_message',
309 $this->conf['changePasswordNotValidMessage_stdWrap.']
310 );
311 $subpartArray['###CHANGEPASSWORD_FORM###'] = '';
312 } else {
313 // All is fine, continue with new password
314 $postData = GeneralUtility::_POST($this->prefixId);
315 if (isset($postData['changepasswordsubmit'])) {
316 if (strlen($postData['password1']) < $minLength) {
317 $markerArray['###STATUS_MESSAGE###'] = sprintf($this->getDisplayText(
318 'change_password_tooshort_message',
319 $this->conf['changePasswordTooShortMessage_stdWrap.']),
320 $minLength
321 );
322 } elseif ($postData['password1'] != $postData['password2']) {
323 $markerArray['###STATUS_MESSAGE###'] = sprintf($this->getDisplayText(
324 'change_password_notequal_message',
325 $this->conf['changePasswordNotEqualMessage_stdWrap.']),
326 $minLength
327 );
328 } else {
329 $newPass = $postData['password1'];
330 if ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['password_changed']) {
331 $_params = array(
332 'user' => $user,
333 'newPassword' => $newPass
334 );
335 foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['password_changed'] as $_funcRef) {
336 if ($_funcRef) {
337 GeneralUtility::callUserFunction($_funcRef, $_params, $this);
338 }
339 }
340 $newPass = $_params['newPassword'];
341 }
342 // Save new password and clear DB-hash
343 $res = $GLOBALS['TYPO3_DB']->exec_UPDATEquery(
344 'fe_users',
345 'uid=' . $user['uid'],
346 array('password' => $newPass, 'felogin_forgotHash' => '', 'tstamp' => $GLOBALS['EXEC_TIME'])
347 );
348 $markerArray['###STATUS_MESSAGE###'] = $this->getDisplayText(
349 'change_password_done_message',
350 $this->conf['changePasswordDoneMessage_stdWrap.']
351 );
352 $done = TRUE;
353 $subpartArray['###CHANGEPASSWORD_FORM###'] = '';
354 $markerArray['###BACKLINK_LOGIN###'] = $this->getPageLink(
355 $this->pi_getLL('ll_forgot_header_backToLogin', '', TRUE),
356 array($this->prefixId . '[redirectReferrer]' => 'off')
357 );
358 }
359 }
360 if (!$done) {
361 // Change password form
362 $markerArray['###ACTION_URI###'] = $this->pi_getPageLink($GLOBALS['TSFE']->id, '', array(
363 $this->prefixId . '[user]' => $user['uid'],
364 $this->prefixId . '[forgothash]' => $piHash
365 ));
366 $markerArray['###LEGEND###'] = $this->pi_getLL('change_password', '', TRUE);
367 $markerArray['###NEWPASSWORD1_LABEL###'] = $this->pi_getLL('newpassword_label1', '', TRUE);
368 $markerArray['###NEWPASSWORD2_LABEL###'] = $this->pi_getLL('newpassword_label2', '', TRUE);
369 $markerArray['###NEWPASSWORD1###'] = $this->prefixId . '[password1]';
370 $markerArray['###NEWPASSWORD2###'] = $this->prefixId . '[password2]';
371 $markerArray['###STORAGE_PID###'] = $this->spid;
372 $markerArray['###SEND_PASSWORD###'] = $this->pi_getLL('change_password', '', TRUE);
373 $markerArray['###FORGOTHASH###'] = $piHash;
374 }
375 }
376 }
377 return $this->cObj->substituteMarkerArrayCached($subpart, $markerArray, $subpartArray, $linkpartArray);
378 }
379
380 /**
381 * Generates a hashed link and send it with email
382 *
383 * @param array $user Contains user data
384 * @return string Empty string with success, error message with no success
385 */
386 protected function generateAndSendHash($user) {
387 $hours = (int)$this->conf['forgotLinkHashValidTime'] > 0 ? (int)$this->conf['forgotLinkHashValidTime'] : 24;
388 $validEnd = time() + 3600 * $hours;
389 $validEndString = date($this->conf['dateFormat'], $validEnd);
390 $hash = md5(GeneralUtility::generateRandomBytes(64));
391 $randHash = $validEnd . '|' . $hash;
392 $randHashDB = $validEnd . '|' . md5($hash);
393 // Write hash to DB
394 $res = $GLOBALS['TYPO3_DB']->exec_UPDATEquery('fe_users', 'uid=' . $user['uid'], array('felogin_forgotHash' => $randHashDB));
395 // Send hashlink to user
396 $this->conf['linkPrefix'] = -1;
397 $isAbsRelPrefix = !empty($GLOBALS['TSFE']->absRefPrefix);
398 $isBaseURL = !empty($GLOBALS['TSFE']->baseUrl);
399 $isFeloginBaseURL = !empty($this->conf['feloginBaseURL']);
400 $link = $this->pi_getPageLink($GLOBALS['TSFE']->id, '', array(
401 rawurlencode($this->prefixId . '[user]') => $user['uid'],
402 rawurlencode($this->prefixId . '[forgothash]') => $randHash
403 ));
404 // Prefix link if necessary
405 if ($isFeloginBaseURL) {
406 // First priority, use specific base URL
407 // "absRefPrefix" must be removed first, otherwise URL will be prepended twice
408 if (!empty($GLOBALS['TSFE']->absRefPrefix)) {
409 $link = substr($link, strlen($GLOBALS['TSFE']->absRefPrefix));
410 }
411 $link = $this->conf['feloginBaseURL'] . $link;
412 } elseif ($isAbsRelPrefix) {
413 // Second priority
414 // absRefPrefix must not necessarily contain a hostname and URL scheme, so add it if needed
415 $link = GeneralUtility::locationHeaderUrl($link);
416 } elseif ($isBaseURL) {
417 // Third priority
418 // Add the global base URL to the link
419 $link = $GLOBALS['TSFE']->baseUrlWrap($link);
420 } else {
421 // No prefix is set, return the error
422 return $this->pi_getLL('ll_change_password_nolinkprefix_message');
423 }
424 $msg = sprintf($this->pi_getLL('ll_forgot_validate_reset_password'), $user['username'], $link, $validEndString);
425 // Add hook for extra processing of mail message
426 if (
427 isset($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['forgotPasswordMail'])
428 && is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['forgotPasswordMail'])
429 ) {
430 $params = array(
431 'message' => &$msg,
432 'user' => &$user
433 );
434 foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['forgotPasswordMail'] as $reference) {
435 if ($reference) {
436 GeneralUtility::callUserFunction($reference, $params, $this);
437 }
438 }
439 }
440 if ($user['email']) {
441 $this->cObj->sendNotifyEmail($msg, $user['email'], '', $this->conf['email_from'], $this->conf['email_fromName'], $this->conf['replyTo']);
442 }
443
444 return '';
445 }
446
447 /**
448 * Shows logout form
449 *
450 * @return string The content.
451 */
452 protected function showLogout() {
453 $subpart = $this->cObj->getSubpart($this->template, '###TEMPLATE_LOGOUT###');
454 $subpartArray = ($linkpartArray = array());
455 $markerArray['###STATUS_HEADER###'] = $this->getDisplayText('status_header', $this->conf['logoutHeader_stdWrap.']);
456 $markerArray['###STATUS_MESSAGE###'] = $this->getDisplayText('status_message', $this->conf['logoutMessage_stdWrap.']);
457 $this->cObj->stdWrap($this->flexFormValue('message', 's_status'), $this->conf['logoutMessage_stdWrap.']);
458 $markerArray['###LEGEND###'] = $this->pi_getLL('logout', '', TRUE);
459 $markerArray['###ACTION_URI###'] = $this->getPageLink('', array(), TRUE);
460 $markerArray['###LOGOUT_LABEL###'] = $this->pi_getLL('logout', '', TRUE);
461 $markerArray['###NAME###'] = htmlspecialchars($GLOBALS['TSFE']->fe_user->user['name']);
462 $markerArray['###STORAGE_PID###'] = $this->spid;
463 $markerArray['###USERNAME###'] = htmlspecialchars($GLOBALS['TSFE']->fe_user->user['username']);
464 $markerArray['###USERNAME_LABEL###'] = $this->pi_getLL('username', '', TRUE);
465 $markerArray['###NOREDIRECT###'] = $this->noRedirect ? '1' : '0';
466 $markerArray['###PREFIXID###'] = $this->prefixId;
467 $markerArray = array_merge($markerArray, $this->getUserFieldMarkers());
468 if ($this->redirectUrl) {
469 // Use redirectUrl for action tag because of possible access restricted pages
470 $markerArray['###ACTION_URI###'] = htmlspecialchars($this->redirectUrl);
471 $this->redirectUrl = '';
472 }
473 return $this->cObj->substituteMarkerArrayCached($subpart, $markerArray, $subpartArray, $linkpartArray);
474 }
475
476 /**
477 * Shows login form
478 *
479 * @return string Content
480 */
481 protected function showLogin() {
482 $subpart = $this->cObj->getSubpart($this->template, '###TEMPLATE_LOGIN###');
483 $subpartArray = ($linkpartArray = ($markerArray = array()));
484 $gpRedirectUrl = '';
485 $markerArray['###LEGEND###'] = $this->pi_getLL('oLabel_header_welcome', '', TRUE);
486 if ($this->logintype === 'login') {
487 if ($this->userIsLoggedIn) {
488 // login success
489 $markerArray['###STATUS_HEADER###'] = $this->getDisplayText('success_header', $this->conf['successHeader_stdWrap.']);
490 $markerArray['###STATUS_MESSAGE###'] = $this->getDisplayText('success_message', $this->conf['successMessage_stdWrap.']);
491 $markerArray = array_merge($markerArray, $this->getUserFieldMarkers());
492 $subpartArray['###LOGIN_FORM###'] = '';
493 // Hook for general actions after after login has been confirmed (by Thomas Danzl <thomas@danzl.org>)
494 if ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['login_confirmed']) {
495 $_params = array();
496 foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['login_confirmed'] as $_funcRef) {
497 if ($_funcRef) {
498 GeneralUtility::callUserFunction($_funcRef, $_params, $this);
499 }
500 }
501 }
502 // show logout form directly
503 if ($this->conf['showLogoutFormAfterLogin']) {
504 $this->redirectUrl = '';
505 return $this->showLogout();
506 }
507 } else {
508 // Hook for general actions on login error
509 if (
510 isset($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['login_error'])
511 && is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['login_error'])
512 ) {
513 $params = array();
514 foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['login_error'] as $funcRef) {
515 if ($funcRef) {
516 GeneralUtility::callUserFunction($funcRef, $params, $this);
517 }
518 }
519 }
520 // login error
521 $markerArray['###STATUS_HEADER###'] = $this->getDisplayText('error_header', $this->conf['errorHeader_stdWrap.']);
522 $markerArray['###STATUS_MESSAGE###'] = $this->getDisplayText('error_message', $this->conf['errorMessage_stdWrap.']);
523 $gpRedirectUrl = GeneralUtility::_GP('redirect_url');
524 }
525 } else {
526 if ($this->logintype === 'logout') {
527 // login form after logout
528 $markerArray['###STATUS_HEADER###'] = $this->getDisplayText('logout_header', $this->conf['logoutHeader_stdWrap.']);
529 $markerArray['###STATUS_MESSAGE###'] = $this->getDisplayText('logout_message', $this->conf['logoutMessage_stdWrap.']);
530 } else {
531 // login form
532 $markerArray['###STATUS_HEADER###'] = $this->getDisplayText('welcome_header', $this->conf['welcomeHeader_stdWrap.']);
533 $markerArray['###STATUS_MESSAGE###'] = $this->getDisplayText('welcome_message', $this->conf['welcomeMessage_stdWrap.']);
534 }
535 }
536
537 // This hook allows to call User JS functions.
538 // The methods should also set the required JS functions to get included
539 $onSubmit = '';
540 $extraHidden = '';
541 $onSubmitAr = array();
542 $extraHiddenAr = array();
543 // Check for referer redirect method. if present, save referer in form field
544 if (GeneralUtility::inList($this->conf['redirectMode'], 'referer') || GeneralUtility::inList($this->conf['redirectMode'], 'refererDomains')) {
545 $referer = $this->referer ? $this->referer : GeneralUtility::getIndpEnv('HTTP_REFERER');
546 if ($referer) {
547 $extraHiddenAr[] = '<input type="hidden" name="referer" value="' . htmlspecialchars($referer) . '" />';
548 if ($this->piVars['redirectReferrer'] === 'off') {
549 $extraHiddenAr[] = '<input type="hidden" name="' . $this->prefixId . '[redirectReferrer]" value="off" />';
550 }
551 }
552 }
553 if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['loginFormOnSubmitFuncs'])) {
554 $_params = array();
555 foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['loginFormOnSubmitFuncs'] as $funcRef) {
556 list($onSub, $hid) = GeneralUtility::callUserFunction($funcRef, $_params, $this);
557 $onSubmitAr[] = $onSub;
558 $extraHiddenAr[] = $hid;
559 }
560 }
561 if (count($onSubmitAr)) {
562 $onSubmit = implode('; ', $onSubmitAr) . '; return true;';
563 }
564 if (count($extraHiddenAr)) {
565 $extraHidden = implode(LF, $extraHiddenAr);
566 }
567 if (!$gpRedirectUrl && $this->redirectUrl) {
568 $gpRedirectUrl = $this->redirectUrl;
569 }
570 // Login form
571 $markerArray['###ACTION_URI###'] = $this->getPageLink('', array(), TRUE);
572 // Used by kb_md5fepw extension...
573 $markerArray['###EXTRA_HIDDEN###'] = $extraHidden;
574 $markerArray['###LEGEND###'] = $this->pi_getLL('login', '', TRUE);
575 $markerArray['###LOGIN_LABEL###'] = $this->pi_getLL('login', '', TRUE);
576 // Used by kb_md5fepw extension...
577 $markerArray['###ON_SUBMIT###'] = $onSubmit;
578 $markerArray['###PASSWORD_LABEL###'] = $this->pi_getLL('password', '', TRUE);
579 $markerArray['###STORAGE_PID###'] = $this->spid;
580 $markerArray['###USERNAME_LABEL###'] = $this->pi_getLL('username', '', TRUE);
581 $markerArray['###REDIRECT_URL###'] = htmlspecialchars($gpRedirectUrl);
582 $markerArray['###NOREDIRECT###'] = $this->noRedirect ? '1' : '0';
583 $markerArray['###PREFIXID###'] = $this->prefixId;
584 $markerArray = array_merge($markerArray, $this->getUserFieldMarkers());
585 if ($this->conf['showForgotPasswordLink']) {
586 $linkpartArray['###FORGOT_PASSWORD_LINK###'] = explode('|', $this->getPageLink('|', array($this->prefixId . '[forgot]' => 1)));
587 $markerArray['###FORGOT_PASSWORD###'] = $this->pi_getLL('ll_forgot_header', '', TRUE);
588 } else {
589 $subpartArray['###FORGOTP_VALID###'] = '';
590 }
591 // The permanent login checkbox should only be shown if permalogin is not deactivated (-1),
592 // not forced to be always active (2) and lifetime is greater than 0
593 if (
594 $this->conf['showPermaLogin']
595 && GeneralUtility::inList('0,1', $GLOBALS['TYPO3_CONF_VARS']['FE']['permalogin'])
596 && $GLOBALS['TYPO3_CONF_VARS']['FE']['lifetime'] > 0
597 ) {
598 $markerArray['###PERMALOGIN###'] = $this->pi_getLL('permalogin', '', TRUE);
599 if ($GLOBALS['TYPO3_CONF_VARS']['FE']['permalogin'] == 1) {
600 $markerArray['###PERMALOGIN_HIDDENFIELD_ATTRIBUTES###'] = 'disabled="disabled"';
601 $markerArray['###PERMALOGIN_CHECKBOX_ATTRIBUTES###'] = 'checked="checked"';
602 } else {
603 $markerArray['###PERMALOGIN_HIDDENFIELD_ATTRIBUTES###'] = '';
604 $markerArray['###PERMALOGIN_CHECKBOX_ATTRIBUTES###'] = '';
605 }
606 } else {
607 $subpartArray['###PERMALOGIN_VALID###'] = '';
608 }
609 return $this->cObj->substituteMarkerArrayCached($subpart, $markerArray, $subpartArray, $linkpartArray);
610 }
611
612 /**
613 * Process redirect methods. The function searches for a redirect url using all configured methods.
614 *
615 * @return string Redirect url
616 */
617 protected function processRedirect() {
618 $redirect_url = array();
619 if ($this->conf['redirectMode']) {
620 $redirectMethods = GeneralUtility::trimExplode(',', $this->conf['redirectMode'], TRUE);
621 foreach ($redirectMethods as $redirMethod) {
622 if ($GLOBALS['TSFE']->loginUser && $this->logintype === 'login') {
623 // Logintype is needed because the login-page wouldn't be accessible anymore after a login (would always redirect)
624 switch ($redirMethod) {
625 case 'groupLogin':
626 // taken from dkd_redirect_at_login written by Ingmar Schlecht; database-field changed
627 $groupData = $GLOBALS['TSFE']->fe_user->groupData;
628 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
629 'felogin_redirectPid',
630 $GLOBALS['TSFE']->fe_user->usergroup_table,
631 'felogin_redirectPid<>\'\' AND uid IN (' . implode(',', $groupData['uid']) . ')'
632 );
633 if ($row = $GLOBALS['TYPO3_DB']->sql_fetch_row($res)) {
634 // take the first group with a redirect page
635 $redirect_url[] = $this->pi_getPageLink($row[0]);
636 }
637 break;
638 case 'userLogin':
639 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
640 'felogin_redirectPid',
641 $GLOBALS['TSFE']->fe_user->user_table,
642 $GLOBALS['TSFE']->fe_user->userid_column . '=' . $GLOBALS['TSFE']->fe_user->user['uid'] . ' AND felogin_redirectPid<>\'\''
643 );
644 if ($row = $GLOBALS['TYPO3_DB']->sql_fetch_row($res)) {
645 $redirect_url[] = $this->pi_getPageLink($row[0]);
646 }
647 break;
648 case 'login':
649 if ($this->conf['redirectPageLogin']) {
650 $redirect_url[] = $this->pi_getPageLink((int)$this->conf['redirectPageLogin']);
651 }
652 break;
653 case 'getpost':
654 $redirect_url[] = $this->redirectUrl;
655 break;
656 case 'referer':
657 // Avoid redirect when logging in after changing password
658 if ($this->piVars['redirectReferrer'] !== 'off') {
659 // Avoid forced logout, when trying to login immediately after a logout
660 $redirect_url[] = preg_replace('/[&?]logintype=[a-z]+/', '', $this->referer);
661 }
662 break;
663 case 'refererDomains':
664 // Auto redirect.
665 // Feature to redirect to the page where the user came from (HTTP_REFERER).
666 // Allowed domains to redirect to, can be configured with plugin.tx_felogin_pi1.domains
667 // Thanks to plan2.net / Martin Kutschker for implementing this feature.
668 // also avoid redirect when logging in after changing password
669 if ($this->conf['domains'] && $this->piVars['redirectReferrer'] !== 'off') {
670 $url = $this->referer;
671 // Is referring url allowed to redirect?
672 $match = array();
673 if (preg_match('#^http://([[:alnum:]._-]+)/#', $url, $match)) {
674 $redirect_domain = $match[1];
675 $found = FALSE;
676 foreach (GeneralUtility::trimExplode(',', $this->conf['domains'], TRUE) as $d) {
677 if (preg_match('/(^|\\.)/' . $d . '$', $redirect_domain)) {
678 $found = TRUE;
679 break;
680 }
681 }
682 if (!$found) {
683 $url = '';
684 }
685 }
686 // Avoid forced logout, when trying to login immediately after a logout
687 if ($url) {
688 $redirect_url[] = preg_replace('/[&?]logintype=[a-z]+/', '', $url);
689 }
690 }
691 break;
692 }
693 } elseif ($this->logintype === 'login') {
694 // after login-error
695 switch ($redirMethod) {
696 case 'loginError':
697 if ($this->conf['redirectPageLoginError']) {
698 $redirect_url[] = $this->pi_getPageLink((int)$this->conf['redirectPageLoginError']);
699 }
700 break;
701 }
702 } elseif ($this->logintype == '' && $redirMethod == 'login' && $this->conf['redirectPageLogin']) {
703 // If login and page not accessible
704 $this->cObj->typolink('', array(
705 'parameter' => $this->conf['redirectPageLogin'],
706 'linkAccessRestrictedPages' => TRUE
707 ));
708 $redirect_url[] = $this->cObj->lastTypoLinkUrl;
709 } elseif ($this->logintype == '' && $redirMethod == 'logout' && $this->conf['redirectPageLogout'] && $GLOBALS['TSFE']->loginUser) {
710 // If logout and page not accessible
711 $redirect_url[] = $this->pi_getPageLink((int)$this->conf['redirectPageLogout']);
712 } elseif ($this->logintype === 'logout') {
713 // after logout
714 // Hook for general actions after after logout has been confirmed
715 if ($this->logintype === 'logout' && $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['logout_confirmed']) {
716 $_params = array();
717 foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['logout_confirmed'] as $_funcRef) {
718 if ($_funcRef) {
719 GeneralUtility::callUserFunction($_funcRef, $_params, $this);
720 }
721 }
722 }
723 switch ($redirMethod) {
724 case 'logout':
725 if ($this->conf['redirectPageLogout']) {
726 $redirect_url[] = $this->pi_getPageLink((int)$this->conf['redirectPageLogout']);
727 }
728 break;
729 }
730 } else {
731 // not logged in
732 // Placeholder for maybe future options
733 switch ($redirMethod) {
734 case 'getpost':
735 // Preserve the get/post value
736 $redirect_url[] = $this->redirectUrl;
737 break;
738 }
739 }
740 }
741 }
742 // Remove empty values
743 if (count($redirect_url)) {
744 return GeneralUtility::trimExplode(',', implode(',', $redirect_url), TRUE);
745 } else {
746 return array();
747 }
748 }
749
750 /**
751 * Reads flexform configuration and merge it with $this->conf
752 *
753 * @return void
754 */
755 protected function mergeflexFormValuesIntoConf() {
756 $flex = array();
757 if ($this->flexFormValue('showForgotPassword', 'sDEF')) {
758 $flex['showForgotPasswordLink'] = $this->flexFormValue('showForgotPassword', 'sDEF');
759 }
760 if ($this->flexFormValue('showPermaLogin', 'sDEF')) {
761 $flex['showPermaLogin'] = $this->flexFormValue('showPermaLogin', 'sDEF');
762 }
763 if ($this->flexFormValue('showLogoutFormAfterLogin', 'sDEF')) {
764 $flex['showLogoutFormAfterLogin'] = $this->flexFormValue('showLogoutFormAfterLogin', 'sDEF');
765 }
766 if ($this->flexFormValue('pages', 'sDEF')) {
767 $flex['pages'] = $this->flexFormValue('pages', 'sDEF');
768 }
769 if ($this->flexFormValue('recursive', 'sDEF')) {
770 $flex['recursive'] = $this->flexFormValue('recursive', 'sDEF');
771 }
772 if ($this->flexFormValue('templateFile', 'sDEF')) {
773 $flex['templateFile'] = $this->uploadDir . $this->flexFormValue('templateFile', 'sDEF');
774 }
775 if ($this->flexFormValue('redirectMode', 's_redirect')) {
776 $flex['redirectMode'] = $this->flexFormValue('redirectMode', 's_redirect');
777 }
778 if ($this->flexFormValue('redirectFirstMethod', 's_redirect')) {
779 $flex['redirectFirstMethod'] = $this->flexFormValue('redirectFirstMethod', 's_redirect');
780 }
781 if ($this->flexFormValue('redirectDisable', 's_redirect')) {
782 $flex['redirectDisable'] = $this->flexFormValue('redirectDisable', 's_redirect');
783 }
784 if ($this->flexFormValue('redirectPageLogin', 's_redirect')) {
785 $flex['redirectPageLogin'] = $this->flexFormValue('redirectPageLogin', 's_redirect');
786 }
787 if ($this->flexFormValue('redirectPageLoginError', 's_redirect')) {
788 $flex['redirectPageLoginError'] = $this->flexFormValue('redirectPageLoginError', 's_redirect');
789 }
790 if ($this->flexFormValue('redirectPageLogout', 's_redirect')) {
791 $flex['redirectPageLogout'] = $this->flexFormValue('redirectPageLogout', 's_redirect');
792 }
793 $pid = $flex['pages'] ? $this->pi_getPidList($flex['pages'], $flex['recursive']) : 0;
794 if ($pid > 0) {
795 $flex['storagePid'] = $pid;
796 }
797 $this->conf = array_merge($this->conf, $flex);
798 }
799
800 /**
801 * Loads a variable from the flexform
802 *
803 * @param string $var Name of variable
804 * @param string $sheet Name of sheet
805 * @return string Value of var
806 */
807 protected function flexFormValue($var, $sheet) {
808 return $this->pi_getFFvalue($this->cObj->data['pi_flexform'], $var, $sheet);
809 }
810
811 /**
812 * Generate link with typolink function
813 *
814 * @param string $label Linktext
815 * @param array $piVars Link vars
816 * @param bool $returnUrl TRUE: returns only url FALSE (default) returns the link)
817 * @return string Link or url
818 */
819 protected function getPageLink($label, $piVars, $returnUrl = FALSE) {
820 $additionalParams = '';
821 if (count($piVars)) {
822 foreach ($piVars as $key => $val) {
823 $additionalParams .= '&' . $key . '=' . $val;
824 }
825 }
826 // Should GETvars be preserved?
827 if ($this->conf['preserveGETvars']) {
828 $additionalParams .= $this->getPreserveGetVars();
829 }
830 $this->conf['linkConfig.']['parameter'] = $GLOBALS['TSFE']->id;
831 if ($additionalParams) {
832 $this->conf['linkConfig.']['additionalParams'] = $additionalParams;
833 }
834 if ($returnUrl) {
835 return htmlspecialchars($this->cObj->typolink_url($this->conf['linkConfig.']));
836 } else {
837 return $this->cObj->typolink($label, $this->conf['linkConfig.']);
838 }
839 }
840
841 /**
842 * Add additional parameters for links according to TS setting preserveGETvars.
843 * Possible values are "all" or a comma separated list of allowed GET-vars.
844 * Supports multi-dimensional GET-vars.
845 * Some hardcoded values are dropped.
846 *
847 * @return string additionalParams-string
848 */
849 protected function getPreserveGetVars() {
850 $getVars = GeneralUtility::_GET();
851 unset(
852 $getVars['id'],
853 $getVars['no_cache'],
854 $getVars['logintype'],
855 $getVars['redirect_url'],
856 $getVars['cHash'],
857 $getVars[$this->prefixId]
858 );
859 if ($this->conf['preserveGETvars'] === 'all') {
860 $preserveQueryParts = $getVars;
861 } else {
862 $preserveQueryParts = GeneralUtility::trimExplode(',', $this->conf['preserveGETvars']);
863 $preserveQueryParts = GeneralUtility::explodeUrl2Array(implode('=1&', $preserveQueryParts) . '=1', TRUE);
864 $preserveQueryParts = \TYPO3\CMS\Core\Utility\ArrayUtility::intersectRecursive($getVars, $preserveQueryParts);
865 }
866 $parameters = GeneralUtility::implodeArrayForUrl('', $preserveQueryParts);
867 return $parameters;
868 }
869
870 /**
871 * Is used by forgot password - function with md5 option.
872 *
873 * @author Bernhard Kraft
874 * @param int $len Length of new password
875 * @return string New password
876 */
877 protected function generatePassword($len) {
878 $pass = '';
879 while ($len--) {
880 $char = rand(0, 35);
881 if ($char < 10) {
882 $pass .= '' . $char;
883 } else {
884 $pass .= chr($char - 10 + 97);
885 }
886 }
887 return $pass;
888 }
889
890 /**
891 * Returns the header / message value from flexform if present, else from locallang.xlf
892 *
893 * @param string $label label name
894 * @param array $stdWrapArray TS stdWrap array
895 * @return string label text
896 */
897 protected function getDisplayText($label, $stdWrapArray = array()) {
898 $text = $this->flexFormValue($label, 's_messages') ? $this->cObj->stdWrap($this->flexFormValue($label, 's_messages'), $stdWrapArray) : $this->cObj->stdWrap($this->pi_getLL('ll_' . $label, '', TRUE), $stdWrapArray);
899 $replace = $this->getUserFieldMarkers();
900 return strtr($text, $replace);
901 }
902
903 /**
904 * Returns Array of markers filled with user fields
905 *
906 * @return array Marker array
907 */
908 protected function getUserFieldMarkers() {
909 $marker = array();
910 // replace markers with fe_user data
911 if ($GLOBALS['TSFE']->fe_user->user) {
912 // All fields of fe_user will be replaced, scheme is ###FEUSER_FIELDNAME###
913 foreach ($GLOBALS['TSFE']->fe_user->user as $field => $value) {
914 $marker['###FEUSER_' . GeneralUtility::strtoupper($field) . '###'] = $this->cObj->stdWrap($value, $this->conf['userfields.'][$field . '.']);
915 }
916 // Add ###USER### for compatibility
917 $marker['###USER###'] = $marker['###FEUSER_USERNAME###'];
918 }
919 return $marker;
920 }
921
922 /**
923 * Returns a valid and XSS cleaned url for redirect, checked against configuration "allowedRedirectHosts"
924 *
925 * @param string $url
926 * @return string cleaned referer or empty string if not valid
927 */
928 protected function validateRedirectUrl($url) {
929 $url = strval($url);
930 if ($url === '') {
931 return '';
932 }
933 $decodedUrl = rawurldecode($url);
934 $sanitizedUrl = GeneralUtility::removeXSS($decodedUrl);
935 if ($decodedUrl !== $sanitizedUrl || preg_match('#["<>\\\\]+#', $url)) {
936 GeneralUtility::sysLog(sprintf($this->pi_getLL('xssAttackDetected'), $url), 'felogin', GeneralUtility::SYSLOG_SEVERITY_WARNING);
937 return '';
938 }
939 // Validate the URL:
940 if ($this->isRelativeUrl($url) || $this->isInCurrentDomain($url) || $this->isInLocalDomain($url)) {
941 return $url;
942 }
943 // URL is not allowed
944 GeneralUtility::sysLog(sprintf($this->pi_getLL('noValidRedirectUrl'), $url), 'felogin', GeneralUtility::SYSLOG_SEVERITY_WARNING);
945 return '';
946 }
947
948 /**
949 * Determines whether the URL is on the current host
950 * and belongs to the current TYPO3 installation.
951 *
952 * @param string $url URL to be checked
953 * @return bool Whether the URL belongs to the current TYPO3 installation
954 */
955 protected function isInCurrentDomain($url) {
956 return GeneralUtility::isOnCurrentHost($url) && GeneralUtility::isFirstPartOfStr($url, GeneralUtility::getIndpEnv('TYPO3_SITE_URL'));
957 }
958
959 /**
960 * Determines whether the URL matches a domain
961 * in the sys_domain database table.
962 *
963 * @param string $url Absolute URL which needs to be checked
964 * @return bool Whether the URL is considered to be local
965 */
966 protected function isInLocalDomain($url) {
967 $result = FALSE;
968 if (GeneralUtility::isValidUrl($url)) {
969 $parsedUrl = parse_url($url);
970 if ($parsedUrl['scheme'] === 'http' || $parsedUrl['scheme'] === 'https') {
971 $host = $parsedUrl['host'];
972 // Removes the last path segment and slash sequences like /// (if given):
973 $path = preg_replace('#/+[^/]*$#', '', $parsedUrl['path']);
974 $localDomains = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('domainName', 'sys_domain', '1=1' . $this->cObj->enableFields('sys_domain'));
975 if (is_array($localDomains)) {
976 foreach ($localDomains as $localDomain) {
977 // strip trailing slashes (if given)
978 $domainName = rtrim($localDomain['domainName'], '/');
979 if (GeneralUtility::isFirstPartOfStr($host . $path . '/', $domainName . '/')) {
980 $result = TRUE;
981 break;
982 }
983 }
984 }
985 }
986 }
987 return $result;
988 }
989
990 /**
991 * Determines whether the URL is relative to the
992 * current TYPO3 installation.
993 *
994 * @param string $url URL which needs to be checked
995 * @return bool Whether the URL is considered to be relative
996 */
997 protected function isRelativeUrl($url) {
998 $parsedUrl = @parse_url($url);
999 if ($parsedUrl !== FALSE && !isset($parsedUrl['scheme']) && !isset($parsedUrl['host'])) {
1000 // If the relative URL starts with a slash, we need to check if it's within the current site path
1001 return $parsedUrl['path'][0] !== '/' || GeneralUtility::isFirstPartOfStr($parsedUrl['path'], GeneralUtility::getIndpEnv('TYPO3_SITE_PATH'));
1002 }
1003 return FALSE;
1004 }
1005
1006 }