vendor/contao/core-bundle/src/Resources/contao/modules/ModuleRegistration.php line 315

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. use Contao\CoreBundle\Exception\ResponseException;
  11. /**
  12.  * Front end module "registration".
  13.  */
  14. class ModuleRegistration extends Module
  15. {
  16.     /**
  17.      * Template
  18.      * @var string
  19.      */
  20.     protected $strTemplate 'member_default';
  21.     /**
  22.      * Display a wildcard in the back end
  23.      *
  24.      * @return string
  25.      */
  26.     public function generate()
  27.     {
  28.         $request System::getContainer()->get('request_stack')->getCurrentRequest();
  29.         if ($request && System::getContainer()->get('contao.routing.scope_matcher')->isBackendRequest($request))
  30.         {
  31.             $objTemplate = new BackendTemplate('be_wildcard');
  32.             $objTemplate->wildcard '### ' $GLOBALS['TL_LANG']['FMD']['registration'][0] . ' ###';
  33.             $objTemplate->title $this->headline;
  34.             $objTemplate->id $this->id;
  35.             $objTemplate->link $this->name;
  36.             $objTemplate->href StringUtil::specialcharsUrl(System::getContainer()->get('router')->generate('contao_backend', array('do'=>'themes''table'=>'tl_module''act'=>'edit''id'=>$this->id)));
  37.             return $objTemplate->parse();
  38.         }
  39.         $this->editable StringUtil::deserialize($this->editable);
  40.         // Return if there are no editable fields
  41.         if (empty($this->editable) || !\is_array($this->editable))
  42.         {
  43.             return '';
  44.         }
  45.         return parent::generate();
  46.     }
  47.     /**
  48.      * Generate the module
  49.      */
  50.     protected function compile()
  51.     {
  52.         System::loadLanguageFile('tl_member');
  53.         $this->loadDataContainer('tl_member');
  54.         // Call onload_callback (e.g. to check permissions)
  55.         if (\is_array($GLOBALS['TL_DCA']['tl_member']['config']['onload_callback'] ?? null))
  56.         {
  57.             foreach ($GLOBALS['TL_DCA']['tl_member']['config']['onload_callback'] as $callback)
  58.             {
  59.                 if (\is_array($callback))
  60.                 {
  61.                     $this->import($callback[0]);
  62.                     $this->{$callback[0]}->{$callback[1]}();
  63.                 }
  64.                 elseif (\is_callable($callback))
  65.                 {
  66.                     $callback();
  67.                 }
  68.             }
  69.         }
  70.         $strFormId 'tl_registration_' $this->id;
  71.         // Remove expired registration (#3709)
  72.         if (Input::post('FORM_SUBMIT') == $strFormId && ($email Input::post('email')) && ($member MemberModel::findExpiredRegistrationByEmail($email)))
  73.         {
  74.             $member->delete();
  75.         }
  76.         // Activate account
  77.         if (strncmp(Input::get('token'), 'reg-'4) === 0)
  78.         {
  79.             $this->activateAcount();
  80.             return;
  81.         }
  82.         if ($this->memberTpl)
  83.         {
  84.             $this->Template = new FrontendTemplate($this->memberTpl);
  85.             $this->Template->setData($this->arrData);
  86.         }
  87.         $this->Template->fields '';
  88.         $objCaptcha null;
  89.         $doNotSubmit false;
  90.         // Predefine the group order (other groups will be appended automatically)
  91.         $arrGroups = array
  92.         (
  93.             'personal' => array(),
  94.             'address'  => array(),
  95.             'contact'  => array(),
  96.             'login'    => array(),
  97.             'profile'  => array()
  98.         );
  99.         // Captcha
  100.         if (!$this->disableCaptcha)
  101.         {
  102.             $arrCaptcha = array
  103.             (
  104.                 'id' => 'registration',
  105.                 'label' => $GLOBALS['TL_LANG']['MSC']['securityQuestion'],
  106.                 'type' => 'captcha',
  107.                 'mandatory' => true,
  108.                 'required' => true
  109.             );
  110.             $strClass $GLOBALS['TL_FFL']['captcha'] ?? null;
  111.             // Fallback to default if the class is not defined
  112.             if (!class_exists($strClass))
  113.             {
  114.                 $strClass 'FormCaptcha';
  115.             }
  116.             /** @var FormCaptcha $objCaptcha */
  117.             $objCaptcha = new $strClass($arrCaptcha);
  118.             if (Input::post('FORM_SUBMIT') == $strFormId)
  119.             {
  120.                 $objCaptcha->validate();
  121.                 if ($objCaptcha->hasErrors())
  122.                 {
  123.                     $doNotSubmit true;
  124.                 }
  125.             }
  126.         }
  127.         $objMember null;
  128.         // Check for a follow-up registration (see #7992)
  129.         if ($this->reg_activate && Input::post('email'true) && ($objMember MemberModel::findUnactivatedByEmail(Input::post('email'true))) !== null)
  130.         {
  131.             $this->resendActivationMail($objMember);
  132.             return;
  133.         }
  134.         $arrUser = array();
  135.         $arrFields = array();
  136.         $hasUpload false;
  137.         $i 0;
  138.         // Build the form
  139.         foreach ($this->editable as $field)
  140.         {
  141.             $arrData $GLOBALS['TL_DCA']['tl_member']['fields'][$field] ?? array();
  142.             // Map checkboxWizards to regular checkbox widgets
  143.             if (($arrData['inputType'] ?? null) == 'checkboxWizard')
  144.             {
  145.                 $arrData['inputType'] = 'checkbox';
  146.             }
  147.             // Map fileTrees to upload widgets (see #8091)
  148.             if (($arrData['inputType'] ?? null) == 'fileTree')
  149.             {
  150.                 $arrData['inputType'] = 'upload';
  151.             }
  152.             $strClass $GLOBALS['TL_FFL'][$arrData['inputType']] ?? null;
  153.             // Continue if the class is not defined
  154.             if (!class_exists($strClass))
  155.             {
  156.                 continue;
  157.             }
  158.             $arrData['eval']['required'] = $arrData['eval']['mandatory'] ?? null;
  159.             // Unset the unique field check upon follow-up registrations
  160.             if ($objMember !== null && ($arrData['eval']['unique'] ?? null) && Input::post($field) == $objMember->$field)
  161.             {
  162.                 $arrData['eval']['unique'] = false;
  163.             }
  164.             $objWidget = new $strClass($strClass::getAttributesFromDca($arrData$field$arrData['default'] ?? null$field'tl_member'$this));
  165.             // Append the module ID to prevent duplicate IDs (see #1493)
  166.             $objWidget->id .= '_' $this->id;
  167.             $objWidget->storeValues true;
  168.             $objWidget->rowClass 'row_' $i . (($i == 0) ? ' row_first' '') . ((($i 2) == 0) ? ' even' ' odd');
  169.             // Increase the row count if it's a password field
  170.             if ($objWidget instanceof FormPassword)
  171.             {
  172.                 $objWidget->rowClassConfirm 'row_' . ++$i . ((($i 2) == 0) ? ' even' ' odd');
  173.             }
  174.             // Validate input
  175.             if (Input::post('FORM_SUBMIT') == $strFormId)
  176.             {
  177.                 $objWidget->validate();
  178.                 $varValue $objWidget->value;
  179.                 $passwordHasher System::getContainer()->get('security.password_hasher_factory')->getPasswordHasher(FrontendUser::class);
  180.                 // Check whether the password matches the username
  181.                 if ($objWidget instanceof FormPassword && ($username Input::post('username')) && $passwordHasher->verify($varValue$username))
  182.                 {
  183.                     $objWidget->addError($GLOBALS['TL_LANG']['ERR']['passwordName']);
  184.                 }
  185.                 $rgxp $arrData['eval']['rgxp'] ?? null;
  186.                 // Convert date formats into timestamps (check the eval setting first -> #3063)
  187.                 if ($varValue !== null && $varValue !== '' && \in_array($rgxp, array('date''time''datim')))
  188.                 {
  189.                     try
  190.                     {
  191.                         $objDate = new Date($varValueDate::getFormatFromRgxp($rgxp));
  192.                         $varValue $objDate->tstamp;
  193.                     }
  194.                     catch (\OutOfBoundsException $e)
  195.                     {
  196.                         $objWidget->addError(sprintf($GLOBALS['TL_LANG']['ERR']['invalidDate'], $varValue));
  197.                     }
  198.                 }
  199.                 // Convert arrays (see #4980)
  200.                 if (($arrData['eval']['multiple'] ?? null) && isset($arrData['eval']['csv']))
  201.                 {
  202.                     $varValue implode($arrData['eval']['csv'], $varValue);
  203.                 }
  204.                 // Make sure that unique fields are unique (check the eval setting first -> #3063)
  205.                 if (($arrData['eval']['unique'] ?? null) && (\is_array($varValue) || (string) $varValue !== '') && !$this->Database->isUniqueValue('tl_member'$field$varValue))
  206.                 {
  207.                     $objWidget->addError(sprintf($GLOBALS['TL_LANG']['ERR']['unique'], $arrData['label'][0] ?: $field));
  208.                 }
  209.                 // Save callback
  210.                 if (\is_array($arrData['save_callback'] ?? null) && $objWidget->submitInput() && !$objWidget->hasErrors())
  211.                 {
  212.                     foreach ($arrData['save_callback'] as $callback)
  213.                     {
  214.                         try
  215.                         {
  216.                             if (\is_array($callback))
  217.                             {
  218.                                 $this->import($callback[0]);
  219.                                 $varValue $this->{$callback[0]}->{$callback[1]}($varValuenull);
  220.                             }
  221.                             elseif (\is_callable($callback))
  222.                             {
  223.                                 $varValue $callback($varValuenull);
  224.                             }
  225.                         }
  226.                         catch (ResponseException $e)
  227.                         {
  228.                             throw $e;
  229.                         }
  230.                         catch (\Exception $e)
  231.                         {
  232.                             $objWidget->class 'error';
  233.                             $objWidget->addError($e->getMessage());
  234.                         }
  235.                     }
  236.                 }
  237.                 // Store the current value
  238.                 if ($objWidget->hasErrors())
  239.                 {
  240.                     $doNotSubmit true;
  241.                 }
  242.                 elseif ($objWidget->submitInput())
  243.                 {
  244.                     // Set the correct empty value (see #6284, #6373)
  245.                     if ($varValue === '')
  246.                     {
  247.                         $varValue $objWidget->getEmptyValue();
  248.                     }
  249.                     // Encrypt the value (see #7815)
  250.                     if ($arrData['eval']['encrypt'] ?? null)
  251.                     {
  252.                         $varValue Encryption::encrypt($varValue);
  253.                     }
  254.                     // Set the new value
  255.                     $arrUser[$field] = $varValue;
  256.                 }
  257.             }
  258.             if ($objWidget instanceof UploadableWidgetInterface)
  259.             {
  260.                 $hasUpload true;
  261.             }
  262.             $temp $objWidget->parse();
  263.             $this->Template->fields .= $temp;
  264.             if (!isset($arrFields[$arrData['eval']['feGroup']][$field]))
  265.             {
  266.                 $arrFields[$arrData['eval']['feGroup']][$field] = '';
  267.             }
  268.             $arrFields[$arrData['eval']['feGroup']][$field] .= $temp;
  269.             ++$i;
  270.         }
  271.         // Captcha
  272.         if (!$this->disableCaptcha)
  273.         {
  274.             $objCaptcha->rowClass 'row_' $i . (($i == 0) ? ' row_first' '') . ((($i 2) == 0) ? ' even' ' odd');
  275.             $strCaptcha $objCaptcha->parse();
  276.             $this->Template->fields .= $strCaptcha;
  277.             $arrFields['captcha']['captcha'] = ($arrFields['captcha']['captcha'] ?? '') . $strCaptcha;
  278.         }
  279.         $this->Template->rowLast 'row_' . ++$i . ((($i 2) == 0) ? ' even' ' odd');
  280.         $this->Template->enctype $hasUpload 'multipart/form-data' 'application/x-www-form-urlencoded';
  281.         $this->Template->hasError $doNotSubmit;
  282.         // Create new user if there are no errors
  283.         if (!$doNotSubmit && Input::post('FORM_SUBMIT') == $strFormId)
  284.         {
  285.             $this->createNewUser($arrUser);
  286.         }
  287.         $this->Template->loginDetails $GLOBALS['TL_LANG']['tl_member']['loginDetails'];
  288.         $this->Template->addressDetails $GLOBALS['TL_LANG']['tl_member']['addressDetails'];
  289.         $this->Template->contactDetails $GLOBALS['TL_LANG']['tl_member']['contactDetails'];
  290.         $this->Template->personalData $GLOBALS['TL_LANG']['tl_member']['personalData'];
  291.         $this->Template->captchaDetails $GLOBALS['TL_LANG']['MSC']['securityQuestion'];
  292.         // Add the groups
  293.         foreach ($arrFields as $k=>$v)
  294.         {
  295.             // Deprecated since Contao 4.0, to be removed in Contao 5.0
  296.             $this->Template->$k $v;
  297.             $key $k . (($k == 'personal') ? 'Data' 'Details');
  298.             $arrGroups[$GLOBALS['TL_LANG']['tl_member'][$key] ?? ''] = $v;
  299.         }
  300.         $this->Template->categories array_filter($arrGroups);
  301.         $this->Template->formId $strFormId;
  302.         $this->Template->slabel StringUtil::specialchars($GLOBALS['TL_LANG']['MSC']['register']);
  303.         $this->Template->requestToken System::getContainer()->get('contao.csrf.token_manager')->getDefaultTokenValue();
  304.         // Deprecated since Contao 4.0, to be removed in Contao 5.0
  305.         $this->Template->captcha $arrFields['captcha']['captcha'] ?? '';
  306.     }
  307.     /**
  308.      * Create a new user and redirect
  309.      *
  310.      * @param array $arrData
  311.      */
  312.     protected function createNewUser($arrData)
  313.     {
  314.         $arrData['tstamp'] = time();
  315.         $arrData['login'] = $this->reg_allowLogin;
  316.         $arrData['dateAdded'] = $arrData['tstamp'];
  317.         // Set default groups
  318.         if (!\array_key_exists('groups'$arrData))
  319.         {
  320.             $arrData['groups'] = $this->reg_groups;
  321.         }
  322.         // Disable account
  323.         $arrData['disable'] = 1;
  324.         // Make sure newsletter is an array
  325.         if (isset($arrData['newsletter']) && !\is_array($arrData['newsletter']))
  326.         {
  327.             $arrData['newsletter'] = array($arrData['newsletter']);
  328.         }
  329.         // Create the user
  330.         $objNewUser = new MemberModel();
  331.         $objNewUser->setRow($arrData);
  332.         $objNewUser->save();
  333.         // Store the new ID (see https://github.com/contao/contao/pull/196#discussion_r243555399)
  334.         $arrData['id'] = $objNewUser->id;
  335.         // Send activation e-mail
  336.         if ($this->reg_activate)
  337.         {
  338.             $this->sendActivationMail($arrData);
  339.         }
  340.         // Assign home directory
  341.         if ($this->reg_assignDir)
  342.         {
  343.             $objHomeDir FilesModel::findByUuid($this->reg_homeDir);
  344.             if ($objHomeDir !== null)
  345.             {
  346.                 $this->import(Files::class, 'Files');
  347.                 $strUserDir StringUtil::standardize($arrData['username']) ?: 'user_' $objNewUser->id;
  348.                 // Add the user ID if the directory exists
  349.                 while (is_dir(System::getContainer()->getParameter('kernel.project_dir') . '/' $objHomeDir->path '/' $strUserDir))
  350.                 {
  351.                     $strUserDir .= '_' $objNewUser->id;
  352.                 }
  353.                 // Create the user folder
  354.                 new Folder($objHomeDir->path '/' $strUserDir);
  355.                 $objUserDir FilesModel::findByPath($objHomeDir->path '/' $strUserDir);
  356.                 // Save the folder ID
  357.                 $objNewUser->assignDir 1;
  358.                 $objNewUser->homeDir $objUserDir->uuid;
  359.                 $objNewUser->save();
  360.             }
  361.         }
  362.         // HOOK: send insert ID and user data
  363.         if (isset($GLOBALS['TL_HOOKS']['createNewUser']) && \is_array($GLOBALS['TL_HOOKS']['createNewUser']))
  364.         {
  365.             foreach ($GLOBALS['TL_HOOKS']['createNewUser'] as $callback)
  366.             {
  367.                 $this->import($callback[0]);
  368.                 $this->{$callback[0]}->{$callback[1]}($objNewUser->id$arrData$this);
  369.             }
  370.         }
  371.         // Create the initial version (see #7816)
  372.         $objVersions = new Versions('tl_member'$objNewUser->id);
  373.         $objVersions->setUsername($objNewUser->username);
  374.         $objVersions->setUserId(0);
  375.         $objVersions->setEditUrl(System::getContainer()->get('router')->generate('contao_backend', array('do'=>'member''act'=>'edit''id'=>$objNewUser->id'rt'=>'1')));
  376.         $objVersions->initialize();
  377.         // Inform admin if no activation link is sent
  378.         if (!$this->reg_activate)
  379.         {
  380.             $this->sendAdminNotification($objNewUser->id$arrData);
  381.         }
  382.         // Check whether there is a jumpTo page
  383.         if (($objJumpTo $this->objModel->getRelated('jumpTo')) instanceof PageModel)
  384.         {
  385.             $this->jumpToOrReload($objJumpTo->row());
  386.         }
  387.         $this->reload();
  388.     }
  389.     /**
  390.      * Send the activation mail
  391.      *
  392.      * @param array $arrData
  393.      */
  394.     protected function sendActivationMail($arrData)
  395.     {
  396.         $optIn System::getContainer()->get('contao.opt_in');
  397.         $optInToken $optIn->create('reg'$arrData['email'], array('tl_member'=>array($arrData['id'])));
  398.         // Prepare the simple token data
  399.         $arrTokenData $arrData;
  400.         $arrTokenData['activation'] = $optInToken->getIdentifier();
  401.         $arrTokenData['domain'] = Idna::decode(Environment::get('host'));
  402.         $arrTokenData['link'] = Idna::decode(Environment::get('base')) . Environment::get('request') . ((strpos(Environment::get('request'), '?') !== false) ? '&' '?') . 'token=' $optInToken->getIdentifier();
  403.         $arrTokenData['channels'] = '';
  404.         $bundles System::getContainer()->getParameter('kernel.bundles');
  405.         if (isset($bundles['ContaoNewsletterBundle']))
  406.         {
  407.             // Make sure newsletter is an array
  408.             if (isset($arrData['newsletter']) && !\is_array($arrData['newsletter']))
  409.             {
  410.                 $arrData['newsletter'] = array($arrData['newsletter']);
  411.             }
  412.             // Replace the wildcard
  413.             if (!empty($arrData['newsletter']))
  414.             {
  415.                 $objChannels NewsletterChannelModel::findByIds($arrData['newsletter']);
  416.                 if ($objChannels !== null)
  417.                 {
  418.                     $arrTokenData['channels'] = implode("\n"$objChannels->fetchEach('title'));
  419.                 }
  420.             }
  421.         }
  422.         // Deprecated since Contao 4.0, to be removed in Contao 5.0
  423.         $arrTokenData['channel'] = $arrTokenData['channels'];
  424.         // Send the token
  425.         $optInToken->send(
  426.             sprintf($GLOBALS['TL_LANG']['MSC']['emailSubject'], Idna::decode(Environment::get('host'))),
  427.             System::getContainer()->get('contao.string.simple_token_parser')->parse($this->reg_text$arrTokenData)
  428.         );
  429.     }
  430.     /**
  431.      * Activate an account
  432.      */
  433.     protected function activateAcount()
  434.     {
  435.         $this->strTemplate 'mod_message';
  436.         $this->Template = new FrontendTemplate($this->strTemplate);
  437.         $optIn System::getContainer()->get('contao.opt_in');
  438.         // Find an unconfirmed token with only one related record
  439.         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])))
  440.         {
  441.             $this->Template->type 'error';
  442.             $this->Template->message $GLOBALS['TL_LANG']['MSC']['invalidToken'];
  443.             return;
  444.         }
  445.         if ($optInToken->isConfirmed())
  446.         {
  447.             $this->Template->type 'error';
  448.             $this->Template->message $GLOBALS['TL_LANG']['MSC']['tokenConfirmed'];
  449.             return;
  450.         }
  451.         if ($optInToken->getEmail() != $objMember->email)
  452.         {
  453.             $this->Template->type 'error';
  454.             $this->Template->message $GLOBALS['TL_LANG']['MSC']['tokenEmailMismatch'];
  455.             return;
  456.         }
  457.         $objMember->disable '';
  458.         $objMember->save();
  459.         $optInToken->confirm();
  460.         // HOOK: post activation callback
  461.         if (isset($GLOBALS['TL_HOOKS']['activateAccount']) && \is_array($GLOBALS['TL_HOOKS']['activateAccount']))
  462.         {
  463.             foreach ($GLOBALS['TL_HOOKS']['activateAccount'] as $callback)
  464.             {
  465.                 $this->import($callback[0]);
  466.                 $this->{$callback[0]}->{$callback[1]}($objMember$this);
  467.             }
  468.         }
  469.         System::getContainer()->get('monolog.logger.contao.access')->info('User account ID ' $objMember->id ' (' Idna::decodeEmail($objMember->email) . ') has been activated');
  470.         // Redirect to the jumpTo page
  471.         if (($objTarget $this->objModel->getRelated('reg_jumpTo')) instanceof PageModel)
  472.         {
  473.             /** @var PageModel $objTarget */
  474.             $this->redirect($objTarget->getFrontendUrl());
  475.         }
  476.         // Confirm activation
  477.         $this->Template->type 'confirm';
  478.         $this->Template->message $GLOBALS['TL_LANG']['MSC']['accountActivated'];
  479.     }
  480.     /**
  481.      * Re-send the activation mail
  482.      *
  483.      * @param MemberModel $objMember
  484.      */
  485.     protected function resendActivationMail(MemberModel $objMember)
  486.     {
  487.         if (!$objMember->disable)
  488.         {
  489.             return;
  490.         }
  491.         $this->strTemplate 'mod_message';
  492.         $this->Template = new FrontendTemplate($this->strTemplate);
  493.         $optIn System::getContainer()->get('contao.opt_in');
  494.         $optInToken null;
  495.         $models OptInModel::findByRelatedTableAndIds('tl_member', array($objMember->id));
  496.         foreach ($models as $model)
  497.         {
  498.             // Look for a valid, unconfirmed token
  499.             if (($token $optIn->find($model->token)) && $token->isValid() && !$token->isConfirmed())
  500.             {
  501.                 $optInToken $token;
  502.                 break;
  503.             }
  504.         }
  505.         if ($optInToken === null)
  506.         {
  507.             return;
  508.         }
  509.         $optInToken->send();
  510.         // Confirm activation
  511.         $this->Template->type 'confirm';
  512.         $this->Template->message $GLOBALS['TL_LANG']['MSC']['resendActivation'];
  513.     }
  514.     /**
  515.      * Send an admin notification e-mail
  516.      *
  517.      * @param integer $intId
  518.      * @param array   $arrData
  519.      */
  520.     protected function sendAdminNotification($intId$arrData)
  521.     {
  522.         System::getContainer()->get('monolog.logger.contao.access')->info('A new user (ID ' $intId ') has registered on the website');
  523.         if (!isset($GLOBALS['TL_ADMIN_EMAIL']))
  524.         {
  525.             return;
  526.         }
  527.         $objEmail = new Email();
  528.         $objEmail->from $GLOBALS['TL_ADMIN_EMAIL'];
  529.         $objEmail->fromName $GLOBALS['TL_ADMIN_NAME'] ?? null;
  530.         $objEmail->subject sprintf($GLOBALS['TL_LANG']['MSC']['adminSubject'], Idna::decode(Environment::get('host')));
  531.         $strData "\n\n";
  532.         // Add user details
  533.         foreach ($arrData as $k=>$v)
  534.         {
  535.             if ($k == 'id' || $k == 'password' || $k == 'tstamp' || $k == 'dateAdded')
  536.             {
  537.                 continue;
  538.             }
  539.             $v StringUtil::deserialize($v);
  540.             if ($k == 'dateOfBirth' && \strlen($v))
  541.             {
  542.                 $v Date::parse(Config::get('dateFormat'), $v);
  543.             }
  544.             $strData .= ($GLOBALS['TL_LANG']['tl_member'][$k][0] ?? $k) . ': ' . (\is_array($v) ? implode(', '$v) : $v) . "\n";
  545.         }
  546.         $objEmail->text sprintf($GLOBALS['TL_LANG']['MSC']['adminText'], $intId$strData "\n") . "\n";
  547.         $objEmail->sendTo($GLOBALS['TL_ADMIN_EMAIL']);
  548.     }
  549. }
  550. class_alias(ModuleRegistration::class, 'ModuleRegistration');