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