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