<?php

/*
	Question2Answer (c) Gideon Greenspan

	http://www.question2answer.org/

	
	File: qa-include/qa-app-users.php
	Version: See define()s at top of qa-include/qa-base.php
	Description: User management (application level) for basic user operations


	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.
	
	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	More about this license: http://www.question2answer.org/license.php
*/

	if (!defined('QA_VERSION')) { // don't allow this page to be requested directly from browser
		header('Location: ../');
		exit;
	}

	define('QA_USER_LEVEL_BASIC', 0);
	define('QA_USER_LEVEL_EXPERT', 20);
	define('QA_USER_LEVEL_EDITOR', 50);
	define('QA_USER_LEVEL_MODERATOR', 80);
	define('QA_USER_LEVEL_ADMIN', 100);
	define('QA_USER_LEVEL_SUPER', 120);
	
	define('QA_USER_FLAGS_EMAIL_CONFIRMED', 1);
	define('QA_USER_FLAGS_USER_BLOCKED', 2);
	define('QA_USER_FLAGS_SHOW_AVATAR', 4);
	define('QA_USER_FLAGS_SHOW_GRAVATAR', 8);
	define('QA_USER_FLAGS_NO_MESSAGES', 16);
	define('QA_USER_FLAGS_NO_MAILINGS', 32);
	define('QA_USER_FLAGS_WELCOME_NOTICE', 64);
	define('QA_USER_FLAGS_MUST_CONFIRM', 128);
	
	define('QA_FIELD_FLAGS_MULTI_LINE', 1);
	define('QA_FIELD_FLAGS_LINK_URL', 2);

	
	if (QA_FINAL_EXTERNAL_USERS) {

	//	If we're using single sign-on integration (WordPress or otherwise), load PHP file for that

		if (defined('QA_FINAL_WORDPRESS_INTEGRATE_PATH'))
			require_once QA_INCLUDE_DIR.'qa-external-users-wp.php';
		else
			require_once QA_EXTERNAL_DIR.'qa-external-users.php';
		

	//	Access functions for user information
	
		function qa_get_logged_in_user_cache()
	/*
		Return array of information about the currently logged in user, cache to ensure only one call to external code
	*/
		{
			global $qa_cached_logged_in_user;
			
			if (!isset($qa_cached_logged_in_user)) {
				$user=qa_get_logged_in_user();
				$qa_cached_logged_in_user=isset($user) ? $user : false; // to save trying again
			}
			
			return @$qa_cached_logged_in_user;
		}
		
		
		function qa_get_logged_in_user_field($field)
	/*
		Return $field of the currently logged in user, or null if not available
	*/
		{
			$user=qa_get_logged_in_user_cache();
			
			return @$user[$field];
		}


		function qa_get_logged_in_userid()
	/*
		Return the userid of the currently logged in user, or null if none
	*/
		{
			return qa_get_logged_in_user_field('userid');
		}
		
		
		function qa_get_logged_in_points()
	/*
		Return the number of points of the currently logged in user, or null if none is logged in
	*/
		{
			global $qa_cached_logged_in_points;
			
			if (!isset($qa_cached_logged_in_points)) {
				require_once QA_INCLUDE_DIR.'qa-db-selects.php'; 
				
				$qa_cached_logged_in_points=qa_db_select_with_pending(qa_db_user_points_selectspec(qa_get_logged_in_userid(), true));
			}
			
			return $qa_cached_logged_in_points['points'];
		}
		
		
		function qa_get_external_avatar_html($userid, $size, $padding=false)
	/*
		Return HTML to display for the avatar of $userid, constrained to $size pixels, with optional $padding to that size
	*/
		{
			if (function_exists('qa_avatar_html_from_userid'))
				return qa_avatar_html_from_userid($userid, $size, $padding);
			else
				return null;
		}
		
		
	} else {
		
		function qa_start_session()
	/*
		Open a PHP session if one isn't opened already
	*/
		{
			if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
		
			@ini_set('session.gc_maxlifetime', 86400); // worth a try, but won't help in shared hosting environment
			@ini_set('session.use_trans_sid', false); // sessions need cookies to work, since we redirect after login
			@ini_set('session.cookie_domain', QA_COOKIE_DOMAIN);

			if (!isset($_SESSION))
				session_start();
		}
		
		
		function qa_session_var_suffix()
	/*
		Returns a suffix to be used for names of session variables to prevent them being shared between multiple Q2A sites on the same server
	*/
		{
			if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
			
			$prefix=defined('QA_MYSQL_USERS_PREFIX') ? QA_MYSQL_USERS_PREFIX : QA_MYSQL_TABLE_PREFIX;
			
			return md5(QA_FINAL_MYSQL_HOSTNAME.'/'.QA_FINAL_MYSQL_USERNAME.'/'.QA_FINAL_MYSQL_PASSWORD.'/'.QA_FINAL_MYSQL_DATABASE.'/'.$prefix);
		}
		
		
		function qa_session_verify_code($userid)
	/*
		Returns a verification code used to ensure that a user session can't be generated by another PHP script running on the same server
	*/
		{
			if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
		
			return sha1($userid.'/'.QA_MYSQL_TABLE_PREFIX.'/'.QA_FINAL_MYSQL_DATABASE.'/'.QA_FINAL_MYSQL_PASSWORD.'/'.QA_FINAL_MYSQL_USERNAME.'/'.QA_FINAL_MYSQL_HOSTNAME);
		}

		
		function qa_set_session_cookie($handle, $sessioncode, $remember)
	/*
		Set cookie in browser for username $handle with $sessioncode (in database).
		Pass true if user checked 'Remember me' (either now or previously, as learned from cookie).
	*/
		{
			if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
		
			// if $remember is true, store in browser for a month, otherwise store only until browser is closed
			setcookie('qa_session', $handle.'/'.$sessioncode.'/'.($remember ? 1 : 0), $remember ? (time()+2592000) : 0, '/', QA_COOKIE_DOMAIN);
		}

		
		function qa_clear_session_cookie()
	/*
		Remove session cookie from browser
	*/
		{
			if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
		
			setcookie('qa_session', false, 0, '/', QA_COOKIE_DOMAIN);
		}
		
		
		function qa_set_session_user($userid, $source)
	/*
		Set the session variables to indicate that $userid is logged in from $source
	*/
		{
			if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
		
			$suffix=qa_session_var_suffix();

			$_SESSION['qa_session_userid_'.$suffix]=$userid;
			$_SESSION['qa_session_source_'.$suffix]=$source;
			$_SESSION['qa_session_verify_'.$suffix]=qa_session_verify_code($userid);
				// prevents one account on a shared server being able to create a log in a user to Q2A on another account on same server
		}
		
		
		function qa_clear_session_user()
	/*
		Clear the session variables indicating that a user is logged in
	*/
		{
			if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
		
			$suffix=qa_session_var_suffix();

			unset($_SESSION['qa_session_userid_'.$suffix]);
			unset($_SESSION['qa_session_source_'.$suffix]);
			unset($_SESSION['qa_session_verify_'.$suffix]);
		}

		
		function qa_set_logged_in_user($userid, $handle='', $remember=false, $source=null)
	/*
		Call for successful log in by $userid and $handle or successful log out with $userid=null.
		$remember states if 'Remember me' was checked in the login form.
	*/
		{
			if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
		
			require_once QA_INCLUDE_DIR.'qa-app-cookies.php';
			
			qa_start_session();

			if (isset($userid)) {
				qa_set_session_user($userid, $source);
				
				// PHP sessions time out too quickly on the server side, so we also set a cookie as backup.
				// Logging in from a second browser will make the previous browser's 'Remember me' no longer
				// work - I'm not sure if this is the right behavior - could see it either way.

				require_once QA_INCLUDE_DIR.'qa-db-selects.php';
				
				$userinfo=qa_db_single_select(qa_db_user_account_selectspec($userid, true));
				
				// if we have logged in before, and are logging in the same way as before, we don't need to change the sessioncode/source
				// this means it will be possible to automatically log in (via cookies) to the same account from more than one browser
				
				if (empty($userinfo['sessioncode']) || ($source!==$userinfo['sessionsource'])) {
					$sessioncode=qa_db_user_rand_sessioncode();
					qa_db_user_set($userid, 'sessioncode', $sessioncode);
					qa_db_user_set($userid, 'sessionsource', $source);
				} else
					$sessioncode=$userinfo['sessioncode'];
				
				qa_db_user_logged_in($userid, qa_remote_ip_address());
				qa_set_session_cookie($handle, $sessioncode, $remember);
				
				qa_report_event('u_login', $userid, $userinfo['handle'], qa_cookie_get());

			} else {
				$olduserid=qa_get_logged_in_userid();
				$oldhandle=qa_get_logged_in_handle();

				qa_clear_session_cookie();
				qa_clear_session_user();

				qa_report_event('u_logout', $olduserid, $oldhandle, qa_cookie_get());
			}
		}
		
		
		function qa_log_in_external_user($source, $identifier, $fields)
	/*
		Call to log in a user based on an external identity provider $source with external $identifier
		A new user is created based on $fields if it's a new combination of $source and $identifier
	*/
		{
			if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
		
			require_once QA_INCLUDE_DIR.'qa-db-users.php';
			
			$users=qa_db_user_login_find($source, $identifier);
			$countusers=count($users);
			
			if ($countusers>1)
				qa_fatal_error('External login mapped to more than one user'); // should never happen
			
			if ($countusers) // user exists so log them in
				qa_set_logged_in_user($users[0]['userid'], $users[0]['handle'], false, $source);
			
			else { // create and log in user
				require_once QA_INCLUDE_DIR.'qa-app-users-edit.php';
				
				qa_db_user_login_sync(true);
				
				$users=qa_db_user_login_find($source, $identifier); // check again after table is locked
				
				if (count($users)==1) {
					qa_db_user_login_sync(false);
					qa_set_logged_in_user($users[0]['userid'], $users[0]['handle'], false, $source);
				
				} else {
					$handle=qa_handle_make_valid(@$fields['handle']);
				
					if (strlen(@$fields['email'])) { // remove email address if it will cause a duplicate
						$emailusers=qa_db_user_find_by_email($fields['email']);
						if (count($emailusers)) {
							unset($fields['email']);
							unset($fields['confirmed']);
						}
					}
					
					$userid=qa_create_new_user((string)@$fields['email'], null /* no password */, $handle,
						isset($fields['level']) ? $fields['level'] : QA_USER_LEVEL_BASIC, @$fields['confirmed']);
					
					qa_db_user_login_add($userid, $source, $identifier);
					qa_db_user_login_sync(false);
					
					$profilefields=array('name', 'location', 'website', 'about');
					
					foreach ($profilefields as $fieldname)
						if (strlen(@$fields[$fieldname]))
							qa_db_user_profile_set($userid, $fieldname, $fields[$fieldname]);
							
					if (strlen(@$fields['avatar']))
						qa_set_user_avatar($userid, $fields['avatar']);
							
					qa_set_logged_in_user($userid, $handle, false, $source);
				}
			}
		}

		
		function qa_get_logged_in_userid()
	/*
		Return the userid of the currently logged in user, or null if none logged in
	*/
		{
			if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
		
			global $qa_logged_in_userid_checked;
			
			$suffix=qa_session_var_suffix();
			
			if (!$qa_logged_in_userid_checked) { // only check once
				qa_start_session(); // this will load logged in userid from the native PHP session, but that's not enough
				
				$sessionuserid=@$_SESSION['qa_session_userid_'.$suffix];
				
				if (isset($sessionuserid)) // check verify code matches
					if (@$_SESSION['qa_session_verify_'.$suffix] != qa_session_verify_code($sessionuserid))
						qa_clear_session_user();
				
				if (!empty($_COOKIE['qa_session'])) {
					@list($handle, $sessioncode, $remember)=explode('/', $_COOKIE['qa_session']);
					
					if ($remember)
						qa_set_session_cookie($handle, $sessioncode, $remember); // extend 'remember me' cookies each time
	
					$sessioncode=trim($sessioncode); // trim to prevent passing in blank values to match uninitiated DB rows
	
					// Try to recover session from the database if PHP session has timed out
					if ( (!isset($_SESSION['qa_session_userid_'.$suffix])) && (!empty($handle)) && (!empty($sessioncode)) ) {
						require_once QA_INCLUDE_DIR.'qa-db-selects.php';
						
						$userinfo=qa_db_single_select(qa_db_user_account_selectspec($handle, false)); // don't get any pending
						
						if (strtolower(trim($userinfo['sessioncode'])) == strtolower($sessioncode))
							qa_set_session_user($userinfo['userid'], $userinfo['sessionsource']);
						else
							qa_clear_session_cookie(); // if cookie not valid, remove it to save future checks
					}
				}
				
				$qa_logged_in_userid_checked=true;
			}
			
			return @$_SESSION['qa_session_userid_'.$suffix];
		}
		
		
		function qa_get_logged_in_source()
	/*
		Get the source of the currently logged in user, from call to qa_log_in_external_user() or null if logged in normally
	*/
		{
			if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
		
			$userid=qa_get_logged_in_userid();
			$suffix=qa_session_var_suffix();
			
			if (isset($userid))
				return @$_SESSION['qa_session_source_'.$suffix];
		}
		
		
		function qa_get_logged_in_user_field($field)
	/*
		Return $field of the currently logged in user, cache to ensure only one call to external code
	*/
		{
			if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
		
			global $qa_cached_logged_in_user;
			
			$userid=qa_get_logged_in_userid();
			
			if (isset($userid) && !isset($qa_cached_logged_in_user)) {
				require_once QA_INCLUDE_DIR.'qa-db-selects.php';
				$qa_cached_logged_in_user=qa_db_get_pending_result('loggedinuser', qa_db_user_account_selectspec($userid, true));
				
				if (!isset($qa_cached_logged_in_user)) { // the user can no longer be found (should be because they're deleted)
					qa_clear_session_user();
					qa_fatal_error('The logged in user cannot be found');
						// it's too late here to proceed because the caller may already be branching based on whether someone is logged in
				}					
			}
			
			return @$qa_cached_logged_in_user[$field];
		}
		
		
		function qa_get_logged_in_points()
	/*
		Return the number of points of the currently logged in user, or null if none is logged in
	*/
		{
			if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
		
			return qa_get_logged_in_user_field('points');
		}

		
		function qa_get_mysql_user_column_type()
	/*
		Return column type to use for users (if not using single sign-on integration)
	*/
		{
			return 'INT UNSIGNED';
		}


		function qa_get_one_user_html($handle, $microformats)
	/*
		Return HTML to display for user with username $handle
	*/
		{
			if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
		
			return strlen($handle) ? ('<A HREF="'.qa_path_html('user/'.$handle).
				'" CLASS="qa-user-link'.($microformats ? ' url nickname' : '').'">'.qa_html($handle).'</A>') : '';
		}
		
		
		function qa_get_user_avatar_html($flags, $email, $handle, $blobid, $width, $height, $size, $padding=false)
	/*
		Return HTML to display for the user's avatar, constrained to $size pixels, with optional $padding to that size
		Pass the user's fields $flags, $email, $handle, and avatar $blobid, $width and $height
	*/	
		{
			if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
		
			require_once QA_INCLUDE_DIR.'qa-app-format.php';
			
			if (qa_opt('avatar_allow_gravatar') && ($flags & QA_USER_FLAGS_SHOW_GRAVATAR))
				$html=qa_get_gravatar_html($email, $size);
			elseif (qa_opt('avatar_allow_upload') && (($flags & QA_USER_FLAGS_SHOW_AVATAR)) && isset($blobid))
				$html=qa_get_avatar_blob_html($blobid, $width, $height, $size, $padding);
			elseif ( (qa_opt('avatar_allow_gravatar')||qa_opt('avatar_allow_upload')) && qa_opt('avatar_default_show') && strlen(qa_opt('avatar_default_blobid')) )
				$html=qa_get_avatar_blob_html(qa_opt('avatar_default_blobid'), qa_opt('avatar_default_width'), qa_opt('avatar_default_height'), $size, $padding);
			else
				$html=null;
				
			return (isset($html) && strlen($handle)) ? ('<A HREF="'.qa_path_html('user/'.$handle).'" CLASS="qa-avatar-link">'.$html.'</A>') : $html;
		}
		

		function qa_get_user_email($userid)
	/*
		Return email address for user $userid (if not using single sign-on integration)
	*/
		{
			$userinfo=qa_db_select_with_pending(qa_db_user_account_selectspec($userid, true));

			return $userinfo['email'];
		}
		

		function qa_user_report_action($userid, $action)
	/*
		Called after a database write $action performed by a user $userid
	*/
		{
			if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
		
			require_once QA_INCLUDE_DIR.'qa-db-users.php';
			
			qa_db_user_written($userid, qa_remote_ip_address());
		}

		
		function qa_user_level_string($level)
	/*
		Return textual representation of the user $level
	*/
		{
			if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
		
			if ($level>=QA_USER_LEVEL_SUPER)
				$string='users/level_super';
			elseif ($level>=QA_USER_LEVEL_ADMIN)
				$string='users/level_admin';
			elseif ($level>=QA_USER_LEVEL_MODERATOR)
				$string='users/level_moderator';
			elseif ($level>=QA_USER_LEVEL_EDITOR)
				$string='users/level_editor';
			elseif ($level>=QA_USER_LEVEL_EXPERT)
				$string='users/level_expert';
			else
				$string='users/registered_user';
			
			return qa_lang($string);
		}

		
		function qa_get_login_links($rooturl, $tourl)
	/*
		Return an array of links to login, register, email confirm and logout pages (if not using single sign-on integration)
	*/
		{
			return array(
				'login' => qa_path('login', isset($tourl) ? array('to' => $tourl) : null, $rooturl),
				'register' => qa_path('register', isset($tourl) ? array('to' => $tourl) : null, $rooturl),
				'confirm' => qa_path('confirm', null, $rooturl),
				'logout' => qa_path('logout', null, $rooturl),
			);
		}

	} // end of: if (QA_FINAL_EXTERNAL_USERS) { ... } else { ... }


	function qa_is_logged_in()
