1e5a6b4d4SGreg Roach<?php 23976b470SGreg Roach 3e5a6b4d4SGreg Roach/** 4e5a6b4d4SGreg Roach * webtrees: online genealogy 5e5a6b4d4SGreg Roach * Copyright (C) 2019 webtrees development team 6e5a6b4d4SGreg Roach * This program is free software: you can redistribute it and/or modify 7e5a6b4d4SGreg Roach * it under the terms of the GNU General Public License as published by 8e5a6b4d4SGreg Roach * the Free Software Foundation, either version 3 of the License, or 9e5a6b4d4SGreg Roach * (at your option) any later version. 10e5a6b4d4SGreg Roach * This program is distributed in the hope that it will be useful, 11e5a6b4d4SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of 12e5a6b4d4SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13e5a6b4d4SGreg Roach * GNU General Public License for more details. 14e5a6b4d4SGreg Roach * You should have received a copy of the GNU General Public License 15e5a6b4d4SGreg Roach * along with this program. If not, see <http://www.gnu.org/licenses/>. 16e5a6b4d4SGreg Roach */ 17e5a6b4d4SGreg Roachdeclare(strict_types=1); 18e5a6b4d4SGreg Roach 19e5a6b4d4SGreg Roachnamespace Fisharebest\Webtrees\Services; 20e5a6b4d4SGreg Roach 21e5a6b4d4SGreg Roachuse Fisharebest\Webtrees\Auth; 22a00bcc63SGreg Roachuse Fisharebest\Webtrees\Carbon; 23e5a6b4d4SGreg Roachuse Fisharebest\Webtrees\Individual; 24*5229eadeSGreg Roachuse Fisharebest\Webtrees\Tree; 25e5a6b4d4SGreg Roachuse Fisharebest\Webtrees\User; 26e5a6b4d4SGreg Roachuse Illuminate\Database\Capsule\Manager as DB; 27e5a6b4d4SGreg Roachuse Illuminate\Database\Query\JoinClause; 28e5a6b4d4SGreg Roachuse Illuminate\Support\Collection; 29*5229eadeSGreg Roachuse InvalidArgumentException; 306ccdf4f0SGreg Roachuse Psr\Http\Message\ServerRequestInterface; 31f1d4b4a2SGreg Roach 326ccdf4f0SGreg Roachuse function app; 33*5229eadeSGreg Roachuse function assert; 34e5a6b4d4SGreg Roach 35e5a6b4d4SGreg Roach/** 36e5a6b4d4SGreg Roach * Functions for managing users. 37e5a6b4d4SGreg Roach */ 38e5a6b4d4SGreg Roachclass UserService 39e5a6b4d4SGreg Roach{ 40e5a6b4d4SGreg Roach /** 41e5a6b4d4SGreg Roach * Find the user with a specified user_id. 42e5a6b4d4SGreg Roach * 43e5a6b4d4SGreg Roach * @param int|null $user_id 44e5a6b4d4SGreg Roach * 45e5a6b4d4SGreg Roach * @return User|null 46e5a6b4d4SGreg Roach */ 4725d7fe95SGreg Roach public function find($user_id): ?User 48e5a6b4d4SGreg Roach { 490b5fd0a6SGreg Roach return app('cache.array')->rememberForever(__CLASS__ . $user_id, static function () use ($user_id): ?User { 50e5a6b4d4SGreg Roach return DB::table('user') 51e5a6b4d4SGreg Roach ->where('user_id', '=', $user_id) 52e5a6b4d4SGreg Roach ->get() 53e5a6b4d4SGreg Roach ->map(User::rowMapper()) 54e5a6b4d4SGreg Roach ->first(); 55e5a6b4d4SGreg Roach }); 56e5a6b4d4SGreg Roach } 57e5a6b4d4SGreg Roach 58e5a6b4d4SGreg Roach /** 59e5a6b4d4SGreg Roach * Find the user with a specified email address. 60e5a6b4d4SGreg Roach * 61e5a6b4d4SGreg Roach * @param string $email 62e5a6b4d4SGreg Roach * 63e5a6b4d4SGreg Roach * @return User|null 64e5a6b4d4SGreg Roach */ 65e364afe4SGreg Roach public function findByEmail($email): ?User 66e5a6b4d4SGreg Roach { 67e5a6b4d4SGreg Roach return DB::table('user') 68e5a6b4d4SGreg Roach ->where('email', '=', $email) 69e5a6b4d4SGreg Roach ->get() 70e5a6b4d4SGreg Roach ->map(User::rowMapper()) 71e5a6b4d4SGreg Roach ->first(); 72e5a6b4d4SGreg Roach } 73e5a6b4d4SGreg Roach 74e5a6b4d4SGreg Roach /** 75e5a6b4d4SGreg Roach * Find the user with a specified user_name or email address. 76e5a6b4d4SGreg Roach * 77e5a6b4d4SGreg Roach * @param string $identifier 78e5a6b4d4SGreg Roach * 79e5a6b4d4SGreg Roach * @return User|null 80e5a6b4d4SGreg Roach */ 81e364afe4SGreg Roach public function findByIdentifier($identifier): ?User 82e5a6b4d4SGreg Roach { 83e5a6b4d4SGreg Roach return DB::table('user') 84e5a6b4d4SGreg Roach ->where('user_name', '=', $identifier) 85e5a6b4d4SGreg Roach ->orWhere('email', '=', $identifier) 86e5a6b4d4SGreg Roach ->get() 87e5a6b4d4SGreg Roach ->map(User::rowMapper()) 88e5a6b4d4SGreg Roach ->first(); 89e5a6b4d4SGreg Roach } 90e5a6b4d4SGreg Roach 91e5a6b4d4SGreg Roach /** 92e5a6b4d4SGreg Roach * Find the user(s) with a specified genealogy record. 93e5a6b4d4SGreg Roach * 94e5a6b4d4SGreg Roach * @param Individual $individual 95e5a6b4d4SGreg Roach * 9654c7f8dfSGreg Roach * @return Collection 97e5a6b4d4SGreg Roach */ 98e5a6b4d4SGreg Roach public function findByIndividual(Individual $individual): Collection 99e5a6b4d4SGreg Roach { 100e5a6b4d4SGreg Roach return DB::table('user') 101e5a6b4d4SGreg Roach ->join('user_gedcom_setting', 'user_gedcom_setting.user_id', '=', 'user.user_id') 102e5a6b4d4SGreg Roach ->where('gedcom_id', '=', $individual->tree()->id()) 103e5a6b4d4SGreg Roach ->where('setting_value', '=', $individual->xref()) 104e5a6b4d4SGreg Roach ->where('setting_name', '=', 'gedcomid') 105e5a6b4d4SGreg Roach ->select(['user.*']) 106e5a6b4d4SGreg Roach ->get() 107e5a6b4d4SGreg Roach ->map(User::rowMapper()); 108e5a6b4d4SGreg Roach } 109e5a6b4d4SGreg Roach 110e5a6b4d4SGreg Roach /** 111a00bcc63SGreg Roach * Find the user with a specified password reset token. 112a00bcc63SGreg Roach * 113a00bcc63SGreg Roach * @param string $token 114a00bcc63SGreg Roach * 115a00bcc63SGreg Roach * @return User|null 116a00bcc63SGreg Roach */ 117a00bcc63SGreg Roach public function findByToken(string $token): ?User 118a00bcc63SGreg Roach { 119a00bcc63SGreg Roach return DB::table('user') 120a00bcc63SGreg Roach ->join('user_setting AS us1', 'us1.user_id', '=', 'user.user_id') 121a00bcc63SGreg Roach ->where('us1.setting_name', '=', 'password-token') 122a00bcc63SGreg Roach ->where('us1.setting_value', '=', $token) 123a00bcc63SGreg Roach ->join('user_setting AS us2', 'us2.user_id', '=', 'user.user_id') 124a00bcc63SGreg Roach ->where('us2.setting_name', '=', 'password-token-expire') 125a00bcc63SGreg Roach ->where('us2.setting_value', '>', Carbon::now()->timestamp) 126a00bcc63SGreg Roach ->select(['user.*']) 127a00bcc63SGreg Roach ->get() 128a00bcc63SGreg Roach ->map(User::rowMapper()) 129a00bcc63SGreg Roach ->first(); 130a00bcc63SGreg Roach } 131a00bcc63SGreg Roach 132a00bcc63SGreg Roach /** 133e5a6b4d4SGreg Roach * Find the user with a specified user_name. 134e5a6b4d4SGreg Roach * 135e5a6b4d4SGreg Roach * @param string $user_name 136e5a6b4d4SGreg Roach * 137e5a6b4d4SGreg Roach * @return User|null 138e5a6b4d4SGreg Roach */ 139e364afe4SGreg Roach public function findByUserName($user_name): ?User 140e5a6b4d4SGreg Roach { 141e5a6b4d4SGreg Roach return DB::table('user') 142e5a6b4d4SGreg Roach ->where('user_name', '=', $user_name) 143e5a6b4d4SGreg Roach ->get() 144e5a6b4d4SGreg Roach ->map(User::rowMapper()) 145e5a6b4d4SGreg Roach ->first(); 146e5a6b4d4SGreg Roach } 147e5a6b4d4SGreg Roach 148e5a6b4d4SGreg Roach /** 149e5a6b4d4SGreg Roach * Get a list of all users. 150e5a6b4d4SGreg Roach * 15154c7f8dfSGreg Roach * @return Collection 152e5a6b4d4SGreg Roach */ 153e5a6b4d4SGreg Roach public function all(): Collection 154e5a6b4d4SGreg Roach { 155e5a6b4d4SGreg Roach return DB::table('user') 156e5a6b4d4SGreg Roach ->where('user_id', '>', 0) 157e5a6b4d4SGreg Roach ->orderBy('real_name') 158e5a6b4d4SGreg Roach ->get() 159e5a6b4d4SGreg Roach ->map(User::rowMapper()); 160e5a6b4d4SGreg Roach } 161e5a6b4d4SGreg Roach 162e5a6b4d4SGreg Roach /** 163e5a6b4d4SGreg Roach * Get a list of all administrators. 164e5a6b4d4SGreg Roach * 16554c7f8dfSGreg Roach * @return Collection 166e5a6b4d4SGreg Roach */ 167e5a6b4d4SGreg Roach public function administrators(): Collection 168e5a6b4d4SGreg Roach { 169e5a6b4d4SGreg Roach return DB::table('user') 1700b5fd0a6SGreg Roach ->join('user_setting', static function (JoinClause $join): void { 171e5a6b4d4SGreg Roach $join 172e5a6b4d4SGreg Roach ->on('user_setting.user_id', '=', 'user.user_id') 173e5a6b4d4SGreg Roach ->where('user_setting.setting_name', '=', 'canadmin') 174e5a6b4d4SGreg Roach ->where('user_setting.setting_value', '=', '1'); 175e5a6b4d4SGreg Roach }) 176e5a6b4d4SGreg Roach ->where('user.user_id', '>', 0) 177e5a6b4d4SGreg Roach ->orderBy('real_name') 178e5a6b4d4SGreg Roach ->select(['user.*']) 179e5a6b4d4SGreg Roach ->get() 180e5a6b4d4SGreg Roach ->map(User::rowMapper()); 181e5a6b4d4SGreg Roach } 182e5a6b4d4SGreg Roach 183e5a6b4d4SGreg Roach /** 184e5a6b4d4SGreg Roach * Get a list of all managers. 185e5a6b4d4SGreg Roach * 18654c7f8dfSGreg Roach * @return Collection 187e5a6b4d4SGreg Roach */ 188e5a6b4d4SGreg Roach public function managers(): Collection 189e5a6b4d4SGreg Roach { 190e5a6b4d4SGreg Roach return DB::table('user') 1910b5fd0a6SGreg Roach ->join('user_gedcom_setting', static function (JoinClause $join): void { 192e5a6b4d4SGreg Roach $join 193e5a6b4d4SGreg Roach ->on('user_gedcom_setting.user_id', '=', 'user.user_id') 194e5a6b4d4SGreg Roach ->where('user_gedcom_setting.setting_name', '=', 'canedit') 195e5a6b4d4SGreg Roach ->where('user_gedcom_setting.setting_value', '=', 'admin'); 196e5a6b4d4SGreg Roach }) 197e5a6b4d4SGreg Roach ->where('user.user_id', '>', 0) 198e5a6b4d4SGreg Roach ->orderBy('real_name') 199e5a6b4d4SGreg Roach ->select(['user.*']) 200e5a6b4d4SGreg Roach ->get() 201e5a6b4d4SGreg Roach ->map(User::rowMapper()); 202e5a6b4d4SGreg Roach } 203e5a6b4d4SGreg Roach 204e5a6b4d4SGreg Roach /** 205e5a6b4d4SGreg Roach * Get a list of all moderators. 206e5a6b4d4SGreg Roach * 20754c7f8dfSGreg Roach * @return Collection 208e5a6b4d4SGreg Roach */ 209e5a6b4d4SGreg Roach public function moderators(): Collection 210e5a6b4d4SGreg Roach { 211e5a6b4d4SGreg Roach return DB::table('user') 2120b5fd0a6SGreg Roach ->join('user_gedcom_setting', static function (JoinClause $join): void { 213e5a6b4d4SGreg Roach $join 214e5a6b4d4SGreg Roach ->on('user_gedcom_setting.user_id', '=', 'user.user_id') 215e5a6b4d4SGreg Roach ->where('user_gedcom_setting.setting_name', '=', 'canedit') 216e5a6b4d4SGreg Roach ->where('user_gedcom_setting.setting_value', '=', 'accept'); 217e5a6b4d4SGreg Roach }) 218e5a6b4d4SGreg Roach ->where('user.user_id', '>', 0) 219e5a6b4d4SGreg Roach ->orderBy('real_name') 220e5a6b4d4SGreg Roach ->select(['user.*']) 221e5a6b4d4SGreg Roach ->get() 222e5a6b4d4SGreg Roach ->map(User::rowMapper()); 223e5a6b4d4SGreg Roach } 224e5a6b4d4SGreg Roach 225e5a6b4d4SGreg Roach /** 226e5a6b4d4SGreg Roach * Get a list of all verified users. 227e5a6b4d4SGreg Roach * 22854c7f8dfSGreg Roach * @return Collection 229e5a6b4d4SGreg Roach */ 230e5a6b4d4SGreg Roach public function unapproved(): Collection 231e5a6b4d4SGreg Roach { 232e5a6b4d4SGreg Roach return DB::table('user') 2330b5fd0a6SGreg Roach ->join('user_setting', static function (JoinClause $join): void { 234e5a6b4d4SGreg Roach $join 235e5a6b4d4SGreg Roach ->on('user_setting.user_id', '=', 'user.user_id') 236e5a6b4d4SGreg Roach ->where('user_setting.setting_name', '=', 'verified_by_admin') 237e5a6b4d4SGreg Roach ->where('user_setting.setting_value', '=', '0'); 238e5a6b4d4SGreg Roach }) 239e5a6b4d4SGreg Roach ->where('user.user_id', '>', 0) 240e5a6b4d4SGreg Roach ->orderBy('real_name') 241e5a6b4d4SGreg Roach ->select(['user.*']) 242e5a6b4d4SGreg Roach ->get() 243e5a6b4d4SGreg Roach ->map(User::rowMapper()); 244e5a6b4d4SGreg Roach } 245e5a6b4d4SGreg Roach 246e5a6b4d4SGreg Roach /** 247e5a6b4d4SGreg Roach * Get a list of all verified users. 248e5a6b4d4SGreg Roach * 24954c7f8dfSGreg Roach * @return Collection 250e5a6b4d4SGreg Roach */ 251e5a6b4d4SGreg Roach public function unverified(): Collection 252e5a6b4d4SGreg Roach { 253e5a6b4d4SGreg Roach return DB::table('user') 2540b5fd0a6SGreg Roach ->join('user_setting', static function (JoinClause $join): void { 255e5a6b4d4SGreg Roach $join 256e5a6b4d4SGreg Roach ->on('user_setting.user_id', '=', 'user.user_id') 257e5a6b4d4SGreg Roach ->where('user_setting.setting_name', '=', 'verified') 258e5a6b4d4SGreg Roach ->where('user_setting.setting_value', '=', '0'); 259e5a6b4d4SGreg Roach }) 260e5a6b4d4SGreg Roach ->where('user.user_id', '>', 0) 261e5a6b4d4SGreg Roach ->orderBy('real_name') 262e5a6b4d4SGreg Roach ->select(['user.*']) 263e5a6b4d4SGreg Roach ->get() 264e5a6b4d4SGreg Roach ->map(User::rowMapper()); 265e5a6b4d4SGreg Roach } 266e5a6b4d4SGreg Roach 267e5a6b4d4SGreg Roach /** 268e5a6b4d4SGreg Roach * Get a list of all users who are currently logged in. 269e5a6b4d4SGreg Roach * 27054c7f8dfSGreg Roach * @return Collection 271e5a6b4d4SGreg Roach */ 272e5a6b4d4SGreg Roach public function allLoggedIn(): Collection 273e5a6b4d4SGreg Roach { 274e5a6b4d4SGreg Roach return DB::table('user') 275e5a6b4d4SGreg Roach ->join('session', 'session.user_id', '=', 'user.user_id') 276e5a6b4d4SGreg Roach ->where('user.user_id', '>', 0) 277e5a6b4d4SGreg Roach ->orderBy('real_name') 278e5a6b4d4SGreg Roach ->select(['user.*']) 279e5a6b4d4SGreg Roach ->distinct() 280e5a6b4d4SGreg Roach ->get() 281e5a6b4d4SGreg Roach ->map(User::rowMapper()); 282e5a6b4d4SGreg Roach } 283e5a6b4d4SGreg Roach 284e5a6b4d4SGreg Roach /** 285e5a6b4d4SGreg Roach * Create a new user. 286e5a6b4d4SGreg Roach * The calling code needs to check for duplicates identifiers before calling 287e5a6b4d4SGreg Roach * this function. 288e5a6b4d4SGreg Roach * 289e5a6b4d4SGreg Roach * @param string $user_name 290e5a6b4d4SGreg Roach * @param string $real_name 291e5a6b4d4SGreg Roach * @param string $email 292e5a6b4d4SGreg Roach * @param string $password 293e5a6b4d4SGreg Roach * 294e5a6b4d4SGreg Roach * @return User 295e5a6b4d4SGreg Roach */ 2966be338f5SGreg Roach public function create(string $user_name, string $real_name, string $email, string $password): User 297e5a6b4d4SGreg Roach { 298e5a6b4d4SGreg Roach DB::table('user')->insert([ 299e5a6b4d4SGreg Roach 'user_name' => $user_name, 300e5a6b4d4SGreg Roach 'real_name' => $real_name, 301e5a6b4d4SGreg Roach 'email' => $email, 302e5a6b4d4SGreg Roach 'password' => password_hash($password, PASSWORD_DEFAULT), 303e5a6b4d4SGreg Roach ]); 304e5a6b4d4SGreg Roach 305e5a6b4d4SGreg Roach $user_id = (int) DB::connection()->getPdo()->lastInsertId(); 306e5a6b4d4SGreg Roach 307e5a6b4d4SGreg Roach return new User($user_id, $user_name, $real_name, $email); 308e5a6b4d4SGreg Roach } 309e5a6b4d4SGreg Roach 310e5a6b4d4SGreg Roach /** 311e5a6b4d4SGreg Roach * Delete a user 312e5a6b4d4SGreg Roach * 313e5a6b4d4SGreg Roach * @param User $user 314e5a6b4d4SGreg Roach * 315e5a6b4d4SGreg Roach * @return void 316e5a6b4d4SGreg Roach */ 317e364afe4SGreg Roach public function delete(User $user): void 318e5a6b4d4SGreg Roach { 319e5a6b4d4SGreg Roach // Don't delete the logs, just set the user to null. 320e5a6b4d4SGreg Roach DB::table('log') 321e5a6b4d4SGreg Roach ->where('user_id', '=', $user->id()) 322e5a6b4d4SGreg Roach ->update(['user_id' => null]); 323e5a6b4d4SGreg Roach 324e5a6b4d4SGreg Roach // Take over the user’s pending changes. (What else could we do with them?) 325e5a6b4d4SGreg Roach DB::table('change') 326e5a6b4d4SGreg Roach ->where('user_id', '=', $user->id()) 327e5a6b4d4SGreg Roach ->where('status', '=', 'rejected') 328e5a6b4d4SGreg Roach ->delete(); 329e5a6b4d4SGreg Roach 330e5a6b4d4SGreg Roach DB::table('change') 331e5a6b4d4SGreg Roach ->where('user_id', '=', $user->id()) 332e5a6b4d4SGreg Roach ->update(['user_id' => Auth::id()]); 333e5a6b4d4SGreg Roach 334e5a6b4d4SGreg Roach // Delete settings and preferences 335e5a6b4d4SGreg Roach DB::table('block_setting') 336e5a6b4d4SGreg Roach ->join('block', 'block_setting.block_id', '=', 'block.block_id') 337e5a6b4d4SGreg Roach ->where('user_id', '=', $user->id()) 338e5a6b4d4SGreg Roach ->delete(); 339e5a6b4d4SGreg Roach 340e5a6b4d4SGreg Roach DB::table('block')->where('user_id', '=', $user->id())->delete(); 341e5a6b4d4SGreg Roach DB::table('user_gedcom_setting')->where('user_id', '=', $user->id())->delete(); 342e5a6b4d4SGreg Roach DB::table('user_setting')->where('user_id', '=', $user->id())->delete(); 343e5a6b4d4SGreg Roach DB::table('message')->where('user_id', '=', $user->id())->delete(); 344e5a6b4d4SGreg Roach DB::table('user')->where('user_id', '=', $user->id())->delete(); 345e5a6b4d4SGreg Roach } 34686730b84SGreg Roach 34786730b84SGreg Roach /** 3484db4b4a9SGreg Roach * @param User $contact_user 349a992e8c1SGreg Roach * @param ServerRequestInterface $request 35086730b84SGreg Roach * 35186730b84SGreg Roach * @return string 35286730b84SGreg Roach */ 353a992e8c1SGreg Roach public function contactLink(User $contact_user, ServerRequestInterface $request): string 354dcbe9044SGreg Roach { 355a992e8c1SGreg Roach $tree = $request->getAttribute('tree'); 356*5229eadeSGreg Roach assert($tree instanceof Tree, new InvalidArgumentException()); 357*5229eadeSGreg Roach 358a992e8c1SGreg Roach $user = $request->getAttribute('user'); 35986730b84SGreg Roach 36086730b84SGreg Roach if ($contact_user->getPreference('contactmethod') === 'mailto') { 36186730b84SGreg Roach $url = 'mailto:' . $contact_user->email(); 36286730b84SGreg Roach } elseif ($user instanceof User) { 36386730b84SGreg Roach // Logged-in users send direct messages 364d72b284aSGreg Roach $url = route('message', ['to' => $contact_user->userName(), 'tree' => $tree->name()]); 36586730b84SGreg Roach } else { 36686730b84SGreg Roach // Visitors use the contact form. 36786730b84SGreg Roach $url = route('contact', [ 368d72b284aSGreg Roach 'tree' => $tree ? $tree->name() : '', 36986730b84SGreg Roach 'to' => $contact_user->userName(), 370d72b284aSGreg Roach 'tree' => $tree->name(), 371f567c3d8SGreg Roach 'url' => (string) $request->getUri(), 37286730b84SGreg Roach ]); 37386730b84SGreg Roach } 37486730b84SGreg Roach 37586730b84SGreg Roach return '<a href="' . e($url) . '" dir="auto">' . e($contact_user->realName()) . '</a>'; 37686730b84SGreg Roach } 377e5a6b4d4SGreg Roach} 378