qa-app-users.php 39.1 KB
Newer Older
Gideon Greenspan committed
1 2 3 4 5 6 7
<?php

/*
	Question2Answer (c) Gideon Greenspan

	http://www.question2answer.org/

Scott Vivian committed
8

Gideon Greenspan committed
9 10 11 12 13 14 15 16 17
	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.
Scott Vivian committed
18

Gideon Greenspan committed
19 20 21 22 23 24 25 26 27 28 29 30 31 32
	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);
Gideon Greenspan committed
33
	define('QA_USER_LEVEL_APPROVED', 10);
Gideon Greenspan committed
34 35 36 37 38
	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);
Scott Vivian committed
39

Gideon Greenspan committed
40 41 42 43 44 45 46 47
	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);
Gideon Greenspan committed
48 49
	define('QA_USER_FLAGS_NO_WALL_POSTS', 256);
	define('QA_USER_FLAGS_MUST_APPROVE', 512);
Scott Vivian committed
50

Gideon Greenspan committed
51 52
	define('QA_FIELD_FLAGS_MULTI_LINE', 1);
	define('QA_FIELD_FLAGS_LINK_URL', 2);
Gideon Greenspan committed
53
	define('QA_FIELD_FLAGS_ON_REGISTER', 4);
Scott Vivian committed
54

Gideon Greenspan committed
55 56
	@define('QA_FORM_EXPIRY_SECS', 86400); // how many seconds a form is valid for submission
	@define('QA_FORM_KEY_LENGTH', 32);
Gideon Greenspan committed
57

Scott Vivian committed
58

Gideon Greenspan committed
59 60 61 62 63 64 65 66
	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';
Scott Vivian committed
67

Gideon Greenspan committed
68 69

	//	Access functions for user information
Scott Vivian committed
70

Gideon Greenspan committed
71 72 73 74 75 76
		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;
Scott Vivian committed
77

Gideon Greenspan committed
78 79 80 81
			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
			}
Scott Vivian committed
82

Gideon Greenspan committed
83 84
			return @$qa_cached_logged_in_user;
		}
Scott Vivian committed
85 86


Gideon Greenspan committed
87 88 89 90 91 92
		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();
Scott Vivian committed
93

Gideon Greenspan committed
94 95 96 97 98 99 100 101 102 103 104
			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');
		}
Scott Vivian committed
105 106


Gideon Greenspan committed
107 108 109 110 111 112
		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;
Scott Vivian committed
113

Gideon Greenspan committed
114
			if (!isset($qa_cached_logged_in_points)) {
Scott Vivian committed
115 116
				require_once QA_INCLUDE_DIR.'qa-db-selects.php';

Gideon Greenspan committed
117
				$qa_cached_logged_in_points=qa_db_select_with_pending(qa_db_user_points_selectspec(qa_get_logged_in_userid(), true));
Gideon Greenspan committed
118
			}
Scott Vivian committed
119

Gideon Greenspan committed
120 121
			return $qa_cached_logged_in_points['points'];
		}
Scott Vivian committed
122 123


Gideon Greenspan committed
124 125 126 127 128 129 130 131 132 133
		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;
		}
Scott Vivian committed
134 135


Gideon Greenspan committed
136
	} else {
Scott Vivian committed
137

Gideon Greenspan committed
138 139 140 141 142
		function qa_start_session()
	/*
		Open a PHP session if one isn't opened already
	*/
		{
Gideon Greenspan committed
143
			if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
Scott Vivian committed
144

Gideon Greenspan committed
145 146 147 148 149 150 151
			@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();
		}
Scott Vivian committed
152 153


Gideon Greenspan committed
154 155 156 157 158
		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
	*/
		{
Gideon Greenspan committed
159
			if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
Scott Vivian committed
160

Gideon Greenspan committed
161
			$prefix=defined('QA_MYSQL_USERS_PREFIX') ? QA_MYSQL_USERS_PREFIX : QA_MYSQL_TABLE_PREFIX;
Scott Vivian committed
162

Gideon Greenspan committed
163
			return md5(QA_FINAL_MYSQL_HOSTNAME.'/'.QA_FINAL_MYSQL_USERNAME.'/'.QA_FINAL_MYSQL_PASSWORD.'/'.QA_FINAL_MYSQL_DATABASE.'/'.$prefix);
Gideon Greenspan committed
164
		}
Scott Vivian committed
165 166


Gideon Greenspan committed
167 168 169 170 171
		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
	*/
		{
Gideon Greenspan committed
172
			if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
Scott Vivian committed
173

Gideon Greenspan committed
174 175 176
			return sha1($userid.'/'.QA_MYSQL_TABLE_PREFIX.'/'.QA_FINAL_MYSQL_DATABASE.'/'.QA_FINAL_MYSQL_PASSWORD.'/'.QA_FINAL_MYSQL_USERNAME.'/'.QA_FINAL_MYSQL_HOSTNAME);
		}

Scott Vivian committed
177

Gideon Greenspan committed
178 179 180 181 182 183
		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).
	*/
		{
Gideon Greenspan committed
184
			if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
Scott Vivian committed
185

Gideon Greenspan committed
186 187 188 189
			// 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);
		}

Scott Vivian committed
190

Gideon Greenspan committed
191 192 193 194 195
		function qa_clear_session_cookie()
	/*
		Remove session cookie from browser
	*/
		{
Gideon Greenspan committed
196
			if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
Scott Vivian committed
197

Gideon Greenspan committed
198 199
			setcookie('qa_session', false, 0, '/', QA_COOKIE_DOMAIN);
		}
Scott Vivian committed
200 201