/*
	Return whether someone is logged in at the moment
*/
	{	
		$userid=qa_get_logged_in_userid();
		return isset($userid);
	}
	
	
	function qa_get_logged_in_handle()
/*
	Return displayable handle/username of currently logged in user, or null if none
*/
	{
		return qa_get_logged_in_user_field(QA_FINAL_EXTERNAL_USERS ? 'publicusername' : 'handle');
	}


	function qa_get_logged_in_email()
/*
	Return email of currently logged in user, or null if none
*/
	{
		return qa_get_logged_in_user_field('email');
	}


	function qa_get_logged_in_level()
/*
	Return level of currently logged in user, or null if none
*/
	{
		return qa_get_logged_in_user_field('level');
	}

	
	function qa_get_logged_in_flags()
/*
	Return flags (see QA_USER_FLAGS_*) of currently logged in user, or null if none
*/
	{
		if (QA_FINAL_EXTERNAL_USERS)
			return qa_get_logged_in_user_field('blocked') ? QA_USER_FLAGS_USER_BLOCKED : 0;
		else
			return qa_get_logged_in_user_field('flags');
	}

	
	function qa_userids_to_handles($userids)
/*
	Return an array mapping each userid in $userids to that user's handle (public username), or to null if not found
*/
	{
		if (QA_FINAL_EXTERNAL_USERS)
			$rawuseridhandles=qa_get_public_from_userids($userids);
		
		else {
			require_once QA_INCLUDE_DIR.'qa-db-users.php';
			$rawuseridhandles=qa_db_user_get_userid_handles($userids);
		}
		
		$gotuseridhandles=array();
		foreach ($userids as $userid)
			$gotuseridhandles[$userid]=@$rawuseridhandles[$userid];
			
		return $gotuseridhandles;
	}
	
	
	function qa_handles_to_userids($handles, $exactonly=false)
