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