1<?php 2 3/** 4 * webtrees: online genealogy 5 * Copyright (C) 2019 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; 21 22use Closure; 23use Fisharebest\Webtrees\Contracts\UserInterface; 24use Illuminate\Database\Capsule\Manager as DB; 25use Illuminate\Support\Collection; 26use stdClass; 27 28/** 29 * Provide an interface to the wt_user table. 30 */ 31class User implements UserInterface 32{ 33 // For historic reasons, user preferences have inconsistent and confusing names. 34 public const PREF_AUTO_ACCEPT_EDITS = 'auto_accept'; 35 public const PREF_CONTACT_METHOD = 'contactmethod'; 36 public const PREF_IS_ACCOUNT_APPROVED = 'verified_by_admin'; 37 public const PREF_IS_ADMINISTRATOR = 'canadmin'; 38 public const PREF_IS_EMAIL_VERIFIED = 'verified'; 39 public const PREF_IS_VISIBLE_ONLINE = 'visibleonline'; 40 public const PREF_LANGUAGE = 'language'; 41 public const PREF_NEW_ACCOUNT_COMMENT = 'comment'; 42 public const PREF_TIMESTAMP_REGISTERED = 'reg_timestamp'; 43 public const PREF_TIMESTAMP_ACTIVE = 'sessiontime'; 44 public const PREF_TIME_ZONE = 'TIMEZONE'; 45 public const PREF_THEME = 'theme'; 46 public const PREF_VERIFICATION_TOKEN = 'reg_hashcode'; 47 48 // For historic reasons, user-tree preferences have inconsistent and confusing names. 49 public const PREF_TREE_ACCOUNT_XREF = 'gedcomid'; 50 public const PREF_TREE_DEFAULT_XREF = 'rootid'; 51 public const PREF_TREE_PATH_LENGTH = 'RELATIONSHIP_PATH_LENGTH'; 52 public const PREF_TREE_ROLE = 'canedit'; 53 54 // For historic reasons, roles have inconsistent and confusing names. 55 public const ROLE_VISITOR = 'none'; 56 public const ROLE_MEMBER = 'access'; 57 public const ROLE_EDITOR = 'edit'; 58 public const ROLE_MODERATOR = 'accept'; 59 public const ROLE_MANAGER = 'admin'; 60 61 /** @var int The primary key of this user. */ 62 private $user_id; 63 64 /** @var string The login name of this user. */ 65 private $user_name; 66 67 /** @var string The real (display) name of this user. */ 68 private $real_name; 69 70 /** @var string The email address of this user. */ 71 private $email; 72 73 /** @var string[] Cached copy of the wt_user_setting table. */ 74 private $preferences = []; 75 76 /** 77 * User constructor. 78 * 79 * @param int $user_id 80 * @param string $user_name 81 * @param string $real_name 82 * @param string $email 83 */ 84 public function __construct(int $user_id, string $user_name, string $real_name, string $email) 85 { 86 $this->user_id = $user_id; 87 $this->user_name = $user_name; 88 $this->real_name = $real_name; 89 $this->email = $email; 90 } 91 92 /** 93 * The user‘s internal identifier. 94 * 95 * @return int 96 */ 97 public function id(): int 98 { 99 return $this->user_id; 100 } 101 102 /** 103 * The users email address. 104 * 105 * @return string 106 */ 107 public function email(): string 108 { 109 return $this->email; 110 } 111 112 /** 113 * Set the email address of this user. 114 * 115 * @param string $email 116 * 117 * @return User 118 */ 119 public function setEmail($email): User 120 { 121 if ($this->email !== $email) { 122 $this->email = $email; 123 124 DB::table('user') 125 ->where('user_id', '=', $this->user_id) 126 ->update([ 127 'email' => $email, 128 ]); 129 } 130 131 return $this; 132 } 133 134 /** 135 * The user‘s real name. 136 * 137 * @return string 138 */ 139 public function realName(): string 140 { 141 return $this->real_name; 142 } 143 144 /** 145 * Set the real name of this user. 146 * 147 * @param string $real_name 148 * 149 * @return User 150 */ 151 public function setRealName($real_name): User 152 { 153 if ($this->real_name !== $real_name) { 154 $this->real_name = $real_name; 155 156 DB::table('user') 157 ->where('user_id', '=', $this->user_id) 158 ->update([ 159 'real_name' => $real_name, 160 ]); 161 } 162 163 return $this; 164 } 165 166 /** 167 * The user‘s login name. 168 * 169 * @return string 170 */ 171 public function userName(): string 172 { 173 return $this->user_name; 174 } 175 176 /** 177 * Set the login name for this user. 178 * 179 * @param string $user_name 180 * 181 * @return $this 182 */ 183 public function setUserName($user_name): self 184 { 185 if ($this->user_name !== $user_name) { 186 $this->user_name = $user_name; 187 188 DB::table('user') 189 ->where('user_id', '=', $this->user_id) 190 ->update([ 191 'user_name' => $user_name, 192 ]); 193 } 194 195 return $this; 196 } 197 198 /** 199 * Fetch a user option/setting from the wt_user_setting table. 200 * Since we'll fetch several settings for each user, and since there aren’t 201 * that many of them, fetch them all in one database query 202 * 203 * @param string $setting_name 204 * @param string $default 205 * 206 * @return string 207 */ 208 public function getPreference(string $setting_name, string $default = ''): string 209 { 210 $preferences = app('cache.array')->remember('user-prefs-' . $this->user_id, function (): Collection { 211 if ($this->user_id) { 212 return DB::table('user_setting') 213 ->where('user_id', '=', $this->user_id) 214 ->pluck('setting_value', 'setting_name'); 215 } 216 217 return new Collection(); 218 }); 219 220 return $preferences->get($setting_name, $default); 221 } 222 223 /** 224 * Update a setting for the user. 225 * 226 * @param string $setting_name 227 * @param string $setting_value 228 * 229 * @return void 230 */ 231 public function setPreference(string $setting_name, string $setting_value): void 232 { 233 if ($this->user_id !== 0 && $this->getPreference($setting_name) !== $setting_value) { 234 DB::table('user_setting')->updateOrInsert([ 235 'user_id' => $this->user_id, 236 'setting_name' => $setting_name, 237 ], [ 238 'setting_value' => $setting_value, 239 ]); 240 241 $this->preferences[$setting_name] = $setting_value; 242 } 243 } 244 245 /** 246 * Set the password of this user. 247 * 248 * @param string $password 249 * 250 * @return User 251 */ 252 public function setPassword(string $password): User 253 { 254 DB::table('user') 255 ->where('user_id', '=', $this->user_id) 256 ->update([ 257 'password' => password_hash($password, PASSWORD_DEFAULT), 258 ]); 259 260 return $this; 261 } 262 263 264 /** 265 * Validate a supplied password 266 * 267 * @param string $password 268 * 269 * @return bool 270 */ 271 public function checkPassword(string $password): bool 272 { 273 $password_hash = DB::table('user') 274 ->where('user_id', '=', $this->id()) 275 ->value('password'); 276 277 if ($password_hash !== null && password_verify($password, $password_hash)) { 278 if (password_needs_rehash($password_hash, PASSWORD_DEFAULT)) { 279 $this->setPassword($password); 280 } 281 282 return true; 283 } 284 285 return false; 286 } 287 288 /** 289 * A closure which will create an object from a database row. 290 * 291 * @return Closure 292 */ 293 public static function rowMapper(): Closure 294 { 295 return static function (stdClass $row): User { 296 return new self((int) $row->user_id, $row->user_name, $row->real_name, $row->email); 297 }; 298 } 299} 300