/*
	Return an array mapping each handle in $handles the user's userid, or null if not found. If $exactonly is true then
	$handles must have the correct case and accents. Otherwise, handles are case- and accent-insensitive, and the keys
	of the returned array will match the $handles provided, not necessary those in the DB.
*/
	{
		require_once QA_INCLUDE_DIR.'qa-util-string.php';
		
		if (QA_FINAL_EXTERNAL_USERS)
			$rawhandleuserids=qa_get_userids_from_public($handles);

		else {
			require_once QA_INCLUDE_DIR.'qa-db-users.php';
			$rawhandleuserids=qa_db_user_get_handle_userids($handles);
		}
		
		$gothandleuserids=array();

		if ($exactonly) { // only take the exact matches
			foreach ($handles as $handle)
				$gothandleuserids[$handle]=@$rawhandleuserids[$handle];
		
		} else { // normalize to lowercase without accents, and then find matches
			$normhandleuserids=array();
			foreach ($rawhandleuserids as $handle => $userid)
				$normhandleuserids[qa_string_remove_accents(qa_strtolower($handle))]=$userid;
			
			foreach ($handles as $handle)
				$gothandleuserids[$handle]=@$normhandleuserids[qa_string_remove_accents(qa_strtolower($handle))];
		}
		
		return $gothandleuserids;
	}
	
	
	function qa_user_permit_error($permitoption=null, $limitaction=null)
