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