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