/*
	Check whether the logged in user has permission to perform $permitoption. If $permitoption is null, this simply
	checks whether the user is blocked. Optionally provide an $limitaction (see top of qa-app-limits.php) to also check
	against user or IP rate limits.

	Possible results, in order of priority (i.e. if more than one reason, the first will be given):
	'level' => a special privilege level (e.g. expert) or minimum number of points is required
	'login' => the user should login or register
	'userblock' => the user has been blocked
	'ipblock' => the ip address has been blocked
	'confirm' => the user should confirm their email address
	'limit' => the user or IP address has reached a rate limit (if $limitaction specified)
	false => the operation can go ahead
*/
	{
		if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
		
		require_once QA_INCLUDE_DIR.'qa-app-limits.php';
		
		$userid=qa_get_logged_in_userid();
		$flags=qa_get_logged_in_flags();

		$error=qa_permit_error($permitoption, $userid, qa_get_logged_in_level(), $flags);
		
		if ((!$error) && qa_is_ip_blocked())
			$error='ipblock';
			
		if ((!$error) && isset($userid) && ($flags & QA_USER_FLAGS_MUST_CONFIRM))
			$error='confirm';
		
		if (isset($limitaction) && !$error)
			if (qa_limits_remaining(qa_get_logged_in_userid(), $limitaction)<=0)
				$error='limit';
		
		return $error;
	}
	
	
	function qa_permit_error($permitoption, $userid, $userlevel, $userflags, $userpoints=null)
