5 use ParagonIE\ConstantTime\Hex
;
6 use wcf\acp\form\UserAddForm
;
7 use wcf\data\blacklist\entry\BlacklistEntry
;
8 use wcf\data\
object\type\ObjectType
;
9 use wcf\data\user\avatar\Gravatar
;
10 use wcf\data\user\group\UserGroup
;
11 use wcf\data\user\User
;
12 use wcf\data\user\UserAction
;
13 use wcf\data\user\UserEditor
;
14 use wcf\system\captcha\CaptchaHandler
;
15 use wcf\system\email\Email
;
16 use wcf\system\email\mime\MimePartFacade
;
17 use wcf\system\email\mime\RecipientAwareTextMimePart
;
18 use wcf\system\email\UserMailbox
;
19 use wcf\system\event\EventHandler
;
20 use wcf\system\exception\NamedUserException
;
21 use wcf\system\exception\PermissionDeniedException
;
22 use wcf\system\exception\SystemException
;
23 use wcf\system\exception\UserInputException
;
24 use wcf\system\request\LinkHandler
;
25 use wcf\system\user\group\assignment\UserGroupAssignmentHandler
;
26 use wcf\system\user\notification\
object\UserRegistrationUserNotificationObject
;
27 use wcf\system\user\notification\UserNotificationHandler
;
29 use wcf\util\HeaderUtil
;
31 use wcf\util\StringUtil
;
32 use wcf\util\UserRegistrationUtil
;
33 use wcf\util\UserUtil
;
36 * Shows the user registration form.
39 * @copyright 2001-2020 WoltLab GmbH
40 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
41 * @package WoltLabSuite\Core\Form
43 class RegisterForm
extends UserAddForm
46 * true if external authentication is used
49 public $isExternalAuthentication = false;
54 public $neededPermissions = [];
57 * holds a language variable with information about the registration process
58 * e.g. if you need to activate your account
64 * captcha object type object
67 public $captchaObjectType;
70 * name of the captcha object type; if empty, captcha is disabled
73 public $captchaObjectTypeName = CAPTCHA_TYPE
;
76 * true if captcha is used
79 public $useCaptcha = REGISTER_USE_CAPTCHA
;
85 public $randomFieldNames = [];
88 * list of fields that have matches in the blacklist
92 public $blacklistMatches = [];
95 * min number of seconds between form request and submit
98 public static $minRegistrationTime = 10;
103 public $passwordStrengthVerdict = [];
108 public function readParameters()
110 parent
::readParameters();
112 // user is already registered
113 if (WCF
::getUser()->userID
) {
114 throw new PermissionDeniedException();
117 // registration disabled
118 if (REGISTER_DISABLED
) {
119 throw new NamedUserException(WCF
::getLanguage()->get('wcf.user.register.error.disabled'));
123 if (REGISTER_ENABLE_DISCLAIMER
&& !WCF
::getSession()->getVar('disclaimerAccepted')) {
124 HeaderUtil
::redirect(LinkHandler
::getInstance()->getLink('Disclaimer'));
129 if (WCF
::getSession()->getVar('__3rdPartyProvider')) {
130 $this->isExternalAuthentication
= true;
137 public function readFormParameters()
139 parent
::readFormParameters();
141 if (!empty($this->username
) ||
!empty($this->email
)) {
142 throw new PermissionDeniedException();
145 $this->randomFieldNames
= WCF
::getSession()->getVar('registrationRandomFieldNames');
146 if ($this->randomFieldNames
=== null) {
147 throw new PermissionDeniedException();
150 if (isset($_POST[$this->randomFieldNames
['username']])) {
151 $this->username
= StringUtil
::trim($_POST[$this->randomFieldNames
['username']]);
153 if (isset($_POST[$this->randomFieldNames
['email']])) {
154 $this->email
= StringUtil
::trim($_POST[$this->randomFieldNames
['email']]);
156 if (isset($_POST[$this->randomFieldNames
['confirmEmail']])) {
157 $this->confirmEmail
= StringUtil
::trim($_POST[$this->randomFieldNames
['confirmEmail']]);
159 if (isset($_POST[$this->randomFieldNames
['password']])) {
160 $this->password
= $_POST[$this->randomFieldNames
['password']];
162 if (isset($_POST[$this->randomFieldNames
['password'] . '_passwordStrengthVerdict'])) {
164 $this->passwordStrengthVerdict
= JSON
::decode(
165 $_POST[$this->randomFieldNames
['password'] . '_passwordStrengthVerdict']
167 } catch (SystemException
$e) {
171 if (isset($_POST[$this->randomFieldNames
['confirmPassword']])) {
172 $this->confirmPassword
= $_POST[$this->randomFieldNames
['confirmPassword']];
175 $this->groupIDs
= [];
177 if ($this->captchaObjectType
) {
178 $this->captchaObjectType
->getProcessor()->readFormParameters();
185 protected function initOptionHandler()
187 /** @noinspection PhpUndefinedMethodInspection */
188 $this->optionHandler
->setInRegistration();
189 parent
::initOptionHandler();
195 public function validate()
197 // validate captcha first
198 $this->validateCaptcha();
202 // validate registration time
204 !$this->isExternalAuthentication
206 !WCF
::getSession()->getVar('registrationStartTime')
207 ||
(TIME_NOW
- WCF
::getSession()->getVar('registrationStartTime')) < self
::$minRegistrationTime
210 throw new UserInputException('registrationStartTime', []);
213 if (BLACKLIST_SFS_ENABLE
) {
214 $this->blacklistMatches
= BlacklistEntry
::getMatches(
217 UserUtil
::getIpAddress()
219 if (!empty($this->blacklistMatches
) && BLACKLIST_SFS_ACTION
=== 'block') {
220 throw new NamedUserException('wcf.user.register.error.blacklistMatches');
228 public function readData()
230 if ($this->useCaptcha
&& $this->captchaObjectTypeName
) {
231 $this->captchaObjectType
= CaptchaHandler
::getInstance()->getObjectTypeByName($this->captchaObjectTypeName
);
232 if ($this->captchaObjectType
=== null) {
233 throw new SystemException("Unknown captcha object type with id '" . $this->captchaObjectTypeName
. "'");
236 if (!$this->captchaObjectType
->getProcessor()->isAvailable()) {
237 $this->captchaObjectType
= null;
240 if (WCF
::getSession()->getVar('noRegistrationCaptcha')) {
241 $this->captchaObjectType
= null;
248 $this->languageID
= WCF
::getLanguage()->languageID
;
250 if (WCF
::getSession()->getVar('__username')) {
251 $this->username
= WCF
::getSession()->getVar('__username');
253 if (WCF
::getSession()->getVar('__email')) {
254 $this->email
= $this->confirmEmail
= WCF
::getSession()->getVar('__email');
257 WCF
::getSession()->register('registrationStartTime', TIME_NOW
);
259 // generate random field names
260 $this->randomFieldNames
= [
261 'username' => UserRegistrationUtil
::getRandomFieldName('username'),
262 'email' => UserRegistrationUtil
::getRandomFieldName('email'),
263 'confirmEmail' => UserRegistrationUtil
::getRandomFieldName('confirmEmail'),
264 'password' => UserRegistrationUtil
::getRandomFieldName('password'),
265 'confirmPassword' => UserRegistrationUtil
::getRandomFieldName('confirmPassword'),
268 WCF
::getSession()->register('registrationRandomFieldNames', $this->randomFieldNames
);
273 * Reads option tree on page init.
275 protected function readOptionTree()
277 $this->optionTree
= $this->optionHandler
->getOptionTree('profile');
283 public function assignVariables()
285 parent
::assignVariables();
287 WCF
::getTPL()->assign([
288 'captchaObjectType' => $this->captchaObjectType
,
289 'isExternalAuthentication' => $this->isExternalAuthentication
,
290 'randomFieldNames' => $this->randomFieldNames
,
291 'passwordRulesAttributeValue' => UserRegistrationUtil
::getPasswordRulesAttributeValue(),
298 public function show()
300 AbstractForm
::show();
304 * Validates the captcha.
306 protected function validateCaptcha()
308 if ($this->captchaObjectType
) {
310 $this->captchaObjectType
->getProcessor()->validate();
311 } catch (UserInputException
$e) {
312 $this->errorType
[$e->getField()] = $e->getType();
320 protected function validateUsername($username)
322 parent
::validateUsername($username);
324 // check for min-max length
325 if (!UserRegistrationUtil
::isValidUsername($username)) {
326 throw new UserInputException('username', 'invalid');
333 protected function validatePassword($password, $confirmPassword)
335 if (!$this->isExternalAuthentication
) {
336 parent
::validatePassword($password, $confirmPassword);
338 // check security of the given password
339 if (($this->passwordStrengthVerdict
['score'] ??
4) < PASSWORD_MIN_SCORE
) {
340 throw new UserInputException('password', 'notSecure');
348 protected function validateEmail($email, $confirmEmail)
350 parent
::validateEmail($email, $confirmEmail);
352 if (!UserRegistrationUtil
::isValidEmail($email)) {
353 throw new UserInputException('email', 'invalid');
360 public function save()
362 AbstractForm
::save();
365 $saveOptions = $this->optionHandler
->save();
366 $registerVia3rdParty = false;
368 if ($this->isExternalAuthentication
) {
369 $provider = WCF
::getSession()->getVar('__3rdPartyProvider');
374 if (($oauthUser = WCF
::getSession()->getVar('__oauthUser'))) {
375 $this->additionalFields
['authData'] = $provider . ':' . $oauthUser->getId();
380 if (WCF
::getSession()->getVar('__twitterData')) {
381 $twitterData = WCF
::getSession()->getVar('__twitterData');
382 $this->additionalFields
['authData'] = 'twitter:' . ($twitterData['id'] ??
$twitterData['user_id']);
384 WCF
::getSession()->unregister('__twitterData');
387 WCF
::getSession()->getVar('__email')
388 && WCF
::getSession()->getVar('__email') == $this->email
390 $registerVia3rdParty = true;
396 // Accounts connected to a 3rdParty login do not have passwords.
397 $this->password
= null;
399 if (WCF
::getSession()->getVar('__email') && WCF
::getSession()->getVar('__email') == $this->email
) {
400 $registerVia3rdParty = true;
405 'saveOptions' => $saveOptions,
406 'registerVia3rdParty' => $registerVia3rdParty,
408 EventHandler
::getInstance()->fireAction($this, 'registerVia3rdParty', $eventParameters);
409 $saveOptions = $eventParameters['saveOptions'];
410 $registerVia3rdParty = $eventParameters['registerVia3rdParty'];
412 $this->additionalFields
['languageID'] = $this->languageID
;
413 if (LOG_IP_ADDRESS
) {
414 $this->additionalFields
['registrationIpAddress'] = UserUtil
::getIpAddress();
417 // generate activation code
418 $addDefaultGroups = true;
420 !empty($this->blacklistMatches
)
421 ||
(REGISTER_ACTIVATION_METHOD
& User
::REGISTER_ACTIVATION_USER
&& !$registerVia3rdParty)
422 ||
(REGISTER_ACTIVATION_METHOD
& User
::REGISTER_ACTIVATION_ADMIN
)
424 $activationCode = UserRegistrationUtil
::getActivationCode();
425 $emailConfirmCode = Hex
::encode(\random_bytes
(20));
426 $this->additionalFields
['activationCode'] = $activationCode;
427 $this->additionalFields
['emailConfirmed'] = $emailConfirmCode;
428 $addDefaultGroups = false;
429 $this->groupIDs
= UserGroup
::getGroupIDsByType([UserGroup
::EVERYONE
, UserGroup
::GUESTS
]);
432 // check gravatar support
433 if (MODULE_GRAVATAR
&& Gravatar
::test($this->email
)) {
434 $this->additionalFields
['enableGravatar'] = 1;
439 'data' => \array_merge
($this->additionalFields
, [
440 'username' => $this->username
,
441 'email' => $this->email
,
442 'password' => $this->password
,
443 'blacklistMatches' => (!empty($this->blacklistMatches
)) ? JSON
::encode($this->blacklistMatches
) : '',
444 'signatureEnableHtml' => 1,
446 'groups' => $this->groupIDs
,
447 'languageIDs' => $this->visibleLanguages
,
448 'options' => $saveOptions,
449 'addDefaultGroups' => $addDefaultGroups,
451 $this->objectAction
= new UserAction([], 'create', $data);
452 $result = $this->objectAction
->executeAction();
453 /** @var User $user */
454 $user = $result['returnValues'];
455 $userEditor = new UserEditor($user);
458 WCF
::getSession()->changeUser($user);
460 // activation management
461 if (REGISTER_ACTIVATION_METHOD
== User
::REGISTER_ACTIVATION_NONE
&& empty($this->blacklistMatches
)) {
462 $this->message
= 'wcf.user.register.success';
464 UserGroupAssignmentHandler
::getInstance()->checkUsers([$user->userID
]);
465 } elseif (REGISTER_ACTIVATION_METHOD
& User
::REGISTER_ACTIVATION_USER
&& empty($this->blacklistMatches
)) {
466 // registering via 3rdParty leads to instant activation
467 if ($registerVia3rdParty) {
468 $this->message
= 'wcf.user.register.success';
470 $email = new Email();
471 $email->addRecipient(new UserMailbox(WCF
::getUser()));
473 WCF
::getLanguage()->getDynamicVariable('wcf.user.register.needActivation.mail.subject')
475 $email->setBody(new MimePartFacade([
476 new RecipientAwareTextMimePart('text/html', 'email_registerNeedActivation'),
477 new RecipientAwareTextMimePart('text/plain', 'email_registerNeedActivation'),
480 $this->message
= 'wcf.user.register.success.needActivation';
482 } elseif (REGISTER_ACTIVATION_METHOD
& User
::REGISTER_ACTIVATION_ADMIN ||
!empty($this->blacklistMatches
)) {
483 $this->message
= 'wcf.user.register.success.awaitActivation';
486 $this->fireNotificationEvent($user);
488 if ($this->captchaObjectType
) {
489 $this->captchaObjectType
->getProcessor()->reset();
492 if (WCF
::getSession()->getVar('noRegistrationCaptcha')) {
493 WCF
::getSession()->unregister('noRegistrationCaptcha');
497 WCF
::getSession()->unregister('registrationRandomFieldNames');
498 WCF
::getSession()->unregister('registrationStartTime');
501 // forward to index page
502 HeaderUtil
::delayedRedirect(
503 LinkHandler
::getInstance()->getLink(),
504 WCF
::getLanguage()->getDynamicVariable($this->message
, ['user' => $user]),
515 protected function fireNotificationEvent(User
$user)
517 $recipientIDs = $this->getRecipientsForNotificationEvent();
518 if (!empty($recipientIDs)) {
519 UserNotificationHandler
::getInstance()->fireEvent(
521 'com.woltlab.wcf.user.registration.notification',
522 new UserRegistrationUserNotificationObject($user),
532 protected function getRecipientsForNotificationEvent()
534 $sql = "SELECT userID
535 FROM wcf" . WCF_N
. "_user_to_group
538 FROM wcf" . WCF_N
. "_user_group_option_value
541 FROM wcf" . WCF_N
. "_user_group_option
546 $statement = WCF
::getDB()->prepareStatement($sql, 100);
547 $statement->execute([
548 'admin.user.canSearchUser',
552 return $statement->fetchAll(\PDO
::FETCH_COLUMN
);