Gideon Greenspan committed
202 203 204 205 206
		function qa_set_session_user($userid, $source)
	/*
		Set the session variables to indicate that $userid is logged in from $source
	*/
		{
Gideon Greenspan committed
207
			if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
Scott Vivian committed
208

Gideon Greenspan committed
209 210 211 212 213 214 215
			$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
		}
Scott Vivian committed
216 217


Gideon Greenspan committed
218 219 220 221 222
		function qa_clear_session_user()
	/*
		Clear the session variables indicating that a user is logged in
	*/
		{
Gideon Greenspan committed
223
			if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
Scott Vivian committed
224

Gideon Greenspan committed
225 226 227 228 229 230 231
			$suffix=qa_session_var_suffix();

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

Scott Vivian committed
232

Gideon Greenspan committed
233 234 235 236 237 238
		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.
	*/
		{
Gideon Greenspan committed
239
			if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
Scott Vivian committed
240

Gideon Greenspan committed
241
			require_once QA_INCLUDE_DIR.'qa-app-cookies.php';
Scott Vivian committed
242

Gideon Greenspan committed
243 244 245 246
			qa_start_session();

			if (isset($userid)) {
				qa_set_session_user($userid, $source);
Scott Vivian committed
247

Gideon Greenspan committed
248 249 250 251 252
				// 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';
Scott Vivian committed
253

Gideon Greenspan committed
254
				$userinfo=qa_db_single_select(qa_db_user_account_selectspec($userid, true));
Scott Vivian committed
255

Gideon Greenspan committed
256 257
				// 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
Scott Vivian committed
258

Gideon Greenspan committed
259 260 261 262 263 264
				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'];
Scott Vivian committed
265

Gideon Greenspan committed
266 267
				qa_db_user_logged_in($userid, qa_remote_ip_address());
				qa_set_session_cookie($handle, $sessioncode, $remember);
Scott Vivian committed
268

Gideon Greenspan committed
269 270 271 272 273 274 275 276 277 278 279 280
				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());
			}
		}
Scott Vivian committed
281 282


Gideon Greenspan committed
283 284 285 286 287 288
		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
	*/
		{
Gideon Greenspan committed
289
			if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
Scott Vivian committed
290

Gideon Greenspan committed
291
			require_once QA_INCLUDE_DIR.'qa-db-users.php';
Scott Vivian committed
292

Gideon Greenspan committed
293 294
			$users=qa_db_user_login_find($source, $identifier);
			$countusers=count($users);
Scott Vivian committed
295

Gideon Greenspan committed
296 297
			if ($countusers>1)
				qa_fatal_error('External login mapped to more than one user'); // should never happen
Scott Vivian committed
298

Gideon Greenspan committed
299 300
			if ($countusers) // user exists so log them in
				qa_set_logged_in_user($users[0]['userid'], $users[0]['handle'], false, $source);
Scott Vivian committed
301

Gideon Greenspan committed
302 303
			else { // create and log in user
				require_once QA_INCLUDE_DIR.'qa-app-users-edit.php';
Scott Vivian committed
304

Gideon Greenspan committed
305
				qa_db_user_login_sync(true);
Scott Vivian committed
306

Gideon Greenspan committed
307
				$users=qa_db_user_login_find($source, $identifier); // check again after table is locked
Scott Vivian committed
308

Gideon Greenspan committed
309 310 311
				if (count($users)==1) {
					qa_db_user_login_sync(false);
					qa_set_logged_in_user($users[0]['userid'], $users[0]['handle'], false, $source);
Scott Vivian committed
312

Gideon Greenspan committed
313 314
				} else {
					$handle=qa_handle_make_valid(@$fields['handle']);
Scott Vivian committed
315

Gideon Greenspan committed
316 317 318
					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)) {
Gideon Greenspan committed
319
							qa_redirect('login', array('e' => $fields['email'], 'ee' => '1'));
Gideon Greenspan committed
320 321 322 323
							unset($fields['email']);
							unset($fields['confirmed']);
						}
					}
Scott Vivian committed
324

Gideon Greenspan committed
325 326
					$userid=qa_create_new_user((string)@$fields['email'], null /* no password */, $handle,
						isset($fields['level']) ? $fields['level'] : QA_USER_LEVEL_BASIC, @$fields['confirmed']);
Scott Vivian committed
327

Gideon Greenspan committed
328 329
					qa_db_user_login_add($userid, $source, $identifier);
					qa_db_user_login_sync(false);
Scott Vivian committed
330

Gideon Greenspan committed
331
					$profilefields=array('name', 'location', 'website', 'about');
Scott Vivian committed
332

Gideon Greenspan committed
333 334 335
					foreach ($profilefields as $fieldname)
						if (strlen(@$fields[$fieldname]))
							qa_db_user_profile_set($userid, $fieldname, $fields[$fieldname]);
Scott Vivian committed
336

Gideon Greenspan committed
337 338
					if (strlen(@$fields['avatar']))
						qa_set_user_avatar($userid, $fields['avatar']);
Scott Vivian committed
339

Gideon Greenspan committed
340 341 342 343 344
					qa_set_logged_in_user($userid, $handle, false, $source);
				}
			}
		}

Scott Vivian committed
345

