xref: /webtrees/app/User.php (revision 4b9ff166b3342695f2a94855b7a33368e6d55c35)
1<?php
2namespace Fisharebest\Webtrees;
3
4/**
5 * webtrees: online genealogy
6 * Copyright (C) 2015 webtrees development team
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19/**
20 * Class User - Provide an interface to the wt_user table.
21 */
22class User {
23	/** @var  string The primary key of this user. */
24	private $user_id;
25
26	/** @var  string The login name of this user. */
27	private $user_name;
28
29	/** @var  string The real (display) name of this user. */
30	private $real_name;
31
32	/** @var  string The email address of this user. */
33	private $email;
34
35	/** @var array Cached copy of the wt_user_setting table. */
36	private $preferences;
37
38	/** @var  User[] Only fetch users from the database once. */
39	private static $cache = array();
40
41	/**
42	 * Find the user with a specified user_id.
43	 *
44	 * @param integer|null $user_id
45	 *
46	 * @return User|null
47	 */
48	public static function find($user_id) {
49		if (!array_key_exists($user_id, self::$cache)) {
50			$row = Database::prepare(
51				"SELECT SQL_CACHE user_id, user_name, real_name, email FROM `##user` WHERE user_id = ?"
52			)->execute(array($user_id))->fetchOneRow();
53			if ($row) {
54				self::$cache[$user_id] = new User($row);
55			} else {
56				self::$cache[$user_id] = null;
57			}
58		}
59
60		return self::$cache[$user_id];
61	}
62
63	/**
64	 * Find the user with a specified user_id.
65	 *
66	 * @param string $identifier
67	 *
68	 * @return User|null
69	 */
70	public static function findByIdentifier($identifier) {
71		$user_id = Database::prepare(
72			"SELECT SQL_CACHE user_id FROM `##user` WHERE ? IN (user_name, email)"
73		)->execute(array($identifier))->fetchOne();
74
75		return self::find($user_id);
76	}
77
78	/**
79	 * Find the user with a specified genealogy record.
80	 *
81	 * @param Individual $individual
82	 *
83	 * @return User|null
84	 */
85	public static function findByGenealogyRecord(Individual $individual) {
86		$user_id = Database::prepare(
87			"SELECT SQL_CACHE user_id" .
88			" FROM `##user_gedcom_setting`" .
89			" WHERE gedcom_id = :tree_id AND setting_name = 'gedcomid' AND setting_value = :xref"
90		)->execute(array(
91			'tree_id' => $individual->getTree()->getTreeId(),
92			'xref'    => $individual->getXref()
93		))->fetchOne();
94
95		return self::find($user_id);
96	}
97
98	/**
99	 * Find the latest user to register.
100	 *
101	 * @return User|null
102	 */
103	public static function findLatestToRegister() {
104		$user_id = Database::prepare(
105			"SELECT SQL_CACHE u.user_id" .
106			" FROM `##user` u" .
107			" LEFT JOIN `##user_setting` us ON (u.user_id=us.user_id AND us.setting_name='reg_timestamp') " .
108			" ORDER BY us.setting_value DESC LIMIT 1"
109		)->execute()->fetchOne();
110
111		return self::find($user_id);
112	}
113
114	/**
115	 * Create a new user.
116	 *
117	 * The calling code needs to check for duplicates identifiers before calling
118	 * this function.
119	 *
120	 * @param string $user_name
121	 * @param string $real_name
122	 * @param string $email
123	 * @param string $password
124	 *
125	 * @return User
126	 */
127	public static function create($user_name, $real_name, $email, $password) {
128		Database::prepare(
129			"INSERT INTO `##user` (user_name, real_name, email, password) VALUES (:user_name, :real_name, :email, :password)"
130		)->execute(array(
131			'user_name' => $user_name,
132			'real_name' => $real_name,
133			'email'     => $email,
134			'password'  => password_hash($password, PASSWORD_DEFAULT),
135		));
136
137		// Set default blocks for this user
138		$user = User::findByIdentifier($user_name);
139		Database::prepare(
140			"INSERT INTO `##block` (`user_id`, `location`, `block_order`, `module_name`)" .
141			" SELECT :user_id , `location`, `block_order`, `module_name` FROM `##block` WHERE `user_id` = -1"
142		)->execute(array('user_id' => $user->getUserId()));
143		return $user;
144	}
145
146	/**
147	 * Get a count of all users.
148	 *
149	 * @return integer
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 User($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 User($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 User($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 User($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	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	 *
279	 * @param string $password
280	 *
281	 * @return boolean
282	 */
283	public function checkPassword($password) {
284		$password_hash = Database::prepare(
285			"SELECT password FROM `##user` WHERE user_id = ?"
286		)->execute(array($this->user_id))->fetchOne();
287
288		if (password_verify($password, $password_hash)) {
289			if (password_needs_rehash($password_hash, PASSWORD_DEFAULT)) {
290				$this->setPassword($password);
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