/*
	Check whether $userid (null for no user) can perform $permitoption. Result as for qa_user_permit_error(...).
	If appropriate, pass the user's level in $userlevel, flags in $userflags and points in $userpoints.
	If $userid is currently logged in, you can set $userpoints=null to retrieve them only if necessary.
*/
	{
		if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
		
		$permit=isset($permitoption) ? qa_opt($permitoption) : QA_PERMIT_ALL;

		if (isset($userid) && (($permit==QA_PERMIT_POINTS) || ($permit==QA_PERMIT_POINTS_CONFIRMED)) ) { // deal with points threshold by converting as appropriate
			if ( (!isset($userpoints)) && ($userid==qa_get_logged_in_userid()) )
				$userpoints=qa_get_logged_in_points(); // allow late retrieval of points (to avoid unnecessary DB query when using external users)
		
			if ($userpoints>=qa_opt($permitoption.'_points'))
				$permit=($permit==QA_PERMIT_POINTS_CONFIRMED) ? QA_PERMIT_CONFIRMED : QA_PERMIT_USERS; // convert if user has enough points
			else
				$permit=QA_PERMIT_EXPERTS; // otherwise, only special users pass
		}
		
		return qa_permit_value_error($permit, $userid, $userlevel, $userflags);
	}
	
	
	function qa_permit_value_error($permit, $userid, $userlevel, $userflags)
