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', 'user_setting.user_id', '=', 'user.user_id') 173 ->where('user_setting.setting_name', '=', 'canadmin') 174 ->where('user_setting.setting_value', '=', '1') 175 ->where('user.user_id', '>', 0) 176 ->orderBy('real_name') 177 ->select(['user.*']) 178 ->get() 179 ->map(User::rowMapper()); 180 } 181 182 /** 183 * Get a list of all managers. 184 * 185 * @return Collection 186 */ 187 public function managers(): Collection 188 { 189 return DB::table('user') 190 ->join('user_gedcom_setting', '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 ->where('user.user_id', '>', 0) 194 ->groupBy(['user.user_id']) 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', 'user_gedcom_setting.user_id', '=', 'user.user_id') 210 ->where('user_gedcom_setting.setting_name', '=', 'canedit') 211 ->where('user_gedcom_setting.setting_value', '=', 'accept') 212 ->where('user.user_id', '>', 0) 213 ->groupBy(['user.user_id']) 214 ->orderBy('real_name') 215 ->select(['user.*']) 216 ->get() 217 ->map(User::rowMapper()); 218 } 219 220 /** 221 * Get a list of all verified users. 222 * 223 * @return Collection 224 */ 225 public function unapproved(): Collection 226 { 227 return DB::table('user') 228 ->join('user_setting', 'user_setting.user_id', '=', 'user.user_id') 229 ->where('user_setting.setting_name', '=', 'verified_by_admin') 230 ->where('user_setting.setting_value', '<>', '1') 231 ->where('user.user_id', '>', 0) 232 ->orderBy('real_name') 233 ->select(['user.*']) 234 ->get() 235 ->map(User::rowMapper()); 236 } 237 238 /** 239 * Get a list of all verified users. 240 * 241 * @return Collection 242 */ 243 public function unverified(): Collection 244 { 245 return DB::table('user') 246 ->join('user_setting', 'user_setting.user_id', '=', 'user.user_id') 247 ->where('user_setting.setting_name', '=', 'verified') 248 ->where('user_setting.setting_value', '<>', '1') 249 ->where('user.user_id', '>', 0) 250 ->orderBy('real_name') 251 ->select(['user.*']) 252 ->get() 253 ->map(User::rowMapper()); 254 } 255 256 /** 257 * Get a list of all users who are currently logged in. 258 * 259 * @return Collection 260 */ 261 public function allLoggedIn(): Collection 262 { 263 return DB::table('user') 264 ->join('session', 'session.user_id', '=', 'user.user_id') 265 ->where('user.user_id', '>', 0) 266 ->orderBy('real_name') 267 ->select(['user.*']) 268 ->distinct() 269 ->get() 270 ->map(User::rowMapper()); 271 } 272 273 /** 274 * Create a new user. 275 * The calling code needs to check for duplicates identifiers before calling 276 * this function. 277 * 278 * @param string $user_name 279 * @param string $real_name 280 * @param string $email 281 * @param string $password 282 * 283 * @return User 284 */ 285 public function create(string $user_name, string $real_name, string $email, string $password): User 286 { 287 DB::table('user')->insert([ 288 'user_name' => $user_name, 289 'real_name' => $real_name, 290 'email' => $email, 291 'password' => password_hash($password, PASSWORD_DEFAULT), 292 ]); 293 294 $user_id = (int) DB::connection()->getPdo()->lastInsertId(); 295 296 return new User($user_id, $user_name, $real_name, $email); 297 } 298 299 /** 300 * Delete a user 301 * 302 * @param User $user 303 * 304 * @return void 305 */ 306 public function delete(User $user): void 307 { 308 // Don't delete the logs, just set the user to null. 309 DB::table('log') 310 ->where('user_id', '=', $user->id()) 311 ->update(['user_id' => null]); 312 313 // Take over the user’s pending changes. (What else could we do with them?) 314 DB::table('change') 315 ->where('user_id', '=', $user->id()) 316 ->where('status', '=', 'rejected') 317 ->delete(); 318 319 DB::table('change') 320 ->where('user_id', '=', $user->id()) 321 ->update(['user_id' => Auth::id()]); 322 323 // Delete settings and preferences 324 DB::table('block_setting') 325 ->join('block', 'block_setting.block_id', '=', 'block.block_id') 326 ->where('user_id', '=', $user->id()) 327 ->delete(); 328 329 DB::table('block')->where('user_id', '=', $user->id())->delete(); 330 DB::table('user_gedcom_setting')->where('user_id', '=', $user->id())->delete(); 331 DB::table('user_setting')->where('user_id', '=', $user->id())->delete(); 332 DB::table('message')->where('user_id', '=', $user->id())->delete(); 333 DB::table('user')->where('user_id', '=', $user->id())->delete(); 334 } 335 336 /** 337 * @param User $contact_user 338 * @param ServerRequestInterface $request 339 * 340 * @return string 341 */ 342 public function contactLink(User $contact_user, ServerRequestInterface $request): string 343 { 344 $tree = $request->getAttribute('tree'); 345 assert($tree instanceof Tree); 346 347 $user = $request->getAttribute('user'); 348 349 if ($contact_user->getPreference('contactmethod') === 'mailto') { 350 $url = 'mailto:' . $contact_user->email(); 351 } elseif ($user instanceof User) { 352 // Logged-in users send direct messages 353 $url = route(MessagePage::class, ['to' => $contact_user->userName(), 'tree' => $tree->name()]); 354 } else { 355 // Visitors use the contact form. 356 $url = route(ContactPage::class, [ 357 'to' => $contact_user->userName(), 358 'tree' => $tree->name(), 359 'url' => (string) $request->getUri(), 360 ]); 361 } 362 363 return '<a href="' . e($url) . '" dir="auto">' . e($contact_user->realName()) . '</a>'; 364 } 365} 366