1<?php 2 3/** 4 * webtrees: online genealogy 5 * Copyright (C) 2023 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 <https://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; 25 26use function is_string; 27 28/** 29 * Provide an interface to the wt_user table. 30 */ 31class User implements UserInterface 32{ 33 private int $user_id; 34 35 private string $user_name; 36 37 private string $real_name; 38 39 private string $email; 40 41 /** @var array<string,string> */ 42 private array $preferences; 43 44 /** 45 * User constructor. 46 * 47 * @param int $user_id 48 * @param string $user_name 49 * @param string $real_name 50 * @param string $email 51 */ 52 public function __construct(int $user_id, string $user_name, string $real_name, string $email) 53 { 54 $this->user_id = $user_id; 55 $this->user_name = $user_name; 56 $this->real_name = $real_name; 57 $this->email = $email; 58 59 $this->preferences = DB::table('user_setting') 60 ->where('user_id', '=', $this->user_id) 61 ->pluck('setting_value', 'setting_name') 62 ->all(); 63 } 64 65 /** 66 * The user‘s internal identifier. 67 * 68 * @return int 69 */ 70 public function id(): int 71 { 72 return $this->user_id; 73 } 74 75 /** 76 * The users email address. 77 * 78 * @return string 79 */ 80 public function email(): string 81 { 82 return $this->email; 83 } 84 85 /** 86 * Set the email address of this user. 87 * 88 * @param string $email 89 * 90 * @return User 91 */ 92 public function setEmail(string $email): User 93 { 94 if ($this->email !== $email) { 95 $this->email = $email; 96 97 DB::table('user') 98 ->where('user_id', '=', $this->user_id) 99 ->update([ 100 'email' => $email, 101 ]); 102 } 103 104 return $this; 105 } 106 107 /** 108 * The user‘s real name. 109 * 110 * @return string 111 */ 112 public function realName(): string 113 { 114 return $this->real_name; 115 } 116 117 /** 118 * Set the real name of this user. 119 * 120 * @param string $real_name 121 * 122 * @return User 123 */ 124 public function setRealName(string $real_name): User 125 { 126 if ($this->real_name !== $real_name) { 127 $this->real_name = $real_name; 128 129 DB::table('user') 130 ->where('user_id', '=', $this->user_id) 131 ->update([ 132 'real_name' => $real_name, 133 ]); 134 } 135 136 return $this; 137 } 138 139 /** 140 * The user‘s login name. 141 * 142 * @return string 143 */ 144 public function userName(): string 145 { 146 return $this->user_name; 147 } 148 149 /** 150 * Set the login name for this user. 151 * 152 * @param string $user_name 153 * 154 * @return self 155 */ 156 public function setUserName(string $user_name): self 157 { 158 if ($this->user_name !== $user_name) { 159 $this->user_name = $user_name; 160 161 DB::table('user') 162 ->where('user_id', '=', $this->user_id) 163 ->update([ 164 'user_name' => $user_name, 165 ]); 166 } 167 168 return $this; 169 } 170 171 /** 172 * Fetch a user option/setting from the wt_user_setting table. 173 * Since we'll fetch several settings for each user, and since there aren't 174 * that many of them, fetch them all in one database query 175 * 176 * @param string $setting_name 177 * @param string $default 178 * 179 * @return string 180 */ 181 public function getPreference(string $setting_name, string $default = ''): string 182 { 183 return $this->preferences[$setting_name] ?? $default; 184 } 185 186 /** 187 * Update a setting for the user. 188 * 189 * @param string $setting_name 190 * @param string $setting_value 191 * 192 * @return void 193 */ 194 public function setPreference(string $setting_name, string $setting_value): void 195 { 196 if ($this->getPreference($setting_name) !== $setting_value) { 197 DB::table('user_setting')->updateOrInsert([ 198 'user_id' => $this->user_id, 199 'setting_name' => $setting_name, 200 ], [ 201 'setting_value' => $setting_value, 202 ]); 203 204 $this->preferences[$setting_name] = $setting_value; 205 } 206 } 207 208 /** 209 * Set the password of this user. 210 * 211 * @param string $password 212 * 213 * @return User 214 */ 215 public function setPassword(#[\SensitiveParameter] string $password): User 216 { 217 DB::table('user') 218 ->where('user_id', '=', $this->user_id) 219 ->update([ 220 'password' => password_hash($password, PASSWORD_DEFAULT), 221 ]); 222 223 return $this; 224 } 225 226 227 /** 228 * Validate a supplied password 229 * 230 * @param string $password 231 * 232 * @return bool 233 */ 234 public function checkPassword(#[\SensitiveParameter] string $password): bool 235 { 236 $password_hash = DB::table('user') 237 ->where('user_id', '=', $this->id()) 238 ->value('password'); 239 240 if (is_string($password_hash) && password_verify($password, $password_hash)) { 241 if (password_needs_rehash($password_hash, PASSWORD_DEFAULT)) { 242 $this->setPassword($password); 243 } 244 245 return true; 246 } 247 248 return false; 249 } 250 251 /** 252 * A closure which will create an object from a database row. 253 * 254 * @return Closure 255 */ 256 public static function rowMapper(): Closure 257 { 258 return static fn (object $row): User => new self((int) $row->user_id, $row->user_name, $row->real_name, $row->email); 259 } 260} 261