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