TYPO3 CMS  TYPO3_8-7
FrontendLoginController.php
Go to the documentation of this file.
1 <?php
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 
29 {
35  public $prefixId = 'tx_felogin_pi1';
36 
42  public $extKey = 'felogin';
43 
47  public $pi_checkCHash = false;
48 
52  public $pi_USER_INT_obj = true;
53 
59  protected $userIsLoggedIn;
60 
66  protected $template;
67 
73  protected $redirectUrl;
74 
80  protected $noRedirect = false;
81 
87  protected $logintype;
88 
94  public $spid;
95 
101  public $referer;
102 
111  public function main($content, $conf)
112  {
113  // Loading TypoScript array into object variable:
114  $this->conf = $conf;
115  // Loading default pivars
116  $this->pi_setPiVarDefaults();
117  // Loading language-labels
118  $this->pi_loadLL('EXT:felogin/Resources/Private/Language/locallang.xlf');
119  // Init FlexForm configuration for plugin:
120  $this->pi_initPIflexForm();
122  // Get storage PIDs:
123  if ($this->conf['storagePid']) {
124  if ((int)$this->conf['recursive']) {
125  $this->spid = $this->pi_getPidList($this->conf['storagePid'], (int)$this->conf['recursive']);
126  } else {
127  $this->spid = $this->conf['storagePid'];
128  }
129  } else {
130  throw new \RuntimeException('No storage folder (option storagePid) for frontend users given.', 1450904202);
131  }
132  // GPvars:
133  $this->logintype = GeneralUtility::_GP('logintype');
134  $this->referer = $this->validateRedirectUrl(GeneralUtility::_GP('referer'));
135  $this->noRedirect = $this->piVars['noredirect'] || $this->conf['redirectDisable'];
136  // If config.typolinkLinkAccessRestrictedPages is set, the var is return_url
137  $returnUrl = GeneralUtility::_GP('return_url');
138  if ($returnUrl) {
139  $this->redirectUrl = $returnUrl;
140  } else {
141  $this->redirectUrl = GeneralUtility::_GP('redirect_url');
142  }
143  $this->redirectUrl = $this->validateRedirectUrl($this->redirectUrl);
144  // Get Template
145  $templateFile = $this->conf['templateFile'] ?: 'EXT:felogin/Resources/Private/Templates/FrontendLogin.html';
147  if ($template !== '' && file_exists($template)) {
148  $this->template = file_get_contents($template);
149  }
150  // Is user logged in?
151  $this->userIsLoggedIn = $this->frontendController->loginUser;
152  // Redirect
153  if ($this->conf['redirectMode'] && !$this->conf['redirectDisable'] && !$this->noRedirect) {
154  $redirectUrl = $this->processRedirect();
155  if (!empty($redirectUrl)) {
156  $this->redirectUrl = $this->conf['redirectFirstMethod'] ? array_shift($redirectUrl) : array_pop($redirectUrl);
157  } else {
158  $this->redirectUrl = '';
159  }
160  }
161  // What to display
162  $content = '';
163  if ($this->piVars['forgot'] && $this->conf['showForgotPasswordLink']) {
164  $content .= $this->showForgot();
165  } elseif ($this->piVars['forgothash']) {
166  $content .= $this->changePassword();
167  } else {
168  if ($this->userIsLoggedIn && !$this->logintype) {
169  $content .= $this->showLogout();
170  } else {
171  $content .= $this->showLogin();
172  }
173  }
174  // Process the redirect
175  if (($this->logintype === 'login' || $this->logintype === 'logout') && $this->redirectUrl && !$this->noRedirect) {
176  if (!$this->frontendController->fe_user->isCookieSet() && $this->userIsLoggedIn) {
177  $content .= $this->cObj->stdWrap($this->pi_getLL('cookie_warning'), $this->conf['cookieWarning_stdWrap.']);
178  } else {
179  // Add hook for extra processing before redirect
180  if (
181  isset($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['beforeRedirect']) &&
182  is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['beforeRedirect'])
183  ) {
184  $_params = [
185  'loginType' => $this->logintype,
186  'redirectUrl' => &$this->redirectUrl
187  ];
188  foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['beforeRedirect'] as $_funcRef) {
189  if ($_funcRef) {
190  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
191  }
192  }
193  }
195  }
196  }
197  // Adds hook for processing of extra item markers / special
198  if (
199  isset($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['postProcContent'])
200  && is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['postProcContent'])
201  ) {
202  $_params = [
203  'content' => $content
204  ];
205  foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['postProcContent'] as $_funcRef) {
206  $content = GeneralUtility::callUserFunction($_funcRef, $_params, $this);
207  }
208  }
209  return $this->conf['wrapContentInBaseClass'] ? $this->pi_wrapInBaseClass($content) : $content;
210  }
211 
217  protected function showForgot()
218  {
219  $subpart = $this->templateService->getSubpart($this->template, '###TEMPLATE_FORGOT###');
220  $subpartArray = ($linkpartArray = []);
221  $postData = GeneralUtility::_POST($this->prefixId);
222  if ($postData['forgot_email']) {
223  // Get hashes for compare
224  $postedHash = $postData['forgot_hash'];
225  $hashData = $this->frontendController->fe_user->getKey('ses', 'forgot_hash');
226  if ($postedHash === $hashData['forgot_hash']) {
227  $userTable = $this->frontendController->fe_user->user_table;
228  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($userTable);
229  $queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class));
230  $row = $queryBuilder
231  ->select('*')
232  ->from($userTable)
233  ->where(
234  $queryBuilder->expr()->orX(
235  $queryBuilder->expr()->eq(
236  'email',
237  $queryBuilder->createNamedParameter($this->piVars['forgot_email'], \PDO::PARAM_STR)
238  ),
239  $queryBuilder->expr()->eq(
240  'username',
241  $queryBuilder->createNamedParameter($this->piVars['forgot_email'], \PDO::PARAM_STR)
242  )
243  ),
244  $queryBuilder->expr()->in(
245  'pid',
246  $queryBuilder->createNamedParameter(
247  GeneralUtility::intExplode(',', $this->spid),
248  Connection::PARAM_INT_ARRAY
249  )
250  )
251  )
252  ->execute()
253  ->fetch();
254 
255  $error = null;
256  if ($row) {
257  // Generate an email with the hashed link
258  $error = $this->generateAndSendHash($row);
259  } elseif ($this->conf['exposeNonexistentUserInForgotPasswordDialog']) {
260  $error = $this->pi_getLL('ll_forgot_reset_message_error');
261  }
262  // Generate message
263  if ($error) {
264  $markerArray['###STATUS_MESSAGE###'] = $this->cObj->stdWrap($error, $this->conf['forgotErrorMessage_stdWrap.']);
265  } else {
266  $markerArray['###STATUS_MESSAGE###'] = $this->cObj->stdWrap(
267  $this->pi_getLL('ll_forgot_reset_message_emailSent'),
268  $this->conf['forgotResetMessageEmailSentMessage_stdWrap.']
269  );
270  }
271  $subpartArray['###FORGOT_FORM###'] = '';
272  } else {
273  // Wrong email
274  $markerArray['###STATUS_MESSAGE###'] = $this->getDisplayText('forgot_reset_message', $this->conf['forgotMessage_stdWrap.']);
275  $markerArray['###BACKLINK_LOGIN###'] = '';
276  }
277  } else {
278  $markerArray['###STATUS_MESSAGE###'] = $this->getDisplayText('forgot_reset_message', $this->conf['forgotMessage_stdWrap.']);
279  $markerArray['###BACKLINK_LOGIN###'] = '';
280  }
281  $markerArray['###BACKLINK_LOGIN###'] = $this->getPageLink(htmlspecialchars($this->pi_getLL('ll_forgot_header_backToLogin')), []);
282  $markerArray['###STATUS_HEADER###'] = $this->getDisplayText('forgot_header', $this->conf['forgotHeader_stdWrap.']);
283  $markerArray['###LEGEND###'] = htmlspecialchars($this->pi_getLL('legend', $this->pi_getLL('reset_password')));
284  $markerArray['###ACTION_URI###'] = $this->getPageLink('', [$this->prefixId . '[forgot]' => 1], true);
285  $markerArray['###EMAIL_LABEL###'] = htmlspecialchars($this->pi_getLL('your_email'));
286  $markerArray['###FORGOT_PASSWORD_ENTEREMAIL###'] = htmlspecialchars($this->pi_getLL('forgot_password_enterEmail'));
287  $markerArray['###FORGOT_EMAIL###'] = $this->prefixId . '[forgot_email]';
288  $markerArray['###SEND_PASSWORD###'] = htmlspecialchars($this->pi_getLL('reset_password'));
289  $markerArray['###DATA_LABEL###'] = htmlspecialchars($this->pi_getLL('ll_enter_your_data'));
290  $markerArray = array_merge($markerArray, $this->getUserFieldMarkers());
291  // Generate hash
292  $hash = md5($this->generatePassword(3));
293  $markerArray['###FORGOTHASH###'] = $hash;
294  // Set hash in feuser session
295  $this->frontendController->fe_user->setKey('ses', 'forgot_hash', ['forgot_hash' => $hash]);
296  return $this->templateService->substituteMarkerArrayCached($subpart, $markerArray, $subpartArray, $linkpartArray);
297  }
298 
305  protected function changePassword()
306  {
307  $subpartArray = ($linkpartArray = []);
308  $done = false;
309  $minLength = (int)$this->conf['newPasswordMinLength'] ?: 6;
310  $subpart = $this->templateService->getSubpart($this->template, '###TEMPLATE_CHANGEPASSWORD###');
311  $markerArray['###STATUS_HEADER###'] = $this->getDisplayText('change_password_header', $this->conf['changePasswordHeader_stdWrap.']);
312  $markerArray['###STATUS_MESSAGE###'] = sprintf($this->getDisplayText(
313  'change_password_message',
314  $this->conf['changePasswordMessage_stdWrap.']
315  ), $minLength);
316 
317  $markerArray['###BACKLINK_LOGIN###'] = '';
318  $uid = $this->piVars['user'];
319  $piHash = $this->piVars['forgothash'];
320  $hash = explode('|', rawurldecode($piHash));
321  if ((int)$uid === 0) {
322  $markerArray['###STATUS_MESSAGE###'] = $this->getDisplayText(
323  'change_password_notvalid_message',
324  $this->conf['changePasswordNotValidMessage_stdWrap.']
325  );
326  $subpartArray['###CHANGEPASSWORD_FORM###'] = '';
327  } else {
328  $user = $this->pi_getRecord('fe_users', (int)$uid);
329  $userHash = $user['felogin_forgotHash'];
330  $compareHash = explode('|', $userHash);
331  if (strlen($compareHash[1]) === 40) {
332  $hashEquals = hash_equals($compareHash[1], GeneralUtility::hmac((string)$hash[1]));
333  } else {
334  // backward-compatibility for previous MD5 hashes
335  $hashEquals = hash_equals($compareHash[1], md5($hash[1]));
336  }
337  if (!$compareHash || !$compareHash[1] || $compareHash[0] < time() || !hash_equals($compareHash[0], $hash[0]) || !$hashEquals) {
338  $markerArray['###STATUS_MESSAGE###'] = $this->getDisplayText(
339  'change_password_notvalid_message',
340  $this->conf['changePasswordNotValidMessage_stdWrap.']
341  );
342  $subpartArray['###CHANGEPASSWORD_FORM###'] = '';
343  } else {
344  // All is fine, continue with new password
345  $postData = GeneralUtility::_POST($this->prefixId);
346  if (isset($postData['changepasswordsubmit'])) {
347  if (strlen($postData['password1']) < $minLength) {
348  $markerArray['###STATUS_MESSAGE###'] = sprintf(
349  $this->getDisplayText(
350  'change_password_tooshort_message',
351  $this->conf['changePasswordTooShortMessage_stdWrap.']
352  ),
353  $minLength
354  );
355  } elseif ($postData['password1'] != $postData['password2']) {
356  $markerArray['###STATUS_MESSAGE###'] = sprintf(
357  $this->getDisplayText(
358  'change_password_notequal_message',
359  $this->conf['changePasswordNotEqualMessage_stdWrap.']
360  ),
361  $minLength
362  );
363  } else {
364  $newPass = $postData['password1'];
365  if ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['password_changed']) {
366  $_params = [
367  'user' => $user,
368  'newPassword' => $newPass,
369  'newPasswordUnencrypted' => $newPass
370  ];
371  foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['password_changed'] as $_funcRef) {
372  if ($_funcRef) {
373  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
374  }
375  }
376  $newPass = $_params['newPassword'];
377  }
378 
379  // Save new password and clear DB-hash
380  $userTable = $this->frontendController->fe_user->user_table;
381  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($userTable);
382  $queryBuilder->getRestrictions()->removeAll();
383  $queryBuilder->update($userTable)
384  ->set('password', $newPass)
385  ->set('felogin_forgotHash', '')
386  ->set('tstamp', (int)$GLOBALS['EXEC_TIME'])
387  ->where(
388  $queryBuilder->expr()->eq(
389  'uid',
390  $queryBuilder->createNamedParameter($user['uid'], \PDO::PARAM_INT)
391  )
392  )
393  ->execute();
394  $this->invalidateUserSessions((int)$user['uid']);
395 
396  $markerArray['###STATUS_MESSAGE###'] = $this->getDisplayText(
397  'change_password_done_message',
398  $this->conf['changePasswordDoneMessage_stdWrap.']
399  );
400  $done = true;
401  $subpartArray['###CHANGEPASSWORD_FORM###'] = '';
402  $markerArray['###BACKLINK_LOGIN###'] = $this->getPageLink(
403  htmlspecialchars($this->pi_getLL('ll_forgot_header_backToLogin')),
404  [$this->prefixId . '[redirectReferrer]' => 'off']
405  );
406  }
407  }
408  if (!$done) {
409  // Change password form
410  $markerArray['###ACTION_URI###'] = $this->getPageLink('', [
411  $this->prefixId . '[user]' => $user['uid'],
412  $this->prefixId . '[forgothash]' => $piHash
413  ], true);
414  $markerArray['###LEGEND###'] = htmlspecialchars($this->pi_getLL('change_password'));
415  $markerArray['###NEWPASSWORD1_LABEL###'] = htmlspecialchars($this->pi_getLL('newpassword_label1'));
416  $markerArray['###NEWPASSWORD2_LABEL###'] = htmlspecialchars($this->pi_getLL('newpassword_label2'));
417  $markerArray['###NEWPASSWORD1###'] = $this->prefixId . '[password1]';
418  $markerArray['###NEWPASSWORD2###'] = $this->prefixId . '[password2]';
419  $markerArray['###STORAGE_PID###'] = $this->spid;
420  $markerArray['###SEND_PASSWORD###'] = htmlspecialchars($this->pi_getLL('change_password'));
421  $markerArray['###FORGOTHASH###'] = $piHash;
422  }
423  }
424  }
425  return $this->templateService->substituteMarkerArrayCached($subpart, $markerArray, $subpartArray, $linkpartArray);
426  }
427 
434  protected function generateAndSendHash($user)
435  {
436  $hours = (int)$this->conf['forgotLinkHashValidTime'] > 0 ? (int)$this->conf['forgotLinkHashValidTime'] : 24;
437  $validEnd = time() + 3600 * $hours;
438  $validEndString = date($this->conf['dateFormat'], $validEnd);
439  $hash = md5(GeneralUtility::makeInstance(Random::class)->generateRandomBytes(64));
440  $randHash = $validEnd . '|' . $hash;
441  $randHashDB = $validEnd . '|' . GeneralUtility::hmac($hash);
442 
443  // Write hash to DB
444  $userTable = $this->frontendController->fe_user->user_table;
445  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($userTable);
446  $queryBuilder->getRestrictions()->removeAll();
447  $queryBuilder->update($userTable)
448  ->set('felogin_forgotHash', $randHashDB)
449  ->where(
450  $queryBuilder->expr()->eq(
451  'uid',
452  $queryBuilder->createNamedParameter($user['uid'], \PDO::PARAM_INT)
453  )
454  )
455  ->execute();
456 
457  // Send hashlink to user
458  $this->conf['linkPrefix'] = -1;
459  $isAbsRefPrefix = !empty($this->frontendController->absRefPrefix);
460  $isBaseURL = !empty($this->frontendController->baseUrl);
461  $isFeloginBaseURL = !empty($this->conf['feloginBaseURL']);
462  $link = $this->pi_getPageLink($this->frontendController->id, '', [
463  rawurlencode($this->prefixId . '[user]') => $user['uid'],
464  rawurlencode($this->prefixId . '[forgothash]') => $randHash
465  ]);
466  // Prefix link if necessary
467  if ($isFeloginBaseURL) {
468  // First priority, use specific base URL
469  // "absRefPrefix" must be removed first, otherwise URL will be prepended twice
470  if ($isAbsRefPrefix) {
471  $link = substr($link, strlen($this->frontendController->absRefPrefix));
472  }
473  $link = $this->conf['feloginBaseURL'] . $link;
474  } elseif ($isAbsRefPrefix) {
475  // Second priority
476  // absRefPrefix must not necessarily contain a hostname and URL scheme, so add it if needed
477  $link = GeneralUtility::locationHeaderUrl($link);
478  } elseif ($isBaseURL) {
479  // Third priority
480  // Add the global base URL to the link
481  $link = $this->frontendController->baseUrlWrap($link);
482  } else {
483  // No prefix is set, return the error
484  return $this->pi_getLL('ll_change_password_nolinkprefix_message');
485  }
486  $msg = sprintf($this->pi_getLL('ll_forgot_validate_reset_password'), $user['username'], $link, $validEndString);
487  // Add hook for extra processing of mail message
488  if (
489  isset($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['forgotPasswordMail'])
490  && is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['forgotPasswordMail'])
491  ) {
492  $params = [
493  'message' => &$msg,
494  'user' => &$user
495  ];
496  foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['forgotPasswordMail'] as $reference) {
497  if ($reference) {
498  GeneralUtility::callUserFunction($reference, $params, $this);
499  }
500  }
501  }
502  if ($user['email']) {
503  $this->cObj->sendNotifyEmail($msg, $user['email'], '', $this->conf['email_from'], $this->conf['email_fromName'], $this->conf['replyTo']);
504  }
505 
506  return '';
507  }
508 
514  protected function showLogout()
515  {
516  $subpart = $this->templateService->getSubpart($this->template, '###TEMPLATE_LOGOUT###');
517  $subpartArray = ($linkpartArray = []);
518  $markerArray['###STATUS_HEADER###'] = $this->getDisplayText('status_header', $this->conf['logoutHeader_stdWrap.']);
519  $markerArray['###STATUS_MESSAGE###'] = $this->getDisplayText('status_message', $this->conf['logoutMessage_stdWrap.']);
520  $this->cObj->stdWrap($this->flexFormValue('message', 's_status'), $this->conf['logoutMessage_stdWrap.']);
521  $markerArray['###LEGEND###'] = htmlspecialchars($this->pi_getLL('logout'));
522  $markerArray['###ACTION_URI###'] = $this->getPageLink('', [], true);
523  $markerArray['###LOGOUT_LABEL###'] = htmlspecialchars($this->pi_getLL('logout'));
524  $markerArray['###NAME###'] = htmlspecialchars($this->frontendController->fe_user->user['name']);
525  $markerArray['###STORAGE_PID###'] = $this->spid;
526  $markerArray['###USERNAME###'] = htmlspecialchars($this->frontendController->fe_user->user['username']);
527  $markerArray['###USERNAME_LABEL###'] = htmlspecialchars($this->pi_getLL('username'));
528  $markerArray['###NOREDIRECT###'] = $this->noRedirect ? '1' : '0';
529  $markerArray['###PREFIXID###'] = $this->prefixId;
530  $markerArray = array_merge($markerArray, $this->getUserFieldMarkers());
531  if ($this->redirectUrl) {
532  // Use redirectUrl for action tag because of possible access restricted pages
533  $markerArray['###ACTION_URI###'] = htmlspecialchars($this->redirectUrl);
534  $this->redirectUrl = '';
535  }
536  return $this->templateService->substituteMarkerArrayCached($subpart, $markerArray, $subpartArray, $linkpartArray);
537  }
538 
544  protected function showLogin()
545  {
546  $subpart = $this->templateService->getSubpart($this->template, '###TEMPLATE_LOGIN###');
547  $subpartArray = ($linkpartArray = ($markerArray = []));
548  $gpRedirectUrl = '';
549  $markerArray['###LEGEND###'] = htmlspecialchars($this->pi_getLL('oLabel_header_welcome'));
550  if ($this->logintype === 'login') {
551  if ($this->userIsLoggedIn) {
552  // login success
553  $markerArray['###STATUS_HEADER###'] = $this->getDisplayText('success_header', $this->conf['successHeader_stdWrap.']);
554  $markerArray['###STATUS_MESSAGE###'] = $this->getDisplayText('success_message', $this->conf['successMessage_stdWrap.']);
555  $markerArray = array_merge($markerArray, $this->getUserFieldMarkers());
556  $subpartArray['###LOGIN_FORM###'] = '';
557  // Hook for general actions after after login has been confirmed (by Thomas Danzl <thomas@danzl.org>)
558  if ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['login_confirmed']) {
559  $_params = [];
560  foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['login_confirmed'] as $_funcRef) {
561  if ($_funcRef) {
562  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
563  }
564  }
565  }
566  // show logout form directly
567  if ($this->conf['showLogoutFormAfterLogin']) {
568  $this->redirectUrl = '';
569  return $this->showLogout();
570  }
571  } else {
572  // Hook for general actions on login error
573  if (
574  isset($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['login_error'])
575  && is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['login_error'])
576  ) {
577  $params = [];
578  foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['login_error'] as $funcRef) {
579  if ($funcRef) {
580  GeneralUtility::callUserFunction($funcRef, $params, $this);
581  }
582  }
583  }
584  // login error
585  $markerArray['###STATUS_HEADER###'] = $this->getDisplayText('error_header', $this->conf['errorHeader_stdWrap.']);
586  $markerArray['###STATUS_MESSAGE###'] = $this->getDisplayText('error_message', $this->conf['errorMessage_stdWrap.']);
587  $gpRedirectUrl = GeneralUtility::_GP('redirect_url');
588  }
589  } else {
590  if ($this->logintype === 'logout') {
591  // login form after logout
592  $markerArray['###STATUS_HEADER###'] = $this->getDisplayText('logout_header', $this->conf['logoutHeader_stdWrap.']);
593  $markerArray['###STATUS_MESSAGE###'] = $this->getDisplayText('logout_message', $this->conf['logoutMessage_stdWrap.']);
594  } else {
595  // login form
596  $markerArray['###STATUS_HEADER###'] = $this->getDisplayText('welcome_header', $this->conf['welcomeHeader_stdWrap.']);
597  $markerArray['###STATUS_MESSAGE###'] = $this->getDisplayText('welcome_message', $this->conf['welcomeMessage_stdWrap.']);
598  }
599  }
600 
601  // This hook allows to call User JS functions.
602  // The methods should also set the required JS functions to get included
603  $onSubmit = '';
604  $extraHidden = '';
605  $onSubmitAr = [];
606  $extraHiddenAr = [];
607  // Check for referer redirect method. if present, save referer in form field
608  if (GeneralUtility::inList($this->conf['redirectMode'], 'referer') || GeneralUtility::inList($this->conf['redirectMode'], 'refererDomains')) {
609  $referer = $this->referer ? $this->referer : GeneralUtility::getIndpEnv('HTTP_REFERER');
610  if ($referer) {
611  $extraHiddenAr[] = '<input type="hidden" name="referer" value="' . htmlspecialchars($referer) . '" />';
612  if ($this->piVars['redirectReferrer'] === 'off') {
613  $extraHiddenAr[] = '<input type="hidden" name="' . $this->prefixId . '[redirectReferrer]" value="off" />';
614  }
615  }
616  }
617  if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['loginFormOnSubmitFuncs'])) {
618  $_params = [];
619  foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['loginFormOnSubmitFuncs'] as $funcRef) {
620  list($onSub, $hid) = GeneralUtility::callUserFunction($funcRef, $_params, $this);
621  $onSubmitAr[] = $onSub;
622  $extraHiddenAr[] = $hid;
623  }
624  }
625  if (!empty($onSubmitAr)) {
626  $onSubmit = implode('; ', $onSubmitAr) . '; return true;';
627  }
628  if (!empty($extraHiddenAr)) {
629  $extraHidden = implode(LF, $extraHiddenAr);
630  }
631  if (!$gpRedirectUrl && $this->redirectUrl) {
632  $gpRedirectUrl = $this->redirectUrl;
633  }
634  // Login form
635  $markerArray['###ACTION_URI###'] = $this->getPageLink('', [], true);
636  // Used by kb_md5fepw extension...
637  $markerArray['###EXTRA_HIDDEN###'] = $extraHidden;
638  $markerArray['###LEGEND###'] = htmlspecialchars($this->pi_getLL('login'));
639  $markerArray['###LOGIN_LABEL###'] = htmlspecialchars($this->pi_getLL('login'));
640  // Used by kb_md5fepw extension...
641  $markerArray['###ON_SUBMIT###'] = $onSubmit;
642  $markerArray['###PASSWORD_LABEL###'] = htmlspecialchars($this->pi_getLL('password'));
643  $markerArray['###STORAGE_PID###'] = $this->spid;
644  $markerArray['###USERNAME_LABEL###'] = htmlspecialchars($this->pi_getLL('username'));
645  $markerArray['###REDIRECT_URL###'] = htmlspecialchars($gpRedirectUrl);
646  $markerArray['###NOREDIRECT###'] = $this->noRedirect ? '1' : '0';
647  $markerArray['###PREFIXID###'] = $this->prefixId;
648  $markerArray = array_merge($markerArray, $this->getUserFieldMarkers());
649  if ($this->conf['showForgotPasswordLink']) {
650  $linkpartArray['###FORGOT_PASSWORD_LINK###'] = explode('|', $this->getPageLink('|', [$this->prefixId . '[forgot]' => 1]));
651  $markerArray['###FORGOT_PASSWORD###'] = htmlspecialchars($this->pi_getLL('ll_forgot_header'));
652  } else {
653  $subpartArray['###FORGOTP_VALID###'] = '';
654  }
655  // The permanent login checkbox should only be shown if permalogin is not deactivated (-1),
656  // not forced to be always active (2) and lifetime is greater than 0
657  $permalogin = (int)$GLOBALS['TYPO3_CONF_VARS']['FE']['permalogin'];
658  if (
659  $this->conf['showPermaLogin']
660  && ($permalogin === 0 || $permalogin === 1)
661  && $GLOBALS['TYPO3_CONF_VARS']['FE']['lifetime'] > 0
662  ) {
663  $markerArray['###PERMALOGIN###'] = htmlspecialchars($this->pi_getLL('permalogin'));
664  if ($permalogin === 1) {
665  $markerArray['###PERMALOGIN_HIDDENFIELD_ATTRIBUTES###'] = 'disabled="disabled"';
666  $markerArray['###PERMALOGIN_CHECKBOX_ATTRIBUTES###'] = 'checked="checked"';
667  } else {
668  $markerArray['###PERMALOGIN_HIDDENFIELD_ATTRIBUTES###'] = '';
669  $markerArray['###PERMALOGIN_CHECKBOX_ATTRIBUTES###'] = '';
670  }
671  } else {
672  $subpartArray['###PERMALOGIN_VALID###'] = '';
673  }
674  return $this->templateService->substituteMarkerArrayCached($subpart, $markerArray, $subpartArray, $linkpartArray);
675  }
676 
682  protected function processRedirect()
683  {
684  $redirect_url = [];
685  if ($this->conf['redirectMode']) {
686  $redirectMethods = GeneralUtility::trimExplode(',', $this->conf['redirectMode'], true);
687  foreach ($redirectMethods as $redirMethod) {
688  if ($this->frontendController->loginUser && $this->logintype === 'login') {
689  // Logintype is needed because the login-page wouldn't be accessible anymore after a login (would always redirect)
690  switch ($redirMethod) {
691  case 'groupLogin':
692  // taken from dkd_redirect_at_login written by Ingmar Schlecht; database-field changed
693  $groupData = $this->frontendController->fe_user->groupData;
694  if (!empty($groupData['uid'])) {
695 
696  // take the first group with a redirect page
697  $userGroupTable = $this->frontendController->fe_user->usergroup_table;
698  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($userGroupTable);
699  $queryBuilder->getRestrictions()->removeAll();
700  $row = $queryBuilder
701  ->select('felogin_redirectPid')
702  ->from($userGroupTable)
703  ->where(
704  $queryBuilder->expr()->neq(
705  'felogin_redirectPid',
706  $queryBuilder->createNamedParameter('', \PDO::PARAM_STR)
707  ),
708  $queryBuilder->expr()->in(
709  'uid',
710  $queryBuilder->createNamedParameter(
711  $groupData['uid'],
712  Connection::PARAM_INT_ARRAY
713  )
714  )
715  )
716  ->execute()
717  ->fetch();
718 
719  if ($row) {
720  $redirect_url[] = $this->pi_getPageLink($row['felogin_redirectPid']);
721  }
722  }
723  break;
724  case 'userLogin':
725 
726  $userTable = $this->frontendController->fe_user->user_table;
727  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($userTable);
728  $queryBuilder->getRestrictions()->removeAll();
729  $row = $queryBuilder
730  ->select('felogin_redirectPid')
731  ->from($userTable)
732  ->where(
733  $queryBuilder->expr()->neq(
734  'felogin_redirectPid',
735  $queryBuilder->createNamedParameter('', \PDO::PARAM_STR)
736  ),
737  $queryBuilder->expr()->eq(
738  $this->frontendController->fe_user->userid_column,
739  $queryBuilder->createNamedParameter(
740  $this->frontendController->fe_user->user['uid'],
741  \PDO::PARAM_INT
742  )
743  )
744  )
745  ->execute()
746  ->fetch();
747 
748  if ($row) {
749  $redirect_url[] = $this->pi_getPageLink($row['felogin_redirectPid']);
750  }
751 
752  break;
753  case 'login':
754  if ($this->conf['redirectPageLogin']) {
755  $redirect_url[] = $this->pi_getPageLink((int)$this->conf['redirectPageLogin']);
756  }
757  break;
758  case 'getpost':
759  $redirect_url[] = $this->redirectUrl;
760  break;
761  case 'referer':
762  // Avoid redirect when logging in after changing password
763  if ($this->piVars['redirectReferrer'] !== 'off') {
764  // Avoid forced logout, when trying to login immediately after a logout
765  $redirect_url[] = preg_replace('/[&?]logintype=[a-z]+/', '', $this->referer);
766  }
767  break;
768  case 'refererDomains':
769  // Auto redirect.
770  // Feature to redirect to the page where the user came from (HTTP_REFERER).
771  // Allowed domains to redirect to, can be configured with plugin.tx_felogin_pi1.domains
772  // Thanks to plan2.net / Martin Kutschker for implementing this feature.
773  // also avoid redirect when logging in after changing password
774  if ($this->conf['domains'] && $this->piVars['redirectReferrer'] !== 'off') {
775  $url = $this->referer;
776  // Is referring url allowed to redirect?
777  $match = [];
778  if (preg_match('#^http://([[:alnum:]._-]+)/#', $url, $match)) {
779  $redirect_domain = $match[1];
780  $found = false;
781  foreach (GeneralUtility::trimExplode(',', $this->conf['domains'], true) as $d) {
782  if (preg_match('/(?:^|\\.)' . $d . '$/', $redirect_domain)) {
783  $found = true;
784  break;
785  }
786  }
787  if (!$found) {
788  $url = '';
789  }
790  }
791  // Avoid forced logout, when trying to login immediately after a logout
792  if ($url) {
793  $redirect_url[] = preg_replace('/[&?]logintype=[a-z]+/', '', $url);
794  }
795  }
796  break;
797  }
798  } elseif ($this->logintype === 'login') {
799  // after login-error
800  switch ($redirMethod) {
801  case 'loginError':
802  if ($this->conf['redirectPageLoginError']) {
803  $redirect_url[] = $this->pi_getPageLink((int)$this->conf['redirectPageLoginError']);
804  }
805  break;
806  }
807  } elseif ($this->logintype == '' && $redirMethod === 'login' && $this->conf['redirectPageLogin']) {
808  // If login and page not accessible
809  $this->cObj->typoLink('', [
810  'parameter' => $this->conf['redirectPageLogin'],
811  'linkAccessRestrictedPages' => true
812  ]);
813  $redirect_url[] = $this->cObj->lastTypoLinkUrl;
814  } elseif ($this->logintype == '' && $redirMethod === 'logout' && $this->conf['redirectPageLogout'] && $this->frontendController->loginUser) {
815  // If logout and page not accessible
816  $redirect_url[] = $this->pi_getPageLink((int)$this->conf['redirectPageLogout']);
817  } elseif ($this->logintype === 'logout') {
818  // after logout
819  // Hook for general actions after after logout has been confirmed
820  if ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['logout_confirmed']) {
821  $_params = [];
822  foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['logout_confirmed'] as $_funcRef) {
823  if ($_funcRef) {
824  GeneralUtility::callUserFunction($_funcRef, $_params, $this);
825  }
826  }
827  }
828  switch ($redirMethod) {
829  case 'logout':
830  if ($this->conf['redirectPageLogout']) {
831  $redirect_url[] = $this->pi_getPageLink((int)$this->conf['redirectPageLogout']);
832  }
833  break;
834  }
835  } else {
836  // not logged in
837  // Placeholder for maybe future options
838  switch ($redirMethod) {
839  case 'getpost':
840  // Preserve the get/post value
841  $redirect_url[] = $this->redirectUrl;
842  break;
843  }
844  }
845  }
846  }
847  // Remove empty values, but keep "0" as value (that's why "strlen" is used as second parameter)
848  if (!empty($redirect_url)) {
849  return array_filter($redirect_url, 'strlen');
850  }
851  return [];
852  }
853 
857  protected function mergeflexFormValuesIntoConf()
858  {
859  $flex = [];
860  if ($this->flexFormValue('showForgotPassword', 'sDEF')) {
861  $flex['showForgotPasswordLink'] = $this->flexFormValue('showForgotPassword', 'sDEF');
862  }
863  if ($this->flexFormValue('showPermaLogin', 'sDEF')) {
864  $flex['showPermaLogin'] = $this->flexFormValue('showPermaLogin', 'sDEF');
865  }
866  if ($this->flexFormValue('showLogoutFormAfterLogin', 'sDEF')) {
867  $flex['showLogoutFormAfterLogin'] = $this->flexFormValue('showLogoutFormAfterLogin', 'sDEF');
868  }
869  if ($this->flexFormValue('pages', 'sDEF')) {
870  $flex['pages'] = $this->flexFormValue('pages', 'sDEF');
871  }
872  if ($this->flexFormValue('recursive', 'sDEF')) {
873  $flex['recursive'] = $this->flexFormValue('recursive', 'sDEF');
874  }
875  if ($this->flexFormValue('redirectMode', 's_redirect')) {
876  $flex['redirectMode'] = $this->flexFormValue('redirectMode', 's_redirect');
877  }
878  if ($this->flexFormValue('redirectFirstMethod', 's_redirect')) {
879  $flex['redirectFirstMethod'] = $this->flexFormValue('redirectFirstMethod', 's_redirect');
880  }
881  if ($this->flexFormValue('redirectDisable', 's_redirect')) {
882  $flex['redirectDisable'] = $this->flexFormValue('redirectDisable', 's_redirect');
883  }
884  if ($this->flexFormValue('redirectPageLogin', 's_redirect')) {
885  $flex['redirectPageLogin'] = $this->flexFormValue('redirectPageLogin', 's_redirect');
886  }
887  if ($this->flexFormValue('redirectPageLoginError', 's_redirect')) {
888  $flex['redirectPageLoginError'] = $this->flexFormValue('redirectPageLoginError', 's_redirect');
889  }
890  if ($this->flexFormValue('redirectPageLogout', 's_redirect')) {
891  $flex['redirectPageLogout'] = $this->flexFormValue('redirectPageLogout', 's_redirect');
892  }
893  $pid = $flex['pages'] ? $this->pi_getPidList($flex['pages'], $flex['recursive']) : 0;
894  if ($pid > 0) {
895  $flex['storagePid'] = $pid;
896  }
897  $this->conf = array_merge($this->conf, $flex);
898  }
899 
907  protected function flexFormValue($var, $sheet)
908  {
909  return $this->pi_getFFvalue($this->cObj->data['pi_flexform'], $var, $sheet);
910  }
911 
920  protected function getPageLink($label, $piVars, $returnUrl = false)
921  {
922  $additionalParams = '';
923  if (!empty($piVars)) {
924  foreach ($piVars as $key => $val) {
925  $additionalParams .= '&' . $key . '=' . $val;
926  }
927  }
928  // Should GETvars be preserved?
929  if ($this->conf['preserveGETvars']) {
930  $additionalParams .= $this->getPreserveGetVars();
931  }
932  $this->conf['linkConfig.']['parameter'] = $this->frontendController->id;
933  if ($additionalParams) {
934  $this->conf['linkConfig.']['additionalParams'] = $additionalParams;
935  }
936  if ($returnUrl) {
937  return htmlspecialchars($this->cObj->typoLink_URL($this->conf['linkConfig.']));
938  }
939  return $this->cObj->typoLink($label, $this->conf['linkConfig.']);
940  }
941 
950  protected function getPreserveGetVars()
951  {
952  $getVars = GeneralUtility::_GET();
953  unset(
954  $getVars['id'],
955  $getVars['no_cache'],
956  $getVars['logintype'],
957  $getVars['redirect_url'],
958  $getVars['cHash'],
959  $getVars[$this->prefixId]
960  );
961  if ($this->conf['preserveGETvars'] === 'all') {
962  $preserveQueryParts = $getVars;
963  } else {
964  $preserveQueryParts = GeneralUtility::trimExplode(',', $this->conf['preserveGETvars']);
965  $preserveQueryParts = GeneralUtility::explodeUrl2Array(implode('=1&', $preserveQueryParts) . '=1', true);
966  $preserveQueryParts = \TYPO3\CMS\Core\Utility\ArrayUtility::intersectRecursive($getVars, $preserveQueryParts);
967  }
968  $parameters = GeneralUtility::implodeArrayForUrl('', $preserveQueryParts);
969  return $parameters;
970  }
971 
977  protected function generatePassword($len)
978  {
979  $pass = '';
980  while ($len--) {
981  $char = rand(0, 35);
982  if ($char < 10) {
983  $pass .= '' . $char;
984  } else {
985  $pass .= chr($char - 10 + 97);
986  }
987  }
988  return $pass;
989  }
990 
998  protected function getDisplayText($label, $stdWrapArray = [])
999  {
1000  $text = $this->flexFormValue($label, 's_messages') ? $this->cObj->stdWrap($this->flexFormValue($label, 's_messages'), $stdWrapArray) : $this->cObj->stdWrap($this->pi_getLL('ll_' . $label), $stdWrapArray);
1001  $replace = $this->getUserFieldMarkers();
1002  return strtr($text, $replace);
1003  }
1004 
1010  protected function getUserFieldMarkers()
1011  {
1012  $marker = [];
1013  // replace markers with fe_user data
1014  if ($this->frontendController->fe_user->user) {
1015  // All fields of fe_user will be replaced, scheme is ###FEUSER_FIELDNAME###
1016  foreach ($this->frontendController->fe_user->user as $field => $value) {
1017  $conf = $this->conf['userfields.'][$field . '.'] ?? [];
1018  $conf = array_replace_recursive(['htmlSpecialChars' => '1'], $conf);
1019  $marker['###FEUSER_' . strtoupper($field) . '###'] = $this->cObj->stdWrap($value, $conf);
1020  }
1021  // Add ###USER### for compatibility
1022  $marker['###USER###'] = $marker['###FEUSER_USERNAME###'];
1023  }
1024  return $marker;
1025  }
1026 
1033  protected function validateRedirectUrl($url)
1034  {
1035  $url = strval($url);
1036  if ($url === '') {
1037  return '';
1038  }
1039  // Validate the URL:
1040  if ($this->isRelativeUrl($url) || $this->isInCurrentDomain($url) || $this->isInLocalDomain($url)) {
1041  return $url;
1042  }
1043  // URL is not allowed
1044  GeneralUtility::sysLog(sprintf($this->pi_getLL('noValidRedirectUrl'), $url), 'felogin', GeneralUtility::SYSLOG_SEVERITY_WARNING);
1045  return '';
1046  }
1047 
1055  protected function isInCurrentDomain($url)
1056  {
1057  $urlWithoutSchema = preg_replace('#^https?://#', '', $url);
1058  $siteUrlWithoutSchema = preg_replace('#^https?://#', '', GeneralUtility::getIndpEnv('TYPO3_SITE_URL'));
1059  return strpos($urlWithoutSchema . '/', GeneralUtility::getIndpEnv('HTTP_HOST') . '/') === 0
1060  && strpos($urlWithoutSchema, $siteUrlWithoutSchema) === 0;
1061  }
1062 
1070  protected function isInLocalDomain($url)
1071  {
1072  $result = false;
1073  if (GeneralUtility::isValidUrl($url)) {
1074  $parsedUrl = parse_url($url);
1075  if ($parsedUrl['scheme'] === 'http' || $parsedUrl['scheme'] === 'https') {
1076  $host = $parsedUrl['host'];
1077  // Removes the last path segment and slash sequences like /// (if given):
1078  $path = preg_replace('#/+[^/]*$#', '', $parsedUrl['path']);
1079 
1080  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_domain');
1081  $queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class));
1082  $localDomains = $queryBuilder->select('domainName')
1083  ->from('sys_domain')
1084  ->execute()
1085  ->fetchAll();
1086 
1087  if (is_array($localDomains)) {
1088  foreach ($localDomains as $localDomain) {
1089  // strip trailing slashes (if given)
1090  $domainName = rtrim($localDomain['domainName'], '/');
1091  if (GeneralUtility::isFirstPartOfStr($host . $path . '/', $domainName . '/')) {
1092  $result = true;
1093  break;
1094  }
1095  }
1096  }
1097  }
1098  }
1099  return $result;
1100  }
1101 
1109  protected function isRelativeUrl($url)
1110  {
1111  $url = GeneralUtility::sanitizeLocalUrl($url);
1112  if (!empty($url)) {
1113  $parsedUrl = @parse_url($url);
1114  if ($parsedUrl !== false && !isset($parsedUrl['scheme']) && !isset($parsedUrl['host'])) {
1115  // If the relative URL starts with a slash, we need to check if it's within the current site path
1116  return $parsedUrl['path'][0] !== '/' || GeneralUtility::isFirstPartOfStr($parsedUrl['path'], GeneralUtility::getIndpEnv('TYPO3_SITE_PATH'));
1117  }
1118  }
1119  return false;
1120  }
1121 
1127  protected function invalidateUserSessions(int $userId)
1128  {
1129  $sessionManager = GeneralUtility::makeInstance(SessionManager::class);
1130  $sessionBackend = $sessionManager->getSessionBackend('FE');
1131  $sessionManager->invalidateAllSessionsByUserId($sessionBackend, $userId, $this->frontendController->fe_user);
1132  }
1133 
1139  protected function getTypoScriptFrontendController()
1140  {
1141  return $GLOBALS['TSFE'];
1142  }
1143 }
pi_getFFvalue($T3FlexForm_array, $fieldName, $sheet='sDEF', $lang='lDEF', $value='vDEF')
static intExplode($delimiter, $string, $removeEmptyValues=false, $limit=0)
static isFirstPartOfStr($str, $partStr)
static callUserFunction($funcName, &$params, &$ref, $_='', $errorMode=0)
static intersectRecursive(array $source, array $mask=[])
pi_getRecord($table, $uid, $checkPage=false)
static hmac($input, $additionalSecret='')
static getFileAbsFileName($filename, $_=null, $_2=null)
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
static makeInstance($className,... $constructorArguments)
static implodeArrayForUrl($name, array $theArray, $str='', $skipBlank=false, $rawurlencodeParamName=false)
static explodeUrl2Array($string, $multidim=false)
pi_getPageLink($id, $target='', $urlParameters=[])
static redirect($url, $httpStatus=self::HTTP_STATUS_303)
pi_getLL($key, $alternativeLabel='', $hsc=false)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']