Replace @see tags with @inheritDoc tags
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / form / RegisterForm.class.php
CommitLineData
320f4a6d
MW
1<?php
2namespace wcf\form;
3use wcf\acp\form\UserAddForm;
4use wcf\data\user\avatar\Gravatar;
5use wcf\data\user\avatar\UserAvatarAction;
6use wcf\data\user\group\UserGroup;
7use wcf\data\user\User;
8use wcf\data\user\UserAction;
9use wcf\data\user\UserEditor;
10use wcf\data\user\UserProfile;
96714cab 11use wcf\system\captcha\CaptchaHandler;
320f4a6d
MW
12use wcf\system\exception\NamedUserException;
13use wcf\system\exception\PermissionDeniedException;
96714cab 14use wcf\system\exception\SystemException;
320f4a6d
MW
15use wcf\system\exception\UserInputException;
16use wcf\system\language\LanguageFactory;
17use wcf\system\mail\Mail;
320f4a6d
MW
18use wcf\system\request\LinkHandler;
19use wcf\system\user\authentication\UserAuthenticationFactory;
20use wcf\system\Regex;
21use wcf\system\WCF;
22use wcf\util\HeaderUtil;
23use wcf\util\StringUtil;
24use wcf\util\UserRegistrationUtil;
25
26/**
27 * Shows the user registration form.
28 *
29 * @author Marcel Werk
efc17365 30 * @copyright 2001-2016 WoltLab GmbH
320f4a6d 31 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
f4f05aa5 32 * @package com.woltlab.wcf
320f4a6d
MW
33 * @subpackage form
34 * @category Community Framework
35 */
36class RegisterForm extends UserAddForm {
320f4a6d 37 /**
0fcfe5f6 38 * @inheritDoc
320f4a6d
MW
39 */
40 public $enableTracking = true;
41
42 /**
43 * true if external authentication is used
44 * @var boolean
45 */
46 public $isExternalAuthentication = false;
47
48 /**
0fcfe5f6 49 * @inheritDoc
320f4a6d 50 */
efc17365 51 public $neededPermissions = [];
320f4a6d
MW
52
53 /**
54 * holds a language variable with information about the registration process
55 * e.g. if you need to activate your account
56 * @var string
57 */
58 public $message = '';
59
60 /**
0fcfe5f6 61 * @inheritDoc
320f4a6d 62 */
96714cab 63 public $captchaObjectType = null;
320f4a6d
MW
64
65 /**
0fcfe5f6 66 * @inheritDoc
fbb526f2
MS
67 */
68 public $captchaObjectTypeName = CAPTCHA_TYPE;
69
70 /**
0fcfe5f6 71 * @inheritDoc
320f4a6d 72 */
fbb526f2 73 public $useCaptcha = REGISTER_USE_CAPTCHA;
320f4a6d 74
78b5eb60
MW
75 /**
76 * field names
06355ec3 77 * @var array
78b5eb60 78 */
efc17365 79 public $randomFieldNames = [];
78b5eb60 80
320f4a6d
MW
81 /**
82 * min number of seconds between form request and submit
83 * @var integer
84 */
0d01979f 85 public static $minRegistrationTime = 10;
320f4a6d
MW
86
87 /**
0fcfe5f6 88 * @inheritDoc
320f4a6d
MW
89 */
90 public function readParameters() {
91 parent::readParameters();
92
93 // user is already registered
94 if (WCF::getUser()->userID) {
95 throw new PermissionDeniedException();
96 }
97
98 // registration disabled
99 if (REGISTER_DISABLED) {
100 throw new NamedUserException(WCF::getLanguage()->get('wcf.user.register.error.disabled'));
101 }
102
103 // check disclaimer
104 if (REGISTER_ENABLE_DISCLAIMER && !WCF::getSession()->getVar('disclaimerAccepted')) {
105 HeaderUtil::redirect(LinkHandler::getInstance()->getLink('Disclaimer'));
106 exit;
107 }
108
f5f2f408 109 if (WCF::getSession()->getVar('__3rdPartyProvider')) {
320f4a6d
MW
110 $this->isExternalAuthentication = true;
111 }
112 }
113
320f4a6d 114 /**
0fcfe5f6 115 * @inheritDoc
320f4a6d
MW
116 */
117 public function readFormParameters() {
118 parent::readFormParameters();
e3369fd2 119
78b5eb60
MW
120 if (!empty($this->username) || !empty($this->email)) {
121 throw new PermissionDeniedException();
122 }
123
124 $this->randomFieldNames = WCF::getSession()->getVar('registrationRandomFieldNames');
125 if ($this->randomFieldNames === null) {
126 throw new PermissionDeniedException();
127 }
128
129 if (isset($_POST[$this->randomFieldNames['username']])) $this->username = StringUtil::trim($_POST[$this->randomFieldNames['username']]);
130 if (isset($_POST[$this->randomFieldNames['email']])) $this->email = StringUtil::trim($_POST[$this->randomFieldNames['email']]);
131 if (isset($_POST[$this->randomFieldNames['confirmEmail']])) $this->confirmEmail = StringUtil::trim($_POST[$this->randomFieldNames['confirmEmail']]);
132 if (isset($_POST[$this->randomFieldNames['password']])) $this->password = $_POST[$this->randomFieldNames['password']];
133 if (isset($_POST[$this->randomFieldNames['confirmPassword']])) $this->confirmPassword = $_POST[$this->randomFieldNames['confirmPassword']];
320f4a6d 134
efc17365 135 $this->groupIDs = [];
96714cab
MS
136
137 if ($this->captchaObjectType) {
138 $this->captchaObjectType->getProcessor()->readFormParameters();
139 }
320f4a6d
MW
140 }
141
78b5eb60
MW
142 /**
143 * wcf\acp\form\AbstractOptionListForm::initOptionHandler()
144 */
145 protected function initOptionHandler() {
146 $this->optionHandler->setInRegistration();
147 parent::initOptionHandler();
148 }
149
320f4a6d 150 /**
0fcfe5f6 151 * @inheritDoc
320f4a6d
MW
152 */
153 public function validate() {
154 // validate captcha first
96714cab 155 $this->validateCaptcha();
320f4a6d
MW
156
157 parent::validate();
158
159 // validate registration time
160 if (!$this->isExternalAuthentication && (!WCF::getSession()->getVar('registrationStartTime') || (TIME_NOW - WCF::getSession()->getVar('registrationStartTime')) < self::$minRegistrationTime)) {
efc17365 161 throw new UserInputException('registrationStartTime', []);
320f4a6d
MW
162 }
163 }
164
165 /**
0fcfe5f6 166 * @inheritDoc
320f4a6d
MW
167 */
168 public function readData() {
fbb526f2 169 if ($this->useCaptcha && $this->captchaObjectTypeName) {
96714cab
MS
170 $this->captchaObjectType = CaptchaHandler::getInstance()->getObjectTypeByName($this->captchaObjectTypeName);
171 if ($this->captchaObjectType === null) {
172 throw new SystemException("Unknown captcha object type with id '".$this->captchaObjectTypeName."'");
173 }
174
175 if (!$this->captchaObjectType->getProcessor()->isAvailable()) {
176 $this->captchaObjectType = null;
177 }
ed7256dd
MS
178
179 if (WCF::getSession()->getVar('noRegistrationCaptcha')) {
180 $this->captchaObjectType = null;
ed7256dd 181 }
96714cab
MS
182 }
183
320f4a6d
MW
184 parent::readData();
185
186 if (empty($_POST)) {
187 $this->languageID = WCF::getLanguage()->languageID;
188
fe55c173
MW
189 if (WCF::getSession()->getVar('__username')) {
190 $this->username = WCF::getSession()->getVar('__username');
efc17365
AE
191 WCF::getSession()->unregister('__username');
192 }
fe55c173
MW
193 if (WCF::getSession()->getVar('__email')) {
194 $this->email = $this->confirmEmail = WCF::getSession()->getVar('__email');
195 WCF::getSession()->unregister('__email');
196 }
197
320f4a6d 198 WCF::getSession()->register('registrationStartTime', TIME_NOW);
78b5eb60
MW
199
200 // generate random field names
efc17365 201 $this->randomFieldNames = [
78b5eb60
MW
202 'username' => UserRegistrationUtil::getRandomFieldName('username'),
203 'email' => UserRegistrationUtil::getRandomFieldName('email'),
204 'confirmEmail' => UserRegistrationUtil::getRandomFieldName('confirmEmail'),
205 'password' => UserRegistrationUtil::getRandomFieldName('password'),
206 'confirmPassword' => UserRegistrationUtil::getRandomFieldName('confirmPassword')
efc17365 207 ];
78b5eb60
MW
208
209 WCF::getSession()->register('registrationRandomFieldNames', $this->randomFieldNames);
320f4a6d
MW
210 }
211 }
212
213 /**
214 * Reads option tree on page init.
215 */
216 protected function readOptionTree() {
217 $this->optionTree = $this->optionHandler->getOptionTree('profile');
218 }
219
220 /**
0fcfe5f6 221 * @inheritDoc
320f4a6d
MW
222 */
223 public function assignVariables() {
224 parent::assignVariables();
225
efc17365 226 WCF::getTPL()->assign([
96714cab 227 'captchaObjectType' => $this->captchaObjectType,
320f4a6d 228 'isExternalAuthentication' => $this->isExternalAuthentication,
78b5eb60 229 'randomFieldNames' => $this->randomFieldNames
efc17365 230 ]);
320f4a6d
MW
231 }
232
233 /**
0fcfe5f6 234 * @inheritDoc
320f4a6d
MW
235 */
236 public function show() {
237 AbstractForm::show();
238 }
239
240 /**
241 * Validates the captcha.
242 */
243 protected function validateCaptcha() {
96714cab 244 if ($this->captchaObjectType) {
320f4a6d 245 try {
96714cab 246 $this->captchaObjectType->getProcessor()->validate();
320f4a6d
MW
247 }
248 catch (UserInputException $e) {
249 $this->errorType[$e->getField()] = $e->getType();
250 }
251 }
252 }
253
254 /**
0fcfe5f6 255 * @inheritDoc
320f4a6d
MW
256 */
257 protected function validateUsername($username) {
258 parent::validateUsername($username);
259
260 // check for min-max length
261 if (!UserRegistrationUtil::isValidUsername($username)) {
262 throw new UserInputException('username', 'notValid');
263 }
264 }
265
266 /**
0fcfe5f6 267 * @inheritDoc
320f4a6d
MW
268 */
269 protected function validatePassword($password, $confirmPassword) {
270 if (!$this->isExternalAuthentication) {
271 parent::validatePassword($password, $confirmPassword);
272
273 // check security of the given password
274 if (!UserRegistrationUtil::isSecurePassword($password)) {
275 throw new UserInputException('password', 'notSecure');
276 }
277 }
278 }
279
280 /**
0fcfe5f6 281 * @inheritDoc
320f4a6d
MW
282 */
283 protected function validateEmail($email, $confirmEmail) {
284 parent::validateEmail($email, $confirmEmail);
285
286 if (!UserRegistrationUtil::isValidEmail($email)) {
287 throw new UserInputException('email', 'notValid');
288 }
289 }
290
291 /**
0fcfe5f6 292 * @inheritDoc
320f4a6d
MW
293 */
294 public function save() {
295 AbstractForm::save();
296
297 // get options
298 $saveOptions = $this->optionHandler->save();
299 $registerVia3rdParty = false;
300
301 $avatarURL = '';
302 if ($this->isExternalAuthentication) {
f5f2f408
TD
303 switch (WCF::getSession()->getVar('__3rdPartyProvider')) {
304 case 'github':
305 // GitHub
306 if (WCF::getSession()->getVar('__githubData')) {
307 $githubData = WCF::getSession()->getVar('__githubData');
308
e37288a1 309 $this->additionalFields['authData'] = 'github:'.$githubData['id'];
f5f2f408
TD
310
311 WCF::getSession()->unregister('__githubData');
312 WCF::getSession()->unregister('__githubToken');
313
314 if (WCF::getSession()->getVar('__email') && WCF::getSession()->getVar('__email') == $this->email) {
315 $registerVia3rdParty = true;
316 }
317
200b2d30
JR
318 if (isset($githubData['bio']) && User::getUserOptionID('aboutMe') !== null) $saveOptions[User::getUserOptionID('aboutMe')] = $githubData['bio'];
319 if (isset($githubData['location']) && User::getUserOptionID('location') !== null) $saveOptions[User::getUserOptionID('location')] = $githubData['location'];
320f4a6d 320 }
f5f2f408
TD
321 break;
322 case 'twitter':
323 // Twitter
324 if (WCF::getSession()->getVar('__twitterData')) {
325 $twitterData = WCF::getSession()->getVar('__twitterData');
326 $this->additionalFields['authData'] = 'twitter:'.$twitterData['user_id'];
327
328 WCF::getSession()->unregister('__twitterData');
329
200b2d30
JR
330 if (isset($twitterData['description']) && User::getUserOptionID('aboutMe') !== null) $saveOptions[User::getUserOptionID('aboutMe')] = $twitterData['description'];
331 if (isset($twitterData['location']) && User::getUserOptionID('location') !== null) $saveOptions[User::getUserOptionID('location')] = $twitterData['location'];
f5f2f408
TD
332 }
333 break;
334 case 'facebook':
335 // Facebook
336 if (WCF::getSession()->getVar('__facebookData')) {
337 $facebookData = WCF::getSession()->getVar('__facebookData');
338 $this->additionalFields['authData'] = 'facebook:'.$facebookData['id'];
339
340 WCF::getSession()->unregister('__facebookData');
341
7acd1b8f 342 if (isset($facebookData['email']) && $facebookData['email'] == $this->email) {
f5f2f408
TD
343 $registerVia3rdParty = true;
344 }
345
200b2d30 346 if (isset($facebookData['gender']) && User::getUserOptionID('gender') !== null) $saveOptions[User::getUserOptionID('gender')] = ($facebookData['gender'] == 'male' ? UserProfile::GENDER_MALE : UserProfile::GENDER_FEMALE);
f5f2f408 347
200b2d30 348 if (isset($facebookData['birthday']) && User::getUserOptionID('birthday') !== null) {
f5f2f408
TD
349 list($month, $day, $year) = explode('/', $facebookData['birthday']);
350 $saveOptions[User::getUserOptionID('birthday')] = $year.'-'.$month.'-'.$day;
351 }
200b2d30
JR
352 if (isset($facebookData['bio']) && User::getUserOptionID('bio') !== null) $saveOptions[User::getUserOptionID('aboutMe')] = $facebookData['bio'];
353 if (isset($facebookData['location']) && User::getUserOptionID('location') !== null) $saveOptions[User::getUserOptionID('location')] = $facebookData['location']['name'];
354 if (isset($facebookData['website']) && User::getUserOptionID('website') !== null) {
270805b2
MW
355 $urls = preg_split('/[\s,;]/', $facebookData['website'], -1, PREG_SPLIT_NO_EMPTY);
356 if (!empty($urls)) {
357 if (!Regex::compile('^https?://')->match($urls[0])) {
358 $urls[0] = 'http://' . $urls[0];
359 }
360
361 $saveOptions[User::getUserOptionID('homepage')] = $urls[0];
f5f2f408 362 }
f5f2f408
TD
363 }
364
365 // avatar
366 if (isset($facebookData['picture']) && !$facebookData['picture']['data']['is_silhouette']) {
367 $avatarURL = $facebookData['picture']['data']['url'];
368 }
369 }
370 break;
371 case 'google':
372 // Google Plus
373 if (WCF::getSession()->getVar('__googleData')) {
374 $googleData = WCF::getSession()->getVar('__googleData');
375 $this->additionalFields['authData'] = 'google:'.$googleData['id'];
376
377 WCF::getSession()->unregister('__googleData');
378
3222d876 379 if (isset($googleData['emails'][0]['value']) && $googleData['emails'][0]['value'] == $this->email) {
f5f2f408
TD
380 $registerVia3rdParty = true;
381 }
382
200b2d30 383 if (isset($googleData['gender']) && User::getUserOptionID('gender') !== null) {
f5f2f408
TD
384 switch ($googleData['gender']) {
385 case 'male':
386 $saveOptions[User::getUserOptionID('gender')] = UserProfile::GENDER_MALE;
387 break;
388 case 'female':
389 $saveOptions[User::getUserOptionID('gender')] = UserProfile::GENDER_FEMALE;
390 break;
391 }
392 }
200b2d30
JR
393 if (isset($googleData['birthday']) && User::getUserOptionID('birthday') !== null) $saveOptions[User::getUserOptionID('birthday')] = $googleData['birthday'];
394 if (isset($googleData['placesLived']) && User::getUserOptionID('location') !== null) {
3222d876
TD
395 // save primary location
396 $saveOptions[User::getUserOptionID('location')] = current(array_map(
397 function ($element) { return $element['value']; },
398 array_filter($googleData['placesLived'], function ($element) { return isset($element['primary']) && $element['primary']; })
399 ));
400 }
401
402 // avatar
403 if (isset($googleData['image']['url'])) {
404 $avatarURL = $googleData['image']['url'];
405 }
6918ec39 406 }
f5f2f408 407 break;
320f4a6d
MW
408 }
409
410 // create fake password
411 $this->password = StringUtil::getRandomID();
412 }
413
414 $this->additionalFields['languageID'] = $this->languageID;
7e57ed3b 415 if (LOG_IP_ADDRESS) $this->additionalFields['registrationIpAddress'] = WCF::getSession()->ipAddress;
320f4a6d
MW
416
417 // generate activation code
418 $addDefaultGroups = true;
419 if ((REGISTER_ACTIVATION_METHOD == 1 && !$registerVia3rdParty) || REGISTER_ACTIVATION_METHOD == 2) {
420 $activationCode = UserRegistrationUtil::getActivationCode();
421 $this->additionalFields['activationCode'] = $activationCode;
422 $addDefaultGroups = false;
efc17365 423 $this->groupIDs = UserGroup::getGroupIDsByType([UserGroup::EVERYONE, UserGroup::GUESTS]);
320f4a6d
MW
424 }
425
426 // check gravatar support
427 if (MODULE_GRAVATAR && Gravatar::test($this->email)) {
428 $this->additionalFields['enableGravatar'] = 1;
429 }
430
431 // create user
efc17365
AE
432 $data = [
433 'data' => array_merge($this->additionalFields, [
320f4a6d
MW
434 'username' => $this->username,
435 'email' => $this->email,
436 'password' => $this->password,
efc17365 437 ]),
320f4a6d 438 'groups' => $this->groupIDs,
7623b12f 439 'languageIDs' => $this->visibleLanguages,
320f4a6d
MW
440 'options' => $saveOptions,
441 'addDefaultGroups' => $addDefaultGroups
efc17365
AE
442 ];
443 $this->objectAction = new UserAction([], 'create', $data);
320f4a6d
MW
444 $result = $this->objectAction->executeAction();
445 $user = $result['returnValues'];
446 $userEditor = new UserEditor($user);
447
19f1a99a
TD
448 // update session
449 WCF::getSession()->changeUser($user);
450
320f4a6d
MW
451 // set avatar if provided
452 if (!empty($avatarURL)) {
efc17365 453 $userAvatarAction = new UserAvatarAction([], 'fetchRemoteAvatar', [
320f4a6d
MW
454 'url' => $avatarURL,
455 'userEditor' => $userEditor
efc17365 456 ]);
320f4a6d
MW
457 $userAvatarAction->executeAction();
458 }
459
320f4a6d
MW
460 // activation management
461 if (REGISTER_ACTIVATION_METHOD == 0) {
462 $this->message = 'wcf.user.register.success';
463 }
464 else if (REGISTER_ACTIVATION_METHOD == 1) {
465 // registering via 3rdParty leads to instant activation
466 if ($registerVia3rdParty) {
467 $this->message = 'wcf.user.register.success';
468 }
469 else {
efc17365 470 $mail = new Mail([$this->username => $this->email],
320f4a6d 471 WCF::getLanguage()->getDynamicVariable('wcf.user.register.needActivation.mail.subject'),
efc17365 472 WCF::getLanguage()->getDynamicVariable('wcf.user.register.needActivation.mail', ['user' => $user])
320f4a6d
MW
473 );
474 $mail->send();
475 $this->message = 'wcf.user.register.needActivation';
476 }
477 }
478 else if (REGISTER_ACTIVATION_METHOD == 2) {
479 $this->message = 'wcf.user.register.awaitActivation';
480 }
481
482 // notify admin
483 if (REGISTER_ADMIN_NOTIFICATION) {
484 // get default language
485 $language = LanguageFactory::getInstance()->getLanguage(LanguageFactory::getInstance()->getDefaultLanguageID());
486
487 // send mail
488 $mail = new Mail(MAIL_ADMIN_ADDRESS,
489 $language->getDynamicVariable('wcf.user.register.notification.mail.subject'),
efc17365 490 $language->getDynamicVariable('wcf.user.register.notification.mail', ['user' => $user])
320f4a6d
MW
491 );
492 $mail->setLanguage($language);
493 $mail->send();
494 }
495
96714cab
MS
496 if ($this->captchaObjectType) {
497 $this->captchaObjectType->getProcessor()->reset();
498 }
499
f1011f33
MS
500 if (WCF::getSession()->getVar('noRegistrationCaptcha')) {
501 WCF::getSession()->unregister('noRegistrationCaptcha');
502 }
503
320f4a6d
MW
504 // login user
505 UserAuthenticationFactory::getInstance()->getUserAuthentication()->storeAccessData($user, $this->username, $this->password);
78b5eb60
MW
506 WCF::getSession()->unregister('registrationRandomFieldNames');
507 WCF::getSession()->unregister('registrationStartTime');
320f4a6d
MW
508 $this->saved();
509
510 // forward to index page
efc17365 511 HeaderUtil::delayedRedirect(LinkHandler::getInstance()->getLink(), WCF::getLanguage()->getDynamicVariable($this->message, ['user' => $user]), 15);
320f4a6d
MW
512 exit;
513 }
514}