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