1<?php 2/** 3 * webtrees: online genealogy 4 * Copyright (C) 2018 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 stdclass; 21 22/** 23 * Provide an interface to the wt_user table. 24 */ 25class User 26{ 27 /** @var int The primary key of this user. */ 28 private $user_id; 29 30 /** @var string The login name of this user. */ 31 private $user_name; 32 33 /** @var string The real (display) name of this user. */ 34 private $real_name; 35 36 /** @var string The email address of this user. */ 37 private $email; 38 39 /** @var string[] Cached copy of the wt_user_setting table. */ 40 private $preferences = []; 41 42 /** @var User[] Only fetch users from the database once. */ 43 private static $cache = []; 44 45 /** 46 * Create a new user object from a row in the database. 47 * 48 * @param stdclass $user A row from the wt_user table 49 */ 50 public function __construct(stdClass $user) 51 { 52 $this->user_id = (int)$user->user_id; 53 $this->user_name = $user->user_name; 54 $this->real_name = $user->real_name; 55 $this->email = $user->email; 56 } 57 58 /** 59 * Create a new user. 60 * 61 * The calling code needs to check for duplicates identifiers before calling 62 * this function. 63 * 64 * @param string $user_name 65 * @param string $real_name 66 * @param string $email 67 * @param string $password 68 * 69 * @return User 70 */ 71 public static function create($user_name, $real_name, $email, $password) 72 { 73 Database::prepare( 74 "INSERT INTO `##user` (user_name, real_name, email, password) VALUES (:user_name, :real_name, :email, :password)" 75 )->execute([ 76 'user_name' => $user_name, 77 'real_name' => $real_name, 78 'email' => $email, 79 'password' => password_hash($password, PASSWORD_DEFAULT), 80 ]); 81 82 // Set default blocks for this user 83 $user = self::findByIdentifier($user_name); 84 Database::prepare( 85 "INSERT INTO `##block` (`user_id`, `location`, `block_order`, `module_name`)" . 86 " SELECT :user_id , `location`, `block_order`, `module_name` FROM `##block` WHERE `user_id` = -1" 87 )->execute(['user_id' => $user->getUserId()]); 88 89 return $user; 90 } 91 92 /** 93 * Delete a user 94 */ 95 public function delete() 96 { 97 // Don't delete the logs. 98 Database::prepare("UPDATE `##log` SET user_id=NULL WHERE user_id =?")->execute([$this->user_id]); 99 // Take over the user’s pending changes. (What else could we do with them?) 100 Database::prepare("DELETE FROM `##change` WHERE user_id=? AND status='rejected'")->execute([$this->user_id]); 101 Database::prepare("UPDATE `##change` SET user_id=? WHERE user_id=?")->execute([ 102 Auth::id(), 103 $this->user_id, 104 ]); 105 Database::prepare("DELETE `##block_setting` FROM `##block_setting` JOIN `##block` USING (block_id) WHERE user_id=?")->execute([$this->user_id]); 106 Database::prepare("DELETE FROM `##block` WHERE user_id=?")->execute([$this->user_id]); 107 Database::prepare("DELETE FROM `##user_gedcom_setting` WHERE user_id=?")->execute([$this->user_id]); 108 Database::prepare("DELETE FROM `##gedcom_setting` WHERE setting_value=? AND setting_name IN ('CONTACT_USER_ID', 'WEBMASTER_USER_ID')")->execute([(string)$this->user_id]); 109 Database::prepare("DELETE FROM `##user_setting` WHERE user_id=?")->execute([$this->user_id]); 110 Database::prepare("DELETE FROM `##message` WHERE user_id=?")->execute([$this->user_id]); 111 Database::prepare("DELETE FROM `##user` WHERE user_id=?")->execute([$this->user_id]); 112 } 113 114 /** 115 * Find the user with a specified user_id. 116 * 117 * @param int|null $user_id 118 * 119 * @return User|null 120 */ 121 public static function find($user_id) 122 { 123 if (!array_key_exists($user_id, self::$cache)) { 124 $row = Database::prepare( 125 "SELECT user_id, user_name, real_name, email FROM `##user` WHERE user_id = ?" 126 )->execute([$user_id])->fetchOneRow(); 127 if ($row) { 128 self::$cache[$user_id] = new self($row); 129 } else { 130 self::$cache[$user_id] = null; 131 } 132 } 133 134 return self::$cache[$user_id]; 135 } 136 137 /** 138 * Find the user with a specified email address. 139 * 140 * @param string $email 141 * 142 * @return User|null 143 */ 144 public static function findByEmail($email) 145 { 146 $user_id = Database::prepare( 147 "SELECT user_id FROM `##user` WHERE email = :email" 148 )->execute([ 149 'email' => $email, 150 ])->fetchOne(); 151 152 return self::find($user_id); 153 } 154 155 /** 156 * Find the user with a specified user_name or email address. 157 * 158 * @param string $identifier 159 * 160 * @return User|null 161 */ 162 public static function findByIdentifier($identifier) 163 { 164 $user_id = Database::prepare( 165 "SELECT user_id FROM `##user` WHERE ? IN (user_name, email)" 166 )->execute([$identifier])->fetchOne(); 167 168 return self::find($user_id); 169 } 170 171 /** 172 * Find the user with a specified genealogy record. 173 * 174 * @param Individual $individual 175 * 176 * @return User|null 177 */ 178 public static function findByIndividual(Individual $individual) 179 { 180 $user_id = Database::prepare( 181 "SELECT user_id" . 182 " FROM `##user_gedcom_setting`" . 183 " WHERE gedcom_id = :tree_id AND setting_name = 'gedcomid' AND setting_value = :xref" 184 )->execute([ 185 'tree_id' => $individual->getTree()->getTreeId(), 186 'xref' => $individual->getXref(), 187 ])->fetchOne(); 188 189 return self::find($user_id); 190 } 191 192 /** 193 * Find the user with a specified user_name. 194 * 195 * @param string $user_name 196 * 197 * @return User|null 198 */ 199 public static function findByUserName($user_name) 200 { 201 $user_id = Database::prepare( 202 "SELECT user_id FROM `##user` WHERE user_name = :user_name" 203 )->execute([ 204 'user_name' => $user_name, 205 ])->fetchOne(); 206 207 return self::find($user_id); 208 } 209 210 /** 211 * Find the latest user to register. 212 * 213 * @return User|null 214 */ 215 public static function findLatestToRegister() 216 { 217 $user_id = Database::prepare( 218 "SELECT u.user_id" . 219 " FROM `##user` u" . 220 " LEFT JOIN `##user_setting` us ON (u.user_id=us.user_id AND us.setting_name='reg_timestamp') " . 221 " ORDER BY us.setting_value DESC LIMIT 1" 222 )->execute()->fetchOne(); 223 224 return self::find($user_id); 225 } 226 227 /** 228 * Get a list of all users. 229 * 230 * @return User[] 231 */ 232 public static function all() 233 { 234 $rows = Database::prepare( 235 "SELECT user_id, user_name, real_name, email" . 236 " FROM `##user`" . 237 " WHERE user_id > 0" . 238 " ORDER BY real_name" 239 )->fetchAll(); 240 241 return array_map(function ($row) { 242 return new static($row); 243 }, $rows); 244 } 245 246 /** 247 * Get a list of all administrators. 248 * 249 * @return User[] 250 */ 251 public static function administrators() 252 { 253 $rows = Database::prepare( 254 "SELECT user_id, user_name, real_name, email" . 255 " FROM `##user`" . 256 " JOIN `##user_setting` USING (user_id)" . 257 " WHERE user_id > 0 AND setting_name = 'canadmin' AND setting_value = '1'" . 258 " ORDER BY real_name" 259 )->fetchAll(); 260 261 return array_map(function ($row) { 262 return new static($row); 263 }, $rows); 264 } 265 266 /** Validate a supplied password 267 * 268 * @param string $password 269 * 270 * @return bool 271 */ 272 public function checkPassword($password) 273 { 274 $password_hash = Database::prepare( 275 "SELECT password FROM `##user` WHERE user_id = ?" 276 )->execute([$this->user_id])->fetchOne(); 277 278 if (password_verify($password, $password_hash)) { 279 if (password_needs_rehash($password_hash, PASSWORD_DEFAULT)) { 280 $this->setPassword($password); 281 } 282 283 return true; 284 } else { 285 return false; 286 } 287 } 288 289 /** 290 * Get a list of all managers. 291 * 292 * @return User[] 293 */ 294 public static function managers() 295 { 296 $rows = Database::prepare( 297 "SELECT user_id, user_name, real_name, email" . 298 " FROM `##user` JOIN `##user_gedcom_setting` USING (user_id)" . 299 " WHERE setting_name = 'canedit' AND setting_value='admin'" . 300 " GROUP BY user_id, real_name" . 301 " ORDER BY real_name" 302 )->fetchAll(); 303 304 return array_map(function ($row) { 305 return new static($row); 306 }, $rows); 307 } 308 309 /** 310 * Get a list of all moderators. 311 * 312 * @return User[] 313 */ 314 public static function moderators() 315 { 316 $rows = Database::prepare( 317 "SELECT user_id, user_name, real_name, email" . 318 " FROM `##user` JOIN `##user_gedcom_setting` USING (user_id)" . 319 " WHERE setting_name = 'canedit' AND setting_value='accept'" . 320 " GROUP BY user_id, real_name" . 321 " ORDER BY real_name" 322 )->fetchAll(); 323 324 return array_map(function ($row) { 325 return new static($row); 326 }, $rows); 327 } 328 329 /** 330 * Get a list of all verified users. 331 * 332 * @return User[] 333 */ 334 public static function unapproved() 335 { 336 $rows = Database::prepare( 337 "SELECT user_id, user_name, real_name, email" . 338 " FROM `##user` JOIN `##user_setting` USING (user_id)" . 339 " WHERE setting_name = 'verified_by_admin' AND setting_value = '0'" . 340 " ORDER BY real_name" 341 )->fetchAll(); 342 343 return array_map(function ($row) { 344 return new static($row); 345 }, $rows); 346 } 347 348 /** 349 * Get a list of all verified users. 350 * 351 * @return User[] 352 */ 353 public static function unverified() 354 { 355 $rows = Database::prepare( 356 "SELECT user_id, user_name, real_name, email" . 357 " FROM `##user` JOIN `##user_setting` USING (user_id)" . 358 " WHERE setting_name = 'verified' AND setting_value = '0'" . 359 " ORDER BY real_name" 360 )->fetchAll(); 361 362 return array_map(function ($row) { 363 return new static($row); 364 }, $rows); 365 } 366 367 /** 368 * Get a list of all users who are currently logged in. 369 * 370 * @return User[] 371 */ 372 public static function allLoggedIn() 373 { 374 $rows = Database::prepare( 375 "SELECT DISTINCT user_id, user_name, real_name, email" . 376 " FROM `##user`" . 377 " JOIN `##session` USING (user_id)" 378 )->fetchAll(); 379 380 return array_map(function ($row) { 381 return new static($row); 382 }, $rows); 383 } 384 385 /** 386 * Get the numeric ID for this user. 387 * 388 * @return int 389 */ 390 public function getUserId(): int 391 { 392 return $this->user_id; 393 } 394 395 /** 396 * Get the login name for this user. 397 * 398 * @return string 399 */ 400 public function getUserName() 401 { 402 return $this->user_name; 403 } 404 405 /** 406 * Set the login name for this user. 407 * 408 * @param string $user_name 409 * 410 * @return $this 411 */ 412 public function setUserName($user_name) 413 { 414 if ($this->user_name !== $user_name) { 415 $this->user_name = $user_name; 416 Database::prepare( 417 "UPDATE `##user` SET user_name = ? WHERE user_id = ?" 418 )->execute([ 419 $user_name, 420 $this->user_id, 421 ]); 422 } 423 424 return $this; 425 } 426 427 /** 428 * Get the real name of this user. 429 * 430 * @return string 431 */ 432 public function getRealName() 433 { 434 return $this->real_name; 435 } 436 437 /** 438 * Set the real name of this user. 439 * 440 * @param string $real_name 441 * 442 * @return User 443 */ 444 public function setRealName($real_name) 445 { 446 if ($this->real_name !== $real_name) { 447 $this->real_name = $real_name; 448 Database::prepare( 449 "UPDATE `##user` SET real_name = ? WHERE user_id = ?" 450 )->execute([ 451 $real_name, 452 $this->user_id, 453 ]); 454 } 455 456 return $this; 457 } 458 459 /** 460 * Get the email address of this user. 461 * 462 * @return string 463 */ 464 public function getEmail() 465 { 466 return $this->email; 467 } 468 469 /** 470 * Set the email address of this user. 471 * 472 * @param string $email 473 * 474 * @return User 475 */ 476 public function setEmail($email) 477 { 478 if ($this->email !== $email) { 479 $this->email = $email; 480 Database::prepare( 481 "UPDATE `##user` SET email = ? WHERE user_id = ?" 482 )->execute([ 483 $email, 484 $this->user_id, 485 ]); 486 } 487 488 return $this; 489 } 490 491 /** 492 * Set the password of this user. 493 * 494 * @param string $password 495 * 496 * @return User 497 */ 498 public function setPassword($password) 499 { 500 Database::prepare( 501 "UPDATE `##user` SET password = :password WHERE user_id = :user_id" 502 )->execute([ 503 'password' => password_hash($password, PASSWORD_DEFAULT), 504 'user_id' => $this->user_id, 505 ]); 506 507 return $this; 508 } 509 510 /** 511 * Fetch a user option/setting from the wt_user_setting table. 512 * 513 * Since we'll fetch several settings for each user, and since there aren’t 514 * that many of them, fetch them all in one database query 515 * 516 * @param string $setting_name 517 * @param string $default 518 * 519 * @return string 520 */ 521 public function getPreference($setting_name, $default = '') 522 { 523 if (empty($this->preferences) && $this->user_id !== 0) { 524 $this->preferences = Database::prepare( 525 "SELECT setting_name, setting_value" . 526 " FROM `##user_setting`" . 527 " WHERE user_id = :user_id" 528 )->execute([ 529 'user_id' => $this->user_id, 530 ])->fetchAssoc(); 531 } 532 533 if (!array_key_exists($setting_name, $this->preferences)) { 534 $this->preferences[$setting_name] = $default; 535 } 536 537 return $this->preferences[$setting_name]; 538 } 539 540 /** 541 * Update a setting for the user. 542 * 543 * @param string $setting_name 544 * @param string $setting_value 545 * 546 * @return User 547 */ 548 public function setPreference($setting_name, $setting_value) 549 { 550 if ($this->user_id !== 0 && $this->getPreference($setting_name) !== $setting_value) { 551 Database::prepare("REPLACE INTO `##user_setting` (user_id, setting_name, setting_value) VALUES (?, ?, LEFT(?, 255))") 552 ->execute([ 553 $this->user_id, 554 $setting_name, 555 $setting_value, 556 ]); 557 558 $this->preferences[$setting_name] = $setting_value; 559 } 560 561 return $this; 562 } 563} 564