/*
	Check whether $userid of level $userlevel with $userflags can reach the permission level in $permit
	(generally retrieved from an option, but not always). Result as for qa_user_permit_error(...).
*/
	{
		if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
		
		if ($permit>=QA_PERMIT_ALL)
			$error=false;
			
		elseif ($permit>=QA_PERMIT_USERS)
			$error=isset($userid) ? false : 'login';
			
		elseif ($permit>=QA_PERMIT_CONFIRMED) {
			if (!isset($userid))
				$error='login';
			
			elseif (
				QA_FINAL_EXTERNAL_USERS || // not currently supported by single sign-on integration
				($userlevel>=QA_USER_LEVEL_EXPERT) || // if user assigned to a higher level, no need
				($userflags & QA_USER_FLAGS_EMAIL_CONFIRMED) || // actual confirmation
				(!qa_opt('confirm_user_emails')) // if this option off, we can't ask it of the user
			)
				$error=false;
			
			else
				$error='confirm';

		} elseif ($permit>=QA_PERMIT_EXPERTS)
			$error=(isset($userid) && ($userlevel>=QA_USER_LEVEL_EXPERT)) ? false : 'level';
			
		elseif ($permit>=QA_PERMIT_EDITORS)
			$error=(isset($userid) && ($userlevel>=QA_USER_LEVEL_EDITOR)) ? false : 'level';
			
		elseif ($permit>=QA_PERMIT_MODERATORS)
			$error=(isset($userid) && ($userlevel>=QA_USER_LEVEL_MODERATOR)) ? false : 'level';
			
		elseif ($permit>=QA_PERMIT_ADMINS)
			$error=(isset($userid) && ($userlevel>=QA_USER_LEVEL_ADMIN)) ? false : 'level';
			
		else
			$error=(isset($userid) && ($userlevel>=QA_USER_LEVEL_SUPER)) ? false : 'level';
		
		if (isset($userid) && ($userflags & QA_USER_FLAGS_USER_BLOCKED) && ($error!='level'))
			$error='userblock';
		
		return $error;
	}
	
	
	function qa_user_use_captcha()
