1<?php
2# MantisBT - A PHP based bugtracking system
3
4# MantisBT is free software: you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation, either version 2 of the License, or
7# (at your option) any later version.
8#
9# MantisBT 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#
14# You should have received a copy of the GNU General Public License
15# along with MantisBT.  If not, see <http://www.gnu.org/licenses/>.
16
17/**
18 * User API
19 *
20 * @package CoreAPI
21 * @subpackage UserAPI
22 * @copyright Copyright 2000 - 2002  Kenzaburo Ito - kenito@300baud.org
23 * @copyright Copyright 2002  MantisBT Team - mantisbt-dev@lists.sourceforge.net
24 * @link http://www.mantisbt.org
25 *
26 * @uses access_api.php
27 * @uses authentication_api.php
28 * @uses config_api.php
29 * @uses constant_inc.php
30 * @uses database_api.php
31 * @uses email_api.php
32 * @uses error_api.php
33 * @uses filter_api.php
34 * @uses helper_api.php
35 * @uses lang_api.php
36 * @uses ldap_api.php
37 * @uses project_api.php
38 * @uses project_hierarchy_api.php
39 * @uses string_api.php
40 * @uses user_pref_api.php
41 * @uses utility_api.php
42 */
43
44require_api( 'access_api.php' );
45require_api( 'authentication_api.php' );
46require_api( 'config_api.php' );
47require_api( 'constant_inc.php' );
48require_api( 'database_api.php' );
49require_api( 'email_api.php' );
50require_api( 'error_api.php' );
51require_api( 'filter_api.php' );
52require_api( 'helper_api.php' );
53require_api( 'lang_api.php' );
54require_api( 'ldap_api.php' );
55require_api( 'project_api.php' );
56require_api( 'project_hierarchy_api.php' );
57require_api( 'string_api.php' );
58require_api( 'user_pref_api.php' );
59require_api( 'utility_api.php' );
60
61use Mantis\Exceptions\ClientException;
62
63# Cache of user rows from {user} table, indexed by user_id
64# If id does not exists, a value of 'false' is stored
65$g_cache_user = array();
66
67$g_user_accessible_subprojects_cache = null;
68
69/**
70 * Cache a user row if necessary and return the cached copy
71 * If the second parameter is true (default), trigger an error
72 * if the user can't be found.  If the second parameter is
73 * false, return false if the user can't be found.
74 *
75 * @param integer $p_user_id        A valid user identifier.
76 * @param boolean $p_trigger_errors Trigger an error is the user does not exist.
77 * @return array|boolean array of database data or false if not found
78 */
79function user_cache_row( $p_user_id, $p_trigger_errors = true ) {
80	global $g_cache_user;
81
82	$c_user_id = (int)$p_user_id;
83
84	if( !isset( $g_cache_user[$c_user_id] ) ) {
85		user_cache_array_rows( array( $c_user_id ) );
86
87		if( !isset( $g_cache_user[$c_user_id] ) ) {
88			if( $p_trigger_errors ) {
89				throw new ClientException(
90					sprintf( "User id '%d' not found.", (integer)$p_user_id ),
91					ERROR_USER_BY_ID_NOT_FOUND,
92					array( (integer)$p_user_id )
93				);
94			}
95
96			return false;
97		}
98	}
99
100	return $g_cache_user[$c_user_id];
101}
102
103/**
104 * Loads user rows in cache for a set of User ID's
105 * Store false if the user does not exists
106 * @param array $p_user_id_array An array of user identifiers.
107 * @return void
108 */
109function user_cache_array_rows( array $p_user_id_array ) {
110	global $g_cache_user;
111	$c_user_id_array = array();
112
113	foreach( $p_user_id_array as $t_user_id ) {
114		if( !isset( $g_cache_user[(int)$t_user_id] ) ) {
115			$c_user_id_array[(int)$t_user_id] = (int)$t_user_id;
116		}
117	}
118	if( empty( $c_user_id_array ) ) {
119		return;
120	}
121
122	db_param_push();
123	$t_params = array();
124	$t_sql_in_params = array();
125	foreach( $c_user_id_array as $t_id ) {
126		$t_params[] = $t_id;
127		$t_sql_in_params[] = db_param();
128	}
129	$t_query = 'SELECT * FROM {user} WHERE id IN (' . implode( ',', $t_sql_in_params ) . ')';
130	$t_result = db_query( $t_query, $t_params );
131
132	if( $t_result !== false ) {
133		while( $t_row = db_fetch_array( $t_result ) ) {
134			$c_user_id = (int)$t_row['id'];
135			$g_cache_user[$c_user_id] = $t_row;
136			unset( $c_user_id_array[$c_user_id] );
137		}
138		# set the remaining ids to false as not-found
139		foreach( $c_user_id_array as $t_id ) {
140			$g_cache_user[$t_id] = false;
141		}
142	}
143}
144
145/**
146 * Cache a user row
147 * @param array $p_user_database_result A user row to cache.
148 * @return void
149 */
150function user_cache_database_result( array $p_user_database_result ) {
151	global $g_cache_user;
152
153	$g_cache_user[$p_user_database_result['id']] = $p_user_database_result;
154}
155
156/**
157 * Clear the user cache (or just the given id if specified)
158 * @param integer $p_user_id A valid user identifier or the default of null to clear cache for all users.
159 * @return boolean
160 */
161function user_clear_cache( $p_user_id = null ) {
162	global $g_cache_user;
163
164	if( null === $p_user_id ) {
165		$g_cache_user = array();
166	} else {
167		unset( $g_cache_user[$p_user_id] );
168	}
169
170	return true;
171}
172
173/**
174 * Update Cache entry for a given user and field
175 * @param integer $p_user_id A valid user id to update.
176 * @param string  $p_field   The name of the field on the user object to update.
177 * @param mixed   $p_value   The updated value for the user object field.
178 * @return void
179 */
180function user_update_cache( $p_user_id, $p_field, $p_value ) {
181	global $g_cache_user;
182
183	if( isset( $g_cache_user[$p_user_id] ) && isset( $g_cache_user[$p_user_id][$p_field] ) ) {
184		$g_cache_user[$p_user_id][$p_field] = $p_value;
185	} else {
186		user_clear_cache( $p_user_id );
187	}
188}
189
190/**
191 * Searches the cache for a given field and value pair against any user,
192 * and returns the first user id that matches
193 *
194 * @param string $p_field The user object field name to search the cache for.
195 * @param mixed  $p_value The field value to look for in the cache.
196 * @return integer|boolean
197 */
198function user_search_cache( $p_field, $p_value ) {
199	global $g_cache_user;
200	if( isset( $g_cache_user ) ) {
201		foreach( $g_cache_user as $t_user ) {
202			if( $t_user && $t_user[$p_field] == $p_value ) {
203				return $t_user;
204			}
205		}
206	}
207	return false;
208}
209
210/**
211 * check to see if user exists by id
212 * return true if it does, false otherwise
213 *
214 * @param integer $p_user_id A valid user identifier.
215 * @return boolean
216 */
217function user_exists( $p_user_id ) {
218	$t_row = user_cache_row( $p_user_id, false );
219
220	if( false === $t_row ) {
221		return false;
222	} else {
223		return true;
224	}
225}
226
227/**
228 * check to see if user exists by id
229 * if the user does not exist, trigger an error
230 *
231 * @param integer $p_user_id A valid user identifier.
232 * @return void
233 */
234function user_ensure_exists( $p_user_id ) {
235	$c_user_id = (integer)$p_user_id;
236
237	if( !user_exists( $c_user_id ) ) {
238		throw new ClientException( "User $c_user_id not found", ERROR_USER_BY_ID_NOT_FOUND, array( $c_user_id ) );
239	}
240}
241
242/**
243 * return true if the username is unique, false if there is already a user with that username
244 * @param string $p_username The username to check.
245 * @param integer|null The user id allowed to conflict, otherwise null.
246 * @return boolean
247 */
248function user_is_name_unique( $p_username, $p_user_id = null ) {
249	$t_existing_user_id = user_get_id_by_name( $p_username );
250	if( $t_existing_user_id !== false && ( $p_user_id === null || (int)$t_existing_user_id !== $p_user_id ) ) {
251		return false;
252	}
253
254	$t_existing_user_id = user_get_id_by_realname( $p_username );
255	if( $t_existing_user_id !== false && ( $p_user_id === null || (int)$t_existing_user_id !== $p_user_id ) ) {
256		return false;
257	}
258
259	return true;
260}
261
262/**
263 * Check if the username is unique and trigger an ERROR if it isn't
264 * @param string $p_username The username to check.
265 * @param integer|null $p_user_id The user id allowed to conflict, otherwise null.
266 * @return void
267 */
268function user_ensure_name_unique( $p_username, $p_user_id = null ) {
269	if( !user_is_name_unique( $p_username, $p_user_id ) ) {
270		throw new ClientException(
271			sprintf( "Username '%s' already used.", $p_username ),
272			ERROR_USER_NAME_NOT_UNIQUE );
273	}
274}
275
276/**
277 * Checks if the email address is unique.
278 *
279 * @param string $p_email The email to check.
280 * @param integer $p_user_id The user id that we are validating for or null for
281 *                           the case of a new user.
282 *
283 * @return boolean true: unique or blank, false: otherwise
284 */
285function user_is_email_unique( $p_email, $p_user_id = null ) {
286	if( is_blank( $p_email ) ) {
287		return true;
288	}
289
290	$p_email = trim( $p_email );
291
292	db_param_push();
293	if ( $p_user_id === null ) {
294		$t_query = 'SELECT email FROM {user} WHERE email=' . db_param();
295		$t_result = db_query( $t_query, array( $p_email ), 1 );
296	} else {
297		$t_query = 'SELECT email FROM {user} WHERE id<>' . db_param() .
298			' AND email=' . db_param();
299		$t_result = db_query( $t_query, array( $p_user_id, $p_email ), 1 );
300	}
301
302	return !db_result( $t_result );
303}
304
305/**
306 * Check if the email is unique and trigger an ERROR if it isn't
307 *
308 * @param string $p_email The email address to check.
309 * @param integer $p_user_id The user id that we are validating for or null for
310 *                           the case of a new user.
311 *
312 * @return void
313 */
314function user_ensure_email_unique( $p_email, $p_user_id = null ) {
315	if( !config_get_global( 'email_ensure_unique' ) ) {
316		return;
317	}
318
319	if( !user_is_email_unique( $p_email, $p_user_id ) ) {
320		throw new ClientException(
321			sprintf( "Email '%s' already used.", $p_email ),
322			ERROR_USER_EMAIL_NOT_UNIQUE );
323	}
324}
325
326/**
327 * Check if the username is a valid username (does not account for uniqueness) realname can match
328 * @param string $p_username The username to check.
329 * @return boolean return true if user name is valid, false otherwise
330 */
331function user_is_name_valid( $p_username ) {
332	# The DB field is hard-coded. DB_FIELD_SIZE_USERNAME should not be modified.
333	if( mb_strlen( $p_username ) > DB_FIELD_SIZE_USERNAME ) {
334		return false;
335	}
336
337	# username must consist of at least one character
338	if( is_blank( $p_username ) ) {
339		return false;
340	}
341
342	# Only allow a basic set of characters
343	if( 0 == preg_match( config_get( 'user_login_valid_regex' ), $p_username ) ) {
344		return false;
345	}
346
347	# We have a valid username
348	return true;
349}
350
351/**
352 * Check if the username is a valid username (does not account for uniqueness)
353 * Trigger an error if the username is not valid
354 * @param string $p_username The username to check.
355 * @return void
356 */
357function user_ensure_name_valid( $p_username ) {
358	if( !user_is_name_valid( $p_username ) ) {
359		throw new ClientException(
360			sprintf( "Invalid username '%s'", $p_username ),
361			ERROR_USER_NAME_INVALID );
362	}
363}
364
365/**
366 * return whether user is monitoring bug for the user id and bug id
367 * @param integer $p_user_id A valid user identifier.
368 * @param integer $p_bug_id  A valid bug identifier.
369 * @return boolean
370 */
371function user_is_monitoring_bug( $p_user_id, $p_bug_id ) {
372	db_param_push();
373	$t_query = 'SELECT COUNT(*) FROM {bug_monitor}
374				  WHERE user_id=' . db_param() . ' AND bug_id=' . db_param();
375
376	$t_result = db_query( $t_query, array( (int)$p_user_id, (int)$p_bug_id ) );
377
378	if( 0 == db_result( $t_result ) ) {
379		return false;
380	} else {
381		return true;
382	}
383}
384
385/**
386 * Check if the specified user is an enabled user with admin access level or above.
387 * @param integer $p_user_id A valid user identifier.
388 * @return boolean true: admin, false: otherwise.
389 */
390function user_is_administrator( $p_user_id ) {
391	if( !user_is_enabled( $p_user_id ) ) {
392		return false;
393	}
394
395	$t_access_level = user_get_field( $p_user_id, 'access_level' );
396	return $t_access_level >= config_get_global( 'admin_site_threshold' );
397}
398
399/**
400 * Check if a user has a protected user account.
401 * Protected user accounts cannot be updated without manage_user_threshold
402 * permission. If the user ID supplied is that of the anonymous user, this
403 * function will always return true. The anonymous user account is always
404 * considered to be protected.
405 *
406 * @param integer $p_user_id A valid user identifier.
407 * @return boolean true: user is protected; false: user is not protected.
408 * @access public
409 */
410function user_is_protected( $p_user_id ) {
411	return user_is_anonymous( $p_user_id ) || ON == user_get_field( $p_user_id, 'protected' );
412}
413
414/**
415 * Check if a user is the anonymous user account.
416 * When anonymous logins are disabled this function will always return false.
417 *
418 * @param integer $p_user_id A valid user identifier.
419 * @return boolean true: user is the anonymous user; false: user is not the anonymous user.
420 * @access public
421 */
422function user_is_anonymous( $p_user_id ) {
423	return auth_anonymous_enabled() && strcasecmp( user_get_username( $p_user_id ), auth_anonymous_account() ) == 0;
424}
425
426/**
427 * Trigger an ERROR if the user account is protected
428 *
429 * @param integer $p_user_id A valid user identifier.
430 * @return void
431 */
432function user_ensure_unprotected( $p_user_id ) {
433	if( user_is_protected( $p_user_id ) ) {
434		throw new ClientException(
435			'User protected.',
436			ERROR_PROTECTED_ACCOUNT );
437	}
438}
439
440/**
441 * return true is the user account is enabled, false otherwise
442 *
443 * @param integer $p_user_id A valid user identifier.
444 * @return boolean
445 */
446function user_is_enabled( $p_user_id ) {
447	if( ON == user_get_field( $p_user_id, 'enabled' ) ) {
448		return true;
449	} else {
450		return false;
451	}
452}
453
454/**
455 * Count the number of users at or greater than a specific level
456 *
457 * @param integer $p_level   Access Level to count users. The default is to include ANYBODY.
458 * @param bool    $p_enabled true: must be enabled, false: must be disabled, null: don't care.
459 * @return integer The number of users.
460 */
461function user_count_level( $p_level = ANYBODY, $p_enabled = null ) {
462	db_param_push();
463	$t_query = 'SELECT COUNT(id) FROM {user} WHERE access_level >= ' . db_param();
464	$t_param = array( $p_level );
465
466	if( $p_enabled !== null ) {
467		$t_query .= ' AND enabled = ' . db_param();
468		$t_param[] = (bool)$p_enabled;
469	}
470
471	# Get the number of users
472	$t_result = db_query( $t_query, $t_param );
473	$t_count = db_result( $t_result );
474
475	return $t_count;
476}
477
478/**
479 * Return an array of user ids that are logged in.
480 * A user is considered logged in if the last visit timestamp is within the
481 * specified session duration.
482 * If the session duration is 0, then no users will be returned.
483 * @param integer $p_session_duration_in_minutes The duration to return logged in users for.
484 * @return array
485 */
486function user_get_logged_in_user_ids( $p_session_duration_in_minutes ) {
487	$t_session_duration_in_minutes = (integer)$p_session_duration_in_minutes;
488
489	# if session duration is 0, then there is no logged in users.
490	if( $t_session_duration_in_minutes == 0 ) {
491		return array();
492	}
493
494	# Generate timestamp
495	$t_last_timestamp_threshold = mktime( date( 'H' ), date( 'i' ) - 1 * $t_session_duration_in_minutes, date( 's' ), date( 'm' ), date( 'd' ), date( 'Y' ) );
496
497	# Execute query
498	db_param_push();
499	$t_query = 'SELECT id FROM {user} WHERE last_visit > ' . db_param();
500	$t_result = db_query( $t_query, array( $t_last_timestamp_threshold ), 1 );
501
502	# Get the list of connected users
503	$t_users_connected = array();
504	while( $t_row = db_fetch_array( $t_result ) ) {
505		$t_users_connected[] = (int)$t_row['id'];
506	}
507
508	return $t_users_connected;
509}
510
511/**
512 * Create a user.
513 * returns false if error, the generated cookie string if valid
514 *
515 * @param string  $p_username     A valid username.
516 * @param string  $p_password     The password to set for the user.
517 * @param string  $p_email        The Email Address of the user.
518 * @param integer $p_access_level The global access level for the user.
519 * @param boolean $p_protected    Whether the account is protected from modifications (default false).
520 * @param boolean $p_enabled      Whether the account is enabled.
521 * @param string  $p_realname     The realname of the user.
522 * @param string  $p_admin_name   The name of the administrator creating the account.
523 * @return string Cookie String
524 */
525function user_create( $p_username, $p_password, $p_email = '',
526	$p_access_level = null, $p_protected = false, $p_enabled = true,
527	$p_realname = '', $p_admin_name = '' ) {
528	if( null === $p_access_level ) {
529		$p_access_level = config_get( 'default_new_account_access_level' );
530	}
531
532	$t_password = auth_process_plain_password( $p_password );
533
534	$c_enabled = (bool)$p_enabled;
535
536	user_ensure_name_valid( $p_username );
537	user_ensure_name_unique( $p_username );
538	user_ensure_email_unique( $p_email );
539	email_ensure_valid( $p_email );
540	email_ensure_not_disposable( $p_email );
541
542	$t_cookie_string = auth_generate_unique_cookie_string();
543
544	db_param_push();
545	$t_query = 'INSERT INTO {user}
546				    ( username, email, password, date_created, last_visit,
547				     enabled, access_level, login_count, cookie_string, realname )
548				  VALUES
549				    ( ' . db_param() . ', ' . db_param() . ', ' . db_param() . ', ' . db_param() . ', ' . db_param()  . ',
550				     ' . db_param() . ',' . db_param() . ',' . db_param() . ',' . db_param() . ', ' . db_param() . ')';
551	db_query( $t_query, array( $p_username, $p_email, $t_password, db_now(), db_now(), $c_enabled, (int)$p_access_level, 0, $t_cookie_string, $p_realname ) );
552
553	# Create preferences for the user
554	$t_user_id = db_insert_id( db_get_table( 'user' ) );
555
556	# Users are added with protected set to FALSE in order to be able to update
557	# preferences.  Now set the real value of protected.
558	if( $p_protected ) {
559		user_set_field( $t_user_id, 'protected', (bool)$p_protected );
560	}
561
562	# Send notification email
563	if( !is_blank( $p_email ) ) {
564		$t_confirm_hash = auth_generate_confirm_hash( $t_user_id );
565		token_set( TOKEN_ACCOUNT_ACTIVATION, $t_confirm_hash, TOKEN_EXPIRY_ACCOUNT_ACTIVATION, $t_user_id );
566		email_signup( $t_user_id, $t_confirm_hash, $p_admin_name );
567	}
568
569	event_signal( 'EVENT_MANAGE_USER_CREATE', array( $t_user_id ) );
570
571	return $t_cookie_string;
572}
573
574/**
575 * Signup a user.
576 * If the use_ldap_email config option is on then tries to find email using
577 * ldap. $p_email may be empty, but the user won't get any emails.
578 * returns false if error, the generated cookie string if ok
579 * @param string $p_username The username to sign up.
580 * @param string $p_email    The email address of the user signing up.
581 * @return string|boolean cookie string or false on error
582 */
583function user_signup( $p_username, $p_email = null ) {
584	if( null === $p_email ) {
585		$p_email = '';
586
587		# @@@ I think the ldap_email stuff is a bit borked
588		#  Where is it being set?  When is it being used?
589		#  Shouldn't we override an email that is passed in here?
590		#  If the user doesn't exist in ldap, is the account created?
591		#  If so, there password won't get set anywhere...  (etc)
592		#  RJF: I was going to check for the existence of an LDAP email.
593		#  however, since we can't create an LDAP account at the moment,
594		#  and we don't know the user password in advance, we may not be able
595		#  to retrieve it anyway.
596		#  I'll re-enable this once a plan has been properly formulated for LDAP
597		#  account management and creation.
598		#			$t_email = '';
599		#			if( ON == config_get_global( 'use_ldap_email' ) ) {
600		#				$t_email = ldap_email_from_username( $p_username );
601		#			}
602		#			if( !is_blank( $t_email ) ) {
603		#				$p_email = $t_email;
604		#			}
605	}
606
607	$p_email = trim( $p_email );
608
609	email_ensure_not_disposable( $p_email );
610	email_ensure_valid( $p_email );
611	user_ensure_email_unique( $p_email );
612
613	# Create random password
614	$t_password = auth_generate_random_password();
615
616	return user_create( $p_username, $t_password, $p_email, auth_signup_access_level() );
617}
618
619/**
620 * delete project-specific user access levels.
621 * returns true when successfully deleted
622 *
623 * @param integer $p_user_id A valid user identifier.
624 * @return boolean Always true
625 */
626function user_delete_project_specific_access_levels( $p_user_id ) {
627	user_ensure_unprotected( $p_user_id );
628
629	db_param_push();
630	$t_query = 'DELETE FROM {project_user_list} WHERE user_id=' . db_param();
631	db_query( $t_query, array( (int)$p_user_id ) );
632
633	user_clear_cache( $p_user_id );
634
635	return true;
636}
637
638/**
639 * delete profiles for the specified user
640 * returns true when successfully deleted
641 * @param integer $p_user_id A valid user identifier.
642 * @return boolean
643 */
644function user_delete_profiles( $p_user_id ) {
645	user_ensure_unprotected( $p_user_id );
646
647	# Remove associated profiles
648	db_param_push();
649	$t_query = 'DELETE FROM {user_profile} WHERE user_id=' . db_param();
650	db_query( $t_query, array( (int)$p_user_id ) );
651
652	user_clear_cache( $p_user_id );
653
654	return true;
655}
656
657/**
658 * delete a user account (account, profiles, preferences, project-specific access levels)
659 * returns true when the account was successfully deleted
660 *
661 * @param integer $p_user_id A valid user identifier.
662 * @return boolean Always true
663 */
664function user_delete( $p_user_id ) {
665	$c_user_id = (int)$p_user_id;
666
667	user_ensure_unprotected( $p_user_id );
668
669	event_signal( 'EVENT_MANAGE_USER_DELETE', array( $p_user_id ) );
670
671	# Remove associated profiles
672	user_delete_profiles( $p_user_id );
673
674	# Remove associated preferences
675	user_pref_db_delete_user( $p_user_id );
676
677	# Remove project specific access levels
678	user_delete_project_specific_access_levels( $p_user_id );
679	user_clear_cache( $p_user_id );
680
681	# Remove account
682	db_param_push();
683	$t_query = 'DELETE FROM {user} WHERE id=' . db_param();
684	db_query( $t_query, array( $c_user_id ) );
685
686	return true;
687}
688
689/**
690 * get a user id from a username
691 * return false if the username does not exist
692 *
693 * @param string $p_username The username to retrieve data for.
694 * @param boolean $p_throw true to throw if not found, false otherwise.
695 * @return integer|boolean
696 */
697function user_get_id_by_name( $p_username, $p_throw = false ) {
698	if( $t_user = user_search_cache( 'username', $p_username ) ) {
699		return (int)$t_user['id'];
700	}
701
702	db_param_push();
703	$t_query = 'SELECT * FROM {user} WHERE username=' . db_param();
704	$t_result = db_query( $t_query, array( $p_username ) );
705
706	$t_row = db_fetch_array( $t_result );
707	if( $t_row ) {
708		user_cache_database_result( $t_row );
709		return (int)$t_row['id'];
710	}
711
712	if( $p_throw ) {
713		throw new ClientException(
714			"Username '$p_username' not found",
715			ERROR_USER_BY_NAME_NOT_FOUND,
716			array( $p_username ) );
717	}
718
719	return false;
720}
721
722/**
723 * Get a user id from their email address
724 *
725 * @param string $p_email The email address to retrieve data for.
726 * @param boolean $p_throw true to throw exception when not found, false otherwise.
727 * @return integer|boolean
728 */
729function user_get_id_by_email( $p_email, $p_throw = false ) {
730	if( $t_user = user_search_cache( 'email', $p_email ) ) {
731		return (int)$t_user['id'];
732	}
733
734	db_param_push();
735	$t_query = 'SELECT * FROM {user} WHERE email=' . db_param();
736	$t_result = db_query( $t_query, array( $p_email ) );
737
738	$t_row = db_fetch_array( $t_result );
739	if( $t_row ) {
740		user_cache_database_result( $t_row );
741		return (int)$t_row['id'];
742	}
743
744	if( $p_throw ) {
745		throw new ClientException(
746			"User with email '$p_email' not found",
747			ERROR_USER_BY_EMAIL_NOT_FOUND,
748			array( $p_email ) );
749	}
750
751	return false;
752}
753
754/**
755 * Given an email address, this method returns the ids of the enabled users with
756 * that address.
757 *
758 * The returned list will be sorted by higher access level first.
759 *
760 * @param string $p_email The email address, can be an empty string to get users
761 *                        without an email address.
762 *
763 * @return array The user ids or an empty array.
764 */
765function user_get_enabled_ids_by_email( $p_email ) {
766	db_param_push();
767	$t_query = 'SELECT * FROM {user} WHERE email=' . db_param() .
768		' AND enabled=' . db_param() . ' ORDER BY access_level DESC';
769	$t_result = db_query( $t_query, array( $p_email, 1 ) );
770
771	$t_user_ids = array();
772	while ( $t_row = db_fetch_array( $t_result ) ) {
773		user_cache_database_result( $t_row );
774		$t_user_ids[] = (int)$t_row['id'];
775	}
776
777	return $t_user_ids;
778}
779
780/**
781 * Get a user id from their real name
782 *
783 * @param string $p_realname The realname to retrieve data for.
784 * @param boolean $p_throw true to throw if not found, false otherwise.
785 * @return integer|boolean
786 */
787function user_get_id_by_realname( $p_realname, $p_throw = false ) {
788	if( $t_user = user_search_cache( 'realname', $p_realname ) ) {
789		return (int)$t_user['id'];
790	}
791
792	db_param_push();
793	$t_query = 'SELECT * FROM {user} WHERE realname=' . db_param();
794	$t_result = db_query( $t_query, array( $p_realname ) );
795
796	$t_row = db_fetch_array( $t_result );
797
798	if( !$t_row ) {
799		if( $p_throw ) {
800			throw new ClientException( "User realname '$p_realname' not found", ERROR_USER_BY_NAME_NOT_FOUND, array( $p_realname ) );
801		}
802
803		return false;
804	}
805
806	user_cache_database_result( $t_row );
807	return (int)$t_row['id'];
808}
809
810/**
811 * Get a user id from their cookie string
812 *
813 * @param string  $p_cookie_string The cookie string to retrieve data for.
814 * @param boolean $p_throw         true to throw if not found, false otherwise.
815 *
816 * @return int|false User Id, false if cookie string not found
817 *
818 * @throws ClientException
819 */
820function user_get_id_by_cookie( $p_cookie_string, $p_throw = false ) {
821	if( $t_user = user_search_cache( 'cookie_string', $p_cookie_string ) ) {
822		return (int)$t_user['id'];
823	}
824
825	db_param_push();
826	$t_query = 'SELECT * FROM {user} WHERE cookie_string=' . db_param();
827	$t_result = db_query( $t_query, array( $p_cookie_string ) );
828
829	$t_row = db_fetch_array( $t_result );
830
831	if( !$t_row ) {
832		if( $p_throw ) {
833			throw new ClientException(
834				"User Cookie String '$p_cookie_string' not found",
835				ERROR_USER_BY_NAME_NOT_FOUND,
836				array( $p_cookie_string )
837			);
838		}
839		return false;
840	}
841
842	user_cache_database_result( $t_row );
843	return (int)$t_row['id'];
844}
845
846/**
847 * Get a user id given an array that may have id, name, real_name, email, or name_or_realname.
848 *
849 * @param array $p_user The user info.
850 * @param boolean $p_throw_if_id_not_found If id specified and doesn't exist, then throw.
851 * @return integer user id
852 * @throws ClientException
853 */
854function user_get_id_by_user_info( array $p_user, $p_throw_if_id_not_found = false ) {
855	if( isset( $p_user['id'] ) && (int)$p_user['id'] != 0 ) {
856		$t_user_id = (int)$p_user['id'];
857		if( $p_throw_if_id_not_found && !user_exists( $t_user_id ) ) {
858			throw new ClientException(
859				sprintf( "User with id '%d' doesn't exist", $t_user_id ),
860				ERROR_USER_BY_ID_NOT_FOUND,
861				array( $t_user_id ) );
862		}
863	} else if( isset( $p_user['name'] ) && !is_blank( $p_user['name'] ) ) {
864		$t_user_id = user_get_id_by_name( $p_user['name'], /* throw */ true );
865	} else if( isset( $p_user['email'] ) && !is_blank( $p_user['email'] ) ) {
866		$t_user_id = user_get_id_by_email( $p_user['email'], /* throw */ true );
867	} else if( isset( $p_user['real_name'] ) && !is_blank( $p_user['real_name'] ) ) {
868		$t_user_id = user_get_id_by_realname( $p_user['real_name'], /* throw */ true );
869	} else if( isset( $p_user['name_or_realname' ] ) && !is_blank( $p_user['name_or_realname' ] ) ) {
870		$t_identifier = $p_user['name_or_realname'];
871		$t_user_id = user_get_id_by_name( $t_identifier );
872
873		if( !$t_user_id ) {
874			$t_user_id = user_get_id_by_realname( $t_identifier );
875		}
876
877		if( !$t_user_id ) {
878			throw new ClientException(
879				"User '$t_identifier' not found",
880				ERROR_USER_BY_NAME_NOT_FOUND,
881				array( $t_identifier ) );
882		}
883	} else {
884		throw new ClientException(
885			"User id missing",
886			ERROR_GPC_VAR_NOT_FOUND,
887			array( 'user id' ) );
888	}
889
890	return $t_user_id;
891}
892
893/**
894 * return all data associated with a particular user name
895 * return false if the username does not exist
896 *
897 * @param integer $p_username The username to retrieve data for.
898 * @return array
899 */
900function user_get_row_by_name( $p_username ) {
901	$t_user_id = user_get_id_by_name( $p_username );
902
903	if( false === $t_user_id ) {
904		return false;
905	}
906
907	$t_row = user_get_row( $t_user_id );
908
909	return $t_row;
910}
911
912/**
913 * return a user row
914 *
915 * @param integer $p_user_id A valid user identifier.
916 * @return array
917 */
918function user_get_row( $p_user_id ) {
919	return user_cache_row( $p_user_id );
920}
921
922/**
923 * return the specified user field for the user id
924 *
925 * @param integer $p_user_id    A valid user identifier.
926 * @param string  $p_field_name The field name to retrieve.
927 * @return string
928 */
929function user_get_field( $p_user_id, $p_field_name ) {
930	if( NO_USER == $p_user_id ) {
931		error_parameters( NO_USER );
932		trigger_error( ERROR_USER_BY_ID_NOT_FOUND, WARNING );
933		return '@null@';
934	}
935
936	$t_row = user_get_row( $p_user_id );
937
938	if( isset( $t_row[$p_field_name] ) ) {
939		switch( $p_field_name ) {
940			case 'access_level':
941				return (int)$t_row[$p_field_name];
942			default:
943				return $t_row[$p_field_name];
944		}
945	} else {
946		error_parameters( $p_field_name );
947		trigger_error( ERROR_DB_FIELD_NOT_FOUND, WARNING );
948		return '';
949	}
950}
951
952/**
953 * lookup the user's email in LDAP or the db as appropriate
954 *
955 * @param integer $p_user_id A valid user identifier.
956 * @return string
957 */
958function user_get_email( $p_user_id ) {
959	$t_email = '';
960	if( LDAP == config_get_global( 'login_method' ) && ON == config_get_global( 'use_ldap_email' ) ) {
961		$t_email = ldap_email( $p_user_id );
962	}
963	if( is_blank( $t_email ) ) {
964		$t_email = user_get_field( $p_user_id, 'email' );
965	}
966	return $t_email;
967}
968
969/**
970 * Lookup the user's login name (username)
971 *
972 * @param integer $p_user_id A valid user identifier.
973 * @return string
974 */
975function user_get_username( $p_user_id ) {
976	$t_row = user_cache_row( $p_user_id, false );
977	if( false == $t_row ) {
978		return lang_get( 'prefix_for_deleted_users' ) . (int)$p_user_id;
979	}
980
981	return $t_row['username'];
982}
983
984/**
985 * lookup the user's realname
986 *
987 * @param integer $p_user_id A valid user identifier.
988 * @return string
989 */
990function user_get_realname( $p_user_id ) {
991	$t_realname = '';
992
993	if( LDAP == config_get_global( 'login_method' ) && ON == config_get_global( 'use_ldap_realname' ) ) {
994		$t_realname = ldap_realname( $p_user_id );
995	}
996
997	if( is_blank( $t_realname ) ) {
998		$t_realname = user_get_field( $p_user_id, 'realname' );
999	}
1000
1001	return $t_realname;
1002}
1003
1004/**
1005 * Return the user's name for display.
1006 *
1007 * The name is determined based on the following sequence:
1008 * - if the user does not exist, returns the user ID prefixed by a localized
1009 *   string (prefix_for_deleted_users, "user" by default);
1010 * - if user_show_realname() is true and realname is not empty, return the user's Real Name;
1011 * - Otherwise, return the username
1012 *
1013 * NOTE: do not use this function to retrieve the user's username
1014 * @see user_get_username()
1015 *
1016 * @param integer $p_user_id A valid user identifier.
1017 *
1018 * @return string
1019 */
1020function user_get_name( $p_user_id ) {
1021	$t_row = user_cache_row( $p_user_id, false );
1022
1023	if( false == $t_row ) {
1024		return lang_get( 'prefix_for_deleted_users' ) . (int)$p_user_id;
1025	}
1026
1027	return user_get_name_from_row( $t_row );
1028}
1029
1030/**
1031 * Should realnames be shown to logged in user?
1032 *
1033 * @return bool true to show, false otherwise.
1034 */
1035function user_show_realname() {
1036	return config_get( 'show_realname' ) == ON;
1037}
1038
1039/**
1040 * Return the user's name for display.  If user_show_realname() is true and realname is not empty
1041 * return realname otherwise return username.
1042 *
1043 * @param array $p_user_row The user row with 'realname' and 'username' fields
1044 * @return string display name
1045 */
1046function user_get_name_from_row( array $p_user_row ) {
1047	if( user_show_realname() ) {
1048		if( !is_blank( $p_user_row['realname'] ) ) {
1049			return $p_user_row['realname'];
1050		}
1051	}
1052
1053	return $p_user_row['username'];
1054}
1055
1056/**
1057 * Return display name in format "realname (username)" if user_show_realname() is true and
1058 * realname is not empty otherwise return username
1059 *
1060 * @param array $p_user_row The user row with 'realname' and 'username' fields
1061 * @return string display name
1062 */
1063function user_get_expanded_name_from_row( array $p_user_row ) {
1064	$t_name = user_get_name_from_row( $p_user_row );
1065	if( $t_name != $p_user_row['username'] ) {
1066		return $t_name . ' (' . $p_user_row['username'] . ')';
1067	}
1068
1069	return $p_user_row['username'];
1070}
1071
1072/**
1073 * Get name used for sorting.
1074 *
1075 * @param array $p_user_row The user row with 'realname' and 'username' fields
1076 * @return string name for sorting
1077 */
1078function user_get_name_for_sorting_from_row( array $p_user_row ) {
1079	if( !is_blank( $p_user_row['realname'] ) ) {
1080		if( user_show_realname() ) {
1081			if( config_get( 'sort_by_last_name' ) == ON ) {
1082				$t_sort_name_bits = explode( ' ', mb_strtolower( trim( $p_user_row['realname'] ) ), 2 );
1083				return ( isset( $t_sort_name_bits[1] ) ? $t_sort_name_bits[1] . ', ' : '' ) . $t_sort_name_bits[0];
1084			}
1085
1086			return mb_strtolower( trim( $p_user_row['realname'] ) );
1087		}
1088	}
1089
1090	return mb_strtolower( $p_user_row['username'] );
1091}
1092
1093/**
1094 * return the user's access level
1095 * account for private project and the project user lists
1096 *
1097 * @param integer $p_user_id    A valid user identifier.
1098 * @param integer $p_project_id A valid project identifier.
1099 * @return integer
1100 */
1101function user_get_access_level( $p_user_id, $p_project_id = ALL_PROJECTS ) {
1102	$t_access_level = user_get_field( $p_user_id, 'access_level' );
1103
1104	if( user_is_administrator( $p_user_id ) ) {
1105		return $t_access_level;
1106	}
1107
1108	$t_project_access_level = access_get_local_level( $p_user_id, $p_project_id );
1109
1110	if( false === $t_project_access_level ) {
1111		return $t_access_level;
1112	} else {
1113		return $t_project_access_level;
1114	}
1115}
1116
1117$g_user_accessible_projects_cache = null;
1118
1119/**
1120 * return an array of project IDs to which the user has access
1121 *
1122 * @param integer $p_user_id       A valid user identifier.
1123 * @param boolean $p_show_disabled Whether to include disabled projects in the result array.
1124 * @return array
1125 */
1126function user_get_accessible_projects( $p_user_id, $p_show_disabled = false ) {
1127	global $g_user_accessible_projects_cache;
1128
1129	if( null !== $g_user_accessible_projects_cache && auth_get_current_user_id() == $p_user_id && false == $p_show_disabled ) {
1130		return $g_user_accessible_projects_cache;
1131	}
1132
1133	if( access_has_global_level( config_get( 'private_project_threshold' ), $p_user_id ) ) {
1134		$t_projects = project_hierarchy_get_subprojects( ALL_PROJECTS, $p_show_disabled );
1135	} else {
1136		$t_public = VS_PUBLIC;
1137		$t_private = VS_PRIVATE;
1138
1139		db_param_push();
1140		$t_query = 'SELECT p.id, p.name, ph.parent_id
1141						  FROM {project} p
1142						  LEFT JOIN {project_user_list} u
1143						    ON p.id=u.project_id AND u.user_id=' . db_param() . '
1144						  LEFT JOIN {project_hierarchy} ph
1145						    ON ph.child_id = p.id
1146						  WHERE ' . ( $p_show_disabled ? '' : ( 'p.enabled = ' . db_param() . ' AND ' ) ) . '
1147							( p.view_state=' . db_param() . '
1148							    OR (p.view_state=' . db_param() . '
1149								    AND
1150							        u.user_id=' . db_param() . ' )
1151							) ORDER BY p.name';
1152		$t_result = db_query( $t_query, ( $p_show_disabled ? array( $p_user_id, $t_public, $t_private, $p_user_id ) : array( $p_user_id, true, $t_public, $t_private, $p_user_id ) ) );
1153
1154		$t_projects = array();
1155
1156		while( $t_row = db_fetch_array( $t_result ) ) {
1157			$t_projects[(int)$t_row['id']] = ( $t_row['parent_id'] === null ) ? 0 : (int)$t_row['parent_id'];
1158		}
1159
1160		# prune out children where the parents are already listed. Make the list
1161		#  first, then prune to avoid pruning a parent before the child is found.
1162		$t_prune = array();
1163		foreach( $t_projects as $t_id => $t_parent ) {
1164			if( ( $t_parent !== 0 ) && isset( $t_projects[$t_parent] ) ) {
1165				$t_prune[] = $t_id;
1166			}
1167		}
1168		foreach( $t_prune as $t_id ) {
1169			unset( $t_projects[$t_id] );
1170		}
1171		$t_projects = array_keys( $t_projects );
1172	}
1173
1174	if( auth_get_current_user_id() == $p_user_id ) {
1175		$g_user_accessible_projects_cache = $t_projects;
1176	}
1177
1178	return $t_projects;
1179}
1180
1181/**
1182 * return an array of sub-project IDs of a certain project to which the user has access
1183 * @param integer $p_user_id       A valid user identifier.
1184 * @param integer $p_project_id    A valid project identifier.
1185 * @param boolean $p_show_disabled Include disabled projects in the resulting array.
1186 * @return array
1187 */
1188function user_get_accessible_subprojects( $p_user_id, $p_project_id, $p_show_disabled = false ) {
1189	global $g_user_accessible_subprojects_cache;
1190
1191	if( null !== $g_user_accessible_subprojects_cache && auth_get_current_user_id() == $p_user_id && false == $p_show_disabled ) {
1192		if( isset( $g_user_accessible_subprojects_cache[$p_project_id] ) ) {
1193			return $g_user_accessible_subprojects_cache[$p_project_id];
1194		} else {
1195			return array();
1196		}
1197	}
1198
1199	db_param_push();
1200
1201	if( access_has_global_level( config_get( 'private_project_threshold' ), $p_user_id ) ) {
1202		$t_enabled_clause = $p_show_disabled ? '' : 'p.enabled = ' . db_param() . ' AND';
1203		$t_query = 'SELECT DISTINCT p.id, p.name, ph.parent_id
1204					  FROM {project} p
1205					  LEFT JOIN {project_hierarchy} ph
1206					    ON ph.child_id = p.id
1207					  WHERE ' . $t_enabled_clause . '
1208					  	 ph.parent_id IS NOT NULL
1209					  ORDER BY p.name';
1210		$t_result = db_query( $t_query, ( $p_show_disabled ? array() : array( true ) ) );
1211	} else {
1212		$t_query = 'SELECT DISTINCT p.id, p.name, ph.parent_id
1213					  FROM {project} p
1214					  LEFT JOIN {project_user_list} u
1215					    ON p.id = u.project_id AND u.user_id=' . db_param() . '
1216					  LEFT JOIN {project_hierarchy} ph
1217					    ON ph.child_id = p.id
1218					  WHERE ' . ( $p_show_disabled ? '' : ( 'p.enabled = ' . db_param() . ' AND ' ) ) . '
1219					  	ph.parent_id IS NOT NULL AND
1220						( p.view_state=' . db_param() . '
1221						    OR (p.view_state=' . db_param() . '
1222							    AND
1223						        u.user_id=' . db_param() . ' )
1224						)
1225					  ORDER BY p.name';
1226		$t_param = array( $p_user_id, VS_PUBLIC, VS_PRIVATE, $p_user_id );
1227		if( !$p_show_disabled ) {
1228			# Insert enabled flag value in 2nd position of parameter array
1229			array_splice( $t_param, 1, 0, true );
1230		}
1231		$t_result = db_query( $t_query, $t_param );
1232	}
1233
1234	$t_projects = array();
1235
1236	while( $t_row = db_fetch_array( $t_result ) ) {
1237		if( !isset( $t_projects[(int)$t_row['parent_id']] ) ) {
1238			$t_projects[(int)$t_row['parent_id']] = array();
1239		}
1240
1241		array_push( $t_projects[(int)$t_row['parent_id']], (int)$t_row['id'] );
1242	}
1243
1244	if( auth_get_current_user_id() == $p_user_id ) {
1245		$g_user_accessible_subprojects_cache = $t_projects;
1246	}
1247
1248	if( !isset( $t_projects[(int)$p_project_id] ) ) {
1249		$t_projects[(int)$p_project_id] = array();
1250	}
1251
1252	return $t_projects[(int)$p_project_id];
1253}
1254
1255/**
1256 * return an array of sub-project IDs of all sub-projects project to which the user has access
1257 * @param integer $p_user_id    A valid user identifier.
1258 * @param integer $p_project_id A valid project identifier.
1259 * @return array
1260 */
1261function user_get_all_accessible_subprojects( $p_user_id, $p_project_id ) {
1262	# @todo (thraxisp) Should all top level projects be a sub-project of ALL_PROJECTS implicitly?
1263	# affects how news and some summaries are generated
1264	$t_todo = user_get_accessible_subprojects( $p_user_id, $p_project_id );
1265	$t_subprojects = array();
1266
1267	while( $t_todo ) {
1268		$t_elem = (int)array_shift( $t_todo );
1269		if( !in_array( $t_elem, $t_subprojects ) ) {
1270			array_push( $t_subprojects, $t_elem );
1271			$t_todo = array_merge( $t_todo, user_get_accessible_subprojects( $p_user_id, $t_elem ) );
1272		}
1273	}
1274
1275	return $t_subprojects;
1276}
1277
1278/**
1279 * Returns an array of project and sub-project IDs of all projects to which the
1280 * user has access and that are children of the specified project.
1281 *
1282 * @param integer $p_user_id    A valid user identifier or null for logged in user.
1283 * @param integer $p_project_id A valid project identifier.  ALL_PROJECTS returns
1284 *                              all top level projects and sub-projects.
1285 * @return array
1286 */
1287function user_get_all_accessible_projects( $p_user_id = null, $p_project_id = ALL_PROJECTS ) {
1288	if( $p_user_id === null ) {
1289		$p_user_id = auth_get_current_user_id();
1290	}
1291
1292	if( ALL_PROJECTS == $p_project_id ) {
1293		$t_top_projects = user_get_accessible_projects( $p_user_id );
1294
1295		# Cover the case for PHP < 5.4 where array_combine() returns
1296		# false and triggers warning if arrays are empty (see #16187)
1297		if( empty( $t_top_projects ) ) {
1298			return array();
1299		}
1300
1301		# Create a combined array where key = value
1302		$t_project_ids = array_combine( $t_top_projects, $t_top_projects );
1303
1304		# Add all subprojects user has access to
1305		foreach( $t_top_projects as $t_project ) {
1306			$t_subprojects_ids = user_get_all_accessible_subprojects( $p_user_id, $t_project );
1307			foreach( $t_subprojects_ids as $t_id ) {
1308				$t_project_ids[$t_id] = $t_id;
1309			}
1310		}
1311	} else {
1312		access_ensure_project_level( config_get( 'view_bug_threshold' ), $p_project_id );
1313		$t_project_ids = user_get_all_accessible_subprojects( $p_user_id, $p_project_id );
1314		array_unshift( $t_project_ids, $p_project_id );
1315	}
1316
1317	return $t_project_ids;
1318}
1319
1320/**
1321 * Get a list of projects the specified user is assigned to.
1322 * @param integer $p_user_id A valid user identifier.
1323 * @return array An array of projects by project id the specified user is assigned to.
1324 *		The array contains the id, name, view state, and project access level for the user.
1325 */
1326function user_get_assigned_projects( $p_user_id ) {
1327	db_param_push();
1328	$t_query = 'SELECT DISTINCT p.id, p.name, p.view_state, u.access_level
1329				FROM {project} p
1330				LEFT JOIN {project_user_list} u
1331				ON p.id=u.project_id
1332				WHERE p.enabled = \'1\' AND
1333					u.user_id=' . db_param() . '
1334				ORDER BY p.name';
1335	$t_result = db_query( $t_query, array( $p_user_id ) );
1336	$t_projects = array();
1337	while( $t_row = db_fetch_array( $t_result ) ) {
1338		$t_project_id = $t_row['id'];
1339		$t_projects[$t_project_id] = $t_row;
1340	}
1341	return $t_projects;
1342}
1343
1344/**
1345 * List of users that are NOT in the specified project and that are enabled
1346 * if no project is specified use the current project
1347 * also exclude any administrators
1348 * @param integer $p_project_id A valid project identifier.
1349 * @return array List of users not assigned to the specified project
1350 */
1351function user_get_unassigned_by_project_id( $p_project_id = null ) {
1352	if( null === $p_project_id ) {
1353		$p_project_id = helper_get_current_project();
1354	}
1355
1356	$t_adm = config_get_global( 'admin_site_threshold' );
1357	db_param_push();
1358	$t_query = 'SELECT DISTINCT u.id, u.username, u.realname
1359				FROM {user} u
1360				LEFT JOIN {project_user_list} p
1361				ON p.user_id=u.id AND p.project_id=' . db_param() . '
1362				WHERE u.access_level<' . db_param() . ' AND
1363					u.enabled = ' . db_param() . ' AND
1364					p.user_id IS NULL
1365				ORDER BY u.realname, u.username';
1366	$t_result = db_query( $t_query, array( $p_project_id, $t_adm, true ) );
1367	$t_display = array();
1368	$t_sort = array();
1369	$t_users = array();
1370
1371	while( $t_row = db_fetch_array( $t_result ) ) {
1372		$t_users[] = (int)$t_row['id'];
1373		$t_display[] = user_get_expanded_name_from_row( $t_row );
1374		$t_sort[] = user_get_name_for_sorting_from_row( $t_row );
1375	}
1376
1377	array_multisort( $t_sort, SORT_ASC, SORT_STRING, $t_users, $t_display );
1378
1379	$t_count = count( $t_sort );
1380	$t_user_list = array();
1381	for( $i = 0;$i < $t_count; $i++ ) {
1382		$t_user_list[$t_users[$i]] = $t_display[$i];
1383	}
1384	return $t_user_list;
1385}
1386
1387/**
1388 * return the number of open assigned bugs to a user in a project
1389 *
1390 * @param integer $p_user_id    A valid user identifier.
1391 * @param integer $p_project_id A valid project identifier.
1392 * @return integer
1393 */
1394function user_get_assigned_open_bug_count( $p_user_id, $p_project_id = ALL_PROJECTS ) {
1395	$t_where_prj = helper_project_specific_where( $p_project_id, $p_user_id ) . ' AND';
1396
1397	$t_resolved = config_get( 'bug_resolved_status_threshold' );
1398
1399	db_param_push();
1400	$t_query = 'SELECT COUNT(*)
1401				  FROM {bug}
1402				  WHERE ' . $t_where_prj . '
1403						status<' . db_param() . ' AND
1404						handler_id=' . db_param();
1405	$t_result = db_query( $t_query, array( $t_resolved, $p_user_id ) );
1406
1407	return db_result( $t_result );
1408}
1409
1410/**
1411 * return the number of open reported bugs by a user in a project
1412 *
1413 * @param integer $p_user_id    A valid user identifier.
1414 * @param integer $p_project_id A valid project identifier.
1415 * @return integer
1416 */
1417function user_get_reported_open_bug_count( $p_user_id, $p_project_id = ALL_PROJECTS ) {
1418	$t_where_prj = helper_project_specific_where( $p_project_id, $p_user_id ) . ' AND';
1419
1420	$t_resolved = config_get( 'bug_resolved_status_threshold' );
1421
1422	db_param_push();
1423	$t_query = 'SELECT COUNT(*) FROM {bug}
1424				  WHERE ' . $t_where_prj . '
1425						  status<' . db_param() . ' AND
1426						  reporter_id=' . db_param();
1427	$t_result = db_query( $t_query, array( $t_resolved, $p_user_id ) );
1428
1429	return db_result( $t_result );
1430}
1431
1432/**
1433 * return a profile row
1434 *
1435 * @param integer $p_user_id    A valid user identifier.
1436 * @param integer $p_profile_id The profile identifier to retrieve.
1437 * @return array
1438 */
1439function user_get_profile_row( $p_user_id, $p_profile_id ) {
1440	db_param_push();
1441	$t_query = 'SELECT * FROM {user_profile}
1442				  WHERE id=' . db_param() . ' AND
1443						user_id=' . db_param();
1444	$t_result = db_query( $t_query, array( $p_profile_id, $p_user_id ) );
1445
1446	$t_row = db_fetch_array( $t_result );
1447
1448	if( !$t_row ) {
1449		trigger_error( ERROR_USER_PROFILE_NOT_FOUND, ERROR );
1450	}
1451
1452	return $t_row;
1453}
1454
1455/**
1456 * Get failed login attempts
1457 *
1458 * @param integer $p_user_id A valid user identifier.
1459 * @return boolean
1460 */
1461function user_is_login_request_allowed( $p_user_id ) {
1462	$t_max_failed_login_count = config_get( 'max_failed_login_count' );
1463	$t_failed_login_count = user_get_field( $p_user_id, 'failed_login_count' );
1464	return( $t_failed_login_count < $t_max_failed_login_count || OFF == $t_max_failed_login_count );
1465}
1466
1467/**
1468 * Get 'lost password' in progress attempts
1469 *
1470 * @param integer $p_user_id A valid user identifier.
1471 * @return boolean
1472 */
1473function user_is_lost_password_request_allowed( $p_user_id ) {
1474	if( OFF == config_get( 'lost_password_feature' ) ) {
1475		return false;
1476	}
1477	$t_max_lost_password_in_progress_count = config_get( 'max_lost_password_in_progress_count' );
1478	$t_lost_password_in_progress_count = user_get_field( $p_user_id, 'lost_password_request_count' );
1479	return( $t_lost_password_in_progress_count < $t_max_lost_password_in_progress_count || OFF == $t_max_lost_password_in_progress_count );
1480}
1481
1482/**
1483 * return the bug filter parameters for the specified user
1484 *
1485 * @param integer $p_user_id    A valid user identifier.
1486 * @param integer $p_project_id A valid project identifier.
1487 * @return array The user filter, or default filter if not valid.
1488 */
1489function user_get_bug_filter( $p_user_id, $p_project_id = null ) {
1490	if( null === $p_project_id ) {
1491		$t_project_id = helper_get_current_project();
1492	} else {
1493		$t_project_id = $p_project_id;
1494	}
1495
1496	# Currently we use the filters saved in db as "current" special filters,
1497	# to track the active settings for filters in use.
1498
1499	# for anonymous user, we don't allow using persistent filter
1500	# if this function is reached, we return a default filter for it.
1501	if( user_is_anonymous( $p_user_id ) ) {
1502		return filter_get_default();
1503	}
1504
1505	$t_filter_id = filter_db_get_project_current( $t_project_id, $p_user_id );
1506	if( $t_filter_id ) {
1507		return filter_get( $t_filter_id );
1508	} else {
1509		return filter_get_default();
1510	}
1511}
1512
1513/**
1514 * Update the last_visited field to be now
1515 *
1516 * @param integer $p_user_id A valid user identifier.
1517 * @return boolean always true
1518 */
1519function user_update_last_visit( $p_user_id ) {
1520	$c_user_id = (int)$p_user_id;
1521	$c_value = db_now();
1522
1523	db_param_push();
1524	$t_query = 'UPDATE {user} SET last_visit=' . db_param() . ' WHERE id=' . db_param();
1525	db_query( $t_query, array( $c_value, $c_user_id ) );
1526
1527	user_update_cache( $c_user_id, 'last_visit', $c_value );
1528
1529	return true;
1530}
1531
1532/**
1533 * Increment the number of times the user has logged in
1534 * This function is only called from the login.php script
1535 *
1536 * @param integer $p_user_id A valid user identifier.
1537 * @return boolean always true
1538 */
1539function user_increment_login_count( $p_user_id ) {
1540	db_param_push();
1541	$t_query = 'UPDATE {user} SET login_count=login_count+1 WHERE id=' . db_param();
1542	db_query( $t_query, array( (int)$p_user_id ) );
1543
1544	user_clear_cache( $p_user_id );
1545
1546	return true;
1547}
1548
1549/**
1550 * Reset to zero the failed login attempts
1551 *
1552 * @param integer $p_user_id A valid user identifier.
1553 * @return boolean always true
1554 */
1555function user_reset_failed_login_count_to_zero( $p_user_id ) {
1556	db_param_push();
1557	$t_query = 'UPDATE {user} SET failed_login_count=0 WHERE id=' . db_param();
1558	db_query( $t_query, array( (int)$p_user_id ) );
1559
1560	user_clear_cache( $p_user_id );
1561
1562	return true;
1563}
1564
1565/**
1566 * Increment the failed login count by 1
1567 *
1568 * @param integer $p_user_id A valid user identifier.
1569 * @return boolean always true
1570 */
1571function user_increment_failed_login_count( $p_user_id ) {
1572	db_param_push();
1573	$t_query = 'UPDATE {user} SET failed_login_count=failed_login_count+1 WHERE id=' . db_param();
1574	db_query( $t_query, array( $p_user_id ) );
1575
1576	user_clear_cache( $p_user_id );
1577
1578	return true;
1579}
1580
1581/**
1582 * Reset to zero the 'lost password' in progress attempts
1583 *
1584 * @param integer $p_user_id A valid user identifier.
1585 * @return boolean always true
1586 */
1587function user_reset_lost_password_in_progress_count_to_zero( $p_user_id ) {
1588	db_param_push();
1589	$t_query = 'UPDATE {user} SET lost_password_request_count=0 WHERE id=' . db_param();
1590	db_query( $t_query, array( $p_user_id ) );
1591
1592	user_clear_cache( $p_user_id );
1593
1594	return true;
1595}
1596
1597/**
1598 * Increment the failed login count by 1
1599 *
1600 * @param integer $p_user_id A valid user identifier.
1601 * @return boolean always true
1602 */
1603function user_increment_lost_password_in_progress_count( $p_user_id ) {
1604	db_param_push();
1605	$t_query = 'UPDATE {user}
1606				SET lost_password_request_count=lost_password_request_count+1
1607				WHERE id=' . db_param();
1608	db_query( $t_query, array( $p_user_id ) );
1609
1610	user_clear_cache( $p_user_id );
1611
1612	return true;
1613}
1614
1615/**
1616 * Sets multiple fields on a user
1617 *
1618 * @param integer $p_user_id A valid user identifier.
1619 * @param array   $p_fields  Keys are the field names and the values are the field values.
1620 * @return void
1621 */
1622function user_set_fields( $p_user_id, array $p_fields ) {
1623	if( empty( $p_fields ) ) {
1624		return;
1625	}
1626
1627	if( !array_key_exists( 'protected', $p_fields ) ) {
1628		user_ensure_unprotected( $p_user_id );
1629	}
1630
1631	db_param_push();
1632	$t_query = 'UPDATE {user}';
1633	$t_parameters = array();
1634
1635	foreach ( $p_fields as $t_field_name => $t_field_value ) {
1636		$c_field_name = db_prepare_string( $t_field_name );
1637
1638		if( count( $t_parameters ) == 0 ) {
1639			$t_query .= ' SET '. $c_field_name. '=' . db_param();
1640		} else {
1641			$t_query .= ' , ' . $c_field_name. '=' . db_param();
1642		}
1643
1644		array_push( $t_parameters, $t_field_value );
1645	}
1646
1647	$t_query .= ' WHERE id=' . db_param();
1648	array_push( $t_parameters, (int)$p_user_id );
1649
1650	db_query( $t_query, $t_parameters );
1651
1652	user_clear_cache( $p_user_id );
1653}
1654
1655/**
1656 * Set a user field
1657 *
1658 * @param integer $p_user_id     A valid user identifier.
1659 * @param string  $p_field_name  A valid field name to set.
1660 * @param string  $p_field_value The field value to set.
1661 * @return boolean always true
1662 */
1663function user_set_field( $p_user_id, $p_field_name, $p_field_value ) {
1664	user_set_fields( $p_user_id, array ( $p_field_name => $p_field_value ) );
1665
1666	return true;
1667}
1668
1669/**
1670 * Set Users Default project in preferences
1671 * @param integer $p_user_id    A valid user identifier.
1672 * @param integer $p_project_id A valid project identifier.
1673 * @return void
1674 */
1675function user_set_default_project( $p_user_id, $p_project_id ) {
1676	user_pref_set_pref( $p_user_id, 'default_project', (int)$p_project_id );
1677}
1678
1679/**
1680 * Set the user's password to the given string, encoded as appropriate
1681 *
1682 * @param integer $p_user_id         A valid user identifier.
1683 * @param string  $p_password        A password to set.
1684 * @param boolean $p_allow_protected Whether Allow password change to a protected account. This defaults to false.
1685 * @return boolean always true
1686 */
1687function user_set_password( $p_user_id, $p_password, $p_allow_protected = false ) {
1688	if( !$p_allow_protected ) {
1689		user_ensure_unprotected( $p_user_id );
1690	}
1691
1692	# When the password is changed, invalidate the cookie to expire sessions that
1693	# may be active on all browsers.
1694	$c_cookie_string = auth_generate_unique_cookie_string();
1695	# Delete token for password activation if there is any
1696	token_delete( TOKEN_ACCOUNT_ACTIVATION, $p_user_id );
1697
1698	$c_password = auth_process_plain_password( $p_password );
1699
1700	db_param_push();
1701	$t_query = 'UPDATE {user}
1702				  SET password=' . db_param() . ', cookie_string=' . db_param() . '
1703				  WHERE id=' . db_param();
1704	db_query( $t_query, array( $c_password, $c_cookie_string, (int)$p_user_id ) );
1705
1706	return true;
1707}
1708
1709/**
1710 * Set the user's email to the given string after checking that it is a valid email
1711 * @param integer $p_user_id A valid user identifier.
1712 * @param string  $p_email   An email address to set.
1713 * @return boolean
1714 */
1715function user_set_email( $p_user_id, $p_email ) {
1716	$p_email = trim( $p_email );
1717
1718	email_ensure_valid( $p_email );
1719	email_ensure_not_disposable( $p_email );
1720
1721	$t_old_email = user_get_email( $p_user_id );
1722	if( strcasecmp( $t_old_email, $p_email ) != 0 ) {
1723		user_ensure_email_unique( $p_email );
1724	}
1725
1726	return user_set_field( $p_user_id, 'email', $p_email );
1727}
1728
1729/**
1730 * Set the user's realname to the given string after checking validity
1731 * @param integer $p_user_id  A valid user identifier.
1732 * @param string  $p_realname A realname to set.
1733 * @return boolean
1734 */
1735function user_set_realname( $p_user_id, $p_realname ) {
1736	return user_set_field( $p_user_id, 'realname', $p_realname );
1737}
1738
1739/**
1740 * Set the user's username to the given string after checking that it is valid
1741 * @param integer $p_user_id  A valid user identifier.
1742 * @param string  $p_username A valid username to set.
1743 * @return boolean
1744 */
1745function user_set_name( $p_user_id, $p_username ) {
1746	user_ensure_name_valid( $p_username );
1747	user_ensure_name_unique( $p_username, $p_user_id );
1748
1749	return user_set_field( $p_user_id, 'username', $p_username );
1750}
1751
1752/**
1753 * Reset the user's password
1754 *  Take into account the 'send_reset_password' setting
1755 *   - if it is ON, generate a random password and send an email
1756 *      (unless the second parameter is false)
1757 *   - if it is OFF, set the password to blank
1758 *
1759 * @param integer $p_user_id    A valid user identifier.
1760 * @param boolean $p_send_email Whether to send confirmation email.
1761 * @return boolean True if the password was successfully reset
1762 *                 False if the user is protected.
1763 * @throws ClientException
1764 */
1765function user_reset_password( $p_user_id, $p_send_email = true ) {
1766	if( user_is_protected( $p_user_id ) ) {
1767		return false;
1768	}
1769
1770	# Go with random password and email it to the user
1771	# @@@ do we want to force blank password instead of random if
1772	#      email notifications are turned off?
1773	#     How would we indicate that we had done this with a return value?
1774	#     Should we just have two functions? (user_reset_password_random()
1775	#     and user_reset_password() )?
1776	if( ( ON == config_get( 'send_reset_password' ) ) && ( ON == config_get( 'enable_email_notification' ) ) ) {
1777		$t_email = user_get_field( $p_user_id, 'email' );
1778		if( is_blank( $t_email ) ) {
1779			trigger_error( ERROR_LOST_PASSWORD_NO_EMAIL_SPECIFIED, ERROR );
1780			throw new ClientException(
1781				sprintf( "User id '%d' does not have an e-mail address.", (int)$p_user_id ),
1782				ERROR_LOST_PASSWORD_NO_EMAIL_SPECIFIED,
1783				array( (int)$p_user_id )
1784			);
1785		}
1786
1787		# Create random password
1788		$t_password = auth_generate_random_password();
1789		$t_password2 = auth_process_plain_password( $t_password );
1790
1791		user_set_field( $p_user_id, 'password', $t_password2 );
1792
1793		# Send notification email
1794		if( $p_send_email ) {
1795			$t_confirm_hash = auth_generate_confirm_hash( $p_user_id );
1796			token_set( TOKEN_ACCOUNT_ACTIVATION, $t_confirm_hash, TOKEN_EXPIRY_ACCOUNT_ACTIVATION, $p_user_id );
1797			email_send_confirm_hash_url( $p_user_id, $t_confirm_hash, true );
1798		}
1799	} else {
1800		# use blank password, no emailing
1801		$t_password = auth_process_plain_password( '' );
1802		user_set_field( $p_user_id, 'password', $t_password );
1803
1804		# reset the failed login count because in this mode there is no emailing
1805		user_reset_failed_login_count_to_zero( $p_user_id );
1806	}
1807
1808	return true;
1809}
1810
1811/**
1812 * Helper function to check if the user has access to more than one project
1813 * (any kind of project or subproject). This can be used to simplify logic when
1814 * the user only has one project to choose from.
1815 *
1816 * @param integer $p_user_id	A valid user identifier.
1817 * @return boolean	True if the user has access to more than one project.
1818 */
1819function user_has_more_than_one_project( $p_user_id ) {
1820	$t_project_ids = user_get_accessible_projects( $p_user_id );
1821	$t_count = count( $t_project_ids );
1822	if( 0 == $t_count ) {
1823		return false;
1824	}
1825	if( 1 == $t_count ) {
1826		$t_project_id = (int) $t_project_ids[0];
1827		if( count( user_get_accessible_subprojects( $p_user_id, $t_project_id ) ) == 0 ) {
1828			return false;
1829		}
1830	}
1831	return true;
1832}
1833