1a25f0a04SGreg Roach<?php 23976b470SGreg Roach 3a25f0a04SGreg Roach/** 4a25f0a04SGreg Roach * webtrees: online genealogy 58fcd0d32SGreg Roach * Copyright (C) 2019 webtrees development team 6a25f0a04SGreg Roach * This program is free software: you can redistribute it and/or modify 7a25f0a04SGreg Roach * it under the terms of the GNU General Public License as published by 8a25f0a04SGreg Roach * the Free Software Foundation, either version 3 of the License, or 9a25f0a04SGreg Roach * (at your option) any later version. 10a25f0a04SGreg Roach * This program is distributed in the hope that it will be useful, 11a25f0a04SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of 12a25f0a04SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13a25f0a04SGreg Roach * GNU General Public License for more details. 14a25f0a04SGreg Roach * You should have received a copy of the GNU General Public License 15a25f0a04SGreg Roach * along with this program. If not, see <http://www.gnu.org/licenses/>. 16a25f0a04SGreg Roach */ 17fcfa147eSGreg Roach 18c04dd3c1SGreg Roachdeclare(strict_types=1); 19c04dd3c1SGreg Roach 2076692c8bSGreg Roachnamespace Fisharebest\Webtrees; 21a25f0a04SGreg Roach 228b67c11aSGreg Roachuse Closure; 23e5a6b4d4SGreg Roachuse Fisharebest\Webtrees\Contracts\UserInterface; 2401461f86SGreg Roachuse Illuminate\Database\Capsule\Manager as DB; 252b7831a1SGreg Roachuse Illuminate\Support\Collection; 26b0b72ea4SGreg Roachuse stdClass; 2760bc3e3fSGreg Roach 28a25f0a04SGreg Roach/** 2976692c8bSGreg Roach * Provide an interface to the wt_user table. 30a25f0a04SGreg Roach */ 31e5a6b4d4SGreg Roachclass User implements UserInterface 32c1010edaSGreg Roach{ 33*7c4add84SGreg Roach // For historic reasons, user preferences have inconsistent and confusing names. 34*7c4add84SGreg Roach public const PREF_AUTO_ACCEPT_EDITS = 'auto_accept'; 35*7c4add84SGreg Roach public const PREF_CONTACT_METHOD = 'contactmethod'; 36*7c4add84SGreg Roach public const PREF_IS_ACCOUNT_APPROVED = 'verified_by_admin'; 37*7c4add84SGreg Roach public const PREF_IS_ADMINISTRATOR = 'canadmin'; 38*7c4add84SGreg Roach public const PREF_IS_EMAIL_VERIFIED = 'verified'; 39*7c4add84SGreg Roach public const PREF_IS_VISIBLE_ONLINE = 'visibleonline'; 40*7c4add84SGreg Roach public const PREF_LANGUAGE = 'language'; 41*7c4add84SGreg Roach public const PREF_NEW_ACCOUNT_COMMENT = 'comment'; 42*7c4add84SGreg Roach public const PREF_TIMESTAMP_REGISTERED = 'reg_timestamp'; 43*7c4add84SGreg Roach public const PREF_TIMESTAMP_ACTIVE = 'sessiontime'; 44*7c4add84SGreg Roach public const PREF_TIME_ZONE = 'TIMEZONE'; 45*7c4add84SGreg Roach public const PREF_THEME = 'theme'; 46*7c4add84SGreg Roach public const PREF_VERIFICATION_TOKEN = 'reg_hashcode'; 47*7c4add84SGreg Roach 48*7c4add84SGreg Roach // For historic reasons, user-tree preferences have inconsistent and confusing names. 49*7c4add84SGreg Roach public const PREF_TREE_ACCOUNT_XREF = 'gedcomid'; 50*7c4add84SGreg Roach public const PREF_TREE_DEFAULT_XREF = 'rootid'; 51*7c4add84SGreg Roach public const PREF_TREE_PATH_LENGTH = 'RELATIONSHIP_PATH_LENGTH'; 52*7c4add84SGreg Roach public const PREF_TREE_ROLE = 'canedit'; 53*7c4add84SGreg Roach 54*7c4add84SGreg Roach // For historic reasons, roles have inconsistent and confusing names. 55*7c4add84SGreg Roach public const ROLE_VISITOR = 'none'; 56*7c4add84SGreg Roach public const ROLE_MEMBER = 'access'; 57*7c4add84SGreg Roach public const ROLE_EDITOR = 'edit'; 58*7c4add84SGreg Roach public const ROLE_MODERATOR = 'accept'; 59*7c4add84SGreg Roach public const ROLE_MANAGER = 'admin'; 60*7c4add84SGreg Roach 61c04dd3c1SGreg Roach /** @var int The primary key of this user. */ 62a25f0a04SGreg Roach private $user_id; 63a25f0a04SGreg Roach 64a25f0a04SGreg Roach /** @var string The login name of this user. */ 65a25f0a04SGreg Roach private $user_name; 66a25f0a04SGreg Roach 67a25f0a04SGreg Roach /** @var string The real (display) name of this user. */ 68a25f0a04SGreg Roach private $real_name; 69a25f0a04SGreg Roach 70a25f0a04SGreg Roach /** @var string The email address of this user. */ 71a25f0a04SGreg Roach private $email; 72a25f0a04SGreg Roach 7315d603e7SGreg Roach /** @var string[] Cached copy of the wt_user_setting table. */ 7415d603e7SGreg Roach private $preferences = []; 75a25f0a04SGreg Roach 76a25f0a04SGreg Roach /** 77e5a6b4d4SGreg Roach * User constructor. 78f7fb7d41SGreg Roach * 798b67c11aSGreg Roach * @param int $user_id 808b67c11aSGreg Roach * @param string $user_name 818b67c11aSGreg Roach * @param string $real_name 828b67c11aSGreg Roach * @param string $email 83f7fb7d41SGreg Roach */ 84e5a6b4d4SGreg Roach public function __construct(int $user_id, string $user_name, string $real_name, string $email) 85c1010edaSGreg Roach { 868b67c11aSGreg Roach $this->user_id = $user_id; 878b67c11aSGreg Roach $this->user_name = $user_name; 888b67c11aSGreg Roach $this->real_name = $real_name; 898b67c11aSGreg Roach $this->email = $email; 908b67c11aSGreg Roach } 918b67c11aSGreg Roach 928b67c11aSGreg Roach /** 93e5a6b4d4SGreg Roach * The user‘s internal identifier. 94a25f0a04SGreg Roach * 95c04dd3c1SGreg Roach * @return int 96a25f0a04SGreg Roach */ 97895230eeSGreg Roach public function id(): int 98c1010edaSGreg Roach { 99a25f0a04SGreg Roach return $this->user_id; 100a25f0a04SGreg Roach } 101a25f0a04SGreg Roach 102a25f0a04SGreg Roach /** 103e5a6b4d4SGreg Roach * The users email address. 104a25f0a04SGreg Roach * 105a25f0a04SGreg Roach * @return string 106a25f0a04SGreg Roach */ 107e5a6b4d4SGreg Roach public function email(): string 108c1010edaSGreg Roach { 109a25f0a04SGreg Roach return $this->email; 110a25f0a04SGreg Roach } 111a25f0a04SGreg Roach 112a25f0a04SGreg Roach /** 113a25f0a04SGreg Roach * Set the email address of this user. 114a25f0a04SGreg Roach * 115a25f0a04SGreg Roach * @param string $email 116a25f0a04SGreg Roach * 117a25f0a04SGreg Roach * @return User 118a25f0a04SGreg Roach */ 1198f53f488SRico Sonntag public function setEmail($email): User 120c1010edaSGreg Roach { 121a25f0a04SGreg Roach if ($this->email !== $email) { 122a25f0a04SGreg Roach $this->email = $email; 12301461f86SGreg Roach 12401461f86SGreg Roach DB::table('user') 12501461f86SGreg Roach ->where('user_id', '=', $this->user_id) 12601461f86SGreg Roach ->update([ 12701461f86SGreg Roach 'email' => $email, 128c1010edaSGreg Roach ]); 129a25f0a04SGreg Roach } 130a25f0a04SGreg Roach 131a25f0a04SGreg Roach return $this; 132a25f0a04SGreg Roach } 133a25f0a04SGreg Roach 134a25f0a04SGreg Roach /** 135e5a6b4d4SGreg Roach * The user‘s real name. 136a25f0a04SGreg Roach * 137e5a6b4d4SGreg Roach * @return string 138e5a6b4d4SGreg Roach */ 139e5a6b4d4SGreg Roach public function realName(): string 140e5a6b4d4SGreg Roach { 141e5a6b4d4SGreg Roach return $this->real_name; 142e5a6b4d4SGreg Roach } 143e5a6b4d4SGreg Roach 144e5a6b4d4SGreg Roach /** 145e5a6b4d4SGreg Roach * Set the real name of this user. 146e5a6b4d4SGreg Roach * 147e5a6b4d4SGreg Roach * @param string $real_name 148a25f0a04SGreg Roach * 149a25f0a04SGreg Roach * @return User 150a25f0a04SGreg Roach */ 151e5a6b4d4SGreg Roach public function setRealName($real_name): User 152c1010edaSGreg Roach { 153e5a6b4d4SGreg Roach if ($this->real_name !== $real_name) { 154e5a6b4d4SGreg Roach $this->real_name = $real_name; 155e5a6b4d4SGreg Roach 15601461f86SGreg Roach DB::table('user') 15701461f86SGreg Roach ->where('user_id', '=', $this->user_id) 15801461f86SGreg Roach ->update([ 159e5a6b4d4SGreg Roach 'real_name' => $real_name, 160015c99a2SGreg Roach ]); 161e5a6b4d4SGreg Roach } 162e5a6b4d4SGreg Roach 163e5a6b4d4SGreg Roach return $this; 164e5a6b4d4SGreg Roach } 165e5a6b4d4SGreg Roach 166e5a6b4d4SGreg Roach /** 167e5a6b4d4SGreg Roach * The user‘s login name. 168e5a6b4d4SGreg Roach * 169e5a6b4d4SGreg Roach * @return string 170e5a6b4d4SGreg Roach */ 171e5a6b4d4SGreg Roach public function userName(): string 172e5a6b4d4SGreg Roach { 173e5a6b4d4SGreg Roach return $this->user_name; 174e5a6b4d4SGreg Roach } 175e5a6b4d4SGreg Roach 176e5a6b4d4SGreg Roach /** 177e5a6b4d4SGreg Roach * Set the login name for this user. 178e5a6b4d4SGreg Roach * 179e5a6b4d4SGreg Roach * @param string $user_name 180e5a6b4d4SGreg Roach * 181e5a6b4d4SGreg Roach * @return $this 182e5a6b4d4SGreg Roach */ 183e5a6b4d4SGreg Roach public function setUserName($user_name): self 184e5a6b4d4SGreg Roach { 185e5a6b4d4SGreg Roach if ($this->user_name !== $user_name) { 186e5a6b4d4SGreg Roach $this->user_name = $user_name; 187e5a6b4d4SGreg Roach 188e5a6b4d4SGreg Roach DB::table('user') 189e5a6b4d4SGreg Roach ->where('user_id', '=', $this->user_id) 190e5a6b4d4SGreg Roach ->update([ 191e5a6b4d4SGreg Roach 'user_name' => $user_name, 192e5a6b4d4SGreg Roach ]); 193e5a6b4d4SGreg Roach } 194a25f0a04SGreg Roach 195a25f0a04SGreg Roach return $this; 196a25f0a04SGreg Roach } 197a25f0a04SGreg Roach 198a25f0a04SGreg Roach /** 199a25f0a04SGreg Roach * Fetch a user option/setting from the wt_user_setting table. 200a25f0a04SGreg Roach * Since we'll fetch several settings for each user, and since there aren’t 201a25f0a04SGreg Roach * that many of them, fetch them all in one database query 202a25f0a04SGreg Roach * 203a25f0a04SGreg Roach * @param string $setting_name 20415d603e7SGreg Roach * @param string $default 205a25f0a04SGreg Roach * 20615d603e7SGreg Roach * @return string 207a25f0a04SGreg Roach */ 208e5a6b4d4SGreg Roach public function getPreference(string $setting_name, string $default = ''): string 209c1010edaSGreg Roach { 2102b7831a1SGreg Roach $preferences = app('cache.array')->rememberForever('user_setting' . $this->user_id, function (): Collection { 21183c7613eSGreg Roach if ($this->user_id) { 21283c7613eSGreg Roach return DB::table('user_setting') 21301461f86SGreg Roach ->where('user_id', '=', $this->user_id) 2142b7831a1SGreg Roach ->pluck('setting_value', 'setting_name'); 215a25f0a04SGreg Roach } 216e364afe4SGreg Roach 217e364afe4SGreg Roach return new Collection(); 21883c7613eSGreg Roach }); 219a25f0a04SGreg Roach 220*7c4add84SGreg Roach return $preferences->get($setting_name, $default); 221a25f0a04SGreg Roach } 222a25f0a04SGreg Roach 223a25f0a04SGreg Roach /** 224a25f0a04SGreg Roach * Update a setting for the user. 225a25f0a04SGreg Roach * 226a25f0a04SGreg Roach * @param string $setting_name 227a25f0a04SGreg Roach * @param string $setting_value 228a25f0a04SGreg Roach * 229*7c4add84SGreg Roach * @return void 230a25f0a04SGreg Roach */ 231*7c4add84SGreg Roach public function setPreference(string $setting_name, string $setting_value): void 232c1010edaSGreg Roach { 233c04dd3c1SGreg Roach if ($this->user_id !== 0 && $this->getPreference($setting_name) !== $setting_value) { 23401461f86SGreg Roach DB::table('user_setting')->updateOrInsert([ 23501461f86SGreg Roach 'user_id' => $this->user_id, 23601461f86SGreg Roach 'setting_name' => $setting_name, 23701461f86SGreg Roach ], [ 23801461f86SGreg Roach 'setting_value' => $setting_value, 239c1010edaSGreg Roach ]); 24015d603e7SGreg Roach 241a25f0a04SGreg Roach $this->preferences[$setting_name] = $setting_value; 242a25f0a04SGreg Roach } 243a25f0a04SGreg Roach 244611d1316SGreg Roach app('cache.array')->forget('user_setting' . $this->user_id); 245a25f0a04SGreg Roach } 246e5a6b4d4SGreg Roach 247e5a6b4d4SGreg Roach /** 248e5a6b4d4SGreg Roach * Set the password of this user. 249e5a6b4d4SGreg Roach * 250e5a6b4d4SGreg Roach * @param string $password 251e5a6b4d4SGreg Roach * 252e5a6b4d4SGreg Roach * @return User 253e5a6b4d4SGreg Roach */ 254e5a6b4d4SGreg Roach public function setPassword(string $password): User 255e5a6b4d4SGreg Roach { 256e5a6b4d4SGreg Roach DB::table('user') 257e5a6b4d4SGreg Roach ->where('user_id', '=', $this->user_id) 258e5a6b4d4SGreg Roach ->update([ 259e5a6b4d4SGreg Roach 'password' => password_hash($password, PASSWORD_DEFAULT), 260e5a6b4d4SGreg Roach ]); 261e5a6b4d4SGreg Roach 262e5a6b4d4SGreg Roach return $this; 263e5a6b4d4SGreg Roach } 264e5a6b4d4SGreg Roach 265e5a6b4d4SGreg Roach 266e5a6b4d4SGreg Roach /** 267e5a6b4d4SGreg Roach * Validate a supplied password 268e5a6b4d4SGreg Roach * 269e5a6b4d4SGreg Roach * @param string $password 270e5a6b4d4SGreg Roach * 271e5a6b4d4SGreg Roach * @return bool 272e5a6b4d4SGreg Roach */ 273e5a6b4d4SGreg Roach public function checkPassword(string $password): bool 274e5a6b4d4SGreg Roach { 275e5a6b4d4SGreg Roach $password_hash = DB::table('user') 2763e1b7b6eSGreg Roach ->where('user_id', '=', $this->id()) 277e5a6b4d4SGreg Roach ->value('password'); 278e5a6b4d4SGreg Roach 279e5a6b4d4SGreg Roach if ($password_hash !== null && password_verify($password, $password_hash)) { 280e5a6b4d4SGreg Roach if (password_needs_rehash($password_hash, PASSWORD_DEFAULT)) { 281e5a6b4d4SGreg Roach $this->setPassword($password); 282e5a6b4d4SGreg Roach } 283e5a6b4d4SGreg Roach 284e5a6b4d4SGreg Roach return true; 285e5a6b4d4SGreg Roach } 286e5a6b4d4SGreg Roach 287e5a6b4d4SGreg Roach return false; 288e5a6b4d4SGreg Roach } 2893e1b7b6eSGreg Roach 2903e1b7b6eSGreg Roach /** 2913e1b7b6eSGreg Roach * A closure which will create an object from a database row. 2923e1b7b6eSGreg Roach * 2933e1b7b6eSGreg Roach * @return Closure 2943e1b7b6eSGreg Roach */ 2953e1b7b6eSGreg Roach public static function rowMapper(): Closure 2963e1b7b6eSGreg Roach { 2976c2179e2SGreg Roach return static function (stdClass $row): User { 2983e1b7b6eSGreg Roach return new static((int) $row->user_id, $row->user_name, $row->real_name, $row->email); 2993e1b7b6eSGreg Roach }; 3003e1b7b6eSGreg Roach } 301a25f0a04SGreg Roach} 302