/*
	Return whether a captcha should be presented to the current user for writing posts
*/
	{
		if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
		
		$usecaptcha=false;
		
		if (qa_get_logged_in_level() < QA_USER_LEVEL_EXPERT) { // experts and above aren't shown captchas
			$userid=qa_get_logged_in_userid();
			
			if (qa_opt('captcha_on_anon_post') && !isset($userid))
				$usecaptcha=true;
			elseif (qa_opt('confirm_user_emails') && qa_opt('captcha_on_unconfirmed') && !(qa_get_logged_in_flags() & QA_USER_FLAGS_EMAIL_CONFIRMED) )
				$usecaptcha=true;
		}
		
		return $usecaptcha;
	}
	
	
	function qa_user_moderation_reason()
/*
	Return whether moderation is required for posts submitted by the current user. Possible results:
	'login' => moderation required because the user is not logged in
	'confirm' => moderation required because the user has not confirmed their email address
	'points' => moderation required because the user has insufficient points
	false => moderation is not required
*/
	{
		if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
		
		$reason=false;
		
		if (
			(qa_get_logged_in_level() < QA_USER_LEVEL_EXPERT) && // experts and above aren't moderated
			qa_user_permit_error('permit_moderate') // if the user can approve posts, no point in moderating theirs
		) {
			$userid=qa_get_logged_in_userid();
			
			if (isset($userid)) {
				if (qa_opt('confirm_user_emails') && qa_opt('moderate_unconfirmed') && !(qa_get_logged_in_flags() & QA_USER_FLAGS_EMAIL_CONFIRMED) )
					$reason='confirm';
				elseif (qa_opt('moderate_by_points') && (qa_get_logged_in_points() < qa_opt('moderate_points_limit')))
					$reason='points';
			
			} elseif (qa_opt('moderate_anon_post'))
				$reason='login';
		}
		
		return $reason;
	}
	
	
	function qa_user_userfield_label($userfield)
/*
	Return the label to display for $userfield as retrieved from the database, using default if no name set
*/
	{
		if (isset($userfield['content']))
			return $userfield['content'];
		
		else {
			$defaultlabels=array(
				'name' => 'users/full_name',
				'about' => 'users/about',
				'location' => 'users/location',
				'website' => 'users/website',
			);
			
			if (isset($defaultlabels[$userfield['title']]))
				return qa_lang($defaultlabels[$userfield['title']]);
		}
			
		return '';
	}
	

/*
	Omit PHP closing tag to help avoid accidental output
*/