1<?php
2/**
3 * MyBB 1.8
4 * Copyright 2014 MyBB Group, All Rights Reserved
5 *
6 * Website: http://www.mybb.com
7 * License: http://www.mybb.com/about/license
8 *
9 */
10
11/**
12 * Checks if a user with uid $uid exists in the database.
13 *
14 * @param int $uid The uid to check for.
15 * @return boolean True when exists, false when not.
16 */
17function user_exists($uid)
18{
19	global $db;
20
21	$query = $db->simple_select("users", "COUNT(*) as user", "uid='".(int)$uid."'", array('limit' => 1));
22	if($db->fetch_field($query, 'user') == 1)
23	{
24		return true;
25	}
26	else
27	{
28		return false;
29	}
30}
31
32/**
33 * Checks if $username already exists in the database.
34 *
35 * @param string $username The username for check for.
36 * @return boolean True when exists, false when not.
37 */
38function username_exists($username)
39{
40	$options = array(
41		'username_method' => 2
42	);
43
44	return (bool)get_user_by_username($username, $options);
45}
46
47/**
48 * Checks a password with a supplied username.
49 *
50 * @param string $username The username of the user.
51 * @param string $password The plain-text password.
52 * @return boolean|array False when no match, array with user info when match.
53 */
54function validate_password_from_username($username, $password)
55{
56	global $mybb;
57
58	$options = array(
59		'fields' => '*',
60		'username_method' => $mybb->settings['username_method'],
61	);
62
63	$user = get_user_by_username($username, $options);
64
65	if(!$user['uid'])
66	{
67		return false;
68	}
69
70	return validate_password_from_uid($user['uid'], $password, $user);
71}
72
73/**
74 * Checks a password with a supplied uid.
75 *
76 * @param int $uid The user id.
77 * @param string $password The plain-text password.
78 * @param array $user An optional user data array.
79 * @return boolean|array False when not valid, user data array when valid.
80 */
81function validate_password_from_uid($uid, $password, $user = array())
82{
83	global $db, $mybb;
84	if(isset($mybb->user['uid']) && $mybb->user['uid'] == $uid)
85	{
86		$user = $mybb->user;
87	}
88	if(!$user['password'])
89	{
90		$user = get_user($uid);
91	}
92	if(!$user['salt'])
93	{
94		// Generate a salt for this user and assume the password stored in db is a plain md5 password
95		$password_fields = create_password($user['password'], false, $user);
96		$db->update_query("users", $password_fields, "uid='".$user['uid']."'");
97	}
98
99	if(!$user['loginkey'])
100	{
101		$user['loginkey'] = generate_loginkey();
102		$sql_array = array(
103			"loginkey" => $user['loginkey']
104		);
105		$db->update_query("users", $sql_array, "uid = ".$user['uid']);
106	}
107	if(verify_user_password($user, $password))
108	{
109		return $user;
110	}
111	else
112	{
113		return false;
114	}
115}
116
117/**
118 * Updates a user's password.
119 *
120 * @param int $uid The user's id.
121 * @param string $password The md5()'ed password.
122 * @param string $salt (Optional) The salt of the user.
123 * @return array The new password.
124 * @deprecated deprecated since version 1.8.6 Please use other alternatives.
125 */
126function update_password($uid, $password, $salt="")
127{
128	global $db, $plugins;
129
130	$newpassword = array();
131
132	// If no salt was specified, check in database first, if still doesn't exist, create one
133	if(!$salt)
134	{
135		$query = $db->simple_select("users", "salt", "uid='$uid'");
136		$user = $db->fetch_array($query);
137		if($user['salt'])
138		{
139			$salt = $user['salt'];
140		}
141		else
142		{
143			$salt = generate_salt();
144		}
145		$newpassword['salt'] = $salt;
146	}
147
148	// Create new password based on salt
149	$saltedpw = salt_password($password, $salt);
150
151	// Generate new login key
152	$loginkey = generate_loginkey();
153
154	// Update password and login key in database
155	$newpassword['password'] = $saltedpw;
156	$newpassword['loginkey'] = $loginkey;
157	$db->update_query("users", $newpassword, "uid='$uid'");
158
159	$plugins->run_hooks("password_changed");
160
161	return $newpassword;
162}
163
164/**
165 * Salts a password based on a supplied salt.
166 *
167 * @param string $password The md5()'ed password.
168 * @param string $salt The salt.
169 * @return string The password hash.
170 * @deprecated deprecated since version 1.8.9 Please use other alternatives.
171 */
172function salt_password($password, $salt)
173{
174	return md5(md5($salt).$password);
175}
176
177/**
178 * Salts a password based on a supplied salt.
179 *
180 * @param string $password The input password.
181 * @param string $salt (Optional) The salt used by the MyBB algorithm.
182 * @param string $user (Optional) An array containing password-related data.
183 * @return array Password-related fields.
184 */
185function create_password($password, $salt = false, $user = false)
186{
187	global $plugins;
188
189	$fields = null;
190
191	$parameters = compact('password', 'salt', 'user', 'fields');
192
193	if(!defined('IN_INSTALL') && !defined('IN_UPGRADE'))
194	{
195		$plugins->run_hooks('create_password', $parameters);
196	}
197
198	if(!is_null($parameters['fields']))
199	{
200		$fields = $parameters['fields'];
201	}
202	else
203	{
204		if(!$salt)
205		{
206			$salt = generate_salt();
207		}
208
209		$hash = md5(md5($salt).md5($password));
210
211		$fields = array(
212			'salt' => $salt,
213			'password' => $hash,
214		);
215	}
216
217	return $fields;
218}
219
220/**
221 * Compares user's password data against provided input.
222 *
223 * @param array $user An array containing password-related data.
224 * @param string $password The plain-text input password.
225 * @return bool Result of the comparison.
226 */
227function verify_user_password($user, $password)
228{
229	global $plugins;
230
231	$result = null;
232
233	$parameters = compact('user', 'password', 'result');
234
235	if(!defined('IN_INSTALL') && !defined('IN_UPGRADE'))
236	{
237		$plugins->run_hooks('verify_user_password', $parameters);
238	}
239
240	if(!is_null($parameters['result']))
241	{
242		return $parameters['result'];
243	}
244	else
245	{
246		$password_fields = create_password($password, $user['salt'], $user);
247
248		return my_hash_equals($user['password'], $password_fields['password']);
249	}
250}
251
252/**
253 * Generates a random salt
254 *
255 * @return string The salt.
256 */
257function generate_salt()
258{
259	return random_str(8);
260}
261
262/**
263 * Generates a 50 character random login key.
264 *
265 * @return string The login key.
266 */
267function generate_loginkey()
268{
269	return random_str(50);
270}
271
272/**
273 * Updates a user's salt in the database (does not update a password).
274 *
275 * @param int $uid The uid of the user to update.
276 * @return string The new salt.
277 */
278function update_salt($uid)
279{
280	global $db;
281
282	$salt = generate_salt();
283	$sql_array = array(
284		"salt" => $salt
285	);
286	$db->update_query("users", $sql_array, "uid='{$uid}'");
287
288	return $salt;
289}
290
291/**
292 * Generates a new login key for a user.
293 *
294 * @param int $uid The uid of the user to update.
295 * @return string The new login key.
296 */
297function update_loginkey($uid)
298{
299	global $db;
300
301	$loginkey = generate_loginkey();
302	$sql_array = array(
303		"loginkey" => $loginkey
304	);
305	$db->update_query("users", $sql_array, "uid='{$uid}'");
306
307	return $loginkey;
308
309}
310
311/**
312 * Adds a thread to a user's thread subscription list.
313 * If no uid is supplied, the currently logged in user's id will be used.
314 *
315 * @param int $tid The tid of the thread to add to the list.
316 * @param int $notification (Optional) The type of notification to receive for replies (0=none, 1=email, 2=pm)
317 * @param int $uid (Optional) The uid of the user who's list to update.
318 * @return boolean True when success, false when otherwise.
319 */
320function add_subscribed_thread($tid, $notification=1, $uid=0)
321{
322	global $mybb, $db;
323
324	if(!$uid)
325	{
326		$uid = $mybb->user['uid'];
327	}
328
329	if(!$uid)
330	{
331		return false;
332	}
333
334	$query = $db->simple_select("threadsubscriptions", "*", "tid='".(int)$tid."' AND uid='".(int)$uid."'");
335	$subscription = $db->fetch_array($query);
336	if(empty($subscription) || !$subscription['tid'])
337	{
338		$insert_array = array(
339			'uid' => (int)$uid,
340			'tid' => (int)$tid,
341			'notification' => (int)$notification,
342			'dateline' => TIME_NOW
343		);
344		$db->insert_query("threadsubscriptions", $insert_array);
345	}
346	else
347	{
348		// Subscription exists - simply update notification
349		$update_array = array(
350			"notification" => (int)$notification
351		);
352		$db->update_query("threadsubscriptions", $update_array, "uid='{$uid}' AND tid='{$tid}'");
353	}
354	return true;
355}
356
357/**
358 * Remove a thread from a user's thread subscription list.
359 * If no uid is supplied, the currently logged in user's id will be used.
360 *
361 * @param int $tid The tid of the thread to remove from the list.
362 * @param int $uid (Optional) The uid of the user who's list to update.
363 * @return boolean True when success, false when otherwise.
364 */
365function remove_subscribed_thread($tid, $uid=0)
366{
367	global $mybb, $db;
368
369	if(!$uid)
370	{
371		$uid = $mybb->user['uid'];
372	}
373
374	if(!$uid)
375	{
376		return false;
377	}
378	$db->delete_query("threadsubscriptions", "tid='".$tid."' AND uid='{$uid}'");
379
380	return true;
381}
382
383/**
384 * Adds a forum to a user's forum subscription list.
385 * If no uid is supplied, the currently logged in user's id will be used.
386 *
387 * @param int $fid The fid of the forum to add to the list.
388 * @param int $uid (Optional) The uid of the user who's list to update.
389 * @return boolean True when success, false when otherwise.
390 */
391function add_subscribed_forum($fid, $uid=0)
392{
393	global $mybb, $db;
394
395	if(!$uid)
396	{
397		$uid = $mybb->user['uid'];
398	}
399
400	if(!$uid)
401	{
402		return false;
403	}
404
405	$fid = (int)$fid;
406	$uid = (int)$uid;
407
408	$query = $db->simple_select("forumsubscriptions", "*", "fid='".$fid."' AND uid='{$uid}'", array('limit' => 1));
409	$fsubscription = $db->fetch_array($query);
410	if(empty($fsubscription) || !$fsubscription['fid'])
411	{
412		$insert_array = array(
413			'fid' => $fid,
414			'uid' => $uid
415		);
416		$db->insert_query("forumsubscriptions", $insert_array);
417	}
418
419	return true;
420}
421
422/**
423 * Removes a forum from a user's forum subscription list.
424 * If no uid is supplied, the currently logged in user's id will be used.
425 *
426 * @param int $fid The fid of the forum to remove from the list.
427 * @param int $uid (Optional) The uid of the user who's list to update.
428 * @return boolean True when success, false when otherwise.
429 */
430function remove_subscribed_forum($fid, $uid=0)
431{
432	global $mybb, $db;
433
434	if(!$uid)
435	{
436		$uid = $mybb->user['uid'];
437	}
438
439	if(!$uid)
440	{
441		return false;
442	}
443	$db->delete_query("forumsubscriptions", "fid='".$fid."' AND uid='{$uid}'");
444
445	return true;
446}
447
448/**
449 * Constructs the usercp navigation menu.
450 *
451 */
452function usercp_menu()
453{
454	global $mybb, $templates, $theme, $plugins, $lang, $usercpnav, $usercpmenu;
455
456	$lang->load("usercpnav");
457
458	// Add the default items as plugins with separated priorities of 10
459	if($mybb->settings['enablepms'] != 0 && $mybb->usergroup['canusepms'] == 1)
460	{
461		$plugins->add_hook("usercp_menu", "usercp_menu_messenger", 10);
462	}
463
464	if($mybb->usergroup['canusercp'] == 1)
465	{
466		$plugins->add_hook("usercp_menu", "usercp_menu_profile", 20);
467		$plugins->add_hook("usercp_menu", "usercp_menu_misc", 30);
468	}
469
470	// Run the plugin hooks
471	$plugins->run_hooks("usercp_menu");
472	global $usercpmenu;
473
474	if($mybb->usergroup['canusercp'] == 1)
475	{
476		eval("\$ucp_nav_home = \"".$templates->get("usercp_nav_home")."\";");
477	}
478
479	eval("\$usercpnav = \"".$templates->get("usercp_nav")."\";");
480
481	$plugins->run_hooks("usercp_menu_built");
482}
483
484/**
485 * Constructs the usercp messenger menu.
486 *
487 */
488function usercp_menu_messenger()
489{
490	global $db, $mybb, $templates, $theme, $usercpmenu, $lang, $collapse, $collapsed, $collapsedimg;
491
492	$expaltext = (in_array("usercppms", $collapse)) ? $lang->expcol_expand : $lang->expcol_collapse;
493	$usercp_nav_messenger = $templates->get("usercp_nav_messenger");
494	// Hide tracking link if no permission
495	$tracking = '';
496	if($mybb->usergroup['cantrackpms'])
497	{
498		$tracking = $templates->get("usercp_nav_messenger_tracking");
499	}
500	eval("\$ucp_nav_tracking = \"". $tracking ."\";");
501
502	// Hide compose link if no permission
503	$ucp_nav_compose = '';
504	if($mybb->usergroup['cansendpms'] == 1)
505	{
506		eval("\$ucp_nav_compose = \"".$templates->get("usercp_nav_messenger_compose")."\";");
507	}
508
509	$folderlinks = $folder_id = $folder_name = '';
510	$foldersexploded = explode("$%%$", $mybb->user['pmfolders']);
511	foreach($foldersexploded as $key => $folders)
512	{
513		$folderinfo = explode("**", $folders, 2);
514		$folderinfo[1] = get_pm_folder_name($folderinfo[0], $folderinfo[1]);
515		if($folderinfo[0] == 4)
516		{
517			$class = "usercp_nav_trash_pmfolder";
518		}
519		else if($folderlinks)
520		{
521			$class = "usercp_nav_sub_pmfolder";
522		}
523		else
524		{
525			$class = "usercp_nav_pmfolder";
526		}
527
528		$folder_id = $folderinfo[0];
529		$folder_name = $folderinfo[1];
530
531		eval("\$folderlinks .= \"".$templates->get("usercp_nav_messenger_folder")."\";");
532	}
533
534	if(!isset($collapsedimg['usercppms']))
535	{
536		$collapsedimg['usercppms'] = '';
537	}
538
539	if(!isset($collapsed['usercppms_e']))
540	{
541		$collapsed['usercppms_e'] = '';
542	}
543
544	eval("\$usercpmenu .= \"".$usercp_nav_messenger."\";");
545}
546
547/**
548 * Constructs the usercp profile menu.
549 *
550 */
551function usercp_menu_profile()
552{
553	global $db, $mybb, $templates, $theme, $usercpmenu, $lang, $collapse, $collapsed, $collapsedimg;
554
555	$changenameop = '';
556	if($mybb->usergroup['canchangename'] != 0)
557	{
558		eval("\$changenameop = \"".$templates->get("usercp_nav_changename")."\";");
559	}
560
561	$changesigop = '';
562	if($mybb->usergroup['canusesig'] == 1 && ($mybb->usergroup['canusesigxposts'] == 0 || $mybb->usergroup['canusesigxposts'] > 0 && $mybb->user['postnum'] > $mybb->usergroup['canusesigxposts']))
563	{
564		if($mybb->user['suspendsignature'] == 0 || $mybb->user['suspendsignature'] == 1 && $mybb->user['suspendsigtime'] > 0 && $mybb->user['suspendsigtime'] < TIME_NOW)
565		{
566			eval("\$changesigop = \"".$templates->get("usercp_nav_editsignature")."\";");
567		}
568	}
569
570	if(!isset($collapsedimg['usercpprofile']))
571	{
572		$collapsedimg['usercpprofile'] = '';
573	}
574
575	if(!isset($collapsed['usercpprofile_e']))
576	{
577		$collapsed['usercpprofile_e'] = '';
578	}
579
580	$expaltext = (in_array("usercpprofile", $collapse)) ? $lang->expcol_expand : $lang->expcol_collapse;
581	eval("\$usercpmenu .= \"".$templates->get("usercp_nav_profile")."\";");
582}
583
584/**
585 * Constructs the usercp misc menu.
586 *
587 */
588function usercp_menu_misc()
589{
590	global $db, $mybb, $templates, $theme, $usercpmenu, $lang, $collapse, $collapsed, $collapsedimg;
591
592	$draftstart = $draftend = '';
593	$draftcount = $lang->ucp_nav_drafts;
594
595	$query = $db->simple_select("posts", "COUNT(pid) AS draftcount", "visible = '-2' AND uid = '{$mybb->user['uid']}'");
596	$count = $db->fetch_field($query, 'draftcount');
597
598	if($count > 0)
599	{
600		$draftcount = $lang->sprintf($lang->ucp_nav_drafts_active, my_number_format($count));
601	}
602
603	if($mybb->settings['enableattachments'] != 0)
604	{
605		eval("\$attachmentop = \"".$templates->get("usercp_nav_attachments")."\";");
606	}
607
608	if(!isset($collapsedimg['usercpmisc']))
609	{
610		$collapsedimg['usercpmisc'] = '';
611	}
612
613	if(!isset($collapsed['usercpmisc_e']))
614	{
615		$collapsed['usercpmisc_e'] = '';
616	}
617
618	$profile_link = get_profile_link($mybb->user['uid']);
619	$expaltext = (in_array("usercpmisc", $collapse)) ? $lang->expcol_expand : $lang->expcol_collapse;
620	eval("\$usercpmenu .= \"".$templates->get("usercp_nav_misc")."\";");
621}
622
623/**
624 * Gets the usertitle for a specific uid.
625 *
626 * @param int $uid The uid of the user to get the usertitle of.
627 * @return string The usertitle of the user.
628 */
629function get_usertitle($uid=0)
630{
631	global $db, $mybb;
632
633	if($mybb->user['uid'] == $uid)
634	{
635		$user = $mybb->user;
636	}
637	else
638	{
639		$query = $db->simple_select("users", "usertitle,postnum", "uid='$uid'", array('limit' => 1));
640		$user = $db->fetch_array($query);
641	}
642
643	if($user['usertitle'])
644	{
645		return $user['usertitle'];
646	}
647	else
648	{
649		$usertitles = $mybb->cache->read('usertitles');
650		foreach($usertitles as $title)
651		{
652			if($title['posts'] <= $user['postnum'])
653			{
654				$usertitle = $title;
655				break;
656			}
657		}
658
659		return $usertitle['title'];
660	}
661}
662
663/**
664 * Updates a users private message count in the users table with the number of pms they have.
665 *
666 * @param int $uid The user id to update the count for. If none, assumes currently logged in user.
667 * @param int $count_to_update Bitwise value for what to update. 1 = total, 2 = new, 4 = unread. Combinations accepted.
668 * @return array The updated counters
669 */
670function update_pm_count($uid=0, $count_to_update=7)
671{
672	global $db, $mybb;
673
674	// If no user id, assume that we mean the current logged in user.
675	if((int)$uid == 0)
676	{
677		$uid = $mybb->user['uid'];
678	}
679
680	$uid = (int)$uid;
681	$pmcount = array();
682	if($uid == 0)
683	{
684		return $pmcount;
685	}
686
687	// Update total number of messages.
688	if($count_to_update & 1)
689	{
690		$query = $db->simple_select("privatemessages", "COUNT(pmid) AS pms_total", "uid='".$uid."'");
691		$total = $db->fetch_array($query);
692		$pmcount['totalpms'] = $total['pms_total'];
693	}
694
695	// Update number of unread messages.
696	if($count_to_update & 2 && $db->field_exists("unreadpms", "users") == true)
697	{
698		$query = $db->simple_select("privatemessages", "COUNT(pmid) AS pms_unread", "uid='".$uid."' AND status='0' AND folder='1'");
699		$unread = $db->fetch_array($query);
700		$pmcount['unreadpms'] = $unread['pms_unread'];
701	}
702
703	if(!empty($pmcount))
704	{
705		$db->update_query("users", $pmcount, "uid='".$uid."'");
706	}
707	return $pmcount;
708}
709
710/**
711 * Return the language specific name for a PM folder.
712 *
713 * @param int $fid The ID of the folder.
714 * @param string $name The folder name - can be blank, will use language default.
715 * @return string The name of the folder.
716 */
717function get_pm_folder_name($fid, $name="")
718{
719	global $lang;
720
721	if($name != '')
722	{
723		return $name;
724	}
725
726	switch($fid)
727	{
728		case 0:
729			return $lang->folder_inbox;
730			break;
731		case 1:
732			return $lang->folder_unread;
733			break;
734		case 2:
735			return $lang->folder_sent_items;
736			break;
737		case 3:
738			return $lang->folder_drafts;
739			break;
740		case 4:
741			return $lang->folder_trash;
742			break;
743		default:
744			return $lang->folder_untitled;
745	}
746}
747
748/**
749 * Generates a security question for registration.
750 *
751 * @param int $old_qid Optional ID of the old question.
752 * @return string The question session id.
753 */
754function generate_question($old_qid=0)
755{
756	global $db;
757
758	if($db->type == 'pgsql' || $db->type == 'sqlite')
759	{
760		$order_by = 'RANDOM()';
761	}
762	else
763	{
764		$order_by = 'RAND()';
765	}
766
767	$excl_old = '';
768	if($old_qid)
769	{
770		$excl_old = ' AND qid != '.(int)$old_qid;
771	}
772
773	$query = $db->simple_select('questions', 'qid, shown', "active=1{$excl_old}", array('limit' => 1, 'order_by' => $order_by));
774	$question = $db->fetch_array($query);
775
776	if(!$db->num_rows($query))
777	{
778		// No active questions exist
779		return false;
780	}
781	else
782	{
783		$sessionid = random_str(32);
784
785		$sql_array = array(
786			"sid" => $sessionid,
787			"qid" => $question['qid'],
788			"dateline" => TIME_NOW
789		);
790		$db->insert_query("questionsessions", $sql_array);
791
792		$update_question = array(
793			"shown" => $question['shown'] + 1
794		);
795		$db->update_query("questions", $update_question, "qid = '{$question['qid']}'");
796
797		return $sessionid;
798	}
799}
800
801/**
802 * Check whether we can show the Purge Spammer Feature
803 *
804 * @param int $post_count The users post count
805 * @param int $usergroup The usergroup of our user
806 * @param int $uid The uid of our user
807 * @return boolean Whether or not to show the feature
808 */
809function purgespammer_show($post_count, $usergroup, $uid)
810{
811		global $mybb, $cache;
812
813		// only show this if the current user has permission to use it and the user has less than the post limit for using this tool
814		$bangroup = $mybb->settings['purgespammerbangroup'];
815		$usergroups = $cache->read('usergroups');
816
817		return ($mybb->user['uid'] != $uid && is_member($mybb->settings['purgespammergroups']) && !is_super_admin($uid)
818			&& !$usergroups[$usergroup]['cancp'] && !$usergroups[$usergroup]['canmodcp'] && !$usergroups[$usergroup]['issupermod']
819			&& (str_replace($mybb->settings['thousandssep'], '', $post_count) <= $mybb->settings['purgespammerpostlimit'] || $mybb->settings['purgespammerpostlimit'] == 0)
820			&& !is_member($bangroup, $uid) && !$usergroups[$usergroup]['isbannedgroup']);
821}
822