1<?php 2 3/** 4 * webtrees: online genealogy 5 * Copyright (C) 2020 webtrees development team 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation, either version 3 of the License, or 9 * (at your option) any later version. 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18declare(strict_types=1); 19 20namespace Fisharebest\Webtrees\Http\RequestHandlers; 21 22use Exception; 23use Fisharebest\Webtrees\Exceptions\HttpNotFoundException; 24use Fisharebest\Webtrees\FlashMessages; 25use Fisharebest\Webtrees\Http\ViewResponseTrait; 26use Fisharebest\Webtrees\I18N; 27use Fisharebest\Webtrees\Log; 28use Fisharebest\Webtrees\NoReplyUser; 29use Fisharebest\Webtrees\Services\CaptchaService; 30use Fisharebest\Webtrees\Services\EmailService; 31use Fisharebest\Webtrees\Services\UserService; 32use Fisharebest\Webtrees\Site; 33use Fisharebest\Webtrees\SiteUser; 34use Fisharebest\Webtrees\Tree; 35use Fisharebest\Webtrees\TreeUser; 36use Fisharebest\Webtrees\User; 37use Illuminate\Database\Capsule\Manager as DB; 38use Illuminate\Support\Str; 39use Psr\Http\Message\ResponseInterface; 40use Psr\Http\Message\ServerRequestInterface; 41use Psr\Http\Server\RequestHandlerInterface; 42 43use function view; 44 45/** 46 * Process a user registration. 47 */ 48class RegisterAction implements RequestHandlerInterface 49{ 50 use ViewResponseTrait; 51 52 /** @var CaptchaService */ 53 private $captcha_service; 54 55 /** @var EmailService */ 56 private $email_service; 57 58 /** @var UserService */ 59 private $user_service; 60 61 /** 62 * RegisterController constructor. 63 * 64 * @param CaptchaService $captcha_service 65 * @param EmailService $email_service 66 * @param UserService $user_service 67 */ 68 public function __construct( 69 CaptchaService $captcha_service, 70 EmailService $email_service, 71 UserService $user_service 72 ) { 73 $this->captcha_service = $captcha_service; 74 $this->email_service = $email_service; 75 $this->user_service = $user_service; 76 } 77 78 /** 79 * Perform a registration. 80 * 81 * @param ServerRequestInterface $request 82 * 83 * @return ResponseInterface 84 */ 85 public function handle(ServerRequestInterface $request): ResponseInterface 86 { 87 $tree = $request->getAttribute('tree'); 88 89 $this->checkRegistrationAllowed(); 90 91 $params = (array) $request->getParsedBody(); 92 93 $comment = $params['comment'] ?? ''; 94 $email = $params['email'] ?? ''; 95 $password = $params['password'] ?? ''; 96 $realname = $params['realname'] ?? ''; 97 $username = $params['username'] ?? ''; 98 99 try { 100 if ($this->captcha_service->isRobot($request)) { 101 throw new Exception(I18N::translate('Please try again.')); 102 } 103 104 $this->doValidateRegistration($request, $username, $email, $realname, $comment, $password); 105 } catch (Exception $ex) { 106 FlashMessages::addMessage($ex->getMessage(), 'danger'); 107 108 return redirect(route(RegisterPage::class, [ 109 'comment' => $comment, 110 'email' => $email, 111 'realname' => $realname, 112 'username' => $username, 113 ])); 114 } 115 116 Log::addAuthenticationLog('User registration requested for: ' . $username); 117 118 $user = $this->user_service->create($username, $realname, $email, $password); 119 $token = Str::random(32); 120 121 $user->setPreference(User::PREF_LANGUAGE, I18N::languageTag()); 122 $user->setPreference(User::PREF_TIME_ZONE, Site::getPreference('TIMEZONE', 'UTC')); 123 $user->setPreference(User::PREF_IS_EMAIL_VERIFIED, ''); 124 $user->setPreference(User::PREF_IS_ACCOUNT_APPROVED, ''); 125 $user->setPreference(User::PREF_TIMESTAMP_REGISTERED, date('U')); 126 $user->setPreference(User::PREF_VERIFICATION_TOKEN, $token); 127 $user->setPreference(User::PREF_CONTACT_METHOD, 'messaging2'); 128 $user->setPreference(User::PREF_NEW_ACCOUNT_COMMENT, $comment); 129 $user->setPreference(User::PREF_IS_VISIBLE_ONLINE, '1'); 130 $user->setPreference(User::PREF_AUTO_ACCEPT_EDITS, ''); 131 $user->setPreference(User::PREF_IS_ADMINISTRATOR, ''); 132 $user->setPreference(User::PREF_TIMESTAMP_ACTIVE, '0'); 133 134 $base_url = $request->getAttribute('base_url'); 135 $reply_to = $tree instanceof Tree ? new TreeUser($tree) : new SiteUser(); 136 137 $verify_url = route(VerifyEmail::class, [ 138 'username' => $user->userName(), 139 'token' => $token, 140 'tree' => $tree instanceof Tree ? $tree->name() : null, 141 ]); 142 143 // Send a verification message to the user. 144 /* I18N: %s is a server name/URL */ 145 $this->email_service->send( 146 new Siteuser(), 147 $user, 148 $reply_to, 149 I18N::translate('Your registration at %s', $base_url), 150 view('emails/register-user-text', ['user' => $user, 'base_url' => $base_url, 'verify_url' => $verify_url]), 151 view('emails/register-user-html', ['user' => $user, 'base_url' => $base_url, 'verify_url' => $verify_url]) 152 ); 153 154 // Tell the administrators about the registration. 155 foreach ($this->user_service->administrators() as $administrator) { 156 I18N::init($administrator->getPreference(User::PREF_LANGUAGE)); 157 158 /* I18N: %s is a server name/URL */ 159 $subject = I18N::translate('New registration at %s', $base_url); 160 161 $body_text = view('emails/register-notify-text', [ 162 'user' => $user, 163 'comments' => $comment, 164 'base_url' => $base_url, 165 'tree' => $tree, 166 ]); 167 168 $body_html = view('emails/register-notify-html', [ 169 'user' => $user, 170 'comments' => $comment, 171 'base_url' => $base_url, 172 'tree' => $tree, 173 ]); 174 175 176 /* I18N: %s is a server name/URL */ 177 $this->email_service->send( 178 new SiteUser(), 179 $administrator, 180 new NoReplyUser(), 181 $subject, 182 $body_text, 183 $body_html 184 ); 185 186 $mail1_method = $administrator->getPreference(User::PREF_CONTACT_METHOD); 187 if ($mail1_method !== 'messaging3' && $mail1_method !== 'mailto' && $mail1_method !== 'none') { 188 DB::table('message')->insert([ 189 'sender' => $user->email(), 190 'ip_address' => $request->getAttribute('client-ip'), 191 'user_id' => $administrator->id(), 192 'subject' => $subject, 193 'body' => $body_text, 194 ]); 195 } 196 } 197 198 $title = I18N::translate('Request a new user account'); 199 200 return $this->viewResponse('register-success-page', [ 201 'title' => $title, 202 'tree' => $tree, 203 'user' => $user, 204 ]); 205 } 206 207 /** 208 * Check that visitors are allowed to register on this site. 209 * 210 * @return void 211 * @throws HttpNotFoundException 212 */ 213 private function checkRegistrationAllowed(): void 214 { 215 if (Site::getPreference('USE_REGISTRATION_MODULE') !== '1') { 216 throw new HttpNotFoundException(); 217 } 218 } 219 220 /** 221 * Check the registration details. 222 * 223 * @param ServerRequestInterface $request 224 * @param string $username 225 * @param string $email 226 * @param string $realname 227 * @param string $comments 228 * @param string $password 229 * 230 * @return void 231 * @throws Exception 232 */ 233 private function doValidateRegistration(ServerRequestInterface $request, string $username, string $email, string $realname, string $comments, string $password): void 234 { 235 // All fields are required 236 if ($username === '' || $email === '' || $realname === '' || $comments === '' || $password === '') { 237 throw new Exception(I18N::translate('All fields must be completed.')); 238 } 239 240 // Username already exists 241 if ($this->user_service->findByUserName($username) !== null) { 242 throw new Exception(I18N::translate('Duplicate username. A user with that username already exists. Please choose another username.')); 243 } 244 245 // Email already exists 246 if ($this->user_service->findByEmail($email) !== null) { 247 throw new Exception(I18N::translate('Duplicate email address. A user with that email already exists.')); 248 } 249 250 $base_url = $request->getAttribute('base_url'); 251 252 // No external links 253 if (preg_match('/(?!' . preg_quote($base_url, '/') . ')(((?:http|https):\/\/)[a-zA-Z0-9.-]+)/', $comments, $match)) { 254 throw new Exception(I18N::translate('You are not allowed to send messages that contain external links.') . ' ' . I18N::translate('You should delete the “%1$s” from “%2$s” and try again.', e($match[2]), e($match[1]))); 255 } 256 } 257} 258