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