[ Index ] |
PHP Cross Reference of MyBB 1.8.38 |
[Summary view] [Print] [Text view]
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 */ 16 function 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 */ 109 function 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 */ 141 function run_shutdown() 142 { 143 global $config, $db, $cache, $plugins, $error_handler, $shutdown_functions, $shutdown_queries, $done_shutdown, $mybb; 144 145 if($done_shutdown == true || !$config || (isset($error_handler) && $error_handler->has_errors)) 146 { 147 return; 148 } 149 150 if(empty($shutdown_queries) && empty($shutdown_functions)) 151 { 152 // Nothing to do 153 return; 154 } 155 156 // Missing the core? Build 157 if(!is_object($mybb)) 158 { 159 require_once MYBB_ROOT."inc/class_core.php"; 160 $mybb = new MyBB; 161 162 // Load the settings 163 require MYBB_ROOT."inc/settings.php"; 164 $mybb->settings = &$settings; 165 } 166 167 // If our DB has been deconstructed already (bad PHP 5.2.0), reconstruct 168 if(!is_object($db)) 169 { 170 if(!isset($config) || empty($config['database']['type'])) 171 { 172 require MYBB_ROOT."inc/config.php"; 173 } 174 175 if(isset($config)) 176 { 177 // Load DB interface 178 require_once MYBB_ROOT."inc/db_base.php"; 179 require_once MYBB_ROOT . 'inc/AbstractPdoDbDriver.php'; 180 181 require_once MYBB_ROOT."inc/db_".$config['database']['type'].".php"; 182 switch($config['database']['type']) 183 { 184 case "sqlite": 185 $db = new DB_SQLite; 186 break; 187 case "pgsql": 188 $db = new DB_PgSQL; 189 break; 190 case "pgsql_pdo": 191 $db = new PostgresPdoDbDriver(); 192 break; 193 case "mysqli": 194 $db = new DB_MySQLi; 195 break; 196 case "mysql_pdo": 197 $db = new MysqlPdoDbDriver(); 198 break; 199 default: 200 $db = new DB_MySQL; 201 } 202 203 $db->connect($config['database']); 204 if(!defined("TABLE_PREFIX")) 205 { 206 define("TABLE_PREFIX", $config['database']['table_prefix']); 207 } 208 $db->set_table_prefix(TABLE_PREFIX); 209 } 210 } 211 212 // Cache object deconstructed? reconstruct 213 if(!is_object($cache)) 214 { 215 require_once MYBB_ROOT."inc/class_datacache.php"; 216 $cache = new datacache; 217 $cache->cache(); 218 } 219 220 // And finally.. plugins 221 if(!is_object($plugins) && !defined("NO_PLUGINS") && !($mybb->settings['no_plugins'] == 1)) 222 { 223 require_once MYBB_ROOT."inc/class_plugins.php"; 224 $plugins = new pluginSystem; 225 $plugins->load(); 226 } 227 228 // We have some shutdown queries needing to be run 229 if(is_array($shutdown_queries)) 230 { 231 // Loop through and run them all 232 foreach($shutdown_queries as $query) 233 { 234 $db->write_query($query); 235 } 236 } 237 238 // Run any shutdown functions if we have them 239 if(is_array($shutdown_functions)) 240 { 241 foreach($shutdown_functions as $function) 242 { 243 call_user_func_array($function['function'], $function['arguments']); 244 } 245 } 246 247 $done_shutdown = true; 248 } 249 250 /** 251 * Sends a specified amount of messages from the mail queue 252 * 253 * @param int $count The number of messages to send (Defaults to 10) 254 */ 255 function send_mail_queue($count=10) 256 { 257 global $db, $cache, $plugins; 258 259 $plugins->run_hooks("send_mail_queue_start"); 260 261 // Check to see if the mail queue has messages needing to be sent 262 $mailcache = $cache->read("mailqueue"); 263 if($mailcache !== false && $mailcache['queue_size'] > 0 && ($mailcache['locked'] == 0 || $mailcache['locked'] < TIME_NOW-300)) 264 { 265 // Lock the queue so no other messages can be sent whilst these are (for popular boards) 266 $cache->update_mailqueue(0, TIME_NOW); 267 268 // Fetch emails for this page view - and send them 269 $query = $db->simple_select("mailqueue", "*", "", array("order_by" => "mid", "order_dir" => "asc", "limit_start" => 0, "limit" => $count)); 270 271 while($email = $db->fetch_array($query)) 272 { 273 // Delete the message from the queue 274 $db->delete_query("mailqueue", "mid='{$email['mid']}'"); 275 276 if($db->affected_rows() == 1) 277 { 278 my_mail($email['mailto'], $email['subject'], $email['message'], $email['mailfrom'], "", $email['headers'], true); 279 } 280 } 281 // Update the mailqueue cache and remove the lock 282 $cache->update_mailqueue(TIME_NOW, 0); 283 } 284 285 $plugins->run_hooks("send_mail_queue_end"); 286 } 287 288 /** 289 * Parses the contents of a page before outputting it. 290 * 291 * @param string $contents The contents of the page. 292 * @return string The parsed page. 293 */ 294 function parse_page($contents) 295 { 296 global $lang, $theme, $mybb, $htmldoctype, $archive_url, $error_handler; 297 298 $contents = str_replace('<navigation>', build_breadcrumb(), $contents); 299 $contents = str_replace('<archive_url>', $archive_url, $contents); 300 301 if($htmldoctype) 302 { 303 $contents = $htmldoctype.$contents; 304 } 305 else 306 { 307 $contents = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n".$contents; 308 } 309 310 $contents = str_replace("<html", "<html xmlns=\"http://www.w3.org/1999/xhtml\"", $contents); 311 312 if($lang->settings['rtl'] == 1) 313 { 314 $contents = str_replace("<html", "<html dir=\"rtl\"", $contents); 315 } 316 317 if($lang->settings['htmllang']) 318 { 319 $contents = str_replace("<html", "<html xml:lang=\"".$lang->settings['htmllang']."\" lang=\"".$lang->settings['htmllang']."\"", $contents); 320 } 321 322 if($error_handler->warnings) 323 { 324 $contents = str_replace("<body>", "<body>\n".$error_handler->show_warnings(), $contents); 325 } 326 327 return $contents; 328 } 329 330 /** 331 * Turn a unix timestamp in to a "friendly" date/time format for the user. 332 * 333 * @param string $format A date format (either relative, normal or PHP's date() structure). 334 * @param int $stamp The unix timestamp the date should be generated for. 335 * @param int|string $offset The offset in hours that should be applied to times. (timezones) Or an empty string to determine that automatically 336 * @param int $ty Whether or not to use today/yesterday formatting. 337 * @param boolean $adodb Whether or not to use the adodb time class for < 1970 or > 2038 times 338 * @return string The formatted timestamp. 339 */ 340 function my_date($format, $stamp=0, $offset="", $ty=1, $adodb=false) 341 { 342 global $mybb, $lang, $plugins; 343 344 // If the stamp isn't set, use TIME_NOW 345 if(empty($stamp)) 346 { 347 $stamp = TIME_NOW; 348 } 349 350 if(!$offset && $offset != '0') 351 { 352 if(isset($mybb->user['uid']) && $mybb->user['uid'] != 0 && array_key_exists("timezone", $mybb->user)) 353 { 354 $offset = (float)$mybb->user['timezone']; 355 $dstcorrection = $mybb->user['dst']; 356 } 357 else 358 { 359 $offset = (float)$mybb->settings['timezoneoffset']; 360 $dstcorrection = $mybb->settings['dstcorrection']; 361 } 362 363 // If DST correction is enabled, add an additional hour to the timezone. 364 if($dstcorrection == 1) 365 { 366 ++$offset; 367 if(my_substr($offset, 0, 1) != "-") 368 { 369 $offset = "+".$offset; 370 } 371 } 372 } 373 374 if($offset == "-") 375 { 376 $offset = 0; 377 } 378 379 // Using ADOdb? 380 if($adodb == true && !function_exists('adodb_date')) 381 { 382 $adodb = false; 383 } 384 385 $todaysdate = $yesterdaysdate = ''; 386 if($ty && ($format == $mybb->settings['dateformat'] || $format == 'relative' || $format == 'normal')) 387 { 388 $_stamp = TIME_NOW; 389 if($adodb == true) 390 { 391 $date = adodb_date($mybb->settings['dateformat'], $stamp + ($offset * 3600)); 392 $todaysdate = adodb_date($mybb->settings['dateformat'], $_stamp + ($offset * 3600)); 393 $yesterdaysdate = adodb_date($mybb->settings['dateformat'], ($_stamp - 86400) + ($offset * 3600)); 394 } 395 else 396 { 397 $date = gmdate($mybb->settings['dateformat'], $stamp + ($offset * 3600)); 398 $todaysdate = gmdate($mybb->settings['dateformat'], $_stamp + ($offset * 3600)); 399 $yesterdaysdate = gmdate($mybb->settings['dateformat'], ($_stamp - 86400) + ($offset * 3600)); 400 } 401 } 402 403 if($format == 'relative') 404 { 405 // Relative formats both date and time 406 $real_date = $real_time = ''; 407 if($adodb == true) 408 { 409 $real_date = adodb_date($mybb->settings['dateformat'], $stamp + ($offset * 3600)); 410 $real_time = $mybb->settings['datetimesep']; 411 $real_time .= adodb_date($mybb->settings['timeformat'], $stamp + ($offset * 3600)); 412 } 413 else 414 { 415 $real_date = gmdate($mybb->settings['dateformat'], $stamp + ($offset * 3600)); 416 $real_time = $mybb->settings['datetimesep']; 417 $real_time .= gmdate($mybb->settings['timeformat'], $stamp + ($offset * 3600)); 418 } 419 420 if($ty != 2 && abs(TIME_NOW - $stamp) < 3600) 421 { 422 $diff = TIME_NOW - $stamp; 423 $relative = array('prefix' => '', 'minute' => 0, 'plural' => $lang->rel_minutes_plural, 'suffix' => $lang->rel_ago); 424 425 if($diff < 0) 426 { 427 $diff = abs($diff); 428 $relative['suffix'] = ''; 429 $relative['prefix'] = $lang->rel_in; 430 } 431 432 $relative['minute'] = floor($diff / 60); 433 434 if($relative['minute'] <= 1) 435 { 436 $relative['minute'] = 1; 437 $relative['plural'] = $lang->rel_minutes_single; 438 } 439 440 if($diff <= 60) 441 { 442 // Less than a minute 443 $relative['prefix'] = $lang->rel_less_than; 444 } 445 446 $date = $lang->sprintf($lang->rel_time, $relative['prefix'], $relative['minute'], $relative['plural'], $relative['suffix'], $real_date, $real_time); 447 } 448 elseif($ty != 2 && abs(TIME_NOW - $stamp) < 43200) 449 { 450 $diff = TIME_NOW - $stamp; 451 $relative = array('prefix' => '', 'hour' => 0, 'plural' => $lang->rel_hours_plural, 'suffix' => $lang->rel_ago); 452 453 if($diff < 0) 454 { 455 $diff = abs($diff); 456 $relative['suffix'] = ''; 457 $relative['prefix'] = $lang->rel_in; 458 } 459 460 $relative['hour'] = floor($diff / 3600); 461 462 if($relative['hour'] <= 1) 463 { 464 $relative['hour'] = 1; 465 $relative['plural'] = $lang->rel_hours_single; 466 } 467 468 $date = $lang->sprintf($lang->rel_time, $relative['prefix'], $relative['hour'], $relative['plural'], $relative['suffix'], $real_date, $real_time); 469 } 470 else 471 { 472 if($ty) 473 { 474 if($todaysdate == $date) 475 { 476 $date = $lang->sprintf($lang->today_rel, $real_date); 477 } 478 else if($yesterdaysdate == $date) 479 { 480 $date = $lang->sprintf($lang->yesterday_rel, $real_date); 481 } 482 } 483 484 $date .= $mybb->settings['datetimesep']; 485 if($adodb == true) 486 { 487 $date .= adodb_date($mybb->settings['timeformat'], $stamp + ($offset * 3600)); 488 } 489 else 490 { 491 $date .= gmdate($mybb->settings['timeformat'], $stamp + ($offset * 3600)); 492 } 493 } 494 } 495 elseif($format == 'normal') 496 { 497 // Normal format both date and time 498 if($ty != 2) 499 { 500 if($todaysdate == $date) 501 { 502 $date = $lang->today; 503 } 504 else if($yesterdaysdate == $date) 505 { 506 $date = $lang->yesterday; 507 } 508 } 509 510 $date .= $mybb->settings['datetimesep']; 511 if($adodb == true) 512 { 513 $date .= adodb_date($mybb->settings['timeformat'], $stamp + ($offset * 3600)); 514 } 515 else 516 { 517 $date .= gmdate($mybb->settings['timeformat'], $stamp + ($offset * 3600)); 518 } 519 } 520 else 521 { 522 if($ty && $format == $mybb->settings['dateformat']) 523 { 524 if($todaysdate == $date) 525 { 526 $date = $lang->today; 527 } 528 else if($yesterdaysdate == $date) 529 { 530 $date = $lang->yesterday; 531 } 532 } 533 else 534 { 535 if($adodb == true) 536 { 537 $date = adodb_date($format, $stamp + ($offset * 3600)); 538 } 539 else 540 { 541 $date = gmdate($format, $stamp + ($offset * 3600)); 542 } 543 } 544 } 545 546 if(is_object($plugins)) 547 { 548 $date = $plugins->run_hooks("my_date", $date); 549 } 550 551 return $date; 552 } 553 554 /** 555 * Get a mail handler instance, a MyBB's built-in SMTP / PHP mail hander or one created by a plugin. 556 * @param bool $use_buitlin Whether to use MyBB's built-in mail handler. 557 * 558 * @return object A MyBB built-in mail handler or one created by plugin(s). 559 */ 560 function &get_my_mailhandler($use_buitlin = false) 561 { 562 global $mybb, $plugins; 563 static $my_mailhandler; 564 static $my_mailhandler_builtin; 565 566 if($use_buitlin) 567 { 568 // If our built-in mail handler doesn't exist, create it. 569 if(!is_object($my_mailhandler_builtin)) 570 { 571 require_once MYBB_ROOT . "inc/class_mailhandler.php"; 572 573 // Using SMTP. 574 if(isset($mybb->settings['mail_handler']) && $mybb->settings['mail_handler'] == 'smtp') 575 { 576 require_once MYBB_ROOT . "inc/mailhandlers/smtp.php"; 577 $my_mailhandler_builtin = new SmtpMail(); 578 } 579 // Using PHP mail(). 580 else 581 { 582 require_once MYBB_ROOT . "inc/mailhandlers/php.php"; 583 $my_mailhandler_builtin = new PhpMail(); 584 if(!empty($mybb->config['mail_parameters'])) 585 { 586 $my_mailhandler_builtin->additional_parameters = $mybb->config['mail_parameters']; 587 } 588 } 589 } 590 591 if(isset($plugins) && is_object($plugins)) 592 { 593 $plugins->run_hooks('my_mailhandler_builtin_after_init', $my_mailhandler_builtin); 594 } 595 596 return $my_mailhandler_builtin; 597 } 598 599 // If our mail handler doesn't exist, create it. 600 if(!is_object($my_mailhandler)) 601 { 602 require_once MYBB_ROOT . "inc/class_mailhandler.php"; 603 604 if(isset($plugins) && is_object($plugins)) 605 { 606 $plugins->run_hooks('my_mailhandler_init', $my_mailhandler); 607 } 608 609 // If no plugin has ever created the mail handler, resort to use the built-in one. 610 if(!is_object($my_mailhandler) || !($my_mailhandler instanceof MailHandler)) 611 { 612 $my_mailhandler = &get_my_mailhandler(true); 613 } 614 } 615 616 return $my_mailhandler; 617 } 618 619 /** 620 * Sends an email using PHP's mail function, formatting it appropriately. 621 * 622 * @param string $to Address the email should be addressed to. 623 * @param string $subject The subject of the email being sent. 624 * @param string $message The message being sent. 625 * @param string $from The from address of the email, if blank, the board name will be used. 626 * @param string $charset The chracter set being used to send this email. 627 * @param string $headers 628 * @param boolean $keep_alive Do we wish to keep the connection to the mail server alive to send more than one message (SMTP only) 629 * @param string $format The format of the email to be sent (text or html). text is default 630 * @param string $message_text The text message of the email if being sent in html format, for email clients that don't support html 631 * @param string $return_email The email address to return to. Defaults to admin return email address. 632 * @return bool True if the mail is sent, false otherwise. 633 */ 634 function my_mail($to, $subject, $message, $from="", $charset="", $headers="", $keep_alive=false, $format="text", $message_text="", $return_email="") 635 { 636 global $mybb, $plugins; 637 638 // Get our mail handler. 639 $mail = &get_my_mailhandler(); 640 641 // If MyBB's built-in SMTP mail handler is used, set the keep alive bit accordingly. 642 if($keep_alive == true && isset($mail->keep_alive) && isset($mybb->settings['mail_handler']) && $mybb->settings['mail_handler'] == 'smtp') 643 { 644 require_once MYBB_ROOT . "inc/class_mailhandler.php"; 645 require_once MYBB_ROOT . "inc/mailhandlers/smtp.php"; 646 if($mail instanceof MailHandler && $mail instanceof SmtpMail) 647 { 648 $mail->keep_alive = true; 649 } 650 } 651 652 // Following variables will help sequential plugins to determine how to process plugin hooks. 653 // Mark this variable true if the hooked plugin has sent the mail, otherwise don't modify it. 654 $is_mail_sent = false; 655 // Mark this variable false if the hooked plugin doesn't suggest sequential plugins to continue processing. 656 $continue_process = true; 657 658 $my_mail_parameters = array( 659 'to' => &$to, 660 'subject' => &$subject, 661 'message' => &$message, 662 'from' => &$from, 663 'charset' => &$charset, 664 'headers' => &$headers, 665 'keep_alive' => &$keep_alive, 666 'format' => &$format, 667 'message_text' => &$message_text, 668 'return_email' => &$return_email, 669 'is_mail_sent' => &$is_mail_sent, 670 'continue_process' => &$continue_process, 671 ); 672 673 if(isset($plugins) && is_object($plugins)) 674 { 675 $plugins->run_hooks('my_mail_pre_build_message', $my_mail_parameters); 676 } 677 678 // Build the mail message. 679 $mail->build_message($to, $subject, $message, $from, $charset, $headers, $format, $message_text, $return_email); 680 681 if(isset($plugins) && is_object($plugins)) 682 { 683 $plugins->run_hooks('my_mail_pre_send', $my_mail_parameters); 684 } 685 686 // Check if the hooked plugins still suggest to send the mail. 687 if($continue_process) 688 { 689 $is_mail_sent = $mail->send(); 690 } 691 692 if(isset($plugins) && is_object($plugins)) 693 { 694 $plugins->run_hooks('my_mail_post_send', $my_mail_parameters); 695 } 696 697 return $is_mail_sent; 698 } 699 700 /** 701 * Generates a code for POST requests to prevent XSS/CSRF attacks. 702 * Unique for each user or guest session and rotated every 6 hours. 703 * 704 * @param int $rotation_shift Adjustment of the rotation number to generate a past/future code 705 * @return string The generated code 706 */ 707 function generate_post_check($rotation_shift=0) 708 { 709 global $mybb, $session; 710 711 $rotation_interval = 6 * 3600; 712 $rotation = floor(TIME_NOW / $rotation_interval) + $rotation_shift; 713 714 $seed = $rotation; 715 716 if($mybb->user['uid']) 717 { 718 $seed .= $mybb->user['loginkey'].$mybb->user['salt'].$mybb->user['regdate']; 719 } 720 else 721 { 722 $seed .= $session->sid; 723 } 724 725 if(defined('IN_ADMINCP')) 726 { 727 $seed .= 'ADMINCP'; 728 } 729 730 $seed .= $mybb->settings['internal']['encryption_key']; 731 732 return md5($seed); 733 } 734 735 /** 736 * Verifies a POST check code is valid (i.e. generated using a rotation number from the past 24 hours) 737 * 738 * @param string $code The incoming POST check code 739 * @param boolean $silent Don't show an error to the user 740 * @return bool|void Result boolean if $silent is true, otherwise shows an error to the user 741 */ 742 function verify_post_check($code, $silent=false) 743 { 744 global $lang; 745 if( 746 generate_post_check() !== $code && 747 generate_post_check(-1) !== $code && 748 generate_post_check(-2) !== $code && 749 generate_post_check(-3) !== $code 750 ) 751 { 752 if($silent == true) 753 { 754 return false; 755 } 756 else 757 { 758 if(defined("IN_ADMINCP")) 759 { 760 return false; 761 } 762 else 763 { 764 error($lang->invalid_post_code); 765 } 766 } 767 } 768 else 769 { 770 return true; 771 } 772 } 773 774 /** 775 * Return a parent list for the specified forum. 776 * 777 * @param int $fid The forum id to get the parent list for. 778 * @return string The comma-separated parent list. 779 */ 780 function get_parent_list($fid) 781 { 782 global $forum_cache; 783 static $forumarraycache; 784 785 if(!empty($forumarraycache[$fid])) 786 { 787 return $forumarraycache[$fid]['parentlist']; 788 } 789 elseif(!empty($forum_cache[$fid])) 790 { 791 return $forum_cache[$fid]['parentlist']; 792 } 793 else 794 { 795 cache_forums(); 796 return $forum_cache[$fid]['parentlist']; 797 } 798 } 799 800 /** 801 * Build a parent list of a specific forum, suitable for querying 802 * 803 * @param int $fid The forum ID 804 * @param string $column The column name to add to the query 805 * @param string $joiner The joiner for each forum for querying (OR | AND | etc) 806 * @param string $parentlist The parent list of the forum - if you have it 807 * @return string The query string generated 808 */ 809 function build_parent_list($fid, $column="fid", $joiner="OR", $parentlist="") 810 { 811 if(!$parentlist) 812 { 813 $parentlist = get_parent_list($fid); 814 } 815 816 $parentsexploded = explode(",", $parentlist); 817 $builtlist = "("; 818 $sep = ''; 819 820 foreach($parentsexploded as $key => $val) 821 { 822 $builtlist .= "$sep$column='$val'"; 823 $sep = " $joiner "; 824 } 825 826 $builtlist .= ")"; 827 828 return $builtlist; 829 } 830 831 /** 832 * Load the forum cache in to memory 833 * 834 * @param boolean $force True to force a reload of the cache 835 * @return array The forum cache 836 */ 837 function cache_forums($force=false) 838 { 839 global $forum_cache, $cache; 840 841 if($force == true) 842 { 843 $forum_cache = $cache->read("forums", 1); 844 return $forum_cache; 845 } 846 847 if(!$forum_cache) 848 { 849 $forum_cache = $cache->read("forums"); 850 if(!$forum_cache) 851 { 852 $cache->update_forums(); 853 $forum_cache = $cache->read("forums", 1); 854 } 855 } 856 return $forum_cache; 857 } 858 859 /** 860 * Generate an array of all child and descendant forums for a specific forum. 861 * 862 * @param int $fid The forum ID 863 * @return Array of descendants 864 */ 865 function get_child_list($fid) 866 { 867 static $forums_by_parent; 868 869 $forums = array(); 870 if(!is_array($forums_by_parent)) 871 { 872 $forum_cache = cache_forums(); 873 foreach($forum_cache as $forum) 874 { 875 if($forum['active'] != 0) 876 { 877 $forums_by_parent[$forum['pid']][$forum['fid']] = $forum; 878 } 879 } 880 } 881 if(isset($forums_by_parent[$fid])) 882 { 883 if(!is_array($forums_by_parent[$fid])) 884 { 885 return $forums; 886 } 887 888 foreach($forums_by_parent[$fid] as $forum) 889 { 890 $forums[] = (int)$forum['fid']; 891 $children = get_child_list($forum['fid']); 892 if(is_array($children)) 893 { 894 $forums = array_merge($forums, $children); 895 } 896 } 897 } 898 return $forums; 899 } 900 901 /** 902 * Produce a friendly error message page 903 * 904 * @param string $error The error message to be shown 905 * @param string $title The title of the message shown in the title of the page and the error table 906 */ 907 function error($error="", $title="") 908 { 909 global $header, $footer, $theme, $headerinclude, $db, $templates, $lang, $mybb, $plugins; 910 911 $error = $plugins->run_hooks("error", $error); 912 if(!$error) 913 { 914 $error = $lang->unknown_error; 915 } 916 917 // AJAX error message? 918 if($mybb->get_input('ajax', MyBB::INPUT_INT)) 919 { 920 // Send our headers. 921 @header("Content-type: application/json; charset={$lang->settings['charset']}"); 922 echo json_encode(array("errors" => array($error))); 923 exit; 924 } 925 926 if(!$title) 927 { 928 $title = $mybb->settings['bbname']; 929 } 930 931 $timenow = my_date('relative', TIME_NOW); 932 reset_breadcrumb(); 933 add_breadcrumb($lang->error); 934 935 eval("\$errorpage = \"".$templates->get("error")."\";"); 936 output_page($errorpage); 937 938 exit; 939 } 940 941 /** 942 * Produce an error message for displaying inline on a page 943 * 944 * @param array $errors Array of errors to be shown 945 * @param string $title The title of the error message 946 * @param array $json_data JSON data to be encoded (we may want to send more data; e.g. newreply.php uses this for CAPTCHA) 947 * @return string The inline error HTML 948 */ 949 function inline_error($errors, $title="", $json_data=array()) 950 { 951 global $theme, $mybb, $db, $lang, $templates; 952 953 if(!$title) 954 { 955 $title = $lang->please_correct_errors; 956 } 957 958 if(!is_array($errors)) 959 { 960 $errors = array($errors); 961 } 962 963 // AJAX error message? 964 if($mybb->get_input('ajax', MyBB::INPUT_INT)) 965 { 966 // Send our headers. 967 @header("Content-type: application/json; charset={$lang->settings['charset']}"); 968 969 if(empty($json_data)) 970 { 971 echo json_encode(array("errors" => $errors)); 972 } 973 else 974 { 975 echo json_encode(array_merge(array("errors" => $errors), $json_data)); 976 } 977 exit; 978 } 979 980 $errorlist = ''; 981 982 foreach($errors as $error) 983 { 984 eval("\$errorlist .= \"".$templates->get("error_inline_item")."\";"); 985 } 986 987 eval("\$errors = \"".$templates->get("error_inline")."\";"); 988 989 return $errors; 990 } 991 992 /** 993 * Presents the user with a "no permission" page 994 */ 995 function error_no_permission() 996 { 997 global $mybb, $theme, $templates, $db, $lang, $plugins, $session; 998 999 $time = TIME_NOW; 1000 $plugins->run_hooks("no_permission"); 1001 1002 $noperm_array = array ( 1003 "nopermission" => '1', 1004 "location1" => 0, 1005 "location2" => 0 1006 ); 1007 1008 $db->update_query("sessions", $noperm_array, "sid='{$session->sid}'"); 1009 1010 if($mybb->get_input('ajax', MyBB::INPUT_INT)) 1011 { 1012 // Send our headers. 1013 header("Content-type: application/json; charset={$lang->settings['charset']}"); 1014 echo json_encode(array("errors" => array($lang->error_nopermission_user_ajax))); 1015 exit; 1016 } 1017 1018 if($mybb->user['uid']) 1019 { 1020 $lang->error_nopermission_user_username = $lang->sprintf($lang->error_nopermission_user_username, htmlspecialchars_uni($mybb->user['username'])); 1021 eval("\$errorpage = \"".$templates->get("error_nopermission_loggedin")."\";"); 1022 } 1023 else 1024 { 1025 // Redirect to where the user came from 1026 $redirect_url = $_SERVER['PHP_SELF']; 1027 if($_SERVER['QUERY_STRING']) 1028 { 1029 $redirect_url .= '?'.$_SERVER['QUERY_STRING']; 1030 } 1031 1032 $redirect_url = htmlspecialchars_uni($redirect_url); 1033 1034 switch($mybb->settings['username_method']) 1035 { 1036 case 0: 1037 $lang_username = $lang->username; 1038 break; 1039 case 1: 1040 $lang_username = $lang->username1; 1041 break; 1042 case 2: 1043 $lang_username = $lang->username2; 1044 break; 1045 default: 1046 $lang_username = $lang->username; 1047 break; 1048 } 1049 eval("\$errorpage = \"".$templates->get("error_nopermission")."\";"); 1050 } 1051 1052 error($errorpage); 1053 } 1054 1055 /** 1056 * Redirect the user to a given URL with a given message 1057 * 1058 * @param string $url The URL to redirect the user to 1059 * @param string $message The redirection message to be shown 1060 * @param string $title The title of the redirection page 1061 * @param boolean $force_redirect Force the redirect page regardless of settings 1062 */ 1063 function redirect($url, $message="", $title="", $force_redirect=false) 1064 { 1065 global $header, $footer, $mybb, $theme, $headerinclude, $templates, $lang, $plugins; 1066 1067 $redirect_args = array('url' => &$url, 'message' => &$message, 'title' => &$title); 1068 1069 $plugins->run_hooks("redirect", $redirect_args); 1070 1071 if($mybb->get_input('ajax', MyBB::INPUT_INT)) 1072 { 1073 // Send our headers. 1074 //@header("Content-type: text/html; charset={$lang->settings['charset']}"); 1075 $data = "<script type=\"text/javascript\">\n"; 1076 if($message != "") 1077 { 1078 $data .= 'alert("'.addslashes($message).'");'; 1079 } 1080 $url = str_replace("#", "&#", $url); 1081 $url = htmlspecialchars_decode($url); 1082 $url = str_replace(array("\n","\r",";"), "", $url); 1083 $data .= 'window.location = "'.addslashes($url).'";'."\n"; 1084 $data .= "</script>\n"; 1085 //exit; 1086 1087 @header("Content-type: application/json; charset={$lang->settings['charset']}"); 1088 echo json_encode(array("data" => $data)); 1089 exit; 1090 } 1091 1092 if(!$message) 1093 { 1094 $message = $lang->redirect; 1095 } 1096 1097 $time = TIME_NOW; 1098 $timenow = my_date('relative', $time); 1099 1100 if(!$title) 1101 { 1102 $title = $mybb->settings['bbname']; 1103 } 1104 1105 // Show redirects only if both ACP and UCP settings are enabled, or ACP is enabled, and user is a guest, or they are forced. 1106 if($force_redirect == true || ($mybb->settings['redirects'] == 1 && (!$mybb->user['uid'] || $mybb->user['showredirect'] == 1))) 1107 { 1108 $url = str_replace("&", "&", $url); 1109 $url = htmlspecialchars_uni($url); 1110 1111 eval("\$redirectpage = \"".$templates->get("redirect")."\";"); 1112 output_page($redirectpage); 1113 } 1114 else 1115 { 1116 $url = htmlspecialchars_decode($url); 1117 $url = str_replace(array("\n","\r",";"), "", $url); 1118 1119 run_shutdown(); 1120 1121 if(!my_validate_url($url, true, true)) 1122 { 1123 header("Location: {$mybb->settings['bburl']}/{$url}"); 1124 } 1125 else 1126 { 1127 header("Location: {$url}"); 1128 } 1129 } 1130 1131 exit; 1132 } 1133 1134 /** 1135 * Generate a listing of page - pagination 1136 * 1137 * @param int $count The number of items 1138 * @param int $perpage The number of items to be shown per page 1139 * @param int $page The current page number 1140 * @param string $url The URL to have page numbers tacked on to (If {page} is specified, the value will be replaced with the page #) 1141 * @param boolean $breadcrumb Whether or not the multipage is being shown in the navigation breadcrumb 1142 * @return string The generated pagination 1143 */ 1144 function multipage($count, $perpage, $page, $url, $breadcrumb=false) 1145 { 1146 global $theme, $templates, $lang, $mybb, $plugins; 1147 1148 if($count <= $perpage) 1149 { 1150 return ''; 1151 } 1152 1153 $args = array( 1154 'count' => &$count, 1155 'perpage' => &$perpage, 1156 'page' => &$page, 1157 'url' => &$url, 1158 'breadcrumb' => &$breadcrumb, 1159 ); 1160 $plugins->run_hooks('multipage', $args); 1161 1162 $page = (int)$page; 1163 1164 $url = str_replace("&", "&", $url); 1165 $url = htmlspecialchars_uni($url); 1166 1167 $pages = ceil($count / $perpage); 1168 1169 $prevpage = ''; 1170 if($page > 1) 1171 { 1172 $prev = $page-1; 1173 $page_url = fetch_page_url($url, $prev); 1174 eval("\$prevpage = \"".$templates->get("multipage_prevpage")."\";"); 1175 } 1176 1177 // Maximum number of "page bits" to show 1178 if(!$mybb->settings['maxmultipagelinks']) 1179 { 1180 $mybb->settings['maxmultipagelinks'] = 5; 1181 } 1182 1183 $from = $page-floor($mybb->settings['maxmultipagelinks']/2); 1184 $to = $page+floor($mybb->settings['maxmultipagelinks']/2); 1185 1186 if($from <= 0) 1187 { 1188 $from = 1; 1189 $to = $from+$mybb->settings['maxmultipagelinks']-1; 1190 } 1191 1192 if($to > $pages) 1193 { 1194 $to = $pages; 1195 $from = $pages-$mybb->settings['maxmultipagelinks']+1; 1196 if($from <= 0) 1197 { 1198 $from = 1; 1199 } 1200 } 1201 1202 if($to == 0) 1203 { 1204 $to = $pages; 1205 } 1206 1207 $start = ''; 1208 if($from > 1) 1209 { 1210 if($from-1 == 1) 1211 { 1212 $lang->multipage_link_start = ''; 1213 } 1214 1215 $page_url = fetch_page_url($url, 1); 1216 eval("\$start = \"".$templates->get("multipage_start")."\";"); 1217 } 1218 1219 $mppage = ''; 1220 for($i = $from; $i <= $to; ++$i) 1221 { 1222 $page_url = fetch_page_url($url, $i); 1223 if($page == $i) 1224 { 1225 if($breadcrumb == true) 1226 { 1227 eval("\$mppage .= \"".$templates->get("multipage_page_link_current")."\";"); 1228 } 1229 else 1230 { 1231 eval("\$mppage .= \"".$templates->get("multipage_page_current")."\";"); 1232 } 1233 } 1234 else 1235 { 1236 eval("\$mppage .= \"".$templates->get("multipage_page")."\";"); 1237 } 1238 } 1239 1240 $end = ''; 1241 if($to < $pages) 1242 { 1243 if($to+1 == $pages) 1244 { 1245 $lang->multipage_link_end = ''; 1246 } 1247 1248 $page_url = fetch_page_url($url, $pages); 1249 eval("\$end = \"".$templates->get("multipage_end")."\";"); 1250 } 1251 1252 $nextpage = ''; 1253 if($page < $pages) 1254 { 1255 $next = $page+1; 1256 $page_url = fetch_page_url($url, $next); 1257 eval("\$nextpage = \"".$templates->get("multipage_nextpage")."\";"); 1258 } 1259 1260 $jumptopage = ''; 1261 if($pages > ($mybb->settings['maxmultipagelinks']+1) && $mybb->settings['jumptopagemultipage'] == 1) 1262 { 1263 // 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 1264 $jump_url = fetch_page_url($url, 1); 1265 eval("\$jumptopage = \"".$templates->get("multipage_jump_page")."\";"); 1266 } 1267 1268 $multipage_pages = $lang->sprintf($lang->multipage_pages, $pages); 1269 1270 if($breadcrumb == true) 1271 { 1272 eval("\$multipage = \"".$templates->get("multipage_breadcrumb")."\";"); 1273 } 1274 else 1275 { 1276 eval("\$multipage = \"".$templates->get("multipage")."\";"); 1277 } 1278 1279 return $multipage; 1280 } 1281 1282 /** 1283 * Generate a page URL for use by the multipage function 1284 * 1285 * @param string $url The URL being passed 1286 * @param int $page The page number 1287 * @return string 1288 */ 1289 function fetch_page_url($url, $page) 1290 { 1291 if($page <= 1) 1292 { 1293 $find = array( 1294 "-page-{page}", 1295 "&page={page}", 1296 "{page}" 1297 ); 1298 1299 // Remove "Page 1" to the defacto URL 1300 $url = str_replace($find, array("", "", $page), $url); 1301 return $url; 1302 } 1303 else if(strpos($url, "{page}") === false) 1304 { 1305 // If no page identifier is specified we tack it on to the end of the URL 1306 if(strpos($url, "?") === false) 1307 { 1308 $url .= "?"; 1309 } 1310 else 1311 { 1312 $url .= "&"; 1313 } 1314 1315 $url .= "page=$page"; 1316 } 1317 else 1318 { 1319 $url = str_replace("{page}", $page, $url); 1320 } 1321 1322 return $url; 1323 } 1324 1325 /** 1326 * Fetch the permissions for a specific user 1327 * 1328 * @param int $uid The user ID, if no user ID is provided then current user's ID will be considered. 1329 * @return array Array of user permissions for the specified user 1330 */ 1331 function user_permissions($uid=null) 1332 { 1333 global $mybb, $cache, $groupscache, $user_cache; 1334 1335 // If no user id is specified, assume it is the current user 1336 if($uid === null) 1337 { 1338 $uid = $mybb->user['uid']; 1339 } 1340 1341 // Its a guest. Return the group permissions directly from cache 1342 if($uid == 0) 1343 { 1344 return $groupscache[1]; 1345 } 1346 1347 // User id does not match current user, fetch permissions 1348 if($uid != $mybb->user['uid']) 1349 { 1350 // We've already cached permissions for this user, return them. 1351 if(!empty($user_cache[$uid]['permissions'])) 1352 { 1353 return $user_cache[$uid]['permissions']; 1354 } 1355 1356 // This user was not already cached, fetch their user information. 1357 if(empty($user_cache[$uid])) 1358 { 1359 $user_cache[$uid] = get_user($uid); 1360 } 1361 1362 // Collect group permissions. 1363 $gid = $user_cache[$uid]['usergroup'].",".$user_cache[$uid]['additionalgroups']; 1364 $groupperms = usergroup_permissions($gid); 1365 1366 // Store group permissions in user cache. 1367 $user_cache[$uid]['permissions'] = $groupperms; 1368 return $groupperms; 1369 } 1370 // This user is the current user, return their permissions 1371 else 1372 { 1373 return $mybb->usergroup; 1374 } 1375 } 1376 1377 /** 1378 * Fetch the usergroup permissions for a specific group or series of groups combined 1379 * 1380 * @param int|string $gid A list of groups (Can be a single integer, or a list of groups separated by a comma) 1381 * @return array Array of permissions generated for the groups, containing also a list of comma-separated checked groups under 'all_usergroups' index 1382 */ 1383 function usergroup_permissions($gid=0) 1384 { 1385 global $cache, $groupscache, $grouppermignore, $groupzerogreater, $groupzerolesser, $groupxgreater, $grouppermbyswitch; 1386 1387 if(!is_array($groupscache)) 1388 { 1389 $groupscache = $cache->read("usergroups"); 1390 } 1391 1392 $groups = explode(",", $gid); 1393 1394 if(count($groups) == 1) 1395 { 1396 $groupscache[$gid]['all_usergroups'] = $gid; 1397 return $groupscache[$gid]; 1398 } 1399 1400 $usergroup = array(); 1401 $usergroup['all_usergroups'] = $gid; 1402 1403 // Get those switch permissions from the first valid group. 1404 $permswitches_usergroup = array(); 1405 $grouppermswitches = array(); 1406 foreach(array_values($grouppermbyswitch) as $permvalue) 1407 { 1408 if(is_array($permvalue)) 1409 { 1410 foreach($permvalue as $perm) 1411 { 1412 $grouppermswitches[] = $perm; 1413 } 1414 } 1415 else 1416 { 1417 $grouppermswitches[] = $permvalue; 1418 } 1419 } 1420 $grouppermswitches = array_unique($grouppermswitches); 1421 foreach($groups as $gid) 1422 { 1423 if(trim($gid) == "" || empty($groupscache[$gid])) 1424 { 1425 continue; 1426 } 1427 foreach($grouppermswitches as $perm) 1428 { 1429 $permswitches_usergroup[$perm] = $groupscache[$gid][$perm]; 1430 } 1431 break; // Only retieve the first available group's permissions as how following action does. 1432 } 1433 1434 foreach($groups as $gid) 1435 { 1436 if(trim($gid) == "" || empty($groupscache[$gid])) 1437 { 1438 continue; 1439 } 1440 1441 foreach($groupscache[$gid] as $perm => $access) 1442 { 1443 if(!in_array($perm, $grouppermignore)) 1444 { 1445 if(isset($usergroup[$perm])) 1446 { 1447 $permbit = $usergroup[$perm]; 1448 } 1449 else 1450 { 1451 $permbit = ""; 1452 } 1453 1454 // permission type: 0 not a numerical permission, otherwise a numerical permission. 1455 // Positive value is for `greater is more` permission, negative for `lesser is more`. 1456 $perm_is_numerical = 0; 1457 $perm_numerical_lowerbound = 0; 1458 1459 // 0 represents unlimited for most numerical group permissions (i.e. private message limit) so take that into account. 1460 if(in_array($perm, $groupzerogreater)) 1461 { 1462 // 1 means a `0 or greater` permission. Value 0 means unlimited. 1463 $perm_is_numerical = 1; 1464 } 1465 // Less is more for some numerical group permissions (i.e. post count required for using signature) so take that into account, too. 1466 else if(in_array($perm, $groupzerolesser)) 1467 { 1468 // -1 means a `0 or lesser` permission. Value 0 means unlimited. 1469 $perm_is_numerical = -1; 1470 } 1471 // Greater is more, but with a lower bound. 1472 else if(array_key_exists($perm, $groupxgreater)) 1473 { 1474 // 2 means a general `greater` permission. Value 0 just means 0. 1475 $perm_is_numerical = 2; 1476 $perm_numerical_lowerbound = $groupxgreater[$perm]; 1477 } 1478 1479 if($perm_is_numerical != 0) 1480 { 1481 $update_current_perm = true; 1482 1483 // Ensure it's an integer. 1484 $access = (int)$access; 1485 // Check if this permission should be activatived by another switch permission in current group. 1486 if(array_key_exists($perm, $grouppermbyswitch)) 1487 { 1488 if(!is_array($grouppermbyswitch[$perm])) 1489 { 1490 $grouppermbyswitch[$perm] = array($grouppermbyswitch[$perm]); 1491 } 1492 1493 $update_current_perm = $group_current_perm_enabled = $group_perm_enabled = false; 1494 foreach($grouppermbyswitch[$perm] as $permswitch) 1495 { 1496 if(!isset($groupscache[$gid][$permswitch])) 1497 { 1498 continue; 1499 } 1500 $permswitches_current = $groupscache[$gid][$permswitch]; 1501 1502 // Determin if the permission is enabled by switches from current group. 1503 if($permswitches_current == 1 || $permswitches_current == "yes") // Keep yes/no for compatibility? 1504 { 1505 $group_current_perm_enabled = true; 1506 } 1507 // Determin if the permission is enabled by switches from previously handled groups. 1508 if($permswitches_usergroup[$permswitch] == 1 || $permswitches_usergroup[$permswitch] == "yes") // Keep yes/no for compatibility? 1509 { 1510 $group_perm_enabled = true; 1511 } 1512 } 1513 1514 // Set this permission if not set yet. 1515 if(!isset($usergroup[$perm])) 1516 { 1517 $usergroup[$perm] = $access; 1518 } 1519 1520 // If current group's setting enables the permission, we may need to update the user's permission. 1521 if($group_current_perm_enabled) 1522 { 1523 // Only update this permission if both its switch and current group switch are on. 1524 if($group_perm_enabled) 1525 { 1526 $update_current_perm = true; 1527 } 1528 // Override old useless value with value from current group. 1529 else 1530 { 1531 $usergroup[$perm] = $access; 1532 } 1533 } 1534 } 1535 1536 // No switch controls this permission, or permission needs an update. 1537 if($update_current_perm) 1538 { 1539 switch($perm_is_numerical) 1540 { 1541 case 1: 1542 case -1: 1543 if($access == 0 || $permbit === 0) 1544 { 1545 $usergroup[$perm] = 0; 1546 break; 1547 } 1548 default: 1549 if($perm_is_numerical > 0 && $access > $permbit || $perm_is_numerical < 0 && $access < $permbit) 1550 { 1551 $usergroup[$perm] = $access; 1552 } 1553 break; 1554 } 1555 } 1556 1557 // Maybe oversubtle, database uses Unsigned on them, but enables usage of permission value with a lower bound. 1558 if($usergroup[$perm] < $perm_numerical_lowerbound) 1559 { 1560 $usergroup[$perm] = $perm_numerical_lowerbound; 1561 } 1562 1563 // Work is done for numerical permissions. 1564 continue; 1565 } 1566 1567 if($access > $permbit || ($access == "yes" && $permbit == "no") || !$permbit) // Keep yes/no for compatibility? 1568 { 1569 $usergroup[$perm] = $access; 1570 } 1571 } 1572 } 1573 1574 foreach($permswitches_usergroup as $perm => $value) 1575 { 1576 $permswitches_usergroup[$perm] = $usergroup[$perm]; 1577 } 1578 } 1579 1580 return $usergroup; 1581 } 1582 1583 /** 1584 * Fetch the display group properties for a specific display group 1585 * 1586 * @param int $gid The group ID to fetch the display properties for 1587 * @return array Array of display properties for the group 1588 */ 1589 function usergroup_displaygroup($gid) 1590 { 1591 global $cache, $groupscache, $displaygroupfields; 1592 1593 if(!is_array($groupscache)) 1594 { 1595 $groupscache = $cache->read("usergroups"); 1596 } 1597 1598 $displaygroup = array(); 1599 $group = $groupscache[$gid]; 1600 1601 foreach($displaygroupfields as $field) 1602 { 1603 $displaygroup[$field] = $group[$field]; 1604 } 1605 1606 return $displaygroup; 1607 } 1608 1609 /** 1610 * Build the forum permissions for a specific forum, user or group 1611 * 1612 * @param int $fid The forum ID to build permissions for (0 builds for all forums) 1613 * @param int $uid The user to build the permissions for (0 will select the uid automatically) 1614 * @param int $gid The group of the user to build permissions for (0 will fetch it) 1615 * @return array Forum permissions for the specific forum or forums 1616 */ 1617 function forum_permissions($fid=0, $uid=0, $gid=0) 1618 { 1619 global $db, $cache, $groupscache, $forum_cache, $fpermcache, $mybb, $cached_forum_permissions_permissions, $cached_forum_permissions; 1620 1621 if($uid == 0) 1622 { 1623 $uid = $mybb->user['uid']; 1624 } 1625 1626 if(!$gid || $gid == 0) // If no group, we need to fetch it 1627 { 1628 if($uid != 0 && $uid != $mybb->user['uid']) 1629 { 1630 $user = get_user($uid); 1631 1632 $gid = $user['usergroup'].",".$user['additionalgroups']; 1633 $groupperms = usergroup_permissions($gid); 1634 } 1635 else 1636 { 1637 $gid = $mybb->user['usergroup']; 1638 1639 if(isset($mybb->user['additionalgroups'])) 1640 { 1641 $gid .= ",".$mybb->user['additionalgroups']; 1642 } 1643 1644 $groupperms = $mybb->usergroup; 1645 } 1646 } 1647 1648 if(!is_array($forum_cache)) 1649 { 1650 $forum_cache = cache_forums(); 1651 1652 if(!$forum_cache) 1653 { 1654 return false; 1655 } 1656 } 1657 1658 if(!is_array($fpermcache)) 1659 { 1660 $fpermcache = $cache->read("forumpermissions"); 1661 } 1662 1663 if($fid) // Fetch the permissions for a single forum 1664 { 1665 if(empty($cached_forum_permissions_permissions[$gid][$fid])) 1666 { 1667 $cached_forum_permissions_permissions[$gid][$fid] = fetch_forum_permissions($fid, $gid, $groupperms); 1668 } 1669 return $cached_forum_permissions_permissions[$gid][$fid]; 1670 } 1671 else 1672 { 1673 if(empty($cached_forum_permissions[$gid])) 1674 { 1675 foreach($forum_cache as $forum) 1676 { 1677 $cached_forum_permissions[$gid][$forum['fid']] = fetch_forum_permissions($forum['fid'], $gid, $groupperms); 1678 } 1679 } 1680 return $cached_forum_permissions[$gid]; 1681 } 1682 } 1683 1684 /** 1685 * Fetches the permissions for a specific forum/group applying the inheritance scheme. 1686 * Called by forum_permissions() 1687 * 1688 * @param int $fid The forum ID 1689 * @param string $gid A comma separated list of usergroups 1690 * @param array $groupperms Group permissions 1691 * @return array Permissions for this forum 1692 */ 1693 function fetch_forum_permissions($fid, $gid, $groupperms) 1694 { 1695 global $groupscache, $forum_cache, $fpermcache, $mybb, $fpermfields; 1696 1697 if(isset($gid)) 1698 { 1699 $groups = explode(",", $gid); 1700 } 1701 else 1702 { 1703 $groups = array(); 1704 } 1705 1706 $current_permissions = array(); 1707 $only_view_own_threads = 1; 1708 $only_reply_own_threads = 1; 1709 1710 if(empty($fpermcache[$fid])) // This forum has no custom or inherited permissions so lets just return the group permissions 1711 { 1712 $current_permissions = $groupperms; 1713 } 1714 else 1715 { 1716 foreach($groups as $gid) 1717 { 1718 // If this forum has custom or inherited permissions for the currently looped group. 1719 if(!empty($fpermcache[$fid][$gid])) 1720 { 1721 $level_permissions = $fpermcache[$fid][$gid]; 1722 } 1723 // Or, use the group permission instead, if available. Some forum permissions not existing here will be added back later. 1724 else if(!empty($groupscache[$gid])) 1725 { 1726 $level_permissions = $groupscache[$gid]; 1727 } 1728 // No permission is available for the currently looped group, probably we have bad data here. 1729 else 1730 { 1731 continue; 1732 } 1733 1734 foreach($level_permissions as $permission => $access) 1735 { 1736 if(empty($current_permissions[$permission]) || $access >= $current_permissions[$permission] || ($access == "yes" && $current_permissions[$permission] == "no")) 1737 { 1738 $current_permissions[$permission] = $access; 1739 } 1740 } 1741 1742 if($level_permissions["canview"] && empty($level_permissions["canonlyviewownthreads"])) 1743 { 1744 $only_view_own_threads = 0; 1745 } 1746 1747 if($level_permissions["canpostreplys"] && empty($level_permissions["canonlyreplyownthreads"])) 1748 { 1749 $only_reply_own_threads = 0; 1750 } 1751 } 1752 1753 if(count($current_permissions) == 0) 1754 { 1755 $current_permissions = $groupperms; 1756 } 1757 } 1758 1759 // Figure out if we can view more than our own threads 1760 if($only_view_own_threads == 0 || !isset($current_permissions["canonlyviewownthreads"])) 1761 { 1762 $current_permissions["canonlyviewownthreads"] = 0; 1763 } 1764 1765 // Figure out if we can reply more than our own threads 1766 if($only_reply_own_threads == 0 || !isset($current_permissions["canonlyreplyownthreads"])) 1767 { 1768 $current_permissions["canonlyreplyownthreads"] = 0; 1769 } 1770 1771 return $current_permissions; 1772 } 1773 1774 /** 1775 * Check whether password for given forum was validated for the current user 1776 * 1777 * @param array $forum The forum data 1778 * @param bool $ignore_empty Whether to treat forum password configured as an empty string as validated 1779 * @param bool $check_parents Whether to check parent forums using `parentlist` 1780 * @return bool 1781 */ 1782 function forum_password_validated($forum, $ignore_empty=false, $check_parents=false) 1783 { 1784 global $mybb, $forum_cache; 1785 1786 if($check_parents && isset($forum['parentlist'])) 1787 { 1788 if(!is_array($forum_cache)) 1789 { 1790 $forum_cache = cache_forums(); 1791 if(!$forum_cache) 1792 { 1793 return false; 1794 } 1795 } 1796 1797 $parents = explode(',', $forum['parentlist']); 1798 rsort($parents); 1799 1800 foreach($parents as $parent_id) 1801 { 1802 if($parent_id != $forum['fid'] && !forum_password_validated($forum_cache[$parent_id], true)) 1803 { 1804 return false; 1805 } 1806 } 1807 } 1808 1809 return ($ignore_empty && $forum['password'] === '') || ( 1810 isset($mybb->cookies['forumpass'][$forum['fid']]) && 1811 my_hash_equals( 1812 md5($mybb->user['uid'].$forum['password']), 1813 $mybb->cookies['forumpass'][$forum['fid']] 1814 ) 1815 ); 1816 } 1817 1818 /** 1819 * Check the password given on a certain forum for validity 1820 * 1821 * @param int $fid The forum ID 1822 * @param int $pid The Parent ID 1823 * @param bool $return 1824 * @return bool 1825 */ 1826 function check_forum_password($fid, $pid=0, $return=false) 1827 { 1828 global $mybb, $header, $footer, $headerinclude, $theme, $templates, $lang, $forum_cache; 1829 1830 $showform = true; 1831 1832 if(!is_array($forum_cache)) 1833 { 1834 $forum_cache = cache_forums(); 1835 if(!$forum_cache) 1836 { 1837 return false; 1838 } 1839 } 1840 1841 // Loop through each of parent forums to ensure we have a password for them too 1842 if(isset($forum_cache[$fid]['parentlist'])) 1843 { 1844 $parents = explode(',', $forum_cache[$fid]['parentlist']); 1845 rsort($parents); 1846 } 1847 if(!empty($parents)) 1848 { 1849 foreach($parents as $parent_id) 1850 { 1851 if($parent_id == $fid || $parent_id == $pid) 1852 { 1853 continue; 1854 } 1855 1856 if($forum_cache[$parent_id]['password'] !== "") 1857 { 1858 check_forum_password($parent_id, $fid); 1859 } 1860 } 1861 } 1862 1863 if($forum_cache[$fid]['password'] !== '') 1864 { 1865 if(isset($mybb->input['pwverify']) && $pid == 0) 1866 { 1867 if(my_hash_equals($forum_cache[$fid]['password'], $mybb->get_input('pwverify'))) 1868 { 1869 my_setcookie("forumpass[$fid]", md5($mybb->user['uid'].$mybb->get_input('pwverify')), null, true); 1870 $showform = false; 1871 } 1872 else 1873 { 1874 eval("\$pwnote = \"".$templates->get("forumdisplay_password_wrongpass")."\";"); 1875 $showform = true; 1876 } 1877 } 1878 else 1879 { 1880 if(!forum_password_validated($forum_cache[$fid])) 1881 { 1882 $showform = true; 1883 } 1884 else 1885 { 1886 $showform = false; 1887 } 1888 } 1889 } 1890 else 1891 { 1892 $showform = false; 1893 } 1894 1895 if($return) 1896 { 1897 return $showform; 1898 } 1899 1900 if($showform) 1901 { 1902 if($pid) 1903 { 1904 header("Location: ".$mybb->settings['bburl']."/".get_forum_link($fid)); 1905 } 1906 else 1907 { 1908 $_SERVER['REQUEST_URI'] = htmlspecialchars_uni($_SERVER['REQUEST_URI']); 1909 eval("\$pwform = \"".$templates->get("forumdisplay_password")."\";"); 1910 output_page($pwform); 1911 } 1912 exit; 1913 } 1914 } 1915 1916 /** 1917 * Return the permissions for a moderator in a specific forum 1918 * 1919 * @param int $fid The forum ID 1920 * @param int $uid The user ID to fetch permissions for (0 assumes current logged in user) 1921 * @param string $parentslist The parent list for the forum (if blank, will be fetched) 1922 * @return array Array of moderator permissions for the specific forum 1923 */ 1924 function get_moderator_permissions($fid, $uid=0, $parentslist="") 1925 { 1926 global $mybb, $cache, $db; 1927 static $modpermscache; 1928 1929 if($uid < 1) 1930 { 1931 $uid = $mybb->user['uid']; 1932 } 1933 1934 if($uid == 0) 1935 { 1936 return false; 1937 } 1938 1939 if(isset($modpermscache[$fid][$uid])) 1940 { 1941 return $modpermscache[$fid][$uid]; 1942 } 1943 1944 if(!$parentslist) 1945 { 1946 $parentslist = explode(',', get_parent_list($fid)); 1947 } 1948 1949 // Get user groups 1950 $perms = array(); 1951 $user = get_user($uid); 1952 1953 $groups = array($user['usergroup']); 1954 1955 if(!empty($user['additionalgroups'])) 1956 { 1957 $extra_groups = explode(",", $user['additionalgroups']); 1958 1959 foreach($extra_groups as $extra_group) 1960 { 1961 $groups[] = $extra_group; 1962 } 1963 } 1964 1965 $mod_cache = $cache->read("moderators"); 1966 1967 foreach($mod_cache as $forumid => $forum) 1968 { 1969 if(empty($forum) || !is_array($forum) || !in_array($forumid, $parentslist)) 1970 { 1971 // No perms or we're not after this forum 1972 continue; 1973 } 1974 1975 // User settings override usergroup settings 1976 if(!empty($forum['users'][$uid])) 1977 { 1978 $perm = $forum['users'][$uid]; 1979 foreach($perm as $action => $value) 1980 { 1981 if(strpos($action, "can") === false) 1982 { 1983 continue; 1984 } 1985 1986 if(!isset($perms[$action])) 1987 { 1988 $perms[$action] = $value; 1989 } 1990 // Figure out the user permissions 1991 else if($value == 0) 1992 { 1993 // The user doesn't have permission to set this action 1994 $perms[$action] = 0; 1995 } 1996 else 1997 { 1998 $perms[$action] = max($perm[$action], $perms[$action]); 1999 } 2000 } 2001 } 2002 2003 foreach($groups as $group) 2004 { 2005 if(empty($forum['usergroups'][$group]) || !is_array($forum['usergroups'][$group])) 2006 { 2007 // There are no permissions set for this group 2008 continue; 2009 } 2010 2011 $perm = $forum['usergroups'][$group]; 2012 foreach($perm as $action => $value) 2013 { 2014 if(strpos($action, "can") === false) 2015 { 2016 continue; 2017 } 2018 2019 if(!isset($perms[$action])) 2020 { 2021 $perms[$action] = $value; 2022 } 2023 else 2024 { 2025 $perms[$action] = max($perm[$action], $perms[$action]); 2026 } 2027 } 2028 } 2029 } 2030 2031 $modpermscache[$fid][$uid] = $perms; 2032 2033 return $perms; 2034 } 2035 2036 /** 2037 * Checks if a moderator has permissions to perform an action in a specific forum 2038 * 2039 * @param int $fid The forum ID (0 assumes global) 2040 * @param string $action The action tyring to be performed. (blank assumes any action at all) 2041 * @param int $uid The user ID (0 assumes current user) 2042 * @return bool Returns true if the user has permission, false if they do not 2043 */ 2044 function is_moderator($fid=0, $action="", $uid=0) 2045 { 2046 global $mybb, $cache, $plugins; 2047 2048 if($uid == 0) 2049 { 2050 $uid = $mybb->user['uid']; 2051 } 2052 2053 if($uid == 0) 2054 { 2055 return false; 2056 } 2057 2058 $user_perms = user_permissions($uid); 2059 2060 $hook_args = array( 2061 'fid' => $fid, 2062 'action' => $action, 2063 'uid' => $uid, 2064 ); 2065 2066 $plugins->run_hooks("is_moderator", $hook_args); 2067 2068 if(isset($hook_args['is_moderator'])) 2069 { 2070 return (boolean) $hook_args['is_moderator']; 2071 } 2072 2073 if(!empty($user_perms['issupermod']) && $user_perms['issupermod'] == 1) 2074 { 2075 if($fid) 2076 { 2077 $forumpermissions = forum_permissions($fid); 2078 if(!empty($forumpermissions['canview']) && !empty($forumpermissions['canviewthreads']) && empty($forumpermissions['canonlyviewownthreads'])) 2079 { 2080 return true; 2081 } 2082 return false; 2083 } 2084 return true; 2085 } 2086 else 2087 { 2088 if(!$fid) 2089 { 2090 $modcache = $cache->read('moderators'); 2091 if(!empty($modcache)) 2092 { 2093 foreach($modcache as $modusers) 2094 { 2095 if(isset($modusers['users'][$uid]) && $modusers['users'][$uid]['mid'] && (!$action || !empty($modusers['users'][$uid][$action]))) 2096 { 2097 return true; 2098 } 2099 2100 $groups = explode(',', $user_perms['all_usergroups']); 2101 2102 foreach($groups as $group) 2103 { 2104 if(trim($group) != '' && isset($modusers['usergroups'][$group]) && (!$action || !empty($modusers['usergroups'][$group][$action]))) 2105 { 2106 return true; 2107 } 2108 } 2109 } 2110 } 2111 return false; 2112 } 2113 else 2114 { 2115 $modperms = get_moderator_permissions($fid, $uid); 2116 2117 if(!$action && $modperms) 2118 { 2119 return true; 2120 } 2121 else 2122 { 2123 if(isset($modperms[$action]) && $modperms[$action] == 1) 2124 { 2125 return true; 2126 } 2127 else 2128 { 2129 return false; 2130 } 2131 } 2132 } 2133 } 2134 } 2135 2136 /** 2137 * Get an array of fids that the forum moderator has access to. 2138 * Do not use for administraotrs or global moderators as they moderate any forum and the function will return false. 2139 * 2140 * @param int $uid The user ID (0 assumes current user) 2141 * @return array|bool an array of the fids the user has moderator access to or bool if called incorrectly. 2142 */ 2143 function get_moderated_fids($uid=0) 2144 { 2145 global $mybb, $cache; 2146 2147 if($uid == 0) 2148 { 2149 $uid = $mybb->user['uid']; 2150 } 2151 2152 if($uid == 0) 2153 { 2154 return array(); 2155 } 2156 2157 $user_perms = user_permissions($uid); 2158 2159 if($user_perms['issupermod'] == 1) 2160 { 2161 return false; 2162 } 2163 2164 $fids = array(); 2165 2166 $modcache = $cache->read('moderators'); 2167 if(!empty($modcache)) 2168 { 2169 $groups = explode(',', $user_perms['all_usergroups']); 2170 2171 foreach($modcache as $fid => $forum) 2172 { 2173 if(isset($forum['users'][$uid]) && $forum['users'][$uid]['mid']) 2174 { 2175 $fids[] = $fid; 2176 continue; 2177 } 2178 2179 foreach($groups as $group) 2180 { 2181 if(trim($group) != '' && isset($forum['usergroups'][$group])) 2182 { 2183 $fids[] = $fid; 2184 } 2185 } 2186 } 2187 } 2188 2189 return $fids; 2190 } 2191 2192 /** 2193 * Generate a list of the posticons. 2194 * 2195 * @return string The template of posticons. 2196 */ 2197 function get_post_icons() 2198 { 2199 global $mybb, $cache, $icon, $theme, $templates, $lang; 2200 2201 if(isset($mybb->input['icon'])) 2202 { 2203 $icon = $mybb->get_input('icon'); 2204 } 2205 2206 $iconlist = ''; 2207 $no_icons_checked = " checked=\"checked\""; 2208 // read post icons from cache, and sort them accordingly 2209 $posticons_cache = (array)$cache->read("posticons"); 2210 $posticons = array(); 2211 foreach($posticons_cache as $posticon) 2212 { 2213 $posticons[$posticon['name']] = $posticon; 2214 } 2215 krsort($posticons); 2216 2217 foreach($posticons as $dbicon) 2218 { 2219 $dbicon['path'] = str_replace("{theme}", $theme['imgdir'], $dbicon['path']); 2220 $dbicon['path'] = htmlspecialchars_uni($mybb->get_asset_url($dbicon['path'])); 2221 $dbicon['name'] = htmlspecialchars_uni($dbicon['name']); 2222 2223 if($icon == $dbicon['iid']) 2224 { 2225 $checked = " checked=\"checked\""; 2226 $no_icons_checked = ''; 2227 } 2228 else 2229 { 2230 $checked = ''; 2231 } 2232 2233 eval("\$iconlist .= \"".$templates->get("posticons_icon")."\";"); 2234 } 2235 2236 if(!empty($iconlist)) 2237 { 2238 eval("\$posticons = \"".$templates->get("posticons")."\";"); 2239 } 2240 else 2241 { 2242 $posticons = ''; 2243 } 2244 2245 return $posticons; 2246 } 2247 2248 /** 2249 * MyBB setcookie() wrapper. 2250 * 2251 * @param string $name The cookie identifier. 2252 * @param string $value The cookie value. 2253 * @param int|string $expires The timestamp of the expiry date. 2254 * @param boolean $httponly True if setting a HttpOnly cookie (supported by the majority of web browsers) 2255 * @param string $samesite The samesite attribute to prevent CSRF. 2256 */ 2257 function my_setcookie($name, $value="", $expires="", $httponly=false, $samesite="") 2258 { 2259 global $mybb; 2260 2261 if(!$mybb->settings['cookiepath']) 2262 { 2263 $mybb->settings['cookiepath'] = "/"; 2264 } 2265 2266 if($expires == -1) 2267 { 2268 $expires = 0; 2269 } 2270 elseif($expires == "" || $expires == null) 2271 { 2272 $expires = TIME_NOW + (60*60*24*365); // Make the cookie expire in a years time 2273 } 2274 else 2275 { 2276 $expires = TIME_NOW + (int)$expires; 2277 } 2278 2279 $mybb->settings['cookiepath'] = str_replace(array("\n","\r"), "", $mybb->settings['cookiepath']); 2280 $mybb->settings['cookiedomain'] = str_replace(array("\n","\r"), "", $mybb->settings['cookiedomain']); 2281 $mybb->settings['cookieprefix'] = str_replace(array("\n","\r", " "), "", $mybb->settings['cookieprefix']); 2282 2283 // 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 2284 $cookie = "Set-Cookie: {$mybb->settings['cookieprefix']}{$name}=".urlencode($value); 2285 2286 if($expires > 0) 2287 { 2288 $cookie .= "; expires=".@gmdate('D, d-M-Y H:i:s \\G\\M\\T', $expires); 2289 } 2290 2291 if(!empty($mybb->settings['cookiepath'])) 2292 { 2293 $cookie .= "; path={$mybb->settings['cookiepath']}"; 2294 } 2295 2296 if(!empty($mybb->settings['cookiedomain'])) 2297 { 2298 $cookie .= "; domain={$mybb->settings['cookiedomain']}"; 2299 } 2300 2301 if($httponly == true) 2302 { 2303 $cookie .= "; HttpOnly"; 2304 } 2305 2306 if($samesite != "" && $mybb->settings['cookiesamesiteflag']) 2307 { 2308 $samesite = strtolower($samesite); 2309 2310 if($samesite == "lax" || $samesite == "strict") 2311 { 2312 $cookie .= "; SameSite=".$samesite; 2313 } 2314 } 2315 2316 if($mybb->settings['cookiesecureflag']) 2317 { 2318 $cookie .= "; Secure"; 2319 } 2320 2321 $mybb->cookies[$name] = $value; 2322 2323 header($cookie, false); 2324 } 2325 2326 /** 2327 * Unset a cookie set by MyBB. 2328 * 2329 * @param string $name The cookie identifier. 2330 */ 2331 function my_unsetcookie($name) 2332 { 2333 global $mybb; 2334 2335 $expires = -3600; 2336 my_setcookie($name, "", $expires); 2337 2338 unset($mybb->cookies[$name]); 2339 } 2340 2341 /** 2342 * Get the contents from a serialised cookie array. 2343 * 2344 * @param string $name The cookie identifier. 2345 * @param int $id The cookie content id. 2346 * @return array|boolean The cookie id's content array or false when non-existent. 2347 */ 2348 function my_get_array_cookie($name, $id) 2349 { 2350 global $mybb; 2351 2352 if(!isset($mybb->cookies['mybb'][$name])) 2353 { 2354 return false; 2355 } 2356 2357 $cookie = my_unserialize($mybb->cookies['mybb'][$name], false); 2358 2359 if(is_array($cookie) && isset($cookie[$id])) 2360 { 2361 return $cookie[$id]; 2362 } 2363 else 2364 { 2365 return 0; 2366 } 2367 } 2368 2369 /** 2370 * Set a serialised cookie array. 2371 * 2372 * @param string $name The cookie identifier. 2373 * @param int $id The cookie content id. 2374 * @param string $value The value to set the cookie to. 2375 * @param int|string $expires The timestamp of the expiry date. 2376 */ 2377 function my_set_array_cookie($name, $id, $value, $expires="") 2378 { 2379 global $mybb; 2380 2381 if(isset($mybb->cookies['mybb'][$name])) 2382 { 2383 $newcookie = my_unserialize($mybb->cookies['mybb'][$name], false); 2384 } 2385 else 2386 { 2387 $newcookie = array(); 2388 } 2389 2390 $newcookie[$id] = $value; 2391 $newcookie = my_serialize($newcookie); 2392 my_setcookie("mybb[$name]", addslashes($newcookie), $expires); 2393 2394 if(isset($mybb->cookies['mybb']) && !is_array($mybb->cookies['mybb'])) 2395 { 2396 $mybb->cookies['mybb'] = array(); 2397 } 2398 2399 // Make sure our current viarables are up-to-date as well 2400 $mybb->cookies['mybb'][$name] = $newcookie; 2401 } 2402 2403 /* 2404 * Arbitrary limits for _safe_unserialize() 2405 */ 2406 define('MAX_SERIALIZED_INPUT_LENGTH', 10240); 2407 define('MAX_SERIALIZED_ARRAY_LENGTH', 256); 2408 define('MAX_SERIALIZED_ARRAY_DEPTH', 5); 2409 2410 /** 2411 * Credits go to https://github.com/piwik 2412 * Safe unserialize() replacement 2413 * - accepts a strict subset of PHP's native my_serialized representation 2414 * - does not unserialize objects 2415 * 2416 * @param string $str 2417 * @param bool $unlimited Whether to apply limits defined in MAX_SERIALIZED_* constants 2418 * @return mixed 2419 * @throw Exception if $str is malformed or contains unsupported types (e.g., resources, objects) 2420 */ 2421 function _safe_unserialize($str, $unlimited = true) 2422 { 2423 if(!$unlimited && strlen($str) > MAX_SERIALIZED_INPUT_LENGTH) 2424 { 2425 // input exceeds MAX_SERIALIZED_INPUT_LENGTH 2426 return false; 2427 } 2428 2429 if(empty($str) || !is_string($str)) 2430 { 2431 return false; 2432 } 2433 2434 $stack = $list = $expected = array(); 2435 2436 /* 2437 * states: 2438 * 0 - initial state, expecting a single value or array 2439 * 1 - terminal state 2440 * 2 - in array, expecting end of array or a key 2441 * 3 - in array, expecting value or another array 2442 */ 2443 $state = 0; 2444 while($state != 1) 2445 { 2446 $type = isset($str[0]) ? $str[0] : ''; 2447 2448 if($type == '}') 2449 { 2450 $str = substr($str, 1); 2451 } 2452 else if($type == 'N' && $str[1] == ';') 2453 { 2454 $value = null; 2455 $str = substr($str, 2); 2456 } 2457 else if($type == 'b' && preg_match('/^b:([01]);/', $str, $matches)) 2458 { 2459 $value = $matches[1] == '1' ? true : false; 2460 $str = substr($str, 4); 2461 } 2462 else if($type == 'i' && preg_match('/^i:(-?[0-9]+);(.*)/s', $str, $matches)) 2463 { 2464 $value = (int)$matches[1]; 2465 $str = $matches[2]; 2466 } 2467 else if($type == 'd' && preg_match('/^d:(-?[0-9]+\.?[0-9]*(E[+-][0-9]+)?);(.*)/s', $str, $matches)) 2468 { 2469 $value = (float)$matches[1]; 2470 $str = $matches[3]; 2471 } 2472 else if($type == 's' && preg_match('/^s:([0-9]+):"(.*)/s', $str, $matches) && substr($matches[2], (int)$matches[1], 2) == '";') 2473 { 2474 $value = substr($matches[2], 0, (int)$matches[1]); 2475 $str = substr($matches[2], (int)$matches[1] + 2); 2476 } 2477 else if( 2478 $type == 'a' && 2479 preg_match('/^a:([0-9]+):{(.*)/s', $str, $matches) && 2480 ($unlimited || $matches[1] < MAX_SERIALIZED_ARRAY_LENGTH) 2481 ) 2482 { 2483 $expectedLength = (int)$matches[1]; 2484 $str = $matches[2]; 2485 } 2486 else 2487 { 2488 // object or unknown/malformed type 2489 return false; 2490 } 2491 2492 switch($state) 2493 { 2494 case 3: // in array, expecting value or another array 2495 if($type == 'a') 2496 { 2497 if(!$unlimited && count($stack) >= MAX_SERIALIZED_ARRAY_DEPTH) 2498 { 2499 // array nesting exceeds MAX_SERIALIZED_ARRAY_DEPTH 2500 return false; 2501 } 2502 2503 $stack[] = &$list; 2504 $list[$key] = array(); 2505 $list = &$list[$key]; 2506 $expected[] = $expectedLength; 2507 $state = 2; 2508 break; 2509 } 2510 if($type != '}') 2511 { 2512 $list[$key] = $value; 2513 $state = 2; 2514 break; 2515 } 2516 2517 // missing array value 2518 return false; 2519 2520 case 2: // in array, expecting end of array or a key 2521 if($type == '}') 2522 { 2523 if(count($list) < end($expected)) 2524 { 2525 // array size less than expected 2526 return false; 2527 } 2528 2529 unset($list); 2530 $list = &$stack[count($stack)-1]; 2531 array_pop($stack); 2532 2533 // go to terminal state if we're at the end of the root array 2534 array_pop($expected); 2535 if(count($expected) == 0) { 2536 $state = 1; 2537 } 2538 break; 2539 } 2540 if($type == 'i' || $type == 's') 2541 { 2542 if(!$unlimited && count($list) >= MAX_SERIALIZED_ARRAY_LENGTH) 2543 { 2544 // array size exceeds MAX_SERIALIZED_ARRAY_LENGTH 2545 return false; 2546 } 2547 if(count($list) >= end($expected)) 2548 { 2549 // array size exceeds expected length 2550 return false; 2551 } 2552 2553 $key = $value; 2554 $state = 3; 2555 break; 2556 } 2557 2558 // illegal array index type 2559 return false; 2560 2561 case 0: // expecting array or value 2562 if($type == 'a') 2563 { 2564 if(!$unlimited && count($stack) >= MAX_SERIALIZED_ARRAY_DEPTH) 2565 { 2566 // array nesting exceeds MAX_SERIALIZED_ARRAY_DEPTH 2567 return false; 2568 } 2569 2570 $data = array(); 2571 $list = &$data; 2572 $expected[] = $expectedLength; 2573 $state = 2; 2574 break; 2575 } 2576 if($type != '}') 2577 { 2578 $data = $value; 2579 $state = 1; 2580 break; 2581 } 2582 2583 // not in array 2584 return false; 2585 } 2586 } 2587 2588 if(!empty($str)) 2589 { 2590 // trailing data in input 2591 return false; 2592 } 2593 return $data; 2594 } 2595 2596 /** 2597 * Credits go to https://github.com/piwik 2598 * Wrapper for _safe_unserialize() that handles exceptions and multibyte encoding issue 2599 * 2600 * @param string $str 2601 * @param bool $unlimited 2602 * @return mixed 2603 */ 2604 function my_unserialize($str, $unlimited = true) 2605 { 2606 // Ensure we use the byte count for strings even when strlen() is overloaded by mb_strlen() 2607 if(function_exists('mb_internal_encoding') && (((int)ini_get('mbstring.func_overload')) & 2)) 2608 { 2609 $mbIntEnc = mb_internal_encoding(); 2610 mb_internal_encoding('ASCII'); 2611 } 2612 2613 $out = _safe_unserialize($str, $unlimited); 2614 2615 if(isset($mbIntEnc)) 2616 { 2617 mb_internal_encoding($mbIntEnc); 2618 } 2619 2620 return $out; 2621 } 2622 2623 /** 2624 * Unserializes data using PHP's `unserialize()`, and its safety options if possible. 2625 * This function should only be used for values from trusted sources. 2626 * 2627 * @param string $str 2628 * @return mixed 2629 */ 2630 function native_unserialize($str) 2631 { 2632 if(version_compare(PHP_VERSION, '7.0.0', '>=')) 2633 { 2634 return unserialize($str, array('allowed_classes' => false)); 2635 } 2636 else 2637 { 2638 return unserialize($str); 2639 } 2640 } 2641 2642 /** 2643 * Credits go to https://github.com/piwik 2644 * Safe serialize() replacement 2645 * - output a strict subset of PHP's native serialized representation 2646 * - does not my_serialize objects 2647 * 2648 * @param mixed $value 2649 * @return string 2650 * @throw Exception if $value is malformed or contains unsupported types (e.g., resources, objects) 2651 */ 2652 function _safe_serialize( $value ) 2653 { 2654 if(is_null($value)) 2655 { 2656 return 'N;'; 2657 } 2658 2659 if(is_bool($value)) 2660 { 2661 return 'b:'.(int)$value.';'; 2662 } 2663 2664 if(is_int($value)) 2665 { 2666 return 'i:'.$value.';'; 2667 } 2668 2669 if(is_float($value)) 2670 { 2671 return 'd:'.str_replace(',', '.', $value).';'; 2672 } 2673 2674 if(is_string($value)) 2675 { 2676 return 's:'.strlen($value).':"'.$value.'";'; 2677 } 2678 2679 if(is_array($value)) 2680 { 2681 $out = ''; 2682 foreach($value as $k => $v) 2683 { 2684 $out .= _safe_serialize($k) . _safe_serialize($v); 2685 } 2686 2687 return 'a:'.count($value).':{'.$out.'}'; 2688 } 2689 2690 // safe_serialize cannot my_serialize resources or objects 2691 return false; 2692 } 2693 2694 /** 2695 * Credits go to https://github.com/piwik 2696 * Wrapper for _safe_serialize() that handles exceptions and multibyte encoding issue 2697 * 2698 * @param mixed $value 2699 * @return string 2700 */ 2701 function my_serialize($value) 2702 { 2703 // ensure we use the byte count for strings even when strlen() is overloaded by mb_strlen() 2704 if(function_exists('mb_internal_encoding') && (((int)ini_get('mbstring.func_overload')) & 2)) 2705 { 2706 $mbIntEnc = mb_internal_encoding(); 2707 mb_internal_encoding('ASCII'); 2708 } 2709 2710 $out = _safe_serialize($value); 2711 if(isset($mbIntEnc)) 2712 { 2713 mb_internal_encoding($mbIntEnc); 2714 } 2715 2716 return $out; 2717 } 2718 2719 /** 2720 * Returns the serverload of the system. 2721 * 2722 * @return int The serverload of the system. 2723 */ 2724 function get_server_load() 2725 { 2726 global $mybb, $lang; 2727 2728 $serverload = array(); 2729 2730 // DIRECTORY_SEPARATOR checks if running windows 2731 if(DIRECTORY_SEPARATOR != '\\') 2732 { 2733 if(function_exists("sys_getloadavg")) 2734 { 2735 // sys_getloadavg() will return an array with [0] being load within the last minute. 2736 $serverload = sys_getloadavg(); 2737 2738 if(!is_array($serverload)) 2739 { 2740 return $lang->unknown; 2741 } 2742 2743 $serverload[0] = round($serverload[0], 4); 2744 } 2745 else if(@file_exists("/proc/loadavg") && $load = @file_get_contents("/proc/loadavg")) 2746 { 2747 $serverload = explode(" ", $load); 2748 $serverload[0] = round($serverload[0], 4); 2749 } 2750 if(!is_numeric($serverload[0])) 2751 { 2752 if($mybb->safemode) 2753 { 2754 return $lang->unknown; 2755 } 2756 2757 // Suhosin likes to throw a warning if exec is disabled then die - weird 2758 if($func_blacklist = @ini_get('suhosin.executor.func.blacklist')) 2759 { 2760 if(strpos(",".$func_blacklist.",", 'exec') !== false) 2761 { 2762 return $lang->unknown; 2763 } 2764 } 2765 // PHP disabled functions? 2766 if($func_blacklist = @ini_get('disable_functions')) 2767 { 2768 if(strpos(",".$func_blacklist.",", 'exec') !== false) 2769 { 2770 return $lang->unknown; 2771 } 2772 } 2773 2774 $load = @exec("uptime"); 2775 $load = explode("load average: ", $load); 2776 $serverload = explode(",", $load[1]); 2777 if(!is_array($serverload)) 2778 { 2779 return $lang->unknown; 2780 } 2781 } 2782 } 2783 else 2784 { 2785 return $lang->unknown; 2786 } 2787 2788 $returnload = trim($serverload[0]); 2789 2790 return $returnload; 2791 } 2792 2793 /** 2794 * Returns the amount of memory allocated to the script. 2795 * 2796 * @return int The amount of memory allocated to the script. 2797 */ 2798 function get_memory_usage() 2799 { 2800 if(function_exists('memory_get_peak_usage')) 2801 { 2802 return memory_get_peak_usage(true); 2803 } 2804 elseif(function_exists('memory_get_usage')) 2805 { 2806 return memory_get_usage(true); 2807 } 2808 return false; 2809 } 2810 2811 /** 2812 * Updates the forum statistics with specific values (or addition/subtraction of the previous value) 2813 * 2814 * @param array $changes Array of items being updated (numthreads,numposts,numusers,numunapprovedthreads,numunapprovedposts,numdeletedposts,numdeletedthreads) 2815 * @param boolean $force Force stats update? 2816 */ 2817 function update_stats($changes=array(), $force=false) 2818 { 2819 global $cache, $db; 2820 static $stats_changes; 2821 2822 if(empty($stats_changes)) 2823 { 2824 // Update stats after all changes are done 2825 add_shutdown('update_stats', array(array(), true)); 2826 } 2827 2828 if(empty($stats_changes) || $stats_changes['inserted']) 2829 { 2830 $stats_changes = array( 2831 'numthreads' => '+0', 2832 'numposts' => '+0', 2833 'numusers' => '+0', 2834 'numunapprovedthreads' => '+0', 2835 'numunapprovedposts' => '+0', 2836 'numdeletedposts' => '+0', 2837 'numdeletedthreads' => '+0', 2838 'inserted' => false // Reset after changes are inserted into cache 2839 ); 2840 $stats = $stats_changes; 2841 } 2842 2843 if($force) // Force writing to cache? 2844 { 2845 if(!empty($changes)) 2846 { 2847 // Calculate before writing to cache 2848 update_stats($changes); 2849 } 2850 $stats = $cache->read("stats"); 2851 $changes = $stats_changes; 2852 } 2853 else 2854 { 2855 $stats = $stats_changes; 2856 } 2857 2858 $new_stats = array(); 2859 $counters = array('numthreads', 'numunapprovedthreads', 'numposts', 'numunapprovedposts', 'numusers', 'numdeletedposts', 'numdeletedthreads'); 2860 foreach($counters as $counter) 2861 { 2862 if(array_key_exists($counter, $changes)) 2863 { 2864 if(substr($changes[$counter], 0, 2) == "+-") 2865 { 2866 $changes[$counter] = substr($changes[$counter], 1); 2867 } 2868 // Adding or subtracting from previous value? 2869 if(substr($changes[$counter], 0, 1) == "+" || substr($changes[$counter], 0, 1) == "-") 2870 { 2871 if((int)$changes[$counter] != 0) 2872 { 2873 $new_stats[$counter] = $stats[$counter] + $changes[$counter]; 2874 if(!$force && (substr($stats[$counter], 0, 1) == "+" || substr($stats[$counter], 0, 1) == "-")) 2875 { 2876 // We had relative values? Then it is still relative 2877 if($new_stats[$counter] >= 0) 2878 { 2879 $new_stats[$counter] = "+{$new_stats[$counter]}"; 2880 } 2881 } 2882 // Less than 0? That's bad 2883 elseif($new_stats[$counter] < 0) 2884 { 2885 $new_stats[$counter] = 0; 2886 } 2887 } 2888 } 2889 else 2890 { 2891 $new_stats[$counter] = $changes[$counter]; 2892 // Less than 0? That's bad 2893 if($new_stats[$counter] < 0) 2894 { 2895 $new_stats[$counter] = 0; 2896 } 2897 } 2898 } 2899 } 2900 2901 if(!$force) 2902 { 2903 $stats_changes = array_merge($stats, $new_stats); // Overwrite changed values 2904 return; 2905 } 2906 2907 // Fetch latest user if the user count is changing 2908 if(array_key_exists('numusers', $changes)) 2909 { 2910 $query = $db->simple_select("users", "uid, username", "", array('order_by' => 'regdate', 'order_dir' => 'DESC', 'limit' => 1)); 2911 $lastmember = $db->fetch_array($query); 2912 $new_stats['lastuid'] = $lastmember['uid']; 2913 $new_stats['lastusername'] = $lastmember['username'] = htmlspecialchars_uni($lastmember['username']); 2914 } 2915 2916 if(!empty($new_stats)) 2917 { 2918 if(is_array($stats)) 2919 { 2920 $stats = array_merge($stats, $new_stats); // Overwrite changed values 2921 } 2922 else 2923 { 2924 $stats = $new_stats; 2925 } 2926 } 2927 2928 // Update stats row for today in the database 2929 $todays_stats = array( 2930 "dateline" => mktime(0, 0, 0, date("m"), date("j"), date("Y")), 2931 "numusers" => (int)$stats['numusers'], 2932 "numthreads" => (int)$stats['numthreads'], 2933 "numposts" => (int)$stats['numposts'] 2934 ); 2935 $db->replace_query("stats", $todays_stats, "dateline"); 2936 2937 $cache->update("stats", $stats, "dateline"); 2938 $stats_changes['inserted'] = true; 2939 } 2940 2941 /** 2942 * Updates the forum counters with a specific value (or addition/subtraction of the previous value) 2943 * 2944 * @param int $fid The forum ID 2945 * @param array $changes Array of items being updated (threads, posts, unapprovedthreads, unapprovedposts, deletedposts, deletedthreads) and their value (ex, 1, +1, -1) 2946 */ 2947 function update_forum_counters($fid, $changes=array()) 2948 { 2949 global $db; 2950 2951 $update_query = array(); 2952 2953 $counters = array('threads', 'unapprovedthreads', 'posts', 'unapprovedposts', 'deletedposts', 'deletedthreads'); 2954 2955 // Fetch above counters for this forum 2956 $query = $db->simple_select("forums", implode(",", $counters), "fid='{$fid}'"); 2957 $forum = $db->fetch_array($query); 2958 2959 foreach($counters as $counter) 2960 { 2961 if(array_key_exists($counter, $changes)) 2962 { 2963 if(substr($changes[$counter], 0, 2) == "+-") 2964 { 2965 $changes[$counter] = substr($changes[$counter], 1); 2966 } 2967 // Adding or subtracting from previous value? 2968 if(substr($changes[$counter], 0, 1) == "+" || substr($changes[$counter], 0, 1) == "-") 2969 { 2970 if((int)$changes[$counter] != 0) 2971 { 2972 $update_query[$counter] = $forum[$counter] + $changes[$counter]; 2973 } 2974 } 2975 else 2976 { 2977 $update_query[$counter] = $changes[$counter]; 2978 } 2979 2980 // Less than 0? That's bad 2981 if(isset($update_query[$counter]) && $update_query[$counter] < 0) 2982 { 2983 $update_query[$counter] = 0; 2984 } 2985 } 2986 } 2987 2988 // Only update if we're actually doing something 2989 if(count($update_query) > 0) 2990 { 2991 $db->update_query("forums", $update_query, "fid='".(int)$fid."'"); 2992 } 2993 2994 // Guess we should update the statistics too? 2995 $new_stats = array(); 2996 if(array_key_exists('threads', $update_query)) 2997 { 2998 $threads_diff = $update_query['threads'] - $forum['threads']; 2999 if($threads_diff > -1) 3000 { 3001 $new_stats['numthreads'] = "+{$threads_diff}"; 3002 } 3003 else 3004 { 3005 $new_stats['numthreads'] = "{$threads_diff}"; 3006 } 3007 } 3008 3009 if(array_key_exists('unapprovedthreads', $update_query)) 3010 { 3011 $unapprovedthreads_diff = $update_query['unapprovedthreads'] - $forum['unapprovedthreads']; 3012 if($unapprovedthreads_diff > -1) 3013 { 3014 $new_stats['numunapprovedthreads'] = "+{$unapprovedthreads_diff}"; 3015 } 3016 else 3017 { 3018 $new_stats['numunapprovedthreads'] = "{$unapprovedthreads_diff}"; 3019 } 3020 } 3021 3022 if(array_key_exists('posts', $update_query)) 3023 { 3024 $posts_diff = $update_query['posts'] - $forum['posts']; 3025 if($posts_diff > -1) 3026 { 3027 $new_stats['numposts'] = "+{$posts_diff}"; 3028 } 3029 else 3030 { 3031 $new_stats['numposts'] = "{$posts_diff}"; 3032 } 3033 } 3034 3035 if(array_key_exists('unapprovedposts', $update_query)) 3036 { 3037 $unapprovedposts_diff = $update_query['unapprovedposts'] - $forum['unapprovedposts']; 3038 if($unapprovedposts_diff > -1) 3039 { 3040 $new_stats['numunapprovedposts'] = "+{$unapprovedposts_diff}"; 3041 } 3042 else 3043 { 3044 $new_stats['numunapprovedposts'] = "{$unapprovedposts_diff}"; 3045 } 3046 } 3047 3048 if(array_key_exists('deletedposts', $update_query)) 3049 { 3050 $deletedposts_diff = $update_query['deletedposts'] - $forum['deletedposts']; 3051 if($deletedposts_diff > -1) 3052 { 3053 $new_stats['numdeletedposts'] = "+{$deletedposts_diff}"; 3054 } 3055 else 3056 { 3057 $new_stats['numdeletedposts'] = "{$deletedposts_diff}"; 3058 } 3059 } 3060 3061 if(array_key_exists('deletedthreads', $update_query)) 3062 { 3063 $deletedthreads_diff = $update_query['deletedthreads'] - $forum['deletedthreads']; 3064 if($deletedthreads_diff > -1) 3065 { 3066 $new_stats['numdeletedthreads'] = "+{$deletedthreads_diff}"; 3067 } 3068 else 3069 { 3070 $new_stats['numdeletedthreads'] = "{$deletedthreads_diff}"; 3071 } 3072 } 3073 3074 if(!empty($new_stats)) 3075 { 3076 update_stats($new_stats); 3077 } 3078 } 3079 3080 /** 3081 * Update the last post information for a specific forum 3082 * 3083 * @param int $fid The forum ID 3084 */ 3085 function update_forum_lastpost($fid) 3086 { 3087 global $db; 3088 3089 // Fetch the last post for this forum 3090 $query = $db->query(" 3091 SELECT tid, lastpost, lastposter, lastposteruid, subject 3092 FROM ".TABLE_PREFIX."threads 3093 WHERE fid='{$fid}' AND visible='1' AND closed NOT LIKE 'moved|%' 3094 ORDER BY lastpost DESC 3095 LIMIT 0, 1 3096 "); 3097 3098 if($db->num_rows($query) > 0) 3099 { 3100 $lastpost = $db->fetch_array($query); 3101 3102 $updated_forum = array( 3103 "lastpost" => (int)$lastpost['lastpost'], 3104 "lastposter" => $db->escape_string($lastpost['lastposter']), 3105 "lastposteruid" => (int)$lastpost['lastposteruid'], 3106 "lastposttid" => (int)$lastpost['tid'], 3107 "lastpostsubject" => $db->escape_string($lastpost['subject']), 3108 ); 3109 } 3110 else { 3111 $updated_forum = array( 3112 "lastpost" => 0, 3113 "lastposter" => '', 3114 "lastposteruid" => 0, 3115 "lastposttid" => 0, 3116 "lastpostsubject" => '', 3117 ); 3118 } 3119 3120 $db->update_query("forums", $updated_forum, "fid='{$fid}'"); 3121 } 3122 3123 /** 3124 * Updates the thread counters with a specific value (or addition/subtraction of the previous value) 3125 * 3126 * @param int $tid The thread ID 3127 * @param array $changes Array of items being updated (replies, unapprovedposts, deletedposts, attachmentcount) and their value (ex, 1, +1, -1) 3128 */ 3129 function update_thread_counters($tid, $changes=array()) 3130 { 3131 global $db; 3132 3133 $update_query = array(); 3134 $tid = (int)$tid; 3135 3136 $counters = array('replies', 'unapprovedposts', 'attachmentcount', 'deletedposts', 'attachmentcount'); 3137 3138 // Fetch above counters for this thread 3139 $query = $db->simple_select("threads", implode(",", $counters), "tid='{$tid}'"); 3140 $thread = $db->fetch_array($query); 3141 3142 foreach($counters as $counter) 3143 { 3144 if(array_key_exists($counter, $changes)) 3145 { 3146 if(substr($changes[$counter], 0, 2) == "+-") 3147 { 3148 $changes[$counter] = substr($changes[$counter], 1); 3149 } 3150 // Adding or subtracting from previous value? 3151 if(substr($changes[$counter], 0, 1) == "+" || substr($changes[$counter], 0, 1) == "-") 3152 { 3153 if((int)$changes[$counter] != 0) 3154 { 3155 $update_query[$counter] = $thread[$counter] + $changes[$counter]; 3156 } 3157 } 3158 else 3159 { 3160 $update_query[$counter] = $changes[$counter]; 3161 } 3162 3163 // Less than 0? That's bad 3164 if(isset($update_query[$counter]) && $update_query[$counter] < 0) 3165 { 3166 $update_query[$counter] = 0; 3167 } 3168 } 3169 } 3170 3171 $db->free_result($query); 3172 3173 // Only update if we're actually doing something 3174 if(count($update_query) > 0) 3175 { 3176 $db->update_query("threads", $update_query, "tid='{$tid}'"); 3177 } 3178 } 3179 3180 /** 3181 * Update the first post and lastpost data for a specific thread 3182 * 3183 * @param int $tid The thread ID 3184 */ 3185 function update_thread_data($tid) 3186 { 3187 global $db; 3188 3189 $thread = get_thread($tid); 3190 3191 // If this is a moved thread marker, don't update it - we need it to stay as it is 3192 if(strpos($thread['closed'], 'moved|') !== false) 3193 { 3194 return; 3195 } 3196 3197 $query = $db->query(" 3198 SELECT u.uid, u.username, p.username AS postusername, p.dateline 3199 FROM ".TABLE_PREFIX."posts p 3200 LEFT JOIN ".TABLE_PREFIX."users u ON (u.uid=p.uid) 3201 WHERE p.tid='$tid' AND p.visible='1' 3202 ORDER BY p.dateline DESC, p.pid DESC 3203 LIMIT 1" 3204 ); 3205 $lastpost = $db->fetch_array($query); 3206 3207 $db->free_result($query); 3208 3209 $query = $db->query(" 3210 SELECT u.uid, u.username, p.pid, p.username AS postusername, p.dateline 3211 FROM ".TABLE_PREFIX."posts p 3212 LEFT JOIN ".TABLE_PREFIX."users u ON (u.uid=p.uid) 3213 WHERE p.tid='$tid' 3214 ORDER BY p.dateline ASC, p.pid ASC 3215 LIMIT 1 3216 "); 3217 $firstpost = $db->fetch_array($query); 3218 3219 $db->free_result($query); 3220 3221 if(empty($firstpost['username'])) 3222 { 3223 $firstpost['username'] = $firstpost['postusername']; 3224 } 3225 3226 if(empty($lastpost['username'])) 3227 { 3228 $lastpost['username'] = $lastpost['postusername']; 3229 } 3230 3231 if(empty($lastpost['dateline'])) 3232 { 3233 $lastpost['username'] = $firstpost['username']; 3234 $lastpost['uid'] = $firstpost['uid']; 3235 $lastpost['dateline'] = $firstpost['dateline']; 3236 } 3237 3238 $lastpost['username'] = $db->escape_string($lastpost['username']); 3239 $firstpost['username'] = $db->escape_string($firstpost['username']); 3240 3241 $update_array = array( 3242 'firstpost' => (int)$firstpost['pid'], 3243 'username' => $firstpost['username'], 3244 'uid' => (int)$firstpost['uid'], 3245 'dateline' => (int)$firstpost['dateline'], 3246 'lastpost' => (int)$lastpost['dateline'], 3247 'lastposter' => $lastpost['username'], 3248 'lastposteruid' => (int)$lastpost['uid'], 3249 ); 3250 $db->update_query("threads", $update_array, "tid='{$tid}'"); 3251 } 3252 3253 /** 3254 * Updates the user counters with a specific value (or addition/subtraction of the previous value) 3255 * 3256 * @param int $uid The user ID 3257 * @param array $changes Array of items being updated (postnum, threadnum) and their value (ex, 1, +1, -1) 3258 */ 3259 function update_user_counters($uid, $changes=array()) 3260 { 3261 global $db; 3262 3263 $update_query = array(); 3264 3265 $counters = array('postnum', 'threadnum'); 3266 $uid = (int)$uid; 3267 3268 // Fetch above counters for this user 3269 $query = $db->simple_select("users", implode(",", $counters), "uid='{$uid}'"); 3270 $user = $db->fetch_array($query); 3271 3272 if($user) 3273 { 3274 foreach($counters as $counter) 3275 { 3276 if(array_key_exists($counter, $changes)) 3277 { 3278 if(substr($changes[$counter], 0, 2) == "+-") 3279 { 3280 $changes[$counter] = substr($changes[$counter], 1); 3281 } 3282 // Adding or subtracting from previous value? 3283 if(substr($changes[$counter], 0, 1) == "+" || substr($changes[$counter], 0, 1) == "-") 3284 { 3285 if((int)$changes[$counter] != 0) 3286 { 3287 $update_query[$counter] = $user[$counter] + $changes[$counter]; 3288 } 3289 } 3290 else 3291 { 3292 $update_query[$counter] = $changes[$counter]; 3293 } 3294 3295 // Less than 0? That's bad 3296 if(isset($update_query[$counter]) && $update_query[$counter] < 0) 3297 { 3298 $update_query[$counter] = 0; 3299 } 3300 } 3301 } 3302 } 3303 3304 $db->free_result($query); 3305 3306 // Only update if we're actually doing something 3307 if(count($update_query) > 0) 3308 { 3309 $db->update_query("users", $update_query, "uid='{$uid}'"); 3310 } 3311 } 3312 3313 /** 3314 * Deletes a thread from the database 3315 * 3316 * @param int $tid The thread ID 3317 * @return bool 3318 */ 3319 function delete_thread($tid) 3320 { 3321 global $moderation; 3322 3323 if(!is_object($moderation)) 3324 { 3325 require_once MYBB_ROOT."inc/class_moderation.php"; 3326 $moderation = new Moderation; 3327 } 3328 3329 return $moderation->delete_thread($tid); 3330 } 3331 3332 /** 3333 * Deletes a post from the database 3334 * 3335 * @param int $pid The thread ID 3336 * @return bool 3337 */ 3338 function delete_post($pid) 3339 { 3340 global $moderation; 3341 3342 if(!is_object($moderation)) 3343 { 3344 require_once MYBB_ROOT."inc/class_moderation.php"; 3345 $moderation = new Moderation; 3346 } 3347 3348 return $moderation->delete_post($pid); 3349 } 3350 3351 /** 3352 * Builds a forum jump menu 3353 * 3354 * @param int $pid The parent forum to start with 3355 * @param int $selitem The selected item ID 3356 * @param int $addselect If we need to add select boxes to this cal or not 3357 * @param string $depth The current depth of forums we're at 3358 * @param int $showextras Whether or not to show extra items such as User CP, Forum home 3359 * @param boolean $showall Ignore the showinjump setting and show all forums (for moderation pages) 3360 * @param mixed $permissions deprecated 3361 * @param string $name The name of the forum jump 3362 * @return string Forum jump items 3363 */ 3364 function build_forum_jump($pid=0, $selitem=0, $addselect=1, $depth="", $showextras=1, $showall=false, $permissions="", $name="fid") 3365 { 3366 global $forum_cache, $jumpfcache, $permissioncache, $mybb, $forumjump, $forumjumpbits, $gobutton, $theme, $templates, $lang; 3367 3368 $pid = (int)$pid; 3369 3370 if(!is_array($jumpfcache)) 3371 { 3372 if(!is_array($forum_cache)) 3373 { 3374 cache_forums(); 3375 } 3376 3377 foreach($forum_cache as $fid => $forum) 3378 { 3379 if($forum['active'] != 0) 3380 { 3381 $jumpfcache[$forum['pid']][$forum['disporder']][$forum['fid']] = $forum; 3382 } 3383 } 3384 } 3385 3386 if(!is_array($permissioncache)) 3387 { 3388 $permissioncache = forum_permissions(); 3389 } 3390 3391 if(isset($jumpfcache[$pid]) && is_array($jumpfcache[$pid])) 3392 { 3393 foreach($jumpfcache[$pid] as $main) 3394 { 3395 foreach($main as $forum) 3396 { 3397 $perms = $permissioncache[$forum['fid']]; 3398 3399 if($forum['fid'] != "0" && ($perms['canview'] != 0 || $mybb->settings['hideprivateforums'] == 0) && $forum['linkto'] == '' && ($forum['showinjump'] != 0 || $showall == true)) 3400 { 3401 $optionselected = ""; 3402 3403 if($selitem == $forum['fid']) 3404 { 3405 $optionselected = 'selected="selected"'; 3406 } 3407 3408 $forum['name'] = htmlspecialchars_uni(strip_tags($forum['name'])); 3409 3410 eval("\$forumjumpbits .= \"".$templates->get("forumjump_bit")."\";"); 3411 3412 if($forum_cache[$forum['fid']]) 3413 { 3414 $newdepth = $depth."--"; 3415 $forumjumpbits .= build_forum_jump($forum['fid'], $selitem, 0, $newdepth, $showextras, $showall); 3416 } 3417 } 3418 } 3419 } 3420 } 3421 3422 if($addselect) 3423 { 3424 if($showextras == 0) 3425 { 3426 $template = "special"; 3427 } 3428 else 3429 { 3430 $template = "advanced"; 3431 3432 if(strpos(FORUM_URL, '.html') !== false) 3433 { 3434 $forum_link = "'".str_replace('{fid}', "'+option+'", FORUM_URL)."'"; 3435 } 3436 else 3437 { 3438 $forum_link = "'".str_replace('{fid}', "'+option", FORUM_URL); 3439 } 3440 } 3441 3442 eval("\$forumjump = \"".$templates->get("forumjump_".$template)."\";"); 3443 } 3444 3445 return $forumjump; 3446 } 3447 3448 /** 3449 * Returns the extension of a file. 3450 * 3451 * @param string $file The filename. 3452 * @return string The extension of the file. 3453 */ 3454 function get_extension($file) 3455 { 3456 return my_strtolower(my_substr(strrchr($file, "."), 1)); 3457 } 3458 3459 /** 3460 * Generates a random string. 3461 * 3462 * @param int $length The length of the string to generate. 3463 * @param bool $complex Whether to return complex string. Defaults to false 3464 * @return string The random string. 3465 */ 3466 function random_str($length=8, $complex=false) 3467 { 3468 $set = array_merge(range(0, 9), range('A', 'Z'), range('a', 'z')); 3469 $str = array(); 3470 3471 // Complex strings have always at least 3 characters, even if $length < 3 3472 if($complex == true) 3473 { 3474 // At least one number 3475 $str[] = $set[my_rand(0, 9)]; 3476 3477 // At least one big letter 3478 $str[] = $set[my_rand(10, 35)]; 3479 3480 // At least one small letter 3481 $str[] = $set[my_rand(36, 61)]; 3482 3483 $length -= 3; 3484 } 3485 3486 for($i = 0; $i < $length; ++$i) 3487 { 3488 $str[] = $set[my_rand(0, 61)]; 3489 } 3490 3491 // Make sure they're in random order and convert them to a string 3492 shuffle($str); 3493 3494 return implode($str); 3495 } 3496 3497 /** 3498 * Formats a username based on their display group 3499 * 3500 * @param string $username The username 3501 * @param int $usergroup The usergroup for the user 3502 * @param int $displaygroup The display group for the user 3503 * @return string The formatted username 3504 */ 3505 function format_name($username, $usergroup, $displaygroup=0) 3506 { 3507 global $groupscache, $cache, $plugins; 3508 3509 static $formattednames = array(); 3510 3511 if(!isset($formattednames[$username])) 3512 { 3513 if(!is_array($groupscache)) 3514 { 3515 $groupscache = $cache->read("usergroups"); 3516 } 3517 3518 if($displaygroup != 0) 3519 { 3520 $usergroup = $displaygroup; 3521 } 3522 3523 $format = "{username}"; 3524 3525 if(isset($groupscache[$usergroup])) 3526 { 3527 $ugroup = $groupscache[$usergroup]; 3528 3529 if(strpos($ugroup['namestyle'], "{username}") !== false) 3530 { 3531 $format = $ugroup['namestyle']; 3532 } 3533 } 3534 3535 $format = stripslashes($format); 3536 3537 $parameters = compact('username', 'usergroup', 'displaygroup', 'format'); 3538 3539 $parameters = $plugins->run_hooks('format_name', $parameters); 3540 3541 $format = $parameters['format']; 3542 3543 $formattednames[$username] = str_replace("{username}", $username, $format); 3544 } 3545 3546 return $formattednames[$username]; 3547 } 3548 3549 /** 3550 * Formats an avatar to a certain dimension 3551 * 3552 * @param string $avatar The avatar file name 3553 * @param string $dimensions Dimensions of the avatar, width x height (e.g. 44|44) 3554 * @param string $max_dimensions The maximum dimensions of the formatted avatar 3555 * @return array Information for the formatted avatar 3556 */ 3557 function format_avatar($avatar, $dimensions = '', $max_dimensions = '') 3558 { 3559 global $mybb, $theme; 3560 static $avatars; 3561 3562 if(!isset($avatars)) 3563 { 3564 $avatars = array(); 3565 } 3566 3567 if(my_strpos($avatar, '://') !== false && !$mybb->settings['allowremoteavatars']) 3568 { 3569 // Remote avatar, but remote avatars are disallowed. 3570 $avatar = null; 3571 } 3572 3573 if(!$avatar) 3574 { 3575 // Default avatar 3576 if(defined('IN_ADMINCP')) 3577 { 3578 $theme['imgdir'] = '../images'; 3579 } 3580 3581 $avatar = str_replace('{theme}', $theme['imgdir'], $mybb->settings['useravatar']); 3582 $dimensions = $mybb->settings['useravatardims']; 3583 } 3584 3585 if(!$max_dimensions) 3586 { 3587 $max_dimensions = $mybb->settings['maxavatardims']; 3588 } 3589 3590 // An empty key wouldn't work so we need to add a fall back 3591 $key = $dimensions; 3592 if(empty($key)) 3593 { 3594 $key = 'default'; 3595 } 3596 $key2 = $max_dimensions; 3597 if(empty($key2)) 3598 { 3599 $key2 = 'default'; 3600 } 3601 3602 if(isset($avatars[$avatar][$key][$key2])) 3603 { 3604 return $avatars[$avatar][$key][$key2]; 3605 } 3606 3607 $avatar_width_height = ''; 3608 3609 if($dimensions) 3610 { 3611 $dimensions = preg_split('/[|x]/', $dimensions); 3612 3613 if($dimensions[0] && $dimensions[1]) 3614 { 3615 list($max_width, $max_height) = preg_split('/[|x]/', $max_dimensions); 3616 3617 if(!empty($max_dimensions) && ($dimensions[0] > $max_width || $dimensions[1] > $max_height)) 3618 { 3619 require_once MYBB_ROOT."inc/functions_image.php"; 3620 $scaled_dimensions = scale_image($dimensions[0], $dimensions[1], $max_width, $max_height); 3621 $avatar_width_height = "width=\"{$scaled_dimensions['width']}\" height=\"{$scaled_dimensions['height']}\""; 3622 } 3623 else 3624 { 3625 $avatar_width_height = "width=\"{$dimensions[0]}\" height=\"{$dimensions[1]}\""; 3626 } 3627 } 3628 } 3629 3630 $avatars[$avatar][$key][$key2] = array( 3631 'image' => htmlspecialchars_uni($mybb->get_asset_url($avatar)), 3632 'width_height' => $avatar_width_height 3633 ); 3634 3635 return $avatars[$avatar][$key][$key2]; 3636 } 3637 3638 /** 3639 * Build the javascript based MyCode inserter. 3640 * 3641 * @param string $bind The ID of the textarea to bind to. Defaults to "message". 3642 * @param bool $smilies Whether to include smilies. Defaults to true. 3643 * 3644 * @return string The MyCode inserter 3645 */ 3646 function build_mycode_inserter($bind="message", $smilies = true) 3647 { 3648 global $db, $mybb, $theme, $templates, $lang, $plugins, $smiliecache, $cache; 3649 3650 if($mybb->settings['bbcodeinserter'] != 0) 3651 { 3652 $editor_lang_strings = array( 3653 "editor_bold" => "Bold", 3654 "editor_italic" => "Italic", 3655 "editor_underline" => "Underline", 3656 "editor_strikethrough" => "Strikethrough", 3657 "editor_subscript" => "Subscript", 3658 "editor_superscript" => "Superscript", 3659 "editor_alignleft" => "Align left", 3660 "editor_center" => "Center", 3661 "editor_alignright" => "Align right", 3662 "editor_justify" => "Justify", 3663 "editor_fontname" => "Font Name", 3664 "editor_fontsize" => "Font Size", 3665 "editor_fontcolor" => "Font Color", 3666 "editor_removeformatting" => "Remove Formatting", 3667 "editor_cut" => "Cut", 3668 "editor_cutnosupport" => "Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X", 3669 "editor_copy" => "Copy", 3670 "editor_copynosupport" => "Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C", 3671 "editor_paste" => "Paste", 3672 "editor_pastenosupport" => "Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V", 3673 "editor_pasteentertext" => "Paste your text inside the following box:", 3674 "editor_pastetext" => "PasteText", 3675 "editor_numlist" => "Numbered list", 3676 "editor_bullist" => "Bullet list", 3677 "editor_undo" => "Undo", 3678 "editor_redo" => "Redo", 3679 "editor_rows" => "Rows:", 3680 "editor_cols" => "Cols:", 3681 "editor_inserttable" => "Insert a table", 3682 "editor_inserthr" => "Insert a horizontal rule", 3683 "editor_code" => "Code", 3684 "editor_width" => "Width (optional):", 3685 "editor_height" => "Height (optional):", 3686 "editor_insertimg" => "Insert an image", 3687 "editor_email" => "E-mail:", 3688 "editor_insertemail" => "Insert an email", 3689 "editor_url" => "URL:", 3690 "editor_insertlink" => "Insert a link", 3691 "editor_unlink" => "Unlink", 3692 "editor_more" => "More", 3693 "editor_insertemoticon" => "Insert an emoticon", 3694 "editor_videourl" => "Video URL:", 3695 "editor_videotype" => "Video Type:", 3696 "editor_insert" => "Insert", 3697 "editor_insertyoutubevideo" => "Insert a YouTube video", 3698 "editor_currentdate" => "Insert current date", 3699 "editor_currenttime" => "Insert current time", 3700 "editor_print" => "Print", 3701 "editor_viewsource" => "View source", 3702 "editor_description" => "Description (optional):", 3703 "editor_enterimgurl" => "Enter the image URL:", 3704 "editor_enteremail" => "Enter the e-mail address:", 3705 "editor_enterdisplayedtext" => "Enter the displayed text:", 3706 "editor_enterurl" => "Enter URL:", 3707 "editor_enteryoutubeurl" => "Enter the YouTube video URL or ID:", 3708 "editor_insertquote" => "Insert a Quote", 3709 "editor_invalidyoutube" => "Invalid YouTube video", 3710 "editor_dailymotion" => "Dailymotion", 3711 "editor_metacafe" => "MetaCafe", 3712 "editor_mixer" => "Mixer", 3713 "editor_vimeo" => "Vimeo", 3714 "editor_youtube" => "Youtube", 3715 "editor_facebook" => "Facebook", 3716 "editor_liveleak" => "LiveLeak", 3717 "editor_insertvideo" => "Insert a video", 3718 "editor_php" => "PHP", 3719 "editor_maximize" => "Maximize" 3720 ); 3721 $editor_language = "(function ($) {\n$.sceditor.locale[\"mybblang\"] = {\n"; 3722 3723 $editor_lang_strings = $plugins->run_hooks("mycode_add_codebuttons", $editor_lang_strings); 3724 3725 $editor_languages_count = count($editor_lang_strings); 3726 $i = 0; 3727 foreach($editor_lang_strings as $lang_string => $key) 3728 { 3729 $i++; 3730 $js_lang_string = str_replace("\"", "\\\"", $key); 3731 $string = str_replace("\"", "\\\"", $lang->$lang_string); 3732 $editor_language .= "\t\"{$js_lang_string}\": \"{$string}\""; 3733 3734 if($i < $editor_languages_count) 3735 { 3736 $editor_language .= ","; 3737 } 3738 3739 $editor_language .= "\n"; 3740 } 3741 3742 $editor_language .= "}})(jQuery);"; 3743 3744 if(defined("IN_ADMINCP")) 3745 { 3746 global $page; 3747 $codeinsert = $page->build_codebuttons_editor($bind, $editor_language, $smilies); 3748 } 3749 else 3750 { 3751 // Smilies 3752 $emoticon = ""; 3753 $emoticons_enabled = "false"; 3754 if($smilies) 3755 { 3756 if(!$smiliecache) 3757 { 3758 if(!isset($smilie_cache) || !is_array($smilie_cache)) 3759 { 3760 $smilie_cache = $cache->read("smilies"); 3761 } 3762 foreach($smilie_cache as $smilie) 3763 { 3764 $smilie['image'] = str_replace("{theme}", $theme['imgdir'], $smilie['image']); 3765 $smiliecache[$smilie['sid']] = $smilie; 3766 } 3767 } 3768 3769 if($mybb->settings['smilieinserter'] && $mybb->settings['smilieinsertercols'] && $mybb->settings['smilieinsertertot'] && !empty($smiliecache)) 3770 { 3771 $emoticon = ",emoticon"; 3772 } 3773 $emoticons_enabled = "true"; 3774 3775 unset($smilie); 3776 3777 if(is_array($smiliecache)) 3778 { 3779 reset($smiliecache); 3780 3781 $dropdownsmilies = $moresmilies = $hiddensmilies = ""; 3782 $i = 0; 3783 3784 foreach($smiliecache as $smilie) 3785 { 3786 $finds = explode("\n", $smilie['find']); 3787 $finds_count = count($finds); 3788 3789 // Only show the first text to replace in the box 3790 $smilie['find'] = $finds[0]; 3791 3792 $find = str_replace(array('\\', '"'), array('\\\\', '\"'), htmlspecialchars_uni($smilie['find'])); 3793 $image = htmlspecialchars_uni($mybb->get_asset_url($smilie['image'])); 3794 $image = str_replace(array('\\', '"'), array('\\\\', '\"'), $image); 3795 3796 if(!$mybb->settings['smilieinserter'] || !$mybb->settings['smilieinsertercols'] || !$mybb->settings['smilieinsertertot'] || !$smilie['showclickable']) 3797 { 3798 $hiddensmilies .= '"'.$find.'": "'.$image.'",'; 3799 } 3800 elseif($i < $mybb->settings['smilieinsertertot']) 3801 { 3802 $dropdownsmilies .= '"'.$find.'": "'.$image.'",'; 3803 ++$i; 3804 } 3805 else 3806 { 3807 $moresmilies .= '"'.$find.'": "'.$image.'",'; 3808 } 3809 3810 for($j = 1; $j < $finds_count; ++$j) 3811 { 3812 $find = str_replace(array('\\', '"'), array('\\\\', '\"'), htmlspecialchars_uni($finds[$j])); 3813 $hiddensmilies .= '"'.$find.'": "'.$image.'",'; 3814 } 3815 } 3816 } 3817 } 3818 3819 $basic1 = $basic2 = $align = $font = $size = $color = $removeformat = $email = $link = $list = $code = $sourcemode = ""; 3820 3821 if($mybb->settings['allowbasicmycode'] == 1) 3822 { 3823 $basic1 = "bold,italic,underline,strike|"; 3824 $basic2 = "horizontalrule,"; 3825 } 3826 3827 if($mybb->settings['allowalignmycode'] == 1) 3828 { 3829 $align = "left,center,right,justify|"; 3830 } 3831 3832 if($mybb->settings['allowfontmycode'] == 1) 3833 { 3834 $font = "font,"; 3835 } 3836 3837 if($mybb->settings['allowsizemycode'] == 1) 3838 { 3839 $size = "size,"; 3840 } 3841 3842 if($mybb->settings['allowcolormycode'] == 1) 3843 { 3844 $color = "color,"; 3845 } 3846 3847 if($mybb->settings['allowfontmycode'] == 1 || $mybb->settings['allowsizemycode'] == 1 || $mybb->settings['allowcolormycode'] == 1) 3848 { 3849 $removeformat = "removeformat|"; 3850 } 3851 3852 if($mybb->settings['allowemailmycode'] == 1) 3853 { 3854 $email = "email,"; 3855 } 3856 3857 if($mybb->settings['allowlinkmycode'] == 1) 3858 { 3859 $link = "link,unlink"; 3860 } 3861 3862 if($mybb->settings['allowlistmycode'] == 1) 3863 { 3864 $list = "bulletlist,orderedlist|"; 3865 } 3866 3867 if($mybb->settings['allowcodemycode'] == 1) 3868 { 3869 $code = "code,php,"; 3870 } 3871 3872 if($mybb->user['sourceeditor'] == 1) 3873 { 3874 $sourcemode = "MyBBEditor.sourceMode(true);"; 3875 } 3876 3877 eval("\$codeinsert = \"".$templates->get("codebuttons")."\";"); 3878 } 3879 } 3880 3881 return $codeinsert; 3882 } 3883 3884 /** 3885 * @param int $tid 3886 * @param array $postoptions The options carried with form submit 3887 * 3888 * @return string Predefined / updated subscription method of the thread for the user 3889 */ 3890 function get_subscription_method($tid = 0, $postoptions = array()) 3891 { 3892 global $mybb; 3893 3894 $subscription_methods = array('', 'none', 'email', 'pm'); // Define methods 3895 $subscription_method = (int)$mybb->user['subscriptionmethod']; // Set user default 3896 3897 // If no user default method available then reset method 3898 if(!$subscription_method) 3899 { 3900 $subscription_method = 0; 3901 } 3902 3903 // Return user default if no thread id available, in case 3904 if(!(int)$tid || (int)$tid <= 0) 3905 { 3906 return $subscription_methods[$subscription_method]; 3907 } 3908 3909 // If method not predefined set using data from database 3910 if(isset($postoptions['subscriptionmethod'])) 3911 { 3912 $method = trim($postoptions['subscriptionmethod']); 3913 return (in_array($method, $subscription_methods)) ? $method : $subscription_methods[0]; 3914 } 3915 else 3916 { 3917 global $db; 3918 3919 $query = $db->simple_select("threadsubscriptions", "tid, notification", "tid='".(int)$tid."' AND uid='".$mybb->user['uid']."'", array('limit' => 1)); 3920 $subscription = $db->fetch_array($query); 3921 3922 if($subscription) 3923 { 3924 $subscription_method = (int)$subscription['notification'] + 1; 3925 } 3926 } 3927 3928 return $subscription_methods[$subscription_method]; 3929 } 3930 3931 /** 3932 * Build the javascript clickable smilie inserter 3933 * 3934 * @return string The clickable smilies list 3935 */ 3936 function build_clickable_smilies() 3937 { 3938 global $cache, $smiliecache, $theme, $templates, $lang, $mybb, $smiliecount; 3939 3940 if($mybb->settings['smilieinserter'] != 0 && $mybb->settings['smilieinsertercols'] && $mybb->settings['smilieinsertertot']) 3941 { 3942 if(!$smiliecount) 3943 { 3944 $smilie_cache = $cache->read("smilies"); 3945 $smiliecount = count($smilie_cache); 3946 } 3947 3948 if(!$smiliecache) 3949 { 3950 if(!is_array($smilie_cache)) 3951 { 3952 $smilie_cache = $cache->read("smilies"); 3953 } 3954 foreach($smilie_cache as $smilie) 3955 { 3956 $smilie['image'] = str_replace("{theme}", $theme['imgdir'], $smilie['image']); 3957 $smiliecache[$smilie['sid']] = $smilie; 3958 } 3959 } 3960 3961 unset($smilie); 3962 3963 if(is_array($smiliecache)) 3964 { 3965 reset($smiliecache); 3966 3967 $getmore = ''; 3968 if($mybb->settings['smilieinsertertot'] >= $smiliecount) 3969 { 3970 $mybb->settings['smilieinsertertot'] = $smiliecount; 3971 } 3972 else if($mybb->settings['smilieinsertertot'] < $smiliecount) 3973 { 3974 $smiliecount = $mybb->settings['smilieinsertertot']; 3975 eval("\$getmore = \"".$templates->get("smilieinsert_getmore")."\";"); 3976 } 3977 3978 $smilies = $smilie_icons = ''; 3979 $counter = 0; 3980 $i = 0; 3981 3982 $extra_class = ''; 3983 foreach($smiliecache as $smilie) 3984 { 3985 if($i < $mybb->settings['smilieinsertertot'] && $smilie['showclickable'] != 0) 3986 { 3987 $smilie['image'] = str_replace("{theme}", $theme['imgdir'], $smilie['image']); 3988 $smilie['image'] = htmlspecialchars_uni($mybb->get_asset_url($smilie['image'])); 3989 $smilie['name'] = htmlspecialchars_uni($smilie['name']); 3990 3991 // Only show the first text to replace in the box 3992 $temp = explode("\n", $smilie['find']); // assign to temporary variable for php 5.3 compatibility 3993 $smilie['find'] = $temp[0]; 3994 3995 $find = str_replace(array('\\', "'"), array('\\\\', "\'"), htmlspecialchars_uni($smilie['find'])); 3996 3997 $onclick = " onclick=\"MyBBEditor.insertText(' $find ');\""; 3998 $extra_class = ' smilie_pointer'; 3999 eval('$smilie = "'.$templates->get('smilie', 1, 0).'";'); 4000 eval("\$smilie_icons .= \"".$templates->get("smilieinsert_smilie")."\";"); 4001 ++$i; 4002 ++$counter; 4003 4004 if($counter == $mybb->settings['smilieinsertercols']) 4005 { 4006 $counter = 0; 4007 eval("\$smilies .= \"".$templates->get("smilieinsert_row")."\";"); 4008 $smilie_icons = ''; 4009 } 4010 } 4011 } 4012 4013 if($counter != 0) 4014 { 4015 $colspan = $mybb->settings['smilieinsertercols'] - $counter; 4016 eval("\$smilies .= \"".$templates->get("smilieinsert_row_empty")."\";"); 4017 } 4018 4019 eval("\$clickablesmilies = \"".$templates->get("smilieinsert")."\";"); 4020 } 4021 else 4022 { 4023 $clickablesmilies = ""; 4024 } 4025 } 4026 else 4027 { 4028 $clickablesmilies = ""; 4029 } 4030 4031 return $clickablesmilies; 4032 } 4033 4034 /** 4035 * Builds thread prefixes and returns a selected prefix (or all) 4036 * 4037 * @param int $pid The prefix ID (0 to return all) 4038 * @return array The thread prefix's values (or all thread prefixes) 4039 */ 4040 function build_prefixes($pid=0) 4041 { 4042 global $cache; 4043 static $prefixes_cache; 4044 4045 if(is_array($prefixes_cache)) 4046 { 4047 if($pid > 0 && is_array($prefixes_cache[$pid])) 4048 { 4049 return $prefixes_cache[$pid]; 4050 } 4051 4052 return $prefixes_cache; 4053 } 4054 4055 $prefix_cache = $cache->read("threadprefixes"); 4056 4057 if(!is_array($prefix_cache)) 4058 { 4059 // No cache 4060 $prefix_cache = $cache->read("threadprefixes", true); 4061 4062 if(!is_array($prefix_cache)) 4063 { 4064 return array(); 4065 } 4066 } 4067 4068 $prefixes_cache = array(); 4069 foreach($prefix_cache as $prefix) 4070 { 4071 $prefixes_cache[$prefix['pid']] = $prefix; 4072 } 4073 4074 if($pid != 0 && is_array($prefixes_cache[$pid])) 4075 { 4076 return $prefixes_cache[$pid]; 4077 } 4078 else if(!empty($prefixes_cache)) 4079 { 4080 return $prefixes_cache; 4081 } 4082 4083 return false; 4084 } 4085 4086 /** 4087 * Build the thread prefix selection menu for the current user 4088 * 4089 * @param int|string $fid The forum ID (integer ID or string all) 4090 * @param int|string $selected_pid The selected prefix ID (integer ID or string any) 4091 * @param int $multiple Allow multiple prefix selection 4092 * @param int $previous_pid The previously selected prefix ID 4093 * @return string The thread prefix selection menu 4094 */ 4095 function build_prefix_select($fid, $selected_pid=0, $multiple=0, $previous_pid=0) 4096 { 4097 global $cache, $db, $lang, $mybb, $templates; 4098 4099 if($fid != 'all') 4100 { 4101 $fid = (int)$fid; 4102 } 4103 4104 $prefix_cache = build_prefixes(0); 4105 if(empty($prefix_cache)) 4106 { 4107 // We've got no prefixes to show 4108 return ''; 4109 } 4110 4111 // Go through each of our prefixes and decide which ones we can use 4112 $prefixes = array(); 4113 foreach($prefix_cache as $prefix) 4114 { 4115 if($fid != "all" && $prefix['forums'] != "-1") 4116 { 4117 // Decide whether this prefix can be used in our forum 4118 $forums = explode(",", $prefix['forums']); 4119 4120 if(!in_array($fid, $forums) && $prefix['pid'] != $previous_pid) 4121 { 4122 // This prefix is not in our forum list 4123 continue; 4124 } 4125 } 4126 4127 if(is_member($prefix['groups']) || $prefix['pid'] == $previous_pid) 4128 { 4129 // The current user can use this prefix 4130 $prefixes[$prefix['pid']] = $prefix; 4131 } 4132 } 4133 4134 if(empty($prefixes)) 4135 { 4136 return ''; 4137 } 4138 4139 $prefixselect = $prefixselect_prefix = ''; 4140 4141 if($multiple == 1) 4142 { 4143 $any_selected = ""; 4144 if($selected_pid == 'any') 4145 { 4146 $any_selected = " selected=\"selected\""; 4147 } 4148 } 4149 4150 $default_selected = ""; 4151 if(((int)$selected_pid == 0) && $selected_pid != 'any') 4152 { 4153 $default_selected = " selected=\"selected\""; 4154 } 4155 4156 foreach($prefixes as $prefix) 4157 { 4158 $selected = ""; 4159 if($prefix['pid'] == $selected_pid) 4160 { 4161 $selected = " selected=\"selected\""; 4162 } 4163 4164 $prefix['prefix'] = htmlspecialchars_uni($prefix['prefix']); 4165 eval("\$prefixselect_prefix .= \"".$templates->get("post_prefixselect_prefix")."\";"); 4166 } 4167 4168 if($multiple != 0) 4169 { 4170 eval("\$prefixselect = \"".$templates->get("post_prefixselect_multiple")."\";"); 4171 } 4172 else 4173 { 4174 eval("\$prefixselect = \"".$templates->get("post_prefixselect_single")."\";"); 4175 } 4176 4177 return $prefixselect; 4178 } 4179 4180 /** 4181 * Build the thread prefix selection menu for a forum without group permission checks 4182 * 4183 * @param int $fid The forum ID (integer ID) 4184 * @param int $selected_pid The selected prefix ID (integer ID) 4185 * @return string The thread prefix selection menu 4186 */ 4187 function build_forum_prefix_select($fid, $selected_pid=0) 4188 { 4189 global $cache, $db, $lang, $mybb, $templates; 4190 4191 $fid = (int)$fid; 4192 4193 $prefix_cache = build_prefixes(0); 4194 if(empty($prefix_cache)) 4195 { 4196 // We've got no prefixes to show 4197 return ''; 4198 } 4199 4200 // Go through each of our prefixes and decide which ones we can use 4201 $prefixes = array(); 4202 foreach($prefix_cache as $prefix) 4203 { 4204 if($prefix['forums'] != "-1") 4205 { 4206 // Decide whether this prefix can be used in our forum 4207 $forums = explode(",", $prefix['forums']); 4208 4209 if(in_array($fid, $forums)) 4210 { 4211 // This forum can use this prefix! 4212 $prefixes[$prefix['pid']] = $prefix; 4213 } 4214 } 4215 else 4216 { 4217 // This prefix is for anybody to use... 4218 $prefixes[$prefix['pid']] = $prefix; 4219 } 4220 } 4221 4222 if(empty($prefixes)) 4223 { 4224 return ''; 4225 } 4226 4227 $default_selected = array('all' => '', 'none' => '', 'any' => ''); 4228 $selected_pid = (int)$selected_pid; 4229 4230 if($selected_pid == 0) 4231 { 4232 $default_selected['all'] = ' selected="selected"'; 4233 } 4234 else if($selected_pid == -1) 4235 { 4236 $default_selected['none'] = ' selected="selected"'; 4237 } 4238 else if($selected_pid == -2) 4239 { 4240 $default_selected['any'] = ' selected="selected"'; 4241 } 4242 4243 $prefixselect_prefix = ''; 4244 foreach($prefixes as $prefix) 4245 { 4246 $selected = ''; 4247 if($prefix['pid'] == $selected_pid) 4248 { 4249 $selected = ' selected="selected"'; 4250 } 4251 4252 $prefix['prefix'] = htmlspecialchars_uni($prefix['prefix']); 4253 eval('$prefixselect_prefix .= "'.$templates->get("forumdisplay_threadlist_prefixes_prefix").'";'); 4254 } 4255 4256 eval('$prefixselect = "'.$templates->get("forumdisplay_threadlist_prefixes").'";'); 4257 return $prefixselect; 4258 } 4259 4260 /** 4261 * Gzip encodes text to a specified level 4262 * 4263 * @param string $contents The string to encode 4264 * @param int $level The level (1-9) to encode at 4265 * @return string The encoded string 4266 */ 4267 function gzip_encode($contents, $level=1) 4268 { 4269 if(function_exists("gzcompress") && function_exists("crc32") && !headers_sent() && !(ini_get('output_buffering') && my_strpos(' '.ini_get('output_handler'), 'ob_gzhandler'))) 4270 { 4271 $httpaccept_encoding = ''; 4272 4273 if(isset($_SERVER['HTTP_ACCEPT_ENCODING'])) 4274 { 4275 $httpaccept_encoding = $_SERVER['HTTP_ACCEPT_ENCODING']; 4276 } 4277 4278 if(my_strpos(" ".$httpaccept_encoding, "x-gzip")) 4279 { 4280 $encoding = "x-gzip"; 4281 } 4282 4283 if(my_strpos(" ".$httpaccept_encoding, "gzip")) 4284 { 4285 $encoding = "gzip"; 4286 } 4287 4288 if(isset($encoding)) 4289 { 4290 header("Content-Encoding: $encoding"); 4291 4292 if(function_exists("gzencode")) 4293 { 4294 $contents = gzencode($contents, $level); 4295 } 4296 else 4297 { 4298 $size = strlen($contents); 4299 $crc = crc32($contents); 4300 $gzdata = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff"; 4301 $gzdata .= my_substr(gzcompress($contents, $level), 2, -4); 4302 $gzdata .= pack("V", $crc); 4303 $gzdata .= pack("V", $size); 4304 $contents = $gzdata; 4305 } 4306 } 4307 } 4308 4309 return $contents; 4310 } 4311 4312 /** 4313 * Log the actions of a moderator. 4314 * 4315 * @param array $data The data of the moderator's action. 4316 * @param string $action The message to enter for the action the moderator performed. 4317 */ 4318 function log_moderator_action($data, $action="") 4319 { 4320 global $mybb, $db, $session; 4321 4322 $fid = 0; 4323 if(isset($data['fid'])) 4324 { 4325 $fid = (int)$data['fid']; 4326 unset($data['fid']); 4327 } 4328 4329 $tid = 0; 4330 if(isset($data['tid'])) 4331 { 4332 $tid = (int)$data['tid']; 4333 unset($data['tid']); 4334 } 4335 4336 $pid = 0; 4337 if(isset($data['pid'])) 4338 { 4339 $pid = (int)$data['pid']; 4340 unset($data['pid']); 4341 } 4342 4343 $tids = array(); 4344 if(isset($data['tids'])) 4345 { 4346 $tids = (array)$data['tids']; 4347 unset($data['tids']); 4348 } 4349 4350 // Any remaining extra data - we my_serialize and insert in to its own column 4351 if(is_array($data)) 4352 { 4353 $data = my_serialize($data); 4354 } 4355 4356 $sql_array = array( 4357 "uid" => (int)$mybb->user['uid'], 4358 "dateline" => TIME_NOW, 4359 "fid" => (int)$fid, 4360 "tid" => $tid, 4361 "pid" => $pid, 4362 "action" => $db->escape_string($action), 4363 "data" => $db->escape_string($data), 4364 "ipaddress" => $db->escape_binary($session->packedip) 4365 ); 4366 4367 if($tids) 4368 { 4369 $multiple_sql_array = array(); 4370 4371 foreach($tids as $tid) 4372 { 4373 $sql_array['tid'] = (int)$tid; 4374 $multiple_sql_array[] = $sql_array; 4375 } 4376 4377 $db->insert_query_multiple("moderatorlog", $multiple_sql_array); 4378 } 4379 else 4380 { 4381 $db->insert_query("moderatorlog", $sql_array); 4382 } 4383 } 4384 4385 /** 4386 * Get the formatted reputation for a user. 4387 * 4388 * @param int $reputation The reputation value 4389 * @param int $uid The user ID (if not specified, the generated reputation will not be a link) 4390 * @return string The formatted repuation 4391 */ 4392 function get_reputation($reputation, $uid=0) 4393 { 4394 global $theme, $templates; 4395 4396 $display_reputation = $reputation_class = ''; 4397 if($reputation < 0) 4398 { 4399 $reputation_class = "reputation_negative"; 4400 } 4401 elseif($reputation > 0) 4402 { 4403 $reputation_class = "reputation_positive"; 4404 } 4405 else 4406 { 4407 $reputation_class = "reputation_neutral"; 4408 } 4409 4410 $reputation = my_number_format($reputation); 4411 4412 if($uid != 0) 4413 { 4414 eval("\$display_reputation = \"".$templates->get("postbit_reputation_formatted_link")."\";"); 4415 } 4416 else 4417 { 4418 eval("\$display_reputation = \"".$templates->get("postbit_reputation_formatted")."\";"); 4419 } 4420 4421 return $display_reputation; 4422 } 4423 4424 /** 4425 * Fetch a color coded version of a warning level (based on it's percentage) 4426 * 4427 * @param int $level The warning level (percentage of 100) 4428 * @return string Formatted warning level 4429 */ 4430 function get_colored_warning_level($level) 4431 { 4432 global $templates; 4433 4434 $warning_class = ''; 4435 if($level >= 80) 4436 { 4437 $warning_class = "high_warning"; 4438 } 4439 else if($level >= 50) 4440 { 4441 $warning_class = "moderate_warning"; 4442 } 4443 else if($level >= 25) 4444 { 4445 $warning_class = "low_warning"; 4446 } 4447 else 4448 { 4449 $warning_class = "normal_warning"; 4450 } 4451 4452 eval("\$level = \"".$templates->get("postbit_warninglevel_formatted")."\";"); 4453 return $level; 4454 } 4455 4456 /** 4457 * Fetch the IP address of the current user. 4458 * 4459 * @return string The IP address. 4460 */ 4461 function get_ip() 4462 { 4463 global $mybb, $plugins; 4464 4465 $ip = strtolower($_SERVER['REMOTE_ADDR']); 4466 4467 if($mybb->settings['ip_forwarded_check']) 4468 { 4469 $addresses = array(); 4470 4471 if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])) 4472 { 4473 $addresses = explode(',', strtolower($_SERVER['HTTP_X_FORWARDED_FOR'])); 4474 } 4475 elseif(isset($_SERVER['HTTP_X_REAL_IP'])) 4476 { 4477 $addresses = explode(',', strtolower($_SERVER['HTTP_X_REAL_IP'])); 4478 } 4479 4480 if(is_array($addresses)) 4481 { 4482 foreach($addresses as $val) 4483 { 4484 $val = trim($val); 4485 // Validate IP address and exclude private addresses 4486 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)) 4487 { 4488 $ip = $val; 4489 break; 4490 } 4491 } 4492 } 4493 } 4494 4495 if(!$ip) 4496 { 4497 if(isset($_SERVER['HTTP_CLIENT_IP'])) 4498 { 4499 $ip = strtolower($_SERVER['HTTP_CLIENT_IP']); 4500 } 4501 } 4502 4503 if($plugins) 4504 { 4505 $ip_array = array("ip" => &$ip); // Used for backwards compatibility on this hook with the updated run_hooks() function. 4506 $plugins->run_hooks("get_ip", $ip_array); 4507 } 4508 4509 return $ip; 4510 } 4511 4512 /** 4513 * Fetch the friendly size (GB, MB, KB, B) for a specified file size. 4514 * 4515 * @param int $size The size in bytes 4516 * @return string The friendly file size 4517 */ 4518 function get_friendly_size($size) 4519 { 4520 global $lang; 4521 4522 if(!is_numeric($size)) 4523 { 4524 return $lang->na; 4525 } 4526 4527 // Yottabyte (1024 Zettabytes) 4528 if($size >= 1208925819614629174706176) 4529 { 4530 $size = my_number_format(round(($size / 1208925819614629174706176), 2))." ".$lang->size_yb; 4531 } 4532 // Zetabyte (1024 Exabytes) 4533 elseif($size >= 1180591620717411303424) 4534 { 4535 $size = my_number_format(round(($size / 1180591620717411303424), 2))." ".$lang->size_zb; 4536 } 4537 // Exabyte (1024 Petabytes) 4538 elseif($size >= 1152921504606846976) 4539 { 4540 $size = my_number_format(round(($size / 1152921504606846976), 2))." ".$lang->size_eb; 4541 } 4542 // Petabyte (1024 Terabytes) 4543 elseif($size >= 1125899906842624) 4544 { 4545 $size = my_number_format(round(($size / 1125899906842624), 2))." ".$lang->size_pb; 4546 } 4547 // Terabyte (1024 Gigabytes) 4548 elseif($size >= 1099511627776) 4549 { 4550 $size = my_number_format(round(($size / 1099511627776), 2))." ".$lang->size_tb; 4551 } 4552 // Gigabyte (1024 Megabytes) 4553 elseif($size >= 1073741824) 4554 { 4555 $size = my_number_format(round(($size / 1073741824), 2))." ".$lang->size_gb; 4556 } 4557 // Megabyte (1024 Kilobytes) 4558 elseif($size >= 1048576) 4559 { 4560 $size = my_number_format(round(($size / 1048576), 2))." ".$lang->size_mb; 4561 } 4562 // Kilobyte (1024 bytes) 4563 elseif($size >= 1024) 4564 { 4565 $size = my_number_format(round(($size / 1024), 2))." ".$lang->size_kb; 4566 } 4567 elseif($size == 0) 4568 { 4569 $size = "0 ".$lang->size_bytes; 4570 } 4571 else 4572 { 4573 $size = my_number_format($size)." ".$lang->size_bytes; 4574 } 4575 4576 return $size; 4577 } 4578 4579 /** 4580 * Format a decimal number in to microseconds, milliseconds, or seconds. 4581 * 4582 * @param int $time The time in microseconds 4583 * @return string The friendly time duration 4584 */ 4585 function format_time_duration($time) 4586 { 4587 global $lang; 4588 4589 if(!is_numeric($time)) 4590 { 4591 return $lang->na; 4592 } 4593 4594 if(round(1000000 * $time, 2) < 1000) 4595 { 4596 $time = number_format(round(1000000 * $time, 2))." μs"; 4597 } 4598 elseif(round(1000000 * $time, 2) >= 1000 && round(1000000 * $time, 2) < 1000000) 4599 { 4600 $time = number_format(round((1000 * $time), 2))." ms"; 4601 } 4602 else 4603 { 4604 $time = round($time, 3)." seconds"; 4605 } 4606 4607 return $time; 4608 } 4609 4610 /** 4611 * Get the attachment icon for a specific file extension 4612 * 4613 * @param string $ext The file extension 4614 * @return string The attachment icon 4615 */ 4616 function get_attachment_icon($ext) 4617 { 4618 global $cache, $attachtypes, $theme, $templates, $lang, $mybb; 4619 4620 if(!$attachtypes) 4621 { 4622 $attachtypes = $cache->read("attachtypes"); 4623 } 4624 4625 $ext = my_strtolower($ext); 4626 4627 if($attachtypes[$ext]['icon']) 4628 { 4629 static $attach_icons_schemes = array(); 4630 if(!isset($attach_icons_schemes[$ext])) 4631 { 4632 $attach_icons_schemes[$ext] = parse_url($attachtypes[$ext]['icon']); 4633 if(!empty($attach_icons_schemes[$ext]['scheme'])) 4634 { 4635 $attach_icons_schemes[$ext] = $attachtypes[$ext]['icon']; 4636 } 4637 elseif(defined("IN_ADMINCP")) 4638 { 4639 $attach_icons_schemes[