xref: /webtrees/app/Services/UserService.php (revision 1a218474113038005e50986fff24ebcbd58554ff)
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 */
17declare(strict_types=1);
18
19namespace Fisharebest\Webtrees\Services;
20
21use Fisharebest\Webtrees\Auth;
22use Fisharebest\Webtrees\Carbon;
23use Fisharebest\Webtrees\Individual;
24use Fisharebest\Webtrees\Tree;
25use Fisharebest\Webtrees\User;
26use Illuminate\Database\Capsule\Manager as DB;
27use Illuminate\Database\Query\JoinClause;
28use Illuminate\Support\Collection;
29use InvalidArgumentException;
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, new InvalidArgumentException());
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