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