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