Replace @see tags with @inheritDoc tags
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / form / RegisterForm.class.php
1 <?php
2 namespace wcf\form;
3 use wcf\acp\form\UserAddForm;
4 use wcf\data\user\avatar\Gravatar;
5 use wcf\data\user\avatar\UserAvatarAction;
6 use wcf\data\user\group\UserGroup;
7 use wcf\data\user\User;
8 use wcf\data\user\UserAction;
9 use wcf\data\user\UserEditor;
10 use wcf\data\user\UserProfile;
11 use wcf\system\captcha\CaptchaHandler;
12 use wcf\system\exception\NamedUserException;
13 use wcf\system\exception\PermissionDeniedException;
14 use wcf\system\exception\SystemException;
15 use wcf\system\exception\UserInputException;
16 use wcf\system\language\LanguageFactory;
17 use wcf\system\mail\Mail;
18 use wcf\system\request\LinkHandler;
19 use wcf\system\user\authentication\UserAuthenticationFactory;
20 use wcf\system\Regex;
21 use wcf\system\WCF;
22 use wcf\util\HeaderUtil;
23 use wcf\util\StringUtil;
24 use wcf\util\UserRegistrationUtil;
25
26 /**
27 * Shows the user registration form.
28 *
29 * @author Marcel Werk
30 * @copyright 2001-2016 WoltLab GmbH
31 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
32 * @package com.woltlab.wcf
33 * @subpackage form
34 * @category Community Framework
35 */
36 class RegisterForm extends UserAddForm {
37 /**
38 * @inheritDoc
39 */
40 public $enableTracking = true;
41
42 /**
43 * true if external authentication is used
44 * @var boolean
45 */
46 public $isExternalAuthentication = false;
47
48 /**
49 * @inheritDoc
50 */
51 public $neededPermissions = [];
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 /**
61 * @inheritDoc
62 */
63 public $captchaObjectType = null;
64
65 /**
66 * @inheritDoc
67 */
68 public $captchaObjectTypeName = CAPTCHA_TYPE;
69
70 /**
71 * @inheritDoc
72 */
73 public $useCaptcha = REGISTER_USE_CAPTCHA;
74
75 /**
76 * field names
77 * @var array
78 */
79 public $randomFieldNames = [];
80
81 /**
82 * min number of seconds between form request and submit
83 * @var integer
84 */
85 public static $minRegistrationTime = 10;
86
87 /**
88 * @inheritDoc
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
109 if (WCF::getSession()->getVar('__3rdPartyProvider')) {
110 $this->isExternalAuthentication = true;
111 }
112 }
113
114 /**
115 * @inheritDoc
116 */
117 public function readFormParameters() {
118 parent::readFormParameters();
119
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']];
134
135 $this->groupIDs = [];
136
137 if ($this->captchaObjectType) {
138 $this->captchaObjectType->getProcessor()->readFormParameters();
139 }
140 }
141
142 /**
143 * wcf\acp\form\AbstractOptionListForm::initOptionHandler()
144 */
145 protected function initOptionHandler() {
146 $this->optionHandler->setInRegistration();
147 parent::initOptionHandler();
148 }
149
150 /**
151 * @inheritDoc
152 */
153 public function validate() {
154 // validate captcha first
155 $this->validateCaptcha();
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)) {
161 throw new UserInputException('registrationStartTime', []);
162 }
163 }
164
165 /**
166 * @inheritDoc
167 */
168 public function readData() {
169 if ($this->useCaptcha && $this->captchaObjectTypeName) {
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 }
178
179 if (WCF::getSession()->getVar('noRegistrationCaptcha')) {
180 $this->captchaObjectType = null;
181 }
182 }
183
184 parent::readData();
185
186 if (empty($_POST)) {
187 $this->languageID = WCF::getLanguage()->languageID;
188
189 if (WCF::getSession()->getVar('__username')) {
190 $this->username = WCF::getSession()->getVar('__username');
191 WCF::getSession()->unregister('__username');
192 }
193 if (WCF::getSession()->getVar('__email')) {
194 $this->email = $this->confirmEmail = WCF::getSession()->getVar('__email');
195 WCF::getSession()->unregister('__email');
196 }
197
198 WCF::getSession()->register('registrationStartTime', TIME_NOW);
199
200 // generate random field names
201 $this->randomFieldNames = [
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')
207 ];
208
209 WCF::getSession()->register('registrationRandomFieldNames', $this->randomFieldNames);
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 /**
221 * @inheritDoc
222 */
223 public function assignVariables() {
224 parent::assignVariables();
225
226 WCF::getTPL()->assign([
227 'captchaObjectType' => $this->captchaObjectType,
228 'isExternalAuthentication' => $this->isExternalAuthentication,
229 'randomFieldNames' => $this->randomFieldNames
230 ]);
231 }
232
233 /**
234 * @inheritDoc
235 */
236 public function show() {
237 AbstractForm::show();
238 }
239
240 /**
241 * Validates the captcha.
242 */
243 protected function validateCaptcha() {
244 if ($this->captchaObjectType) {
245 try {
246 $this->captchaObjectType->getProcessor()->validate();
247 }
248 catch (UserInputException $e) {
249 $this->errorType[$e->getField()] = $e->getType();
250 }
251 }
252 }
253
254 /**
255 * @inheritDoc
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 /**
267 * @inheritDoc
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 /**
281 * @inheritDoc
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 /**
292 * @inheritDoc
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) {
303 switch (WCF::getSession()->getVar('__3rdPartyProvider')) {
304 case 'github':
305 // GitHub
306 if (WCF::getSession()->getVar('__githubData')) {
307 $githubData = WCF::getSession()->getVar('__githubData');
308
309 $this->additionalFields['authData'] = 'github:'.$githubData['id'];
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
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'];
320 }
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
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'];
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
342 if (isset($facebookData['email']) && $facebookData['email'] == $this->email) {
343 $registerVia3rdParty = true;
344 }
345
346 if (isset($facebookData['gender']) && User::getUserOptionID('gender') !== null) $saveOptions[User::getUserOptionID('gender')] = ($facebookData['gender'] == 'male' ? UserProfile::GENDER_MALE : UserProfile::GENDER_FEMALE);
347
348 if (isset($facebookData['birthday']) && User::getUserOptionID('birthday') !== null) {
349 list($month, $day, $year) = explode('/', $facebookData['birthday']);
350 $saveOptions[User::getUserOptionID('birthday')] = $year.'-'.$month.'-'.$day;
351 }
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) {
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];
362 }
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
379 if (isset($googleData['emails'][0]['value']) && $googleData['emails'][0]['value'] == $this->email) {
380 $registerVia3rdParty = true;
381 }
382
383 if (isset($googleData['gender']) && User::getUserOptionID('gender') !== null) {
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 }
393 if (isset($googleData['birthday']) && User::getUserOptionID('birthday') !== null) $saveOptions[User::getUserOptionID('birthday')] = $googleData['birthday'];
394 if (isset($googleData['placesLived']) && User::getUserOptionID('location') !== null) {
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 }
406 }
407 break;
408 }
409
410 // create fake password
411 $this->password = StringUtil::getRandomID();
412 }
413
414 $this->additionalFields['languageID'] = $this->languageID;
415 if (LOG_IP_ADDRESS) $this->additionalFields['registrationIpAddress'] = WCF::getSession()->ipAddress;
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;
423 $this->groupIDs = UserGroup::getGroupIDsByType([UserGroup::EVERYONE, UserGroup::GUESTS]);
424 }
425
426 // check gravatar support
427 if (MODULE_GRAVATAR && Gravatar::test($this->email)) {
428 $this->additionalFields['enableGravatar'] = 1;
429 }
430
431 // create user
432 $data = [
433 'data' => array_merge($this->additionalFields, [
434 'username' => $this->username,
435 'email' => $this->email,
436 'password' => $this->password,
437 ]),
438 'groups' => $this->groupIDs,
439 'languageIDs' => $this->visibleLanguages,
440 'options' => $saveOptions,
441 'addDefaultGroups' => $addDefaultGroups
442 ];
443 $this->objectAction = new UserAction([], 'create', $data);
444 $result = $this->objectAction->executeAction();
445 $user = $result['returnValues'];
446 $userEditor = new UserEditor($user);
447
448 // update session
449 WCF::getSession()->changeUser($user);
450
451 // set avatar if provided
452 if (!empty($avatarURL)) {
453 $userAvatarAction = new UserAvatarAction([], 'fetchRemoteAvatar', [
454 'url' => $avatarURL,
455 'userEditor' => $userEditor
456 ]);
457 $userAvatarAction->executeAction();
458 }
459
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 {
470 $mail = new Mail([$this->username => $this->email],
471 WCF::getLanguage()->getDynamicVariable('wcf.user.register.needActivation.mail.subject'),
472 WCF::getLanguage()->getDynamicVariable('wcf.user.register.needActivation.mail', ['user' => $user])
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'),
490 $language->getDynamicVariable('wcf.user.register.notification.mail', ['user' => $user])
491 );
492 $mail->setLanguage($language);
493 $mail->send();
494 }
495
496 if ($this->captchaObjectType) {
497 $this->captchaObjectType->getProcessor()->reset();
498 }
499
500 if (WCF::getSession()->getVar('noRegistrationCaptcha')) {
501 WCF::getSession()->unregister('noRegistrationCaptcha');
502 }
503
504 // login user
505 UserAuthenticationFactory::getInstance()->getUserAuthentication()->storeAccessData($user, $this->username, $this->password);
506 WCF::getSession()->unregister('registrationRandomFieldNames');
507 WCF::getSession()->unregister('registrationStartTime');
508 $this->saved();
509
510 // forward to index page
511 HeaderUtil::delayedRedirect(LinkHandler::getInstance()->getLink(), WCF::getLanguage()->getDynamicVariable($this->message, ['user' => $user]), 15);
512 exit;
513 }
514 }