Gideon Greenspan committed
346 347 348 349 350
		function qa_get_logged_in_userid()
	/*
		Return the userid of the currently logged in user, or null if none logged in
	*/
		{
Gideon Greenspan committed
351
			if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
Scott Vivian committed
352

Gideon Greenspan committed
353
			global $qa_logged_in_userid_checked;
Scott Vivian committed
354

Gideon Greenspan committed
355
			$suffix=qa_session_var_suffix();
Scott Vivian committed
356

Gideon Greenspan committed
357 358
			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
Scott Vivian committed
359

Gideon Greenspan committed
360
				$sessionuserid=@$_SESSION['qa_session_userid_'.$suffix];
Scott Vivian committed
361

Gideon Greenspan committed
362 363 364
				if (isset($sessionuserid)) // check verify code matches
					if (@$_SESSION['qa_session_verify_'.$suffix] != qa_session_verify_code($sessionuserid))
						qa_clear_session_user();
Scott Vivian committed
365

Gideon Greenspan committed
366 367
				if (!empty($_COOKIE['qa_session'])) {
					@list($handle, $sessioncode, $remember)=explode('/', $_COOKIE['qa_session']);
Scott Vivian committed
368

Gideon Greenspan committed
369 370
					if ($remember)
						qa_set_session_cookie($handle, $sessioncode, $remember); // extend 'remember me' cookies each time
Scott Vivian committed
371

Gideon Greenspan committed
372
					$sessioncode=trim($sessioncode); // trim to prevent passing in blank values to match uninitiated DB rows
Scott Vivian committed
373

Gideon Greenspan committed
374 375 376
					// 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';
Scott Vivian committed
377

Gideon Greenspan committed
378
						$userinfo=qa_db_single_select(qa_db_user_account_selectspec($handle, false)); // don't get any pending
Scott Vivian committed
379

Gideon Greenspan committed
380 381 382 383 384 385
						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
					}
				}
Scott Vivian committed
386

Gideon Greenspan committed
387 388
				$qa_logged_in_userid_checked=true;
			}
Scott Vivian committed
389

Gideon Greenspan committed
390 391
			return @$_SESSION['qa_session_userid_'.$suffix];
		}
Scott Vivian committed
392 393


Gideon Greenspan committed
394 395 396 397 398
		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
	*/
		{
Gideon Greenspan committed
399
			if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
Scott Vivian committed
400

Gideon Greenspan committed
401 402
			$userid=qa_get_logged_in_userid();
			$suffix=qa_session_var_suffix();
Scott Vivian committed
403

Gideon Greenspan committed
404 405 406
			if (isset($userid))
				return @$_SESSION['qa_session_source_'.$suffix];
		}
Scott Vivian committed
407 408


Gideon Greenspan committed
409 410 411 412 413
		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
	*/
		{
Gideon Greenspan committed
414
			if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
Scott Vivian committed
415

Gideon Greenspan committed
416
			global $qa_cached_logged_in_user;
Scott Vivian committed
417

Gideon Greenspan committed
418
			$userid=qa_get_logged_in_userid();
Scott Vivian committed
419

Gideon Greenspan committed
420 421 422
			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));
Scott Vivian committed
423

424 425
				if (!isset($qa_cached_logged_in_user)) {
					// the user can no longer be found (should only apply to deleted users)
Gideon Greenspan committed
426
					qa_clear_session_user();
427
					qa_redirect(''); // implicit exit;
Scott Vivian committed
428
				}
Gideon Greenspan committed
429
			}
Scott Vivian committed
430

Gideon Greenspan committed
431 432
			return @$qa_cached_logged_in_user[$field];
		}
Scott Vivian committed
433 434


Gideon Greenspan committed
435 436 437 438 439
		function qa_get_logged_in_points()
	/*
		Return the number of points of the currently logged in user, or null if none is logged in
	*/
		{
Gideon Greenspan committed
440
			if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
Scott Vivian committed
441

Gideon Greenspan committed
442 443 444
			return qa_get_logged_in_user_field('points');
		}

Scott Vivian committed
445

Gideon Greenspan committed
446 447 448 449 450 451 452 453 454
		function qa_get_mysql_user_column_type()
	/*
		Return column type to use for users (if not using single sign-on integration)
	*/
		{
			return 'INT UNSIGNED';
		}


Gideon Greenspan committed
455
		function qa_get_one_user_html($handle, $microformats=false, $favorited=false)
Gideon Greenspan committed
456
	/*
Gideon Greenspan committed
457
		Return HTML to display for user with username $handle, with microformats if $microformats is true. Set $favorited to true to show the user as favorited.
Gideon Greenspan committed
458 459
	*/
		{
Gideon Greenspan committed
460
			if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
Scott Vivian committed
461

Scott committed
462 463 464 465 466 467 468 469
			if (!strlen($handle))
				return '';

			$url = qa_path_html('user/'.$handle);
			$favclass = $favorited ? ' qa-user-favorited' : '';
			$mfclass = $microformats ? ' url nickname' : '';

			return '<a href="'.$url.'" class="qa-user-link'.$favclass.$mfclass.'">'.qa_html($handle).'</a>';
Gideon Greenspan committed
470
		}
Scott Vivian committed
471 472


Gideon Greenspan committed
473 474 475 476
		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
Scott Vivian committed
477
	*/
Gideon Greenspan committed
478
		{
Gideon Greenspan committed
479
			if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
Scott Vivian committed
480

Gideon Greenspan committed
481
			require_once QA_INCLUDE_DIR.'qa-app-format.php';
Scott Vivian committed
482

Gideon Greenspan committed
483 484 485 486 487 488 489 490
			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;
Scott Vivian committed
491

Gideon Greenspan committed
492
			return (isset($html) && strlen($handle)) ? ('<a href="'.qa_path_html('user/'.$handle).'" class="qa-avatar-link">'.$html.'</a>') : $html;
Gideon Greenspan committed
493
		}
Scott Vivian committed
494

Gideon Greenspan committed
495 496 497 498 499 500 501 502 503 504

		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'];
		}
Scott Vivian committed
505

