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