xref: /webtrees/app/User.php (revision 9b5537c37ce2b6d3b5c1a28c56f73d7ff9b9355c)
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        // Set default blocks for this user
122        $user = self::findByIdentifier($user_name);
123
124        (new Builder(DB::connection()))->from('block')->insertUsing(
125            ['user_id', 'location', 'block_order', 'module_name'],
126            function (Builder $query) use ($user): void {
127                $query
128                    ->select([DB::raw($user->id()), 'location', 'block_order', 'module_name'])
129                    ->from('block')
130                    ->where('user_id', '=', -1);
131            }
132        );
133
134        return $user;
135    }
136
137    /**
138     * Delete a user
139     *
140     * @return void
141     */
142    public function delete()
143    {
144        // Don't delete the logs, just set the user to null.
145        DB::table('log')
146            ->where('user_id', '=', $this->user_id)
147            ->update(['user_id' => null]);
148
149        // Take over the user’s pending changes. (What else could we do with them?)
150        DB::table('change')
151            ->where('user_id', '=', $this->user_id)
152            ->where('status', '=', 'rejected')
153            ->delete();
154
155        DB::table('change')
156            ->where('user_id', '=', $this->user_id)
157            ->update(['user_id' => Auth::id()]);
158
159        // Take over the user's contact details
160        DB::table('gedcom_setting')
161            ->where('setting_value', '=', $this->user_id)
162            ->whereIn('setting_name', ['CONTACT_USER_ID', 'WEBMASTER_USER_ID'])
163            ->update(['setting_value' => Auth::id()]);
164
165        // Delete settings and preferences
166        DB::table('block_setting')
167            ->join('block', 'block_setting.block_id', '=', 'block.block_id')
168            ->where('user_id', '=', $this->user_id)
169            ->delete();
170
171        DB::table('block')->where('user_id', '=', $this->user_id)->delete();
172        DB::table('user_gedcom_setting')->where('user_id', '=', $this->user_id)->delete();
173        DB::table('user_setting')->where('user_id', '=', $this->user_id)->delete();
174        DB::table('message')->where('user_id', '=', $this->user_id)->delete();
175        DB::table('user')->where('user_id', '=', $this->user_id)->delete();
176    }
177
178    /**
179     * Find the user with a specified user_id.
180     *
181     * @param int|null $user_id
182     *
183     * @return User|null
184     */
185    public static function find($user_id)
186    {
187        return app('cache.array')->rememberForever(__CLASS__ . $user_id, function () use ($user_id) {
188            return DB::table('user')
189                ->where('user_id', '=', $user_id)
190                ->get();
191        })
192        ->map(static::rowMapper())
193        ->first();
194    }
195
196    /**
197     * Find the user with a specified email address.
198     *
199     * @param string $email
200     *
201     * @return User|null
202     */
203    public static function findByEmail($email)
204    {
205        return DB::table('user')
206            ->where('email', '=', $email)
207            ->get()
208            ->map(static::rowMapper())
209            ->first();
210    }
211
212    /**
213     * Find the user with a specified user_name or email address.
214     *
215     * @param string $identifier
216     *
217     * @return User|null
218     */
219    public static function findByIdentifier($identifier)
220    {
221        return DB::table('user')
222            ->where('user_name', '=', $identifier)
223            ->orWhere('email', '=', $identifier)
224            ->get()
225            ->map(static::rowMapper())
226            ->first();
227    }
228
229    /**
230     * Find the user(s) with a specified genealogy record.
231     *
232     * @param Individual $individual
233     *
234     * @return Collection|User[]
235     */
236    public static function findByIndividual(Individual $individual): Collection
237    {
238        return DB::table('user')
239            ->join('user_gedcom_setting', 'user_gedcom_setting.user_id', '=', 'user.user_id')
240            ->where('gedcom_id', '=', $individual->tree()->id())
241            ->where('setting_value', '=', $individual->xref())
242            ->where('setting_name', '=', 'gedcomid')
243            ->select(['user.*'])
244            ->get()
245            ->map(static::rowMapper());
246    }
247
248    /**
249     * Find the user with a specified user_name.
250     *
251     * @param string $user_name
252     *
253     * @return User|null
254     */
255    public static function findByUserName($user_name)
256    {
257        return DB::table('user')
258            ->where('user_name', '=', $user_name)
259            ->get()
260            ->map(static::rowMapper())
261            ->first();
262    }
263
264    /**
265     * Get a list of all users.
266     *
267     * @return Collection|User[]
268     */
269    public static function all(): Collection
270    {
271        return DB::table('user')
272            ->where('user_id', '>', 0)
273            ->orderBy('real_name')
274            ->select(['user_id', 'user_name', 'real_name', 'email'])
275            ->get()
276            ->map(static::rowMapper());
277    }
278
279    /**
280     * Get a list of all administrators.
281     *
282     * @return Collection|User[]
283     */
284    public static function administrators(): Collection
285    {
286        return DB::table('user')
287            ->join('user_setting', function (JoinClause $join): void {
288                $join
289                    ->on('user_setting.user_id', '=', 'user.user_id')
290                    ->where('user_setting.setting_name', '=', 'canadmin')
291                    ->where('user_setting.setting_value', '=', '1');
292            })
293            ->where('user.user_id', '>', 0)
294            ->orderBy('real_name')
295            ->select(['user.user_id', 'user_name', 'real_name', 'email'])
296            ->get()
297            ->map(static::rowMapper());
298    }
299
300    /**
301     * Validate a supplied password
302     *
303     * @param string $password
304     *
305     * @return bool
306     */
307    public function checkPassword(string $password): bool
308    {
309        $password_hash = DB::table('user')
310            ->where('user_id', '=', $this->user_id)
311            ->value('password');
312
313        if ($password_hash !== null && password_verify($password, $password_hash)) {
314            if (password_needs_rehash($password_hash, PASSWORD_DEFAULT)) {
315                $this->setPassword($password);
316            }
317
318            return true;
319        }
320
321        return false;
322    }
323
324    /**
325     * Get a list of all managers.
326     *
327     * @return Collection|User[]
328     */
329    public static function managers(): Collection
330    {
331        return DB::table('user')
332            ->join('user_gedcom_setting', function (JoinClause $join): void {
333                $join
334                    ->on('user_gedcom_setting.user_id', '=', 'user.user_id')
335                    ->where('user_gedcom_setting.setting_name', '=', 'canedit')
336                    ->where('user_gedcom_setting.setting_value', '=', 'admin');
337            })
338            ->where('user.user_id', '>', 0)
339            ->orderBy('real_name')
340            ->select(['user.user_id', 'user_name', 'real_name', 'email'])
341            ->get()
342            ->map(static::rowMapper());
343    }
344
345    /**
346     * Get a list of all moderators.
347     *
348     * @return Collection|User[]
349     */
350    public static function moderators(): Collection
351    {
352        return DB::table('user')
353            ->join('user_gedcom_setting', function (JoinClause $join): void {
354                $join
355                    ->on('user_gedcom_setting.user_id', '=', 'user.user_id')
356                    ->where('user_gedcom_setting.setting_name', '=', 'canedit')
357                    ->where('user_gedcom_setting.setting_value', '=', 'accept');
358            })
359            ->where('user.user_id', '>', 0)
360            ->orderBy('real_name')
361            ->select(['user.user_id', 'user_name', 'real_name', 'email'])
362            ->get()
363            ->map(static::rowMapper());
364    }
365
366    /**
367     * Get a list of all verified users.
368     *
369     * @return Collection|User[]
370     */
371    public static function unapproved(): Collection
372    {
373        return DB::table('user')
374            ->join('user_setting', function (JoinClause $join): void {
375                $join
376                    ->on('user_setting.user_id', '=', 'user.user_id')
377                    ->where('user_setting.setting_name', '=', 'verified_by_admin')
378                    ->where('user_setting.setting_value', '=', '0');
379            })
380            ->where('user.user_id', '>', 0)
381            ->orderBy('real_name')
382            ->select(['user.user_id', 'user_name', 'real_name', 'email'])
383            ->get()
384            ->map(static::rowMapper());
385    }
386
387    /**
388     * Get a list of all verified users.
389     *
390     * @return Collection|User[]
391     */
392    public static function unverified(): Collection
393    {
394        return DB::table('user')
395            ->join('user_setting', function (JoinClause $join): void {
396                $join
397                    ->on('user_setting.user_id', '=', 'user.user_id')
398                    ->where('user_setting.setting_name', '=', 'verified')
399                    ->where('user_setting.setting_value', '=', '0');
400            })
401            ->where('user.user_id', '>', 0)
402            ->orderBy('real_name')
403            ->select(['user.user_id', 'user_name', 'real_name', 'email'])
404            ->get()
405            ->map(static::rowMapper());
406    }
407
408    /**
409     * Get a list of all users who are currently logged in.
410     *
411     * @return Collection|User[]
412     */
413    public static function allLoggedIn(): Collection
414    {
415        return DB::table('user')
416            ->join('session', 'session.user_id', '=', 'user.user_id')
417            ->where('user.user_id', '>', 0)
418            ->orderBy('real_name')
419            ->select(['user.user_id', 'user_name', 'real_name', 'email'])
420            ->distinct()
421            ->get()
422            ->map(static::rowMapper());
423    }
424
425    /**
426     * Get the numeric ID for this user.
427     *
428     * @return int
429     */
430    public function id(): int
431    {
432        return $this->user_id;
433    }
434
435    /**
436     * Get the login name for this user.
437     *
438     * @return string
439     */
440    public function getUserName(): string
441    {
442        return $this->user_name;
443    }
444
445    /**
446     * Set the login name for this user.
447     *
448     * @param string $user_name
449     *
450     * @return $this
451     */
452    public function setUserName($user_name): self
453    {
454        if ($this->user_name !== $user_name) {
455            $this->user_name = $user_name;
456
457            DB::table('user')
458                ->where('user_id', '=', $this->user_id)
459                ->update([
460                    'user_name' => $user_name,
461                ]);
462        }
463
464        return $this;
465    }
466
467    /**
468     * Get the real name of this user.
469     *
470     * @return string
471     */
472    public function getRealName(): string
473    {
474        return $this->real_name;
475    }
476
477    /**
478     * Set the real name of this user.
479     *
480     * @param string $real_name
481     *
482     * @return User
483     */
484    public function setRealName($real_name): User
485    {
486        if ($this->real_name !== $real_name) {
487            $this->real_name = $real_name;
488
489            DB::table('user')
490                ->where('user_id', '=', $this->user_id)
491                ->update([
492                    'real_name' => $real_name,
493                ]);
494        }
495
496        return $this;
497    }
498
499    /**
500     * Get the email address of this user.
501     *
502     * @return string
503     */
504    public function getEmail(): string
505    {
506        return $this->email;
507    }
508
509    /**
510     * Set the email address of this user.
511     *
512     * @param string $email
513     *
514     * @return User
515     */
516    public function setEmail($email): User
517    {
518        if ($this->email !== $email) {
519            $this->email = $email;
520
521            DB::table('user')
522                ->where('user_id', '=', $this->user_id)
523                ->update([
524                    'email' => $email,
525                ]);
526        }
527
528        return $this;
529    }
530
531    /**
532     * Set the password of this user.
533     *
534     * @param string $password
535     *
536     * @return User
537     */
538    public function setPassword($password): User
539    {
540        DB::table('user')
541            ->where('user_id', '=', $this->user_id)
542            ->update([
543                'password' => password_hash($password, PASSWORD_DEFAULT),
544            ]);
545
546        return $this;
547    }
548
549    /**
550     * Fetch a user option/setting from the wt_user_setting table.
551     * Since we'll fetch several settings for each user, and since there aren’t
552     * that many of them, fetch them all in one database query
553     *
554     * @param string $setting_name
555     * @param string $default
556     *
557     * @return string
558     */
559    public function getPreference($setting_name, $default = ''): string
560    {
561        $preferences = app('cache.array')->rememberForever('user_setting' . $this->user_id, function () {
562            if ($this->user_id) {
563                return DB::table('user_setting')
564                    ->where('user_id', '=', $this->user_id)
565                    ->pluck('setting_value', 'setting_name')
566                    ->all();
567            } else {
568                return [];
569            }
570        });
571
572        return $preferences[$setting_name] ?? $default;
573    }
574
575    /**
576     * Update a setting for the user.
577     *
578     * @param string $setting_name
579     * @param string $setting_value
580     *
581     * @return User
582     */
583    public function setPreference($setting_name, $setting_value): User
584    {
585        if ($this->user_id !== 0 && $this->getPreference($setting_name) !== $setting_value) {
586            DB::table('user_setting')->updateOrInsert([
587                'user_id'      => $this->user_id,
588                'setting_name' => $setting_name,
589            ], [
590                'setting_value' => $setting_value,
591            ]);
592
593            $this->preferences[$setting_name] = $setting_value;
594        }
595
596        app('cache.array')->forget('user_setting' . $this->user_id);
597
598        return $this;
599    }
600}
601