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