xref: /webtrees/app/User.php (revision 00b1984e374faddab4698e73088f0c964e529b65)
1<?php
2
3/**
4 * webtrees: online genealogy
5 * Copyright (C) 2019 webtrees development team
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18declare(strict_types=1);
19
20namespace Fisharebest\Webtrees;
21
22use Closure;
23use Fisharebest\Webtrees\Contracts\UserInterface;
24use Illuminate\Database\Capsule\Manager as DB;
25use Illuminate\Support\Collection;
26use stdClass;
27
28/**
29 * Provide an interface to the wt_user table.
30 */
31class User implements UserInterface
32{
33    // For historic reasons, user preferences have inconsistent and confusing names.
34    public const PREF_AUTO_ACCEPT_EDITS    = 'auto_accept';
35    public const PREF_CONTACT_METHOD       = 'contactmethod';
36    public const PREF_IS_ACCOUNT_APPROVED  = 'verified_by_admin';
37    public const PREF_IS_ADMINISTRATOR     = 'canadmin';
38    public const PREF_IS_EMAIL_VERIFIED    = 'verified';
39    public const PREF_IS_VISIBLE_ONLINE    = 'visibleonline';
40    public const PREF_LANGUAGE             = 'language';
41    public const PREF_NEW_ACCOUNT_COMMENT  = 'comment';
42    public const PREF_TIMESTAMP_REGISTERED = 'reg_timestamp';
43    public const PREF_TIMESTAMP_ACTIVE     = 'sessiontime';
44    public const PREF_TIME_ZONE            = 'TIMEZONE';
45    public const PREF_THEME                = 'theme';
46    public const PREF_VERIFICATION_TOKEN   = 'reg_hashcode';
47
48    // For historic reasons, user-tree preferences have inconsistent and confusing names.
49    public const PREF_TREE_ACCOUNT_XREF = 'gedcomid';
50    public const PREF_TREE_DEFAULT_XREF = 'rootid';
51    public const PREF_TREE_PATH_LENGTH  = 'RELATIONSHIP_PATH_LENGTH';
52    public const PREF_TREE_ROLE         = 'canedit';
53
54    // For historic reasons, roles have inconsistent and confusing names.
55    public const ROLE_VISITOR   = 'none';
56    public const ROLE_MEMBER    = 'access';
57    public const ROLE_EDITOR    = 'edit';
58    public const ROLE_MODERATOR = 'accept';
59    public const ROLE_MANAGER   = 'admin';
60
61    /** @var  int The primary key of this user. */
62    private $user_id;
63
64    /** @var  string The login name of this user. */
65    private $user_name;
66
67    /** @var  string The real (display) name of this user. */
68    private $real_name;
69
70    /** @var  string The email address of this user. */
71    private $email;
72
73    /** @var string[] Cached copy of the wt_user_setting table. */
74    private $preferences = [];
75
76    /**
77     * User constructor.
78     *
79     * @param int    $user_id
80     * @param string $user_name
81     * @param string $real_name
82     * @param string $email
83     */
84    public function __construct(int $user_id, string $user_name, string $real_name, string $email)
85    {
86        $this->user_id   = $user_id;
87        $this->user_name = $user_name;
88        $this->real_name = $real_name;
89        $this->email     = $email;
90    }
91
92    /**
93     * The user‘s internal identifier.
94     *
95     * @return int
96     */
97    public function id(): int
98    {
99        return $this->user_id;
100    }
101
102    /**
103     * The users email address.
104     *
105     * @return string
106     */
107    public function email(): string
108    {
109        return $this->email;
110    }
111
112    /**
113     * Set the email address of this user.
114     *
115     * @param string $email
116     *
117     * @return User
118     */
119    public function setEmail($email): User
120    {
121        if ($this->email !== $email) {
122            $this->email = $email;
123
124            DB::table('user')
125                ->where('user_id', '=', $this->user_id)
126                ->update([
127                    'email' => $email,
128                ]);
129        }
130
131        return $this;
132    }
133
134    /**
135     * The user‘s real name.
136     *
137     * @return string
138     */
139    public function realName(): string
140    {
141        return $this->real_name;
142    }
143
144    /**
145     * Set the real name of this user.
146     *
147     * @param string $real_name
148     *
149     * @return User
150     */
151    public function setRealName($real_name): User
152    {
153        if ($this->real_name !== $real_name) {
154            $this->real_name = $real_name;
155
156            DB::table('user')
157                ->where('user_id', '=', $this->user_id)
158                ->update([
159                    'real_name' => $real_name,
160                ]);
161        }
162
163        return $this;
164    }
165
166    /**
167     * The user‘s login name.
168     *
169     * @return string
170     */
171    public function userName(): string
172    {
173        return $this->user_name;
174    }
175
176    /**
177     * Set the login name for this user.
178     *
179     * @param string $user_name
180     *
181     * @return $this
182     */
183    public function setUserName($user_name): self
184    {
185        if ($this->user_name !== $user_name) {
186            $this->user_name = $user_name;
187
188            DB::table('user')
189                ->where('user_id', '=', $this->user_id)
190                ->update([
191                    'user_name' => $user_name,
192                ]);
193        }
194
195        return $this;
196    }
197
198    /**
199     * Fetch a user option/setting from the wt_user_setting table.
200     * Since we'll fetch several settings for each user, and since there aren’t
201     * that many of them, fetch them all in one database query
202     *
203     * @param string $setting_name
204     * @param string $default
205     *
206     * @return string
207     */
208    public function getPreference(string $setting_name, string $default = ''): string
209    {
210        $preferences = app('cache.array')->remember('user-prefs-' . $this->user_id, function (): Collection {
211            if ($this->user_id) {
212                return DB::table('user_setting')
213                    ->where('user_id', '=', $this->user_id)
214                    ->pluck('setting_value', 'setting_name');
215            }
216
217            return new Collection();
218        });
219
220        return $preferences->get($setting_name, $default);
221    }
222
223    /**
224     * Update a setting for the user.
225     *
226     * @param string $setting_name
227     * @param string $setting_value
228     *
229     * @return void
230     */
231    public function setPreference(string $setting_name, string $setting_value): void
232    {
233        if ($this->user_id !== 0 && $this->getPreference($setting_name) !== $setting_value) {
234            DB::table('user_setting')->updateOrInsert([
235                'user_id'      => $this->user_id,
236                'setting_name' => $setting_name,
237            ], [
238                'setting_value' => $setting_value,
239            ]);
240
241            $this->preferences[$setting_name] = $setting_value;
242        }
243    }
244
245    /**
246     * Set the password of this user.
247     *
248     * @param string $password
249     *
250     * @return User
251     */
252    public function setPassword(string $password): User
253    {
254        DB::table('user')
255            ->where('user_id', '=', $this->user_id)
256            ->update([
257                'password' => password_hash($password, PASSWORD_DEFAULT),
258            ]);
259
260        return $this;
261    }
262
263
264    /**
265     * Validate a supplied password
266     *
267     * @param string $password
268     *
269     * @return bool
270     */
271    public function checkPassword(string $password): bool
272    {
273        $password_hash = DB::table('user')
274            ->where('user_id', '=', $this->id())
275            ->value('password');
276
277        if ($password_hash !== null && password_verify($password, $password_hash)) {
278            if (password_needs_rehash($password_hash, PASSWORD_DEFAULT)) {
279                $this->setPassword($password);
280            }
281
282            return true;
283        }
284
285        return false;
286    }
287
288    /**
289     * A closure which will create an object from a database row.
290     *
291     * @return Closure
292     */
293    public static function rowMapper(): Closure
294    {
295        return static function (stdClass $row): User {
296            return new self((int) $row->user_id, $row->user_name, $row->real_name, $row->email);
297        };
298    }
299}
300