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