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