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