1e5a6b4d4SGreg Roach<?php 23976b470SGreg Roach 3e5a6b4d4SGreg Roach/** 4e5a6b4d4SGreg Roach * webtrees: online genealogy 5e5a6b4d4SGreg Roach * Copyright (C) 2019 webtrees development team 6e5a6b4d4SGreg Roach * This program is free software: you can redistribute it and/or modify 7e5a6b4d4SGreg Roach * it under the terms of the GNU General Public License as published by 8e5a6b4d4SGreg Roach * the Free Software Foundation, either version 3 of the License, or 9e5a6b4d4SGreg Roach * (at your option) any later version. 10e5a6b4d4SGreg Roach * This program is distributed in the hope that it will be useful, 11e5a6b4d4SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of 12e5a6b4d4SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13e5a6b4d4SGreg Roach * GNU General Public License for more details. 14e5a6b4d4SGreg Roach * You should have received a copy of the GNU General Public License 15e5a6b4d4SGreg Roach * along with this program. If not, see <http://www.gnu.org/licenses/>. 16e5a6b4d4SGreg Roach */ 17fcfa147eSGreg Roach 18e5a6b4d4SGreg Roachdeclare(strict_types=1); 19e5a6b4d4SGreg Roach 20e5a6b4d4SGreg Roachnamespace Fisharebest\Webtrees\Services; 21e5a6b4d4SGreg Roach 222474349cSGreg Roachuse Closure; 23e5a6b4d4SGreg Roachuse Fisharebest\Webtrees\Auth; 24a00bcc63SGreg Roachuse Fisharebest\Webtrees\Carbon; 252474349cSGreg Roachuse Fisharebest\Webtrees\Contracts\UserInterface; 26e381f98dSGreg Roachuse Fisharebest\Webtrees\Http\RequestHandlers\ContactPage; 27e381f98dSGreg Roachuse Fisharebest\Webtrees\Http\RequestHandlers\MessagePage; 28e5a6b4d4SGreg Roachuse Fisharebest\Webtrees\Individual; 295229eadeSGreg Roachuse Fisharebest\Webtrees\Tree; 30e5a6b4d4SGreg Roachuse Fisharebest\Webtrees\User; 31e5a6b4d4SGreg Roachuse Illuminate\Database\Capsule\Manager as DB; 327c4add84SGreg Roachuse Illuminate\Database\Query\Builder; 337c4add84SGreg Roachuse Illuminate\Database\Query\JoinClause; 34e5a6b4d4SGreg Roachuse Illuminate\Support\Collection; 356ccdf4f0SGreg Roachuse Psr\Http\Message\ServerRequestInterface; 36f1d4b4a2SGreg Roach 376ccdf4f0SGreg Roachuse function app; 385229eadeSGreg Roachuse function assert; 392474349cSGreg Roachuse function max; 40e5a6b4d4SGreg Roach 41e5a6b4d4SGreg Roach/** 42e5a6b4d4SGreg Roach * Functions for managing users. 43e5a6b4d4SGreg Roach */ 44e5a6b4d4SGreg Roachclass UserService 45e5a6b4d4SGreg Roach{ 46e5a6b4d4SGreg Roach /** 47e5a6b4d4SGreg Roach * Find the user with a specified user_id. 48e5a6b4d4SGreg Roach * 49e5a6b4d4SGreg Roach * @param int|null $user_id 50e5a6b4d4SGreg Roach * 51e5a6b4d4SGreg Roach * @return User|null 52e5a6b4d4SGreg Roach */ 5325d7fe95SGreg Roach public function find($user_id): ?User 54e5a6b4d4SGreg Roach { 557476e8a5SGreg Roach return app('cache.array')->remember('user-' . $user_id, static function () use ($user_id): ?User { 56e5a6b4d4SGreg Roach return DB::table('user') 57e5a6b4d4SGreg Roach ->where('user_id', '=', $user_id) 58e5a6b4d4SGreg Roach ->get() 59e5a6b4d4SGreg Roach ->map(User::rowMapper()) 60e5a6b4d4SGreg Roach ->first(); 61e5a6b4d4SGreg Roach }); 62e5a6b4d4SGreg Roach } 63e5a6b4d4SGreg Roach 64e5a6b4d4SGreg Roach /** 65e5a6b4d4SGreg Roach * Find the user with a specified email address. 66e5a6b4d4SGreg Roach * 67e5a6b4d4SGreg Roach * @param string $email 68e5a6b4d4SGreg Roach * 69e5a6b4d4SGreg Roach * @return User|null 70e5a6b4d4SGreg Roach */ 71e364afe4SGreg Roach public function findByEmail($email): ?User 72e5a6b4d4SGreg Roach { 73e5a6b4d4SGreg Roach return DB::table('user') 74e5a6b4d4SGreg Roach ->where('email', '=', $email) 75e5a6b4d4SGreg Roach ->get() 76e5a6b4d4SGreg Roach ->map(User::rowMapper()) 77e5a6b4d4SGreg Roach ->first(); 78e5a6b4d4SGreg Roach } 79e5a6b4d4SGreg Roach 80e5a6b4d4SGreg Roach /** 81e5a6b4d4SGreg Roach * Find the user with a specified user_name or email address. 82e5a6b4d4SGreg Roach * 83e5a6b4d4SGreg Roach * @param string $identifier 84e5a6b4d4SGreg Roach * 85e5a6b4d4SGreg Roach * @return User|null 86e5a6b4d4SGreg Roach */ 87e364afe4SGreg Roach public function findByIdentifier($identifier): ?User 88e5a6b4d4SGreg Roach { 89e5a6b4d4SGreg Roach return DB::table('user') 90e5a6b4d4SGreg Roach ->where('user_name', '=', $identifier) 91e5a6b4d4SGreg Roach ->orWhere('email', '=', $identifier) 92e5a6b4d4SGreg Roach ->get() 93e5a6b4d4SGreg Roach ->map(User::rowMapper()) 94e5a6b4d4SGreg Roach ->first(); 95e5a6b4d4SGreg Roach } 96e5a6b4d4SGreg Roach 97e5a6b4d4SGreg Roach /** 98e5a6b4d4SGreg Roach * Find the user(s) with a specified genealogy record. 99e5a6b4d4SGreg Roach * 100e5a6b4d4SGreg Roach * @param Individual $individual 101e5a6b4d4SGreg Roach * 102*b5c8fd7eSGreg Roach * @return Collection<User> 103e5a6b4d4SGreg Roach */ 104e5a6b4d4SGreg Roach public function findByIndividual(Individual $individual): Collection 105e5a6b4d4SGreg Roach { 106e5a6b4d4SGreg Roach return DB::table('user') 107e5a6b4d4SGreg Roach ->join('user_gedcom_setting', 'user_gedcom_setting.user_id', '=', 'user.user_id') 108e5a6b4d4SGreg Roach ->where('gedcom_id', '=', $individual->tree()->id()) 109e5a6b4d4SGreg Roach ->where('setting_value', '=', $individual->xref()) 1107c4add84SGreg Roach ->where('setting_name', '=', User::PREF_TREE_ACCOUNT_XREF) 111e5a6b4d4SGreg Roach ->select(['user.*']) 112e5a6b4d4SGreg Roach ->get() 113e5a6b4d4SGreg Roach ->map(User::rowMapper()); 114e5a6b4d4SGreg Roach } 115e5a6b4d4SGreg Roach 116e5a6b4d4SGreg Roach /** 117a00bcc63SGreg Roach * Find the user with a specified password reset token. 118a00bcc63SGreg Roach * 119a00bcc63SGreg Roach * @param string $token 120a00bcc63SGreg Roach * 121a00bcc63SGreg Roach * @return User|null 122a00bcc63SGreg Roach */ 123a00bcc63SGreg Roach public function findByToken(string $token): ?User 124a00bcc63SGreg Roach { 125a00bcc63SGreg Roach return DB::table('user') 126a00bcc63SGreg Roach ->join('user_setting AS us1', 'us1.user_id', '=', 'user.user_id') 127a00bcc63SGreg Roach ->where('us1.setting_name', '=', 'password-token') 128a00bcc63SGreg Roach ->where('us1.setting_value', '=', $token) 129a00bcc63SGreg Roach ->join('user_setting AS us2', 'us2.user_id', '=', 'user.user_id') 130a00bcc63SGreg Roach ->where('us2.setting_name', '=', 'password-token-expire') 131a00bcc63SGreg Roach ->where('us2.setting_value', '>', Carbon::now()->timestamp) 132a00bcc63SGreg Roach ->select(['user.*']) 133a00bcc63SGreg Roach ->get() 134a00bcc63SGreg Roach ->map(User::rowMapper()) 135a00bcc63SGreg Roach ->first(); 136a00bcc63SGreg Roach } 137a00bcc63SGreg Roach 138a00bcc63SGreg Roach /** 139e5a6b4d4SGreg Roach * Find the user with a specified user_name. 140e5a6b4d4SGreg Roach * 141e5a6b4d4SGreg Roach * @param string $user_name 142e5a6b4d4SGreg Roach * 143e5a6b4d4SGreg Roach * @return User|null 144e5a6b4d4SGreg Roach */ 145e364afe4SGreg Roach public function findByUserName($user_name): ?User 146e5a6b4d4SGreg Roach { 147e5a6b4d4SGreg Roach return DB::table('user') 148e5a6b4d4SGreg Roach ->where('user_name', '=', $user_name) 149e5a6b4d4SGreg Roach ->get() 150e5a6b4d4SGreg Roach ->map(User::rowMapper()) 151e5a6b4d4SGreg Roach ->first(); 152e5a6b4d4SGreg Roach } 153e5a6b4d4SGreg Roach 154e5a6b4d4SGreg Roach /** 1552474349cSGreg Roach * Callback to sort users by their last-login (or registration) time. 1562474349cSGreg Roach * 1572474349cSGreg Roach * @return Closure 1582474349cSGreg Roach */ 1592474349cSGreg Roach public function sortByLastLogin(): Closure 1602474349cSGreg Roach { 1617c4add84SGreg Roach return static function (UserInterface $user1, UserInterface $user2) { 1627c4add84SGreg Roach $registered_at1 = (int) $user1->getPreference(User::PREF_TIMESTAMP_REGISTERED); 1637c4add84SGreg Roach $logged_in_at1 = (int) $user1->getPreference(User::PREF_TIMESTAMP_ACTIVE); 1647c4add84SGreg Roach $registered_at2 = (int) $user2->getPreference(User::PREF_TIMESTAMP_REGISTERED); 1657c4add84SGreg Roach $logged_in_at2 = (int) $user2->getPreference(User::PREF_TIMESTAMP_ACTIVE); 1662474349cSGreg Roach 1672474349cSGreg Roach return max($registered_at1, $logged_in_at1) <=> max($registered_at2, $logged_in_at2); 1682474349cSGreg Roach }; 1692474349cSGreg Roach } 1702474349cSGreg Roach 1712474349cSGreg Roach /** 1722474349cSGreg Roach * Callback to filter users who have not logged in since a given time. 1732474349cSGreg Roach * 1742474349cSGreg Roach * @param int $timestamp 1752474349cSGreg Roach * 1762474349cSGreg Roach * @return Closure 1772474349cSGreg Roach */ 1782474349cSGreg Roach public function filterInactive(int $timestamp): Closure 1792474349cSGreg Roach { 1807c4add84SGreg Roach return static function (UserInterface $user) use ($timestamp): bool { 1817c4add84SGreg Roach $registered_at = (int) $user->getPreference(User::PREF_TIMESTAMP_REGISTERED); 1827c4add84SGreg Roach $logged_in_at = (int) $user->getPreference(User::PREF_TIMESTAMP_ACTIVE); 1832474349cSGreg Roach 1842474349cSGreg Roach return max($registered_at, $logged_in_at) < $timestamp; 1852474349cSGreg Roach }; 1862474349cSGreg Roach } 1872474349cSGreg Roach 1882474349cSGreg Roach /** 189e5a6b4d4SGreg Roach * Get a list of all users. 190e5a6b4d4SGreg Roach * 191*b5c8fd7eSGreg Roach * @return Collection<User> 192e5a6b4d4SGreg Roach */ 193e5a6b4d4SGreg Roach public function all(): Collection 194e5a6b4d4SGreg Roach { 195e5a6b4d4SGreg Roach return DB::table('user') 196e5a6b4d4SGreg Roach ->where('user_id', '>', 0) 197e5a6b4d4SGreg Roach ->orderBy('real_name') 198e5a6b4d4SGreg Roach ->get() 199e5a6b4d4SGreg Roach ->map(User::rowMapper()); 200e5a6b4d4SGreg Roach } 201e5a6b4d4SGreg Roach 202e5a6b4d4SGreg Roach /** 203e5a6b4d4SGreg Roach * Get a list of all administrators. 204e5a6b4d4SGreg Roach * 205*b5c8fd7eSGreg Roach * @return Collection<User> 206e5a6b4d4SGreg Roach */ 207e5a6b4d4SGreg Roach public function administrators(): Collection 208e5a6b4d4SGreg Roach { 209e5a6b4d4SGreg Roach return DB::table('user') 2101ab2f386SGreg Roach ->join('user_setting', 'user_setting.user_id', '=', 'user.user_id') 2117c4add84SGreg Roach ->where('user_setting.setting_name', '=', User::PREF_IS_ADMINISTRATOR) 2121ab2f386SGreg Roach ->where('user_setting.setting_value', '=', '1') 213e5a6b4d4SGreg Roach ->where('user.user_id', '>', 0) 214e5a6b4d4SGreg Roach ->orderBy('real_name') 215e5a6b4d4SGreg Roach ->select(['user.*']) 216e5a6b4d4SGreg Roach ->get() 217e5a6b4d4SGreg Roach ->map(User::rowMapper()); 218e5a6b4d4SGreg Roach } 219e5a6b4d4SGreg Roach 220e5a6b4d4SGreg Roach /** 221e5a6b4d4SGreg Roach * Get a list of all managers. 222e5a6b4d4SGreg Roach * 223*b5c8fd7eSGreg Roach * @return Collection<User> 224e5a6b4d4SGreg Roach */ 225e5a6b4d4SGreg Roach public function managers(): Collection 226e5a6b4d4SGreg Roach { 227e5a6b4d4SGreg Roach return DB::table('user') 2281ab2f386SGreg Roach ->join('user_gedcom_setting', 'user_gedcom_setting.user_id', '=', 'user.user_id') 2297c4add84SGreg Roach ->where('user_gedcom_setting.setting_name', '=', User::PREF_TREE_ROLE) 2307c4add84SGreg Roach ->where('user_gedcom_setting.setting_value', '=', User::ROLE_MANAGER) 231e5a6b4d4SGreg Roach ->where('user.user_id', '>', 0) 2321ab2f386SGreg Roach ->groupBy(['user.user_id']) 233e5a6b4d4SGreg Roach ->orderBy('real_name') 234e5a6b4d4SGreg Roach ->select(['user.*']) 235e5a6b4d4SGreg Roach ->get() 236e5a6b4d4SGreg Roach ->map(User::rowMapper()); 237e5a6b4d4SGreg Roach } 238e5a6b4d4SGreg Roach 239e5a6b4d4SGreg Roach /** 240e5a6b4d4SGreg Roach * Get a list of all moderators. 241e5a6b4d4SGreg Roach * 242*b5c8fd7eSGreg Roach * @return Collection<User> 243e5a6b4d4SGreg Roach */ 244e5a6b4d4SGreg Roach public function moderators(): Collection 245e5a6b4d4SGreg Roach { 246e5a6b4d4SGreg Roach return DB::table('user') 2471ab2f386SGreg Roach ->join('user_gedcom_setting', 'user_gedcom_setting.user_id', '=', 'user.user_id') 2487c4add84SGreg Roach ->where('user_gedcom_setting.setting_name', '=', User::PREF_TREE_ROLE) 2497c4add84SGreg Roach ->where('user_gedcom_setting.setting_value', '=', User::ROLE_MODERATOR) 250e5a6b4d4SGreg Roach ->where('user.user_id', '>', 0) 2511ab2f386SGreg Roach ->groupBy(['user.user_id']) 252e5a6b4d4SGreg Roach ->orderBy('real_name') 253e5a6b4d4SGreg Roach ->select(['user.*']) 254e5a6b4d4SGreg Roach ->get() 255e5a6b4d4SGreg Roach ->map(User::rowMapper()); 256e5a6b4d4SGreg Roach } 257e5a6b4d4SGreg Roach 258e5a6b4d4SGreg Roach /** 259e5a6b4d4SGreg Roach * Get a list of all verified users. 260e5a6b4d4SGreg Roach * 261*b5c8fd7eSGreg Roach * @return Collection<User> 262e5a6b4d4SGreg Roach */ 263e5a6b4d4SGreg Roach public function unapproved(): Collection 264e5a6b4d4SGreg Roach { 265e5a6b4d4SGreg Roach return DB::table('user') 2667c4add84SGreg Roach ->leftJoin('user_setting', static function (JoinClause $join): void { 2677c4add84SGreg Roach $join 2687c4add84SGreg Roach ->on('user_setting.user_id', '=', 'user.user_id') 2697c4add84SGreg Roach ->where('user_setting.setting_name', '=', User::PREF_IS_ACCOUNT_APPROVED); 2707c4add84SGreg Roach }) 2717c4add84SGreg Roach ->where(static function (Builder $query): void { 2727c4add84SGreg Roach $query 2731ab2f386SGreg Roach ->where('user_setting.setting_value', '<>', '1') 2747c4add84SGreg Roach ->orWhereNull('user_setting.setting_value'); 2757c4add84SGreg Roach }) 276e5a6b4d4SGreg Roach ->where('user.user_id', '>', 0) 277e5a6b4d4SGreg Roach ->orderBy('real_name') 278090fa720SGreg Roach ->select(['user.*']) 279e5a6b4d4SGreg Roach ->get() 280e5a6b4d4SGreg Roach ->map(User::rowMapper()); 281e5a6b4d4SGreg Roach } 282e5a6b4d4SGreg Roach 283e5a6b4d4SGreg Roach /** 284e5a6b4d4SGreg Roach * Get a list of all verified users. 285e5a6b4d4SGreg Roach * 286*b5c8fd7eSGreg Roach * @return Collection<User> 287e5a6b4d4SGreg Roach */ 288e5a6b4d4SGreg Roach public function unverified(): Collection 289e5a6b4d4SGreg Roach { 290e5a6b4d4SGreg Roach return DB::table('user') 2917c4add84SGreg Roach ->leftJoin('user_setting', static function (JoinClause $join): void { 2927c4add84SGreg Roach $join 2937c4add84SGreg Roach ->on('user_setting.user_id', '=', 'user.user_id') 2947c4add84SGreg Roach ->where('user_setting.setting_name', '=', User::PREF_IS_EMAIL_VERIFIED); 2957c4add84SGreg Roach }) 2967c4add84SGreg Roach ->where(static function (Builder $query): void { 2977c4add84SGreg Roach $query 2981ab2f386SGreg Roach ->where('user_setting.setting_value', '<>', '1') 2997c4add84SGreg Roach ->orWhereNull('user_setting.setting_value'); 3007c4add84SGreg Roach }) 301e5a6b4d4SGreg Roach ->where('user.user_id', '>', 0) 302e5a6b4d4SGreg Roach ->orderBy('real_name') 303090fa720SGreg Roach ->select(['user.*']) 304e5a6b4d4SGreg Roach ->get() 305e5a6b4d4SGreg Roach ->map(User::rowMapper()); 306e5a6b4d4SGreg Roach } 307e5a6b4d4SGreg Roach 308e5a6b4d4SGreg Roach /** 309e5a6b4d4SGreg Roach * Get a list of all users who are currently logged in. 310e5a6b4d4SGreg Roach * 311*b5c8fd7eSGreg Roach * @return Collection<User> 312e5a6b4d4SGreg Roach */ 313e5a6b4d4SGreg Roach public function allLoggedIn(): Collection 314e5a6b4d4SGreg Roach { 315e5a6b4d4SGreg Roach return DB::table('user') 316e5a6b4d4SGreg Roach ->join('session', 'session.user_id', '=', 'user.user_id') 317e5a6b4d4SGreg Roach ->where('user.user_id', '>', 0) 318e5a6b4d4SGreg Roach ->orderBy('real_name') 319e5a6b4d4SGreg Roach ->select(['user.*']) 320e5a6b4d4SGreg Roach ->distinct() 321e5a6b4d4SGreg Roach ->get() 322e5a6b4d4SGreg Roach ->map(User::rowMapper()); 323e5a6b4d4SGreg Roach } 324e5a6b4d4SGreg Roach 325e5a6b4d4SGreg Roach /** 326e5a6b4d4SGreg Roach * Create a new user. 327e5a6b4d4SGreg Roach * The calling code needs to check for duplicates identifiers before calling 328e5a6b4d4SGreg Roach * this function. 329e5a6b4d4SGreg Roach * 330e5a6b4d4SGreg Roach * @param string $user_name 331e5a6b4d4SGreg Roach * @param string $real_name 332e5a6b4d4SGreg Roach * @param string $email 333e5a6b4d4SGreg Roach * @param string $password 334e5a6b4d4SGreg Roach * 335e5a6b4d4SGreg Roach * @return User 336e5a6b4d4SGreg Roach */ 3376be338f5SGreg Roach public function create(string $user_name, string $real_name, string $email, string $password): User 338e5a6b4d4SGreg Roach { 339e5a6b4d4SGreg Roach DB::table('user')->insert([ 340e5a6b4d4SGreg Roach 'user_name' => $user_name, 341e5a6b4d4SGreg Roach 'real_name' => $real_name, 342e5a6b4d4SGreg Roach 'email' => $email, 343e5a6b4d4SGreg Roach 'password' => password_hash($password, PASSWORD_DEFAULT), 344e5a6b4d4SGreg Roach ]); 345e5a6b4d4SGreg Roach 346e5a6b4d4SGreg Roach $user_id = (int) DB::connection()->getPdo()->lastInsertId(); 347e5a6b4d4SGreg Roach 348e5a6b4d4SGreg Roach return new User($user_id, $user_name, $real_name, $email); 349e5a6b4d4SGreg Roach } 350e5a6b4d4SGreg Roach 351e5a6b4d4SGreg Roach /** 352e5a6b4d4SGreg Roach * Delete a user 353e5a6b4d4SGreg Roach * 354e5a6b4d4SGreg Roach * @param User $user 355e5a6b4d4SGreg Roach * 356e5a6b4d4SGreg Roach * @return void 357e5a6b4d4SGreg Roach */ 358e364afe4SGreg Roach public function delete(User $user): void 359e5a6b4d4SGreg Roach { 360e5a6b4d4SGreg Roach // Don't delete the logs, just set the user to null. 361e5a6b4d4SGreg Roach DB::table('log') 362e5a6b4d4SGreg Roach ->where('user_id', '=', $user->id()) 363e5a6b4d4SGreg Roach ->update(['user_id' => null]); 364e5a6b4d4SGreg Roach 365e5a6b4d4SGreg Roach // Take over the user’s pending changes. (What else could we do with them?) 366e5a6b4d4SGreg Roach DB::table('change') 367e5a6b4d4SGreg Roach ->where('user_id', '=', $user->id()) 368e5a6b4d4SGreg Roach ->where('status', '=', 'rejected') 369e5a6b4d4SGreg Roach ->delete(); 370e5a6b4d4SGreg Roach 371e5a6b4d4SGreg Roach DB::table('change') 372e5a6b4d4SGreg Roach ->where('user_id', '=', $user->id()) 373e5a6b4d4SGreg Roach ->update(['user_id' => Auth::id()]); 374e5a6b4d4SGreg Roach 375e5a6b4d4SGreg Roach // Delete settings and preferences 376e5a6b4d4SGreg Roach DB::table('block_setting') 377e5a6b4d4SGreg Roach ->join('block', 'block_setting.block_id', '=', 'block.block_id') 378e5a6b4d4SGreg Roach ->where('user_id', '=', $user->id()) 379e5a6b4d4SGreg Roach ->delete(); 380e5a6b4d4SGreg Roach 381e5a6b4d4SGreg Roach DB::table('block')->where('user_id', '=', $user->id())->delete(); 382e5a6b4d4SGreg Roach DB::table('user_gedcom_setting')->where('user_id', '=', $user->id())->delete(); 383e5a6b4d4SGreg Roach DB::table('user_setting')->where('user_id', '=', $user->id())->delete(); 384e5a6b4d4SGreg Roach DB::table('message')->where('user_id', '=', $user->id())->delete(); 385e5a6b4d4SGreg Roach DB::table('user')->where('user_id', '=', $user->id())->delete(); 386e5a6b4d4SGreg Roach } 38786730b84SGreg Roach 38886730b84SGreg Roach /** 3894db4b4a9SGreg Roach * @param User $contact_user 390a992e8c1SGreg Roach * @param ServerRequestInterface $request 39186730b84SGreg Roach * 39286730b84SGreg Roach * @return string 39386730b84SGreg Roach */ 394a992e8c1SGreg Roach public function contactLink(User $contact_user, ServerRequestInterface $request): string 395dcbe9044SGreg Roach { 396a992e8c1SGreg Roach $tree = $request->getAttribute('tree'); 39775964c75SGreg Roach assert($tree instanceof Tree); 3985229eadeSGreg Roach 399a992e8c1SGreg Roach $user = $request->getAttribute('user'); 40086730b84SGreg Roach 4017c4add84SGreg Roach if ($contact_user->getPreference(User::PREF_CONTACT_METHOD) === 'mailto') { 40286730b84SGreg Roach $url = 'mailto:' . $contact_user->email(); 40386730b84SGreg Roach } elseif ($user instanceof User) { 40486730b84SGreg Roach // Logged-in users send direct messages 4050bf22806SGreg Roach $url = route(MessagePage::class, [ 4060bf22806SGreg Roach 'to' => $contact_user->userName(), 4070bf22806SGreg Roach 'tree' => $tree->name(), 4080bf22806SGreg Roach 'url' => (string) $request->getUri(), 4090bf22806SGreg Roach ]); 41086730b84SGreg Roach } else { 41186730b84SGreg Roach // Visitors use the contact form. 412e381f98dSGreg Roach $url = route(ContactPage::class, [ 41386730b84SGreg Roach 'to' => $contact_user->userName(), 414d72b284aSGreg Roach 'tree' => $tree->name(), 415f567c3d8SGreg Roach 'url' => (string) $request->getUri(), 41686730b84SGreg Roach ]); 41786730b84SGreg Roach } 41886730b84SGreg Roach 41986730b84SGreg Roach return '<a href="' . e($url) . '" dir="auto">' . e($contact_user->realName()) . '</a>'; 42086730b84SGreg Roach } 421e5a6b4d4SGreg Roach} 422