Gideon Greenspan committed
506 507 508 509 510 511

		function qa_user_report_action($userid, $action)
	/*
		Called after a database write $action performed by a user $userid
	*/
		{
Gideon Greenspan committed
512
			if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
Scott Vivian committed
513

Gideon Greenspan committed
514
			require_once QA_INCLUDE_DIR.'qa-db-users.php';
Scott Vivian committed
515

Gideon Greenspan committed
516 517 518
			qa_db_user_written($userid, qa_remote_ip_address());
		}

Scott Vivian committed
519

Gideon Greenspan committed
520 521 522 523 524
		function qa_user_level_string($level)
	/*
		Return textual representation of the user $level
	*/
		{
Gideon Greenspan committed
525
			if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
Scott Vivian committed
526

Gideon Greenspan committed
527 528 529 530 531 532 533 534 535 536
			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';
Gideon Greenspan committed
537 538
			elseif ($level>=QA_USER_LEVEL_APPROVED)
				$string='users/approved_user';
Gideon Greenspan committed
539 540
			else
				$string='users/registered_user';
Scott Vivian committed
541

Gideon Greenspan committed
542 543 544
			return qa_lang($string);
		}

Scott Vivian committed
545

Gideon Greenspan committed
546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565
		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
*/
Scott Vivian committed
566
	{
Gideon Greenspan committed
567 568 569
		$userid=qa_get_logged_in_userid();
		return isset($userid);
	}
Scott Vivian committed
570 571


Gideon Greenspan committed
572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597
	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');
	}

Scott Vivian committed
598

Gideon Greenspan committed
599 600 601 602 603
	function qa_get_logged_in_flags()
/*
	Return flags (see QA_USER_FLAGS_*) of currently logged in user, or null if none
*/
	{
Gideon Greenspan committed
604 605 606 607
		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');
Gideon Greenspan committed
608 609
	}

Scott Vivian committed
610

Gideon Greenspan committed
611
	function qa_get_logged_in_levels()
Gideon Greenspan committed
612 613 614
/*
	Return an array of all the specific (e.g. per category) level privileges for the logged in user, retrieving from the database if necessary
*/
Gideon Greenspan committed
615 616
	{
		require_once QA_INCLUDE_DIR.'qa-db-selects.php';
Scott Vivian committed
617

Gideon Greenspan committed
618 619
		return qa_db_get_pending_result('userlevels', qa_db_user_levels_selectspec(qa_get_logged_in_userid(), true));
	}
Scott Vivian committed
620 621


Gideon Greenspan committed
622 623 624 625 626 627 628
	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);
Scott Vivian committed
629

Gideon Greenspan committed
630 631 632 633
		else {
			require_once QA_INCLUDE_DIR.'qa-db-users.php';
			$rawuseridhandles=qa_db_user_get_userid_handles($userids);
		}
Scott Vivian committed
634

Gideon Greenspan committed
635 636 637
		$gotuseridhandles=array();
		foreach ($userids as $userid)
			$gotuseridhandles[$userid]=@$rawuseridhandles[$userid];
Scott Vivian committed
638

Gideon Greenspan committed
639 640
		return $gotuseridhandles;
	}
Scott Vivian committed
641 642


643 644 645 646 647 648 649 650
	function qa_userid_to_handle($userid)
/*
	Return an string mapping the received userid to that user's handle (public username), or to null if not found
*/
	{
		$handles=qa_userids_to_handles(array($userid));
		return empty($handles) ? null : $handles[$userid];
	}
651 652


Gideon Greenspan committed
653 654 655 656 657 658 659 660
	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';
Scott Vivian committed
661

Gideon Greenspan committed
662 663 664 665 666 667 668
		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);
		}
Scott Vivian committed
669

Gideon Greenspan committed
670 671 672 673 674
		$gothandleuserids=array();

		if ($exactonly) { // only take the exact matches
			foreach ($handles as $handle)
				$gothandleuserids[$handle]=@$rawhandleuserids[$handle];
Scott Vivian committed
675

Gideon Greenspan committed
676 677 678 679
		} 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;
Scott Vivian committed
680

Gideon Greenspan committed
681 682 683
			foreach ($handles as $handle)
				$gothandleuserids[$handle]=@$normhandleuserids[qa_string_remove_accents(qa_strtolower($handle))];
		}
Scott Vivian committed
684

Gideon Greenspan committed
685 686
		return $gothandleuserids;
	}
Scott Vivian committed
687 688


Gideon Greenspan committed
689
	function qa_handle_to_userid($handle)
Gideon Greenspan committed
690 691 692
/*
	Return the userid corresponding to $handle (not case- or accent-sensitive)
*/
Gideon Greenspan committed
693 694 695 696 697 698 699 700
	{
		if (QA_FINAL_EXTERNAL_USERS)
			$handleuserids=qa_get_userids_from_public(array($handle));

		else {
			require_once QA_INCLUDE_DIR.'qa-db-users.php';
			$handleuserids=qa_db_user_get_handle_userids(array($handle));
		}
Scott Vivian committed
701

Gideon Greenspan committed
702 703
		if (count($handleuserids)==1)
			return reset($handleuserids); // don't use $handleuserids[$handle] since capitalization might be different
Scott Vivian committed
704

Gideon Greenspan committed
705 706
		return null;
	}
Scott Vivian committed
707 708


Gideon Greenspan committed
709
	function qa_user_level_for_categories($categoryids)
Gideon Greenspan committed
710 711 712
/*
	Return the level of the logged in user for a post with $categoryids (expressing the full hierarchy to the final category)
*/
Gideon Greenspan committed
713 714 715 716 717 718
	{
		if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }

		require_once QA_INCLUDE_DIR.'qa-app-updates.php';

		$level=qa_get_logged_in_level();
Scott Vivian committed
719

