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