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