Gideon Greenspan committed
720 721
		if (count($categoryids)) {
			$userlevels=qa_get_logged_in_levels();
Scott Vivian committed
722

Gideon Greenspan committed
723 724 725 726
			$categorylevels=array(); // create a map
			foreach ($userlevels as $userlevel)
				if ($userlevel['entitytype']==QA_ENTITY_CATEGORY)
					$categorylevels[$userlevel['entityid']]=$userlevel['level'];
Scott Vivian committed
727

Gideon Greenspan committed
728 729 730
			foreach ($categoryids as $categoryid)
				$level=max($level, @$categorylevels[$categoryid]);
		}
Scott Vivian committed
731

Gideon Greenspan committed
732 733
		return $level;
	}
Scott Vivian committed
734 735


Gideon Greenspan committed
736
	function qa_user_level_for_post($post)
Gideon Greenspan committed
737 738 739
/*
	Return the level of the logged in user for $post, as retrieved from the database
*/
Gideon Greenspan committed
740 741
	{
		if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
Scott Vivian committed
742

Gideon Greenspan committed
743 744 745 746 747
		if (strlen(@$post['categoryids']))
			return qa_user_level_for_categories(explode(',', $post['categoryids']));

		return null;
	}
Scott Vivian committed
748 749


Gideon Greenspan committed
750
	function qa_user_level_maximum()
Gideon Greenspan committed
751 752 753
/*
	Return the maximum possible level of the logged in user in any context (i.e. for any category)
*/
Gideon Greenspan committed
754 755
	{
		if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
Scott Vivian committed
756

Gideon Greenspan committed
757 758 759 760 761 762 763 764
		$level=qa_get_logged_in_level();

		$userlevels=qa_get_logged_in_levels();
		foreach ($userlevels as $userlevel)
			$level=max($level, $userlevel['level']);

		return $level;
	}
Scott Vivian committed
765

Gideon Greenspan committed
766 767

	function qa_user_post_permit_error($permitoption, $post, $limitaction=null, $checkblocks=true)
Gideon Greenspan committed
768 769 770 771
/*
	Check whether the logged in user has permission to perform $permitoption on post $post (from the database)
	Other parameters and the return value are as for qa_user_permit_error(...)
*/
Gideon Greenspan committed
772 773 774
	{
		return qa_user_permit_error($permitoption, $limitaction, qa_user_level_for_post($post), $checkblocks);
	}
Scott Vivian committed
775 776


Gideon Greenspan committed
777
	function qa_user_maximum_permit_error($permitoption, $limitaction=null, $checkblocks=true)
Gideon Greenspan committed
778 779 780 781
/*
	Check whether the logged in user would have permittion to perform $permitoption in any context (i.e. for any category)
	Other parameters and the return value are as for qa_user_permit_error(...)
*/
Gideon Greenspan committed
782 783 784
	{
		return qa_user_permit_error($permitoption, $limitaction, qa_user_level_maximum(), $checkblocks);
	}
Scott Vivian committed
785 786


Gideon Greenspan committed
787
	function qa_user_permit_error($permitoption=null, $limitaction=null, $userlevel=null, $checkblocks=true)
Gideon Greenspan committed
788 789 790
/*
	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
Gideon Greenspan committed
791 792 793
	against user or IP rate limits. You can pass in a QA_USER_LEVEL_* constant in $userlevel to consider the user at a
	different level to usual (e.g. if they are performing this action in a category for which they have elevated
	privileges). To ignore the user's blocked status, set $checkblocks to false.
Gideon Greenspan committed
794 795 796 797 798 799 800

	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
Gideon Greenspan committed
801
	'approve' => the user needs to be approved by the site admins
Gideon Greenspan committed
802 803 804 805
	'limit' => the user or IP address has reached a rate limit (if $limitaction specified)
	false => the operation can go ahead
*/
	{
Gideon Greenspan committed
806
		if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
Scott Vivian committed
807

Gideon Greenspan committed
808
		require_once QA_INCLUDE_DIR.'qa-app-limits.php';
Scott Vivian committed
809

Gideon Greenspan committed
810
		$userid=qa_get_logged_in_userid();
Gideon Greenspan committed
811 812 813
		if (!isset($userlevel))
			$userlevel=qa_get_logged_in_level();

Gideon Greenspan committed
814
		$flags=qa_get_logged_in_flags();
Gideon Greenspan committed
815 816
		if (!$checkblocks)
			$flags&=~QA_USER_FLAGS_USER_BLOCKED;
Gideon Greenspan committed
817

Gideon Greenspan committed
818
		$error=qa_permit_error($permitoption, $userid, $userlevel, $flags);
Scott Vivian committed
819

Gideon Greenspan committed
820
		if ($checkblocks && (!$error) && qa_is_ip_blocked())
Gideon Greenspan committed
821
			$error='ipblock';
Scott Vivian committed
822

Gideon Greenspan committed
823
		if ((!$error) && isset($userid) && ($flags & QA_USER_FLAGS_MUST_CONFIRM) && qa_opt('confirm_user_emails'))
Gideon Greenspan committed
824
			$error='confirm';
Scott Vivian committed
825

Gideon Greenspan committed
826 827
		if ((!$error) && isset($userid) && ($flags & QA_USER_FLAGS_MUST_APPROVE) && qa_opt('moderate_users'))
			$error='approve';
Scott Vivian committed
828

Gideon Greenspan committed
829
		if (isset($limitaction) && !$error)
Gideon Greenspan committed
830
			if (qa_user_limits_remaining($limitaction)<=0)
Gideon Greenspan committed
831
				$error='limit';
Scott Vivian committed
832

Gideon Greenspan committed
833 834
		return $error;
	}
Scott Vivian committed
835 836


Gideon Greenspan committed
837 838 839 840 841 842 843
	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.
