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