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