*/
	{
Gideon Greenspan committed
844
		if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
Gideon Greenspan committed
845

Gideon Greenspan committed
846 847
		$permit=isset($permitoption) ? qa_opt($permitoption) : QA_PERMIT_ALL;

Gideon Greenspan committed
848 849
		if (isset($userid) && (($permit==QA_PERMIT_POINTS) || ($permit==QA_PERMIT_POINTS_CONFIRMED) || ($permit==QA_PERMIT_APPROVED_POINTS)) ) {
				// deal with points threshold by converting as appropriate
Scott Vivian committed
850

Gideon Greenspan committed
851 852
			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)
Scott Vivian committed
853

Gideon Greenspan committed
854
			if ($userpoints>=qa_opt($permitoption.'_points'))
Gideon Greenspan committed
855 856
				$permit=($permit==QA_PERMIT_APPROVED_POINTS) ? QA_PERMIT_APPROVED :
					(($permit==QA_PERMIT_POINTS_CONFIRMED) ? QA_PERMIT_CONFIRMED : QA_PERMIT_USERS); // convert if user has enough points
Gideon Greenspan committed
857
			else
Gideon Greenspan committed
858
				$permit=QA_PERMIT_EXPERTS; // otherwise show a generic message so they're not tempted to collect points just for this
Gideon Greenspan committed
859
		}
Scott Vivian committed
860

Gideon Greenspan committed
861 862
		return qa_permit_value_error($permit, $userid, $userlevel, $userflags);
	}
Scott Vivian committed
863 864


Gideon Greenspan committed
865 866 867 868 869 870
	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(...).
