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