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