*/
	{
Gideon Greenspan committed
871
		if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
Scott Vivian committed
872

Gideon Greenspan committed
873 874
		if ($permit>=QA_PERMIT_ALL)
			$error=false;
Scott Vivian committed
875

Gideon Greenspan committed
876 877
		elseif ($permit>=QA_PERMIT_USERS)
			$error=isset($userid) ? false : 'login';
Scott Vivian committed
878

Gideon Greenspan committed
879 880 881
		elseif ($permit>=QA_PERMIT_CONFIRMED) {
			if (!isset($userid))
				$error='login';
Scott Vivian committed
882

Gideon Greenspan committed
883 884
			elseif (
				QA_FINAL_EXTERNAL_USERS || // not currently supported by single sign-on integration
Gideon Greenspan committed
885
				($userlevel>=QA_PERMIT_APPROVED) || // if user approved or assigned to a higher level, no need
Gideon Greenspan committed
886 887 888 889
				($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;
Scott Vivian committed
890

Gideon Greenspan committed
891 892 893
			else
				$error='confirm';

Gideon Greenspan committed
894 895 896
		} elseif ($permit>=QA_PERMIT_APPROVED) {
			if (!isset($userid))
				$error='login';
Scott Vivian committed
897

Gideon Greenspan committed
898 899 900 901 902
			elseif (
				($userlevel>=QA_USER_LEVEL_APPROVED) || // user has been approved
				(!qa_opt('moderate_users')) // if this option off, we can't ask it of the user
			)
				$error=false;
Scott Vivian committed
903

Gideon Greenspan committed
904 905
			else
				$error='approve';
Scott Vivian committed
906

Gideon Greenspan committed
907 908
		} elseif ($permit>=QA_PERMIT_EXPERTS)
			$error=(isset($userid) && ($userlevel>=QA_USER_LEVEL_EXPERT)) ? false : 'level';
Scott Vivian committed
909

Gideon Greenspan committed
910 911
		elseif ($permit>=QA_PERMIT_EDITORS)
			$error=(isset($userid) && ($userlevel>=QA_USER_LEVEL_EDITOR)) ? false : 'level';
Scott Vivian committed
912

Gideon Greenspan committed
913 914
		elseif ($permit>=QA_PERMIT_MODERATORS)
			$error=(isset($userid) && ($userlevel>=QA_USER_LEVEL_MODERATOR)) ? false : 'level';
Scott Vivian committed
915

Gideon Greenspan committed
916 917
		elseif ($permit>=QA_PERMIT_ADMINS)
			$error=(isset($userid) && ($userlevel>=QA_USER_LEVEL_ADMIN)) ? false : 'level';
Scott Vivian committed
918

Gideon Greenspan committed
919 920
		else
			$error=(isset($userid) && ($userlevel>=QA_USER_LEVEL_SUPER)) ? false : 'level';
Scott Vivian committed
921

Gideon Greenspan committed
922 923
		if (isset($userid) && ($userflags & QA_USER_FLAGS_USER_BLOCKED) && ($error!='level'))
			$error='userblock';
Scott Vivian committed
924

Gideon Greenspan committed
925 926
		return $error;
	}
Scott Vivian committed
927 928


Gideon Greenspan committed
929
	function qa_user_captcha_reason($userlevel=null)
Gideon Greenspan committed
930 931 932 933
/*
	Return whether a captcha is required for posts submitted by the current user. You can pass in a QA_USER_LEVEL_*
	constant in $userlevel to consider the user at a different level to usual (e.g. if they are performing this action
	in a category for which they have elevated privileges).
Scott Vivian committed
934

Gideon Greenspan committed
935 936 937 938 939 940
	Possible results:
	'login' => captcha required because the user is not logged in
	'approve' => captcha required because the user has not been approved
	'confirm' => captcha required because the user has not confirmed their email address
	false => captcha is not required
*/
Gideon Greenspan committed
941
	{
Gideon Greenspan committed
942
		if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
Scott Vivian committed
943

Gideon Greenspan committed
944 945 946
		$reason=false;
		if (!isset($userlevel))
			$userlevel=qa_get_logged_in_level();
Scott Vivian committed
947

Gideon Greenspan committed
948
		if ($userlevel < QA_USER_LEVEL_APPROVED) { // approved users and above aren't shown captchas
Gideon Greenspan committed
949
			$userid=qa_get_logged_in_userid();
Scott Vivian committed
950

Gideon Greenspan committed
951
			if (qa_opt('captcha_on_anon_post') && !isset($userid))
Gideon Greenspan committed
952 953 954
				$reason='login';
			elseif (qa_opt('moderate_users') && qa_opt('captcha_on_unapproved'))
				$reason='approve';
Gideon Greenspan committed
955
			elseif (qa_opt('confirm_user_emails') && qa_opt('captcha_on_unconfirmed') && !(qa_get_logged_in_flags() & QA_USER_FLAGS_EMAIL_CONFIRMED) )
Gideon Greenspan committed
956
				$reason='confirm';
Gideon Greenspan committed
957
		}
Scott Vivian committed
958

Gideon Greenspan committed
959
		return $reason;
Gideon Greenspan committed
960
	}
Gideon Greenspan committed
961

Scott Vivian committed
962

Gideon Greenspan committed
963 964
	function qa_user_use_captcha($userlevel=null)
/*
Gideon Greenspan committed
965 966
	Return whether a captcha should be presented to the logged in user for writing posts. You can pass in a
	QA_USER_LEVEL_* constant in $userlevel to consider the user at a different level to usual.
Gideon Greenspan committed
967 968 969
*/
	{
		if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
Scott Vivian committed
970

Gideon Greenspan committed
971 972
		return qa_user_captcha_reason($userlevel)!=false;
	}
Scott Vivian committed
973 974


Gideon Greenspan committed
975
	function qa_user_moderation_reason($userlevel=null)
Gideon Greenspan committed
976
/*
Gideon Greenspan committed
977 978 979
	Return whether moderation is required for posts submitted by the current user. You can pass in a QA_USER_LEVEL_*
constant in $userlevel to consider the user at a different level to usual (e.g. if they are performing this action
in a category for which they have elevated privileges).
Scott Vivian committed
980

Gideon Greenspan committed
981
	Possible results:
Gideon Greenspan committed
982
	'login' => moderation required because the user is not logged in
Gideon Greenspan committed
983
	'approve' => moderation required because the user has not been approved
Gideon Greenspan committed
984 985 986 987 988
	'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
*/
	{
Gideon Greenspan committed
989
		if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
Scott Vivian committed
990

Gideon Greenspan committed
991
		$reason=false;
Gideon Greenspan committed
992 993
		if (!isset($userlevel))
			$userlevel=qa_get_logged_in_level();
Scott Vivian committed
994

Gideon Greenspan committed
995
		if (
Gideon Greenspan committed
996
			($userlevel < QA_USER_LEVEL_EXPERT) && // experts and above aren't moderated
Gideon Greenspan committed
997 998 999
			qa_user_permit_error('permit_moderate') // if the user can approve posts, no point in moderating theirs
		) {
			$userid=qa_get_logged_in_userid();
Scott Vivian committed
1000

Gideon Greenspan committed
1001
			if (isset($userid)) {
Gideon Greenspan committed
1002 1003 1004
				if (qa_opt('moderate_users') && qa_opt('moderate_unapproved') && ($userlevel<QA_USER_LEVEL_APPROVED))
					$reason='approve';
				elseif (qa_opt('confirm_user_emails') && qa_opt('moderate_unconfirmed') && !(qa_get_logged_in_flags() & QA_USER_FLAGS_EMAIL_CONFIRMED) )
Gideon Greenspan committed
1005 1006 1007
					$reason='confirm';
				elseif (qa_opt('moderate_by_points') && (qa_get_logged_in_points() < qa_opt('moderate_points_limit')))
					$reason='points';
Scott Vivian committed
1008

Gideon Greenspan committed
1009
			} elseif (qa_opt('moderate_anon_post'))
Gideon Greenspan committed
1010 1011
				$reason='login';
		}
Scott Vivian committed
1012

Gideon Greenspan committed
1013 1014
		return $reason;
	}
Scott Vivian committed
1015 1016


Gideon Greenspan committed
1017 1018 1019 1020 1021 1022 1023
	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'];
Scott Vivian committed
1024

Gideon Greenspan committed
1025 1026 1027 1028 1029 1030 1031
		else {
			$defaultlabels=array(
				'name' => 'users/full_name',
				'about' => 'users/about',
				'location' => 'users/location',
				'website' => 'users/website',
			);
Scott Vivian committed
1032

Gideon Greenspan committed
1033 1034 1035
			if (isset($defaultlabels[$userfield['title']]))
				return qa_lang($defaultlabels[$userfield['title']]);
		}
Scott Vivian committed
1036

Gideon Greenspan committed
1037 1038
		return '';
	}
Gideon Greenspan committed
1039 1040 1041


	function qa_set_form_security_key()
Gideon Greenspan committed
1042 1043 1044
/*
	Set or extend the cookie in browser of non logged-in users which identifies them for the purposes of form security (anti-CSRF protection)
*/
Gideon Greenspan committed
1045 1046 1047 1048
	{
		if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }

		global $qa_form_key_cookie_set;
Scott Vivian committed
1049

Gideon Greenspan committed
1050 1051
		if ( (!qa_is_logged_in()) && !@$qa_form_key_cookie_set) {
			$qa_form_key_cookie_set=true;
Scott Vivian committed
1052

Gideon Greenspan committed
1053 1054 1055 1056
			if (strlen(@$_COOKIE['qa_key'])!=QA_FORM_KEY_LENGTH) {
				require_once QA_INCLUDE_DIR.'qa-util-string.php';
				$_COOKIE['qa_key']=qa_random_alphanum(QA_FORM_KEY_LENGTH);
			}
Scott Vivian committed
1057

Gideon Greenspan committed
1058 1059 1060
			setcookie('qa_key', $_COOKIE['qa_key'], time()+2*QA_FORM_EXPIRY_SECS, '/', QA_COOKIE_DOMAIN); // extend on every page request
		}
	}
