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 integer|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 User($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 = User::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 return $user; 144 } 145 146 /** 147 * Get a count of all users. 148 * 149 * @return integer 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 User($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 User($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 User($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 User($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 * 279 * @param string $password 280 * 281 * @return boolean 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 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