xref: /webtrees/app/User.php (revision 94026f200c17dcfccf296678dc90ea88b14f6246)
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 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(s) with a specified genealogy record.
199     *
200     * @param Individual $individual
201     *
202     * @return User[]
203     */
204    public static function findByIndividual(Individual $individual): array
205    {
206        $user_ids = DB::table('user_gedcom_setting')
207            ->where('gedcom_id', '=', $individual->tree()->id())
208            ->where('setting_value', '=', $individual->xref())
209            ->where('setting_name', '=', 'gedcomid')
210            ->pluck('user_id');
211
212        return $user_ids->map(function (string $user_id): User {
213            return self::find((int) $user_id);
214        })->all();
215    }
216
217    /**
218     * Find the user with a specified user_name.
219     *
220     * @param string $user_name
221     *
222     * @return User|null
223     */
224    public static function findByUserName($user_name)
225    {
226        $user_id = (int) DB::table('user')
227            ->where('user_name', '=', $user_name)
228            ->value('user_id');
229
230        return self::find($user_id);
231    }
232
233    /**
234     * Get a list of all users.
235     *
236     * @return User[]
237     */
238    public static function all(): array
239    {
240        $rows = Database::prepare(
241            "SELECT user_id, user_name, real_name, email" .
242            " FROM `##user`" .
243            " WHERE user_id > 0" .
244            " ORDER BY real_name"
245        )->fetchAll();
246
247        return array_map(function (stdClass $row): User {
248            return new static($row);
249        }, $rows);
250    }
251
252    /**
253     * Get a list of all administrators.
254     *
255     * @return User[]
256     */
257    public static function administrators(): array
258    {
259        $rows = Database::prepare(
260            "SELECT user_id, user_name, real_name, email" .
261            " FROM `##user`" .
262            " JOIN `##user_setting` USING (user_id)" .
263            " WHERE user_id > 0 AND setting_name = 'canadmin' AND setting_value = '1'" .
264            " ORDER BY real_name"
265        )->fetchAll();
266
267        return array_map(function (stdClass $row): User {
268            return new static($row);
269        }, $rows);
270    }
271
272    /**
273     * Validate a supplied password
274     *
275     * @param string $password
276     *
277     * @return bool
278     */
279    public function checkPassword(string $password): bool
280    {
281        $password_hash = Database::prepare(
282            "SELECT password FROM `##user` WHERE user_id = ?"
283        )->execute([$this->user_id])->fetchOne();
284
285        if ($password_hash !== null && password_verify($password, $password_hash)) {
286            if (password_needs_rehash($password_hash, PASSWORD_DEFAULT)) {
287                $this->setPassword($password);
288            }
289
290            return true;
291        }
292
293        return false;
294    }
295
296    /**
297     * Get a list of all managers.
298     *
299     * @return User[]
300     */
301    public static function managers(): array
302    {
303        $rows = Database::prepare(
304            "SELECT user_id, user_name, real_name, email" .
305            " FROM `##user` JOIN `##user_gedcom_setting` USING (user_id)" .
306            " WHERE setting_name = 'canedit' AND setting_value='admin'" .
307            " GROUP BY user_id, real_name" .
308            " ORDER BY real_name"
309        )->fetchAll();
310
311        return array_map(function (stdClass $row): User {
312            return new static($row);
313        }, $rows);
314    }
315
316    /**
317     * Get a list of all moderators.
318     *
319     * @return User[]
320     */
321    public static function moderators(): array
322    {
323        $rows = Database::prepare(
324            "SELECT user_id, user_name, real_name, email" .
325            " FROM `##user` JOIN `##user_gedcom_setting` USING (user_id)" .
326            " WHERE setting_name = 'canedit' AND setting_value='accept'" .
327            " GROUP BY user_id, real_name" .
328            " ORDER BY real_name"
329        )->fetchAll();
330
331        return array_map(function (stdClass $row): User {
332            return new static($row);
333        }, $rows);
334    }
335
336    /**
337     * Get a list of all verified users.
338     *
339     * @return User[]
340     */
341    public static function unapproved(): array
342    {
343        $rows = Database::prepare(
344            "SELECT user_id, user_name, real_name, email" .
345            " FROM `##user` JOIN `##user_setting` USING (user_id)" .
346            " WHERE setting_name = 'verified_by_admin' AND setting_value = '0'" .
347            " ORDER BY real_name"
348        )->fetchAll();
349
350        return array_map(function (stdClass $row): User {
351            return new static($row);
352        }, $rows);
353    }
354
355    /**
356     * Get a list of all verified users.
357     *
358     * @return User[]
359     */
360    public static function unverified(): array
361    {
362        $rows = Database::prepare(
363            "SELECT user_id, user_name, real_name, email" .
364            " FROM `##user` JOIN `##user_setting` USING (user_id)" .
365            " WHERE setting_name = 'verified' AND setting_value = '0'" .
366            " ORDER BY real_name"
367        )->fetchAll();
368
369        return array_map(function (stdClass $row): User {
370            return new static($row);
371        }, $rows);
372    }
373
374    /**
375     * Get a list of all users who are currently logged in.
376     *
377     * @return User[]
378     */
379    public static function allLoggedIn(): array
380    {
381        $rows = Database::prepare(
382            "SELECT DISTINCT user_id, user_name, real_name, email" .
383            " FROM `##user`" .
384            " JOIN `##session` USING (user_id)"
385        )->fetchAll();
386
387        return array_map(function (stdClass $row): User {
388            return new static($row);
389        }, $rows);
390    }
391
392    /**
393     * Get the numeric ID for this user.
394     *
395     * @return int
396     */
397    public function getUserId(): int
398    {
399        return $this->user_id;
400    }
401
402    /**
403     * Get the login name for this user.
404     *
405     * @return string
406     */
407    public function getUserName(): string
408    {
409        return $this->user_name;
410    }
411
412    /**
413     * Set the login name for this user.
414     *
415     * @param string $user_name
416     *
417     * @return $this
418     */
419    public function setUserName($user_name): self
420    {
421        if ($this->user_name !== $user_name) {
422            $this->user_name = $user_name;
423
424            DB::table('user')
425                ->where('user_id', '=', $this->user_id)
426                ->update([
427                    'user_name' => $user_name,
428                ]);
429        }
430
431        return $this;
432    }
433
434    /**
435     * Get the real name of this user.
436     *
437     * @return string
438     */
439    public function getRealName(): string
440    {
441        return $this->real_name;
442    }
443
444    /**
445     * Set the real name of this user.
446     *
447     * @param string $real_name
448     *
449     * @return User
450     */
451    public function setRealName($real_name): User
452    {
453        if ($this->real_name !== $real_name) {
454            $this->real_name = $real_name;
455
456            DB::table('user')
457                ->where('user_id', '=', $this->user_id)
458                ->update([
459                    'real_name' => $real_name,
460                ]);
461        }
462
463        return $this;
464    }
465
466    /**
467     * Get the email address of this user.
468     *
469     * @return string
470     */
471    public function getEmail(): string
472    {
473        return $this->email;
474    }
475
476    /**
477     * Set the email address of this user.
478     *
479     * @param string $email
480     *
481     * @return User
482     */
483    public function setEmail($email): User
484    {
485        if ($this->email !== $email) {
486            $this->email = $email;
487
488            DB::table('user')
489                ->where('user_id', '=', $this->user_id)
490                ->update([
491                    'email' => $email,
492                ]);
493        }
494
495        return $this;
496    }
497
498    /**
499     * Set the password of this user.
500     *
501     * @param string $password
502     *
503     * @return User
504     */
505    public function setPassword($password): User
506    {
507        DB::table('user')
508            ->where('user_id', '=', $this->user_id)
509            ->update([
510                'password' => password_hash($password, PASSWORD_DEFAULT),
511            ]);
512
513        return $this;
514    }
515
516    /**
517     * Fetch a user option/setting from the wt_user_setting table.
518     * Since we'll fetch several settings for each user, and since there aren’t
519     * that many of them, fetch them all in one database query
520     *
521     * @param string $setting_name
522     * @param string $default
523     *
524     * @return string
525     */
526    public function getPreference($setting_name, $default = ''): string
527    {
528        if (empty($this->preferences) && $this->user_id !== 0) {
529            $this->preferences = DB::table('user_setting')
530                ->where('user_id', '=', $this->user_id)
531                ->pluck('setting_value', 'setting_name')
532                ->all();
533        }
534
535        return $this->preferences[$setting_name] ?? $default;
536    }
537
538    /**
539     * Update a setting for the user.
540     *
541     * @param string $setting_name
542     * @param string $setting_value
543     *
544     * @return User
545     */
546    public function setPreference($setting_name, $setting_value): User
547    {
548        if ($this->user_id !== 0 && $this->getPreference($setting_name) !== $setting_value) {
549            DB::table('user_setting')->updateOrInsert([
550                'user_id'      => $this->user_id,
551                'setting_name' => $setting_name,
552            ], [
553                'setting_value' => $setting_value,
554            ]);
555
556            $this->preferences[$setting_name] = $setting_value;
557        }
558
559        return $this;
560    }
561}
562