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 * Outputs a page directly to the browser, parsing anything which needs to be parsed.
13 *
14 * @param string $contents The contents of the page.
15 */
16function output_page($contents)
17{
18	global $db, $lang, $theme, $templates, $plugins, $mybb;
19	global $debug, $templatecache, $templatelist, $maintimer, $globaltime, $parsetime;
20
21	$contents = $plugins->run_hooks("pre_parse_page", $contents);
22	$contents = parse_page($contents);
23	$totaltime = format_time_duration($maintimer->stop());
24	$contents = $plugins->run_hooks("pre_output_page", $contents);
25
26	if($mybb->usergroup['cancp'] == 1 || $mybb->dev_mode == 1)
27	{
28		if($mybb->settings['extraadmininfo'] != 0)
29		{
30			$phptime = $maintimer->totaltime - $db->query_time;
31			$query_time = $db->query_time;
32
33			if($maintimer->totaltime > 0)
34			{
35				$percentphp = number_format((($phptime/$maintimer->totaltime) * 100), 2);
36				$percentsql = number_format((($query_time/$maintimer->totaltime) * 100), 2);
37			}
38			else
39			{
40				// if we've got a super fast script...  all we can do is assume something
41				$percentphp = 0;
42				$percentsql = 0;
43			}
44
45			$serverload = get_server_load();
46
47			if(my_strpos(getenv("REQUEST_URI"), "?"))
48			{
49				$debuglink = htmlspecialchars_uni(getenv("REQUEST_URI")) . "&amp;debug=1";
50			}
51			else
52			{
53				$debuglink = htmlspecialchars_uni(getenv("REQUEST_URI")) . "?debug=1";
54			}
55
56			$memory_usage = get_memory_usage();
57
58			if($memory_usage)
59			{
60				$memory_usage = $lang->sprintf($lang->debug_memory_usage, get_friendly_size($memory_usage));
61			}
62			else
63			{
64				$memory_usage = '';
65			}
66			// MySQLi is still MySQL, so present it that way to the user
67			$database_server = $db->short_title;
68
69			if($database_server == 'MySQLi')
70			{
71				$database_server = 'MySQL';
72			}
73			$generated_in = $lang->sprintf($lang->debug_generated_in, $totaltime);
74			$debug_weight = $lang->sprintf($lang->debug_weight, $percentphp, $percentsql, $database_server);
75			$sql_queries = $lang->sprintf($lang->debug_sql_queries, $db->query_count);
76			$server_load = $lang->sprintf($lang->debug_server_load, $serverload);
77
78			eval("\$debugstuff = \"".$templates->get("debug_summary")."\";");
79			$contents = str_replace("<debugstuff>", $debugstuff, $contents);
80		}
81
82		if($mybb->debug_mode == true)
83		{
84			debug_page();
85		}
86	}
87
88	$contents = str_replace("<debugstuff>", "", $contents);
89
90	if($mybb->settings['gzipoutput'] == 1)
91	{
92		$contents = gzip_encode($contents, $mybb->settings['gziplevel']);
93	}
94
95	@header("Content-type: text/html; charset={$lang->settings['charset']}");
96
97	echo $contents;
98
99	$plugins->run_hooks("post_output_page");
100}
101
102/**
103 * Adds a function or class to the list of code to run on shutdown.
104 *
105 * @param string|array $name The name of the function.
106 * @param mixed $arguments Either an array of arguments for the function or one argument
107 * @return boolean True if function exists, otherwise false.
108 */
109function add_shutdown($name, $arguments=array())
110{
111	global $shutdown_functions;
112
113	if(!is_array($shutdown_functions))
114	{
115		$shutdown_functions = array();
116	}
117
118	if(!is_array($arguments))
119	{
120		$arguments = array($arguments);
121	}
122
123	if(is_array($name) && method_exists($name[0], $name[1]))
124	{
125		$shutdown_functions[] = array('function' => $name, 'arguments' => $arguments);
126		return true;
127	}
128	else if(!is_array($name) && function_exists($name))
129	{
130		$shutdown_functions[] = array('function' => $name, 'arguments' => $arguments);
131		return true;
132	}
133
134	return false;
135}
136
137/**
138 * Runs the shutdown items after the page has been sent to the browser.
139 *
140 */
141function run_shutdown()
142{
143	global $config, $db, $cache, $plugins, $error_handler, $shutdown_functions, $shutdown_queries, $done_shutdown, $mybb;
144
145	if($done_shutdown == true || !$config || (isset($error_handler) && $error_handler->has_errors))
146	{
147		return;
148	}
149
150	if(empty($shutdown_queries) && empty($shutdown_functions))
151	{
152		// Nothing to do
153		return;
154	}
155
156	// Missing the core? Build
157	if(!is_object($mybb))
158	{
159		require_once MYBB_ROOT."inc/class_core.php";
160		$mybb = new MyBB;
161
162		// Load the settings
163		require MYBB_ROOT."inc/settings.php";
164		$mybb->settings = &$settings;
165	}
166
167	// If our DB has been deconstructed already (bad PHP 5.2.0), reconstruct
168	if(!is_object($db))
169	{
170		if(!isset($config) || empty($config['database']['type']))
171		{
172			require MYBB_ROOT."inc/config.php";
173		}
174
175		if(isset($config))
176		{
177			// Load DB interface
178			require_once MYBB_ROOT."inc/db_base.php";
179			require_once MYBB_ROOT . 'inc/AbstractPdoDbDriver.php';
180
181			require_once MYBB_ROOT."inc/db_".$config['database']['type'].".php";
182			switch($config['database']['type'])
183			{
184				case "sqlite":
185					$db = new DB_SQLite;
186					break;
187				case "pgsql":
188					$db = new DB_PgSQL;
189					break;
190				case "pgsql_pdo":
191					$db = new PostgresPdoDbDriver();
192					break;
193				case "mysqli":
194					$db = new DB_MySQLi;
195					break;
196				case "mysql_pdo":
197					$db = new MysqlPdoDbDriver();
198					break;
199				default:
200					$db = new DB_MySQL;
201			}
202
203			$db->connect($config['database']);
204			if(!defined("TABLE_PREFIX"))
205			{
206				define("TABLE_PREFIX", $config['database']['table_prefix']);
207			}
208			$db->set_table_prefix(TABLE_PREFIX);
209		}
210	}
211
212	// Cache object deconstructed? reconstruct
213	if(!is_object($cache))
214	{
215		require_once MYBB_ROOT."inc/class_datacache.php";
216		$cache = new datacache;
217		$cache->cache();
218	}
219
220	// And finally.. plugins
221	if(!is_object($plugins) && !defined("NO_PLUGINS") && !($mybb->settings['no_plugins'] == 1))
222	{
223		require_once MYBB_ROOT."inc/class_plugins.php";
224		$plugins = new pluginSystem;
225		$plugins->load();
226	}
227
228	// We have some shutdown queries needing to be run
229	if(is_array($shutdown_queries))
230	{
231		// Loop through and run them all
232		foreach($shutdown_queries as $query)
233		{
234			$db->write_query($query);
235		}
236	}
237
238	// Run any shutdown functions if we have them
239	if(is_array($shutdown_functions))
240	{
241		foreach($shutdown_functions as $function)
242		{
243			call_user_func_array($function['function'], $function['arguments']);
244		}
245	}
246
247	$done_shutdown = true;
248}
249
250/**
251 * Sends a specified amount of messages from the mail queue
252 *
253 * @param int $count The number of messages to send (Defaults to 10)
254 */
255function send_mail_queue($count=10)
256{
257	global $db, $cache, $plugins;
258
259	$plugins->run_hooks("send_mail_queue_start");
260
261	// Check to see if the mail queue has messages needing to be sent
262	$mailcache = $cache->read("mailqueue");
263	if($mailcache !== false && $mailcache['queue_size'] > 0 && ($mailcache['locked'] == 0 || $mailcache['locked'] < TIME_NOW-300))
264	{
265		// Lock the queue so no other messages can be sent whilst these are (for popular boards)
266		$cache->update_mailqueue(0, TIME_NOW);
267
268		// Fetch emails for this page view - and send them
269		$query = $db->simple_select("mailqueue", "*", "", array("order_by" => "mid", "order_dir" => "asc", "limit_start" => 0, "limit" => $count));
270
271		while($email = $db->fetch_array($query))
272		{
273			// Delete the message from the queue
274			$db->delete_query("mailqueue", "mid='{$email['mid']}'");
275
276			if($db->affected_rows() == 1)
277			{
278				my_mail($email['mailto'], $email['subject'], $email['message'], $email['mailfrom'], "", $email['headers'], true);
279			}
280		}
281		// Update the mailqueue cache and remove the lock
282		$cache->update_mailqueue(TIME_NOW, 0);
283	}
284
285	$plugins->run_hooks("send_mail_queue_end");
286}
287
288/**
289 * Parses the contents of a page before outputting it.
290 *
291 * @param string $contents The contents of the page.
292 * @return string The parsed page.
293 */
294function parse_page($contents)
295{
296	global $lang, $theme, $mybb, $htmldoctype, $archive_url, $error_handler;
297
298	$contents = str_replace('<navigation>', build_breadcrumb(), $contents);
299	$contents = str_replace('<archive_url>', $archive_url, $contents);
300
301	if($htmldoctype)
302	{
303		$contents = $htmldoctype.$contents;
304	}
305	else
306	{
307		$contents = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n".$contents;
308	}
309
310	$contents = str_replace("<html", "<html xmlns=\"http://www.w3.org/1999/xhtml\"", $contents);
311
312	if($lang->settings['rtl'] == 1)
313	{
314		$contents = str_replace("<html", "<html dir=\"rtl\"", $contents);
315	}
316
317	if($lang->settings['htmllang'])
318	{
319		$contents = str_replace("<html", "<html xml:lang=\"".$lang->settings['htmllang']."\" lang=\"".$lang->settings['htmllang']."\"", $contents);
320	}
321
322	if($error_handler->warnings)
323	{
324		$contents = str_replace("<body>", "<body>\n".$error_handler->show_warnings(), $contents);
325	}
326
327	return $contents;
328}
329
330/**
331 * Turn a unix timestamp in to a "friendly" date/time format for the user.
332 *
333 * @param string $format A date format (either relative, normal or PHP's date() structure).
334 * @param int $stamp The unix timestamp the date should be generated for.
335 * @param int|string $offset The offset in hours that should be applied to times. (timezones) Or an empty string to determine that automatically
336 * @param int $ty Whether or not to use today/yesterday formatting.
337 * @param boolean $adodb Whether or not to use the adodb time class for < 1970 or > 2038 times
338 * @return string The formatted timestamp.
339 */
340function my_date($format, $stamp=0, $offset="", $ty=1, $adodb=false)
341{
342	global $mybb, $lang, $plugins;
343
344	// If the stamp isn't set, use TIME_NOW
345	if(empty($stamp))
346	{
347		$stamp = TIME_NOW;
348	}
349
350	if(!$offset && $offset != '0')
351	{
352		if(isset($mybb->user['uid']) && $mybb->user['uid'] != 0 && array_key_exists("timezone", $mybb->user))
353		{
354			$offset = (float)$mybb->user['timezone'];
355			$dstcorrection = $mybb->user['dst'];
356		}
357		else
358		{
359			$offset = (float)$mybb->settings['timezoneoffset'];
360			$dstcorrection = $mybb->settings['dstcorrection'];
361		}
362
363		// If DST correction is enabled, add an additional hour to the timezone.
364		if($dstcorrection == 1)
365		{
366			++$offset;
367			if(my_substr($offset, 0, 1) != "-")
368			{
369				$offset = "+".$offset;
370			}
371		}
372	}
373
374	if($offset == "-")
375	{
376		$offset = 0;
377	}
378
379	// Using ADOdb?
380	if($adodb == true && !function_exists('adodb_date'))
381	{
382		$adodb = false;
383	}
384
385	$todaysdate = $yesterdaysdate = '';
386	if($ty && ($format == $mybb->settings['dateformat'] || $format == 'relative' || $format == 'normal'))
387	{
388		$_stamp = TIME_NOW;
389		if($adodb == true)
390		{
391			$date = adodb_date($mybb->settings['dateformat'], $stamp + ($offset * 3600));
392			$todaysdate = adodb_date($mybb->settings['dateformat'], $_stamp + ($offset * 3600));
393			$yesterdaysdate = adodb_date($mybb->settings['dateformat'], ($_stamp - 86400) + ($offset * 3600));
394		}
395		else
396		{
397			$date = gmdate($mybb->settings['dateformat'], $stamp + ($offset * 3600));
398			$todaysdate = gmdate($mybb->settings['dateformat'], $_stamp + ($offset * 3600));
399			$yesterdaysdate = gmdate($mybb->settings['dateformat'], ($_stamp - 86400) + ($offset * 3600));
400		}
401	}
402
403	if($format == 'relative')
404	{
405		// Relative formats both date and time
406		$real_date = $real_time = '';
407		if($adodb == true)
408		{
409			$real_date = adodb_date($mybb->settings['dateformat'], $stamp + ($offset * 3600));
410			$real_time = $mybb->settings['datetimesep'];
411			$real_time .= adodb_date($mybb->settings['timeformat'], $stamp + ($offset * 3600));
412		}
413		else
414		{
415			$real_date = gmdate($mybb->settings['dateformat'], $stamp + ($offset * 3600));
416			$real_time = $mybb->settings['datetimesep'];
417			$real_time .= gmdate($mybb->settings['timeformat'], $stamp + ($offset * 3600));
418		}
419
420		if($ty != 2 && abs(TIME_NOW - $stamp) < 3600)
421		{
422			$diff = TIME_NOW - $stamp;
423			$relative = array('prefix' => '', 'minute' => 0, 'plural' => $lang->rel_minutes_plural, 'suffix' => $lang->rel_ago);
424
425			if($diff < 0)
426			{
427				$diff = abs($diff);
428				$relative['suffix'] = '';
429				$relative['prefix'] = $lang->rel_in;
430			}
431
432			$relative['minute'] = floor($diff / 60);
433
434			if($relative['minute'] <= 1)
435			{
436				$relative['minute'] = 1;
437				$relative['plural'] = $lang->rel_minutes_single;
438			}
439
440			if($diff <= 60)
441			{
442				// Less than a minute
443				$relative['prefix'] = $lang->rel_less_than;
444			}
445
446			$date = $lang->sprintf($lang->rel_time, $relative['prefix'], $relative['minute'], $relative['plural'], $relative['suffix'], $real_date, $real_time);
447		}
448		elseif($ty != 2 && abs(TIME_NOW - $stamp) < 43200)
449		{
450			$diff = TIME_NOW - $stamp;
451			$relative = array('prefix' => '', 'hour' => 0, 'plural' => $lang->rel_hours_plural, 'suffix' => $lang->rel_ago);
452
453			if($diff < 0)
454			{
455				$diff = abs($diff);
456				$relative['suffix'] = '';
457				$relative['prefix'] = $lang->rel_in;
458			}
459
460			$relative['hour'] = floor($diff / 3600);
461
462			if($relative['hour'] <= 1)
463			{
464				$relative['hour'] = 1;
465				$relative['plural'] = $lang->rel_hours_single;
466			}
467
468			$date = $lang->sprintf($lang->rel_time, $relative['prefix'], $relative['hour'], $relative['plural'], $relative['suffix'], $real_date, $real_time);
469		}
470		else
471		{
472			if($ty)
473			{
474				if($todaysdate == $date)
475				{
476					$date = $lang->sprintf($lang->today_rel, $real_date);
477				}
478				else if($yesterdaysdate == $date)
479				{
480					$date = $lang->sprintf($lang->yesterday_rel, $real_date);
481				}
482			}
483
484			$date .= $mybb->settings['datetimesep'];
485			if($adodb == true)
486			{
487				$date .= adodb_date($mybb->settings['timeformat'], $stamp + ($offset * 3600));
488			}
489			else
490			{
491				$date .= gmdate($mybb->settings['timeformat'], $stamp + ($offset * 3600));
492			}
493		}
494	}
495	elseif($format == 'normal')
496	{
497		// Normal format both date and time
498		if($ty != 2)
499		{
500			if($todaysdate == $date)
501			{
502				$date = $lang->today;
503			}
504			else if($yesterdaysdate == $date)
505			{
506				$date = $lang->yesterday;
507			}
508		}
509
510		$date .= $mybb->settings['datetimesep'];
511		if($adodb == true)
512		{
513			$date .= adodb_date($mybb->settings['timeformat'], $stamp + ($offset * 3600));
514		}
515		else
516		{
517			$date .= gmdate($mybb->settings['timeformat'], $stamp + ($offset * 3600));
518		}
519	}
520	else
521	{
522		if($ty && $format == $mybb->settings['dateformat'])
523		{
524			if($todaysdate == $date)
525			{
526				$date = $lang->today;
527			}
528			else if($yesterdaysdate == $date)
529			{
530				$date = $lang->yesterday;
531			}
532		}
533		else
534		{
535			if($adodb == true)
536			{
537				$date = adodb_date($format, $stamp + ($offset * 3600));
538			}
539			else
540			{
541				$date = gmdate($format, $stamp + ($offset * 3600));
542			}
543		}
544	}
545
546	if(is_object($plugins))
547	{
548		$date = $plugins->run_hooks("my_date", $date);
549	}
550
551	return $date;
552}
553
554/**
555 * Get a mail handler instance, a MyBB's built-in SMTP / PHP mail hander or one created by a plugin.
556 * @param bool $use_buitlin Whether to use MyBB's built-in mail handler.
557 *
558 * @return object A MyBB built-in mail handler or one created by plugin(s).
559 */
560function &get_my_mailhandler($use_buitlin = false)
561{
562	global $mybb, $plugins;
563	static $my_mailhandler;
564	static $my_mailhandler_builtin;
565
566	if($use_buitlin)
567	{
568		// If our built-in mail handler doesn't exist, create it.
569		if(!is_object($my_mailhandler_builtin))
570		{
571			require_once MYBB_ROOT . "inc/class_mailhandler.php";
572
573			// Using SMTP.
574			if(isset($mybb->settings['mail_handler']) && $mybb->settings['mail_handler'] == 'smtp')
575			{
576				require_once MYBB_ROOT . "inc/mailhandlers/smtp.php";
577				$my_mailhandler_builtin = new SmtpMail();
578			}
579			// Using PHP mail().
580			else
581			{
582				require_once MYBB_ROOT . "inc/mailhandlers/php.php";
583				$my_mailhandler_builtin = new PhpMail();
584				if(!empty($mybb->settings['mail_parameters']))
585				{
586					$my_mailhandler_builtin->additional_parameters = $mybb->settings['mail_parameters'];
587				}
588			}
589		}
590
591		$plugins->run_hooks('my_mailhandler_builtin_after_init', $my_mailhandler_builtin);
592
593		return $my_mailhandler_builtin;
594	}
595
596	// If our mail handler doesn't exist, create it.
597	if(!is_object($my_mailhandler))
598	{
599		require_once MYBB_ROOT . "inc/class_mailhandler.php";
600
601		$plugins->run_hooks('my_mailhandler_init', $my_mailhandler);
602
603		// If no plugin has ever created the mail handler, resort to use the built-in one.
604		if(!is_object($my_mailhandler) || !($my_mailhandler instanceof MailHandler))
605		{
606			$my_mailhandler = &get_my_mailhandler(true);
607		}
608	}
609
610	return $my_mailhandler;
611}
612
613/**
614 * Sends an email using PHP's mail function, formatting it appropriately.
615 *
616 * @param string $to Address the email should be addressed to.
617 * @param string $subject The subject of the email being sent.
618 * @param string $message The message being sent.
619 * @param string $from The from address of the email, if blank, the board name will be used.
620 * @param string $charset The chracter set being used to send this email.
621 * @param string $headers
622 * @param boolean $keep_alive Do we wish to keep the connection to the mail server alive to send more than one message (SMTP only)
623 * @param string $format The format of the email to be sent (text or html). text is default
624 * @param string $message_text The text message of the email if being sent in html format, for email clients that don't support html
625 * @param string $return_email The email address to return to. Defaults to admin return email address.
626 * @return bool True if the mail is sent, false otherwise.
627 */
628function my_mail($to, $subject, $message, $from="", $charset="", $headers="", $keep_alive=false, $format="text", $message_text="", $return_email="")
629{
630	global $mybb, $plugins;
631
632	// Get our mail handler.
633	$mail = &get_my_mailhandler();
634
635	// If MyBB's built-in SMTP mail handler is used, set the keep alive bit accordingly.
636	if($keep_alive == true && isset($mail->keep_alive) && isset($mybb->settings['mail_handler']) && $mybb->settings['mail_handler'] == 'smtp')
637	{
638		require_once MYBB_ROOT . "inc/class_mailhandler.php";
639		require_once MYBB_ROOT . "inc/mailhandlers/smtp.php";
640		if($mail instanceof MailHandler && $mail instanceof SmtpMail)
641		{
642			$mail->keep_alive = true;
643		}
644	}
645
646	// Following variables will help sequential plugins to determine how to process plugin hooks.
647	// Mark this variable true if the hooked plugin has sent the mail, otherwise don't modify it.
648	$is_mail_sent = false;
649	// Mark this variable false if the hooked plugin doesn't suggest sequential plugins to continue processing.
650	$continue_process = true;
651
652	$my_mail_parameters = array(
653		'to' => &$to,
654		'subject' => &$subject,
655		'message' => &$message,
656		'from' => &$from,
657		'charset' => &$charset,
658		'headers' => &$headers,
659		'keep_alive' => &$keep_alive,
660		'format' => &$format,
661		'message_text' => &$message_text,
662		'return_email' => &$return_email,
663		'is_mail_sent' => &$is_mail_sent,
664		'continue_process' => &$continue_process,
665	);
666
667	$plugins->run_hooks('my_mail_pre_build_message', $my_mail_parameters);
668
669	// Build the mail message.
670	$mail->build_message($to, $subject, $message, $from, $charset, $headers, $format, $message_text, $return_email);
671
672	$plugins->run_hooks('my_mail_pre_send', $my_mail_parameters);
673
674	// Check if the hooked plugins still suggest to send the mail.
675	if($continue_process)
676	{
677		$is_mail_sent = $mail->send();
678	}
679
680	$plugins->run_hooks('my_mail_post_send', $my_mail_parameters);
681
682	return $is_mail_sent;
683}
684
685/**
686 * Generates a code for POST requests to prevent XSS/CSRF attacks.
687 * Unique for each user or guest session and rotated every 6 hours.
688 *
689 * @param int $rotation_shift Adjustment of the rotation number to generate a past/future code
690 * @return string The generated code
691 */
692function generate_post_check($rotation_shift=0)
693{
694	global $mybb, $session;
695
696	$rotation_interval = 6 * 3600;
697	$rotation = floor(TIME_NOW / $rotation_interval) + $rotation_shift;
698
699	$seed = $rotation;
700
701	if($mybb->user['uid'])
702	{
703		$seed .= $mybb->user['loginkey'].$mybb->user['salt'].$mybb->user['regdate'];
704	}
705	else
706	{
707		$seed .= $session->sid;
708	}
709
710	if(defined('IN_ADMINCP'))
711	{
712		$seed .= 'ADMINCP';
713	}
714
715	$seed .= $mybb->settings['internal']['encryption_key'];
716
717	return md5($seed);
718}
719
720/**
721 * Verifies a POST check code is valid (i.e. generated using a rotation number from the past 24 hours)
722 *
723 * @param string $code The incoming POST check code
724 * @param boolean $silent Don't show an error to the user
725 * @return bool|void Result boolean if $silent is true, otherwise shows an error to the user
726 */
727function verify_post_check($code, $silent=false)
728{
729	global $lang;
730	if(
731		generate_post_check() !== $code &&
732		generate_post_check(-1) !== $code &&
733		generate_post_check(-2) !== $code &&
734		generate_post_check(-3) !== $code
735	)
736	{
737		if($silent == true)
738		{
739			return false;
740		}
741		else
742		{
743			if(defined("IN_ADMINCP"))
744			{
745				return false;
746			}
747			else
748			{
749				error($lang->invalid_post_code);
750			}
751		}
752	}
753	else
754	{
755		return true;
756	}
757}
758
759/**
760 * Return a parent list for the specified forum.
761 *
762 * @param int $fid The forum id to get the parent list for.
763 * @return string The comma-separated parent list.
764 */
765function get_parent_list($fid)
766{
767	global $forum_cache;
768	static $forumarraycache;
769
770	if(!empty($forumarraycache[$fid]))
771	{
772		return $forumarraycache[$fid]['parentlist'];
773	}
774	elseif(!empty($forum_cache[$fid]))
775	{
776		return $forum_cache[$fid]['parentlist'];
777	}
778	else
779	{
780		cache_forums();
781		return $forum_cache[$fid]['parentlist'];
782	}
783}
784
785/**
786 * Build a parent list of a specific forum, suitable for querying
787 *
788 * @param int $fid The forum ID
789 * @param string $column The column name to add to the query
790 * @param string $joiner The joiner for each forum for querying (OR | AND | etc)
791 * @param string $parentlist The parent list of the forum - if you have it
792 * @return string The query string generated
793 */
794function build_parent_list($fid, $column="fid", $joiner="OR", $parentlist="")
795{
796	if(!$parentlist)
797	{
798		$parentlist = get_parent_list($fid);
799	}
800
801	$parentsexploded = explode(",", $parentlist);
802	$builtlist = "(";
803	$sep = '';
804
805	foreach($parentsexploded as $key => $val)
806	{
807		$builtlist .= "$sep$column='$val'";
808		$sep = " $joiner ";
809	}
810
811	$builtlist .= ")";
812
813	return $builtlist;
814}
815
816/**
817 * Load the forum cache in to memory
818 *
819 * @param boolean $force True to force a reload of the cache
820 * @return array The forum cache
821 */
822function cache_forums($force=false)
823{
824	global $forum_cache, $cache;
825
826	if($force == true)
827	{
828		$forum_cache = $cache->read("forums", 1);
829		return $forum_cache;
830	}
831
832	if(!$forum_cache)
833	{
834		$forum_cache = $cache->read("forums");
835		if(!$forum_cache)
836		{
837			$cache->update_forums();
838			$forum_cache = $cache->read("forums", 1);
839		}
840	}
841	return $forum_cache;
842}
843
844/**
845 * Generate an array of all child and descendant forums for a specific forum.
846 *
847 * @param int $fid The forum ID
848 * @return Array of descendants
849 */
850function get_child_list($fid)
851{
852	static $forums_by_parent;
853
854	$forums = array();
855	if(!is_array($forums_by_parent))
856	{
857		$forum_cache = cache_forums();
858		foreach($forum_cache as $forum)
859		{
860			if($forum['active'] != 0)
861			{
862				$forums_by_parent[$forum['pid']][$forum['fid']] = $forum;
863			}
864		}
865	}
866	if(isset($forums_by_parent[$fid]))
867	{
868		if(!is_array($forums_by_parent[$fid]))
869		{
870			return $forums;
871		}
872
873		foreach($forums_by_parent[$fid] as $forum)
874		{
875			$forums[] = (int)$forum['fid'];
876			$children = get_child_list($forum['fid']);
877			if(is_array($children))
878			{
879				$forums = array_merge($forums, $children);
880			}
881		}
882	}
883	return $forums;
884}
885
886/**
887 * Produce a friendly error message page
888 *
889 * @param string $error The error message to be shown
890 * @param string $title The title of the message shown in the title of the page and the error table
891 */
892function error($error="", $title="")
893{
894	global $header, $footer, $theme, $headerinclude, $db, $templates, $lang, $mybb, $plugins;
895
896	$error = $plugins->run_hooks("error", $error);
897	if(!$error)
898	{
899		$error = $lang->unknown_error;
900	}
901
902	// AJAX error message?
903	if($mybb->get_input('ajax', MyBB::INPUT_INT))
904	{
905		// Send our headers.
906		@header("Content-type: application/json; charset={$lang->settings['charset']}");
907		echo json_encode(array("errors" => array($error)));
908		exit;
909	}
910
911	if(!$title)
912	{
913		$title = $mybb->settings['bbname'];
914	}
915
916	$timenow = my_date('relative', TIME_NOW);
917	reset_breadcrumb();
918	add_breadcrumb($lang->error);
919
920	eval("\$errorpage = \"".$templates->get("error")."\";");
921	output_page($errorpage);
922
923	exit;
924}
925
926/**
927 * Produce an error message for displaying inline on a page
928 *
929 * @param array $errors Array of errors to be shown
930 * @param string $title The title of the error message
931 * @param array $json_data JSON data to be encoded (we may want to send more data; e.g. newreply.php uses this for CAPTCHA)
932 * @return string The inline error HTML
933 */
934function inline_error($errors, $title="", $json_data=array())
935{
936	global $theme, $mybb, $db, $lang, $templates;
937
938	if(!$title)
939	{
940		$title = $lang->please_correct_errors;
941	}
942
943	if(!is_array($errors))
944	{
945		$errors = array($errors);
946	}
947
948	// AJAX error message?
949	if($mybb->get_input('ajax', MyBB::INPUT_INT))
950	{
951		// Send our headers.
952		@header("Content-type: application/json; charset={$lang->settings['charset']}");
953
954		if(empty($json_data))
955		{
956			echo json_encode(array("errors" => $errors));
957		}
958		else
959		{
960			echo json_encode(array_merge(array("errors" => $errors), $json_data));
961		}
962		exit;
963	}
964
965	$errorlist = '';
966
967	foreach($errors as $error)
968	{
969		eval("\$errorlist .= \"".$templates->get("error_inline_item")."\";");
970	}
971
972	eval("\$errors = \"".$templates->get("error_inline")."\";");
973
974	return $errors;
975}
976
977/**
978 * Presents the user with a "no permission" page
979 */
980function error_no_permission()
981{
982	global $mybb, $theme, $templates, $db, $lang, $plugins, $session;
983
984	$time = TIME_NOW;
985	$plugins->run_hooks("no_permission");
986
987	$noperm_array = array (
988		"nopermission" => '1',
989		"location1" => 0,
990		"location2" => 0
991	);
992
993	$db->update_query("sessions", $noperm_array, "sid='{$session->sid}'");
994
995	if($mybb->get_input('ajax', MyBB::INPUT_INT))
996	{
997		// Send our headers.
998		header("Content-type: application/json; charset={$lang->settings['charset']}");
999		echo json_encode(array("errors" => array($lang->error_nopermission_user_ajax)));
1000		exit;
1001	}
1002
1003	if($mybb->user['uid'])
1004	{
1005		$lang->error_nopermission_user_username = $lang->sprintf($lang->error_nopermission_user_username, htmlspecialchars_uni($mybb->user['username']));
1006		eval("\$errorpage = \"".$templates->get("error_nopermission_loggedin")."\";");
1007	}
1008	else
1009	{
1010		// Redirect to where the user came from
1011		$redirect_url = $_SERVER['PHP_SELF'];
1012		if($_SERVER['QUERY_STRING'])
1013		{
1014			$redirect_url .= '?'.$_SERVER['QUERY_STRING'];
1015		}
1016
1017		$redirect_url = htmlspecialchars_uni($redirect_url);
1018
1019		switch($mybb->settings['username_method'])
1020		{
1021			case 0:
1022				$lang_username = $lang->username;
1023				break;
1024			case 1:
1025				$lang_username = $lang->username1;
1026				break;
1027			case 2:
1028				$lang_username = $lang->username2;
1029				break;
1030			default:
1031				$lang_username = $lang->username;
1032				break;
1033		}
1034		eval("\$errorpage = \"".$templates->get("error_nopermission")."\";");
1035	}
1036
1037	error($errorpage);
1038}
1039
1040/**
1041 * Redirect the user to a given URL with a given message
1042 *
1043 * @param string $url The URL to redirect the user to
1044 * @param string $message The redirection message to be shown
1045 * @param string $title The title of the redirection page
1046 * @param boolean $force_redirect Force the redirect page regardless of settings
1047 */
1048function redirect($url, $message="", $title="", $force_redirect=false)
1049{
1050	global $header, $footer, $mybb, $theme, $headerinclude, $templates, $lang, $plugins;
1051
1052	$redirect_args = array('url' => &$url, 'message' => &$message, 'title' => &$title);
1053
1054	$plugins->run_hooks("redirect", $redirect_args);
1055
1056	if($mybb->get_input('ajax', MyBB::INPUT_INT))
1057	{
1058		// Send our headers.
1059		//@header("Content-type: text/html; charset={$lang->settings['charset']}");
1060		$data = "<script type=\"text/javascript\">\n";
1061		if($message != "")
1062		{
1063			$data .=  'alert("'.addslashes($message).'");';
1064		}
1065		$url = str_replace("#", "&#", $url);
1066		$url = htmlspecialchars_decode($url);
1067		$url = str_replace(array("\n","\r",";"), "", $url);
1068		$data .=  'window.location = "'.addslashes($url).'";'."\n";
1069		$data .= "</script>\n";
1070		//exit;
1071
1072		@header("Content-type: application/json; charset={$lang->settings['charset']}");
1073		echo json_encode(array("data" => $data));
1074		exit;
1075	}
1076
1077	if(!$message)
1078	{
1079		$message = $lang->redirect;
1080	}
1081
1082	$time = TIME_NOW;
1083	$timenow = my_date('relative', $time);
1084
1085	if(!$title)
1086	{
1087		$title = $mybb->settings['bbname'];
1088	}
1089
1090	// Show redirects only if both ACP and UCP settings are enabled, or ACP is enabled, and user is a guest, or they are forced.
1091	if($force_redirect == true || ($mybb->settings['redirects'] == 1 && (!$mybb->user['uid'] || $mybb->user['showredirect'] == 1)))
1092	{
1093		$url = str_replace("&amp;", "&", $url);
1094		$url = htmlspecialchars_uni($url);
1095
1096		eval("\$redirectpage = \"".$templates->get("redirect")."\";");
1097		output_page($redirectpage);
1098	}
1099	else
1100	{
1101		$url = htmlspecialchars_decode($url);
1102		$url = str_replace(array("\n","\r",";"), "", $url);
1103
1104		run_shutdown();
1105
1106		if(!my_validate_url($url, true, true))
1107		{
1108			header("Location: {$mybb->settings['bburl']}/{$url}");
1109		}
1110		else
1111		{
1112			header("Location: {$url}");
1113		}
1114	}
1115
1116	exit;
1117}
1118
1119/**
1120 * Generate a listing of page - pagination
1121 *
1122 * @param int $count The number of items
1123 * @param int $perpage The number of items to be shown per page
1124 * @param int $page The current page number
1125 * @param string $url The URL to have page numbers tacked on to (If {page} is specified, the value will be replaced with the page #)
1126 * @param boolean $breadcrumb Whether or not the multipage is being shown in the navigation breadcrumb
1127 * @return string The generated pagination
1128 */
1129function multipage($count, $perpage, $page, $url, $breadcrumb=false)
1130{
1131	global $theme, $templates, $lang, $mybb, $plugins;
1132
1133	if($count <= $perpage)
1134	{
1135		return '';
1136	}
1137
1138	$args = array(
1139		'count' => &$count,
1140		'perpage' => &$perpage,
1141		'page' => &$page,
1142		'url' => &$url,
1143		'breadcrumb' => &$breadcrumb,
1144	);
1145	$plugins->run_hooks('multipage', $args);
1146
1147	$page = (int)$page;
1148
1149	$url = str_replace("&amp;", "&", $url);
1150	$url = htmlspecialchars_uni($url);
1151
1152	$pages = ceil($count / $perpage);
1153
1154	$prevpage = '';
1155	if($page > 1)
1156	{
1157		$prev = $page-1;
1158		$page_url = fetch_page_url($url, $prev);
1159		eval("\$prevpage = \"".$templates->get("multipage_prevpage")."\";");
1160	}
1161
1162	// Maximum number of "page bits" to show
1163	if(!$mybb->settings['maxmultipagelinks'])
1164	{
1165		$mybb->settings['maxmultipagelinks'] = 5;
1166	}
1167
1168	$from = $page-floor($mybb->settings['maxmultipagelinks']/2);
1169	$to = $page+floor($mybb->settings['maxmultipagelinks']/2);
1170
1171	if($from <= 0)
1172	{
1173		$from = 1;
1174		$to = $from+$mybb->settings['maxmultipagelinks']-1;
1175	}
1176
1177	if($to > $pages)
1178	{
1179		$to = $pages;
1180		$from = $pages-$mybb->settings['maxmultipagelinks']+1;
1181		if($from <= 0)
1182		{
1183			$from = 1;
1184		}
1185	}
1186
1187	if($to == 0)
1188	{
1189		$to = $pages;
1190	}
1191
1192	$start = '';
1193	if($from > 1)
1194	{
1195		if($from-1 == 1)
1196		{
1197			$lang->multipage_link_start = '';
1198		}
1199
1200		$page_url = fetch_page_url($url, 1);
1201		eval("\$start = \"".$templates->get("multipage_start")."\";");
1202	}
1203
1204	$mppage = '';
1205	for($i = $from; $i <= $to; ++$i)
1206	{
1207		$page_url = fetch_page_url($url, $i);
1208		if($page == $i)
1209		{
1210			if($breadcrumb == true)
1211			{
1212				eval("\$mppage .= \"".$templates->get("multipage_page_link_current")."\";");
1213			}
1214			else
1215			{
1216				eval("\$mppage .= \"".$templates->get("multipage_page_current")."\";");
1217			}
1218		}
1219		else
1220		{
1221			eval("\$mppage .= \"".$templates->get("multipage_page")."\";");
1222		}
1223	}
1224
1225	$end = '';
1226	if($to < $pages)
1227	{
1228		if($to+1 == $pages)
1229		{
1230			$lang->multipage_link_end = '';
1231		}
1232
1233		$page_url = fetch_page_url($url, $pages);
1234		eval("\$end = \"".$templates->get("multipage_end")."\";");
1235	}
1236
1237	$nextpage = '';
1238	if($page < $pages)
1239	{
1240		$next = $page+1;
1241		$page_url = fetch_page_url($url, $next);
1242		eval("\$nextpage = \"".$templates->get("multipage_nextpage")."\";");
1243	}
1244
1245	$jumptopage = '';
1246	if($pages > ($mybb->settings['maxmultipagelinks']+1) && $mybb->settings['jumptopagemultipage'] == 1)
1247	{
1248		// When the second parameter is set to 1, fetch_page_url thinks it's the first page and removes it from the URL as it's unnecessary
1249		$jump_url = fetch_page_url($url, 1);
1250		eval("\$jumptopage = \"".$templates->get("multipage_jump_page")."\";");
1251	}
1252
1253	$multipage_pages = $lang->sprintf($lang->multipage_pages, $pages);
1254
1255	if($breadcrumb == true)
1256	{
1257		eval("\$multipage = \"".$templates->get("multipage_breadcrumb")."\";");
1258	}
1259	else
1260	{
1261		eval("\$multipage = \"".$templates->get("multipage")."\";");
1262	}
1263
1264	return $multipage;
1265}
1266
1267/**
1268 * Generate a page URL for use by the multipage function
1269 *
1270 * @param string $url The URL being passed
1271 * @param int $page The page number
1272 * @return string
1273 */
1274function fetch_page_url($url, $page)
1275{
1276	if($page <= 1)
1277	{
1278		$find = array(
1279			"-page-{page}",
1280			"&amp;page={page}",
1281			"{page}"
1282		);
1283
1284		// Remove "Page 1" to the defacto URL
1285		$url = str_replace($find, array("", "", $page), $url);
1286		return $url;
1287	}
1288	else if(strpos($url, "{page}") === false)
1289	{
1290		// If no page identifier is specified we tack it on to the end of the URL
1291		if(strpos($url, "?") === false)
1292		{
1293			$url .= "?";
1294		}
1295		else
1296		{
1297			$url .= "&amp;";
1298		}
1299
1300		$url .= "page=$page";
1301	}
1302	else
1303	{
1304		$url = str_replace("{page}", $page, $url);
1305	}
1306
1307	return $url;
1308}
1309
1310/**
1311 * Fetch the permissions for a specific user
1312 *
1313 * @param int $uid The user ID, if no user ID is provided then current user's ID will be considered.
1314 * @return array Array of user permissions for the specified user
1315 */
1316function user_permissions($uid=null)
1317{
1318	global $mybb, $cache, $groupscache, $user_cache;
1319
1320	// If no user id is specified, assume it is the current user
1321	if($uid === null)
1322	{
1323		$uid = $mybb->user['uid'];
1324	}
1325
1326	// Its a guest. Return the group permissions directly from cache
1327	if($uid == 0)
1328	{
1329		return $groupscache[1];
1330	}
1331
1332	// User id does not match current user, fetch permissions
1333	if($uid != $mybb->user['uid'])
1334	{
1335		// We've already cached permissions for this user, return them.
1336		if(!empty($user_cache[$uid]['permissions']))
1337		{
1338			return $user_cache[$uid]['permissions'];
1339		}
1340
1341		// This user was not already cached, fetch their user information.
1342		if(empty($user_cache[$uid]))
1343		{
1344			$user_cache[$uid] = get_user($uid);
1345		}
1346
1347		// Collect group permissions.
1348		$gid = $user_cache[$uid]['usergroup'].",".$user_cache[$uid]['additionalgroups'];
1349		$groupperms = usergroup_permissions($gid);
1350
1351		// Store group permissions in user cache.
1352		$user_cache[$uid]['permissions'] = $groupperms;
1353		return $groupperms;
1354	}
1355	// This user is the current user, return their permissions
1356	else
1357	{
1358		return $mybb->usergroup;
1359	}
1360}
1361
1362/**
1363 * Fetch the usergroup permissions for a specific group or series of groups combined
1364 *
1365 * @param int|string $gid A list of groups (Can be a single integer, or a list of groups separated by a comma)
1366 * @return array Array of permissions generated for the groups, containing also a list of comma-separated checked groups under 'all_usergroups' index
1367 */
1368function usergroup_permissions($gid=0)
1369{
1370	global $cache, $groupscache, $grouppermignore, $groupzerogreater, $groupzerolesser, $groupxgreater, $grouppermbyswitch;
1371
1372	if(!is_array($groupscache))
1373	{
1374		$groupscache = $cache->read("usergroups");
1375	}
1376
1377	$groups = explode(",", $gid);
1378
1379	if(count($groups) == 1)
1380	{
1381		$groupscache[$gid]['all_usergroups'] = $gid;
1382		return $groupscache[$gid];
1383	}
1384
1385	$usergroup = array();
1386	$usergroup['all_usergroups'] = $gid;
1387
1388	// Get those switch permissions from the first valid group.
1389	$permswitches_usergroup = array();
1390	$grouppermswitches = array();
1391	foreach(array_values($grouppermbyswitch) as $permvalue)
1392	{
1393		if(is_array($permvalue))
1394		{
1395			foreach($permvalue as $perm)
1396			{
1397				$grouppermswitches[] = $perm;
1398			}
1399		}
1400		else
1401		{
1402			$grouppermswitches[] = $permvalue;
1403		}
1404	}
1405	$grouppermswitches = array_unique($grouppermswitches);
1406	foreach($groups as $gid)
1407	{
1408		if(trim($gid) == "" || empty($groupscache[$gid]))
1409		{
1410			continue;
1411		}
1412		foreach($grouppermswitches as $perm)
1413		{
1414			$permswitches_usergroup[$perm] = $groupscache[$gid][$perm];
1415		}
1416		break;	// Only retieve the first available group's permissions as how following action does.
1417	}
1418
1419	foreach($groups as $gid)
1420	{
1421		if(trim($gid) == "" || empty($groupscache[$gid]))
1422		{
1423			continue;
1424		}
1425
1426		foreach($groupscache[$gid] as $perm => $access)
1427		{
1428			if(!in_array($perm, $grouppermignore))
1429			{
1430				if(isset($usergroup[$perm]))
1431				{
1432					$permbit = $usergroup[$perm];
1433				}
1434				else
1435				{
1436					$permbit = "";
1437				}
1438
1439				// permission type: 0 not a numerical permission, otherwise a numerical permission.
1440				// Positive value is for `greater is more` permission, negative for `lesser is more`.
1441				$perm_is_numerical = 0;
1442				$perm_numerical_lowerbound = 0;
1443
1444				// 0 represents unlimited for most numerical group permissions (i.e. private message limit) so take that into account.
1445				if(in_array($perm, $groupzerogreater))
1446				{
1447					// 1 means a `0 or greater` permission. Value 0 means unlimited.
1448					$perm_is_numerical = 1;
1449				}
1450				// Less is more for some numerical group permissions (i.e. post count required for using signature) so take that into account, too.
1451				else if(in_array($perm, $groupzerolesser))
1452				{
1453					// -1 means a `0 or lesser` permission. Value 0 means unlimited.
1454					$perm_is_numerical = -1;
1455				}
1456				// Greater is more, but with a lower bound.
1457				else if(array_key_exists($perm, $groupxgreater))
1458				{
1459					// 2 means a general `greater` permission. Value 0 just means 0.
1460					$perm_is_numerical = 2;
1461					$perm_numerical_lowerbound = $groupxgreater[$perm];
1462				}
1463
1464				if($perm_is_numerical != 0)
1465				{
1466					$update_current_perm = true;
1467
1468					// Ensure it's an integer.
1469					$access = (int)$access;
1470					// Check if this permission should be activatived by another switch permission in current group.
1471					if(array_key_exists($perm, $grouppermbyswitch))
1472					{
1473						if(!is_array($grouppermbyswitch[$perm]))
1474						{
1475							$grouppermbyswitch[$perm] = array($grouppermbyswitch[$perm]);
1476						}
1477
1478						$update_current_perm = $group_current_perm_enabled = $group_perm_enabled = false;
1479						foreach($grouppermbyswitch[$perm] as $permswitch)
1480						{
1481							if(!isset($groupscache[$gid][$permswitch]))
1482							{
1483								continue;
1484							}
1485							$permswitches_current = $groupscache[$gid][$permswitch];
1486
1487							// Determin if the permission is enabled by switches from current group.
1488							if($permswitches_current == 1 || $permswitches_current == "yes") // Keep yes/no for compatibility?
1489							{
1490								$group_current_perm_enabled = true;
1491							}
1492							// Determin if the permission is enabled by switches from previously handled groups.
1493							if($permswitches_usergroup[$permswitch] == 1 || $permswitches_usergroup[$permswitch] == "yes") // Keep yes/no for compatibility?
1494							{
1495								$group_perm_enabled = true;
1496							}
1497						}
1498
1499						// Set this permission if not set yet.
1500						if(!isset($usergroup[$perm]))
1501						{
1502							$usergroup[$perm] = $access;
1503						}
1504
1505						// If current group's setting enables the permission, we may need to update the user's permission.
1506						if($group_current_perm_enabled)
1507						{
1508							// Only update this permission if both its switch and current group switch are on.
1509							if($group_perm_enabled)
1510							{
1511								$update_current_perm = true;
1512							}
1513							// Override old useless value with value from current group.
1514							else
1515							{
1516								$usergroup[$perm] = $access;
1517							}
1518						}
1519					}
1520
1521					// No switch controls this permission, or permission needs an update.
1522					if($update_current_perm)
1523					{
1524						switch($perm_is_numerical)
1525						{
1526							case 1:
1527							case -1:
1528								if($access == 0 || $permbit === 0)
1529								{
1530									$usergroup[$perm] = 0;
1531									break;
1532								}
1533							default:
1534								if($perm_is_numerical > 0 && $access > $permbit || $perm_is_numerical < 0 && $access < $permbit)
1535								{
1536									$usergroup[$perm] = $access;
1537								}
1538								break;
1539						}
1540					}
1541
1542					// Maybe oversubtle, database uses Unsigned on them, but enables usage of permission value with a lower bound.
1543					if($usergroup[$perm] < $perm_numerical_lowerbound)
1544					{
1545						$usergroup[$perm] = $perm_numerical_lowerbound;
1546					}
1547
1548					// Work is done for numerical permissions.
1549					continue;
1550				}
1551
1552				if($access > $permbit || ($access == "yes" && $permbit == "no") || !$permbit) // Keep yes/no for compatibility?
1553				{
1554					$usergroup[$perm] = $access;
1555				}
1556			}
1557		}
1558
1559		foreach($permswitches_usergroup as $perm => $value)
1560		{
1561			$permswitches_usergroup[$perm] = $usergroup[$perm];
1562		}
1563	}
1564
1565	return $usergroup;
1566}
1567
1568/**
1569 * Fetch the display group properties for a specific display group
1570 *
1571 * @param int $gid The group ID to fetch the display properties for
1572 * @return array Array of display properties for the group
1573 */
1574function usergroup_displaygroup($gid)
1575{
1576	global $cache, $groupscache, $displaygroupfields;
1577
1578	if(!is_array($groupscache))
1579	{
1580		$groupscache = $cache->read("usergroups");
1581	}
1582
1583	$displaygroup = array();
1584	$group = $groupscache[$gid];
1585
1586	foreach($displaygroupfields as $field)
1587	{
1588		$displaygroup[$field] = $group[$field];
1589	}
1590
1591	return $displaygroup;
1592}
1593
1594/**
1595 * Build the forum permissions for a specific forum, user or group
1596 *
1597 * @param int $fid The forum ID to build permissions for (0 builds for all forums)
1598 * @param int $uid The user to build the permissions for (0 will select the uid automatically)
1599 * @param int $gid The group of the user to build permissions for (0 will fetch it)
1600 * @return array Forum permissions for the specific forum or forums
1601 */
1602function forum_permissions($fid=0, $uid=0, $gid=0)
1603{
1604	global $db, $cache, $groupscache, $forum_cache, $fpermcache, $mybb, $cached_forum_permissions_permissions, $cached_forum_permissions;
1605
1606	if($uid == 0)
1607	{
1608		$uid = $mybb->user['uid'];
1609	}
1610
1611	if(!$gid || $gid == 0) // If no group, we need to fetch it
1612	{
1613		if($uid != 0 && $uid != $mybb->user['uid'])
1614		{
1615			$user = get_user($uid);
1616
1617			$gid = $user['usergroup'].",".$user['additionalgroups'];
1618			$groupperms = usergroup_permissions($gid);
1619		}
1620		else
1621		{
1622			$gid = $mybb->user['usergroup'];
1623
1624			if(isset($mybb->user['additionalgroups']))
1625			{
1626				$gid .= ",".$mybb->user['additionalgroups'];
1627			}
1628
1629			$groupperms = $mybb->usergroup;
1630		}
1631	}
1632
1633	if(!is_array($forum_cache))
1634	{
1635		$forum_cache = cache_forums();
1636
1637		if(!$forum_cache)
1638		{
1639			return false;
1640		}
1641	}
1642
1643	if(!is_array($fpermcache))
1644	{
1645		$fpermcache = $cache->read("forumpermissions");
1646	}
1647
1648	if($fid) // Fetch the permissions for a single forum
1649	{
1650		if(empty($cached_forum_permissions_permissions[$gid][$fid]))
1651		{
1652			$cached_forum_permissions_permissions[$gid][$fid] = fetch_forum_permissions($fid, $gid, $groupperms);
1653		}
1654		return $cached_forum_permissions_permissions[$gid][$fid];
1655	}
1656	else
1657	{
1658		if(empty($cached_forum_permissions[$gid]))
1659		{
1660			foreach($forum_cache as $forum)
1661			{
1662				$cached_forum_permissions[$gid][$forum['fid']] = fetch_forum_permissions($forum['fid'], $gid, $groupperms);
1663			}
1664		}
1665		return $cached_forum_permissions[$gid];
1666	}
1667}
1668
1669/**
1670 * Fetches the permissions for a specific forum/group applying the inheritance scheme.
1671 * Called by forum_permissions()
1672 *
1673 * @param int $fid The forum ID
1674 * @param string $gid A comma separated list of usergroups
1675 * @param array $groupperms Group permissions
1676 * @return array Permissions for this forum
1677*/
1678function fetch_forum_permissions($fid, $gid, $groupperms)
1679{
1680	global $groupscache, $forum_cache, $fpermcache, $mybb, $fpermfields;
1681
1682	$groups = explode(",", $gid);
1683
1684	if(empty($fpermcache[$fid])) // This forum has no custom or inherited permissions so lets just return the group permissions
1685	{
1686		return $groupperms;
1687	}
1688
1689	$current_permissions = array();
1690	$only_view_own_threads = 1;
1691	$only_reply_own_threads = 1;
1692
1693	foreach($groups as $gid)
1694	{
1695		if(!empty($groupscache[$gid]))
1696		{
1697			$level_permissions = array();
1698
1699			// If our permissions arn't inherited we need to figure them out
1700			if(empty($fpermcache[$fid][$gid]))
1701			{
1702				$parents = explode(',', $forum_cache[$fid]['parentlist']);
1703				rsort($parents);
1704				if(!empty($parents))
1705				{
1706					foreach($parents as $parent_id)
1707					{
1708						if(!empty($fpermcache[$parent_id][$gid]))
1709						{
1710							$level_permissions = $fpermcache[$parent_id][$gid];
1711							break;
1712						}
1713					}
1714				}
1715			}
1716			else
1717			{
1718				$level_permissions = $fpermcache[$fid][$gid];
1719			}
1720
1721			// If we STILL don't have forum permissions we use the usergroup itself
1722			if(empty($level_permissions))
1723			{
1724				$level_permissions = $groupscache[$gid];
1725			}
1726
1727			foreach($level_permissions as $permission => $access)
1728			{
1729				if(empty($current_permissions[$permission]) || $access >= $current_permissions[$permission] || ($access == "yes" && $current_permissions[$permission] == "no"))
1730				{
1731					$current_permissions[$permission] = $access;
1732				}
1733			}
1734
1735			if($level_permissions["canview"] && empty($level_permissions["canonlyviewownthreads"]))
1736			{
1737				$only_view_own_threads = 0;
1738			}
1739
1740			if($level_permissions["canpostreplys"] && empty($level_permissions["canonlyreplyownthreads"]))
1741			{
1742				$only_reply_own_threads = 0;
1743			}
1744		}
1745	}
1746
1747	// Figure out if we can view more than our own threads
1748	if($only_view_own_threads == 0)
1749	{
1750		$current_permissions["canonlyviewownthreads"] = 0;
1751	}
1752
1753	// Figure out if we can reply more than our own threads
1754	if($only_reply_own_threads == 0)
1755	{
1756		$current_permissions["canonlyreplyownthreads"] = 0;
1757	}
1758
1759	if(count($current_permissions) == 0)
1760	{
1761		$current_permissions = $groupperms;
1762	}
1763	return $current_permissions;
1764}
1765
1766/**
1767 * Check whether password for given forum was validated for the current user
1768 *
1769 * @param array $forum The forum data
1770 * @param bool $ignore_empty Whether to treat forum password configured as an empty string as validated
1771 * @param bool $check_parents Whether to check parent forums using `parentlist`
1772 * @return bool
1773 */
1774function forum_password_validated($forum, $ignore_empty=false, $check_parents=false)
1775{
1776	global $mybb, $forum_cache;
1777
1778	if($check_parents && isset($forum['parentlist']))
1779	{
1780		if(!is_array($forum_cache))
1781		{
1782			$forum_cache = cache_forums();
1783			if(!$forum_cache)
1784			{
1785				return false;
1786			}
1787		}
1788
1789		$parents = explode(',', $forum['parentlist']);
1790		rsort($parents);
1791
1792		foreach($parents as $parent_id)
1793		{
1794			if($parent_id != $forum['fid'] && !forum_password_validated($forum_cache[$parent_id], true))
1795			{
1796				return false;
1797			}
1798		}
1799	}
1800
1801	return ($ignore_empty && $forum['password'] === '') || (
1802		isset($mybb->cookies['forumpass'][$forum['fid']]) &&
1803		my_hash_equals(
1804			md5($mybb->user['uid'].$forum['password']),
1805			$mybb->cookies['forumpass'][$forum['fid']]
1806		)
1807	);
1808}
1809
1810/**
1811 * Check the password given on a certain forum for validity
1812 *
1813 * @param int $fid The forum ID
1814 * @param int $pid The Parent ID
1815 * @param bool $return
1816 * @return bool
1817 */
1818function check_forum_password($fid, $pid=0, $return=false)
1819{
1820	global $mybb, $header, $footer, $headerinclude, $theme, $templates, $lang, $forum_cache;
1821
1822	$showform = true;
1823
1824	if(!is_array($forum_cache))
1825	{
1826		$forum_cache = cache_forums();
1827		if(!$forum_cache)
1828		{
1829			return false;
1830		}
1831	}
1832
1833	// Loop through each of parent forums to ensure we have a password for them too
1834	if(isset($forum_cache[$fid]['parentlist']))
1835	{
1836		$parents = explode(',', $forum_cache[$fid]['parentlist']);
1837		rsort($parents);
1838	}
1839	if(!empty($parents))
1840	{
1841		foreach($parents as $parent_id)
1842		{
1843			if($parent_id == $fid || $parent_id == $pid)
1844			{
1845				continue;
1846			}
1847
1848			if($forum_cache[$parent_id]['password'] !== "")
1849			{
1850				check_forum_password($parent_id, $fid);
1851			}
1852		}
1853	}
1854
1855	if($forum_cache[$fid]['password'] !== '')
1856	{
1857		if(isset($mybb->input['pwverify']) && $pid == 0)
1858		{
1859			if(my_hash_equals($forum_cache[$fid]['password'], $mybb->get_input('pwverify')))
1860			{
1861				my_setcookie("forumpass[$fid]", md5($mybb->user['uid'].$mybb->get_input('pwverify')), null, true);
1862				$showform = false;
1863			}
1864			else
1865			{
1866				eval("\$pwnote = \"".$templates->get("forumdisplay_password_wrongpass")."\";");
1867				$showform = true;
1868			}
1869		}
1870		else
1871		{
1872			if(!forum_password_validated($forum_cache[$fid]))
1873			{
1874				$showform = true;
1875			}
1876			else
1877			{
1878				$showform = false;
1879			}
1880		}
1881	}
1882	else
1883	{
1884		$showform = false;
1885	}
1886
1887	if($return)
1888	{
1889		return $showform;
1890	}
1891
1892	if($showform)
1893	{
1894		if($pid)
1895		{
1896			header("Location: ".$mybb->settings['bburl']."/".get_forum_link($fid));
1897		}
1898		else
1899		{
1900			$_SERVER['REQUEST_URI'] = htmlspecialchars_uni($_SERVER['REQUEST_URI']);
1901			eval("\$pwform = \"".$templates->get("forumdisplay_password")."\";");
1902			output_page($pwform);
1903		}
1904		exit;
1905	}
1906}
1907
1908/**
1909 * Return the permissions for a moderator in a specific forum
1910 *
1911 * @param int $fid The forum ID
1912 * @param int $uid The user ID to fetch permissions for (0 assumes current logged in user)
1913 * @param string $parentslist The parent list for the forum (if blank, will be fetched)
1914 * @return array Array of moderator permissions for the specific forum
1915 */
1916function get_moderator_permissions($fid, $uid=0, $parentslist="")
1917{
1918	global $mybb, $cache, $db;
1919	static $modpermscache;
1920
1921	if($uid < 1)
1922	{
1923		$uid = $mybb->user['uid'];
1924	}
1925
1926	if($uid == 0)
1927	{
1928		return false;
1929	}
1930
1931	if(isset($modpermscache[$fid][$uid]))
1932	{
1933		return $modpermscache[$fid][$uid];
1934	}
1935
1936	if(!$parentslist)
1937	{
1938		$parentslist = explode(',', get_parent_list($fid));
1939	}
1940
1941	// Get user groups
1942	$perms = array();
1943	$user = get_user($uid);
1944
1945	$groups = array($user['usergroup']);
1946
1947	if(!empty($user['additionalgroups']))
1948	{
1949		$extra_groups = explode(",", $user['additionalgroups']);
1950
1951		foreach($extra_groups as $extra_group)
1952		{
1953			$groups[] = $extra_group;
1954		}
1955	}
1956
1957	$mod_cache = $cache->read("moderators");
1958
1959	foreach($mod_cache as $forumid => $forum)
1960	{
1961		if(empty($forum) || !is_array($forum) || !in_array($forumid, $parentslist))
1962		{
1963			// No perms or we're not after this forum
1964			continue;
1965		}
1966
1967		// User settings override usergroup settings
1968		if(!empty($forum['users'][$uid]))
1969		{
1970			$perm = $forum['users'][$uid];
1971			foreach($perm as $action => $value)
1972			{
1973				if(strpos($action, "can") === false)
1974				{
1975					continue;
1976				}
1977
1978				if(!isset($perms[$action]))
1979				{
1980					$perms[$action] = $value;
1981				}
1982				// Figure out the user permissions
1983				else if($value == 0)
1984				{
1985					// The user doesn't have permission to set this action
1986					$perms[$action] = 0;
1987				}
1988				else
1989				{
1990					$perms[$action] = max($perm[$action], $perms[$action]);
1991				}
1992			}
1993		}
1994
1995		foreach($groups as $group)
1996		{
1997			if(empty($forum['usergroups'][$group]) || !is_array($forum['usergroups'][$group]))
1998			{
1999				// There are no permissions set for this group
2000				continue;
2001			}
2002
2003			$perm = $forum['usergroups'][$group];
2004			foreach($perm as $action => $value)
2005			{
2006				if(strpos($action, "can") === false)
2007				{
2008					continue;
2009				}
2010
2011				if(!isset($perms[$action]))
2012				{
2013					$perms[$action] = $value;
2014				}
2015				else
2016				{
2017					$perms[$action] = max($perm[$action], $perms[$action]);
2018				}
2019			}
2020		}
2021	}
2022
2023	$modpermscache[$fid][$uid] = $perms;
2024
2025	return $perms;
2026}
2027
2028/**
2029 * Checks if a moderator has permissions to perform an action in a specific forum
2030 *
2031 * @param int $fid The forum ID (0 assumes global)
2032 * @param string $action The action tyring to be performed. (blank assumes any action at all)
2033 * @param int $uid The user ID (0 assumes current user)
2034 * @return bool Returns true if the user has permission, false if they do not
2035 */
2036function is_moderator($fid=0, $action="", $uid=0)
2037{
2038	global $mybb, $cache, $plugins;
2039
2040	if($uid == 0)
2041	{
2042		$uid = $mybb->user['uid'];
2043	}
2044
2045	if($uid == 0)
2046	{
2047		return false;
2048	}
2049
2050	$user_perms = user_permissions($uid);
2051
2052	$hook_args = array(
2053		'fid' => $fid,
2054		'action' => $action,
2055		'uid' => $uid,
2056	);
2057
2058	$plugins->run_hooks("is_moderator", $hook_args);
2059
2060	if(isset($hook_args['is_moderator']))
2061	{
2062		return (boolean) $hook_args['is_moderator'];
2063	}
2064
2065	if(!empty($user_perms['issupermod']) && $user_perms['issupermod'] == 1)
2066	{
2067		if($fid)
2068		{
2069			$forumpermissions = forum_permissions($fid);
2070			if(!empty($forumpermissions['canview']) && !empty($forumpermissions['canviewthreads']) && empty($forumpermissions['canonlyviewownthreads']))
2071			{
2072				return true;
2073			}
2074			return false;
2075		}
2076		return true;
2077	}
2078	else
2079	{
2080		if(!$fid)
2081		{
2082			$modcache = $cache->read('moderators');
2083			if(!empty($modcache))
2084			{
2085				foreach($modcache as $modusers)
2086				{
2087					if(isset($modusers['users'][$uid]) && $modusers['users'][$uid]['mid'] && (!$action || !empty($modusers['users'][$uid][$action])))
2088					{
2089						return true;
2090					}
2091
2092					$groups = explode(',', $user_perms['all_usergroups']);
2093
2094					foreach($groups as $group)
2095					{
2096						if(trim($group) != '' && isset($modusers['usergroups'][$group]) && (!$action || !empty($modusers['usergroups'][$group][$action])))
2097						{
2098							return true;
2099						}
2100					}
2101				}
2102			}
2103			return false;
2104		}
2105		else
2106		{
2107			$modperms = get_moderator_permissions($fid, $uid);
2108
2109			if(!$action && $modperms)
2110			{
2111				return true;
2112			}
2113			else
2114			{
2115				if(isset($modperms[$action]) && $modperms[$action] == 1)
2116				{
2117					return true;
2118				}
2119				else
2120				{
2121					return false;
2122				}
2123			}
2124		}
2125	}
2126}
2127
2128/**
2129 * Get an array of fids that the forum moderator has access to.
2130 * Do not use for administraotrs or global moderators as they moderate any forum and the function will return false.
2131 *
2132 * @param int $uid The user ID (0 assumes current user)
2133 * @return array|bool an array of the fids the user has moderator access to or bool if called incorrectly.
2134 */
2135function get_moderated_fids($uid=0)
2136{
2137	global $mybb, $cache;
2138
2139	if($uid == 0)
2140	{
2141		$uid = $mybb->user['uid'];
2142	}
2143
2144	if($uid == 0)
2145	{
2146		return array();
2147	}
2148
2149	$user_perms = user_permissions($uid);
2150
2151	if($user_perms['issupermod'] == 1)
2152	{
2153		return false;
2154	}
2155
2156	$fids = array();
2157
2158	$modcache = $cache->read('moderators');
2159	if(!empty($modcache))
2160	{
2161		$groups = explode(',', $user_perms['all_usergroups']);
2162
2163		foreach($modcache as $fid => $forum)
2164		{
2165			if(isset($forum['users'][$uid]) && $forum['users'][$uid]['mid'])
2166			{
2167				$fids[] = $fid;
2168				continue;
2169			}
2170
2171			foreach($groups as $group)
2172			{
2173				if(trim($group) != '' && isset($forum['usergroups'][$group]))
2174				{
2175					$fids[] = $fid;
2176				}
2177			}
2178		}
2179	}
2180
2181	return $fids;
2182}
2183
2184/**
2185 * Generate a list of the posticons.
2186 *
2187 * @return string The template of posticons.
2188 */
2189function get_post_icons()
2190{
2191	global $mybb, $cache, $icon, $theme, $templates, $lang;
2192
2193	if(isset($mybb->input['icon']))
2194	{
2195		$icon = $mybb->get_input('icon');
2196	}
2197
2198	$iconlist = '';
2199	$no_icons_checked = " checked=\"checked\"";
2200	// read post icons from cache, and sort them accordingly
2201	$posticons_cache = (array)$cache->read("posticons");
2202	$posticons = array();
2203	foreach($posticons_cache as $posticon)
2204	{
2205		$posticons[$posticon['name']] = $posticon;
2206	}
2207	krsort($posticons);
2208
2209	foreach($posticons as $dbicon)
2210	{
2211		$dbicon['path'] = str_replace("{theme}", $theme['imgdir'], $dbicon['path']);
2212		$dbicon['path'] = htmlspecialchars_uni($mybb->get_asset_url($dbicon['path']));
2213		$dbicon['name'] = htmlspecialchars_uni($dbicon['name']);
2214
2215		if($icon == $dbicon['iid'])
2216		{
2217			$checked = " checked=\"checked\"";
2218			$no_icons_checked = '';
2219		}
2220		else
2221		{
2222			$checked = '';
2223		}
2224
2225		eval("\$iconlist .= \"".$templates->get("posticons_icon")."\";");
2226	}
2227
2228	if(!empty($iconlist))
2229	{
2230		eval("\$posticons = \"".$templates->get("posticons")."\";");
2231	}
2232	else
2233	{
2234		$posticons = '';
2235	}
2236
2237	return $posticons;
2238}
2239
2240/**
2241 * MyBB setcookie() wrapper.
2242 *
2243 * @param string $name The cookie identifier.
2244 * @param string $value The cookie value.
2245 * @param int|string $expires The timestamp of the expiry date.
2246 * @param boolean $httponly True if setting a HttpOnly cookie (supported by the majority of web browsers)
2247 * @param string $samesite The samesite attribute to prevent CSRF.
2248 */
2249function my_setcookie($name, $value="", $expires="", $httponly=false, $samesite="")
2250{
2251	global $mybb;
2252
2253	if(!$mybb->settings['cookiepath'])
2254	{
2255		$mybb->settings['cookiepath'] = "/";
2256	}
2257
2258	if($expires == -1)
2259	{
2260		$expires = 0;
2261	}
2262	elseif($expires == "" || $expires == null)
2263	{
2264		$expires = TIME_NOW + (60*60*24*365); // Make the cookie expire in a years time
2265	}
2266	else
2267	{
2268		$expires = TIME_NOW + (int)$expires;
2269	}
2270
2271	$mybb->settings['cookiepath'] = str_replace(array("\n","\r"), "", $mybb->settings['cookiepath']);
2272	$mybb->settings['cookiedomain'] = str_replace(array("\n","\r"), "", $mybb->settings['cookiedomain']);
2273	$mybb->settings['cookieprefix'] = str_replace(array("\n","\r", " "), "", $mybb->settings['cookieprefix']);
2274
2275	// Versions of PHP prior to 5.2 do not support HttpOnly cookies and IE is buggy when specifying a blank domain so set the cookie manually
2276	$cookie = "Set-Cookie: {$mybb->settings['cookieprefix']}{$name}=".urlencode($value);
2277
2278	if($expires > 0)
2279	{
2280		$cookie .= "; expires=".@gmdate('D, d-M-Y H:i:s \\G\\M\\T', $expires);
2281	}
2282
2283	if(!empty($mybb->settings['cookiepath']))
2284	{
2285		$cookie .= "; path={$mybb->settings['cookiepath']}";
2286	}
2287
2288	if(!empty($mybb->settings['cookiedomain']))
2289	{
2290		$cookie .= "; domain={$mybb->settings['cookiedomain']}";
2291	}
2292
2293	if($httponly == true)
2294	{
2295		$cookie .= "; HttpOnly";
2296	}
2297
2298	if($samesite != "" && $mybb->settings['cookiesamesiteflag'])
2299	{
2300		$samesite = strtolower($samesite);
2301
2302		if($samesite == "lax" || $samesite == "strict")
2303		{
2304			$cookie .= "; SameSite=".$samesite;
2305		}
2306	}
2307
2308	if($mybb->settings['cookiesecureflag'])
2309	{
2310		$cookie .= "; Secure";
2311	}
2312
2313	$mybb->cookies[$name] = $value;
2314
2315	header($cookie, false);
2316}
2317
2318/**
2319 * Unset a cookie set by MyBB.
2320 *
2321 * @param string $name The cookie identifier.
2322 */
2323function my_unsetcookie($name)
2324{
2325	global $mybb;
2326
2327	$expires = -3600;
2328	my_setcookie($name, "", $expires);
2329
2330	unset($mybb->cookies[$name]);
2331}
2332
2333/**
2334 * Get the contents from a serialised cookie array.
2335 *
2336 * @param string $name The cookie identifier.
2337 * @param int $id The cookie content id.
2338 * @return array|boolean The cookie id's content array or false when non-existent.
2339 */
2340function my_get_array_cookie($name, $id)
2341{
2342	global $mybb;
2343
2344	if(!isset($mybb->cookies['mybb'][$name]))
2345	{
2346		return false;
2347	}
2348
2349	$cookie = my_unserialize($mybb->cookies['mybb'][$name]);
2350
2351	if(is_array($cookie) && isset($cookie[$id]))
2352	{
2353		return $cookie[$id];
2354	}
2355	else
2356	{
2357		return 0;
2358	}
2359}
2360
2361/**
2362 * Set a serialised cookie array.
2363 *
2364 * @param string $name The cookie identifier.
2365 * @param int $id The cookie content id.
2366 * @param string $value The value to set the cookie to.
2367 * @param int|string $expires The timestamp of the expiry date.
2368 */
2369function my_set_array_cookie($name, $id, $value, $expires="")
2370{
2371	global $mybb;
2372
2373	if(isset($mybb->cookies['mybb'][$name]))
2374	{
2375		$newcookie = my_unserialize($mybb->cookies['mybb'][$name]);
2376	}
2377	else
2378	{
2379		$newcookie = array();
2380	}
2381
2382	$newcookie[$id] = $value;
2383	$newcookie = my_serialize($newcookie);
2384	my_setcookie("mybb[$name]", addslashes($newcookie), $expires);
2385
2386	// Make sure our current viarables are up-to-date as well
2387	$mybb->cookies['mybb'][$name] = $newcookie;
2388}
2389
2390/*
2391 * Arbitrary limits for _safe_unserialize()
2392 */
2393define('MAX_SERIALIZED_INPUT_LENGTH', 10240);
2394define('MAX_SERIALIZED_ARRAY_LENGTH', 256);
2395define('MAX_SERIALIZED_ARRAY_DEPTH', 5);
2396
2397/**
2398 * Credits go to https://github.com/piwik
2399 * Safe unserialize() replacement
2400 * - accepts a strict subset of PHP's native my_serialized representation
2401 * - does not unserialize objects
2402 *
2403 * @param string $str
2404 * @return mixed
2405 * @throw Exception if $str is malformed or contains unsupported types (e.g., resources, objects)
2406 */
2407function _safe_unserialize($str)
2408{
2409	if(strlen($str) > MAX_SERIALIZED_INPUT_LENGTH)
2410	{
2411		// input exceeds MAX_SERIALIZED_INPUT_LENGTH
2412		return false;
2413	}
2414
2415	if(empty($str) || !is_string($str))
2416	{
2417		return false;
2418	}
2419
2420	$stack = $list = $expected = array();
2421
2422	/*
2423	 * states:
2424	 *   0 - initial state, expecting a single value or array
2425	 *   1 - terminal state
2426	 *   2 - in array, expecting end of array or a key
2427	 *   3 - in array, expecting value or another array
2428	 */
2429	$state = 0;
2430	while($state != 1)
2431	{
2432		$type = isset($str[0]) ? $str[0] : '';
2433
2434		if($type == '}')
2435		{
2436			$str = substr($str, 1);
2437		}
2438		else if($type == 'N' && $str[1] == ';')
2439		{
2440			$value = null;
2441			$str = substr($str, 2);
2442		}
2443		else if($type == 'b' && preg_match('/^b:([01]);/', $str, $matches))
2444		{
2445			$value = $matches[1] == '1' ? true : false;
2446			$str = substr($str, 4);
2447		}
2448		else if($type == 'i' && preg_match('/^i:(-?[0-9]+);(.*)/s', $str, $matches))
2449		{
2450			$value = (int)$matches[1];
2451			$str = $matches[2];
2452		}
2453		else if($type == 'd' && preg_match('/^d:(-?[0-9]+\.?[0-9]*(E[+-][0-9]+)?);(.*)/s', $str, $matches))
2454		{
2455			$value = (float)$matches[1];
2456			$str = $matches[3];
2457		}
2458		else if($type == 's' && preg_match('/^s:([0-9]+):"(.*)/s', $str, $matches) && substr($matches[2], (int)$matches[1], 2) == '";')
2459		{
2460			$value = substr($matches[2], 0, (int)$matches[1]);
2461			$str = substr($matches[2], (int)$matches[1] + 2);
2462		}
2463		else if($type == 'a' && preg_match('/^a:([0-9]+):{(.*)/s', $str, $matches) && $matches[1] < MAX_SERIALIZED_ARRAY_LENGTH)
2464		{
2465			$expectedLength = (int)$matches[1];
2466			$str = $matches[2];
2467		}
2468		else
2469		{
2470			// object or unknown/malformed type
2471			return false;
2472		}
2473
2474		switch($state)
2475		{
2476			case 3: // in array, expecting value or another array
2477				if($type == 'a')
2478				{
2479					if(count($stack) >= MAX_SERIALIZED_ARRAY_DEPTH)
2480					{
2481						// array nesting exceeds MAX_SERIALIZED_ARRAY_DEPTH
2482						return false;
2483					}
2484
2485					$stack[] = &$list;
2486					$list[$key] = array();
2487					$list = &$list[$key];
2488					$expected[] = $expectedLength;
2489					$state = 2;
2490					break;
2491				}
2492				if($type != '}')
2493				{
2494					$list[$key] = $value;
2495					$state = 2;
2496					break;
2497				}
2498
2499				// missing array value
2500				return false;
2501
2502			case 2: // in array, expecting end of array or a key
2503				if($type == '}')
2504				{
2505					if(count($list) < end($expected))
2506					{
2507						// array size less than expected
2508						return false;
2509					}
2510
2511					unset($list);
2512					$list = &$stack[count($stack)-1];
2513					array_pop($stack);
2514
2515					// go to terminal state if we're at the end of the root array
2516					array_pop($expected);
2517					if(count($expected) == 0) {
2518						$state = 1;
2519					}
2520					break;
2521				}
2522				if($type == 'i' || $type == 's')
2523				{
2524					if(count($list) >= MAX_SERIALIZED_ARRAY_LENGTH)
2525					{
2526						// array size exceeds MAX_SERIALIZED_ARRAY_LENGTH
2527						return false;
2528					}
2529					if(count($list) >= end($expected))
2530					{
2531						// array size exceeds expected length
2532						return false;
2533					}
2534
2535					$key = $value;
2536					$state = 3;
2537					break;
2538				}
2539
2540				// illegal array index type
2541				return false;
2542
2543			case 0: // expecting array or value
2544				if($type == 'a')
2545				{
2546					if(count($stack) >= MAX_SERIALIZED_ARRAY_DEPTH)
2547					{
2548						// array nesting exceeds MAX_SERIALIZED_ARRAY_DEPTH
2549						return false;
2550					}
2551
2552					$data = array();
2553					$list = &$data;
2554					$expected[] = $expectedLength;
2555					$state = 2;
2556					break;
2557				}
2558				if($type != '}')
2559				{
2560					$data = $value;
2561					$state = 1;
2562					break;
2563				}
2564
2565				// not in array
2566				return false;
2567		}
2568	}
2569
2570	if(!empty($str))
2571	{
2572		// trailing data in input
2573		return false;
2574	}
2575	return $data;
2576}
2577
2578/**
2579 * Credits go to https://github.com/piwik
2580 * Wrapper for _safe_unserialize() that handles exceptions and multibyte encoding issue
2581 *
2582 * @param string $str
2583 * @return mixed
2584 */
2585function my_unserialize($str)
2586{
2587	// Ensure we use the byte count for strings even when strlen() is overloaded by mb_strlen()
2588	if(function_exists('mb_internal_encoding') && (((int)ini_get('mbstring.func_overload')) & 2))
2589	{
2590		$mbIntEnc = mb_internal_encoding();
2591		mb_internal_encoding('ASCII');
2592	}
2593
2594	$out = _safe_unserialize($str);
2595
2596	if(isset($mbIntEnc))
2597	{
2598		mb_internal_encoding($mbIntEnc);
2599	}
2600
2601	return $out;
2602}
2603
2604/**
2605 * Credits go to https://github.com/piwik
2606 * Safe serialize() replacement
2607 * - output a strict subset of PHP's native serialized representation
2608 * - does not my_serialize objects
2609 *
2610 * @param mixed $value
2611 * @return string
2612 * @throw Exception if $value is malformed or contains unsupported types (e.g., resources, objects)
2613 */
2614function _safe_serialize( $value )
2615{
2616	if(is_null($value))
2617	{
2618		return 'N;';
2619	}
2620
2621	if(is_bool($value))
2622	{
2623		return 'b:'.(int)$value.';';
2624	}
2625
2626	if(is_int($value))
2627	{
2628		return 'i:'.$value.';';
2629	}
2630
2631	if(is_float($value))
2632	{
2633		return 'd:'.str_replace(',', '.', $value).';';
2634	}
2635
2636	if(is_string($value))
2637	{
2638		return 's:'.strlen($value).':"'.$value.'";';
2639	}
2640
2641	if(is_array($value))
2642	{
2643		$out = '';
2644		foreach($value as $k => $v)
2645		{
2646			$out .= _safe_serialize($k) . _safe_serialize($v);
2647		}
2648
2649		return 'a:'.count($value).':{'.$out.'}';
2650	}
2651
2652	// safe_serialize cannot my_serialize resources or objects
2653	return false;
2654}
2655
2656/**
2657 * Credits go to https://github.com/piwik
2658 * Wrapper for _safe_serialize() that handles exceptions and multibyte encoding issue
2659 *
2660 * @param mixed $value
2661 * @return string
2662*/
2663function my_serialize($value)
2664{
2665	// ensure we use the byte count for strings even when strlen() is overloaded by mb_strlen()
2666	if(function_exists('mb_internal_encoding') && (((int)ini_get('mbstring.func_overload')) & 2))
2667	{
2668		$mbIntEnc = mb_internal_encoding();
2669		mb_internal_encoding('ASCII');
2670	}
2671
2672	$out = _safe_serialize($value);
2673	if(isset($mbIntEnc))
2674	{
2675		mb_internal_encoding($mbIntEnc);
2676	}
2677
2678	return $out;
2679}
2680
2681/**
2682 * Returns the serverload of the system.
2683 *
2684 * @return int The serverload of the system.
2685 */
2686function get_server_load()
2687{
2688	global $mybb, $lang;
2689
2690	$serverload = array();
2691
2692	// DIRECTORY_SEPARATOR checks if running windows
2693	if(DIRECTORY_SEPARATOR != '\\')
2694	{
2695		if(function_exists("sys_getloadavg"))
2696		{
2697			// sys_getloadavg() will return an array with [0] being load within the last minute.
2698			$serverload = sys_getloadavg();
2699			$serverload[0] = round($serverload[0], 4);
2700		}
2701		else if(@file_exists("/proc/loadavg") && $load = @file_get_contents("/proc/loadavg"))
2702		{
2703			$serverload = explode(" ", $load);
2704			$serverload[0] = round($serverload[0], 4);
2705		}
2706		if(!is_numeric($serverload[0]))
2707		{
2708			if($mybb->safemode)
2709			{
2710				return $lang->unknown;
2711			}
2712
2713			// Suhosin likes to throw a warning if exec is disabled then die - weird
2714			if($func_blacklist = @ini_get('suhosin.executor.func.blacklist'))
2715			{
2716				if(strpos(",".$func_blacklist.",", 'exec') !== false)
2717				{
2718					return $lang->unknown;
2719				}
2720			}
2721			// PHP disabled functions?
2722			if($func_blacklist = @ini_get('disable_functions'))
2723			{
2724				if(strpos(",".$func_blacklist.",", 'exec') !== false)
2725				{
2726					return $lang->unknown;
2727				}
2728			}
2729
2730			$load = @exec("uptime");
2731			$load = explode("load average: ", $load);
2732			$serverload = explode(",", $load[1]);
2733			if(!is_array($serverload))
2734			{
2735				return $lang->unknown;
2736			}
2737		}
2738	}
2739	else
2740	{
2741		return $lang->unknown;
2742	}
2743
2744	$returnload = trim($serverload[0]);
2745
2746	return $returnload;
2747}
2748
2749/**
2750 * Returns the amount of memory allocated to the script.
2751 *
2752 * @return int The amount of memory allocated to the script.
2753 */
2754function get_memory_usage()
2755{
2756	if(function_exists('memory_get_peak_usage'))
2757	{
2758		return memory_get_peak_usage(true);
2759	}
2760	elseif(function_exists('memory_get_usage'))
2761	{
2762		return memory_get_usage(true);
2763	}
2764	return false;
2765}
2766
2767/**
2768 * Updates the forum statistics with specific values (or addition/subtraction of the previous value)
2769 *
2770 * @param array $changes Array of items being updated (numthreads,numposts,numusers,numunapprovedthreads,numunapprovedposts,numdeletedposts,numdeletedthreads)
2771 * @param boolean $force Force stats update?
2772 */
2773function update_stats($changes=array(), $force=false)
2774{
2775	global $cache, $db;
2776	static $stats_changes;
2777
2778	if(empty($stats_changes))
2779	{
2780		// Update stats after all changes are done
2781		add_shutdown('update_stats', array(array(), true));
2782	}
2783
2784	if(empty($stats_changes) || $stats_changes['inserted'])
2785	{
2786		$stats_changes = array(
2787			'numthreads' => '+0',
2788			'numposts' => '+0',
2789			'numusers' => '+0',
2790			'numunapprovedthreads' => '+0',
2791			'numunapprovedposts' => '+0',
2792			'numdeletedposts' => '+0',
2793			'numdeletedthreads' => '+0',
2794			'inserted' => false // Reset after changes are inserted into cache
2795		);
2796		$stats = $stats_changes;
2797	}
2798
2799	if($force) // Force writing to cache?
2800	{
2801		if(!empty($changes))
2802		{
2803			// Calculate before writing to cache
2804			update_stats($changes);
2805		}
2806		$stats = $cache->read("stats");
2807		$changes = $stats_changes;
2808	}
2809	else
2810	{
2811		$stats = $stats_changes;
2812	}
2813
2814	$new_stats = array();
2815	$counters = array('numthreads', 'numunapprovedthreads', 'numposts', 'numunapprovedposts', 'numusers', 'numdeletedposts', 'numdeletedthreads');
2816	foreach($counters as $counter)
2817	{
2818		if(array_key_exists($counter, $changes))
2819		{
2820			if(substr($changes[$counter], 0, 2) == "+-")
2821			{
2822				$changes[$counter] = substr($changes[$counter], 1);
2823			}
2824			// Adding or subtracting from previous value?
2825			if(substr($changes[$counter], 0, 1) == "+" || substr($changes[$counter], 0, 1) == "-")
2826			{
2827				if((int)$changes[$counter] != 0)
2828				{
2829					$new_stats[$counter] = $stats[$counter] + $changes[$counter];
2830					if(!$force && (substr($stats[$counter], 0, 1) == "+" || substr($stats[$counter], 0, 1) == "-"))
2831					{
2832						// We had relative values? Then it is still relative
2833						if($new_stats[$counter] >= 0)
2834						{
2835							$new_stats[$counter] = "+{$new_stats[$counter]}";
2836						}
2837					}
2838					// Less than 0? That's bad
2839					elseif($new_stats[$counter] < 0)
2840					{
2841						$new_stats[$counter] = 0;
2842					}
2843				}
2844			}
2845			else
2846			{
2847				$new_stats[$counter] = $changes[$counter];
2848				// Less than 0? That's bad
2849				if($new_stats[$counter] < 0)
2850				{
2851					$new_stats[$counter] = 0;
2852				}
2853			}
2854		}
2855	}
2856
2857	if(!$force)
2858	{
2859		$stats_changes = array_merge($stats, $new_stats); // Overwrite changed values
2860		return;
2861	}
2862
2863	// Fetch latest user if the user count is changing
2864	if(array_key_exists('numusers', $changes))
2865	{
2866		$query = $db->simple_select("users", "uid, username", "", array('order_by' => 'regdate', 'order_dir' => 'DESC', 'limit' => 1));
2867		$lastmember = $db->fetch_array($query);
2868		$new_stats['lastuid'] = $lastmember['uid'];
2869		$new_stats['lastusername'] = $lastmember['username'] = htmlspecialchars_uni($lastmember['username']);
2870	}
2871
2872	if(!empty($new_stats))
2873	{
2874		if(is_array($stats))
2875		{
2876			$stats = array_merge($stats, $new_stats); // Overwrite changed values
2877		}
2878		else
2879		{
2880			$stats = $new_stats;
2881		}
2882	}
2883
2884	// Update stats row for today in the database
2885	$todays_stats = array(
2886		"dateline" => mktime(0, 0, 0, date("m"), date("j"), date("Y")),
2887		"numusers" => (int)$stats['numusers'],
2888		"numthreads" => (int)$stats['numthreads'],
2889		"numposts" => (int)$stats['numposts']
2890	);
2891	$db->replace_query("stats", $todays_stats, "dateline");
2892
2893	$cache->update("stats", $stats, "dateline");
2894	$stats_changes['inserted'] = true;
2895}
2896
2897/**
2898 * Updates the forum counters with a specific value (or addition/subtraction of the previous value)
2899 *
2900 * @param int $fid The forum ID
2901 * @param array $changes Array of items being updated (threads, posts, unapprovedthreads, unapprovedposts, deletedposts, deletedthreads) and their value (ex, 1, +1, -1)
2902 */
2903function update_forum_counters($fid, $changes=array())
2904{
2905	global $db;
2906
2907	$update_query = array();
2908
2909	$counters = array('threads', 'unapprovedthreads', 'posts', 'unapprovedposts', 'deletedposts', 'deletedthreads');
2910
2911	// Fetch above counters for this forum
2912	$query = $db->simple_select("forums", implode(",", $counters), "fid='{$fid}'");
2913	$forum = $db->fetch_array($query);
2914
2915	foreach($counters as $counter)
2916	{
2917		if(array_key_exists($counter, $changes))
2918		{
2919			if(substr($changes[$counter], 0, 2) == "+-")
2920			{
2921				$changes[$counter] = substr($changes[$counter], 1);
2922			}
2923			// Adding or subtracting from previous value?
2924			if(substr($changes[$counter], 0, 1) == "+" || substr($changes[$counter], 0, 1) == "-")
2925			{
2926				if((int)$changes[$counter] != 0)
2927				{
2928					$update_query[$counter] = $forum[$counter] + $changes[$counter];
2929				}
2930			}
2931			else
2932			{
2933				$update_query[$counter] = $changes[$counter];
2934			}
2935
2936			// Less than 0? That's bad
2937			if(isset($update_query[$counter]) && $update_query[$counter] < 0)
2938			{
2939				$update_query[$counter] = 0;
2940			}
2941		}
2942	}
2943
2944	// Only update if we're actually doing something
2945	if(count($update_query) > 0)
2946	{
2947		$db->update_query("forums", $update_query, "fid='".(int)$fid."'");
2948	}
2949
2950	// Guess we should update the statistics too?
2951	$new_stats = array();
2952	if(array_key_exists('threads', $update_query))
2953	{
2954		$threads_diff = $update_query['threads'] - $forum['threads'];
2955		if($threads_diff > -1)
2956		{
2957			$new_stats['numthreads'] = "+{$threads_diff}";
2958		}
2959		else
2960		{
2961			$new_stats['numthreads'] = "{$threads_diff}";
2962		}
2963	}
2964
2965	if(array_key_exists('unapprovedthreads', $update_query))
2966	{
2967		$unapprovedthreads_diff = $update_query['unapprovedthreads'] - $forum['unapprovedthreads'];
2968		if($unapprovedthreads_diff > -1)
2969		{
2970			$new_stats['numunapprovedthreads'] = "+{$unapprovedthreads_diff}";
2971		}
2972		else
2973		{
2974			$new_stats['numunapprovedthreads'] = "{$unapprovedthreads_diff}";
2975		}
2976	}
2977
2978	if(array_key_exists('posts', $update_query))
2979	{
2980		$posts_diff = $update_query['posts'] - $forum['posts'];
2981		if($posts_diff > -1)
2982		{
2983			$new_stats['numposts'] = "+{$posts_diff}";
2984		}
2985		else
2986		{
2987			$new_stats['numposts'] = "{$posts_diff}";
2988		}
2989	}
2990
2991	if(array_key_exists('unapprovedposts', $update_query))
2992	{
2993		$unapprovedposts_diff = $update_query['unapprovedposts'] - $forum['unapprovedposts'];
2994		if($unapprovedposts_diff > -1)
2995		{
2996			$new_stats['numunapprovedposts'] = "+{$unapprovedposts_diff}";
2997		}
2998		else
2999		{
3000			$new_stats['numunapprovedposts'] = "{$unapprovedposts_diff}";
3001		}
3002	}
3003
3004	if(array_key_exists('deletedposts', $update_query))
3005	{
3006		$deletedposts_diff = $update_query['deletedposts'] - $forum['deletedposts'];
3007		if($deletedposts_diff > -1)
3008		{
3009			$new_stats['numdeletedposts'] = "+{$deletedposts_diff}";
3010		}
3011		else
3012		{
3013			$new_stats['numdeletedposts'] = "{$deletedposts_diff}";
3014		}
3015	}
3016
3017	if(array_key_exists('deletedthreads', $update_query))
3018	{
3019		$deletedthreads_diff = $update_query['deletedthreads'] - $forum['deletedthreads'];
3020		if($deletedthreads_diff > -1)
3021		{
3022			$new_stats['numdeletedthreads'] = "+{$deletedthreads_diff}";
3023		}
3024		else
3025		{
3026			$new_stats['numdeletedthreads'] = "{$deletedthreads_diff}";
3027		}
3028	}
3029
3030	if(!empty($new_stats))
3031	{
3032		update_stats($new_stats);
3033	}
3034}
3035
3036/**
3037 * Update the last post information for a specific forum
3038 *
3039 * @param int $fid The forum ID
3040 */
3041function update_forum_lastpost($fid)
3042{
3043	global $db;
3044
3045	// Fetch the last post for this forum
3046	$query = $db->query("
3047		SELECT tid, lastpost, lastposter, lastposteruid, subject
3048		FROM ".TABLE_PREFIX."threads
3049		WHERE fid='{$fid}' AND visible='1' AND closed NOT LIKE 'moved|%'
3050		ORDER BY lastpost DESC
3051		LIMIT 0, 1
3052	");
3053
3054	if($db->num_rows($query) > 0)
3055	{
3056		$lastpost = $db->fetch_array($query);
3057
3058		$updated_forum = array(
3059			"lastpost" => (int)$lastpost['lastpost'],
3060			"lastposter" => $db->escape_string($lastpost['lastposter']),
3061			"lastposteruid" => (int)$lastpost['lastposteruid'],
3062			"lastposttid" => (int)$lastpost['tid'],
3063			"lastpostsubject" => $db->escape_string($lastpost['subject']),
3064		);
3065	}
3066	else {
3067		$updated_forum = array(
3068			"lastpost" => 0,
3069			"lastposter" => '',
3070			"lastposteruid" => 0,
3071			"lastposttid" => 0,
3072			"lastpostsubject" => '',
3073		);
3074	}
3075
3076	$db->update_query("forums", $updated_forum, "fid='{$fid}'");
3077}
3078
3079/**
3080 * Updates the thread counters with a specific value (or addition/subtraction of the previous value)
3081 *
3082 * @param int $tid The thread ID
3083 * @param array $changes Array of items being updated (replies, unapprovedposts, deletedposts, attachmentcount) and their value (ex, 1, +1, -1)
3084 */
3085function update_thread_counters($tid, $changes=array())
3086{
3087	global $db;
3088
3089	$update_query = array();
3090	$tid = (int)$tid;
3091
3092	$counters = array('replies', 'unapprovedposts', 'attachmentcount', 'deletedposts', 'attachmentcount');
3093
3094	// Fetch above counters for this thread
3095	$query = $db->simple_select("threads", implode(",", $counters), "tid='{$tid}'");
3096	$thread = $db->fetch_array($query);
3097
3098	foreach($counters as $counter)
3099	{
3100		if(array_key_exists($counter, $changes))
3101		{
3102			if(substr($changes[$counter], 0, 2) == "+-")
3103			{
3104				$changes[$counter] = substr($changes[$counter], 1);
3105			}
3106			// Adding or subtracting from previous value?
3107			if(substr($changes[$counter], 0, 1) == "+" || substr($changes[$counter], 0, 1) == "-")
3108			{
3109				if((int)$changes[$counter] != 0)
3110				{
3111					$update_query[$counter] = $thread[$counter] + $changes[$counter];
3112				}
3113			}
3114			else
3115			{
3116				$update_query[$counter] = $changes[$counter];
3117			}
3118
3119			// Less than 0? That's bad
3120			if(isset($update_query[$counter]) && $update_query[$counter] < 0)
3121			{
3122				$update_query[$counter] = 0;
3123			}
3124		}
3125	}
3126
3127	$db->free_result($query);
3128
3129	// Only update if we're actually doing something
3130	if(count($update_query) > 0)
3131	{
3132		$db->update_query("threads", $update_query, "tid='{$tid}'");
3133	}
3134}
3135
3136/**
3137 * Update the first post and lastpost data for a specific thread
3138 *
3139 * @param int $tid The thread ID
3140 */
3141function update_thread_data($tid)
3142{
3143	global $db;
3144
3145	$thread = get_thread($tid);
3146
3147	// If this is a moved thread marker, don't update it - we need it to stay as it is
3148	if(strpos($thread['closed'], 'moved|') !== false)
3149	{
3150		return;
3151	}
3152
3153	$query = $db->query("
3154		SELECT u.uid, u.username, p.username AS postusername, p.dateline
3155		FROM ".TABLE_PREFIX."posts p
3156		LEFT JOIN ".TABLE_PREFIX."users u ON (u.uid=p.uid)
3157		WHERE p.tid='$tid' AND p.visible='1'
3158		ORDER BY p.dateline DESC, p.pid DESC
3159		LIMIT 1"
3160	);
3161	$lastpost = $db->fetch_array($query);
3162
3163	$db->free_result($query);
3164
3165	$query = $db->query("
3166		SELECT u.uid, u.username, p.pid, p.username AS postusername, p.dateline
3167		FROM ".TABLE_PREFIX."posts p
3168		LEFT JOIN ".TABLE_PREFIX."users u ON (u.uid=p.uid)
3169		WHERE p.tid='$tid'
3170		ORDER BY p.dateline ASC, p.pid ASC
3171		LIMIT 1
3172	");
3173	$firstpost = $db->fetch_array($query);
3174
3175	$db->free_result($query);
3176
3177	if(empty($firstpost['username']))
3178	{
3179		$firstpost['username'] = $firstpost['postusername'];
3180	}
3181
3182	if(empty($lastpost['username']))
3183	{
3184		$lastpost['username'] = $lastpost['postusername'];
3185	}
3186
3187	if(empty($lastpost['dateline']))
3188	{
3189		$lastpost['username'] = $firstpost['username'];
3190		$lastpost['uid'] = $firstpost['uid'];
3191		$lastpost['dateline'] = $firstpost['dateline'];
3192	}
3193
3194	$lastpost['username'] = $db->escape_string($lastpost['username']);
3195	$firstpost['username'] = $db->escape_string($firstpost['username']);
3196
3197	$update_array = array(
3198		'firstpost' => (int)$firstpost['pid'],
3199		'username' => $firstpost['username'],
3200		'uid' => (int)$firstpost['uid'],
3201		'dateline' => (int)$firstpost['dateline'],
3202		'lastpost' => (int)$lastpost['dateline'],
3203		'lastposter' => $lastpost['username'],
3204		'lastposteruid' => (int)$lastpost['uid'],
3205	);
3206	$db->update_query("threads", $update_array, "tid='{$tid}'");
3207}
3208
3209/**
3210 * Updates the user counters with a specific value (or addition/subtraction of the previous value)
3211 *
3212 * @param int $uid The user ID
3213 * @param array $changes Array of items being updated (postnum, threadnum) and their value (ex, 1, +1, -1)
3214 */
3215function update_user_counters($uid, $changes=array())
3216{
3217	global $db;
3218
3219	$update_query = array();
3220
3221	$counters = array('postnum', 'threadnum');
3222	$uid = (int)$uid;
3223
3224	// Fetch above counters for this user
3225	$query = $db->simple_select("users", implode(",", $counters), "uid='{$uid}'");
3226	$user = $db->fetch_array($query);
3227
3228	foreach($counters as $counter)
3229	{
3230		if(array_key_exists($counter, $changes))
3231		{
3232			if(substr($changes[$counter], 0, 2) == "+-")
3233			{
3234				$changes[$counter] = substr($changes[$counter], 1);
3235			}
3236			// Adding or subtracting from previous value?
3237			if(substr($changes[$counter], 0, 1) == "+" || substr($changes[$counter], 0, 1) == "-")
3238			{
3239				if((int)$changes[$counter] != 0)
3240				{
3241					$update_query[$counter] = $user[$counter] + $changes[$counter];
3242				}
3243			}
3244			else
3245			{
3246				$update_query[$counter] = $changes[$counter];
3247			}
3248
3249			// Less than 0? That's bad
3250			if(isset($update_query[$counter]) && $update_query[$counter] < 0)
3251			{
3252				$update_query[$counter] = 0;
3253			}
3254		}
3255	}
3256
3257	$db->free_result($query);
3258
3259	// Only update if we're actually doing something
3260	if(count($update_query) > 0)
3261	{
3262		$db->update_query("users", $update_query, "uid='{$uid}'");
3263	}
3264}
3265
3266/**
3267 * Deletes a thread from the database
3268 *
3269 * @param int $tid The thread ID
3270 * @return bool
3271 */
3272function delete_thread($tid)
3273{
3274	global $moderation;
3275
3276	if(!is_object($moderation))
3277	{
3278		require_once MYBB_ROOT."inc/class_moderation.php";
3279		$moderation = new Moderation;
3280	}
3281
3282	return $moderation->delete_thread($tid);
3283}
3284
3285/**
3286 * Deletes a post from the database
3287 *
3288 * @param int $pid The thread ID
3289 * @return bool
3290 */
3291function delete_post($pid)
3292{
3293	global $moderation;
3294
3295	if(!is_object($moderation))
3296	{
3297		require_once MYBB_ROOT."inc/class_moderation.php";
3298		$moderation = new Moderation;
3299	}
3300
3301	return $moderation->delete_post($pid);
3302}
3303
3304/**
3305 * Builds a forum jump menu
3306 *
3307 * @param int $pid The parent forum to start with
3308 * @param int $selitem The selected item ID
3309 * @param int $addselect If we need to add select boxes to this cal or not
3310 * @param string $depth The current depth of forums we're at
3311 * @param int $showextras Whether or not to show extra items such as User CP, Forum home
3312 * @param boolean $showall Ignore the showinjump setting and show all forums (for moderation pages)
3313 * @param mixed $permissions deprecated
3314 * @param string $name The name of the forum jump
3315 * @return string Forum jump items
3316 */
3317function build_forum_jump($pid=0, $selitem=0, $addselect=1, $depth="", $showextras=1, $showall=false, $permissions="", $name="fid")
3318{
3319	global $forum_cache, $jumpfcache, $permissioncache, $mybb, $forumjump, $forumjumpbits, $gobutton, $theme, $templates, $lang;
3320
3321	$pid = (int)$pid;
3322
3323	if(!is_array($jumpfcache))
3324	{
3325		if(!is_array($forum_cache))
3326		{
3327			cache_forums();
3328		}
3329
3330		foreach($forum_cache as $fid => $forum)
3331		{
3332			if($forum['active'] != 0)
3333			{
3334				$jumpfcache[$forum['pid']][$forum['disporder']][$forum['fid']] = $forum;
3335			}
3336		}
3337	}
3338
3339	if(!is_array($permissioncache))
3340	{
3341		$permissioncache = forum_permissions();
3342	}
3343
3344	if(isset($jumpfcache[$pid]) && is_array($jumpfcache[$pid]))
3345	{
3346		foreach($jumpfcache[$pid] as $main)
3347		{
3348			foreach($main as $forum)
3349			{
3350				$perms = $permissioncache[$forum['fid']];
3351
3352				if($forum['fid'] != "0" && ($perms['canview'] != 0 || $mybb->settings['hideprivateforums'] == 0) && $forum['linkto'] == '' && ($forum['showinjump'] != 0 || $showall == true))
3353				{
3354					$optionselected = "";
3355
3356					if($selitem == $forum['fid'])
3357					{
3358						$optionselected = 'selected="selected"';
3359					}
3360
3361					$forum['name'] = htmlspecialchars_uni(strip_tags($forum['name']));
3362
3363					eval("\$forumjumpbits .= \"".$templates->get("forumjump_bit")."\";");
3364
3365					if($forum_cache[$forum['fid']])
3366					{
3367						$newdepth = $depth."--";
3368						$forumjumpbits .= build_forum_jump($forum['fid'], $selitem, 0, $newdepth, $showextras, $showall);
3369					}
3370				}
3371			}
3372		}
3373	}
3374
3375	if($addselect)
3376	{
3377		if($showextras == 0)
3378		{
3379			$template = "special";
3380		}
3381		else
3382		{
3383			$template = "advanced";
3384
3385			if(strpos(FORUM_URL, '.html') !== false)
3386			{
3387				$forum_link = "'".str_replace('{fid}', "'+option+'", FORUM_URL)."'";
3388			}
3389			else
3390			{
3391				$forum_link = "'".str_replace('{fid}', "'+option", FORUM_URL);
3392			}
3393		}
3394
3395		eval("\$forumjump = \"".$templates->get("forumjump_".$template)."\";");
3396	}
3397
3398	return $forumjump;
3399}
3400
3401/**
3402 * Returns the extension of a file.
3403 *
3404 * @param string $file The filename.
3405 * @return string The extension of the file.
3406 */
3407function get_extension($file)
3408{
3409	return my_strtolower(my_substr(strrchr($file, "."), 1));
3410}
3411
3412/**
3413 * Generates a random string.
3414 *
3415 * @param int $length The length of the string to generate.
3416 * @param bool $complex Whether to return complex string. Defaults to false
3417 * @return string The random string.
3418 */
3419function random_str($length=8, $complex=false)
3420{
3421	$set = array_merge(range(0, 9), range('A', 'Z'), range('a', 'z'));
3422	$str = array();
3423
3424	// Complex strings have always at least 3 characters, even if $length < 3
3425	if($complex == true)
3426	{
3427		// At least one number
3428		$str[] = $set[my_rand(0, 9)];
3429
3430		// At least one big letter
3431		$str[] = $set[my_rand(10, 35)];
3432
3433		// At least one small letter
3434		$str[] = $set[my_rand(36, 61)];
3435
3436		$length -= 3;
3437	}
3438
3439	for($i = 0; $i < $length; ++$i)
3440	{
3441		$str[] = $set[my_rand(0, 61)];
3442	}
3443
3444	// Make sure they're in random order and convert them to a string
3445	shuffle($str);
3446
3447	return implode($str);
3448}
3449
3450/**
3451 * Formats a username based on their display group
3452 *
3453 * @param string $username The username
3454 * @param int $usergroup The usergroup for the user
3455 * @param int $displaygroup The display group for the user
3456 * @return string The formatted username
3457 */
3458function format_name($username, $usergroup, $displaygroup=0)
3459{
3460	global $groupscache, $cache, $plugins;
3461
3462	static $formattednames = array();
3463
3464	if(!isset($formattednames[$username]))
3465	{
3466		if(!is_array($groupscache))
3467		{
3468			$groupscache = $cache->read("usergroups");
3469		}
3470
3471		if($displaygroup != 0)
3472		{
3473			$usergroup = $displaygroup;
3474		}
3475
3476		$format = "{username}";
3477
3478		if(isset($groupscache[$usergroup]))
3479		{
3480			$ugroup = $groupscache[$usergroup];
3481
3482			if(strpos($ugroup['namestyle'], "{username}") !== false)
3483			{
3484				$format = $ugroup['namestyle'];
3485			}
3486		}
3487
3488		$format = stripslashes($format);
3489
3490		$parameters = compact('username', 'usergroup', 'displaygroup', 'format');
3491
3492		$parameters = $plugins->run_hooks('format_name', $parameters);
3493
3494		$format = $parameters['format'];
3495
3496		$formattednames[$username] = str_replace("{username}", $username, $format);
3497	}
3498
3499	return $formattednames[$username];
3500}
3501
3502/**
3503 * Formats an avatar to a certain dimension
3504 *
3505 * @param string $avatar The avatar file name
3506 * @param string $dimensions Dimensions of the avatar, width x height (e.g. 44|44)
3507 * @param string $max_dimensions The maximum dimensions of the formatted avatar
3508 * @return array Information for the formatted avatar
3509 */
3510function format_avatar($avatar, $dimensions = '', $max_dimensions = '')
3511{
3512	global $mybb, $theme;
3513	static $avatars;
3514
3515	if(!isset($avatars))
3516	{
3517		$avatars = array();
3518	}
3519
3520	if(my_strpos($avatar, '://') !== false && !$mybb->settings['allowremoteavatars'])
3521	{
3522		// Remote avatar, but remote avatars are disallowed.
3523		$avatar = null;
3524	}
3525
3526	if(!$avatar)
3527	{
3528		// Default avatar
3529		if(defined('IN_ADMINCP'))
3530		{
3531			$theme['imgdir'] = '../images';
3532		}
3533
3534		$avatar = str_replace('{theme}', $theme['imgdir'], $mybb->settings['useravatar']);
3535		$dimensions = $mybb->settings['useravatardims'];
3536	}
3537
3538	if(!$max_dimensions)
3539	{
3540		$max_dimensions = $mybb->settings['maxavatardims'];
3541	}
3542
3543	// An empty key wouldn't work so we need to add a fall back
3544	$key = $dimensions;
3545	if(empty($key))
3546	{
3547		$key = 'default';
3548	}
3549	$key2 = $max_dimensions;
3550	if(empty($key2))
3551	{
3552		$key2 = 'default';
3553	}
3554
3555	if(isset($avatars[$avatar][$key][$key2]))
3556	{
3557		return $avatars[$avatar][$key][$key2];
3558	}
3559
3560	$avatar_width_height = '';
3561
3562	if($dimensions)
3563	{
3564		$dimensions = preg_split('/[|x]/', $dimensions);
3565
3566		if($dimensions[0] && $dimensions[1])
3567		{
3568			list($max_width, $max_height) = preg_split('/[|x]/', $max_dimensions);
3569
3570			if(!empty($max_dimensions) && ($dimensions[0] > $max_width || $dimensions[1] > $max_height))
3571			{
3572				require_once MYBB_ROOT."inc/functions_image.php";
3573				$scaled_dimensions = scale_image($dimensions[0], $dimensions[1], $max_width, $max_height);
3574				$avatar_width_height = "width=\"{$scaled_dimensions['width']}\" height=\"{$scaled_dimensions['height']}\"";
3575			}
3576			else
3577			{
3578				$avatar_width_height = "width=\"{$dimensions[0]}\" height=\"{$dimensions[1]}\"";
3579			}
3580		}
3581	}
3582
3583	$avatars[$avatar][$key][$key2] = array(
3584		'image' => htmlspecialchars_uni($mybb->get_asset_url($avatar)),
3585		'width_height' => $avatar_width_height
3586	);
3587
3588	return $avatars[$avatar][$key][$key2];
3589}
3590
3591/**
3592 * Build the javascript based MyCode inserter.
3593 *
3594 * @param string $bind The ID of the textarea to bind to. Defaults to "message".
3595 * @param bool $smilies Whether to include smilies. Defaults to true.
3596 *
3597 * @return string The MyCode inserter
3598 */
3599function build_mycode_inserter($bind="message", $smilies = true)
3600{
3601	global $db, $mybb, $theme, $templates, $lang, $plugins, $smiliecache, $cache;
3602
3603	if($mybb->settings['bbcodeinserter'] != 0)
3604	{
3605		$editor_lang_strings = array(
3606			"editor_bold" => "Bold",
3607			"editor_italic" => "Italic",
3608			"editor_underline" => "Underline",
3609			"editor_strikethrough" => "Strikethrough",
3610			"editor_subscript" => "Subscript",
3611			"editor_superscript" => "Superscript",
3612			"editor_alignleft" => "Align left",
3613			"editor_center" => "Center",
3614			"editor_alignright" => "Align right",
3615			"editor_justify" => "Justify",
3616			"editor_fontname" => "Font Name",
3617			"editor_fontsize" => "Font Size",
3618			"editor_fontcolor" => "Font Color",
3619			"editor_removeformatting" => "Remove Formatting",
3620			"editor_cut" => "Cut",
3621			"editor_cutnosupport" => "Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X",
3622			"editor_copy" => "Copy",
3623			"editor_copynosupport" => "Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C",
3624			"editor_paste" => "Paste",
3625			"editor_pastenosupport" => "Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V",
3626			"editor_pasteentertext" => "Paste your text inside the following box:",
3627			"editor_pastetext" => "PasteText",
3628			"editor_numlist" => "Numbered list",
3629			"editor_bullist" => "Bullet list",
3630			"editor_undo" => "Undo",
3631			"editor_redo" => "Redo",
3632			"editor_rows" => "Rows:",
3633			"editor_cols" => "Cols:",
3634			"editor_inserttable" => "Insert a table",
3635			"editor_inserthr" => "Insert a horizontal rule",
3636			"editor_code" => "Code",
3637			"editor_width" => "Width (optional):",
3638			"editor_height" => "Height (optional):",
3639			"editor_insertimg" => "Insert an image",
3640			"editor_email" => "E-mail:",
3641			"editor_insertemail" => "Insert an email",
3642			"editor_url" => "URL:",
3643			"editor_insertlink" => "Insert a link",
3644			"editor_unlink" => "Unlink",
3645			"editor_more" => "More",
3646			"editor_insertemoticon" => "Insert an emoticon",
3647			"editor_videourl" => "Video URL:",
3648			"editor_videotype" => "Video Type:",
3649			"editor_insert" => "Insert",
3650			"editor_insertyoutubevideo" => "Insert a YouTube video",
3651			"editor_currentdate" => "Insert current date",
3652			"editor_currenttime" => "Insert current time",
3653			"editor_print" => "Print",
3654			"editor_viewsource" => "View source",
3655			"editor_description" => "Description (optional):",
3656			"editor_enterimgurl" => "Enter the image URL:",
3657			"editor_enteremail" => "Enter the e-mail address:",
3658			"editor_enterdisplayedtext" => "Enter the displayed text:",
3659			"editor_enterurl" => "Enter URL:",
3660			"editor_enteryoutubeurl" => "Enter the YouTube video URL or ID:",
3661			"editor_insertquote" => "Insert a Quote",
3662			"editor_invalidyoutube" => "Invalid YouTube video",
3663			"editor_dailymotion" => "Dailymotion",
3664			"editor_metacafe" => "MetaCafe",
3665			"editor_mixer" => "Mixer",
3666			"editor_vimeo" => "Vimeo",
3667			"editor_youtube" => "Youtube",
3668			"editor_facebook" => "Facebook",
3669			"editor_liveleak" => "LiveLeak",
3670			"editor_insertvideo" => "Insert a video",
3671			"editor_php" => "PHP",
3672			"editor_maximize" => "Maximize"
3673		);
3674		$editor_language = "(function ($) {\n$.sceditor.locale[\"mybblang\"] = {\n";
3675
3676		$editor_lang_strings = $plugins->run_hooks("mycode_add_codebuttons", $editor_lang_strings);
3677
3678		$editor_languages_count = count($editor_lang_strings);
3679		$i = 0;
3680		foreach($editor_lang_strings as $lang_string => $key)
3681		{
3682			$i++;
3683			$js_lang_string = str_replace("\"", "\\\"", $key);
3684			$string = str_replace("\"", "\\\"", $lang->$lang_string);
3685			$editor_language .= "\t\"{$js_lang_string}\": \"{$string}\"";
3686
3687			if($i < $editor_languages_count)
3688			{
3689				$editor_language .= ",";
3690			}
3691
3692			$editor_language .= "\n";
3693		}
3694
3695		$editor_language .= "}})(jQuery);";
3696
3697		if(defined("IN_ADMINCP"))
3698		{
3699			global $page;
3700			$codeinsert = $page->build_codebuttons_editor($bind, $editor_language, $smilies);
3701		}
3702		else
3703		{
3704			// Smilies
3705			$emoticon = "";
3706			$emoticons_enabled = "false";
3707			if($smilies)
3708			{
3709				if(!$smiliecache)
3710				{
3711					if(!isset($smilie_cache) || !is_array($smilie_cache))
3712					{
3713						$smilie_cache = $cache->read("smilies");
3714					}
3715					foreach($smilie_cache as $smilie)
3716					{
3717						$smilie['image'] = str_replace("{theme}", $theme['imgdir'], $smilie['image']);
3718						$smiliecache[$smilie['sid']] = $smilie;
3719					}
3720				}
3721
3722				if($mybb->settings['smilieinserter'] && $mybb->settings['smilieinsertercols'] && $mybb->settings['smilieinsertertot'] && !empty($smiliecache))
3723				{
3724					$emoticon = ",emoticon";
3725				}
3726				$emoticons_enabled = "true";
3727
3728				unset($smilie);
3729
3730				if(is_array($smiliecache))
3731				{
3732					reset($smiliecache);
3733
3734					$dropdownsmilies = $moresmilies = $hiddensmilies = "";
3735					$i = 0;
3736
3737					foreach($smiliecache as $smilie)
3738					{
3739						$finds = explode("\n", $smilie['find']);
3740						$finds_count = count($finds);
3741
3742						// Only show the first text to replace in the box
3743						$smilie['find'] = $finds[0];
3744
3745						$find = str_replace(array('\\', '"'), array('\\\\', '\"'), htmlspecialchars_uni($smilie['find']));
3746						$image = htmlspecialchars_uni($mybb->get_asset_url($smilie['image']));
3747						$image = str_replace(array('\\', '"'), array('\\\\', '\"'), $image);
3748
3749						if(!$mybb->settings['smilieinserter'] || !$mybb->settings['smilieinsertercols'] || !$mybb->settings['smilieinsertertot'] || !$smilie['showclickable'])
3750						{
3751							$hiddensmilies .= '"'.$find.'": "'.$image.'",';
3752						}
3753						elseif($i < $mybb->settings['smilieinsertertot'])
3754						{
3755							$dropdownsmilies .= '"'.$find.'": "'.$image.'",';
3756							++$i;
3757						}
3758						else
3759						{
3760							$moresmilies .= '"'.$find.'": "'.$image.'",';
3761						}
3762
3763						for($j = 1; $j < $finds_count; ++$j)
3764						{
3765							$find = str_replace(array('\\', '"'), array('\\\\', '\"'), htmlspecialchars_uni($finds[$j]));
3766							$hiddensmilies .= '"'.$find.'": "'.$image.'",';
3767						}
3768					}
3769				}
3770			}
3771
3772			$basic1 = $basic2 = $align = $font = $size = $color = $removeformat = $email = $link = $list = $code = $sourcemode = "";
3773
3774			if($mybb->settings['allowbasicmycode'] == 1)
3775			{
3776				$basic1 = "bold,italic,underline,strike|";
3777				$basic2 = "horizontalrule,";
3778			}
3779
3780			if($mybb->settings['allowalignmycode'] == 1)
3781			{
3782				$align = "left,center,right,justify|";
3783			}
3784
3785			if($mybb->settings['allowfontmycode'] == 1)
3786			{
3787				$font = "font,";
3788			}
3789
3790			if($mybb->settings['allowsizemycode'] == 1)
3791			{
3792				$size = "size,";
3793			}
3794
3795			if($mybb->settings['allowcolormycode'] == 1)
3796			{
3797				$color = "color,";
3798			}
3799
3800			if($mybb->settings['allowfontmycode'] == 1 || $mybb->settings['allowsizemycode'] == 1 || $mybb->settings['allowcolormycode'] == 1)
3801			{
3802				$removeformat = "removeformat|";
3803			}
3804
3805			if($mybb->settings['allowemailmycode'] == 1)
3806			{
3807				$email = "email,";
3808			}
3809
3810			if($mybb->settings['allowlinkmycode'] == 1)
3811			{
3812				$link = "link,unlink";
3813			}
3814
3815			if($mybb->settings['allowlistmycode'] == 1)
3816			{
3817				$list = "bulletlist,orderedlist|";
3818			}
3819
3820			if($mybb->settings['allowcodemycode'] == 1)
3821			{
3822				$code = "code,php,";
3823			}
3824
3825			if($mybb->user['sourceeditor'] == 1)
3826			{
3827				$sourcemode = "MyBBEditor.sourceMode(true);";
3828			}
3829
3830			eval("\$codeinsert = \"".$templates->get("codebuttons")."\";");
3831		}
3832	}
3833
3834	return $codeinsert;
3835}
3836
3837/**
3838 * @param int $tid
3839 * @param array $postoptions The options carried with form submit
3840 *
3841 * @return string Predefined / updated subscription method of the thread for the user
3842 */
3843function get_subscription_method($tid = 0, $postoptions = array())
3844{
3845	global $mybb;
3846
3847	$subscription_methods = array('', 'none', 'email', 'pm'); // Define methods
3848	$subscription_method = (int)$mybb->user['subscriptionmethod']; // Set user default
3849
3850	// If no user default method available then reset method
3851	if(!$subscription_method)
3852	{
3853		$subscription_method = 0;
3854	}
3855
3856	// Return user default if no thread id available, in case
3857	if(!(int)$tid || (int)$tid <= 0)
3858	{
3859		return $subscription_methods[$subscription_method];
3860	}
3861
3862	// If method not predefined set using data from database
3863	if(isset($postoptions['subscriptionmethod']))
3864	{
3865		$method = trim($postoptions['subscriptionmethod']);
3866		return (in_array($method, $subscription_methods)) ? $method : $subscription_methods[0];
3867	}
3868	else
3869	{
3870		global $db;
3871
3872		$query = $db->simple_select("threadsubscriptions", "tid, notification", "tid='".(int)$tid."' AND uid='".$mybb->user['uid']."'", array('limit' => 1));
3873		$subscription = $db->fetch_array($query);
3874
3875		if(!empty($subscription) && $subscription['tid'])
3876		{
3877			$subscription_method = (int)$subscription['notification'] + 1;
3878		}
3879	}
3880
3881	return $subscription_methods[$subscription_method];
3882}
3883
3884/**
3885 * Build the javascript clickable smilie inserter
3886 *
3887 * @return string The clickable smilies list
3888 */
3889function build_clickable_smilies()
3890{
3891	global $cache, $smiliecache, $theme, $templates, $lang, $mybb, $smiliecount;
3892
3893	if($mybb->settings['smilieinserter'] != 0 && $mybb->settings['smilieinsertercols'] && $mybb->settings['smilieinsertertot'])
3894	{
3895		if(!$smiliecount)
3896		{
3897			$smilie_cache = $cache->read("smilies");
3898			$smiliecount = count($smilie_cache);
3899		}
3900
3901		if(!$smiliecache)
3902		{
3903			if(!is_array($smilie_cache))
3904			{
3905				$smilie_cache = $cache->read("smilies");
3906			}
3907			foreach($smilie_cache as $smilie)
3908			{
3909				$smilie['image'] = str_replace("{theme}", $theme['imgdir'], $smilie['image']);
3910				$smiliecache[$smilie['sid']] = $smilie;
3911			}
3912		}
3913
3914		unset($smilie);
3915
3916		if(is_array($smiliecache))
3917		{
3918			reset($smiliecache);
3919
3920			$getmore = '';
3921			if($mybb->settings['smilieinsertertot'] >= $smiliecount)
3922			{
3923				$mybb->settings['smilieinsertertot'] = $smiliecount;
3924			}
3925			else if($mybb->settings['smilieinsertertot'] < $smiliecount)
3926			{
3927				$smiliecount = $mybb->settings['smilieinsertertot'];
3928				eval("\$getmore = \"".$templates->get("smilieinsert_getmore")."\";");
3929			}
3930
3931			$smilies = $smilie_icons = '';
3932			$counter = 0;
3933			$i = 0;
3934
3935			$extra_class = '';
3936			foreach($smiliecache as $smilie)
3937			{
3938				if($i < $mybb->settings['smilieinsertertot'] && $smilie['showclickable'] != 0)
3939				{
3940					$smilie['image'] = str_replace("{theme}", $theme['imgdir'], $smilie['image']);
3941					$smilie['image'] = htmlspecialchars_uni($mybb->get_asset_url($smilie['image']));
3942					$smilie['name'] = htmlspecialchars_uni($smilie['name']);
3943
3944					// Only show the first text to replace in the box
3945					$temp = explode("\n", $smilie['find']); // assign to temporary variable for php 5.3 compatibility
3946					$smilie['find'] = $temp[0];
3947
3948					$find = str_replace(array('\\', "'"), array('\\\\', "\'"), htmlspecialchars_uni($smilie['find']));
3949
3950					$onclick = " onclick=\"MyBBEditor.insertText(' $find ');\"";
3951					$extra_class = ' smilie_pointer';
3952					eval('$smilie = "'.$templates->get('smilie', 1, 0).'";');
3953					eval("\$smilie_icons .= \"".$templates->get("smilieinsert_smilie")."\";");
3954					++$i;
3955					++$counter;
3956
3957					if($counter == $mybb->settings['smilieinsertercols'])
3958					{
3959						$counter = 0;
3960						eval("\$smilies .= \"".$templates->get("smilieinsert_row")."\";");
3961						$smilie_icons = '';
3962					}
3963				}
3964			}
3965
3966			if($counter != 0)
3967			{
3968				$colspan = $mybb->settings['smilieinsertercols'] - $counter;
3969				eval("\$smilies .= \"".$templates->get("smilieinsert_row_empty")."\";");
3970			}
3971
3972			eval("\$clickablesmilies = \"".$templates->get("smilieinsert")."\";");
3973		}
3974		else
3975		{
3976			$clickablesmilies = "";
3977		}
3978	}
3979	else
3980	{
3981		$clickablesmilies = "";
3982	}
3983
3984	return $clickablesmilies;
3985}
3986
3987/**
3988 * Builds thread prefixes and returns a selected prefix (or all)
3989 *
3990 *  @param int $pid The prefix ID (0 to return all)
3991 *  @return array The thread prefix's values (or all thread prefixes)
3992 */
3993function build_prefixes($pid=0)
3994{
3995	global $cache;
3996	static $prefixes_cache;
3997
3998	if(is_array($prefixes_cache))
3999	{
4000		if($pid > 0 && is_array($prefixes_cache[$pid]))
4001		{
4002			return $prefixes_cache[$pid];
4003		}
4004
4005		return $prefixes_cache;
4006	}
4007
4008	$prefix_cache = $cache->read("threadprefixes");
4009
4010	if(!is_array($prefix_cache))
4011	{
4012		// No cache
4013		$prefix_cache = $cache->read("threadprefixes", true);
4014
4015		if(!is_array($prefix_cache))
4016		{
4017			return array();
4018		}
4019	}
4020
4021	$prefixes_cache = array();
4022	foreach($prefix_cache as $prefix)
4023	{
4024		$prefixes_cache[$prefix['pid']] = $prefix;
4025	}
4026
4027	if($pid != 0 && is_array($prefixes_cache[$pid]))
4028	{
4029		return $prefixes_cache[$pid];
4030	}
4031	else if(!empty($prefixes_cache))
4032	{
4033		return $prefixes_cache;
4034	}
4035
4036	return false;
4037}
4038
4039/**
4040 * Build the thread prefix selection menu for the current user
4041 *
4042 *  @param int|string $fid The forum ID (integer ID or string all)
4043 *  @param int|string $selected_pid The selected prefix ID (integer ID or string any)
4044 *  @param int $multiple Allow multiple prefix selection
4045 *  @param int $previous_pid The previously selected prefix ID
4046 *  @return string The thread prefix selection menu
4047 */
4048function build_prefix_select($fid, $selected_pid=0, $multiple=0, $previous_pid=0)
4049{
4050	global $cache, $db, $lang, $mybb, $templates;
4051
4052	if($fid != 'all')
4053	{
4054		$fid = (int)$fid;
4055	}
4056
4057	$prefix_cache = build_prefixes(0);
4058	if(empty($prefix_cache))
4059	{
4060		// We've got no prefixes to show
4061		return '';
4062	}
4063
4064	// Go through each of our prefixes and decide which ones we can use
4065	$prefixes = array();
4066	foreach($prefix_cache as $prefix)
4067	{
4068		if($fid != "all" && $prefix['forums'] != "-1")
4069		{
4070			// Decide whether this prefix can be used in our forum
4071			$forums = explode(",", $prefix['forums']);
4072
4073			if(!in_array($fid, $forums) && $prefix['pid'] != $previous_pid)
4074			{
4075				// This prefix is not in our forum list
4076				continue;
4077			}
4078		}
4079
4080		if(is_member($prefix['groups']) || $prefix['pid'] == $previous_pid)
4081		{
4082			// The current user can use this prefix
4083			$prefixes[$prefix['pid']] = $prefix;
4084		}
4085	}
4086
4087	if(empty($prefixes))
4088	{
4089		return '';
4090	}
4091
4092	$prefixselect = $prefixselect_prefix = '';
4093
4094	if($multiple == 1)
4095	{
4096		$any_selected = "";
4097		if($selected_pid == 'any')
4098		{
4099			$any_selected = " selected=\"selected\"";
4100		}
4101	}
4102
4103	$default_selected = "";
4104	if(((int)$selected_pid == 0) && $selected_pid != 'any')
4105	{
4106		$default_selected = " selected=\"selected\"";
4107	}
4108
4109	foreach($prefixes as $prefix)
4110	{
4111		$selected = "";
4112		if($prefix['pid'] == $selected_pid)
4113		{
4114			$selected = " selected=\"selected\"";
4115		}
4116
4117		$prefix['prefix'] = htmlspecialchars_uni($prefix['prefix']);
4118		eval("\$prefixselect_prefix .= \"".$templates->get("post_prefixselect_prefix")."\";");
4119	}
4120
4121	if($multiple != 0)
4122	{
4123		eval("\$prefixselect = \"".$templates->get("post_prefixselect_multiple")."\";");
4124	}
4125	else
4126	{
4127		eval("\$prefixselect = \"".$templates->get("post_prefixselect_single")."\";");
4128	}
4129
4130	return $prefixselect;
4131}
4132
4133/**
4134 * Build the thread prefix selection menu for a forum without group permission checks
4135 *
4136 *  @param int $fid The forum ID (integer ID)
4137 *  @param int $selected_pid The selected prefix ID (integer ID)
4138 *  @return string The thread prefix selection menu
4139 */
4140function build_forum_prefix_select($fid, $selected_pid=0)
4141{
4142	global $cache, $db, $lang, $mybb, $templates;
4143
4144	$fid = (int)$fid;
4145
4146	$prefix_cache = build_prefixes(0);
4147	if(empty($prefix_cache))
4148	{
4149		// We've got no prefixes to show
4150		return '';
4151	}
4152
4153	// Go through each of our prefixes and decide which ones we can use
4154	$prefixes = array();
4155	foreach($prefix_cache as $prefix)
4156	{
4157		if($prefix['forums'] != "-1")
4158		{
4159			// Decide whether this prefix can be used in our forum
4160			$forums = explode(",", $prefix['forums']);
4161
4162			if(in_array($fid, $forums))
4163			{
4164				// This forum can use this prefix!
4165				$prefixes[$prefix['pid']] = $prefix;
4166			}
4167		}
4168		else
4169		{
4170			// This prefix is for anybody to use...
4171			$prefixes[$prefix['pid']] = $prefix;
4172		}
4173	}
4174
4175	if(empty($prefixes))
4176	{
4177		return '';
4178	}
4179
4180	$default_selected = array('all' => '', 'none' => '', 'any' => '');
4181	$selected_pid = (int)$selected_pid;
4182
4183	if($selected_pid == 0)
4184	{
4185		$default_selected['all'] = ' selected="selected"';
4186	}
4187	else if($selected_pid == -1)
4188	{
4189		$default_selected['none'] = ' selected="selected"';
4190	}
4191	else if($selected_pid == -2)
4192	{
4193		$default_selected['any'] = ' selected="selected"';
4194	}
4195
4196	$prefixselect_prefix = '';
4197	foreach($prefixes as $prefix)
4198	{
4199		$selected = '';
4200		if($prefix['pid'] == $selected_pid)
4201		{
4202			$selected = ' selected="selected"';
4203		}
4204
4205		$prefix['prefix'] = htmlspecialchars_uni($prefix['prefix']);
4206		eval('$prefixselect_prefix .= "'.$templates->get("forumdisplay_threadlist_prefixes_prefix").'";');
4207	}
4208
4209	eval('$prefixselect = "'.$templates->get("forumdisplay_threadlist_prefixes").'";');
4210	return $prefixselect;
4211}
4212
4213/**
4214 * Gzip encodes text to a specified level
4215 *
4216 * @param string $contents The string to encode
4217 * @param int $level The level (1-9) to encode at
4218 * @return string The encoded string
4219 */
4220function gzip_encode($contents, $level=1)
4221{
4222	if(function_exists("gzcompress") && function_exists("crc32") && !headers_sent() && !(ini_get('output_buffering') && my_strpos(' '.ini_get('output_handler'), 'ob_gzhandler')))
4223	{
4224		$httpaccept_encoding = '';
4225
4226		if(isset($_SERVER['HTTP_ACCEPT_ENCODING']))
4227		{
4228			$httpaccept_encoding = $_SERVER['HTTP_ACCEPT_ENCODING'];
4229		}
4230
4231		if(my_strpos(" ".$httpaccept_encoding, "x-gzip"))
4232		{
4233			$encoding = "x-gzip";
4234		}
4235
4236		if(my_strpos(" ".$httpaccept_encoding, "gzip"))
4237		{
4238			$encoding = "gzip";
4239		}
4240
4241		if(isset($encoding))
4242		{
4243			header("Content-Encoding: $encoding");
4244
4245			if(function_exists("gzencode"))
4246			{
4247				$contents = gzencode($contents, $level);
4248			}
4249			else
4250			{
4251				$size = strlen($contents);
4252				$crc = crc32($contents);
4253				$gzdata = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff";
4254				$gzdata .= my_substr(gzcompress($contents, $level), 2, -4);
4255				$gzdata .= pack("V", $crc);
4256				$gzdata .= pack("V", $size);
4257				$contents = $gzdata;
4258			}
4259		}
4260	}
4261
4262	return $contents;
4263}
4264
4265/**
4266 * Log the actions of a moderator.
4267 *
4268 * @param array $data The data of the moderator's action.
4269 * @param string $action The message to enter for the action the moderator performed.
4270 */
4271function log_moderator_action($data, $action="")
4272{
4273	global $mybb, $db, $session;
4274
4275	$fid = 0;
4276	if(isset($data['fid']))
4277	{
4278		$fid = (int)$data['fid'];
4279		unset($data['fid']);
4280	}
4281
4282	$tid = 0;
4283	if(isset($data['tid']))
4284	{
4285		$tid = (int)$data['tid'];
4286		unset($data['tid']);
4287	}
4288
4289	$pid = 0;
4290	if(isset($data['pid']))
4291	{
4292		$pid = (int)$data['pid'];
4293		unset($data['pid']);
4294	}
4295
4296	$tids = array();
4297	if(isset($data['tids']))
4298	{
4299		$tids = (array)$data['tids'];
4300		unset($data['tids']);
4301	}
4302
4303	// Any remaining extra data - we my_serialize and insert in to its own column
4304	if(is_array($data))
4305	{
4306		$data = my_serialize($data);
4307	}
4308
4309	$sql_array = array(
4310		"uid" => (int)$mybb->user['uid'],
4311		"dateline" => TIME_NOW,
4312		"fid" => (int)$fid,
4313		"tid" => $tid,
4314		"pid" => $pid,
4315		"action" => $db->escape_string($action),
4316		"data" => $db->escape_string($data),
4317		"ipaddress" => $db->escape_binary($session->packedip)
4318	);
4319
4320	if($tids)
4321	{
4322		$multiple_sql_array = array();
4323
4324		foreach($tids as $tid)
4325		{
4326			$sql_array['tid'] = (int)$tid;
4327			$multiple_sql_array[] = $sql_array;
4328		}
4329
4330		$db->insert_query_multiple("moderatorlog", $multiple_sql_array);
4331	}
4332	else
4333	{
4334		$db->insert_query("moderatorlog", $sql_array);
4335	}
4336}
4337
4338/**
4339 * Get the formatted reputation for a user.
4340 *
4341 * @param int $reputation The reputation value
4342 * @param int $uid The user ID (if not specified, the generated reputation will not be a link)
4343 * @return string The formatted repuation
4344 */
4345function get_reputation($reputation, $uid=0)
4346{
4347	global $theme, $templates;
4348
4349	$display_reputation = $reputation_class = '';
4350	if($reputation < 0)
4351	{
4352		$reputation_class = "reputation_negative";
4353	}
4354	elseif($reputation > 0)
4355	{
4356		$reputation_class = "reputation_positive";
4357	}
4358	else
4359	{
4360		$reputation_class = "reputation_neutral";
4361	}
4362
4363	$reputation = my_number_format($reputation);
4364
4365	if($uid != 0)
4366	{
4367		eval("\$display_reputation = \"".$templates->get("postbit_reputation_formatted_link")."\";");
4368	}
4369	else
4370	{
4371		eval("\$display_reputation = \"".$templates->get("postbit_reputation_formatted")."\";");
4372	}
4373
4374	return $display_reputation;
4375}
4376
4377/**
4378 * Fetch a color coded version of a warning level (based on it's percentage)
4379 *
4380 * @param int $level The warning level (percentage of 100)
4381 * @return string Formatted warning level
4382 */
4383function get_colored_warning_level($level)
4384{
4385	global $templates;
4386
4387	$warning_class = '';
4388	if($level >= 80)
4389	{
4390		$warning_class = "high_warning";
4391	}
4392	else if($level >= 50)
4393	{
4394		$warning_class = "moderate_warning";
4395	}
4396	else if($level >= 25)
4397	{
4398		$warning_class = "low_warning";
4399	}
4400	else
4401	{
4402		$warning_class = "normal_warning";
4403	}
4404
4405	eval("\$level = \"".$templates->get("postbit_warninglevel_formatted")."\";");
4406	return $level;
4407}
4408
4409/**
4410 * Fetch the IP address of the current user.
4411 *
4412 * @return string The IP address.
4413 */
4414function get_ip()
4415{
4416	global $mybb, $plugins;
4417
4418	$ip = strtolower($_SERVER['REMOTE_ADDR']);
4419
4420	if($mybb->settings['ip_forwarded_check'])
4421	{
4422		$addresses = array();
4423
4424		if(isset($_SERVER['HTTP_X_FORWARDED_FOR']))
4425		{
4426			$addresses = explode(',', strtolower($_SERVER['HTTP_X_FORWARDED_FOR']));
4427		}
4428		elseif(isset($_SERVER['HTTP_X_REAL_IP']))
4429		{
4430			$addresses = explode(',', strtolower($_SERVER['HTTP_X_REAL_IP']));
4431		}
4432
4433		if(is_array($addresses))
4434		{
4435			foreach($addresses as $val)
4436			{
4437				$val = trim($val);
4438				// Validate IP address and exclude private addresses
4439				if(my_inet_ntop(my_inet_pton($val)) == $val && !preg_match("#^(10\.|172\.(1[6-9]|2[0-9]|3[0-1])\.|192\.168\.|fe80:|fe[c-f][0-f]:|f[c-d][0-f]{2}:)#", $val))
4440				{
4441					$ip = $val;
4442					break;
4443				}
4444			}
4445		}
4446	}
4447
4448	if(!$ip)
4449	{
4450		if(isset($_SERVER['HTTP_CLIENT_IP']))
4451		{
4452			$ip = strtolower($_SERVER['HTTP_CLIENT_IP']);
4453		}
4454	}
4455
4456	if($plugins)
4457	{
4458		$ip_array = array("ip" => &$ip); // Used for backwards compatibility on this hook with the updated run_hooks() function.
4459		$plugins->run_hooks("get_ip", $ip_array);
4460	}
4461
4462	return $ip;
4463}
4464
4465/**
4466 * Fetch the friendly size (GB, MB, KB, B) for a specified file size.
4467 *
4468 * @param int $size The size in bytes
4469 * @return string The friendly file size
4470 */
4471function get_friendly_size($size)
4472{
4473	global $lang;
4474
4475	if(!is_numeric($size))
4476	{
4477		return $lang->na;
4478	}
4479
4480	// Yottabyte (1024 Zettabytes)
4481	if($size >= 1208925819614629174706176)
4482	{
4483		$size = my_number_format(round(($size / 1208925819614629174706176), 2))." ".$lang->size_yb;
4484	}
4485	// Zetabyte (1024 Exabytes)
4486	elseif($size >= 1180591620717411303424)
4487	{
4488		$size = my_number_format(round(($size / 1180591620717411303424), 2))." ".$lang->size_zb;
4489	}
4490	// Exabyte (1024 Petabytes)
4491	elseif($size >= 1152921504606846976)
4492	{
4493		$size = my_number_format(round(($size / 1152921504606846976), 2))." ".$lang->size_eb;
4494	}
4495	// Petabyte (1024 Terabytes)
4496	elseif($size >= 1125899906842624)
4497	{
4498		$size = my_number_format(round(($size / 1125899906842624), 2))." ".$lang->size_pb;
4499	}
4500	// Terabyte (1024 Gigabytes)
4501	elseif($size >= 1099511627776)
4502	{
4503		$size = my_number_format(round(($size / 1099511627776), 2))." ".$lang->size_tb;
4504	}
4505	// Gigabyte (1024 Megabytes)
4506	elseif($size >= 1073741824)
4507	{
4508		$size = my_number_format(round(($size / 1073741824), 2))." ".$lang->size_gb;
4509	}
4510	// Megabyte (1024 Kilobytes)
4511	elseif($size >= 1048576)
4512	{
4513		$size = my_number_format(round(($size / 1048576), 2))." ".$lang->size_mb;
4514	}
4515	// Kilobyte (1024 bytes)
4516	elseif($size >= 1024)
4517	{
4518		$size = my_number_format(round(($size / 1024), 2))." ".$lang->size_kb;
4519	}
4520	elseif($size == 0)
4521	{
4522		$size = "0 ".$lang->size_bytes;
4523	}
4524	else
4525	{
4526		$size = my_number_format($size)." ".$lang->size_bytes;
4527	}
4528
4529	return $size;
4530}
4531
4532/**
4533 * Format a decimal number in to microseconds, milliseconds, or seconds.
4534 *
4535 * @param int $time The time in microseconds
4536 * @return string The friendly time duration
4537 */
4538function format_time_duration($time)
4539{
4540	global $lang;
4541
4542	if(!is_numeric($time))
4543	{
4544		return $lang->na;
4545	}
4546
4547	if(round(1000000 * $time, 2) < 1000)
4548	{
4549		$time = number_format(round(1000000 * $time, 2))." μs";
4550	}
4551	elseif(round(1000000 * $time, 2) >= 1000 && round(1000000 * $time, 2) < 1000000)
4552	{
4553		$time = number_format(round((1000 * $time), 2))." ms";
4554	}
4555	else
4556	{
4557		$time = round($time, 3)." seconds";
4558	}
4559
4560	return $time;
4561}
4562
4563/**
4564 * Get the attachment icon for a specific file extension
4565 *
4566 * @param string $ext The file extension
4567 * @return string The attachment icon
4568 */
4569function get_attachment_icon($ext)
4570{
4571	global $cache, $attachtypes, $theme, $templates, $lang, $mybb;
4572
4573	if(!$attachtypes)
4574	{
4575		$attachtypes = $cache->read("attachtypes");
4576	}
4577
4578	$ext = my_strtolower($ext);
4579
4580	if($attachtypes[$ext]['icon'])
4581	{
4582		static $attach_icons_schemes = array();
4583		if(!isset($attach_icons_schemes[$ext]))
4584		{
4585			$attach_icons_schemes[$ext] = parse_url($attachtypes[$ext]['icon']);
4586			if(!empty($attach_icons_schemes[$ext]['scheme']))
4587			{
4588				$attach_icons_schemes[$ext] = $attachtypes[$ext]['icon'];
4589			}
4590			elseif(defined("IN_ADMINCP"))
4591			{
4592				$attach_icons_schemes[$ext] = str_replace("{theme}", "", $attachtypes[$ext]['icon']);
4593				if(my_substr($attach_icons_schemes[$ext], 0, 1) != "/")
4594				{
4595					$attach_icons_schemes[$ext] = "../".$attach_icons_schemes[$ext];
4596				}
4597			}
4598			elseif(defined("IN_PORTAL"))
4599			{
4600				global $change_dir;
4601				$attach_icons_schemes[$ext] = $change_dir."/".str_replace("{theme}", $theme['imgdir'], $attachtypes[$ext]['icon']);
4602				$attach_icons_schemes[$ext] = $mybb->get_asset_url($attach_icons_schemes[$ext]);
4603			}
4604			else
4605			{
4606				$attach_icons_schemes[$ext] = str_replace("{theme}", $theme['imgdir'], $attachtypes[$ext]['icon']);
4607				$attach_icons_schemes[$ext] = $mybb->get_asset_url($attach_icons_schemes[$ext]);
4608			}
4609		}
4610
4611		$icon = $attach_icons_schemes[$ext];
4612
4613		$name = htmlspecialchars_uni($attachtypes[$ext]['name']);
4614	}
4615	else
4616	{
4617		if(defined("IN_ADMINCP"))
4618		{
4619			$theme['imgdir'] = "../images";
4620		}
4621		else if(defined("IN_PORTAL"))
4622		{
4623			global $change_dir;
4624			$theme['imgdir'] = "{$change_dir}/images";
4625		}
4626
4627		$icon = "{$theme['imgdir']}/attachtypes/unknown.png";
4628
4629		$name = $lang->unknown;
4630	}
4631
4632	$icon = htmlspecialchars_uni($icon);
4633	eval("\$attachment_icon = \"".$templates->get("attachment_icon")."\";");
4634	return $attachment_icon;
4635}
4636
4637/**
4638 * Get a list of the unviewable forums for the current user
4639 *
4640 * @param boolean $only_readable_threads Set to true to only fetch those forums for which users can actually read a thread in.
4641 * @return string Comma separated values list of the forum IDs which the user cannot view
4642 */
4643function get_unviewable_forums($only_readable_threads=false)
4644{
4645	global $forum_cache, $permissioncache, $mybb;
4646
4647	if(!is_array($forum_cache))
4648	{
4649		cache_forums();
4650	}
4651
4652	if(!is_array($permissioncache))
4653	{
4654		$permissioncache = forum_permissions();
4655	}
4656
4657	$unviewable = array();
4658	foreach($forum_cache as $fid => $forum)
4659	{
4660		if($permissioncache[$forum['fid']])
4661		{
4662			$perms = $permissioncache[$forum['fid']];
4663		}
4664		else
4665		{
4666			$perms = $mybb->usergroup;
4667		}
4668
4669		$pwverified = 1;
4670
4671
4672		if(!forum_password_validated($forum, true))
4673		{
4674			$pwverified = 0;
4675		}
4676		else
4677		{
4678			// Check parents for passwords
4679			$parents = explode(",", $forum['parentlist']);
4680			foreach($parents as $parent)
4681			{
4682				if(!forum_password_validated($forum_cache[$parent], true))
4683				{
4684					$pwverified = 0;
4685					break;
4686				}
4687			}
4688		}
4689
4690		if($perms['canview'] == 0 || $pwverified == 0 || ($only_readable_threads == true && $perms['canviewthreads'] == 0))
4691		{
4692			$unviewable[] = $forum['fid'];
4693		}
4694	}
4695
4696	$unviewableforums = implode(',', $unviewable);
4697
4698	return $unviewableforums;
4699}
4700
4701/**
4702 * Fixes mktime for dates earlier than 1970
4703 *
4704 * @param string $format The date format to use
4705 * @param int $year The year of the date
4706 * @return string The correct date format
4707 */
4708function fix_mktime($format, $year)
4709{
4710	// Our little work around for the date < 1970 thing.
4711	// -2 idea provided by Matt Light (http://www.mephex.com)
4712	$format = str_replace("Y", $year, $format);
4713	$format = str_replace("y", my_substr($year, -2), $format);
4714
4715	return $format;
4716}
4717
4718/**
4719 * Build the breadcrumb navigation trail from the specified items
4720 *
4721 * @return string The formatted breadcrumb navigation trail
4722 */
4723function build_breadcrumb()
4724{
4725	global $nav, $navbits, $templates, $theme, $lang, $mybb;
4726
4727	eval("\$navsep = \"".$templates->get("nav_sep")."\";");
4728
4729	$i = 0;
4730	$activesep = '';
4731
4732	if(is_array($navbits))
4733	{
4734		reset($navbits);
4735		foreach($navbits as $key => $navbit)
4736		{
4737			if(isset($navbits[$key+1]))
4738			{
4739				if(isset($navbits[$key+2]))
4740				{
4741					$sep = $navsep;
4742				}
4743				else
4744				{
4745					$sep = "";
4746				}
4747
4748				$multipage = null;
4749				$multipage_dropdown = null;
4750				if(!empty($navbit['multipage']))
4751				{
4752					if(!$mybb->settings['threadsperpage'] || (int)$mybb->settings['threadsperpage'] < 1)
4753					{
4754						$mybb->settings['threadsperpage'] = 20;
4755					}
4756
4757					$multipage = multipage($navbit['multipage']['num_threads'], $mybb->settings['threadsperpage'], $navbit['multipage']['current_page'], $navbit['multipage']['url'], true);
4758					if($multipage)
4759					{
4760						++$i;
4761						eval("\$multipage_dropdown = \"".$templates->get("nav_dropdown")."\";");
4762						$sep = $multipage_dropdown.$sep;
4763					}
4764				}
4765
4766				// Replace page 1 URLs
4767				$navbit['url'] = str_replace("-page-1.html", ".html", $navbit['url']);
4768				$navbit['url'] = preg_replace("/&amp;page=1$/", "", $navbit['url']);
4769
4770				eval("\$nav .= \"".$templates->get("nav_bit")."\";");
4771			}
4772		}
4773		$navsize = count($navbits);
4774		$navbit = $navbits[$navsize-1];
4775	}
4776
4777	if($nav)
4778	{
4779		eval("\$activesep = \"".$templates->get("nav_sep_active")."\";");
4780	}
4781
4782	eval("\$activebit = \"".$templates->get("nav_bit_active")."\";");
4783	eval("\$donenav = \"".$templates->get("nav")."\";");
4784
4785	return $donenav;
4786}
4787
4788/**
4789 * Add a breadcrumb menu item to the list.
4790 *
4791 * @param string $name The name of the item to add
4792 * @param string $url The URL of the item to add
4793 */
4794function add_breadcrumb($name, $url="")
4795{
4796	global $navbits;
4797
4798	$navsize = count($navbits);
4799	$navbits[$navsize]['name'] = $name;
4800	$navbits[$navsize]['url'] = $url;
4801}
4802
4803/**
4804 * Build the forum breadcrumb nagiation (the navigation to a specific forum including all parent forums)
4805 *
4806 * @param int $fid The forum ID to build the navigation for
4807 * @param array $multipage The multipage drop down array of information
4808 * @return int Returns 1 in every case. Kept for compatibility
4809 */
4810function build_forum_breadcrumb($fid, $multipage=array())
4811{
4812	global $pforumcache, $currentitem, $forum_cache, $navbits, $lang, $base_url, $archiveurl;
4813
4814	if(!$pforumcache)
4815	{
4816		if(!is_array($forum_cache))
4817		{
4818			cache_forums();
4819		}
4820
4821		foreach($forum_cache as $key => $val)
4822		{
4823			$pforumcache[$val['fid']][$val['pid']] = $val;
4824		}
4825	}
4826
4827	if(is_array($pforumcache[$fid]))
4828	{
4829		foreach($pforumcache[$fid] as $key => $forumnav)
4830		{
4831			if($fid == $forumnav['fid'])
4832			{
4833				if(!empty($pforumcache[$forumnav['pid']]))
4834				{
4835					build_forum_breadcrumb($forumnav['pid']);
4836				}
4837
4838				$navsize = count($navbits);
4839				// Convert & to &amp;
4840				$navbits[$navsize]['name'] = preg_replace("#&(?!\#[0-9]+;)#si", "&amp;", $forumnav['name']);
4841
4842				if(defined("IN_ARCHIVE"))
4843				{
4844					// Set up link to forum in breadcrumb.
4845					if($pforumcache[$fid][$forumnav['pid']]['type'] == 'f' || $pforumcache[$fid][$forumnav['pid']]['type'] == 'c')
4846					{
4847						$navbits[$navsize]['url'] = "{$base_url}forum-".$forumnav['fid'].".html";
4848					}
4849					else
4850					{
4851						$navbits[$navsize]['url'] = $archiveurl."/index.php";
4852					}
4853				}
4854				elseif(!empty($multipage))
4855				{
4856					$navbits[$navsize]['url'] = get_forum_link($forumnav['fid'], $multipage['current_page']);
4857
4858					$navbits[$navsize]['multipage'] = $multipage;
4859					$navbits[$navsize]['multipage']['url'] = str_replace('{fid}', $forumnav['fid'], FORUM_URL_PAGED);
4860				}
4861				else
4862				{
4863					$navbits[$navsize]['url'] = get_forum_link($forumnav['fid']);
4864				}
4865			}
4866		}
4867	}
4868
4869	return 1;
4870}
4871
4872/**
4873 * Resets the breadcrumb navigation to the first item, and clears the rest
4874 */
4875function reset_breadcrumb()
4876{
4877	global $navbits;
4878
4879	$newnav[0]['name'] = $navbits[0]['name'];
4880	$newnav[0]['url'] = $navbits[0]['url'];
4881	if(!empty($navbits[0]['options']))
4882	{
4883		$newnav[0]['options'] = $navbits[0]['options'];
4884	}
4885
4886	unset($GLOBALS['navbits']);
4887	$GLOBALS['navbits'] = $newnav;
4888}
4889
4890/**
4891 * Builds a URL to an archive mode page
4892 *
4893 * @param string $type The type of page (thread|announcement|forum)
4894 * @param int $id The ID of the item
4895 * @return string The URL
4896 */
4897function build_archive_link($type="", $id=0)
4898{
4899	global $mybb;
4900
4901	// If the server OS is not Windows and not Apache or the PHP is running as a CGI or we have defined ARCHIVE_QUERY_STRINGS, use query strings - DIRECTORY_SEPARATOR checks if running windows
4902	//if((DIRECTORY_SEPARATOR == '\\' && is_numeric(stripos($_SERVER['SERVER_SOFTWARE'], "apache")) == false) || is_numeric(stripos(SAPI_NAME, "cgi")) !== false || defined("ARCHIVE_QUERY_STRINGS"))
4903	if($mybb->settings['seourls_archive'] == 1)
4904	{
4905		$base_url = $mybb->settings['bburl']."/archive/index.php/";
4906	}
4907	else
4908	{
4909		$base_url = $mybb->settings['bburl']."/archive/index.php?";
4910	}
4911
4912	switch($type)
4913	{
4914		case "thread":
4915			$url = "{$base_url}thread-{$id}.html";
4916			break;
4917		case "announcement":
4918			$url = "{$base_url}announcement-{$id}.html";
4919			break;
4920		case "forum":
4921			$url = "{$base_url}forum-{$id}.html";
4922			break;
4923		default:
4924			$url = $mybb->settings['bburl']."/archive/index.php";
4925	}
4926
4927	return $url;
4928}
4929
4930/**
4931 * Prints a debug information page
4932 */
4933function debug_page()
4934{
4935	global $db, $debug, $templates, $templatelist, $mybb, $maintimer, $globaltime, $ptimer, $parsetime, $lang, $cache;
4936
4937	$totaltime = format_time_duration($maintimer->totaltime);
4938	$phptime = $maintimer->totaltime - $db->query_time;
4939	$query_time = $db->query_time;
4940	$globaltime = format_time_duration($globaltime);
4941
4942	$percentphp = number_format((($phptime/$maintimer->totaltime)*100), 2);
4943	$percentsql = number_format((($query_time/$maintimer->totaltime)*100), 2);
4944
4945	$phptime = format_time_duration($maintimer->totaltime - $db->query_time);
4946	$query_time = format_time_duration($db->query_time);
4947
4948	$call_time = format_time_duration($cache->call_time);
4949
4950	$phpversion = PHP_VERSION;
4951
4952	$serverload = get_server_load();
4953
4954	if($mybb->settings['gzipoutput'] != 0)
4955	{
4956		$gzipen = "Enabled";
4957	}
4958	else
4959	{
4960		$gzipen = "Disabled";
4961	}
4962
4963	echo "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
4964	echo "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">";
4965	echo "<head>";
4966	echo "<meta name=\"robots\" content=\"noindex\" />";
4967	echo "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />";
4968	echo "<title>MyBB Debug Information</title>";
4969	echo "</head>";
4970	echo "<body>";
4971	echo "<h1>MyBB Debug Information</h1>\n";
4972	echo "<h2>Page Generation</h2>\n";
4973	echo "<table bgcolor=\"#666666\" width=\"95%\" cellpadding=\"4\" cellspacing=\"1\" align=\"center\">\n";
4974	echo "<tr>\n";
4975	echo "<td bgcolor=\"#cccccc\" colspan=\"4\"><b><span style=\"size:2;\">Page Generation Statistics</span></b></td>\n";
4976	echo "</tr>\n";
4977	echo "<tr>\n";
4978	echo "<td bgcolor=\"#efefef\" width=\"25%\"><b><span style=\"font-family: tahoma; font-size: 12px;\">Page Generation Time:</span></b></td>\n";
4979	echo "<td bgcolor=\"#fefefe\" width=\"25%\"><span style=\"font-family: tahoma; font-size: 12px;\">$totaltime</span></td>\n";
4980	echo "<td bgcolor=\"#efefef\" width=\"25%\"><b><span style=\"font-family: tahoma; font-size: 12px;\">No. DB Queries:</span></b></td>\n";
4981	echo "<td bgcolor=\"#fefefe\" width=\"25%\"><span style=\"font-family: tahoma; font-size: 12px;\">$db->query_count</span></td>\n";
4982	echo "</tr>\n";
4983	echo "<tr>\n";
4984	echo "<td bgcolor=\"#efefef\" width=\"25%\"><b><span style=\"font-family: tahoma; font-size: 12px;\">PHP Processing Time:</span></b></td>\n";
4985	echo "<td bgcolor=\"#fefefe\" width=\"25%\"><span style=\"font-family: tahoma; font-size: 12px;\">$phptime ($percentphp%)</span></td>\n";
4986	echo "<td bgcolor=\"#efefef\" width=\"25%\"><b><span style=\"font-family: tahoma; font-size: 12px;\">DB Processing Time:</span></b></td>\n";
4987	echo "<td bgcolor=\"#fefefe\" width=\"25%\"><span style=\"font-family: tahoma; font-size: 12px;\">$query_time ($percentsql%)</span></td>\n";
4988	echo "</tr>\n";
4989	echo "<tr>\n";
4990	echo "<td bgcolor=\"#efefef\" width=\"25%\"><b><span style=\"font-family: tahoma; font-size: 12px;\">Extensions Used:</span></b></td>\n";
4991	echo "<td bgcolor=\"#fefefe\" width=\"25%\"><span style=\"font-family: tahoma; font-size: 12px;\">{$mybb->config['database']['type']}, xml</span></td>\n";
4992	echo "<td bgcolor=\"#efefef\" width=\"25%\"><b><span style=\"font-family: tahoma; font-size: 12px;\">Global.php Processing Time:</span></b></td>\n";
4993	echo "<td bgcolor=\"#fefefe\" width=\"25%\"><span style=\"font-family: tahoma; font-size: 12px;\">$globaltime</span></td>\n";
4994	echo "</tr>\n";
4995	echo "<tr>\n";
4996	echo "<td bgcolor=\"#efefef\" width=\"25%\"><b><span style=\"font-family: tahoma; font-size: 12px;\">PHP Version:</span></b></td>\n";
4997	echo "<td bgcolor=\"#fefefe\" width=\"25%\"><span style=\"font-family: tahoma; font-size: 12px;\">$phpversion</span></td>\n";
4998	echo "<td bgcolor=\"#efefef\" width=\"25%\"><b><span style=\"font-family: tahoma; font-size: 12px;\">Server Load:</span></b></td>\n";
4999	echo "<td bgcolor=\"#fefefe\" width=\"25%\"><span style=\"font-family: tahoma; font-size: 12px;\">$serverload</span></td>\n";
5000	echo "</tr>\n";
5001	echo "<tr>\n";
5002	echo "<td bgcolor=\"#efefef\" width=\"25%\"><b><span style=\"font-family: tahoma; font-size: 12px;\">GZip Encoding Status:</span></b></td>\n";
5003	echo "<td bgcolor=\"#fefefe\" width=\"25%\"><span style=\"font-family: tahoma; font-size: 12px;\">$gzipen</span></td>\n";
5004	echo "<td bgcolor=\"#efefef\" width=\"25%\"><b><span style=\"font-family: tahoma; font-size: 12px;\">No. Templates Used:</span></b></td>\n";
5005	echo "<td bgcolor=\"#fefefe\" width=\"25%\"><span style=\"font-family: tahoma; font-size: 12px;\">".count($templates->cache)." (".(int)count(explode(",", $templatelist))." Cached / ".(int)count($templates->uncached_templates)." Manually Loaded)</span></td>\n";
5006	echo "</tr>\n";
5007
5008	$memory_usage = get_memory_usage();
5009	if(!$memory_usage)
5010	{
5011		$memory_usage = $lang->unknown;
5012	}
5013	else
5014	{
5015		$memory_usage = get_friendly_size($memory_usage)." ({$memory_usage} bytes)";
5016	}
5017	$memory_limit = @ini_get("memory_limit");
5018	echo "<tr>\n";
5019	echo "<td bgcolor=\"#EFEFEF\" width=\"25%\"><b><span style=\"font-family: tahoma; font-size: 12px;\">Memory Usage:</span></b></td>\n";
5020	echo "<td bgcolor=\"#FEFEFE\" width=\"25%\"><span style=\"font-family: tahoma; font-size: 12px;\">{$memory_usage}</span></td>\n";
5021	echo "<td bgcolor=\"#EFEFEF\" width=\"25%\"><b><span style=\"font-family: tahoma; font-size: 12px;\">Memory Limit:</span></b></td>\n";
5022	echo "<td bgcolor=\"#FEFEFE\" width=\"25%\"><span style=\"font-family: tahoma; font-size: 12px;\">{$memory_limit}</span></td>\n";
5023	echo "</tr>\n";
5024
5025	echo "</table>\n";
5026
5027	echo "<h2>Database Connections (".count($db->connections)." Total) </h2>\n";
5028	echo "<table style=\"background-color: #666;\" width=\"95%\" cellpadding=\"4\" cellspacing=\"1\" align=\"center\">\n";
5029	echo "<tr>\n";
5030	echo "<td style=\"background: #fff;\">".implode("<br />", $db->connections)."</td>\n";
5031	echo "</tr>\n";
5032	echo "</table>\n";
5033	echo "<br />\n";
5034
5035	echo "<h2>Database Queries (".$db->query_count." Total) </h2>\n";
5036	echo $db->explain;
5037
5038	if($cache->call_count > 0)
5039	{
5040		echo "<h2>Cache Calls (".$cache->call_count." Total, ".$call_time.") </h2>\n";
5041		echo $cache->cache_debug;
5042	}
5043
5044	echo "<h2>Template Statistics</h2>\n";
5045
5046	if(count($templates->cache) > 0)
5047	{
5048		echo "<table style=\"background-color: #666;\" width=\"95%\" cellpadding=\"4\" cellspacing=\"1\" align=\"center\">\n";
5049		echo "<tr>\n";
5050		echo "<td style=\"background-color: #ccc;\"><strong>Templates Used (Loaded for this Page) - ".count($templates->cache)." Total</strong></td>\n";
5051		echo "</tr>\n";
5052		echo "<tr>\n";
5053		echo "<td style=\"background: #fff;\">".implode(", ", array_keys($templates->cache))."</td>\n";
5054		echo "</tr>\n";
5055		echo "</table>\n";
5056		echo "<br />\n";
5057	}
5058
5059	if(count($templates->uncached_templates) > 0)
5060	{
5061		echo "<table style=\"background-color: #666;\" width=\"95%\" cellpadding=\"4\" cellspacing=\"1\" align=\"center\">\n";
5062		echo "<tr>\n";
5063		echo "<td style=\"background-color: #ccc;\"><strong>Templates Requiring Additional Calls (Not Cached at Startup) - ".count($templates->uncached_templates)." Total</strong></td>\n";
5064		echo "</tr>\n";
5065		echo "<tr>\n";
5066		echo "<td style=\"background: #fff;\">".implode(", ", $templates->uncached_templates)."</td>\n";
5067		echo "</tr>\n";
5068		echo "</table>\n";
5069		echo "<br />\n";
5070	}
5071	echo "</body>";
5072	echo "</html>";
5073	exit;
5074}
5075
5076/**
5077 * Outputs the correct page headers.
5078 */
5079function send_page_headers()
5080{
5081	global $mybb;
5082
5083	if($mybb->settings['nocacheheaders'] == 1)
5084	{
5085		header("Cache-Control: no-cache, private");
5086	}
5087}
5088
5089/**
5090 * Mark specific reported posts of a certain type as dealt with
5091 *
5092 * @param array|int $id An array or int of the ID numbers you're marking as dealt with
5093 * @param string $type The type of item the above IDs are for - post, posts, thread, threads, forum, all
5094 */
5095function mark_reports($id, $type="post")
5096{
5097	global $db, $cache, $plugins;
5098
5099	switch($type)
5100	{
5101		case "posts":
5102			if(is_array($id))
5103			{
5104				$rids = implode("','", $id);
5105				$rids = "'0','$rids'";
5106				$db->update_query("reportedcontent", array('reportstatus' => 1), "id IN($rids) AND reportstatus='0' AND (type = 'post' OR type = '')");
5107			}
5108			break;
5109		case "post":
5110			$db->update_query("reportedcontent", array('reportstatus' => 1), "id='$id' AND reportstatus='0' AND (type = 'post' OR type = '')");
5111			break;
5112		case "threads":
5113			if(is_array($id))
5114			{
5115				$rids = implode("','", $id);
5116				$rids = "'0','$rids'";
5117				$db->update_query("reportedcontent", array('reportstatus' => 1), "id2 IN($rids) AND reportstatus='0' AND (type = 'post' OR type = '')");
5118			}
5119			break;
5120		case "thread":
5121			$db->update_query("reportedcontent", array('reportstatus' => 1), "id2='$id' AND reportstatus='0' AND (type = 'post' OR type = '')");
5122			break;
5123		case "forum":
5124			$db->update_query("reportedcontent", array('reportstatus' => 1), "id3='$id' AND reportstatus='0' AND (type = 'post' OR type = '')");
5125			break;
5126		case "all":
5127			$db->update_query("reportedcontent", array('reportstatus' => 1), "reportstatus='0' AND (type = 'post' OR type = '')");
5128			break;
5129	}
5130
5131	$arguments = array('id' => $id, 'type' => $type);
5132	$plugins->run_hooks("mark_reports", $arguments);
5133	$cache->update_reportedcontent();
5134}
5135
5136/**
5137 * Fetch a friendly x days, y months etc date stamp from a timestamp
5138 *
5139 * @param int $stamp The timestamp
5140 * @param array $options Array of options
5141 * @return string The friendly formatted timestamp
5142 */
5143function nice_time($stamp, $options=array())
5144{
5145	global $lang;
5146
5147	$ysecs = 365*24*60*60;
5148	$mosecs = 31*24*60*60;
5149	$wsecs = 7*24*60*60;
5150	$dsecs = 24*60*60;
5151	$hsecs = 60*60;
5152	$msecs = 60;
5153
5154	if(isset($options['short']))
5155	{
5156		$lang_year = $lang->year_short;
5157		$lang_years = $lang->years_short;
5158		$lang_month = $lang->month_short;
5159		$lang_months = $lang->months_short;
5160		$lang_week = $lang->week_short;
5161		$lang_weeks = $lang->weeks_short;
5162		$lang_day = $lang->day_short;
5163		$lang_days = $lang->days_short;
5164		$lang_hour = $lang->hour_short;
5165		$lang_hours = $lang->hours_short;
5166		$lang_minute = $lang->minute_short;
5167		$lang_minutes = $lang->minutes_short;
5168		$lang_second = $lang->second_short;
5169		$lang_seconds = $lang->seconds_short;
5170	}
5171	else
5172	{
5173		$lang_year = " ".$lang->year;
5174		$lang_years = " ".$lang->years;
5175		$lang_month = " ".$lang->month;
5176		$lang_months = " ".$lang->months;
5177		$lang_week = " ".$lang->week;
5178		$lang_weeks = " ".$lang->weeks;
5179		$lang_day = " ".$lang->day;
5180		$lang_days = " ".$lang->days;
5181		$lang_hour = " ".$lang->hour;
5182		$lang_hours = " ".$lang->hours;
5183		$lang_minute = " ".$lang->minute;
5184		$lang_minutes = " ".$lang->minutes;
5185		$lang_second = " ".$lang->second;
5186		$lang_seconds = " ".$lang->seconds;
5187	}
5188
5189	$years = floor($stamp/$ysecs);
5190	$stamp %= $ysecs;
5191	$months = floor($stamp/$mosecs);
5192	$stamp %= $mosecs;
5193	$weeks = floor($stamp/$wsecs);
5194	$stamp %= $wsecs;
5195	$days = floor($stamp/$dsecs);
5196	$stamp %= $dsecs;
5197	$hours = floor($stamp/$hsecs);
5198	$stamp %= $hsecs;
5199	$minutes = floor($stamp/$msecs);
5200	$stamp %= $msecs;
5201	$seconds = $stamp;
5202
5203	// Prevent gross over accuracy ($options parameter will override these)
5204	if($years > 0)
5205	{
5206		$options = array_merge(array(
5207			'days' => false,
5208			'hours' => false,
5209			'minutes' => false,
5210			'seconds' => false
5211		), $options);
5212	}
5213	elseif($months > 0)
5214	{
5215		$options = array_merge(array(
5216			'hours' => false,
5217			'minutes' => false,
5218			'seconds' => false
5219		), $options);
5220	}
5221	elseif($weeks > 0)
5222	{
5223		$options = array_merge(array(
5224			'minutes' => false,
5225			'seconds' => false
5226		), $options);
5227	}
5228	elseif($days > 0)
5229	{
5230		$options = array_merge(array(
5231			'seconds' => false
5232		), $options);
5233	}
5234
5235	$nicetime = array();
5236
5237	if(!isset($options['years']) || $options['years'] !== false)
5238	{
5239		if($years == 1)
5240		{
5241			$nicetime['years'] = "1".$lang_year;
5242		}
5243		else if($years > 1)
5244		{
5245			$nicetime['years'] = $years.$lang_years;
5246		}
5247	}
5248
5249	if(!isset($options['months']) || $options['months'] !== false)
5250	{
5251		if($months == 1)
5252		{
5253			$nicetime['months'] = "1".$lang_month;
5254		}
5255		else if($months > 1)
5256		{
5257			$nicetime['months'] = $months.$lang_months;
5258		}
5259	}
5260
5261	if(!isset($options['weeks']) || $options['weeks'] !== false)
5262	{
5263		if($weeks == 1)
5264		{
5265			$nicetime['weeks'] = "1".$lang_week;
5266		}
5267		else if($weeks > 1)
5268		{
5269			$nicetime['weeks'] = $weeks.$lang_weeks;
5270		}
5271	}
5272
5273	if(!isset($options['days']) || $options['days'] !== false)
5274	{
5275		if($days == 1)
5276		{
5277			$nicetime['days'] = "1".$lang_day;
5278		}
5279		else if($days > 1)
5280		{
5281			$nicetime['days'] = $days.$lang_days;
5282		}
5283	}
5284
5285	if(!isset($options['hours']) || $options['hours'] !== false)
5286	{
5287		if($hours == 1)
5288		{
5289			$nicetime['hours'] = "1".$lang_hour;
5290		}
5291		else if($hours > 1)
5292		{
5293			$nicetime['hours'] = $hours.$lang_hours;
5294		}
5295	}
5296
5297	if(!isset($options['minutes']) || $options['minutes'] !== false)
5298	{
5299		if($minutes == 1)
5300		{
5301			$nicetime['minutes'] = "1".$lang_minute;
5302		}
5303		else if($minutes > 1)
5304		{
5305			$nicetime['minutes'] = $minutes.$lang_minutes;
5306		}
5307	}
5308
5309	if(!isset($options['seconds']) || $options['seconds'] !== false)
5310	{
5311		if($seconds == 1)
5312		{
5313			$nicetime['seconds'] = "1".$lang_second;
5314		}
5315		else if($seconds > 1)
5316		{
5317			$nicetime['seconds'] = $seconds.$lang_seconds;
5318		}
5319	}
5320
5321	if(!empty($nicetime))
5322	{
5323		return implode(", ", $nicetime);
5324	}
5325}
5326
5327/**
5328 * Select an alternating row colour based on the previous call to this function
5329 *
5330 * @param int $reset 1 to reset the row to trow1.
5331 * @return string trow1 or trow2 depending on the previous call
5332 */
5333function alt_trow($reset=0)
5334{
5335	global $alttrow;
5336
5337	if($alttrow == "trow1" && !$reset)
5338	{
5339		$trow = "trow2";
5340	}
5341	else
5342	{
5343		$trow = "trow1";
5344	}
5345
5346	$alttrow = $trow;
5347
5348	return $trow;
5349}
5350
5351/**
5352 * Add a user to a specific additional user group.
5353 *
5354 * @param int $uid The user ID
5355 * @param int $joingroup The user group ID to join
5356 * @return bool
5357 */
5358function join_usergroup($uid, $joingroup)
5359{
5360	global $db, $mybb;
5361
5362	if($uid == $mybb->user['uid'])
5363	{
5364		$user = $mybb->user;
5365	}
5366	else
5367	{
5368		$query = $db->simple_select("users", "additionalgroups, usergroup", "uid='".(int)$uid."'");
5369		$user = $db->fetch_array($query);
5370	}
5371
5372	// Build the new list of additional groups for this user and make sure they're in the right format
5373	$groups = array_map(
5374		'intval',
5375		explode(',', $user['additionalgroups'])
5376	);
5377
5378	if(!in_array((int)$joingroup, $groups))
5379	{
5380		$groups[] = (int)$joingroup;
5381		$groups = array_diff($groups, array($user['usergroup']));
5382		$groups = array_unique($groups);
5383
5384		$groupslist = implode(',', $groups);
5385
5386		$db->update_query("users", array('additionalgroups' => $groupslist), "uid='".(int)$uid."'");
5387		return true;
5388	}
5389	else
5390	{
5391		return false;
5392	}
5393}
5394
5395/**
5396 * Remove a user from a specific additional user group
5397 *
5398 * @param int $uid The user ID
5399 * @param int $leavegroup The user group ID
5400 */
5401function leave_usergroup($uid, $leavegroup)
5402{
5403	global $db, $mybb, $cache;
5404
5405	$user = get_user($uid);
5406
5407	if($user['usergroup'] == $leavegroup)
5408	{
5409		return false;
5410	}
5411
5412	$groups = array_map(
5413		'intval',
5414		explode(',', $user['additionalgroups'])
5415	);
5416	$groups = array_diff($groups, array($leavegroup));
5417	$groups = array_unique($groups);
5418
5419	$groupslist = implode(',', $groups);
5420
5421	$dispupdate = "";
5422	if($leavegroup == $user['displaygroup'])
5423	{
5424		$dispupdate = ", displaygroup=usergroup";
5425	}
5426
5427	$db->write_query("
5428		UPDATE ".TABLE_PREFIX."users
5429		SET additionalgroups='$groupslist' $dispupdate
5430		WHERE uid='".(int)$uid."'
5431	");
5432
5433	$cache->update_moderators();
5434}
5435
5436/**
5437 * Get the current location taking in to account different web serves and systems
5438 *
5439 * @param boolean $fields True to return as "hidden" fields
5440 * @param array $ignore Array of fields to ignore for returning "hidden" fields or URL being accessed
5441 * @param boolean $quick True to skip all inputs and return only the file path part of the URL
5442 * @return string|array The current URL being accessed or form data if $fields is true
5443 */
5444function get_current_location($fields=false, $ignore=array(), $quick=false)
5445{
5446	global $mybb;
5447
5448	if(defined("MYBB_LOCATION"))
5449	{
5450		return MYBB_LOCATION;
5451	}
5452
5453	if(!empty($_SERVER['SCRIPT_NAME']))
5454	{
5455		$location = htmlspecialchars_uni($_SERVER['SCRIPT_NAME']);
5456	}
5457	elseif(!empty($_SERVER['PHP_SELF']))
5458	{
5459		$location = htmlspecialchars_uni($_SERVER['PHP_SELF']);
5460	}
5461	elseif(!empty($_ENV['PHP_SELF']))
5462	{
5463		$location = htmlspecialchars_uni($_ENV['PHP_SELF']);
5464	}
5465	elseif(!empty($_SERVER['PATH_INFO']))
5466	{
5467		$location = htmlspecialchars_uni($_SERVER['PATH_INFO']);
5468	}
5469	else
5470	{
5471		$location = htmlspecialchars_uni($_ENV['PATH_INFO']);
5472	}
5473
5474	if($quick)
5475	{
5476		return $location;
5477	}
5478
5479	if(!is_array($ignore))
5480	{
5481		$ignore = array($ignore);
5482	}
5483
5484	if($fields == true)
5485	{
5486
5487		$form_html = '';
5488		if(!empty($mybb->input))
5489		{
5490			foreach($mybb->input as $name => $value)
5491			{
5492				if(in_array($name, $ignore) || is_array($name) || is_array($value))
5493				{
5494					continue;
5495				}
5496
5497				$form_html .= "<input type=\"hidden\" name=\"".htmlspecialchars_uni($name)."\" value=\"".htmlspecialchars_uni($value)."\" />\n";
5498			}
5499		}
5500
5501		return array('location' => $location, 'form_html' => $form_html, 'form_method' => $mybb->request_method);
5502	}
5503	else
5504	{
5505		$parameters = array();
5506
5507		if(isset($_SERVER['QUERY_STRING']))
5508		{
5509			$current_query_string = $_SERVER['QUERY_STRING'];
5510		}
5511		else if(isset($_ENV['QUERY_STRING']))
5512		{
5513			$current_query_string = $_ENV['QUERY_STRING'];
5514		} else
5515		{
5516			$current_query_string = '';
5517		}
5518
5519		parse_str($current_query_string, $current_parameters);
5520
5521		foreach($current_parameters as $name => $value)
5522		{
5523			if(!in_array($name, $ignore))
5524			{
5525				$parameters[$name] = $value;
5526			}
5527		}
5528
5529		if($mybb->request_method === 'post')
5530		{
5531			$post_array = array('action', 'fid', 'pid', 'tid', 'uid', 'eid');
5532
5533			foreach($post_array as $var)
5534			{
5535				if(isset($_POST[$var]) && !in_array($var, $ignore))
5536				{
5537					$parameters[$var] = $_POST[$var];
5538				}
5539			}
5540		}
5541
5542		if(!empty($parameters))
5543		{
5544			$location .= '?'.http_build_query($parameters, '', '&amp;');
5545		}
5546
5547		return $location;
5548	}
5549}
5550
5551/**
5552 * Build a theme selection menu
5553 *
5554 * @param string $name The name of the menu
5555 * @param int $selected The ID of the selected theme
5556 * @param int $tid The ID of the parent theme to select from
5557 * @param string $depth The current selection depth
5558 * @param boolean $usergroup_override Whether or not to override usergroup permissions (true to override)
5559 * @param boolean $footer Whether or not theme select is in the footer (true if it is)
5560 * @param boolean $count_override Whether or not to override output based on theme count (true to override)
5561 * @return string The theme selection list
5562 */
5563function build_theme_select($name, $selected=-1, $tid=0, $depth="", $usergroup_override=false, $footer=false, $count_override=false)
5564{
5565	global $db, $themeselect, $tcache, $lang, $mybb, $limit, $templates, $num_themes, $themeselect_option;
5566
5567	if($tid == 0)
5568	{
5569		$tid = 1;
5570		$num_themes = 0;
5571		$themeselect_option = '';
5572	}
5573
5574	if(!is_array($tcache))
5575	{
5576		$query = $db->simple_select('themes', 'tid, name, pid, allowedgroups', "pid!='0'");
5577
5578		while($theme = $db->fetch_array($query))
5579		{
5580			$tcache[$theme['pid']][$theme['tid']] = $theme;
5581		}
5582	}
5583
5584	if(is_array($tcache[$tid]))
5585	{
5586		foreach($tcache[$tid] as $theme)
5587		{
5588			$sel = "";
5589			// Show theme if allowed, or if override is on
5590			if(is_member($theme['allowedgroups']) || $theme['allowedgroups'] == "all" || $usergroup_override == true)
5591			{
5592				if($theme['tid'] == $selected)
5593				{
5594					$sel = " selected=\"selected\"";
5595				}
5596
5597				if($theme['pid'] != 0)
5598				{
5599					$theme['name'] = htmlspecialchars_uni($theme['name']);
5600					eval("\$themeselect_option .= \"".$templates->get("usercp_themeselector_option")."\";");
5601					++$num_themes;
5602					$depthit = $depth."--";
5603				}
5604
5605				if(array_key_exists($theme['tid'], $tcache))
5606				{
5607					build_theme_select($name, $selected, $theme['tid'], $depthit, $usergroup_override, $footer, $count_override);
5608				}
5609			}
5610		}
5611	}
5612
5613	if($tid == 1 && ($num_themes > 1 || $count_override == true))
5614	{
5615		if($footer == true)
5616		{
5617			eval("\$themeselect = \"".$templates->get("footer_themeselector")."\";");
5618		}
5619		else
5620		{
5621			eval("\$themeselect = \"".$templates->get("usercp_themeselector")."\";");
5622		}
5623
5624		return $themeselect;
5625	}
5626	else
5627	{
5628		return false;
5629	}
5630}
5631
5632/**
5633 * Get the theme data of a theme id.
5634 *
5635 * @param int $tid The theme id of the theme.
5636 * @return boolean|array False if no valid theme, Array with the theme data otherwise
5637 */
5638function get_theme($tid)
5639{
5640	global $tcache, $db;
5641
5642	if(!is_array($tcache))
5643	{
5644		$query = $db->simple_select('themes', 'tid, name, pid, allowedgroups', "pid!='0'");
5645
5646		while($theme = $db->fetch_array($query))
5647		{
5648			$tcache[$theme['pid']][$theme['tid']] = $theme;
5649		}
5650	}
5651
5652	$s_theme = false;
5653
5654	foreach($tcache as $themes)
5655	{
5656		foreach($themes as $theme)
5657		{
5658			if($tid == $theme['tid'])
5659			{
5660				$s_theme = $theme;
5661				break 2;
5662			}
5663		}
5664	}
5665
5666	return $s_theme;
5667}
5668
5669/**
5670 * Custom function for htmlspecialchars which takes in to account unicode
5671 *
5672 * @param string $message The string to format
5673 * @return string The string with htmlspecialchars applied
5674 */
5675function htmlspecialchars_uni($message)
5676{
5677	$message = preg_replace("#&(?!\#[0-9]+;)#si", "&amp;", $message); // Fix & but allow unicode
5678	$message = str_replace("<", "&lt;", $message);
5679	$message = str_replace(">", "&gt;", $message);
5680	$message = str_replace("\"", "&quot;", $message);
5681	return $message;
5682}
5683
5684/**
5685 * Custom function for formatting numbers.
5686 *
5687 * @param int $number The number to format.
5688 * @return int The formatted number.
5689 */
5690function my_number_format($number)
5691{
5692	global $mybb;
5693
5694	if($number == "-")
5695	{
5696		return $number;
5697	}
5698
5699	if(is_int($number))
5700	{
5701		return number_format($number, 0, $mybb->settings['decpoint'], $mybb->settings['thousandssep']);
5702	}
5703	else
5704	{
5705		$parts = explode('.', $number);
5706
5707		if(isset($parts[1]))
5708		{
5709			$decimals = my_strlen($parts[1]);
5710		}
5711		else
5712		{
5713			$decimals = 0;
5714		}
5715
5716		return number_format((double)$number, $decimals, $mybb->settings['decpoint'], $mybb->settings['thousandssep']);
5717	}
5718}
5719
5720/**
5721 * Converts a string of text to or from UTF-8.
5722 *
5723 * @param string $str The string of text to convert
5724 * @param boolean $to Whether or not the string is being converted to or from UTF-8 (true if converting to)
5725 * @return string The converted string
5726 */
5727function convert_through_utf8($str, $to=true)
5728{
5729	global $lang;
5730	static $charset;
5731	static $use_mb;
5732	static $use_iconv;
5733
5734	if(!isset($charset))
5735	{
5736		$charset = my_strtolower($lang->settings['charset']);
5737	}
5738
5739	if($charset == "utf-8")
5740	{
5741		return $str;
5742	}
5743
5744	if(!isset($use_iconv))
5745	{
5746		$use_iconv = function_exists("iconv");
5747	}
5748
5749	if(!isset($use_mb))
5750	{
5751		$use_mb = function_exists("mb_convert_encoding");
5752	}
5753
5754	if($use_iconv || $use_mb)
5755	{
5756		if($to)
5757		{
5758			$from_charset = $lang->settings['charset'];
5759			$to_charset = "UTF-8";
5760		}
5761		else
5762		{
5763			$from_charset = "UTF-8";
5764			$to_charset = $lang->settings['charset'];
5765		}
5766		if($use_iconv)
5767		{
5768			return iconv($from_charset, $to_charset."//IGNORE", $str);
5769		}
5770		else
5771		{
5772			return @mb_convert_encoding($str, $to_charset, $from_charset);
5773		}
5774	}
5775	elseif($charset == "iso-8859-1" && function_exists("utf8_encode"))
5776	{
5777		if($to)
5778		{
5779			return utf8_encode($str);
5780		}
5781		else
5782		{
5783			return utf8_decode($str);
5784		}
5785	}
5786	else
5787	{
5788		return $str;
5789	}
5790}
5791
5792/**
5793 * DEPRECATED! Please use other alternatives.
5794 *
5795 * @deprecated
5796 * @param string $message
5797 *
5798 * @return string
5799 */
5800function my_wordwrap($message)
5801{
5802	return $message;
5803}
5804
5805/**
5806 * Workaround for date limitation in PHP to establish the day of a birthday (Provided by meme)
5807 *
5808 * @param int $month The month of the birthday
5809 * @param int $day The day of the birthday
5810 * @param int $year The year of the bithday
5811 * @return int The numeric day of the week for the birthday
5812 */
5813function get_weekday($month, $day, $year)
5814{
5815	$h = 4;
5816
5817	for($i = 1969; $i >= $year; $i--)
5818	{
5819		$j = get_bdays($i);
5820
5821		for($k = 11; $k >= 0; $k--)
5822		{
5823			$l = ($k + 1);
5824
5825			for($m = $j[$k]; $m >= 1; $m--)
5826			{
5827				$h--;
5828
5829				if($i == $year && $l == $month && $m == $day)
5830				{
5831					return $h;
5832				}
5833
5834				if($h == 0)
5835				{
5836					$h = 7;
5837				}
5838			}
5839		}
5840	}
5841}
5842
5843/**
5844 * Workaround for date limitation in PHP to establish the day of a birthday (Provided by meme)
5845 *
5846 * @param int $in The year.
5847 * @return array The number of days in each month of that year
5848 */
5849function get_bdays($in)
5850{
5851	return array(
5852		31,
5853		($in % 4 == 0 && ($in % 100 > 0 || $in % 400 == 0) ? 29 : 28),
5854		31,
5855		30,
5856		31,
5857		30,
5858		31,
5859		31,
5860		30,
5861		31,
5862		30,
5863		31
5864	);
5865}
5866
5867/**
5868 * DEPRECATED! Please use mktime()!
5869 * Formats a birthday appropriately
5870 *
5871 * @deprecated
5872 * @param string $display The PHP date format string
5873 * @param int $bm The month of the birthday
5874 * @param int $bd The day of the birthday
5875 * @param int $by The year of the birthday
5876 * @param int $wd The weekday of the birthday
5877 * @return string The formatted birthday
5878 */
5879function format_bdays($display, $bm, $bd, $by, $wd)
5880{
5881	global $lang;
5882
5883	$bdays = array(
5884		$lang->sunday,
5885		$lang->monday,
5886		$lang->tuesday,
5887		$lang->wednesday,
5888		$lang->thursday,
5889		$lang->friday,
5890		$lang->saturday
5891	);
5892
5893	$bmonth = array(
5894		$lang->month_1,
5895		$lang->month_2,
5896		$lang->month_3,
5897		$lang->month_4,
5898		$lang->month_5,
5899		$lang->month_6,
5900		$lang->month_7,
5901		$lang->month_8,
5902		$lang->month_9,
5903		$lang->month_10,
5904		$lang->month_11,
5905		$lang->month_12
5906	);
5907
5908	// This needs to be in this specific order
5909	$find = array(
5910		'm',
5911		'n',
5912		'd',
5913		'D',
5914		'y',
5915		'Y',
5916		'j',
5917		'S',
5918		'F',
5919		'l',
5920		'M',
5921	);
5922
5923	$html = array(
5924		'&#109;',
5925		'&#110;',
5926		'&#99;',
5927		'&#68;',
5928		'&#121;',
5929		'&#89;',
5930		'&#106;',
5931		'&#83;',
5932		'&#70;',
5933		'&#108;',
5934		'&#77;',
5935	);
5936
5937	$bdays = str_replace($find, $html, $bdays);
5938	$bmonth = str_replace($find, $html, $bmonth);
5939
5940	$replace = array(
5941		sprintf('%02s', $bm),
5942		$bm,
5943		sprintf('%02s', $bd),
5944		($wd == 2 ? my_substr($bdays[$wd], 0, 4) : ($wd == 4 ? my_substr($bdays[$wd], 0, 5) : my_substr($bdays[$wd], 0, 3))),
5945		my_substr($by, 2),
5946		$by,
5947		($bd[0] == 0 ? my_substr($bd, 1) : $bd),
5948		($bd == 1 || $bd == 21 || $bd == 31 ? 'st' : ($bd == 2 || $bd == 22 ? 'nd' : ($bd == 3 || $bd == 23 ? 'rd' : 'th'))),
5949		$bmonth[$bm-1],
5950		$wd,
5951		($bm == 9 ? my_substr($bmonth[$bm-1], 0, 4) :  my_substr($bmonth[$bm-1], 0, 3)),
5952	);
5953
5954	// Do we have the full month in our output?
5955	// If so there's no need for the short month
5956	if(strpos($display, 'F') !== false)
5957	{
5958		array_pop($find);
5959		array_pop($replace);
5960	}
5961
5962	return str_replace($find, $replace, $display);
5963}
5964
5965/**
5966 * Returns the age of a user with specified birthday.
5967 *
5968 * @param string $birthday The birthday of a user.
5969 * @return int The age of a user with that birthday.
5970 */
5971function get_age($birthday)
5972{
5973	$bday = explode("-", $birthday);
5974	if(!$bday[2])
5975	{
5976		return;
5977	}
5978
5979	list($day, $month, $year) = explode("-", my_date("j-n-Y", TIME_NOW, 0, 0));
5980
5981	$age = $year-$bday[2];
5982
5983	if(($month == $bday[1] && $day < $bday[0]) || $month < $bday[1])
5984	{
5985		--$age;
5986	}
5987	return $age;
5988}
5989
5990/**
5991 * Updates the first posts in a thread.
5992 *
5993 * @param int $tid The thread id for which to update the first post id.
5994 */
5995function update_first_post($tid)
5996{
5997	global $db;
5998
5999	$query = $db->query("
6000		SELECT u.uid, u.username, p.pid, p.username AS postusername, p.dateline
6001		FROM ".TABLE_PREFIX."posts p
6002		LEFT JOIN ".TABLE_PREFIX."users u ON (u.uid=p.uid)
6003		WHERE p.tid='$tid'
6004		ORDER BY p.dateline ASC, p.pid ASC
6005		LIMIT 1
6006	");
6007	$firstpost = $db->fetch_array($query);
6008
6009	if(empty($firstpost['username']))
6010	{
6011		$firstpost['username'] = $firstpost['postusername'];
6012	}
6013	$firstpost['username'] = $db->escape_string($firstpost['username']);
6014
6015	$update_array = array(
6016		'firstpost' => (int)$firstpost['pid'],
6017		'username' => $firstpost['username'],
6018		'uid' => (int)$firstpost['uid'],
6019		'dateline' => (int)$firstpost['dateline']
6020	);
6021	$db->update_query("threads", $update_array, "tid='{$tid}'");
6022}
6023
6024/**
6025 * Updates the last posts in a thread.
6026 *
6027 * @param int $tid The thread id for which to update the last post id.
6028 */
6029function update_last_post($tid)
6030{
6031	global $db;
6032
6033	$query = $db->query("
6034		SELECT u.uid, u.username, p.username AS postusername, p.dateline
6035		FROM ".TABLE_PREFIX."posts p
6036		LEFT JOIN ".TABLE_PREFIX."users u ON (u.uid=p.uid)
6037		WHERE p.tid='$tid' AND p.visible='1'
6038		ORDER BY p.dateline DESC, p.pid DESC
6039		LIMIT 1"
6040	);
6041	$lastpost = $db->fetch_array($query);
6042
6043	if(!$lastpost)
6044	{
6045		return false;
6046	}
6047
6048	if(empty($lastpost['username']))
6049	{
6050		$lastpost['username'] = $lastpost['postusername'];
6051	}
6052
6053	if(empty($lastpost['dateline']))
6054	{
6055		$query = $db->query("
6056			SELECT u.uid, u.username, p.pid, p.username AS postusername, p.dateline
6057			FROM ".TABLE_PREFIX."posts p
6058			LEFT JOIN ".TABLE_PREFIX."users u ON (u.uid=p.uid)
6059			WHERE p.tid='$tid'
6060			ORDER BY p.dateline ASC, p.pid ASC
6061			LIMIT 1
6062		");
6063		$firstpost = $db->fetch_array($query);
6064
6065		$lastpost['username'] = $firstpost['username'];
6066		$lastpost['uid'] = $firstpost['uid'];
6067		$lastpost['dateline'] = $firstpost['dateline'];
6068	}
6069
6070	$lastpost['username'] = $db->escape_string($lastpost['username']);
6071
6072	$update_array = array(
6073		'lastpost' => (int)$lastpost['dateline'],
6074		'lastposter' => $lastpost['username'],
6075		'lastposteruid' => (int)$lastpost['uid']
6076	);
6077	$db->update_query("threads", $update_array, "tid='{$tid}'");
6078}
6079
6080/**
6081 * Checks for the length of a string, mb strings accounted for
6082 *
6083 * @param string $string The string to check the length of.
6084 * @return int The length of the string.
6085 */
6086function my_strlen($string)
6087{
6088	global $lang;
6089
6090	$string = preg_replace("#&\#([0-9]+);#", "-", $string);
6091
6092	if(strtolower($lang->settings['charset']) == "utf-8")
6093	{
6094		// Get rid of any excess RTL and LTR override for they are the workings of the devil
6095		$string = str_replace(dec_to_utf8(8238), "", $string);
6096		$string = str_replace(dec_to_utf8(8237), "", $string);
6097
6098		// Remove dodgy whitespaces
6099		$string = str_replace(chr(0xCA), "", $string);
6100	}
6101	$string = trim($string);
6102
6103	if(function_exists("mb_strlen"))
6104	{
6105		$string_length = mb_strlen($string);
6106	}
6107	else
6108	{
6109		$string_length = strlen($string);
6110	}
6111
6112	return $string_length;
6113}
6114
6115/**
6116 * Cuts a string at a specified point, mb strings accounted for
6117 *
6118 * @param string $string The string to cut.
6119 * @param int $start Where to cut
6120 * @param int $length (optional) How much to cut
6121 * @param bool $handle_entities (optional) Properly handle HTML entities?
6122 * @return string The cut part of the string.
6123 */
6124function my_substr($string, $start, $length=null, $handle_entities = false)
6125{
6126	if($handle_entities)
6127	{
6128		$string = unhtmlentities($string);
6129	}
6130	if(function_exists("mb_substr"))
6131	{
6132		if($length != null)
6133		{
6134			$cut_string = mb_substr($string, $start, $length);
6135		}
6136		else
6137		{
6138			$cut_string = mb_substr($string, $start);
6139		}
6140	}
6141	else
6142	{
6143		if($length != null)
6144		{
6145			$cut_string = substr($string, $start, $length);
6146		}
6147		else
6148		{
6149			$cut_string = substr($string, $start);
6150		}
6151	}
6152
6153	if($handle_entities)
6154	{
6155		$cut_string = htmlspecialchars_uni($cut_string);
6156	}
6157	return $cut_string;
6158}
6159
6160/**
6161 * Lowers the case of a string, mb strings accounted for
6162 *
6163 * @param string $string The string to lower.
6164 * @return string The lowered string.
6165 */
6166function my_strtolower($string)
6167{
6168	if(function_exists("mb_strtolower"))
6169	{
6170		$string = mb_strtolower($string);
6171	}
6172	else
6173	{
6174		$string = strtolower($string);
6175	}
6176
6177	return $string;
6178}
6179
6180/**
6181 * Finds a needle in a haystack and returns it position, mb strings accounted for, case insensitive
6182 *
6183 * @param string $haystack String to look in (haystack)
6184 * @param string $needle What to look for (needle)
6185 * @param int $offset (optional) How much to offset
6186 * @return int|bool false on needle not found, integer position if found
6187 */
6188function my_stripos($haystack, $needle, $offset=0)
6189{
6190	if($needle == '')
6191	{
6192		return false;
6193	}
6194
6195	if(function_exists("mb_stripos"))
6196	{
6197		$position = mb_stripos($haystack, $needle, $offset);
6198	}
6199	else
6200	{
6201		$position = stripos($haystack, $needle, $offset);
6202	}
6203
6204	return $position;
6205}
6206
6207/**
6208 * Finds a needle in a haystack and returns it position, mb strings accounted for
6209 *
6210 * @param string $haystack String to look in (haystack)
6211 * @param string $needle What to look for (needle)
6212 * @param int $offset (optional) How much to offset
6213 * @return int|bool false on needle not found, integer position if found
6214 */
6215function my_strpos($haystack, $needle, $offset=0)
6216{
6217	if($needle == '')
6218	{
6219		return false;
6220	}
6221
6222	if(function_exists("mb_strpos"))
6223	{
6224		$position = mb_strpos($haystack, $needle, $offset);
6225	}
6226	else
6227	{
6228		$position = strpos($haystack, $needle, $offset);
6229	}
6230
6231	return $position;
6232}
6233
6234/**
6235 * Ups the case of a string, mb strings accounted for
6236 *
6237 * @param string $string The string to up.
6238 * @return string The uped string.
6239 */
6240function my_strtoupper($string)
6241{
6242	if(function_exists("mb_strtoupper"))
6243	{
6244		$string = mb_strtoupper($string);
6245	}
6246	else
6247	{
6248		$string = strtoupper($string);
6249	}
6250
6251	return $string;
6252}
6253
6254/**
6255 * Returns any html entities to their original character
6256 *
6257 * @param string $string The string to un-htmlentitize.
6258 * @return string The un-htmlentitied' string.
6259 */
6260function unhtmlentities($string)
6261{
6262	// Replace numeric entities
6263	$string = preg_replace_callback('~&#x([0-9a-f]+);~i', 'unichr_callback1', $string);
6264	$string = preg_replace_callback('~&#([0-9]+);~', 'unichr_callback2', $string);
6265
6266	// Replace literal entities
6267	$trans_tbl = get_html_translation_table(HTML_ENTITIES);
6268	$trans_tbl = array_flip($trans_tbl);
6269
6270	return strtr($string, $trans_tbl);
6271}
6272
6273/**
6274 * Returns any ascii to it's character (utf-8 safe).
6275 *
6276 * @param int $c The ascii to characterize.
6277 * @return string|bool The characterized ascii. False on failure
6278 */
6279function unichr($c)
6280{
6281	if($c <= 0x7F)
6282	{
6283		return chr($c);
6284	}
6285	else if($c <= 0x7FF)
6286	{
6287		return chr(0xC0 | $c >> 6) . chr(0x80 | $c & 0x3F);
6288	}
6289	else if($c <= 0xFFFF)
6290	{
6291		return chr(0xE0 | $c >> 12) . chr(0x80 | $c >> 6 & 0x3F)
6292									. chr(0x80 | $c & 0x3F);
6293	}
6294	else if($c <= 0x10FFFF)
6295	{
6296		return chr(0xF0 | $c >> 18) . chr(0x80 | $c >> 12 & 0x3F)
6297									. chr(0x80 | $c >> 6 & 0x3F)
6298									. chr(0x80 | $c & 0x3F);
6299	}
6300	else
6301	{
6302		return false;
6303	}
6304}
6305
6306/**
6307 * Returns any ascii to it's character (utf-8 safe).
6308 *
6309 * @param array $matches Matches.
6310 * @return string|bool The characterized ascii. False on failure
6311 */
6312function unichr_callback1($matches)
6313{
6314	return unichr(hexdec($matches[1]));
6315}
6316
6317/**
6318 * Returns any ascii to it's character (utf-8 safe).
6319 *
6320 * @param array $matches Matches.
6321 * @return string|bool The characterized ascii. False on failure
6322 */
6323function unichr_callback2($matches)
6324{
6325	return unichr($matches[1]);
6326}
6327
6328/**
6329 * Get the event poster.
6330 *
6331 * @param array $event The event data array.
6332 * @return string The link to the event poster.
6333 */
6334function get_event_poster($event)
6335{
6336	$event['username'] = htmlspecialchars_uni($event['username']);
6337	$event['username'] = format_name($event['username'], $event['usergroup'], $event['displaygroup']);
6338	$event_poster = build_profile_link($event['username'], $event['author']);
6339	return $event_poster;
6340}
6341
6342/**
6343 * Get the event date.
6344 *
6345 * @param array $event The event data array.
6346 * @return string The event date.
6347 */
6348function get_event_date($event)
6349{
6350	global $mybb;
6351
6352	$event_date = explode("-", $event['date']);
6353	$event_date = gmmktime(0, 0, 0, $event_date[1], $event_date[0], $event_date[2]);
6354	$event_date = my_date($mybb->settings['dateformat'], $event_date);
6355
6356	return $event_date;
6357}
6358
6359/**
6360 * Get the profile link.
6361 *
6362 * @param int $uid The user id of the profile.
6363 * @return string The url to the profile.
6364 */
6365function get_profile_link($uid=0)
6366{
6367	$link = str_replace("{uid}", $uid, PROFILE_URL);
6368	return htmlspecialchars_uni($link);
6369}
6370
6371/**
6372 * Get the announcement link.
6373 *
6374 * @param int $aid The announement id of the announcement.
6375 * @return string The url to the announcement.
6376 */
6377function get_announcement_link($aid=0)
6378{
6379	$link = str_replace("{aid}", $aid, ANNOUNCEMENT_URL);
6380	return htmlspecialchars_uni($link);
6381}
6382
6383/**
6384 * Build the profile link.
6385 *
6386 * @param string $username The Username of the profile.
6387 * @param int $uid The user id of the profile.
6388 * @param string $target The target frame
6389 * @param string $onclick Any onclick javascript.
6390 * @return string The complete profile link.
6391 */
6392function build_profile_link($username="", $uid=0, $target="", $onclick="")
6393{
6394	global $mybb, $lang;
6395
6396	if(!$username && $uid == 0)
6397	{
6398		// Return Guest phrase for no UID, no guest nickname
6399		return htmlspecialchars_uni($lang->guest);
6400	}
6401	elseif($uid == 0)
6402	{
6403		// Return the guest's nickname if user is a guest but has a nickname
6404		return $username;
6405	}
6406	else
6407	{
6408		// Build the profile link for the registered user
6409		if(!empty($target))
6410		{
6411			$target = " target=\"{$target}\"";
6412		}
6413
6414		if(!empty($onclick))
6415		{
6416			$onclick = " onclick=\"{$onclick}\"";
6417		}
6418
6419		return "<a href=\"{$mybb->settings['bburl']}/".get_profile_link($uid)."\"{$target}{$onclick}>{$username}</a>";
6420	}
6421}
6422
6423/**
6424 * Build the forum link.
6425 *
6426 * @param int $fid The forum id of the forum.
6427 * @param int $page (Optional) The page number of the forum.
6428 * @return string The url to the forum.
6429 */
6430function get_forum_link($fid, $page=0)
6431{
6432	if($page > 0)
6433	{
6434		$link = str_replace("{fid}", $fid, FORUM_URL_PAGED);
6435		$link = str_replace("{page}", $page, $link);
6436		return htmlspecialchars_uni($link);
6437	}
6438	else
6439	{
6440		$link = str_replace("{fid}", $fid, FORUM_URL);
6441		return htmlspecialchars_uni($link);
6442	}
6443}
6444
6445/**
6446 * Build the thread link.
6447 *
6448 * @param int $tid The thread id of the thread.
6449 * @param int $page (Optional) The page number of the thread.
6450 * @param string $action (Optional) The action we're performing (ex, lastpost, newpost, etc)
6451 * @return string The url to the thread.
6452 */
6453function get_thread_link($tid, $page=0, $action='')
6454{
6455	if($page > 1)
6456	{
6457		if($action)
6458		{
6459			$link = THREAD_URL_ACTION;
6460			$link = str_replace("{action}", $action, $link);
6461		}
6462		else
6463		{
6464			$link = THREAD_URL_PAGED;
6465		}
6466		$link = str_replace("{tid}", $tid, $link);
6467		$link = str_replace("{page}", $page, $link);
6468		return htmlspecialchars_uni($link);
6469	}
6470	else
6471	{
6472		if($action)
6473		{
6474			$link = THREAD_URL_ACTION;
6475			$link = str_replace("{action}", $action, $link);
6476		}
6477		else
6478		{
6479			$link = THREAD_URL;
6480		}
6481		$link = str_replace("{tid}", $tid, $link);
6482		return htmlspecialchars_uni($link);
6483	}
6484}
6485
6486/**
6487 * Build the post link.
6488 *
6489 * @param int $pid The post ID of the post
6490 * @param int $tid The thread id of the post.
6491 * @return string The url to the post.
6492 */
6493function get_post_link($pid, $tid=0)
6494{
6495	if($tid > 0)
6496	{
6497		$link = str_replace("{tid}", $tid, THREAD_URL_POST);
6498		$link = str_replace("{pid}", $pid, $link);
6499		return htmlspecialchars_uni($link);
6500	}
6501	else
6502	{
6503		$link = str_replace("{pid}", $pid, POST_URL);
6504		return htmlspecialchars_uni($link);
6505	}
6506}
6507
6508/**
6509 * Build the event link.
6510 *
6511 * @param int $eid The event ID of the event
6512 * @return string The URL of the event
6513 */
6514function get_event_link($eid)
6515{
6516	$link = str_replace("{eid}", $eid, EVENT_URL);
6517	return htmlspecialchars_uni($link);
6518}
6519
6520/**
6521 * Build the link to a specified date on the calendar
6522 *
6523 * @param int $calendar The ID of the calendar
6524 * @param int $year The year
6525 * @param int $month The month
6526 * @param int $day The day (optional)
6527 * @return string The URL of the calendar
6528 */
6529function get_calendar_link($calendar, $year=0, $month=0, $day=0)
6530{
6531	if($day > 0)
6532	{
6533		$link = str_replace("{month}", $month, CALENDAR_URL_DAY);
6534		$link = str_replace("{year}", $year, $link);
6535		$link = str_replace("{day}", $day, $link);
6536		$link = str_replace("{calendar}", $calendar, $link);
6537		return htmlspecialchars_uni($link);
6538	}
6539	else if($month > 0)
6540	{
6541		$link = str_replace("{month}", $month, CALENDAR_URL_MONTH);
6542		$link = str_replace("{year}", $year, $link);
6543		$link = str_replace("{calendar}", $calendar, $link);
6544		return htmlspecialchars_uni($link);
6545	}
6546	/* Not implemented
6547	else if($year > 0)
6548	{
6549	}*/
6550	else
6551	{
6552		$link = str_replace("{calendar}", $calendar, CALENDAR_URL);
6553		return htmlspecialchars_uni($link);
6554	}
6555}
6556
6557/**
6558 * Build the link to a specified week on the calendar
6559 *
6560 * @param int $calendar The ID of the calendar
6561 * @param int $week The week
6562 * @return string The URL of the calendar
6563 */
6564function get_calendar_week_link($calendar, $week)
6565{
6566	if($week < 0)
6567	{
6568		$week = str_replace('-', "n", $week);
6569	}
6570	$link = str_replace("{week}", $week, CALENDAR_URL_WEEK);
6571	$link = str_replace("{calendar}", $calendar, $link);
6572	return htmlspecialchars_uni($link);
6573}
6574
6575/**
6576 * Get the user data of an user id.
6577 *
6578 * @param int $uid The user id of the user.
6579 * @return array The users data
6580 */
6581function get_user($uid)
6582{
6583	global $mybb, $db;
6584	static $user_cache;
6585
6586	$uid = (int)$uid;
6587
6588	if(!empty($mybb->user) && $uid == $mybb->user['uid'])
6589	{
6590		return $mybb->user;
6591	}
6592	elseif(isset($user_cache[$uid]))
6593	{
6594		return $user_cache[$uid];
6595	}
6596	elseif($uid > 0)
6597	{
6598		$query = $db->simple_select("users", "*", "uid = '{$uid}'");
6599		$user_cache[$uid] = $db->fetch_array($query);
6600
6601		return $user_cache[$uid];
6602	}
6603	return array();
6604}
6605
6606/**
6607 * Get the user data of an user username.
6608 *
6609 * @param string $username The user username of the user.
6610 * @param array $options
6611 * @return array The users data
6612 */
6613function get_user_by_username($username, $options=array())
6614{
6615	global $mybb, $db;
6616
6617	$username = $db->escape_string(my_strtolower($username));
6618
6619	if(!isset($options['username_method']))
6620	{
6621		$options['username_method'] = 0;
6622	}
6623
6624	switch($db->type)
6625	{
6626		case 'mysql':
6627		case 'mysqli':
6628			$field = 'username';
6629			$efield = 'email';
6630			break;
6631		default:
6632			$field = 'LOWER(username)';
6633			$efield = 'LOWER(email)';
6634			break;
6635	}
6636
6637	switch($options['username_method'])
6638	{
6639		case 1:
6640			$sqlwhere = "{$efield}='{$username}'";
6641			break;
6642		case 2:
6643			$sqlwhere = "{$field}='{$username}' OR {$efield}='{$username}'";
6644			break;
6645		default:
6646			$sqlwhere = "{$field}='{$username}'";
6647			break;
6648	}
6649
6650	$fields = array('uid');
6651	if(isset($options['fields']))
6652	{
6653		$fields = array_merge((array)$options['fields'], $fields);
6654	}
6655
6656	$query = $db->simple_select('users', implode(',', array_unique($fields)), $sqlwhere, array('limit' => 1));
6657
6658	if(isset($options['exists']))
6659	{
6660		return (bool)$db->num_rows($query);
6661	}
6662
6663	return $db->fetch_array($query);
6664}
6665
6666/**
6667 * Get the forum of a specific forum id.
6668 *
6669 * @param int $fid The forum id of the forum.
6670 * @param int $active_override (Optional) If set to 1, will override the active forum status
6671 * @return array|bool The database row of a forum. False on failure
6672 */
6673function get_forum($fid, $active_override=0)
6674{
6675	global $cache;
6676	static $forum_cache;
6677
6678	if(!isset($forum_cache) || !is_array($forum_cache))
6679	{
6680		$forum_cache = $cache->read("forums");
6681	}
6682
6683	if(empty($forum_cache[$fid]))
6684	{
6685		return false;
6686	}
6687
6688	if($active_override != 1)
6689	{
6690		$parents = explode(",", $forum_cache[$fid]['parentlist']);
6691		if(is_array($parents))
6692		{
6693			foreach($parents as $parent)
6694			{
6695				if($forum_cache[$parent]['active'] == 0)
6696				{
6697					return false;
6698				}
6699			}
6700		}
6701	}
6702
6703	return $forum_cache[$fid];
6704}
6705
6706/**
6707 * Get the thread of a thread id.
6708 *
6709 * @param int $tid The thread id of the thread.
6710 * @param boolean $recache Whether or not to recache the thread.
6711 * @return array|bool The database row of the thread. False on failure
6712 */
6713function get_thread($tid, $recache = false)
6714{
6715	global $db;
6716	static $thread_cache;
6717
6718	$tid = (int)$tid;
6719
6720	if(isset($thread_cache[$tid]) && !$recache)
6721	{
6722		return $thread_cache[$tid];
6723	}
6724	else
6725	{
6726		$query = $db->simple_select("threads", "*", "tid = '{$tid}'");
6727		$thread = $db->fetch_array($query);
6728
6729		if($thread)
6730		{
6731			$thread_cache[$tid] = $thread;
6732			return $thread;
6733		}
6734		else
6735		{
6736			$thread_cache[$tid] = false;
6737			return false;
6738		}
6739	}
6740}
6741
6742/**
6743 * Get the post of a post id.
6744 *
6745 * @param int $pid The post id of the post.
6746 * @return array|bool The database row of the post. False on failure
6747 */
6748function get_post($pid)
6749{
6750	global $db;
6751	static $post_cache;
6752
6753	$pid = (int)$pid;
6754
6755	if(isset($post_cache[$pid]))
6756	{
6757		return $post_cache[$pid];
6758	}
6759	else
6760	{
6761		$query = $db->simple_select("posts", "*", "pid = '{$pid}'");
6762		$post = $db->fetch_array($query);
6763
6764		if($post)
6765		{
6766			$post_cache[$pid] = $post;
6767			return $post;
6768		}
6769		else
6770		{
6771			$post_cache[$pid] = false;
6772			return false;
6773		}
6774	}
6775}
6776
6777/**
6778 * Get inactivate forums.
6779 *
6780 * @return string The comma separated values of the inactivate forum.
6781 */
6782function get_inactive_forums()
6783{
6784	global $forum_cache, $cache;
6785
6786	if(!$forum_cache)
6787	{
6788		cache_forums();
6789	}
6790
6791	$inactive = array();
6792
6793	foreach($forum_cache as $fid => $forum)
6794	{
6795		if($forum['active'] == 0)
6796		{
6797			$inactive[] = $fid;
6798			foreach($forum_cache as $fid1 => $forum1)
6799			{
6800				if(my_strpos(",".$forum1['parentlist'].",", ",".$fid.",") !== false && !in_array($fid1, $inactive))
6801				{
6802					$inactive[] = $fid1;
6803				}
6804			}
6805		}
6806	}
6807
6808	$inactiveforums = implode(",", $inactive);
6809
6810	return $inactiveforums;
6811}
6812
6813/**
6814 * Checks to make sure a user has not tried to login more times than permitted
6815 *
6816 * @param bool $fatal (Optional) Stop execution if it finds an error with the login. Default is True
6817 * @return bool|int Number of logins when success, false if failed.
6818 */
6819function login_attempt_check($uid = 0, $fatal = true)
6820{
6821	global $mybb, $lang, $db;
6822
6823	$attempts = array();
6824	$uid = (int)$uid;
6825	$now = TIME_NOW;
6826
6827	// Get this user's login attempts and eventual lockout, if a uid is provided
6828	if($uid > 0)
6829	{
6830		$query = $db->simple_select("users", "loginattempts, loginlockoutexpiry", "uid='{$uid}'", 1);
6831		$attempts = $db->fetch_array($query);
6832
6833		if($attempts['loginattempts'] <= 0)
6834		{
6835			return 0;
6836		}
6837	}
6838	// This user has a cookie lockout, show waiting time
6839	elseif(!empty($mybb->cookies['lockoutexpiry']) && $mybb->cookies['lockoutexpiry'] > $now)
6840	{
6841		if($fatal)
6842		{
6843			$secsleft = (int)($mybb->cookies['lockoutexpiry'] - $now);
6844			$hoursleft = floor($secsleft / 3600);
6845			$minsleft = floor(($secsleft / 60) % 60);
6846			$secsleft = floor($secsleft % 60);
6847
6848			error($lang->sprintf($lang->failed_login_wait, $hoursleft, $minsleft, $secsleft));
6849		}
6850
6851		return false;
6852	}
6853
6854	if($mybb->settings['failedlogincount'] > 0 && isset($attempts['loginattempts']) && $attempts['loginattempts'] >= $mybb->settings['failedlogincount'])
6855	{
6856		// Set the expiry dateline if not set yet
6857		if($attempts['loginlockoutexpiry'] == 0)
6858		{
6859			$attempts['loginlockoutexpiry'] = $now + ((int)$mybb->settings['failedlogintime'] * 60);
6860
6861			// Add a cookie lockout. This is used to prevent access to the login page immediately.
6862			// A deep lockout is issued if he tries to login into a locked out account
6863			my_setcookie('lockoutexpiry', $attempts['loginlockoutexpiry']);
6864
6865			$db->update_query("users", array(
6866				"loginlockoutexpiry" => $attempts['loginlockoutexpiry']
6867			), "uid='{$uid}'");
6868		}
6869
6870		if(empty($mybb->cookies['lockoutexpiry']))
6871		{
6872			$failedtime = $attempts['loginlockoutexpiry'];
6873		}
6874		else
6875		{
6876			$failedtime = $mybb->cookies['lockoutexpiry'];
6877		}
6878
6879		// Are we still locked out?
6880		if($attempts['loginlockoutexpiry'] > $now)
6881		{
6882			if($fatal)
6883			{
6884				$secsleft = (int)($attempts['loginlockoutexpiry'] - $now);
6885				$hoursleft = floor($secsleft / 3600);
6886				$minsleft = floor(($secsleft / 60) % 60);
6887				$secsleft = floor($secsleft % 60);
6888
6889				error($lang->sprintf($lang->failed_login_wait, $hoursleft, $minsleft, $secsleft));
6890			}
6891
6892			return false;
6893		}
6894		// Unlock if enough time has passed
6895		else {
6896
6897			if($uid > 0)
6898			{
6899				$db->update_query("users", array(
6900					"loginattempts" => 0,
6901					"loginlockoutexpiry" => 0
6902				), "uid='{$uid}'");
6903			}
6904
6905			// Wipe the cookie, no matter if a guest or a member
6906			my_unsetcookie('lockoutexpiry');
6907
6908			return 0;
6909		}
6910	}
6911
6912	if(!isset($attempts['loginattempts']))
6913	{
6914		$attempts['loginattempts'] = 0;
6915	}
6916
6917	// User can attempt another login
6918	return $attempts['loginattempts'];
6919}
6920
6921/**
6922 * Validates the format of an email address.
6923 *
6924 * @param string $email The string to check.
6925 * @return boolean True when valid, false when invalid.
6926 */
6927function validate_email_format($email)
6928{
6929	return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
6930}
6931
6932/**
6933 * Checks to see if the email is already in use by another
6934 *
6935 * @param string $email The email to check.
6936 * @param int $uid User ID of the user (updating only)
6937 * @return boolean True when in use, false when not.
6938 */
6939function email_already_in_use($email, $uid=0)
6940{
6941	global $db;
6942
6943	$uid_string = "";
6944	if($uid)
6945	{
6946		$uid_string = " AND uid != '".(int)$uid."'";
6947	}
6948	$query = $db->simple_select("users", "COUNT(email) as emails", "email = '".$db->escape_string($email)."'{$uid_string}");
6949
6950	if($db->fetch_field($query, "emails") > 0)
6951	{
6952		return true;
6953	}
6954
6955	return false;
6956}
6957
6958/**
6959 * Rebuilds settings.php
6960 *
6961 */
6962function rebuild_settings()
6963{
6964	global $db, $mybb;
6965
6966	$query = $db->simple_select("settings", "value, name", "", array(
6967		'order_by' => 'title',
6968		'order_dir' => 'ASC',
6969	));
6970
6971	$settings = '';
6972	while($setting = $db->fetch_array($query))
6973	{
6974		$mybb->settings[$setting['name']] = $setting['value'];
6975
6976		$setting['name'] = addcslashes($setting['name'], "\\'");
6977		$setting['value'] = addcslashes($setting['value'], '\\"$');
6978		$settings .= "\$settings['{$setting['name']}'] = \"{$setting['value']}\";\n";
6979	}
6980
6981	$settings = "<"."?php\n/*********************************\ \n  DO NOT EDIT THIS FILE, PLEASE USE\n  THE SETTINGS EDITOR\n\*********************************/\n\n$settings\n";
6982
6983	file_put_contents(MYBB_ROOT.'inc/settings.php', $settings, LOCK_EX);
6984
6985	$GLOBALS['settings'] = &$mybb->settings;
6986}
6987
6988/**
6989 * Build a PREG compatible array of search highlight terms to replace in posts.
6990 *
6991 * @param string $terms Incoming terms to highlight
6992 * @return array PREG compatible array of terms
6993 */
6994function build_highlight_array($terms)
6995{
6996	global $mybb;
6997
6998	if($mybb->settings['minsearchword'] < 1)
6999	{
7000		$mybb->settings['minsearchword'] = 3;
7001	}
7002
7003	if(is_array($terms))
7004	{
7005		$terms = implode(' ', $terms);
7006	}
7007
7008	// Strip out any characters that shouldn't be included
7009	$bad_characters = array(
7010		"(",
7011		")",
7012		"+",
7013		"-",
7014		"~"
7015	);
7016	$terms = str_replace($bad_characters, '', $terms);
7017
7018	// Check if this is a "series of words" - should be treated as an EXACT match
7019	if(my_strpos($terms, "\"") !== false)
7020	{
7021		$inquote = false;
7022		$terms = explode("\"", $terms);
7023		$words = array();
7024		foreach($terms as $phrase)
7025		{
7026			$phrase = htmlspecialchars_uni($phrase);
7027			if($phrase != "")
7028			{
7029				if($inquote)
7030				{
7031					$words[] = trim($phrase);
7032				}
7033				else
7034				{
7035					$split_words = preg_split("#\s{1,}#", $phrase, -1);
7036					if(!is_array($split_words))
7037					{
7038						continue;
7039					}
7040					foreach($split_words as $word)
7041					{
7042						if(!$word || strlen($word) < $mybb->settings['minsearchword'])
7043						{
7044							continue;
7045						}
7046						$words[] = trim($word);
7047					}
7048				}
7049			}
7050			$inquote = !$inquote;
7051		}
7052	}
7053	// Otherwise just a simple search query with no phrases
7054	else
7055	{
7056		$terms = htmlspecialchars_uni($terms);
7057		$split_words = preg_split("#\s{1,}#", $terms, -1);
7058		if(is_array($split_words))
7059		{
7060			foreach($split_words as $word)
7061			{
7062				if(!$word || strlen($word) < $mybb->settings['minsearchword'])
7063				{
7064					continue;
7065				}
7066				$words[] = trim($word);
7067			}
7068		}
7069	}
7070
7071	if(!is_array($words))
7072	{
7073		return false;
7074	}
7075
7076	// Sort the word array by length. Largest terms go first and work their way down to the smallest term.
7077	// This resolves problems like "test tes" where "tes" will be highlighted first, then "test" can't be highlighted because of the changed html
7078	usort($words, 'build_highlight_array_sort');
7079
7080	// Loop through our words to build the PREG compatible strings
7081	foreach($words as $word)
7082	{
7083		$word = trim($word);
7084
7085		$word = my_strtolower($word);
7086
7087		// Special boolean operators should be stripped
7088		if($word == "" || $word == "or" || $word == "not" || $word == "and")
7089		{
7090			continue;
7091		}
7092
7093		// Now make PREG compatible
7094		$find = "#(?!<.*?)(".preg_quote($word, "#").")(?![^<>]*?>)#ui";
7095		$replacement = "<span class=\"highlight\" style=\"padding-left: 0px; padding-right: 0px;\">$1</span>";
7096		$highlight_cache[$find] = $replacement;
7097	}
7098
7099	return $highlight_cache;
7100}
7101
7102/**
7103 * Sort the word array by length. Largest terms go first and work their way down to the smallest term.
7104 *
7105 * @param string $a First word.
7106 * @param string $b Second word.
7107 * @return integer Result of comparison function.
7108 */
7109function build_highlight_array_sort($a, $b)
7110{
7111	return strlen($b) - strlen($a);
7112}
7113
7114/**
7115 * Converts a decimal reference of a character to its UTF-8 equivalent
7116 * (Code by Anne van Kesteren, http://annevankesteren.nl/2005/05/character-references)
7117 *
7118 * @param int $src Decimal value of a character reference
7119 * @return string|bool
7120 */
7121function dec_to_utf8($src)
7122{
7123	$dest = '';
7124
7125	if($src < 0)
7126	{
7127		return false;
7128	}
7129	elseif($src <= 0x007f)
7130	{
7131		$dest .= chr($src);
7132	}
7133	elseif($src <= 0x07ff)
7134	{
7135		$dest .= chr(0xc0 | ($src >> 6));
7136		$dest .= chr(0x80 | ($src & 0x003f));
7137	}
7138	elseif($src <= 0xffff)
7139	{
7140		$dest .= chr(0xe0 | ($src >> 12));
7141		$dest .= chr(0x80 | (($src >> 6) & 0x003f));
7142		$dest .= chr(0x80 | ($src & 0x003f));
7143	}
7144	elseif($src <= 0x10ffff)
7145	{
7146		$dest .= chr(0xf0 | ($src >> 18));
7147		$dest .= chr(0x80 | (($src >> 12) & 0x3f));
7148		$dest .= chr(0x80 | (($src >> 6) & 0x3f));
7149		$dest .= chr(0x80 | ($src & 0x3f));
7150	}
7151	else
7152	{
7153		// Out of range
7154		return false;
7155	}
7156
7157	return $dest;
7158}
7159
7160/**
7161 * Checks if a username has been disallowed for registration/use.
7162 *
7163 * @param string $username The username
7164 * @param boolean $update_lastuse True if the 'last used' dateline should be updated if a match is found.
7165 * @return boolean True if banned, false if not banned
7166 */
7167function is_banned_username($username, $update_lastuse=false)
7168{
7169	global $db;
7170	$query = $db->simple_select('banfilters', 'filter, fid', "type='2'");
7171	while($banned_username = $db->fetch_array($query))
7172	{
7173		// Make regular expression * match
7174		$banned_username['filter'] = str_replace('\*', '(.*)', preg_quote($banned_username['filter'], '#'));
7175		if(preg_match("#(^|\b){$banned_username['filter']}($|\b)#i", $username))
7176		{
7177			// Updating last use
7178			if($update_lastuse == true)
7179			{
7180				$db->update_query("banfilters", array("lastuse" => TIME_NOW), "fid='{$banned_username['fid']}'");
7181			}
7182			return true;
7183		}
7184	}
7185	// Still here - good username
7186	return false;
7187}
7188
7189/**
7190 * Check if a specific email address has been banned.
7191 *
7192 * @param string $email The email address.
7193 * @param boolean $update_lastuse True if the 'last used' dateline should be updated if a match is found.
7194 * @return boolean True if banned, false if not banned
7195 */
7196function is_banned_email($email, $update_lastuse=false)
7197{
7198	global $cache, $db;
7199
7200	$banned_cache = $cache->read("bannedemails");
7201
7202	if($banned_cache === false)
7203	{
7204		// Failed to read cache, see if we can rebuild it
7205		$cache->update_bannedemails();
7206		$banned_cache = $cache->read("bannedemails");
7207	}
7208
7209	if(is_array($banned_cache) && !empty($banned_cache))
7210	{
7211		foreach($banned_cache as $banned_email)
7212		{
7213			// Make regular expression * match
7214			$banned_email['filter'] = str_replace('\*', '(.*)', preg_quote($banned_email['filter'], '#'));
7215
7216			if(preg_match("#{$banned_email['filter']}#i", $email))
7217			{
7218				// Updating last use
7219				if($update_lastuse == true)
7220				{
7221					$db->update_query("banfilters", array("lastuse" => TIME_NOW), "fid='{$banned_email['fid']}'");
7222				}
7223				return true;
7224			}
7225		}
7226	}
7227
7228	// Still here - good email
7229	return false;
7230}
7231
7232/**
7233 * Checks if a specific IP address has been banned.
7234 *
7235 * @param string $ip_address The IP address.
7236 * @param boolean $update_lastuse True if the 'last used' dateline should be updated if a match is found.
7237 * @return boolean True if banned, false if not banned.
7238 */
7239function is_banned_ip($ip_address, $update_lastuse=false)
7240{
7241	global $db, $cache;
7242
7243	$banned_ips = $cache->read("bannedips");
7244	if(!is_array($banned_ips))
7245	{
7246		return false;
7247	}
7248
7249	$ip_address = my_inet_pton($ip_address);
7250	foreach($banned_ips as $banned_ip)
7251	{
7252		if(!$banned_ip['filter'])
7253		{
7254			continue;
7255		}
7256
7257		$banned = false;
7258
7259		$ip_range = fetch_ip_range($banned_ip['filter']);
7260		if(is_array($ip_range))
7261		{
7262			if(strcmp($ip_range[0], $ip_address) <= 0 && strcmp($ip_range[1], $ip_address) >= 0)
7263			{
7264				$banned = true;
7265			}
7266		}
7267		elseif($ip_address == $ip_range)
7268		{
7269			$banned = true;
7270		}
7271		if($banned)
7272		{
7273			// Updating last use
7274			if($update_lastuse == true)
7275			{
7276				$db->update_query("banfilters", array("lastuse" => TIME_NOW), "fid='{$banned_ip['fid']}'");
7277			}
7278			return true;
7279		}
7280	}
7281
7282	// Still here - good ip
7283	return false;
7284}
7285
7286/**
7287 * Returns an array of supported timezones
7288 *
7289 * @return string[] Key is timezone offset, Value the language description
7290 */
7291function get_supported_timezones()
7292{
7293	global $lang;
7294	$timezones = array(
7295		"-12" => $lang->timezone_gmt_minus_1200,
7296		"-11" => $lang->timezone_gmt_minus_1100,
7297		"-10" => $lang->timezone_gmt_minus_1000,
7298		"-9.5" => $lang->timezone_gmt_minus_950,
7299		"-9" => $lang->timezone_gmt_minus_900,
7300		"-8" => $lang->timezone_gmt_minus_800,
7301		"-7" => $lang->timezone_gmt_minus_700,
7302		"-6" => $lang->timezone_gmt_minus_600,
7303		"-5" => $lang->timezone_gmt_minus_500,
7304		"-4.5" => $lang->timezone_gmt_minus_450,
7305		"-4" => $lang->timezone_gmt_minus_400,
7306		"-3.5" => $lang->timezone_gmt_minus_350,
7307		"-3" => $lang->timezone_gmt_minus_300,
7308		"-2" => $lang->timezone_gmt_minus_200,
7309		"-1" => $lang->timezone_gmt_minus_100,
7310		"0" => $lang->timezone_gmt,
7311		"1" => $lang->timezone_gmt_100,
7312		"2" => $lang->timezone_gmt_200,
7313		"3" => $lang->timezone_gmt_300,
7314		"3.5" => $lang->timezone_gmt_350,
7315		"4" => $lang->timezone_gmt_400,
7316		"4.5" => $lang->timezone_gmt_450,
7317		"5" => $lang->timezone_gmt_500,
7318		"5.5" => $lang->timezone_gmt_550,
7319		"5.75" => $lang->timezone_gmt_575,
7320		"6" => $lang->timezone_gmt_600,
7321		"6.5" => $lang->timezone_gmt_650,
7322		"7" => $lang->timezone_gmt_700,
7323		"8" => $lang->timezone_gmt_800,
7324		"8.5" => $lang->timezone_gmt_850,
7325		"8.75" => $lang->timezone_gmt_875,
7326		"9" => $lang->timezone_gmt_900,
7327		"9.5" => $lang->timezone_gmt_950,
7328		"10" => $lang->timezone_gmt_1000,
7329		"10.5" => $lang->timezone_gmt_1050,
7330		"11" => $lang->timezone_gmt_1100,
7331		"11.5" => $lang->timezone_gmt_1150,
7332		"12" => $lang->timezone_gmt_1200,
7333		"12.75" => $lang->timezone_gmt_1275,
7334		"13" => $lang->timezone_gmt_1300,
7335		"14" => $lang->timezone_gmt_1400
7336	);
7337	return $timezones;
7338}
7339
7340/**
7341 * Build a time zone selection list.
7342 *
7343 * @param string $name The name of the select
7344 * @param int $selected The selected time zone (defaults to GMT)
7345 * @param boolean $short True to generate a "short" list with just timezone and current time
7346 * @return string
7347 */
7348function build_timezone_select($name, $selected=0, $short=false)
7349{
7350	global $mybb, $lang, $templates;
7351
7352	$timezones = get_supported_timezones();
7353
7354	$selected = str_replace("+", "", $selected);
7355	$timezone_option = '';
7356	foreach($timezones as $timezone => $label)
7357	{
7358		$selected_add = "";
7359		if($selected == $timezone)
7360		{
7361			$selected_add = " selected=\"selected\"";
7362		}
7363		if($short == true)
7364		{
7365			$label = '';
7366			if($timezone != 0)
7367			{
7368				$label = $timezone;
7369				if($timezone > 0)
7370				{
7371					$label = "+{$label}";
7372				}
7373				if(strpos($timezone, ".") !== false)
7374				{
7375					$label = str_replace(".", ":", $label);
7376					$label = str_replace(":5", ":30", $label);
7377					$label = str_replace(":75", ":45", $label);
7378				}
7379				else
7380				{
7381					$label .= ":00";
7382				}
7383			}
7384			$time_in_zone = my_date($mybb->settings['timeformat'], TIME_NOW, $timezone);
7385			$label = $lang->sprintf($lang->timezone_gmt_short, $label." ", $time_in_zone);
7386		}
7387
7388		eval("\$timezone_option .= \"".$templates->get("usercp_options_timezone_option")."\";");
7389	}
7390
7391	eval("\$select = \"".$templates->get("usercp_options_timezone")."\";");
7392	return $select;
7393}
7394
7395/**
7396 * Fetch the contents of a remote file.
7397 *
7398 * @param string $url The URL of the remote file
7399 * @param array $post_data The array of post data
7400 * @param int $max_redirects Number of maximum redirects
7401 * @return string|bool The remote file contents. False on failure
7402 */
7403function fetch_remote_file($url, $post_data=array(), $max_redirects=20)
7404{
7405	global $mybb, $config;
7406
7407	if(!my_validate_url($url, true))
7408	{
7409		return false;
7410	}
7411
7412	$url_components = @parse_url($url);
7413
7414	if(!isset($url_components['scheme']))
7415	{
7416		$url_components['scheme'] = 'https';
7417	}
7418	if(!isset($url_components['port']))
7419	{
7420		$url_components['port'] = $url_components['scheme'] == 'https' ? 443 : 80;
7421	}
7422
7423	if(
7424		!$url_components ||
7425		empty($url_components['host']) ||
7426		(!empty($url_components['scheme']) && !in_array($url_components['scheme'], array('http', 'https'))) ||
7427		(!in_array($url_components['port'], array(80, 8080, 443))) ||
7428		(!empty($config['disallowed_remote_hosts']) && in_array($url_components['host'], $config['disallowed_remote_hosts']))
7429	)
7430	{
7431		return false;
7432	}
7433
7434	$addresses = get_ip_by_hostname($url_components['host']);
7435	$destination_address = $addresses[0];
7436
7437	if(!empty($config['disallowed_remote_addresses']))
7438	{
7439		foreach($config['disallowed_remote_addresses'] as $disallowed_address)
7440		{
7441			$ip_range = fetch_ip_range($disallowed_address);
7442
7443			$packed_address = my_inet_pton($destination_address);
7444
7445			if(is_array($ip_range))
7446			{
7447				if(strcmp($ip_range[0], $packed_address) <= 0 && strcmp($ip_range[1], $packed_address) >= 0)
7448				{
7449					return false;
7450				}
7451			}
7452			elseif($destination_address == $disallowed_address)
7453			{
7454				return false;
7455			}
7456		}
7457	}
7458
7459	$post_body = '';
7460	if(!empty($post_data))
7461	{
7462		foreach($post_data as $key => $val)
7463		{
7464			$post_body .= '&'.urlencode($key).'='.urlencode($val);
7465		}
7466		$post_body = ltrim($post_body, '&');
7467	}
7468
7469	if(function_exists("curl_init"))
7470	{
7471		$fetch_header = $max_redirects > 0;
7472
7473		$ch = curl_init();
7474
7475		$curlopt = array(
7476			CURLOPT_URL => $url,
7477			CURLOPT_HEADER => $fetch_header,
7478			CURLOPT_TIMEOUT => 10,
7479			CURLOPT_RETURNTRANSFER => 1,
7480			CURLOPT_FOLLOWLOCATION => 0,
7481		);
7482
7483		if($ca_bundle_path = get_ca_bundle_path())
7484		{
7485			$curlopt[CURLOPT_SSL_VERIFYPEER] = 1;
7486			$curlopt[CURLOPT_CAINFO] = $ca_bundle_path;
7487		}
7488		else
7489		{
7490			$curlopt[CURLOPT_SSL_VERIFYPEER] = 0;
7491		}
7492
7493		$curl_version_info = curl_version();
7494		$curl_version = $curl_version_info['version'];
7495
7496		if(version_compare(PHP_VERSION, '7.0.7', '>=') && version_compare($curl_version, '7.49', '>='))
7497		{
7498			// CURLOPT_CONNECT_TO
7499			$curlopt[10243] = array(
7500				$url_components['host'].':'.$url_components['port'].':'.$destination_address
7501			);
7502		}
7503		elseif(version_compare(PHP_VERSION, '5.5', '>=') && version_compare($curl_version, '7.21.3', '>='))
7504		{
7505			// CURLOPT_RESOLVE
7506			$curlopt[10203] = array(
7507				$url_components['host'].':'.$url_components['port'].':'.$destination_address
7508			);
7509		}
7510
7511		if(!empty($post_body))
7512		{
7513			$curlopt[CURLOPT_POST] = 1;
7514			$curlopt[CURLOPT_POSTFIELDS] = $post_body;
7515		}
7516
7517		curl_setopt_array($ch, $curlopt);
7518
7519		$response = curl_exec($ch);
7520
7521		if($fetch_header)
7522		{
7523			$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
7524			$header = substr($response, 0, $header_size);
7525			$body = substr($response, $header_size);
7526
7527			if(in_array(curl_getinfo($ch, CURLINFO_HTTP_CODE), array(301, 302)))
7528			{
7529				preg_match('/^Location:(.*?)(?:\n|$)/im', $header, $matches);
7530
7531				if($matches)
7532				{
7533					$data = fetch_remote_file(trim(array_pop($matches)), $post_data, --$max_redirects);
7534				}
7535			}
7536			else
7537			{
7538				$data = $body;
7539			}
7540		}
7541		else
7542		{
7543			$data = $response;
7544		}
7545
7546		curl_close($ch);
7547		return $data;
7548	}
7549	else if(function_exists("fsockopen"))
7550	{
7551		if(!isset($url_components['path']))
7552		{
7553			$url_components['path'] = "/";
7554		}
7555		if(isset($url_components['query']))
7556		{
7557			$url_components['path'] .= "?{$url_components['query']}";
7558		}
7559
7560		$scheme = '';
7561
7562		if($url_components['scheme'] == 'https')
7563		{
7564			$scheme = 'ssl://';
7565			if($url_components['port'] == 80)
7566			{
7567				$url_components['port'] = 443;
7568			}
7569		}
7570
7571		if(function_exists('stream_context_create'))
7572		{
7573			if($url_components['scheme'] == 'https' && $ca_bundle_path = get_ca_bundle_path())
7574			{
7575				$context = stream_context_create(array(
7576					'ssl' => array(
7577						'verify_peer' => true,
7578						'verify_peer_name' => true,
7579						'peer_name' => $url_components['host'],
7580						'cafile' => $ca_bundle_path,
7581					),
7582				));
7583			}
7584			else
7585			{
7586				$context = stream_context_create(array(
7587					'ssl' => array(
7588						'verify_peer' => false,
7589						'verify_peer_name' => false,
7590						'peer_name' => $url_components['host'],
7591					),
7592				));
7593			}
7594
7595			$fp = @stream_socket_client($scheme.$destination_address.':'.(int)$url_components['port'], $error_no, $error, 10, STREAM_CLIENT_CONNECT, $context);
7596		}
7597		else
7598		{
7599			$fp = @fsockopen($scheme.$url_components['host'], (int)$url_components['port'], $error_no, $error, 10);
7600		}
7601
7602		if(!$fp)
7603		{
7604			return false;
7605		}
7606		@stream_set_timeout($fp, 10);
7607		$headers = array();
7608		if(!empty($post_body))
7609		{
7610			$headers[] = "POST {$url_components['path']} HTTP/1.0";
7611			$headers[] = "Content-Length: ".strlen($post_body);
7612			$headers[] = "Content-Type: application/x-www-form-urlencoded";
7613		}
7614		else
7615		{
7616			$headers[] = "GET {$url_components['path']} HTTP/1.0";
7617		}
7618
7619		$headers[] = "Host: {$url_components['host']}";
7620		$headers[] = "Connection: Close";
7621		$headers[] = '';
7622
7623		if(!empty($post_body))
7624		{
7625			$headers[] = $post_body;
7626		}
7627		else
7628		{
7629			// If we have no post body, we need to add an empty element to make sure we've got \r\n\r\n before the (non-existent) body starts
7630			$headers[] = '';
7631		}
7632
7633		$headers = implode("\r\n", $headers);
7634		if(!@fwrite($fp, $headers))
7635		{
7636			return false;
7637		}
7638
7639		$data = null;
7640
7641		while(!feof($fp))
7642		{
7643			$data .= fgets($fp, 12800);
7644		}
7645		fclose($fp);
7646
7647		$data = explode("\r\n\r\n", $data, 2);
7648
7649		$header = $data[0];
7650		$status_line = current(explode("\n\n", $header, 1));
7651		$body = $data[1];
7652
7653		if($max_redirects > 0 && (strstr($status_line, ' 301 ') || strstr($status_line, ' 302 ')))
7654		{
7655			preg_match('/^Location:(.*?)(?:\n|$)/im', $header, $matches);
7656
7657			if($matches)
7658			{
7659				$data = fetch_remote_file(trim(array_pop($matches)), $post_data, --$max_redirects);
7660			}
7661		}
7662		else
7663		{
7664			$data = $body;
7665		}
7666
7667		return $data;
7668	}
7669	else
7670	{
7671		return false;
7672	}
7673}
7674
7675/**
7676 * Resolves a hostname into a set of IP addresses.
7677 *
7678 * @param string $hostname The hostname to be resolved
7679 * @return array|bool The resulting IP addresses. False on failure
7680 */
7681function get_ip_by_hostname($hostname)
7682{
7683	$addresses = @gethostbynamel($hostname);
7684
7685	if(!$addresses)
7686	{
7687		$result_set = @dns_get_record($hostname, DNS_A | DNS_AAAA);
7688
7689		if($result_set)
7690		{
7691			$addresses = array_column($result_set, 'ip');
7692		}
7693		else
7694		{
7695			return false;
7696		}
7697	}
7698
7699	return $addresses;
7700}
7701
7702/**
7703 * Returns the location of the CA bundle defined in the PHP configuration.
7704 *
7705 * @return string|bool The location of the CA bundle, false if not set
7706 */
7707function get_ca_bundle_path()
7708{
7709	if($path = ini_get('openssl.cafile'))
7710	{
7711		return $path;
7712	}
7713	if($path = ini_get('curl.cainfo'))
7714	{
7715		return $path;
7716	}
7717
7718	return false;
7719}
7720
7721/**
7722 * Checks if a particular user is a super administrator.
7723 *
7724 * @param int $uid The user ID to check against the list of super admins
7725 * @return boolean True if a super admin, false if not
7726 */
7727function is_super_admin($uid)
7728{
7729	static $super_admins;
7730
7731	if(!isset($super_admins))
7732	{
7733		global $mybb;
7734		$super_admins = str_replace(" ", "", $mybb->config['super_admins']);
7735	}
7736
7737	if(my_strpos(",{$super_admins},", ",{$uid},") === false)
7738	{
7739		return false;
7740	}
7741	else
7742	{
7743		return true;
7744	}
7745}
7746
7747/**
7748 * Checks if a user is a member of a particular group
7749 * Originates from frostschutz's PluginLibrary
7750 * github.com/frostschutz
7751 *
7752 * @param array|int|string A selection of groups (as array or comma seperated) to check or -1 for any group
7753 * @param bool|array|int False assumes the current user. Otherwise an user array or an id can be passed
7754 * @return array Array of groups specified in the first param to which the user belongs
7755 */
7756function is_member($groups, $user = false)
7757{
7758	global $mybb;
7759
7760	if(empty($groups))
7761	{
7762		return array();
7763	}
7764
7765	if($user == false)
7766	{
7767		$user = $mybb->user;
7768	}
7769	else if(!is_array($user))
7770	{
7771		// Assume it's a UID
7772		$user = get_user($user);
7773	}
7774
7775	$memberships = array_map('intval', explode(',', $user['additionalgroups']));
7776	$memberships[] = $user['usergroup'];
7777
7778	if(!is_array($groups))
7779	{
7780		if((int)$groups == -1)
7781		{
7782			return $memberships;
7783		}
7784		else
7785		{
7786			if(is_string($groups))
7787			{
7788				$groups = explode(',', $groups);
7789			}
7790			else
7791			{
7792				$groups = (array)$groups;
7793			}
7794		}
7795	}
7796
7797	$groups = array_filter(array_map('intval', $groups));
7798
7799	return array_intersect($groups, $memberships);
7800}
7801
7802/**
7803 * Split a string based on the specified delimeter, ignoring said delimeter in escaped strings.
7804 * Ex: the "quick brown fox" jumped, could return 1 => the, 2 => quick brown fox, 3 => jumped
7805 *
7806 * @param string $delimeter The delimeter to split by
7807 * @param string $string The string to split
7808 * @param string $escape The escape character or string if we have one.
7809 * @return array Array of split string
7810 */
7811function escaped_explode($delimeter, $string, $escape="")
7812{
7813	$strings = array();
7814	$original = $string;
7815	$in_escape = false;
7816	if($escape)
7817	{
7818		if(is_array($escape))
7819		{
7820			function escaped_explode_escape($string)
7821			{
7822				return preg_quote($string, "#");
7823			}
7824			$escape_preg = "(".implode("|", array_map("escaped_explode_escape", $escape)).")";
7825		}
7826		else
7827		{
7828			$escape_preg = preg_quote($escape, "#");
7829		}
7830		$quoted_strings = preg_split("#(?<!\\\){$escape_preg}#", $string);
7831	}
7832	else
7833	{
7834		$quoted_strings = array($string);
7835	}
7836	foreach($quoted_strings as $string)
7837	{
7838		if($string != "")
7839		{
7840			if($in_escape)
7841			{
7842				$strings[] = trim($string);
7843			}
7844			else
7845			{
7846				$split_strings = explode($delimeter, $string);
7847				foreach($split_strings as $string)
7848				{
7849					if($string == "") continue;
7850					$strings[] = trim($string);
7851				}
7852			}
7853		}
7854		$in_escape = !$in_escape;
7855	}
7856	if(!count($strings))
7857	{
7858		return $original;
7859	}
7860	return $strings;
7861}
7862
7863/**
7864 * DEPRECATED! Please use IPv6 compatible fetch_ip_range!
7865 * Fetch an IPv4 long formatted range for searching IPv4 IP addresses.
7866 *
7867 * @deprecated
7868 * @param string $ip The IP address to convert to a range based LONG
7869 * @return string|array If a full IP address is provided, the ip2long equivalent, otherwise an array of the upper & lower extremities of the IP
7870 */
7871function fetch_longipv4_range($ip)
7872{
7873	$ip_bits = explode(".", $ip);
7874	$ip_string1 = $ip_string2 = "";
7875
7876	if($ip == "*")
7877	{
7878		return array(ip2long('0.0.0.0'), ip2long('255.255.255.255'));
7879	}
7880
7881	if(strpos($ip, ".*") === false)
7882	{
7883		$ip = str_replace("*", "", $ip);
7884		if(count($ip_bits) == 4)
7885		{
7886			return ip2long($ip);
7887		}
7888		else
7889		{
7890			return array(ip2long($ip.".0"), ip2long($ip.".255"));
7891		}
7892	}
7893	// Wildcard based IP provided
7894	else
7895	{
7896		$sep = "";
7897		foreach($ip_bits as $piece)
7898		{
7899			if($piece == "*")
7900			{
7901				$ip_string1 .= $sep."0";
7902				$ip_string2 .= $sep."255";
7903			}
7904			else
7905			{
7906				$ip_string1 .= $sep.$piece;
7907				$ip_string2 .= $sep.$piece;
7908			}
7909			$sep = ".";
7910		}
7911		return array(ip2long($ip_string1), ip2long($ip_string2));
7912	}
7913}
7914
7915/**
7916 * Fetch a list of ban times for a user account.
7917 *
7918 * @return array Array of ban times
7919 */
7920function fetch_ban_times()
7921{
7922	global $plugins, $lang;
7923
7924	// Days-Months-Years
7925	$ban_times = array(
7926		"1-0-0" => "1 {$lang->day}",
7927		"2-0-0" => "2 {$lang->days}",
7928		"3-0-0" => "3 {$lang->days}",
7929		"4-0-0" => "4 {$lang->days}",
7930		"5-0-0" => "5 {$lang->days}",
7931		"6-0-0" => "6 {$lang->days}",
7932		"7-0-0" => "1 {$lang->week}",
7933		"14-0-0" => "2 {$lang->weeks}",
7934		"21-0-0" => "3 {$lang->weeks}",
7935		"0-1-0" => "1 {$lang->month}",
7936		"0-2-0" => "2 {$lang->months}",
7937		"0-3-0" => "3 {$lang->months}",
7938		"0-4-0" => "4 {$lang->months}",
7939		"0-5-0" => "5 {$lang->months}",
7940		"0-6-0" => "6 {$lang->months}",
7941		"0-0-1" => "1 {$lang->year}",
7942		"0-0-2" => "2 {$lang->years}"
7943	);
7944
7945	$ban_times = $plugins->run_hooks("functions_fetch_ban_times", $ban_times);
7946
7947	$ban_times['---'] = $lang->permanent;
7948	return $ban_times;
7949}
7950
7951/**
7952 * Format a ban length in to a UNIX timestamp.
7953 *
7954 * @param string $date The ban length string
7955 * @param int $stamp The optional UNIX timestamp, if 0, current time is used.
7956 * @return int The UNIX timestamp when the ban will be lifted
7957 */
7958function ban_date2timestamp($date, $stamp=0)
7959{
7960	if($stamp == 0)
7961	{
7962		$stamp = TIME_NOW;
7963	}
7964	$d = explode('-', $date);
7965	$nowdate = date("H-j-n-Y", $stamp);
7966	$n = explode('-', $nowdate);
7967	$n[1] += $d[0];
7968	$n[2] += $d[1];
7969	$n[3] += $d[2];
7970	return mktime(date("G", $stamp), date("i", $stamp), 0, $n[2], $n[1], $n[3]);
7971}
7972
7973/**
7974 * Expire old warnings in the database.
7975 *
7976 * @return bool
7977 */
7978function expire_warnings()
7979{
7980	global $warningshandler;
7981
7982	if(!is_object($warningshandler))
7983	{
7984		require_once MYBB_ROOT.'inc/datahandlers/warnings.php';
7985		$warningshandler = new WarningsHandler('update');
7986	}
7987
7988	return $warningshandler->expire_warnings();
7989}
7990
7991/**
7992 * Custom chmod function to fix problems with hosts who's server configurations screw up umasks
7993 *
7994 * @param string $file The file to chmod
7995 * @param string $mode The mode to chmod(i.e. 0666)
7996 * @return bool
7997 */
7998function my_chmod($file, $mode)
7999{
8000	// Passing $mode as an octal number causes strlen and substr to return incorrect values. Instead pass as a string
8001	if(substr($mode, 0, 1) != '0' || strlen($mode) !== 4)
8002	{
8003		return false;
8004	}
8005	$old_umask = umask(0);
8006
8007	// We convert the octal string to a decimal number because passing a octal string doesn't work with chmod
8008	// and type casting subsequently removes the prepended 0 which is needed for octal numbers
8009	$result = chmod($file, octdec($mode));
8010	umask($old_umask);
8011	return $result;
8012}
8013
8014/**
8015 * Custom rmdir function to loop through an entire directory and delete all files/folders within
8016 *
8017 * @param string $path The path to the directory
8018 * @param array $ignore Any files you wish to ignore (optional)
8019 * @return bool
8020 */
8021function my_rmdir_recursive($path, $ignore=array())
8022{
8023	global $orig_dir;
8024
8025	if(!isset($orig_dir))
8026	{
8027		$orig_dir = $path;
8028	}
8029
8030	if(@is_dir($path) && !@is_link($path))
8031	{
8032		if($dh = @opendir($path))
8033		{
8034			while(($file = @readdir($dh)) !== false)
8035			{
8036				if($file == '.' || $file == '..' || $file == '.svn' || in_array($path.'/'.$file, $ignore) || !my_rmdir_recursive($path.'/'.$file))
8037				{
8038					continue;
8039				}
8040			}
8041		   @closedir($dh);
8042		}
8043
8044		// Are we done? Don't delete the main folder too and return true
8045		if($path == $orig_dir)
8046		{
8047			return true;
8048		}
8049
8050		return @rmdir($path);
8051	}
8052
8053	return @unlink($path);
8054}
8055
8056/**
8057 * Counts the number of subforums in a array([pid][disporder][fid]) starting from the pid
8058 *
8059 * @param array $array The array of forums
8060 * @return integer The number of sub forums
8061 */
8062function subforums_count($array=array())
8063{
8064	$count = 0;
8065	foreach($array as $array2)
8066	{
8067		$count += count($array2);
8068	}
8069
8070	return $count;
8071}
8072
8073/**
8074 * DEPRECATED! Please use IPv6 compatible my_inet_pton!
8075 * Fix for PHP's ip2long to guarantee a 32-bit signed integer value is produced (this is aimed
8076 * at 64-bit versions of PHP)
8077 *
8078 * @deprecated
8079 * @param string $ip The IP to convert
8080 * @return integer IP in 32-bit signed format
8081 */
8082function my_ip2long($ip)
8083{
8084	$ip_long = ip2long($ip);
8085
8086	if(!$ip_long)
8087	{
8088		$ip_long = sprintf("%u", ip2long($ip));
8089
8090		if(!$ip_long)
8091		{
8092			return 0;
8093		}
8094	}
8095
8096	if($ip_long >= 2147483648) // Won't occur on 32-bit PHP
8097	{
8098		$ip_long -= 4294967296;
8099	}
8100
8101	return $ip_long;
8102}
8103
8104/**
8105 * DEPRECATED! Please use IPv6 compatible my_inet_ntop!
8106 * As above, fix for PHP's long2ip on 64-bit versions
8107 *
8108 * @deprecated
8109 * @param integer $long The IP to convert (will accept 64-bit IPs as well)
8110 * @return string IP in IPv4 format
8111 */
8112function my_long2ip($long)
8113{
8114	// On 64-bit machines is_int will return true. On 32-bit it will return false
8115	if($long < 0 && is_int(2147483648))
8116	{
8117		// We have a 64-bit system
8118		$long += 4294967296;
8119	}
8120	return long2ip($long);
8121}
8122
8123/**
8124 * Converts a human readable IP address to its packed in_addr representation
8125 *
8126 * @param string $ip The IP to convert
8127 * @return string IP in 32bit or 128bit binary format
8128 */
8129function my_inet_pton($ip)
8130{
8131	if(function_exists('inet_pton'))
8132	{
8133		return @inet_pton($ip);
8134	}
8135	else
8136	{
8137		/**
8138		 * Replace inet_pton()
8139		 *
8140		 * @category    PHP
8141		 * @package     PHP_Compat
8142		 * @license     LGPL - http://www.gnu.org/licenses/lgpl.html
8143		 * @copyright   2004-2007 Aidan Lister <aidan@php.net>, Arpad Ray <arpad@php.net>
8144		 * @link        http://php.net/inet_pton
8145		 * @author      Arpad Ray <arpad@php.net>
8146		 * @version     $Revision: 269597 $
8147		 */
8148		$r = ip2long($ip);
8149		if($r !== false && $r != -1)
8150		{
8151			return pack('N', $r);
8152		}
8153
8154		$delim_count = substr_count($ip, ':');
8155		if($delim_count < 1 || $delim_count > 7)
8156		{
8157			return false;
8158		}
8159
8160		$r = explode(':', $ip);
8161		$rcount = count($r);
8162		if(($doub = array_search('', $r, 1)) !== false)
8163		{
8164			$length = (!$doub || $doub == $rcount - 1 ? 2 : 1);
8165			array_splice($r, $doub, $length, array_fill(0, 8 + $length - $rcount, 0));
8166		}
8167
8168		$r = array_map('hexdec', $r);
8169		array_unshift($r, 'n*');
8170		$r = call_user_func_array('pack', $r);
8171
8172		return $r;
8173	}
8174}
8175
8176/**
8177 * Converts a packed internet address to a human readable representation
8178 *
8179 * @param string $ip IP in 32bit or 128bit binary format
8180 * @return string IP in human readable format
8181 */
8182function my_inet_ntop($ip)
8183{
8184	if(function_exists('inet_ntop'))
8185	{
8186		return @inet_ntop($ip);
8187	}
8188	else
8189	{
8190		/**
8191		 * Replace inet_ntop()
8192		 *
8193		 * @category    PHP
8194		 * @package     PHP_Compat
8195		 * @license     LGPL - http://www.gnu.org/licenses/lgpl.html
8196		 * @copyright   2004-2007 Aidan Lister <aidan@php.net>, Arpad Ray <arpad@php.net>
8197		 * @link        http://php.net/inet_ntop
8198		 * @author      Arpad Ray <arpad@php.net>
8199		 * @version     $Revision: 269597 $
8200		 */
8201		switch(strlen($ip))
8202		{
8203			case 4:
8204				list(,$r) = unpack('N', $ip);
8205				return long2ip($r);
8206			case 16:
8207				$r = substr(chunk_split(bin2hex($ip), 4, ':'), 0, -1);
8208				$r = preg_replace(
8209					array('/(?::?\b0+\b:?){2,}/', '/\b0+([^0])/e'),
8210					array('::', '(int)"$1"?"$1":"0$1"'),
8211					$r);
8212				return $r;
8213		}
8214		return false;
8215	}
8216}
8217
8218/**
8219 * Fetch an binary formatted range for searching IPv4 and IPv6 IP addresses.
8220 *
8221 * @param string $ipaddress The IP address to convert to a range
8222 * @return string|array|bool If a full IP address is provided, the in_addr representation, otherwise an array of the upper & lower extremities of the IP. False on failure
8223 */
8224function fetch_ip_range($ipaddress)
8225{
8226	// Wildcard
8227	if(strpos($ipaddress, '*') !== false)
8228	{
8229		if(strpos($ipaddress, ':') !== false)
8230		{
8231			// IPv6
8232			$upper = str_replace('*', 'ffff', $ipaddress);
8233			$lower = str_replace('*', '0', $ipaddress);
8234		}
8235		else
8236		{
8237			// IPv4
8238			$ip_bits = count(explode('.', $ipaddress));
8239			if($ip_bits < 4)
8240			{
8241				// Support for 127.0.*
8242				$replacement = str_repeat('.*', 4-$ip_bits);
8243				$ipaddress = substr_replace($ipaddress, $replacement, strrpos($ipaddress, '*')+1, 0);
8244			}
8245			$upper = str_replace('*', '255', $ipaddress);
8246			$lower = str_replace('*', '0', $ipaddress);
8247		}
8248		$upper = my_inet_pton($upper);
8249		$lower = my_inet_pton($lower);
8250		if($upper === false || $lower === false)
8251		{
8252			return false;
8253		}
8254		return array($lower, $upper);
8255	}
8256	// CIDR notation
8257	elseif(strpos($ipaddress, '/') !== false)
8258	{
8259		$ipaddress = explode('/', $ipaddress);
8260		$ip_address = $ipaddress[0];
8261		$ip_range = (int)$ipaddress[1];
8262
8263		if(empty($ip_address) || empty($ip_range))
8264		{
8265			// Invalid input
8266			return false;
8267		}
8268		else
8269		{
8270			$ip_address = my_inet_pton($ip_address);
8271
8272			if(!$ip_address)
8273			{
8274				// Invalid IP address
8275				return false;
8276			}
8277		}
8278
8279		/**
8280		 * Taken from: https://github.com/NewEraCracker/php_work/blob/master/ipRangeCalculate.php
8281		 * Author: NewEraCracker
8282		 * License: Public Domain
8283		 */
8284
8285		// Pack IP, Set some vars
8286		$ip_pack = $ip_address;
8287		$ip_pack_size = strlen($ip_pack);
8288		$ip_bits_size = $ip_pack_size*8;
8289
8290		// IP bits (lots of 0's and 1's)
8291		$ip_bits = '';
8292		for($i = 0; $i < $ip_pack_size; $i = $i+1)
8293		{
8294			$bit = decbin(ord($ip_pack[$i]));
8295			$bit = str_pad($bit, 8, '0', STR_PAD_LEFT);
8296			$ip_bits .= $bit;
8297		}
8298
8299		// Significative bits (from the ip range)
8300		$ip_bits = substr($ip_bits, 0, $ip_range);
8301
8302		// Some calculations
8303		$ip_lower_bits = str_pad($ip_bits, $ip_bits_size, '0', STR_PAD_RIGHT);
8304		$ip_higher_bits = str_pad($ip_bits, $ip_bits_size, '1', STR_PAD_RIGHT);
8305
8306		// Lower IP
8307		$ip_lower_pack = '';
8308		for($i=0; $i < $ip_bits_size; $i=$i+8)
8309		{
8310			$chr = substr($ip_lower_bits, $i, 8);
8311			$chr = chr(bindec($chr));
8312			$ip_lower_pack .= $chr;
8313		}
8314
8315		// Higher IP
8316		$ip_higher_pack = '';
8317		for($i=0; $i < $ip_bits_size; $i=$i+8)
8318		{
8319			$chr = substr($ip_higher_bits, $i, 8);
8320			$chr = chr( bindec($chr) );
8321			$ip_higher_pack .= $chr;
8322		}
8323
8324		return array($ip_lower_pack, $ip_higher_pack);
8325	}
8326	// Just on IP address
8327	else
8328	{
8329		return my_inet_pton($ipaddress);
8330	}
8331}
8332
8333/**
8334 * Time how long it takes for a particular piece of code to run. Place calls above & below the block of code.
8335 *
8336 * @return float The time taken
8337 */
8338function get_execution_time()
8339{
8340	static $time_start;
8341
8342	$time = microtime(true);
8343
8344	// Just starting timer, init and return
8345	if(!$time_start)
8346	{
8347		$time_start = $time;
8348		return;
8349	}
8350	// Timer has run, return execution time
8351	else
8352	{
8353		$total = $time-$time_start;
8354		if($total < 0) $total = 0;
8355		$time_start = 0;
8356		return $total;
8357	}
8358}
8359
8360/**
8361 * Processes a checksum list on MyBB files and returns a result set
8362 *
8363 * @param string $path The base path
8364 * @param int $count The count of files
8365 * @return array The bad files
8366 */
8367function verify_files($path=MYBB_ROOT, $count=0)
8368{
8369	global $mybb, $checksums, $bad_verify_files;
8370
8371	// We don't need to check these types of files
8372	$ignore = array(".", "..", ".svn", "config.php", "settings.php", "Thumb.db", "config.default.php", "lock", "htaccess.txt", "htaccess-nginx.txt", "logo.gif", "logo.png");
8373	$ignore_ext = array("attach");
8374
8375	if(substr($path, -1, 1) == "/")
8376	{
8377		$path = substr($path, 0, -1);
8378	}
8379
8380	if(!is_array($bad_verify_files))
8381	{
8382		$bad_verify_files = array();
8383	}
8384
8385	// Make sure that we're in a directory and it's not a symbolic link
8386	if(@is_dir($path) && !@is_link($path))
8387	{
8388		if($dh = @opendir($path))
8389		{
8390			// Loop through all the files/directories in this directory
8391			while(($file = @readdir($dh)) !== false)
8392			{
8393				if(in_array($file, $ignore) || in_array(get_extension($file), $ignore_ext))
8394				{
8395					continue;
8396				}
8397
8398				// Recurse through the directory tree
8399				if(is_dir($path."/".$file))
8400				{
8401					verify_files($path."/".$file, ($count+1));
8402					continue;
8403				}
8404
8405				// We only need the last part of the path (from the MyBB directory to the file. i.e. inc/functions.php)
8406				$file_path = ".".str_replace(substr(MYBB_ROOT, 0, -1), "", $path)."/".$file;
8407
8408				// Does this file even exist in our official list? Perhaps it's a plugin
8409				if(array_key_exists($file_path, $checksums))
8410				{
8411					$filename = $path."/".$file;
8412					$handle = fopen($filename, "rb");
8413					$hashingContext = hash_init('sha512');
8414					while(!feof($handle))
8415					{
8416						hash_update($hashingContext, fread($handle, 8192));
8417					}
8418					fclose($handle);
8419
8420					$checksum = hash_final($hashingContext);
8421
8422					// Does it match any of our hashes (unix/windows new lines taken into consideration with the hashes)
8423					if(!in_array($checksum, $checksums[$file_path]))
8424					{
8425						$bad_verify_files[] = array("status" => "changed", "path" => $file_path);
8426					}
8427				}
8428				unset($checksums[$file_path]);
8429			}
8430		   @closedir($dh);
8431		}
8432	}
8433
8434	if($count == 0)
8435	{
8436		if(!empty($checksums))
8437		{
8438			foreach($checksums as $file_path => $hashes)
8439			{
8440				if(in_array(basename($file_path), $ignore))
8441				{
8442					continue;
8443				}
8444				$bad_verify_files[] = array("status" => "missing", "path" => $file_path);
8445			}
8446		}
8447	}
8448
8449	// uh oh
8450	if($count == 0)
8451	{
8452		return $bad_verify_files;
8453	}
8454}
8455
8456/**
8457 * Returns a signed value equal to an integer
8458 *
8459 * @param int $int The integer
8460 * @return string The signed equivalent
8461 */
8462function signed($int)
8463{
8464	if($int < 0)
8465	{
8466		return "$int";
8467	}
8468	else
8469	{
8470		return "+$int";
8471	}
8472}
8473
8474/**
8475 * Returns a securely generated seed
8476 *
8477 * @return string A secure binary seed
8478 */
8479function secure_binary_seed_rng($bytes)
8480{
8481	$output = null;
8482
8483	if(version_compare(PHP_VERSION, '7.0', '>='))
8484	{
8485		try
8486		{
8487			$output = random_bytes($bytes);
8488		} catch (Exception $e) {
8489		}
8490	}
8491
8492	if(strlen($output) < $bytes)
8493	{
8494		if(@is_readable('/dev/urandom') && ($handle = @fopen('/dev/urandom', 'rb')))
8495		{
8496			$output = @fread($handle, $bytes);
8497			@fclose($handle);
8498		}
8499	}
8500	else
8501	{
8502		return $output;
8503	}
8504
8505	if(strlen($output) < $bytes)
8506	{
8507		if(function_exists('mcrypt_create_iv'))
8508		{
8509			if (DIRECTORY_SEPARATOR == '/')
8510			{
8511				$source = MCRYPT_DEV_URANDOM;
8512			}
8513			else
8514			{
8515				$source = MCRYPT_RAND;
8516			}
8517
8518			$output = @mcrypt_create_iv($bytes, $source);
8519		}
8520	}
8521	else
8522	{
8523		return $output;
8524	}
8525
8526	if(strlen($output) < $bytes)
8527	{
8528		if(function_exists('openssl_random_pseudo_bytes'))
8529		{
8530			// PHP <5.3.4 had a bug which makes that function unusable on Windows
8531			if ((DIRECTORY_SEPARATOR == '/') || version_compare(PHP_VERSION, '5.3.4', '>='))
8532			{
8533				$output = openssl_random_pseudo_bytes($bytes, $crypto_strong);
8534				if ($crypto_strong == false)
8535				{
8536					$output = null;
8537				}
8538			}
8539		}
8540	}
8541	else
8542	{
8543		return $output;
8544	}
8545
8546	if(strlen($output) < $bytes)
8547	{
8548		if(class_exists('COM'))
8549		{
8550			try
8551			{
8552				$CAPI_Util = new COM('CAPICOM.Utilities.1');
8553				if(is_callable(array($CAPI_Util, 'GetRandom')))
8554				{
8555					$output = $CAPI_Util->GetRandom($bytes, 0);
8556				}
8557			} catch (Exception $e) {
8558			}
8559		}
8560	}
8561	else
8562	{
8563		return $output;
8564	}
8565
8566	if(strlen($output) < $bytes)
8567	{
8568		// Close to what PHP basically uses internally to seed, but not quite.
8569		$unique_state = microtime().@getmypid();
8570
8571		$rounds = ceil($bytes / 16);
8572
8573		for($i = 0; $i < $rounds; $i++)
8574		{
8575			$unique_state = md5(microtime().$unique_state);
8576			$output .= md5($unique_state);
8577		}
8578
8579		$output = substr($output, 0, ($bytes * 2));
8580
8581		$output = pack('H*', $output);
8582
8583		return $output;
8584	}
8585	else
8586	{
8587		return $output;
8588	}
8589}
8590
8591/**
8592 * Returns a securely generated seed integer
8593 *
8594 * @return int An integer equivalent of a secure hexadecimal seed
8595 */
8596function secure_seed_rng()
8597{
8598	$bytes = PHP_INT_SIZE;
8599
8600	do
8601	{
8602
8603		$output = secure_binary_seed_rng($bytes);
8604
8605		// convert binary data to a decimal number
8606		if ($bytes == 4)
8607		{
8608			$elements = unpack('i', $output);
8609			$output = abs($elements[1]);
8610		}
8611		else
8612		{
8613			$elements = unpack('N2', $output);
8614			$output = abs($elements[1] << 32 | $elements[2]);
8615		}
8616
8617	} while($output > PHP_INT_MAX);
8618
8619	return $output;
8620}
8621
8622/**
8623 * Generates a cryptographically secure random number.
8624 *
8625 * @param int $min Optional lowest value to be returned (default: 0)
8626 * @param int $max Optional highest value to be returned (default: PHP_INT_MAX)
8627 */
8628function my_rand($min=0, $max=PHP_INT_MAX)
8629{
8630	// backward compatibility
8631	if($min === null || $max === null || $max < $min)
8632	{
8633		$min = 0;
8634		$max = PHP_INT_MAX;
8635	}
8636
8637	if(version_compare(PHP_VERSION, '7.0', '>='))
8638	{
8639		try
8640		{
8641			$result = random_int($min, $max);
8642		} catch (Exception $e) {
8643		}
8644
8645		if(isset($result))
8646		{
8647			return $result;
8648		}
8649	}
8650
8651	$seed = secure_seed_rng();
8652
8653	$distance = $max - $min;
8654	return $min + floor($distance * ($seed / PHP_INT_MAX) );
8655}
8656
8657/**
8658 * More robust version of PHP's trim() function. It includes a list of UTF-8 blank characters
8659 * from http://kb.mozillazine.org/Network.IDN.blacklist_chars
8660 *
8661 * @param string $string The string to trim from
8662 * @param string $charlist Optional. The stripped characters can also be specified using the charlist parameter
8663 * @return string The trimmed string
8664 */
8665function trim_blank_chrs($string, $charlist="")
8666{
8667	$hex_chrs = array(
8668		0x09 => 1, // \x{0009}
8669		0x0A => 1, // \x{000A}
8670		0x0B => 1, // \x{000B}
8671		0x0D => 1, // \x{000D}
8672		0x20 => 1, // \x{0020}
8673		0xC2 => array(0x81 => 1, 0x8D => 1, 0x90 => 1, 0x9D => 1, 0xA0 => 1, 0xAD => 1), // \x{0081}, \x{008D}, \x{0090}, \x{009D}, \x{00A0}, \x{00AD}
8674		0xCC => array(0xB7 => 1, 0xB8 => 1), // \x{0337}, \x{0338}
8675		0xE1 => array(0x85 => array(0x9F => 1, 0xA0 => 1), 0x9A => array(0x80 => 1), 0xA0 => array(0x8E => 1)), // \x{115F}, \x{1160}, \x{1680}, \x{180E}
8676		0xE2 => array(0x80 => array(0x80 => 1, 0x81 => 1, 0x82 => 1, 0x83 => 1, 0x84 => 1, 0x85 => 1, 0x86 => 1, 0x87 => 1, 0x88 => 1, 0x89 => 1, 0x8A => 1, 0x8B => 1, 0x8C => 1, 0x8D => 1, 0x8E => 1, 0x8F => 1, // \x{2000} - \x{200F}
8677			0xA8 => 1, 0xA9 => 1, 0xAA => 1, 0xAB => 1, 0xAC => 1, 0xAD => 1, 0xAE => 1, 0xAF => 1), // \x{2028} - \x{202F}
8678			0x81 => array(0x9F => 1)), // \x{205F}
8679		0xE3 => array(0x80 => array(0x80 => 1), // \x{3000}
8680			0x85 => array(0xA4 => 1)), // \x{3164}
8681		0xEF => array(0xBB => array(0xBF => 1), // \x{FEFF}
8682			0xBE => array(0xA0 => 1), // \x{FFA0}
8683			0xBF => array(0xB9 => 1, 0xBA => 1, 0xBB => 1)), // \x{FFF9} - \x{FFFB}
8684	);
8685
8686	$hex_chrs_rev = array(
8687		0x09 => 1, // \x{0009}
8688		0x0A => 1, // \x{000A}
8689		0x0B => 1, // \x{000B}
8690		0x0D => 1, // \x{000D}
8691		0x20 => 1, // \x{0020}
8692		0x81 => array(0xC2 => 1, 0x80 => array(0xE2 => 1)), // \x{0081}, \x{2001}
8693		0x8D => array(0xC2 => 1, 0x80 => array(0xE2 => 1)), // \x{008D}, \x{200D}
8694		0x90 => array(0xC2 => 1), // \x{0090}
8695		0x9D => array(0xC2 => 1), // \x{009D}
8696		0xA0 => array(0xC2 => 1, 0x85 => array(0xE1 => 1), 0x81 => array(0xE2 => 1), 0xBE => array(0xEF => 1)), // \x{00A0}, \x{1160}, \x{2060}, \x{FFA0}
8697		0xAD => array(0xC2 => 1, 0x80 => array(0xE2 => 1)), // \x{00AD}, \x{202D}
8698		0xB8 => array(0xCC => 1), // \x{0338}
8699		0xB7 => array(0xCC => 1), // \x{0337}
8700		0x9F => array(0x85 => array(0xE1 => 1), 0x81 => array(0xE2 => 1)), // \x{115F}, \x{205F}
8701		0x80 => array(0x9A => array(0xE1 => 1), 0x80 => array(0xE2 => 1, 0xE3 => 1)), // \x{1680}, \x{2000}, \x{3000}
8702		0x8E => array(0xA0 => array(0xE1 => 1), 0x80 => array(0xE2 => 1)), // \x{180E}, \x{200E}
8703		0x82 => array(0x80 => array(0xE2 => 1)), // \x{2002}
8704		0x83 => array(0x80 => array(0xE2 => 1)), // \x{2003}
8705		0x84 => array(0x80 => array(0xE2 => 1)), // \x{2004}
8706		0x85 => array(0x80 => array(0xE2 => 1)), // \x{2005}
8707		0x86 => array(0x80 => array(0xE2 => 1)), // \x{2006}
8708		0x87 => array(0x80 => array(0xE2 => 1)), // \x{2007}
8709		0x88 => array(0x80 => array(0xE2 => 1)), // \x{2008}
8710		0x89 => array(0x80 => array(0xE2 => 1)), // \x{2009}
8711		0x8A => array(0x80 => array(0xE2 => 1)), // \x{200A}
8712		0x8B => array(0x80 => array(0xE2 => 1)), // \x{200B}
8713		0x8C => array(0x80 => array(0xE2 => 1)), // \x{200C}
8714		0x8F => array(0x80 => array(0xE2 => 1)), // \x{200F}
8715		0xA8 => array(0x80 => array(0xE2 => 1)), // \x{2028}
8716		0xA9 => array(0x80 => array(0xE2 => 1)), // \x{2029}
8717		0xAA => array(0x80 => array(0xE2 => 1)), // \x{202A}
8718		0xAB => array(0x80 => array(0xE2 => 1)), // \x{202B}
8719		0xAC => array(0x80 => array(0xE2 => 1)), // \x{202C}
8720		0xAE => array(0x80 => array(0xE2 => 1)), // \x{202E}
8721		0xAF => array(0x80 => array(0xE2 => 1)), // \x{202F}
8722		0xA4 => array(0x85 => array(0xE3 => 1)), // \x{3164}
8723		0xBF => array(0xBB => array(0xEF => 1)), // \x{FEFF}
8724		0xB9 => array(0xBF => array(0xEF => 1)), // \x{FFF9}
8725		0xBA => array(0xBF => array(0xEF => 1)), // \x{FFFA}
8726		0xBB => array(0xBF => array(0xEF => 1)), // \x{FFFB}
8727	);
8728
8729	// Start from the beginning and work our way in
8730	$i = 0;
8731	do
8732	{
8733		// Check to see if we have matched a first character in our utf-8 array
8734		$offset = match_sequence($string, $hex_chrs);
8735		if(!$offset)
8736		{
8737			// If not, then we must have a "good" character and we don't need to do anymore processing
8738			break;
8739		}
8740		$string = substr($string, $offset);
8741	}
8742	while(++$i);
8743
8744	// Start from the end and work our way in
8745	$string = strrev($string);
8746	$i = 0;
8747	do
8748	{
8749		// Check to see if we have matched a first character in our utf-8 array
8750		$offset = match_sequence($string, $hex_chrs_rev);
8751		if(!$offset)
8752		{
8753			// If not, then we must have a "good" character and we don't need to do anymore processing
8754			break;
8755		}
8756		$string = substr($string, $offset);
8757	}
8758	while(++$i);
8759	$string = strrev($string);
8760
8761	if($charlist)
8762	{
8763		$string = trim($string, $charlist);
8764	}
8765	else
8766	{
8767		$string = trim($string);
8768	}
8769
8770	return $string;
8771}
8772
8773/**
8774 * Match a sequence
8775 *
8776 * @param string $string The string to match from
8777 * @param array $array The array to match from
8778 * @param int $i Number in the string
8779 * @param int $n Number of matches
8780 * @return int The number matched
8781 */
8782function match_sequence($string, $array, $i=0, $n=0)
8783{
8784	if($string === "")
8785	{
8786		return 0;
8787	}
8788
8789	$ord = ord($string[$i]);
8790	if(array_key_exists($ord, $array))
8791	{
8792		$level = $array[$ord];
8793		++$n;
8794		if(is_array($level))
8795		{
8796			++$i;
8797			return match_sequence($string, $level, $i, $n);
8798		}
8799		return $n;
8800	}
8801
8802	return 0;
8803}
8804
8805/**
8806 * Obtain the version of GD installed.
8807 *
8808 * @return float|null Version of GD
8809 */
8810function gd_version()
8811{
8812	static $gd_version;
8813
8814	if($gd_version)
8815	{
8816		return $gd_version;
8817	}
8818
8819	if(!extension_loaded('gd'))
8820	{
8821		return null;
8822	}
8823
8824	if(function_exists("gd_info"))
8825	{
8826		$gd_info = gd_info();
8827		preg_match('/\d/', $gd_info['GD Version'], $gd);
8828		$gd_version = $gd[0];
8829	}
8830	else
8831	{
8832		ob_start();
8833		phpinfo(8);
8834		$info = ob_get_contents();
8835		ob_end_clean();
8836		$info = stristr($info, 'gd version');
8837		preg_match('/\d/', $info, $gd);
8838		$gd_version = $gd[0];
8839	}
8840
8841	return $gd_version;
8842}
8843
8844/*
8845 * Validates an UTF-8 string.
8846 *
8847 * @param string $input The string to be checked
8848 * @param boolean $allow_mb4 Allow 4 byte UTF-8 characters?
8849 * @param boolean $return Return the cleaned string?
8850 * @return string|boolean Cleaned string or boolean
8851 */
8852function validate_utf8_string($input, $allow_mb4=true, $return=true)
8853{
8854	// Valid UTF-8 sequence?
8855	if(!preg_match('##u', $input))
8856	{
8857		$string = '';
8858		$len = strlen($input);
8859		for($i = 0; $i < $len; $i++)
8860		{
8861			$c = ord($input[$i]);
8862			if($c > 128)
8863			{
8864				if($c > 247 || $c <= 191)
8865				{
8866					if($return)
8867					{
8868						$string .= '?';
8869						continue;
8870					}
8871					else
8872					{
8873						return false;
8874					}
8875				}
8876				elseif($c > 239)
8877				{
8878					$bytes = 4;
8879				}
8880				elseif($c > 223)
8881				{
8882					$bytes = 3;
8883				}
8884				elseif($c > 191)
8885				{
8886					$bytes = 2;
8887				}
8888				if(($i + $bytes) > $len)
8889				{
8890					if($return)
8891					{
8892						$string .= '?';
8893						break;
8894					}
8895					else
8896					{
8897						return false;
8898					}
8899				}
8900				$valid = true;
8901				$multibytes = $input[$i];
8902				while($bytes > 1)
8903				{
8904					$i++;
8905					$b = ord($input[$i]);
8906					if($b < 128 || $b > 191)
8907					{
8908						if($return)
8909						{
8910							$valid = false;
8911							$string .= '?';
8912							break;
8913						}
8914						else
8915						{
8916							return false;
8917						}
8918					}
8919					else
8920					{
8921						$multibytes .= $input[$i];
8922					}
8923					$bytes--;
8924				}
8925				if($valid)
8926				{
8927					$string .= $multibytes;
8928				}
8929			}
8930			else
8931			{
8932				$string .= $input[$i];
8933			}
8934		}
8935		$input = $string;
8936	}
8937	if($return)
8938	{
8939		if($allow_mb4)
8940		{
8941			return $input;
8942		}
8943		else
8944		{
8945			return preg_replace("#[^\\x00-\\x7F][\\x80-\\xBF]{3,}#", '?', $input);
8946		}
8947	}
8948	else
8949	{
8950		if($allow_mb4)
8951		{
8952			return true;
8953		}
8954		else
8955		{
8956			return !preg_match("#[^\\x00-\\x7F][\\x80-\\xBF]{3,}#", $input);
8957		}
8958	}
8959}
8960
8961/**
8962 * Send a Private Message to a user.
8963 *
8964 * @param array $pm Array containing: 'subject', 'message', 'touid' and 'receivepms' (the latter should reflect the value found in the users table: receivepms and receivefrombuddy)
8965 * @param int $fromid Sender UID (0 if you want to use $mybb->user['uid'] or -1 to use MyBB Engine)
8966 * @param bool $admin_override Whether or not do override user defined options for receiving PMs
8967 * @return bool True if PM sent
8968 */
8969function send_pm($pm, $fromid = 0, $admin_override=false)
8970{
8971	global $lang, $mybb, $db, $session;
8972
8973	if($mybb->settings['enablepms'] == 0)
8974	{
8975		return false;
8976	}
8977
8978	if(!is_array($pm))
8979	{
8980		return false;
8981	}
8982
8983	if(isset($pm['language']))
8984	{
8985		if($pm['language'] != $mybb->user['language'] && $lang->language_exists($pm['language']))
8986		{
8987			// Load user language
8988			$lang->set_language($pm['language']);
8989			$lang->load($pm['language_file']);
8990
8991			$revert = true;
8992		}
8993
8994		foreach(array('subject', 'message') as $key)
8995		{
8996			if(is_array($pm[$key]))
8997			{
8998				$lang_string = $lang->{$pm[$key][0]};
8999				$num_args = count($pm[$key]);
9000
9001				for($i = 1; $i < $num_args; $i++)
9002				{
9003					$lang_string = str_replace('{'.$i.'}', $pm[$key][$i], $lang_string);
9004				}
9005			}
9006			else
9007			{
9008				$lang_string = $lang->{$pm[$key]};
9009			}
9010
9011			$pm[$key] = $lang_string;
9012		}
9013
9014		if(isset($revert))
9015		{
9016			// Revert language
9017			$lang->set_language($mybb->user['language']);
9018			$lang->load($pm['language_file']);
9019		}
9020	}
9021
9022	if(!$pm['subject'] ||!$pm['message'] || !$pm['touid'] || (!$pm['receivepms'] && !$admin_override))
9023	{
9024		return false;
9025	}
9026
9027	require_once MYBB_ROOT."inc/datahandlers/pm.php";
9028
9029	$pmhandler = new PMDataHandler();
9030
9031	$subject = $pm['subject'];
9032	$message = $pm['message'];
9033	$toid = $pm['touid'];
9034
9035	// Our recipients
9036	if(is_array($toid))
9037	{
9038		$recipients_to = $toid;
9039	}
9040	else
9041	{
9042		$recipients_to = array($toid);
9043	}
9044
9045	$recipients_bcc = array();
9046
9047	// Determine user ID
9048	if((int)$fromid == 0)
9049	{
9050		$fromid = (int)$mybb->user['uid'];
9051	}
9052	elseif((int)$fromid < 0)
9053	{
9054		$fromid = 0;
9055	}
9056
9057	// Build our final PM array
9058	$pm = array(
9059		"subject" => $subject,
9060		"message" => $message,
9061		"icon" => -1,
9062		"fromid" => $fromid,
9063		"toid" => $recipients_to,
9064		"bccid" => $recipients_bcc,
9065		"do" => '',
9066		"pmid" => ''
9067	);
9068
9069	if(isset($session))
9070	{
9071		$pm['ipaddress'] = $session->packedip;
9072	}
9073
9074	$pm['options'] = array(
9075		"disablesmilies" => 0,
9076		"savecopy" => 0,
9077		"readreceipt" => 0
9078	);
9079
9080	$pm['saveasdraft'] = 0;
9081
9082	// Admin override
9083	$pmhandler->admin_override = (int)$admin_override;
9084
9085	$pmhandler->set_data($pm);
9086
9087	if($pmhandler->validate_pm())
9088	{
9089		$pmhandler->insert_pm();
9090		return true;
9091	}
9092
9093	return false;
9094}
9095
9096/**
9097 * Log a user spam block from StopForumSpam (or other spam service providers...)
9098 *
9099 * @param string $username The username that the user was using.
9100 * @param string $email    The email address the user was using.
9101 * @param string $ip_address The IP addres of the user.
9102 * @param array  $data     An array of extra data to go with the block (eg: confidence rating).
9103 * @return bool Whether the action was logged successfully.
9104 */
9105function log_spam_block($username = '', $email = '', $ip_address = '', $data = array())
9106{
9107	global $db, $session;
9108
9109	if(!is_array($data))
9110	{
9111		$data = array($data);
9112	}
9113
9114	if(!$ip_address)
9115	{
9116		$ip_address = get_ip();
9117	}
9118
9119	$ip_address = my_inet_pton($ip_address);
9120
9121	$insert_array = array(
9122		'username'  => $db->escape_string($username),
9123		'email'     => $db->escape_string($email),
9124		'ipaddress' => $db->escape_binary($ip_address),
9125		'dateline'  => (int)TIME_NOW,
9126		'data'      => $db->escape_string(@my_serialize($data)),
9127	);
9128
9129	return (bool)$db->insert_query('spamlog', $insert_array);
9130}
9131
9132/**
9133 * Copy a file to the CDN.
9134 *
9135 * @param string $file_path     The path to the file to upload to the CDN.
9136 *
9137 * @param string $uploaded_path The path the file was uploaded to, reference parameter for when this may be needed.
9138 *
9139 * @return bool Whether the file was copied successfully.
9140 */
9141function copy_file_to_cdn($file_path = '', &$uploaded_path = null)
9142{
9143	global $mybb, $plugins;
9144
9145	$success = false;
9146
9147	$file_path = (string)$file_path;
9148
9149	$real_file_path = realpath($file_path);
9150
9151	$file_dir_path = dirname($real_file_path);
9152	$file_dir_path = str_replace(MYBB_ROOT, '', $file_dir_path);
9153	$file_dir_path = ltrim($file_dir_path, './\\');
9154
9155	$file_name = basename($real_file_path);
9156
9157	if(file_exists($file_path))
9158	{
9159
9160		if(is_object($plugins))
9161		{
9162			$hook_args = array(
9163				'file_path' => &$file_path,
9164				'real_file_path' => &$real_file_path,
9165				'file_name' => &$file_name,
9166				'file_dir_path'	=> &$file_dir_path
9167			);
9168			$plugins->run_hooks('copy_file_to_cdn_start', $hook_args);
9169		}
9170
9171		if(!empty($mybb->settings['usecdn']) && !empty($mybb->settings['cdnpath']))
9172		{
9173			$cdn_path = rtrim($mybb->settings['cdnpath'], '/\\');
9174
9175			if(substr($file_dir_path, 0, my_strlen(MYBB_ROOT)) == MYBB_ROOT)
9176			{
9177				$file_dir_path = str_replace(MYBB_ROOT, '', $file_dir_path);
9178			}
9179
9180			$cdn_upload_path = $cdn_path . DIRECTORY_SEPARATOR . $file_dir_path;
9181
9182			if(!($dir_exists = is_dir($cdn_upload_path)))
9183			{
9184				$dir_exists = @mkdir($cdn_upload_path, 0777, true);
9185			}
9186
9187			if($dir_exists)
9188			{
9189				if(($cdn_upload_path = realpath($cdn_upload_path)) !== false)
9190				{
9191					$success = @copy($file_path, $cdn_upload_path.DIRECTORY_SEPARATOR.$file_name);
9192
9193					if($success)
9194					{
9195						$uploaded_path = $cdn_upload_path;
9196					}
9197				}
9198			}
9199		}
9200
9201		if(is_object($plugins))
9202		{
9203			$hook_args = array(
9204				'file_path' => &$file_path,
9205				'real_file_path' => &$real_file_path,
9206				'file_name' => &$file_name,
9207				'uploaded_path' => &$uploaded_path,
9208				'success' => &$success,
9209			);
9210
9211			$plugins->run_hooks('copy_file_to_cdn_end', $hook_args);
9212		}
9213	}
9214
9215	return $success;
9216}
9217
9218/**
9219 * Validate an url
9220 *
9221 * @param string $url The url to validate.
9222 * @param bool $relative_path Whether or not the url could be a relative path.
9223 * @param bool $allow_local Whether or not the url could be pointing to local networks.
9224 *
9225 * @return bool Whether this is a valid url.
9226 */
9227function my_validate_url($url, $relative_path=false, $allow_local=false)
9228{
9229	if($allow_local)
9230	{
9231		$regex = '_^(?:(?:https?|ftp)://)(?:\S+(?::\S*)?@)?(?:(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:localhost|(?:(?:[a-z\x{00a1}-\x{ffff}0-9]-*)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.(?:[a-z\x{00a1}-\x{ffff}0-9]-*)*[a-z\x{00a1}-\x{ffff}0-9]+)*(?:\.(?:[a-z\x{00a1}-\x{ffff}]{2,}))\.?))(?::\d{2,5})?(?:[/?#]\S*)?$_iuS';
9232	}
9233	else
9234	{
9235		$regex = '_^(?:(?:https?|ftp)://)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\x{00a1}-\x{ffff}0-9]-*)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.(?:[a-z\x{00a1}-\x{ffff}0-9]-*)*[a-z\x{00a1}-\x{ffff}0-9]+)*(?:\.(?:[a-z\x{00a1}-\x{ffff}]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?$_iuS';
9236	}
9237
9238	if($relative_path && my_substr($url, 0, 1) == '/' || preg_match($regex, $url))
9239	{
9240		return true;
9241	}
9242	return false;
9243}
9244
9245/**
9246 * Strip html tags from string, also removes <script> and <style> contents.
9247 *
9248 * @deprecated
9249 * @param  string $string         String to stripe
9250 * @param  string $allowable_tags Allowed html tags
9251 *
9252 * @return string                 Striped string
9253 */
9254function my_strip_tags($string, $allowable_tags = '')
9255{
9256	$pattern = array(
9257		'@(&lt;)style[^(&gt;)]*?(&gt;).*?(&lt;)/style(&gt;)@siu',
9258		'@(&lt;)script[^(&gt;)]*?.*?(&lt;)/script(&gt;)@siu',
9259		'@<style[^>]*?>.*?</style>@siu',
9260		'@<script[^>]*?.*?</script>@siu',
9261	);
9262	$string = preg_replace($pattern, '', $string);
9263	return strip_tags($string, $allowable_tags);
9264}
9265
9266/**
9267 * Escapes a RFC 4180-compliant CSV string.
9268 * Based on https://github.com/Automattic/camptix/blob/f80725094440bf09861383b8f11e96c177c45789/camptix.php#L2867
9269 *
9270 * @param string $string The string to be escaped
9271 * @param boolean $escape_active_content Whether or not to escape active content trigger characters
9272 * @return string The escaped string
9273 */
9274function my_escape_csv($string, $escape_active_content=true)
9275{
9276	if($escape_active_content)
9277	{
9278		$active_content_triggers = array('=', '+', '-', '@');
9279		$delimiters = array(',', ';', ':', '|', '^', "\n", "\t", " ");
9280
9281		$first_character = mb_substr($string, 0, 1);
9282
9283		if(
9284			in_array($first_character, $active_content_triggers, true) ||
9285			in_array($first_character, $delimiters, true)
9286		)
9287		{
9288			$string = "'".$string;
9289		}
9290
9291		foreach($delimiters as $delimiter)
9292		{
9293			foreach($active_content_triggers as $trigger)
9294			{
9295				$string = str_replace($delimiter.$trigger, $delimiter."'".$trigger, $string);
9296			}
9297		}
9298	}
9299
9300	$string = str_replace('"', '""', $string);
9301
9302	return $string;
9303}
9304
9305// Fallback function for 'array_column', PHP < 5.5.0 compatibility
9306if(!function_exists('array_column'))
9307{
9308	function array_column($input, $column_key)
9309	{
9310		$values = array();
9311 		if(!is_array($input))
9312		{
9313			$input = array($input);
9314		}
9315 		foreach($input as $val)
9316		{
9317			if(is_array($val) && isset($val[$column_key]))
9318			{
9319				$values[] = $val[$column_key];
9320			}
9321			elseif(is_object($val) && isset($val->$column_key))
9322			{
9323				$values[] = $val->$column_key;
9324			}
9325		}
9326 		return $values;
9327	}
9328}
9329
9330/**
9331 * Performs a timing attack safe string comparison.
9332 *
9333 * @param string $known_string The first string to be compared.
9334 * @param string $user_string The second, user-supplied string to be compared.
9335 * @return bool Result of the comparison.
9336 */
9337function my_hash_equals($known_string, $user_string)
9338{
9339	if(version_compare(PHP_VERSION, '5.6.0', '>='))
9340	{
9341		return hash_equals($known_string, $user_string);
9342	}
9343	else
9344	{
9345		$known_string_length = my_strlen($known_string);
9346		$user_string_length = my_strlen($user_string);
9347
9348		if($user_string_length != $known_string_length)
9349		{
9350			return false;
9351		}
9352
9353		$result = 0;
9354
9355		for($i = 0; $i < $known_string_length; $i++)
9356		{
9357			$result |= ord($known_string[$i]) ^ ord($user_string[$i]);
9358		}
9359
9360		return $result === 0;
9361	}
9362}
9363
9364/**
9365 * Retrieves all referrals for a specified user
9366 *
9367 * @param int uid
9368 * @param int start position
9369 * @param int total entries
9370 * @param bool false (default) only return display info, true for all info
9371 * @return array
9372 */
9373function get_user_referrals($uid, $start=0, $limit=0, $full=false)
9374{
9375	global $db;
9376
9377	$referrals = $query_options = array();
9378	$uid = (int) $uid;
9379
9380	if($uid === 0)
9381	{
9382		return $referrals;
9383	}
9384
9385	if($start && $limit)
9386	{
9387		$query_options['limit_start'] = $start;
9388	}
9389
9390	if($limit)
9391	{
9392		$query_options['limit'] = $limit;
9393	}
9394
9395	$fields = 'uid, username, usergroup, displaygroup, regdate';
9396	if($full === true)
9397	{
9398		$fields = '*';
9399	}
9400
9401	$query = $db->simple_select('users', $fields, "referrer='{$uid}'", $query_options);
9402
9403	while($referral = $db->fetch_array($query))
9404	{
9405		$referrals[] = $referral;
9406	}
9407
9408	return $referrals;
9409}
9410
9411/**
9412 * Initialize the parser and store the XML data to be parsed.
9413 *
9414 * @param string $data
9415 * @return MyBBXMLParser The constructed XML parser.
9416 */
9417function create_xml_parser($data)
9418{
9419	if(version_compare(PHP_VERSION, '8.0', '>='))
9420	{
9421		require_once MYBB_ROOT."inc/class_xmlparser.php";
9422
9423		return new MyBBXMLParser($data);
9424	}
9425	else
9426	{
9427		require_once MYBB_ROOT."inc/class_xml.php";
9428
9429		return new XMLParser($data);
9430	}
9431}
9432
9433/**
9434 * Make a filesystem path absolute.
9435 *
9436 * Returns as-is paths which are already absolute.
9437 *
9438 * @param string $path The input path. Can be either absolute or relative.
9439 * @param string $base The absolute base to which to append $path if $path is
9440 *                     relative. Must end in DIRECTORY_SEPARATOR or a forward
9441 *                     slash.
9442 * @return string An absolute filesystem path corresponding to the input path.
9443 */
9444function mk_path_abs($path, $base = MYBB_ROOT)
9445{
9446	$iswin = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';
9447	$char1 = my_substr($path, 0, 1);
9448	if($char1 != '/' && !($iswin && ($char1 == '\\' || preg_match('(^[a-zA-Z]:\\\\)', $path))))
9449	{
9450		$path = $base.$path;
9451	}
9452
9453	return $path;
9454}
9455