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