xref: /webtrees/app/User.php (revision 37b0f34f0addc8d3fe661aca488ebda8027f894f)
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    public 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, just set the user to null.
105        DB::table('log')
106            ->where('user_id', '=', $this->user_id)
107            ->update(['user_id' => null]);
108
109        // Take over the user’s pending changes. (What else could we do with them?)
110        DB::table('change')
111            ->where('user_id', '=', $this->user_id)
112            ->where('status', '=', 'rejected')
113            ->delete();
114
115        DB::table('change')
116            ->where('user_id', '=', $this->user_id)
117            ->update(['user_id' => Auth::id()]);
118
119        // Take over the user's contact details
120        DB::table('gedcom_setting')
121            ->where('setting_value', '=', $this->user_id)
122            ->whereIn('setting_name', ['CONTACT_USER_ID', 'WEBMASTER_USER_ID'])
123            ->update(['setting_value' => Auth::id()]);
124
125        // Delete settings and preferences
126        DB::table('block_setting')
127            ->join('block', 'block_setting.block_id', '=', 'block.block_id')
128            ->where('user_id', '=', $this->user_id)
129            ->delete();
130
131        DB::table('block')->where('user_id', '=', $this->user_id)->delete();
132        DB::table('user_gedcom_setting')->where('user_id', '=', $this->user_id)->delete();
133        DB::table('user_setting')->where('user_id', '=', $this->user_id)->delete();
134        DB::table('message')->where('user_id', '=', $this->user_id)->delete();
135        DB::table('user')->where('user_id', '=', $this->user_id)->delete();
136
137        unset(self::$cache[$this->user_id]);
138    }
139
140    /**
141     * Find the user with a specified user_id.
142     *
143     * @param int|null $user_id
144     *
145     * @return User|null
146     */
147    public static function find($user_id)
148    {
149        if (!array_key_exists($user_id, self::$cache)) {
150            $row = DB::table('user')
151                ->where('user_id', '=', $user_id)
152                ->first();
153
154            if ($row) {
155                self::$cache[$user_id] = new self($row);
156            } else {
157                self::$cache[$user_id] = null;
158            }
159        }
160
161        return self::$cache[$user_id];
162    }
163
164    /**
165     * Find the user with a specified email address.
166     *
167     * @param string $email
168     *
169     * @return User|null
170     */
171    public static function findByEmail($email)
172    {
173        $user_id = (int) DB::table('user')
174            ->where('email', '=', $email)
175            ->value('user_id');
176
177        return self::find($user_id);
178    }
179
180    /**
181     * Find the user with a specified user_name or email address.
182     *
183     * @param string $identifier
184     *
185     * @return User|null
186     */
187    public static function findByIdentifier($identifier)
188    {
189        $user_id = (int) DB::table('user')
190            ->where('user_name', '=', $identifier)
191            ->orWhere('email', '=', $identifier)
192            ->value('user_id');
193
194        return self::find($user_id);
195    }
196
197    /**
198     * Find the user with a specified genealogy record.
199     *
200     * @param Individual $individual
201     *
202     * @return User|null
203     */
204    public static function findByIndividual(Individual $individual)
205    {
206        $user_id = (int) Database::prepare(
207            "SELECT user_id" .
208            " FROM `##user_gedcom_setting`" .
209            " WHERE gedcom_id = :tree_id AND setting_name = 'gedcomid' AND setting_value = :xref"
210        )->execute([
211            'tree_id' => $individual->tree()->id(),
212            'xref'    => $individual->xref(),
213        ])->fetchOne();
214
215        return self::find($user_id);
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 = Database::prepare(
242            "SELECT user_id, user_name, real_name, email" .
243            " FROM `##user`" .
244            " WHERE user_id > 0" .
245            " ORDER BY real_name"
246        )->fetchAll();
247
248        return array_map(function (stdClass $row): User {
249            return new static($row);
250        }, $rows);
251    }
252
253    /**
254     * Get a list of all administrators.
255     *
256     * @return User[]
257     */
258    public static function administrators(): array
259    {
260        $rows = Database::prepare(
261            "SELECT user_id, user_name, real_name, email" .
262            " FROM `##user`" .
263            " JOIN `##user_setting` USING (user_id)" .
264            " WHERE user_id > 0 AND setting_name = 'canadmin' AND setting_value = '1'" .
265            " ORDER BY real_name"
266        )->fetchAll();
267
268        return array_map(function (stdClass $row): User {
269            return new static($row);
270        }, $rows);
271    }
272
273    /**
274     * Validate a supplied password
275     *
276     * @param string $password
277     *
278     * @return bool
279     */
280    public function checkPassword(string $password): bool
281    {
282        $password_hash = Database::prepare(
283            "SELECT password FROM `##user` WHERE user_id = ?"
284        )->execute([$this->user_id])->fetchOne();
285
286        if ($password_hash !== null && password_verify($password, $password_hash)) {
287            if (password_needs_rehash($password_hash, PASSWORD_DEFAULT)) {
288                $this->setPassword($password);
289            }
290
291            return true;
292        }
293
294        return false;
295    }
296
297    /**
298     * Get a list of all managers.
299     *
300     * @return User[]
301     */
302    public static function managers(): array
303    {
304        $rows = Database::prepare(
305            "SELECT user_id, user_name, real_name, email" .
306            " FROM `##user` JOIN `##user_gedcom_setting` USING (user_id)" .
307            " WHERE setting_name = 'canedit' AND setting_value='admin'" .
308            " GROUP BY user_id, real_name" .
309            " ORDER BY real_name"
310        )->fetchAll();
311
312        return array_map(function (stdClass $row): User {
313            return new static($row);
314        }, $rows);
315    }
316
317    /**
318     * Get a list of all moderators.
319     *
320     * @return User[]
321     */
322    public static function moderators(): array
323    {
324        $rows = Database::prepare(
325            "SELECT user_id, user_name, real_name, email" .
326            " FROM `##user` JOIN `##user_gedcom_setting` USING (user_id)" .
327            " WHERE setting_name = 'canedit' AND setting_value='accept'" .
328            " GROUP BY user_id, real_name" .
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 unapproved(): 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_by_admin' 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 verified users.
358     *
359     * @return User[]
360     */
361    public static function unverified(): array
362    {
363        $rows = Database::prepare(
364            "SELECT user_id, user_name, real_name, email" .
365            " FROM `##user` JOIN `##user_setting` USING (user_id)" .
366            " WHERE setting_name = 'verified' AND setting_value = '0'" .
367            " ORDER BY real_name"
368        )->fetchAll();
369
370        return array_map(function (stdClass $row): User {
371            return new static($row);
372        }, $rows);
373    }
374
375    /**
376     * Get a list of all users who are currently logged in.
377     *
378     * @return User[]
379     */
380    public static function allLoggedIn(): array
381    {
382        $rows = Database::prepare(
383            "SELECT DISTINCT user_id, user_name, real_name, email" .
384            " FROM `##user`" .
385            " JOIN `##session` USING (user_id)"
386        )->fetchAll();
387
388        return array_map(function (stdClass $row): User {
389            return new static($row);
390        }, $rows);
391    }
392
393    /**
394     * Get the numeric ID for this user.
395     *
396     * @return int
397     */
398    public function getUserId(): int
399    {
400        return $this->user_id;
401    }
402
403    /**
404     * Get the login name for this user.
405     *
406     * @return string
407     */
408    public function getUserName(): string
409    {
410        return $this->user_name;
411    }
412
413    /**
414     * Set the login name for this user.
415     *
416     * @param string $user_name
417     *
418     * @return $this
419     */
420    public function setUserName($user_name): self
421    {
422        if ($this->user_name !== $user_name) {
423            $this->user_name = $user_name;
424
425            DB::table('user')
426                ->where('user_id', '=', $this->user_id)
427                ->update([
428                    'user_name' => $user_name,
429                ]);
430        }
431
432        return $this;
433    }
434
435    /**
436     * Get the real name of this user.
437     *
438     * @return string
439     */
440    public function getRealName(): string
441    {
442        return $this->real_name;
443    }
444
445    /**
446     * Set the real name of this user.
447     *
448     * @param string $real_name
449     *
450     * @return User
451     */
452    public function setRealName($real_name): User
453    {
454        if ($this->real_name !== $real_name) {
455            $this->real_name = $real_name;
456
457            DB::table('user')
458                ->where('user_id', '=', $this->user_id)
459                ->update([
460                    'real_name' => $real_name,
461                ]);
462        }
463
464        return $this;
465    }
466
467    /**
468     * Get the email address of this user.
469     *
470     * @return string
471     */
472    public function getEmail(): string
473    {
474        return $this->email;
475    }
476
477    /**
478     * Set the email address of this user.
479     *
480     * @param string $email
481     *
482     * @return User
483     */
484    public function setEmail($email): User
485    {
486        if ($this->email !== $email) {
487            $this->email = $email;
488
489            DB::table('user')
490                ->where('user_id', '=', $this->user_id)
491                ->update([
492                    'email' => $email,
493                ]);
494        }
495
496        return $this;
497    }
498
499    /**
500     * Set the password of this user.
501     *
502     * @param string $password
503     *
504     * @return User
505     */
506    public function setPassword($password): User
507    {
508        DB::table('user')
509            ->where('user_id', '=', $this->user_id)
510            ->update([
511                'password' => password_hash($password, PASSWORD_DEFAULT),
512            ]);
513
514        return $this;
515    }
516
517    /**
518     * Fetch a user option/setting from the wt_user_setting table.
519     * Since we'll fetch several settings for each user, and since there aren’t
520     * that many of them, fetch them all in one database query
521     *
522     * @param string $setting_name
523     * @param string $default
524     *
525     * @return string
526     */
527    public function getPreference($setting_name, $default = ''): string
528    {
529        if (empty($this->preferences) && $this->user_id !== 0) {
530            $this->preferences = DB::table('user_setting')
531                ->where('user_id', '=', $this->user_id)
532                ->pluck('setting_value', 'setting_name')
533                ->all();
534        }
535
536        return $this->preferences[$setting_name] ?? $default;
537    }
538
539    /**
540     * Update a setting for the user.
541     *
542     * @param string $setting_name
543     * @param string $setting_value
544     *
545     * @return User
546     */
547    public function setPreference($setting_name, $setting_value): User
548    {
549        if ($this->user_id !== 0 && $this->getPreference($setting_name) !== $setting_value) {
550            DB::table('user_setting')->updateOrInsert([
551                'user_id'      => $this->user_id,
552                'setting_name' => $setting_name,
553            ], [
554                'setting_value' => $setting_value,
555            ]);
556
557            $this->preferences[$setting_name] = $setting_value;
558        }
559
560        return $this;
561    }
562}
563