xref: /webtrees/app/User.php (revision 40d8fd37a2ed9124e92a2d07ca41ca39b2663e3a)
1<?php
2/**
3 * webtrees: online genealogy
4 * Copyright (C) 2019 webtrees development team
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16declare(strict_types=1);
17
18namespace Fisharebest\Webtrees;
19
20use Closure;
21use Illuminate\Database\Capsule\Manager as DB;
22use Illuminate\Database\Query\Builder;
23use Illuminate\Database\Query\JoinClause;
24use Illuminate\Support\Collection;
25use stdClass;
26
27/**
28 * Provide an interface to the wt_user table.
29 */
30class User
31{
32    /** @var  int The primary key of this user. */
33    private $user_id;
34
35    /** @var  string The login name of this user. */
36    private $user_name;
37
38    /** @var  string The real (display) name of this user. */
39    private $real_name;
40
41    /** @var  string The email address of this user. */
42    private $email;
43
44    /** @var string[] Cached copy of the wt_user_setting table. */
45    private $preferences = [];
46
47    /**
48     * Create a new user object from a row in the database.
49     *
50     * @param int    $user_id
51     * @param string $user_name
52     * @param string $real_name
53     * @param string $email
54     */
55    private function __construct(int $user_id, string $user_name, string $real_name, string $email)
56    {
57        $this->user_id   = $user_id;
58        $this->user_name = $user_name;
59        $this->real_name = $real_name;
60        $this->email     = $email;
61    }
62
63    /**
64     * A closure which will create an object from a database row.
65     *
66     * @return Closure
67     */
68    public static function rowMapper(): Closure
69    {
70        return function (stdClass $row): User {
71            return new static((int) $row->user_id, $row->user_name, $row->real_name, $row->email);
72        };
73    }
74
75    /**
76     * Create a dummy user from a tree, to send messages.
77     *
78     * @param Tree $tree
79     *
80     * @return User
81     */
82    public static function userFromTree(Tree $tree): User
83    {
84        return  new static(0, '', $tree->title(),$tree->getPreference('WEBTREES_EMAIL'));
85    }
86
87    /**
88     * A dummy/null user for visitors.
89     *
90     * @param string $real_name
91     * @param string $email
92     *
93     * @return User
94     */
95    public static function visitor(string $real_name = '', string $email = ''): User
96    {
97        return new static(0, '', $real_name, $email);
98    }
99
100    /**
101     * Create a new user.
102     * The calling code needs to check for duplicates identifiers before calling
103     * this function.
104     *
105     * @param string $user_name
106     * @param string $real_name
107     * @param string $email
108     * @param string $password
109     *
110     * @return User
111     */
112    public static function create($user_name, $real_name, $email, $password): User
113    {
114        DB::table('user')->insert([
115            'user_name' => $user_name,
116            'real_name' => $real_name,
117            'email'     => $email,
118            'password'  => password_hash($password, PASSWORD_DEFAULT),
119        ]);
120
121        $user_id = (int) DB::connection()->getPdo()->lastInsertId();
122
123        return new static($user_id, $user_name, $real_name, $email);
124    }
125
126    /**
127     * Delete a user
128     *
129     * @return void
130     */
131    public function delete()
132    {
133        // Don't delete the logs, just set the user to null.
134        DB::table('log')
135            ->where('user_id', '=', $this->user_id)
136            ->update(['user_id' => null]);
137
138        // Take over the user’s pending changes. (What else could we do with them?)
139        DB::table('change')
140            ->where('user_id', '=', $this->user_id)
141            ->where('status', '=', 'rejected')
142            ->delete();
143
144        DB::table('change')
145            ->where('user_id', '=', $this->user_id)
146            ->update(['user_id' => Auth::id()]);
147
148        // Take over the user's contact details
149        DB::table('gedcom_setting')
150            ->where('setting_value', '=', $this->user_id)
151            ->whereIn('setting_name', ['CONTACT_USER_ID', 'WEBMASTER_USER_ID'])
152            ->update(['setting_value' => Auth::id()]);
153
154        // Delete settings and preferences
155        DB::table('block_setting')
156            ->join('block', 'block_setting.block_id', '=', 'block.block_id')
157            ->where('user_id', '=', $this->user_id)
158            ->delete();
159
160        DB::table('block')->where('user_id', '=', $this->user_id)->delete();
161        DB::table('user_gedcom_setting')->where('user_id', '=', $this->user_id)->delete();
162        DB::table('user_setting')->where('user_id', '=', $this->user_id)->delete();
163        DB::table('message')->where('user_id', '=', $this->user_id)->delete();
164        DB::table('user')->where('user_id', '=', $this->user_id)->delete();
165    }
166
167    /**
168     * Find the user with a specified user_id.
169     *
170     * @param int|null $user_id
171     *
172     * @return User|null
173     */
174    public static function find($user_id)
175    {
176        return app('cache.array')->rememberForever(__CLASS__ . $user_id, function () use ($user_id) {
177            return DB::table('user')
178                ->where('user_id', '=', $user_id)
179                ->get();
180        })
181        ->map(static::rowMapper())
182        ->first();
183    }
184
185    /**
186     * Find the user with a specified email address.
187     *
188     * @param string $email
189     *
190     * @return User|null
191     */
192    public static function findByEmail($email)
193    {
194        return DB::table('user')
195            ->where('email', '=', $email)
196            ->get()
197            ->map(static::rowMapper())
198            ->first();
199    }
200
201    /**
202     * Find the user with a specified user_name or email address.
203     *
204     * @param string $identifier
205     *
206     * @return User|null
207     */
208    public static function findByIdentifier($identifier)
209    {
210        return DB::table('user')
211            ->where('user_name', '=', $identifier)
212            ->orWhere('email', '=', $identifier)
213            ->get()
214            ->map(static::rowMapper())
215            ->first();
216    }
217
218    /**
219     * Find the user(s) with a specified genealogy record.
220     *
221     * @param Individual $individual
222     *
223     * @return Collection|User[]
224     */
225    public static function findByIndividual(Individual $individual): Collection
226    {
227        return DB::table('user')
228            ->join('user_gedcom_setting', 'user_gedcom_setting.user_id', '=', 'user.user_id')
229            ->where('gedcom_id', '=', $individual->tree()->id())
230            ->where('setting_value', '=', $individual->xref())
231            ->where('setting_name', '=', 'gedcomid')
232            ->select(['user.*'])
233            ->get()
234            ->map(static::rowMapper());
235    }
236
237    /**
238     * Find the user with a specified user_name.
239     *
240     * @param string $user_name
241     *
242     * @return User|null
243     */
244    public static function findByUserName($user_name)
245    {
246        return DB::table('user')
247            ->where('user_name', '=', $user_name)
248            ->get()
249            ->map(static::rowMapper())
250            ->first();
251    }
252
253    /**
254     * Get a list of all users.
255     *
256     * @return Collection|User[]
257     */
258    public static function all(): Collection
259    {
260        return DB::table('user')
261            ->where('user_id', '>', 0)
262            ->orderBy('real_name')
263            ->select(['user_id', 'user_name', 'real_name', 'email'])
264            ->get()
265            ->map(static::rowMapper());
266    }
267
268    /**
269     * Get a list of all administrators.
270     *
271     * @return Collection|User[]
272     */
273    public static function administrators(): Collection
274    {
275        return DB::table('user')
276            ->join('user_setting', function (JoinClause $join): void {
277                $join
278                    ->on('user_setting.user_id', '=', 'user.user_id')
279                    ->where('user_setting.setting_name', '=', 'canadmin')
280                    ->where('user_setting.setting_value', '=', '1');
281            })
282            ->where('user.user_id', '>', 0)
283            ->orderBy('real_name')
284            ->select(['user.user_id', 'user_name', 'real_name', 'email'])
285            ->get()
286            ->map(static::rowMapper());
287    }
288
289    /**
290     * Validate a supplied password
291     *
292     * @param string $password
293     *
294     * @return bool
295     */
296    public function checkPassword(string $password): bool
297    {
298        $password_hash = DB::table('user')
299            ->where('user_id', '=', $this->user_id)
300            ->value('password');
301
302        if ($password_hash !== null && password_verify($password, $password_hash)) {
303            if (password_needs_rehash($password_hash, PASSWORD_DEFAULT)) {
304                $this->setPassword($password);
305            }
306
307            return true;
308        }
309
310        return false;
311    }
312
313    /**
314     * Get a list of all managers.
315     *
316     * @return Collection|User[]
317     */
318    public static function managers(): Collection
319    {
320        return DB::table('user')
321            ->join('user_gedcom_setting', function (JoinClause $join): void {
322                $join
323                    ->on('user_gedcom_setting.user_id', '=', 'user.user_id')
324                    ->where('user_gedcom_setting.setting_name', '=', 'canedit')
325                    ->where('user_gedcom_setting.setting_value', '=', 'admin');
326            })
327            ->where('user.user_id', '>', 0)
328            ->orderBy('real_name')
329            ->select(['user.user_id', 'user_name', 'real_name', 'email'])
330            ->get()
331            ->map(static::rowMapper());
332    }
333
334    /**
335     * Get a list of all moderators.
336     *
337     * @return Collection|User[]
338     */
339    public static function moderators(): Collection
340    {
341        return DB::table('user')
342            ->join('user_gedcom_setting', function (JoinClause $join): void {
343                $join
344                    ->on('user_gedcom_setting.user_id', '=', 'user.user_id')
345                    ->where('user_gedcom_setting.setting_name', '=', 'canedit')
346                    ->where('user_gedcom_setting.setting_value', '=', 'accept');
347            })
348            ->where('user.user_id', '>', 0)
349            ->orderBy('real_name')
350            ->select(['user.user_id', 'user_name', 'real_name', 'email'])
351            ->get()
352            ->map(static::rowMapper());
353    }
354
355    /**
356     * Get a list of all verified users.
357     *
358     * @return Collection|User[]
359     */
360    public static function unapproved(): Collection
361    {
362        return DB::table('user')
363            ->join('user_setting', function (JoinClause $join): void {
364                $join
365                    ->on('user_setting.user_id', '=', 'user.user_id')
366                    ->where('user_setting.setting_name', '=', 'verified_by_admin')
367                    ->where('user_setting.setting_value', '=', '0');
368            })
369            ->where('user.user_id', '>', 0)
370            ->orderBy('real_name')
371            ->select(['user.user_id', 'user_name', 'real_name', 'email'])
372            ->get()
373            ->map(static::rowMapper());
374    }
375
376    /**
377     * Get a list of all verified users.
378     *
379     * @return Collection|User[]
380     */
381    public static function unverified(): Collection
382    {
383        return DB::table('user')
384            ->join('user_setting', function (JoinClause $join): void {
385                $join
386                    ->on('user_setting.user_id', '=', 'user.user_id')
387                    ->where('user_setting.setting_name', '=', 'verified')
388                    ->where('user_setting.setting_value', '=', '0');
389            })
390            ->where('user.user_id', '>', 0)
391            ->orderBy('real_name')
392            ->select(['user.user_id', 'user_name', 'real_name', 'email'])
393            ->get()
394            ->map(static::rowMapper());
395    }
396
397    /**
398     * Get a list of all users who are currently logged in.
399     *
400     * @return Collection|User[]
401     */
402    public static function allLoggedIn(): Collection
403    {
404        return DB::table('user')
405            ->join('session', 'session.user_id', '=', 'user.user_id')
406            ->where('user.user_id', '>', 0)
407            ->orderBy('real_name')
408            ->select(['user.user_id', 'user_name', 'real_name', 'email'])
409            ->distinct()
410            ->get()
411            ->map(static::rowMapper());
412    }
413
414    /**
415     * Get the numeric ID for this user.
416     *
417     * @return int
418     */
419    public function id(): int
420    {
421        return $this->user_id;
422    }
423
424    /**
425     * Get the login name for this user.
426     *
427     * @return string
428     */
429    public function getUserName(): string
430    {
431        return $this->user_name;
432    }
433
434    /**
435     * Set the login name for this user.
436     *
437     * @param string $user_name
438     *
439     * @return $this
440     */
441    public function setUserName($user_name): self
442    {
443        if ($this->user_name !== $user_name) {
444            $this->user_name = $user_name;
445
446            DB::table('user')
447                ->where('user_id', '=', $this->user_id)
448                ->update([
449                    'user_name' => $user_name,
450                ]);
451        }
452
453        return $this;
454    }
455
456    /**
457     * Get the real name of this user.
458     *
459     * @return string
460     */
461    public function getRealName(): string
462    {
463        return $this->real_name;
464    }
465
466    /**
467     * Set the real name of this user.
468     *
469     * @param string $real_name
470     *
471     * @return User
472     */
473    public function setRealName($real_name): User
474    {
475        if ($this->real_name !== $real_name) {
476            $this->real_name = $real_name;
477
478            DB::table('user')
479                ->where('user_id', '=', $this->user_id)
480                ->update([
481                    'real_name' => $real_name,
482                ]);
483        }
484
485        return $this;
486    }
487
488    /**
489     * Get the email address of this user.
490     *
491     * @return string
492     */
493    public function getEmail(): string
494    {
495        return $this->email;
496    }
497
498    /**
499     * Set the email address of this user.
500     *
501     * @param string $email
502     *
503     * @return User
504     */
505    public function setEmail($email): User
506    {
507        if ($this->email !== $email) {
508            $this->email = $email;
509
510            DB::table('user')
511                ->where('user_id', '=', $this->user_id)
512                ->update([
513                    'email' => $email,
514                ]);
515        }
516
517        return $this;
518    }
519
520    /**
521     * Set the password of this user.
522     *
523     * @param string $password
524     *
525     * @return User
526     */
527    public function setPassword($password): User
528    {
529        DB::table('user')
530            ->where('user_id', '=', $this->user_id)
531            ->update([
532                'password' => password_hash($password, PASSWORD_DEFAULT),
533            ]);
534
535        return $this;
536    }
537
538    /**
539     * Fetch a user option/setting from the wt_user_setting table.
540     * Since we'll fetch several settings for each user, and since there aren’t
541     * that many of them, fetch them all in one database query
542     *
543     * @param string $setting_name
544     * @param string $default
545     *
546     * @return string
547     */
548    public function getPreference($setting_name, $default = ''): string
549    {
550        $preferences = app('cache.array')->rememberForever('user_setting' . $this->user_id, function () {
551            if ($this->user_id) {
552                return DB::table('user_setting')
553                    ->where('user_id', '=', $this->user_id)
554                    ->pluck('setting_value', 'setting_name')
555                    ->all();
556            } else {
557                return [];
558            }
559        });
560
561        return $preferences[$setting_name] ?? $default;
562    }
563
564    /**
565     * Update a setting for the user.
566     *
567     * @param string $setting_name
568     * @param string $setting_value
569     *
570     * @return User
571     */
572    public function setPreference($setting_name, $setting_value): User
573    {
574        if ($this->user_id !== 0 && $this->getPreference($setting_name) !== $setting_value) {
575            DB::table('user_setting')->updateOrInsert([
576                'user_id'      => $this->user_id,
577                'setting_name' => $setting_name,
578            ], [
579                'setting_value' => $setting_value,
580            ]);
581
582            $this->preferences[$setting_name] = $setting_value;
583        }
584
585        app('cache.array')->forget('user_setting' . $this->user_id);
586
587        return $this;
588    }
589}
590