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