xref: /webtrees/app/User.php (revision e8e7866be1a6002e498a9d6b7d1257c59d7e3484)
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 int|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 self($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 = self::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
144		return $user;
145	}
146
147	/**
148	 * Get a count of all users.
149	 *
150	 * @return int
151	 */
152	public static function count() {
153		return (int) Database::prepare(
154			"SELECT SQL_CACHE COUNT(*)" .
155			" FROM `##user`" .
156			" WHERE user_id > 0"
157		)->fetchOne();
158	}
159
160	/**
161	 * Get a list of all users.
162	 *
163	 * @return User[]
164	 */
165	public static function all() {
166		$users = array();
167
168		$rows = Database::prepare(
169			"SELECT SQL_CACHE user_id, user_name, real_name, email" .
170			" FROM `##user`" .
171			" WHERE user_id > 0" .
172			" ORDER BY user_name"
173		)->fetchAll();
174
175		foreach ($rows as $row) {
176			$users[] = new self($row);
177		}
178
179		return $users;
180	}
181
182	/**
183	 * Get a list of all administrators.
184	 *
185	 * @return User[]
186	 */
187	public static function allAdmins() {
188		$rows = Database::prepare(
189			"SELECT SQL_CACHE user_id, user_name, real_name, email" .
190			" FROM `##user`" .
191			" JOIN `##user_setting` USING (user_id)" .
192			" WHERE user_id > 0" .
193			"   AND setting_name = 'canadmin'" .
194			"   AND setting_value = '1'"
195		)->fetchAll();
196
197		$users = array();
198		foreach ($rows as $row) {
199			$users[] = new self($row);
200		}
201
202		return $users;
203	}
204
205	/**
206	 * Get a list of all verified uses.
207	 *
208	 * @return User[]
209	 */
210	public static function allVerified() {
211		$rows = Database::prepare(
212			"SELECT SQL_CACHE user_id, user_name, real_name, email" .
213			" FROM `##user`" .
214			" JOIN `##user_setting` USING (user_id)" .
215			" WHERE user_id > 0" .
216			"   AND setting_name = 'verified'" .
217			"   AND setting_value = '1'"
218		)->fetchAll();
219
220		$users = array();
221		foreach ($rows as $row) {
222			$users[] = new self($row);
223		}
224
225		return $users;
226	}
227
228	/**
229	 * Get a list of all users who are currently logged in.
230	 *
231	 * @return User[]
232	 */
233	public static function allLoggedIn() {
234		$rows = Database::prepare(
235			"SELECT SQL_NO_CACHE DISTINCT user_id, user_name, real_name, email" .
236			" FROM `##user`" .
237			" JOIN `##session` USING (user_id)"
238		)->fetchAll();
239
240		$users = array();
241		foreach ($rows as $row) {
242			$users[] = new self($row);
243		}
244
245		return $users;
246	}
247
248	/**
249	 * Create a new user object from a row in the database.
250	 *
251	 * @param \stdclass $user A row from the wt_user table
252	 */
253	public function __construct(\stdClass $user) {
254		$this->user_id   = $user->user_id;
255		$this->user_name = $user->user_name;
256		$this->real_name = $user->real_name;
257		$this->email     = $user->email;
258	}
259
260	/**
261	 * Delete a user
262	 */
263	public function delete() {
264		// Don't delete the logs.
265		Database::prepare("UPDATE `##log` SET user_id=NULL WHERE user_id =?")->execute(array($this->user_id));
266		// Take over the user’s pending changes. (What else could we do with them?)
267		Database::prepare("DELETE FROM `##change` WHERE user_id=? AND status='accepted'")->execute(array($this->user_id));
268		Database::prepare("UPDATE `##change` SET user_id=? WHERE user_id=?")->execute(array($this->user_id, $this->user_id));
269		Database::prepare("DELETE `##block_setting` FROM `##block_setting` JOIN `##block` USING (block_id) WHERE user_id=?")->execute(array($this->user_id));
270		Database::prepare("DELETE FROM `##block` WHERE user_id=?")->execute(array($this->user_id));
271		Database::prepare("DELETE FROM `##user_gedcom_setting` WHERE user_id=?")->execute(array($this->user_id));
272		Database::prepare("DELETE FROM `##gedcom_setting` WHERE setting_value=? AND setting_name in ('CONTACT_USER_ID', 'WEBMASTER_USER_ID')")->execute(array($this->user_id));
273		Database::prepare("DELETE FROM `##user_setting` WHERE user_id=?")->execute(array($this->user_id));
274		Database::prepare("DELETE FROM `##message` WHERE user_id=?")->execute(array($this->user_id));
275		Database::prepare("DELETE FROM `##user` WHERE user_id=?")->execute(array($this->user_id));
276	}
277
278	/** Validate a supplied password
279	 * @param string $password
280	 *
281	 * @return bool
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
293			return true;
294		} else {
295			return false;
296		}
297	}
298
299	/**
300	 * Get the numeric ID for this user.
301	 *
302	 * @return string
303	 */
304	public function getUserId() {
305		return $this->user_id;
306	}
307
308	/**
309	 * Get the login name for this user.
310	 *
311	 * @return string
312	 */
313	public function getUserName() {
314		return $this->user_name;
315	}
316
317	/**
318	 * Set the login name for this user.
319	 *
320	 * @param string $user_name
321	 *
322	 * @return $this
323	 */
324	public function setUserName($user_name) {
325		if ($this->user_name !== $user_name) {
326			$this->user_name = $user_name;
327			Database::prepare(
328				"UPDATE `##user` SET user_name = ? WHERE user_id = ?"
329			)->execute(array($user_name, $this->user_id));
330		}
331
332		return $this;
333	}
334
335	/**
336	 * Get the real name of this user.
337	 *
338	 * @return string
339	 */
340	public function getRealName() {
341		return $this->real_name;
342	}
343
344	/**
345	 * Get the real name of this user, for display on screen.
346	 *
347	 * @return string
348	 */
349	public function getRealNameHtml() {
350		return '<span dir="auto">' . Filter::escapeHtml($this->real_name) . '</span>';
351	}
352
353	/**
354	 * Set the real name of this user.
355	 *
356	 * @param string $real_name
357	 *
358	 * @return User
359	 */
360	public function setRealName($real_name) {
361		if ($this->real_name !== $real_name) {
362			$this->real_name = $real_name;
363			Database::prepare(
364				"UPDATE `##user` SET real_name = ? WHERE user_id = ?"
365			)->execute(array($real_name, $this->user_id));
366		}
367
368		return $this;
369	}
370
371	/**
372	 * Get the email address of this user.
373	 *
374	 * @return string
375	 */
376	public function getEmail() {
377		return $this->email;
378	}
379
380	/**
381	 * Set the email address of this user.
382	 *
383	 * @param string $email
384	 *
385	 * @return User
386	 */
387	public function setEmail($email) {
388		if ($this->email !== $email) {
389			$this->email = $email;
390			Database::prepare(
391				"UPDATE `##user` SET email = ? WHERE user_id = ?"
392			)->execute(array($email, $this->user_id));
393		}
394
395		return $this;
396	}
397
398	/**
399	 * Set the password of this user.
400	 *
401	 * @param string $password
402	 *
403	 * @return User
404	 */
405	public function setPassword($password) {
406		Database::prepare(
407			"UPDATE `##user` SET password = ? WHERE user_id = ?"
408		)->execute(array(password_hash($password, PASSWORD_DEFAULT), $this->user_id));
409
410		return $this;
411	}
412
413	/**
414	 * Fetch a user option/setting from the wt_user_setting table.
415	 *
416	 * Since we'll fetch several settings for each user, and since there aren’t
417	 * that many of them, fetch them all in one database query
418	 *
419	 * @param string      $setting_name
420	 * @param string|null $default
421	 *
422	 * @return string|null
423	 */
424	public function getPreference($setting_name, $default = null) {
425		if ($this->preferences === null) {
426			if ($this->user_id) {
427				$this->preferences = Database::prepare(
428					"SELECT SQL_CACHE setting_name, setting_value FROM `##user_setting` WHERE user_id = ?"
429				)->execute(array($this->user_id))->fetchAssoc();
430			} else {
431				// Not logged in?  We have no preferences.
432				$this->preferences = array();
433			}
434		}
435
436		if (array_key_exists($setting_name, $this->preferences)) {
437			return $this->preferences[$setting_name];
438		} else {
439			return $default;
440		}
441	}
442
443	/**
444	 * Update a setting for the user.
445	 *
446	 * @param string $setting_name
447	 * @param string $setting_value
448	 *
449	 * @return User
450	 */
451	public function setPreference($setting_name, $setting_value) {
452		if ($this->user_id && $this->getPreference($setting_name) !== $setting_value) {
453			Database::prepare("REPLACE INTO `##user_setting` (user_id, setting_name, setting_value) VALUES (?, ?, LEFT(?, 255))")
454				->execute(array($this->user_id, $setting_name, $setting_value));
455			$this->preferences[$setting_name] = $setting_value;
456		}
457
458		return $this;
459	}
460
461	/**
462	 * Delete a setting for the user.
463	 *
464	 * @param string $setting_name
465	 *
466	 * @return User
467	 */
468	public function deletePreference($setting_name) {
469		if ($this->user_id && $this->getPreference($setting_name) !== null) {
470			Database::prepare("DELETE FROM `##user_setting` WHERE user_id = ? AND setting_name = ?")
471				->execute(array($this->user_id, $setting_name));
472			unset($this->preferences[$setting_name]);
473		}
474
475		return $this;
476	}
477}
478