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