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