vendor/contao/core-bundle/src/Resources/contao/modules/ModulePassword.php line 53

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of Contao.
  4.  *
  5.  * (c) Leo Feyer
  6.  *
  7.  * @license LGPL-3.0-or-later
  8.  */
  9. namespace Contao;
  10. /**
  11.  * Front end module "lost password".
  12.  *
  13.  * @todo Rename to ModuleLostPassword in Contao 5.0
  14.  */
  15. class ModulePassword extends Module
  16. {
  17.     /**
  18.      * Template
  19.      * @var string
  20.      */
  21.     protected $strTemplate 'mod_lostPassword';
  22.     /**
  23.      * Display a wildcard in the back end
  24.      *
  25.      * @return string
  26.      */
  27.     public function generate()
  28.     {
  29.         $request System::getContainer()->get('request_stack')->getCurrentRequest();
  30.         if ($request && System::getContainer()->get('contao.routing.scope_matcher')->isBackendRequest($request))
  31.         {
  32.             $objTemplate = new BackendTemplate('be_wildcard');
  33.             $objTemplate->wildcard '### ' $GLOBALS['TL_LANG']['FMD']['lostPassword'][0] . ' ###';
  34.             $objTemplate->title $this->headline;
  35.             $objTemplate->id $this->id;
  36.             $objTemplate->link $this->name;
  37.             $objTemplate->href StringUtil::specialcharsUrl(System::getContainer()->get('router')->generate('contao_backend', array('do'=>'themes''table'=>'tl_module''act'=>'edit''id'=>$this->id)));
  38.             return $objTemplate->parse();
  39.         }
  40.         return parent::generate();
  41.     }
  42.     /**
  43.      * Generate the module
  44.      */
  45.     protected function compile()
  46.     {
  47.         System::loadLanguageFile('tl_member');
  48.         $this->loadDataContainer('tl_member');
  49.         // Call onload_callback (e.g. to check permissions)
  50.         if (\is_array($GLOBALS['TL_DCA']['tl_member']['config']['onload_callback'] ?? null))
  51.         {
  52.             foreach ($GLOBALS['TL_DCA']['tl_member']['config']['onload_callback'] as $callback)
  53.             {
  54.                 if (\is_array($callback))
  55.                 {
  56.                     $this->import($callback[0]);
  57.                     $this->{$callback[0]}->{$callback[1]}();
  58.                 }
  59.                 elseif (\is_callable($callback))
  60.                 {
  61.                     $callback();
  62.                 }
  63.             }
  64.         }
  65.         $this->Template->requestToken System::getContainer()->get('contao.csrf.token_manager')->getDefaultTokenValue();
  66.         // Set new password
  67.         if (strncmp(Input::get('token'), 'pw-'3) === 0)
  68.         {
  69.             $this->setNewPassword();
  70.             return;
  71.         }
  72.         // Username widget
  73.         if (!$this->reg_skipName)
  74.         {
  75.             $arrFields['username'] = $GLOBALS['TL_DCA']['tl_member']['fields']['username'];
  76.             $arrFields['username']['name'] = 'username';
  77.         }
  78.         // E-mail widget
  79.         $arrFields['email'] = $GLOBALS['TL_DCA']['tl_member']['fields']['email'];
  80.         $arrFields['email']['name'] = 'email';
  81.         // Captcha widget
  82.         if (!$this->disableCaptcha)
  83.         {
  84.             $arrFields['captcha'] = array
  85.             (
  86.                 'name' => 'lost_password',
  87.                 'label' => $GLOBALS['TL_LANG']['MSC']['securityQuestion'],
  88.                 'inputType' => 'captcha',
  89.                 'eval' => array('mandatory'=>true)
  90.             );
  91.         }
  92.         $row 0;
  93.         $strFields '';
  94.         $doNotSubmit false;
  95.         $strFormId 'tl_lost_password_' $this->id;
  96.         // Initialize the widgets
  97.         foreach ($arrFields as $arrField)
  98.         {
  99.             $strClass $GLOBALS['TL_FFL'][$arrField['inputType']] ?? null;
  100.             // Continue if the class is not defined
  101.             if (!class_exists($strClass))
  102.             {
  103.                 continue;
  104.             }
  105.             $arrField['eval']['required'] = $arrField['eval']['mandatory'] ?? null;
  106.             /** @var Widget $objWidget */
  107.             $objWidget = new $strClass($strClass::getAttributesFromDca($arrField$arrField['name']));
  108.             $objWidget->storeValues true;
  109.             $objWidget->rowClass 'row_' $row . (($row == 0) ? ' row_first' '') . ((($row 2) == 0) ? ' even' ' odd');
  110.             ++$row;
  111.             // Validate the widget
  112.             if (Input::post('FORM_SUBMIT') == $strFormId)
  113.             {
  114.                 $objWidget->validate();
  115.                 if ($objWidget->hasErrors())
  116.                 {
  117.                     $doNotSubmit true;
  118.                 }
  119.             }
  120.             $strFields .= $objWidget->parse();
  121.         }
  122.         $this->Template->fields $strFields;
  123.         $this->Template->hasError $doNotSubmit;
  124.         // Look for an account and send the password link
  125.         if (!$doNotSubmit && Input::post('FORM_SUBMIT') == $strFormId)
  126.         {
  127.             if ($this->reg_skipName)
  128.             {
  129.                 $objMember MemberModel::findActiveByEmailAndUsername(Input::post('email'true));
  130.             }
  131.             else
  132.             {
  133.                 $objMember MemberModel::findActiveByEmailAndUsername(Input::post('email'true), Input::post('username'));
  134.             }
  135.             if ($objMember === null)
  136.             {
  137.                 $this->Template->error $GLOBALS['TL_LANG']['MSC']['accountNotFound'];
  138.             }
  139.             else
  140.             {
  141.                 $this->sendPasswordLink($objMember);
  142.             }
  143.         }
  144.         $this->Template->formId $strFormId;
  145.         $this->Template->username StringUtil::specialchars($GLOBALS['TL_LANG']['MSC']['username']);
  146.         $this->Template->email StringUtil::specialchars($GLOBALS['TL_LANG']['MSC']['emailAddress']);
  147.         $this->Template->slabel StringUtil::specialchars($GLOBALS['TL_LANG']['MSC']['requestPassword']);
  148.         $this->Template->rowLast 'row_' $row ' row_last' . ((($row 2) == 0) ? ' even' ' odd');
  149.     }
  150.     /**
  151.      * Set the new password
  152.      */
  153.     protected function setNewPassword()
  154.     {
  155.         $optIn System::getContainer()->get('contao.opt_in');
  156.         // Find an unconfirmed token with only one related record
  157.         if ((!$optInToken $optIn->find(Input::get('token'))) || !$optInToken->isValid() || \count($arrRelated $optInToken->getRelatedRecords()) != || key($arrRelated) != 'tl_member' || \count($arrIds current($arrRelated)) != || (!$objMember MemberModel::findByPk($arrIds[0])))
  158.         {
  159.             $this->strTemplate 'mod_message';
  160.             $this->Template = new FrontendTemplate($this->strTemplate);
  161.             $this->Template->type 'error';
  162.             $this->Template->message $GLOBALS['TL_LANG']['MSC']['invalidToken'];
  163.             return;
  164.         }
  165.         if ($optInToken->isConfirmed())
  166.         {
  167.             $this->strTemplate 'mod_message';
  168.             $this->Template = new FrontendTemplate($this->strTemplate);
  169.             $this->Template->type 'error';
  170.             $this->Template->message $GLOBALS['TL_LANG']['MSC']['tokenConfirmed'];
  171.             return;
  172.         }
  173.         if ($optInToken->getEmail() != $objMember->email)
  174.         {
  175.             $this->strTemplate 'mod_message';
  176.             $this->Template = new FrontendTemplate($this->strTemplate);
  177.             $this->Template->type 'error';
  178.             $this->Template->message $GLOBALS['TL_LANG']['MSC']['tokenEmailMismatch'];
  179.             return;
  180.         }
  181.         // Initialize the versioning (see #8301)
  182.         $objVersions = new Versions('tl_member'$objMember->id);
  183.         $objVersions->setUsername($objMember->username);
  184.         $objVersions->setUserId(0);
  185.         $objVersions->setEditUrl(System::getContainer()->get('router')->generate('contao_backend', array('do'=>'member''act'=>'edit''id'=>$objMember->id'rt'=>'1')));
  186.         $objVersions->initialize();
  187.         // Define the form field
  188.         $arrField $GLOBALS['TL_DCA']['tl_member']['fields']['password'];
  189.         $strClass $GLOBALS['TL_FFL']['password'] ?? null;
  190.         // Fallback to default if the class is not defined
  191.         if (!class_exists($strClass))
  192.         {
  193.             $strClass 'FormPassword';
  194.         }
  195.         /** @var Widget $objWidget */
  196.         $objWidget = new $strClass($strClass::getAttributesFromDca($arrField'password'));
  197.         $objWidget->currentRecord $objMember->id;
  198.         // Set row classes
  199.         $objWidget->rowClass 'row_0 row_first even';
  200.         $objWidget->rowClassConfirm 'row_1 odd';
  201.         $this->Template->rowLast 'row_2 row_last even';
  202.         $objSession System::getContainer()->get('session');
  203.         // Validate the field
  204.         if (Input::post('FORM_SUBMIT') && Input::post('FORM_SUBMIT') == $objSession->get('setPasswordToken'))
  205.         {
  206.             $objWidget->validate();
  207.             // Set the new password and redirect
  208.             if (!$objWidget->hasErrors())
  209.             {
  210.                 $objSession->set('setPasswordToken''');
  211.                 $objMember->tstamp time();
  212.                 $objMember->locked 0// see #8545
  213.                 $objMember->password $objWidget->value;
  214.                 $objMember->save();
  215.                 $optInToken->confirm();
  216.                 // Create a new version
  217.                 if ($GLOBALS['TL_DCA']['tl_member']['config']['enableVersioning'] ?? null)
  218.                 {
  219.                     $objVersions->create();
  220.                 }
  221.                 // HOOK: set new password callback
  222.                 if (isset($GLOBALS['TL_HOOKS']['setNewPassword']) && \is_array($GLOBALS['TL_HOOKS']['setNewPassword']))
  223.                 {
  224.                     foreach ($GLOBALS['TL_HOOKS']['setNewPassword'] as $callback)
  225.                     {
  226.                         $this->import($callback[0]);
  227.                         $this->{$callback[0]}->{$callback[1]}($objMember$objWidget->value$this);
  228.                     }
  229.                 }
  230.                 // Redirect to the jumpTo page
  231.                 if (($objTarget $this->objModel->getRelated('reg_jumpTo')) instanceof PageModel)
  232.                 {
  233.                     /** @var PageModel $objTarget */
  234.                     $this->redirect($objTarget->getFrontendUrl());
  235.                 }
  236.                 // Confirm
  237.                 $this->strTemplate 'mod_message';
  238.                 $this->Template = new FrontendTemplate($this->strTemplate);
  239.                 $this->Template->type 'confirm';
  240.                 $this->Template->message $GLOBALS['TL_LANG']['MSC']['newPasswordSet'];
  241.                 return;
  242.             }
  243.         }
  244.         $strToken md5(uniqid(mt_rand(), true));
  245.         $objSession->set('setPasswordToken'$strToken);
  246.         $this->Template->formId $strToken;
  247.         $this->Template->fields $objWidget->parse();
  248.         $this->Template->slabel StringUtil::specialchars($GLOBALS['TL_LANG']['MSC']['setNewPassword']);
  249.     }
  250.     /**
  251.      * Create a new user and redirect
  252.      *
  253.      * @param MemberModel $objMember
  254.      */
  255.     protected function sendPasswordLink($objMember)
  256.     {
  257.         $optIn System::getContainer()->get('contao.opt_in');
  258.         $optInToken $optIn->create('pw'$objMember->email, array('tl_member'=>array($objMember->id)));
  259.         // Prepare the simple token data
  260.         $arrData $objMember->row();
  261.         $arrData['activation'] = $optInToken->getIdentifier();
  262.         $arrData['domain'] = Idna::decode(Environment::get('host'));
  263.         $arrData['link'] = Idna::decode(Environment::get('base')) . Environment::get('request') . ((strpos(Environment::get('request'), '?') !== false) ? '&' '?') . 'token=' $optInToken->getIdentifier();
  264.         // Send the token
  265.         $optInToken->send(
  266.             sprintf($GLOBALS['TL_LANG']['MSC']['passwordSubject'], Idna::decode(Environment::get('host'))),
  267.             System::getContainer()->get('contao.string.simple_token_parser')->parse($this->reg_password$arrData)
  268.         );
  269.         System::getContainer()->get('monolog.logger.contao.access')->info('A new password has been requested for user ID ' $objMember->id ' (' Idna::decodeEmail($objMember->email) . ')');
  270.         // Check whether there is a jumpTo page
  271.         if (($objJumpTo $this->objModel->getRelated('jumpTo')) instanceof PageModel)
  272.         {
  273.             $this->jumpToOrReload($objJumpTo->row());
  274.         }
  275.         $this->reload();
  276.     }
  277. }
  278. class_alias(ModulePassword::class, 'ModulePassword');