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