Scott Vivian committed
1061 1062


Gideon Greenspan committed
1063
	function qa_calc_form_security_hash($action, $timestamp)
Gideon Greenspan committed
1064 1065 1066 1067
/*
	Return the form security (anti-CSRF protection) hash for an $action (any string), that can be performed within
	QA_FORM_EXPIRY_SECS of $timestamp (in unix seconds) by the current user.
*/
Gideon Greenspan committed
1068 1069
	{
		if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
Scott Vivian committed
1070

Gideon Greenspan committed
1071
		$salt=qa_opt('form_security_salt');
Scott Vivian committed
1072

Gideon Greenspan committed
1073 1074 1075 1076 1077
		if (qa_is_logged_in())
			return sha1($salt.'/'.$action.'/'.$timestamp.'/'.qa_get_logged_in_userid().'/'.qa_get_logged_in_user_field('passsalt'));
		else
			return sha1($salt.'/'.$action.'/'.$timestamp.'/'.@$_COOKIE['qa_key']); // lower security for non logged in users - code+cookie can be transferred
	}
Scott Vivian committed
1078 1079


Gideon Greenspan committed
1080
	function qa_get_form_security_code($action)
Gideon Greenspan committed
1081 1082 1083 1084
/*
	Return the full form security (anti-CSRF protection) code for an $action (any string) performed within
	QA_FORM_EXPIRY_SECS of now by the current user.
*/
Gideon Greenspan committed
1085 1086
	{
		if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
Scott Vivian committed
1087

Gideon Greenspan committed
1088
		qa_set_form_security_key();
Scott Vivian committed
1089

Gideon Greenspan committed
1090
		$timestamp=qa_opt('db_time');
Scott Vivian committed
1091

Gideon Greenspan committed
1092 1093
		return (int)qa_is_logged_in().'-'.$timestamp.'-'.qa_calc_form_security_hash($action, $timestamp);
	}
Scott Vivian committed
1094 1095


Gideon Greenspan committed
1096
	function qa_check_form_security_code($action, $value)
Gideon Greenspan committed
1097 1098 1099 1100
/*
	Return whether $value matches the expected form security (anti-CSRF protection) code for $action (any string) and
	that the code has not expired (if more than QA_FORM_EXPIRY_SECS have passed). Logs causes for suspicion.
*/
Gideon Greenspan committed
1101 1102
	{
		if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
Scott Vivian committed
1103

Gideon Greenspan committed
1104 1105
		$reportproblems=array();
		$silentproblems=array();
Scott Vivian committed
1106

Gideon Greenspan committed
1107 1108 1109 1110 1111 1112 1113 1114
		if (!isset($value))
			$silentproblems[]='code missing';

		else if (!strlen($value))
			$silentproblems[]='code empty';

		else {
			$parts=explode('-', $value);
Scott Vivian committed
1115

Gideon Greenspan committed
1116 1117 1118 1119 1120
			if (count($parts)==3) {
				$loggedin=$parts[0];
				$timestamp=$parts[1];
				$hash=$parts[2];
				$timenow=qa_opt('db_time');
Scott Vivian committed
1121

Gideon Greenspan committed
1122 1123 1124 1125
				if ($timestamp>$timenow)
					$reportproblems[]='time '.($timestamp-$timenow).'s in future';
				elseif ($timestamp<($timenow-QA_FORM_EXPIRY_SECS))
					$silentproblems[]='timeout after '.($timenow-$timestamp).'s';
Scott Vivian committed
1126

Gideon Greenspan committed
1127 1128 1129
				if (qa_is_logged_in()) {
					if (!$loggedin)
						$silentproblems[]='now logged in';
Scott Vivian committed
1130

Gideon Greenspan committed
1131 1132 1133 1134 1135 1136
				} else {
					if ($loggedin)
						$silentproblems[]='now logged out';

					else {
						$key=@$_COOKIE['qa_key'];
Scott Vivian committed
1137

Gideon Greenspan committed
1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153
						if (!isset($key))
							$silentproblems[]='key cookie missing';
						elseif (!strlen($key))
							$silentproblems[]='key cookie empty';
						else if (strlen($key)!=QA_FORM_KEY_LENGTH)
							$reportproblems[]='key cookie '.$key.' invalid';
					}
				}

				if (empty($silentproblems) && empty($reportproblems))
					if (strtolower(qa_calc_form_security_hash($action, $timestamp))!=strtolower($hash))
						$reportproblems[]='code mismatch';

			} else
				$reportproblems[]='code '.$value.' malformed';
		}
Scott Vivian committed
1154

Gideon Greenspan committed
1155 1156 1157 1158 1159 1160 1161 1162
		if (count($reportproblems))
			@error_log(
				'PHP Question2Answer form security violation for '.$action.
				' by '.(qa_is_logged_in() ? ('userid '.qa_get_logged_in_userid()) : 'anonymous').
				' ('.implode(', ', array_merge($reportproblems, $silentproblems)).')'.
				' on '.@$_SERVER['REQUEST_URI'].
				' via '.@$_SERVER['HTTP_REFERER']
			);
Scott Vivian committed
1163

Gideon Greenspan committed
1164 1165 1166
		return (empty($silentproblems) && empty($reportproblems));
	}

Gideon Greenspan committed
1167 1168 1169

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