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