[ Index ] |
PHP Cross Reference of MyBB 1.8.36 |
[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 $groups = explode(",", $gid); 1698 1699 $current_permissions = array(); 1700 $only_view_own_threads = 1; 1701 $only_reply_own_threads = 1; 1702 1703 if(empty($fpermcache[$fid])) // This forum has no custom or inherited permissions so lets just return the group permissions 1704 { 1705 $current_permissions = $groupperms; 1706 } 1707 else 1708 { 1709 foreach($groups as $gid) 1710 { 1711 // If this forum has custom or inherited permissions for the currently looped group. 1712 if(!empty($fpermcache[$fid][$gid])) 1713 { 1714 $level_permissions = $fpermcache[$fid][$gid]; 1715 } 1716 // Or, use the group permission instead, if available. Some forum permissions not existing here will be added back later. 1717 else if(!empty($groupscache[$gid])) 1718 { 1719 $level_permissions = $groupscache[$gid]; 1720 } 1721 // No permission is available for the currently looped group, probably we have bad data here. 1722 else 1723 { 1724 continue; 1725 } 1726 1727 foreach($level_permissions as $permission => $access) 1728 { 1729 if(empty($current_permissions[$permission]) || $access >= $current_permissions[$permission] || ($access == "yes" && $current_permissions[$permission] == "no")) 1730 { 1731 $current_permissions[$permission] = $access; 1732 } 1733 } 1734 1735 if($level_permissions["canview"] && empty($level_permissions["canonlyviewownthreads"])) 1736 { 1737 $only_view_own_threads = 0; 1738 } 1739 1740 if($level_permissions["canpostreplys"] && empty($level_permissions["canonlyreplyownthreads"])) 1741 { 1742 $only_reply_own_threads = 0; 1743 } 1744 } 1745 1746 if(count($current_permissions) == 0) 1747 { 1748 $current_permissions = $groupperms; 1749 } 1750 } 1751 1752 // Figure out if we can view more than our own threads 1753 if($only_view_own_threads == 0 || !isset($current_permissions["canonlyviewownthreads"])) 1754 { 1755 $current_permissions["canonlyviewownthreads"] = 0; 1756 } 1757 1758 // Figure out if we can reply more than our own threads 1759 if($only_reply_own_threads == 0 || !isset($current_permissions["canonlyreplyownthreads"])) 1760 { 1761 $current_permissions["canonlyreplyownthreads"] = 0; 1762 } 1763 1764 return $current_permissions; 1765 } 1766 1767 /** 1768 * Check whether password for given forum was validated for the current user 1769 * 1770 * @param array $forum The forum data 1771 * @param bool $ignore_empty Whether to treat forum password configured as an empty string as validated 1772 * @param bool $check_parents Whether to check parent forums using `parentlist` 1773 * @return bool 1774 */ 1775 function forum_password_validated($forum, $ignore_empty=false, $check_parents=false) 1776 { 1777 global $mybb, $forum_cache; 1778 1779 if($check_parents && isset($forum['parentlist'])) 1780 { 1781 if(!is_array($forum_cache)) 1782 { 1783 $forum_cache = cache_forums(); 1784 if(!$forum_cache) 1785 { 1786 return false; 1787 } 1788 } 1789 1790 $parents = explode(',', $forum['parentlist']); 1791 rsort($parents); 1792 1793 foreach($parents as $parent_id) 1794 { 1795 if($parent_id != $forum['fid'] && !forum_password_validated($forum_cache[$parent_id], true)) 1796 { 1797 return false; 1798 } 1799 } 1800 } 1801 1802 return ($ignore_empty && $forum['password'] === '') || ( 1803 isset($mybb->cookies['forumpass'][$forum['fid']]) && 1804 my_hash_equals( 1805 md5($mybb->user['uid'].$forum['password']), 1806 $mybb->cookies['forumpass'][$forum['fid']] 1807 ) 1808 ); 1809 } 1810 1811 /** 1812 * Check the password given on a certain forum for validity 1813 * 1814 * @param int $fid The forum ID 1815 * @param int $pid The Parent ID 1816 * @param bool $return 1817 * @return bool 1818 */ 1819 function check_forum_password($fid, $pid=0, $return=false) 1820 { 1821 global $mybb, $header, $footer, $headerinclude, $theme, $templates, $lang, $forum_cache; 1822 1823 $showform = true; 1824 1825 if(!is_array($forum_cache)) 1826 { 1827 $forum_cache = cache_forums(); 1828 if(!$forum_cache) 1829 { 1830 return false; 1831 } 1832 } 1833 1834 // Loop through each of parent forums to ensure we have a password for them too 1835 if(isset($forum_cache[$fid]['parentlist'])) 1836 { 1837 $parents = explode(',', $forum_cache[$fid]['parentlist']); 1838 rsort($parents); 1839 } 1840 if(!empty($parents)) 1841 { 1842 foreach($parents as $parent_id) 1843 { 1844 if($parent_id == $fid || $parent_id == $pid) 1845 { 1846 continue; 1847 } 1848 1849 if($forum_cache[$parent_id]['password'] !== "") 1850 { 1851 check_forum_password($parent_id, $fid); 1852 } 1853 } 1854 } 1855 1856 if($forum_cache[$fid]['password'] !== '') 1857 { 1858 if(isset($mybb->input['pwverify']) && $pid == 0) 1859 { 1860 if(my_hash_equals($forum_cache[$fid]['password'], $mybb->get_input('pwverify'))) 1861 { 1862 my_setcookie("forumpass[$fid]", md5($mybb->user['uid'].$mybb->get_input('pwverify')), null, true); 1863 $showform = false; 1864 } 1865 else 1866 { 1867 eval("\$pwnote = \"".$templates->get("forumdisplay_password_wrongpass")."\";"); 1868 $showform = true; 1869 } 1870 } 1871 else 1872 { 1873 if(!forum_password_validated($forum_cache[$fid])) 1874 { 1875 $showform = true; 1876 } 1877 else 1878 { 1879 $showform = false; 1880 } 1881 } 1882 } 1883 else 1884 { 1885 $showform = false; 1886 } 1887 1888 if($return) 1889 { 1890 return $showform; 1891 } 1892 1893 if($showform) 1894 { 1895 if($pid) 1896 { 1897 header("Location: ".$mybb->settings['bburl']."/".get_forum_link($fid)); 1898 } 1899 else 1900 { 1901 $_SERVER['REQUEST_URI'] = htmlspecialchars_uni($_SERVER['REQUEST_URI']); 1902 eval("\$pwform = \"".$templates->get("forumdisplay_password")."\";"); 1903 output_page($pwform); 1904 } 1905 exit; 1906 } 1907 } 1908 1909 /** 1910 * Return the permissions for a moderator in a specific forum 1911 * 1912 * @param int $fid The forum ID 1913 * @param int $uid The user ID to fetch permissions for (0 assumes current logged in user) 1914 * @param string $parentslist The parent list for the forum (if blank, will be fetched) 1915 * @return array Array of moderator permissions for the specific forum 1916 */ 1917 function get_moderator_permissions($fid, $uid=0, $parentslist="") 1918 { 1919 global $mybb, $cache, $db; 1920 static $modpermscache; 1921 1922 if($uid < 1) 1923 { 1924 $uid = $mybb->user['uid']; 1925 } 1926 1927 if($uid == 0) 1928 { 1929 return false; 1930 } 1931 1932 if(isset($modpermscache[$fid][$uid])) 1933 { 1934 return $modpermscache[$fid][$uid]; 1935 } 1936 1937 if(!$parentslist) 1938 { 1939 $parentslist = explode(',', get_parent_list($fid)); 1940 } 1941 1942 // Get user groups 1943 $perms = array(); 1944 $user = get_user($uid); 1945 1946 $groups = array($user['usergroup']); 1947 1948 if(!empty($user['additionalgroups'])) 1949 { 1950 $extra_groups = explode(",", $user['additionalgroups']); 1951 1952 foreach($extra_groups as $extra_group) 1953 { 1954 $groups[] = $extra_group; 1955 } 1956 } 1957 1958 $mod_cache = $cache->read("moderators"); 1959 1960 foreach($mod_cache as $forumid => $forum) 1961 { 1962 if(empty($forum) || !is_array($forum) || !in_array($forumid, $parentslist)) 1963 { 1964 // No perms or we're not after this forum 1965 continue; 1966 } 1967 1968 // User settings override usergroup settings 1969 if(!empty($forum['users'][$uid])) 1970 { 1971 $perm = $forum['users'][$uid]; 1972 foreach($perm as $action => $value) 1973 { 1974 if(strpos($action, "can") === false) 1975 { 1976 continue; 1977 } 1978 1979 if(!isset($perms[$action])) 1980 { 1981 $perms[$action] = $value; 1982 } 1983 // Figure out the user permissions 1984 else if($value == 0) 1985 { 1986 // The user doesn't have permission to set this action 1987 $perms[$action] = 0; 1988 } 1989 else 1990 { 1991 $perms[$action] = max($perm[$action], $perms[$action]); 1992 } 1993 } 1994 } 1995 1996 foreach($groups as $group) 1997 { 1998 if(empty($forum['usergroups'][$group]) || !is_array($forum['usergroups'][$group])) 1999 { 2000 // There are no permissions set for this group 2001 continue; 2002 } 2003 2004 $perm = $forum['usergroups'][$group]; 2005 foreach($perm as $action => $value) 2006 { 2007 if(strpos($action, "can") === false) 2008 { 2009 continue; 2010 } 2011 2012 if(!isset($perms[$action])) 2013 { 2014 $perms[$action] = $value; 2015 } 2016 else 2017 { 2018 $perms[$action] = max($perm[$action], $perms[$action]); 2019 } 2020 } 2021 } 2022 } 2023 2024 $modpermscache[$fid][$uid] = $perms; 2025 2026 return $perms; 2027 } 2028 2029 /** 2030 * Checks if a moderator has permissions to perform an action in a specific forum 2031 * 2032 * @param int $fid The forum ID (0 assumes global) 2033 * @param string $action The action tyring to be performed. (blank assumes any action at all) 2034 * @param int $uid The user ID (0 assumes current user) 2035 * @return bool Returns true if the user has permission, false if they do not 2036 */ 2037 function is_moderator($fid=0, $action="", $uid=0) 2038 { 2039 global $mybb, $cache, $plugins; 2040 2041 if($uid == 0) 2042 { 2043 $uid = $mybb->user['uid']; 2044 } 2045 2046 if($uid == 0) 2047 { 2048 return false; 2049 } 2050 2051 $user_perms = user_permissions($uid); 2052 2053 $hook_args = array( 2054 'fid' => $fid, 2055 'action' => $action, 2056 'uid' => $uid, 2057 ); 2058 2059 $plugins->run_hooks("is_moderator", $hook_args); 2060 2061 if(isset($hook_args['is_moderator'])) 2062 { 2063 return (boolean) $hook_args['is_moderator']; 2064 } 2065 2066 if(!empty($user_perms['issupermod']) && $user_perms['issupermod'] == 1) 2067 { 2068 if($fid) 2069 { 2070 $forumpermissions = forum_permissions($fid); 2071 if(!empty($forumpermissions['canview']) && !empty($forumpermissions['canviewthreads']) && empty($forumpermissions['canonlyviewownthreads'])) 2072 { 2073 return true; 2074 } 2075 return false; 2076 } 2077 return true; 2078 } 2079 else 2080 { 2081 if(!$fid) 2082 { 2083 $modcache = $cache->read('moderators'); 2084 if(!empty($modcache)) 2085 { 2086 foreach($modcache as $modusers) 2087 { 2088 if(isset($modusers['users'][$uid]) && $modusers['users'][$uid]['mid'] && (!$action || !empty($modusers['users'][$uid][$action]))) 2089 { 2090 return true; 2091 } 2092 2093 $groups = explode(',', $user_perms['all_usergroups']); 2094 2095 foreach($groups as $group) 2096 { 2097 if(trim($group) != '' && isset($modusers['usergroups'][$group]) && (!$action || !empty($modusers['usergroups'][$group][$action]))) 2098 { 2099 return true; 2100 } 2101 } 2102 } 2103 } 2104 return false; 2105 } 2106 else 2107 { 2108 $modperms = get_moderator_permissions($fid, $uid); 2109 2110 if(!$action && $modperms) 2111 { 2112 return true; 2113 } 2114 else 2115 { 2116 if(isset($modperms[$action]) && $modperms[$action] == 1) 2117 { 2118 return true; 2119 } 2120 else 2121 { 2122 return false; 2123 } 2124 } 2125 } 2126 } 2127 } 2128 2129 /** 2130 * Get an array of fids that the forum moderator has access to. 2131 * Do not use for administraotrs or global moderators as they moderate any forum and the function will return false. 2132 * 2133 * @param int $uid The user ID (0 assumes current user) 2134 * @return array|bool an array of the fids the user has moderator access to or bool if called incorrectly. 2135 */ 2136 function get_moderated_fids($uid=0) 2137 { 2138 global $mybb, $cache; 2139 2140 if($uid == 0) 2141 { 2142 $uid = $mybb->user['uid']; 2143 } 2144 2145 if($uid == 0) 2146 { 2147 return array(); 2148 } 2149 2150 $user_perms = user_permissions($uid); 2151 2152 if($user_perms['issupermod'] == 1) 2153 { 2154 return false; 2155 } 2156 2157 $fids = array(); 2158 2159 $modcache = $cache->read('moderators'); 2160 if(!empty($modcache)) 2161 { 2162 $groups = explode(',', $user_perms['all_usergroups']); 2163 2164 foreach($modcache as $fid => $forum) 2165 { 2166 if(isset($forum['users'][$uid]) && $forum['users'][$uid]['mid']) 2167 { 2168 $fids[] = $fid; 2169 continue; 2170 } 2171 2172 foreach($groups as $group) 2173 { 2174 if(trim($group) != '' && isset($forum['usergroups'][$group])) 2175 { 2176 $fids[] = $fid; 2177 } 2178 } 2179 } 2180 } 2181 2182 return $fids; 2183 } 2184 2185 /** 2186 * Generate a list of the posticons. 2187 * 2188 * @return string The template of posticons. 2189 */ 2190 function get_post_icons() 2191 { 2192 global $mybb, $cache, $icon, $theme, $templates, $lang; 2193 2194 if(isset($mybb->input['icon'])) 2195 { 2196 $icon = $mybb->get_input('icon'); 2197 } 2198 2199 $iconlist = ''; 2200 $no_icons_checked = " checked=\"checked\""; 2201 // read post icons from cache, and sort them accordingly 2202 $posticons_cache = (array)$cache->read("posticons"); 2203 $posticons = array(); 2204 foreach($posticons_cache as $posticon) 2205 { 2206 $posticons[$posticon['name']] = $posticon; 2207 } 2208 krsort($posticons); 2209 2210 foreach($posticons as $dbicon) 2211 { 2212 $dbicon['path'] = str_replace("{theme}", $theme['imgdir'], $dbicon['path']); 2213 $dbicon['path'] = htmlspecialchars_uni($mybb->get_asset_url($dbicon['path'])); 2214 $dbicon['name'] = htmlspecialchars_uni($dbicon['name']); 2215 2216 if($icon == $dbicon['iid']) 2217 { 2218 $checked = " checked=\"checked\""; 2219 $no_icons_checked = ''; 2220 } 2221 else 2222 { 2223 $checked = ''; 2224 } 2225 2226 eval("\$iconlist .= \"".$templates->get("posticons_icon")."\";"); 2227 } 2228 2229 if(!empty($iconlist)) 2230 { 2231 eval("\$posticons = \"".$templates->get("posticons")."\";"); 2232 } 2233 else 2234 { 2235 $posticons = ''; 2236 } 2237 2238 return $posticons; 2239 } 2240 2241 /** 2242 * MyBB setcookie() wrapper. 2243 * 2244 * @param string $name The cookie identifier. 2245 * @param string $value The cookie value. 2246 * @param int|string $expires The timestamp of the expiry date. 2247 * @param boolean $httponly True if setting a HttpOnly cookie (supported by the majority of web browsers) 2248 * @param string $samesite The samesite attribute to prevent CSRF. 2249 */ 2250 function my_setcookie($name, $value="", $expires="", $httponly=false, $samesite="") 2251 { 2252 global $mybb; 2253 2254 if(!$mybb->settings['cookiepath']) 2255 { 2256 $mybb->settings['cookiepath'] = "/"; 2257 } 2258 2259 if($expires == -1) 2260 { 2261 $expires = 0; 2262 } 2263 elseif($expires == "" || $expires == null) 2264 { 2265 $expires = TIME_NOW + (60*60*24*365); // Make the cookie expire in a years time 2266 } 2267 else 2268 { 2269 $expires = TIME_NOW + (int)$expires; 2270 } 2271 2272 $mybb->settings['cookiepath'] = str_replace(array("\n","\r"), "", $mybb->settings['cookiepath']); 2273 $mybb->settings['cookiedomain'] = str_replace(array("\n","\r"), "", $mybb->settings['cookiedomain']); 2274 $mybb->settings['cookieprefix'] = str_replace(array("\n","\r", " "), "", $mybb->settings['cookieprefix']); 2275 2276 // 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 2277 $cookie = "Set-Cookie: {$mybb->settings['cookieprefix']}{$name}=".urlencode($value); 2278 2279 if($expires > 0) 2280 { 2281 $cookie .= "; expires=".@gmdate('D, d-M-Y H:i:s \\G\\M\\T', $expires); 2282 } 2283 2284 if(!empty($mybb->settings['cookiepath'])) 2285 { 2286 $cookie .= "; path={$mybb->settings['cookiepath']}"; 2287 } 2288 2289 if(!empty($mybb->settings['cookiedomain'])) 2290 { 2291 $cookie .= "; domain={$mybb->settings['cookiedomain']}"; 2292 } 2293 2294 if($httponly == true) 2295 { 2296 $cookie .= "; HttpOnly"; 2297 } 2298 2299 if($samesite != "" && $mybb->settings['cookiesamesiteflag']) 2300 { 2301 $samesite = strtolower($samesite); 2302 2303 if($samesite == "lax" || $samesite == "strict") 2304 { 2305 $cookie .= "; SameSite=".$samesite; 2306 } 2307 } 2308 2309 if($mybb->settings['cookiesecureflag']) 2310 { 2311 $cookie .= "; Secure"; 2312 } 2313 2314 $mybb->cookies[$name] = $value; 2315 2316 header($cookie, false); 2317 } 2318 2319 /** 2320 * Unset a cookie set by MyBB. 2321 * 2322 * @param string $name The cookie identifier. 2323 */ 2324 function my_unsetcookie($name) 2325 { 2326 global $mybb; 2327 2328 $expires = -3600; 2329 my_setcookie($name, "", $expires); 2330 2331 unset($mybb->cookies[$name]); 2332 } 2333 2334 /** 2335 * Get the contents from a serialised cookie array. 2336 * 2337 * @param string $name The cookie identifier. 2338 * @param int $id The cookie content id. 2339 * @return array|boolean The cookie id's content array or false when non-existent. 2340 */ 2341 function my_get_array_cookie($name, $id) 2342 { 2343 global $mybb; 2344 2345 if(!isset($mybb->cookies['mybb'][$name])) 2346 { 2347 return false; 2348 } 2349 2350 $cookie = my_unserialize($mybb->cookies['mybb'][$name], false); 2351 2352 if(is_array($cookie) && isset($cookie[$id])) 2353 { 2354 return $cookie[$id]; 2355 } 2356 else 2357 { 2358 return 0; 2359 } 2360 } 2361 2362 /** 2363 * Set a serialised cookie array. 2364 * 2365 * @param string $name The cookie identifier. 2366 * @param int $id The cookie content id. 2367 * @param string $value The value to set the cookie to. 2368 * @param int|string $expires The timestamp of the expiry date. 2369 */ 2370 function my_set_array_cookie($name, $id, $value, $expires="") 2371 { 2372 global $mybb; 2373 2374 if(isset($mybb->cookies['mybb'][$name])) 2375 { 2376 $newcookie = my_unserialize($mybb->cookies['mybb'][$name], false); 2377 } 2378 else 2379 { 2380 $newcookie = array(); 2381 } 2382 2383 $newcookie[$id] = $value; 2384 $newcookie = my_serialize($newcookie); 2385 my_setcookie("mybb[$name]", addslashes($newcookie), $expires); 2386 2387 if(isset($mybb->cookies['mybb']) && !is_array($mybb->cookies['mybb'])) 2388 { 2389 $mybb->cookies['mybb'] = array(); 2390 } 2391 2392 // Make sure our current viarables are up-to-date as well 2393 $mybb->cookies['mybb'][$name] = $newcookie; 2394 } 2395 2396 /* 2397 * Arbitrary limits for _safe_unserialize() 2398 */ 2399 define('MAX_SERIALIZED_INPUT_LENGTH', 10240); 2400 define('MAX_SERIALIZED_ARRAY_LENGTH', 256); 2401 define('MAX_SERIALIZED_ARRAY_DEPTH', 5); 2402 2403 /** 2404 * Credits go to https://github.com/piwik 2405 * Safe unserialize() replacement 2406 * - accepts a strict subset of PHP's native my_serialized representation 2407 * - does not unserialize objects 2408 * 2409 * @param string $str 2410 * @param bool $unlimited Whether to apply limits defined in MAX_SERIALIZED_* constants 2411 * @return mixed 2412 * @throw Exception if $str is malformed or contains unsupported types (e.g., resources, objects) 2413 */ 2414 function _safe_unserialize($str, $unlimited = true) 2415 { 2416 if(!$unlimited && strlen($str) > MAX_SERIALIZED_INPUT_LENGTH) 2417 { 2418 // input exceeds MAX_SERIALIZED_INPUT_LENGTH 2419 return false; 2420 } 2421 2422 if(empty($str) || !is_string($str)) 2423 { 2424 return false; 2425 } 2426 2427 $stack = $list = $expected = array(); 2428 2429 /* 2430 * states: 2431 * 0 - initial state, expecting a single value or array 2432 * 1 - terminal state 2433 * 2 - in array, expecting end of array or a key 2434 * 3 - in array, expecting value or another array 2435 */ 2436 $state = 0; 2437 while($state != 1) 2438 { 2439 $type = isset($str[0]) ? $str[0] : ''; 2440 2441 if($type == '}') 2442 { 2443 $str = substr($str, 1); 2444 } 2445 else if($type == 'N' && $str[1] == ';') 2446 { 2447 $value = null; 2448 $str = substr($str, 2); 2449 } 2450 else if($type == 'b' && preg_match('/^b:([01]);/', $str, $matches)) 2451 { 2452 $value = $matches[1] == '1' ? true : false; 2453 $str = substr($str, 4); 2454 } 2455 else if($type == 'i' && preg_match('/^i:(-?[0-9]+);(.*)/s', $str, $matches)) 2456 { 2457 $value = (int)$matches[1]; 2458 $str = $matches[2]; 2459 } 2460 else if($type == 'd' && preg_match('/^d:(-?[0-9]+\.?[0-9]*(E[+-][0-9]+)?);(.*)/s', $str, $matches)) 2461 { 2462 $value = (float)$matches[1]; 2463 $str = $matches[3]; 2464 } 2465 else if($type == 's' && preg_match('/^s:([0-9]+):"(.*)/s', $str, $matches) && substr($matches[2], (int)$matches[1], 2) == '";') 2466 { 2467 $value = substr($matches[2], 0, (int)$matches[1]); 2468 $str = substr($matches[2], (int)$matches[1] + 2); 2469 } 2470 else if( 2471 $type == 'a' && 2472 preg_match('/^a:([0-9]+):{(.*)/s', $str, $matches) && 2473 ($unlimited || $matches[1] < MAX_SERIALIZED_ARRAY_LENGTH) 2474 ) 2475 { 2476 $expectedLength = (int)$matches[1]; 2477 $str = $matches[2]; 2478 } 2479 else 2480 { 2481 // object or unknown/malformed type 2482 return false; 2483 } 2484 2485 switch($state) 2486 { 2487 case 3: // in array, expecting value or another array 2488 if($type == 'a') 2489 { 2490 if(!$unlimited && count($stack) >= MAX_SERIALIZED_ARRAY_DEPTH) 2491 { 2492 // array nesting exceeds MAX_SERIALIZED_ARRAY_DEPTH 2493 return false; 2494 } 2495 2496 $stack[] = &$list; 2497 $list[$key] = array(); 2498 $list = &$list[$key]; 2499 $expected[] = $expectedLength; 2500 $state = 2; 2501 break; 2502 } 2503 if($type != '}') 2504 { 2505 $list[$key] = $value; 2506 $state = 2; 2507 break; 2508 } 2509 2510 // missing array value 2511 return false; 2512 2513 case 2: // in array, expecting end of array or a key 2514 if($type == '}') 2515 { 2516 if(count($list) < end($expected)) 2517 { 2518 // array size less than expected 2519 return false; 2520 } 2521 2522 unset($list); 2523 $list = &$stack[count($stack)-1]; 2524 array_pop($stack); 2525 2526 // go to terminal state if we're at the end of the root array 2527 array_pop($expected); 2528 if(count($expected) == 0) { 2529 $state = 1; 2530 } 2531 break; 2532 } 2533 if($type == 'i' || $type == 's') 2534 { 2535 if(!$unlimited && count($list) >= MAX_SERIALIZED_ARRAY_LENGTH) 2536 { 2537 // array size exceeds MAX_SERIALIZED_ARRAY_LENGTH 2538 return false; 2539 } 2540 if(count($list) >= end($expected)) 2541 { 2542 // array size exceeds expected length 2543 return false; 2544 } 2545 2546 $key = $value; 2547 $state = 3; 2548 break; 2549 } 2550 2551 // illegal array index type 2552 return false; 2553 2554 case 0: // expecting array or value 2555 if($type == 'a') 2556 { 2557 if(!$unlimited && count($stack) >= MAX_SERIALIZED_ARRAY_DEPTH) 2558 { 2559 // array nesting exceeds MAX_SERIALIZED_ARRAY_DEPTH 2560 return false; 2561 } 2562 2563 $data = array(); 2564 $list = &$data; 2565 $expected[] = $expectedLength; 2566 $state = 2; 2567 break; 2568 } 2569 if($type != '}') 2570 { 2571 $data = $value; 2572 $state = 1; 2573 break; 2574 } 2575 2576 // not in array 2577 return false; 2578 } 2579 } 2580 2581 if(!empty($str)) 2582 { 2583 // trailing data in input 2584 return false; 2585 } 2586 return $data; 2587 } 2588 2589 /** 2590 * Credits go to https://github.com/piwik 2591 * Wrapper for _safe_unserialize() that handles exceptions and multibyte encoding issue 2592 * 2593 * @param string $str 2594 * @param bool $unlimited 2595 * @return mixed 2596 */ 2597 function my_unserialize($str, $unlimited = true) 2598 { 2599 // Ensure we use the byte count for strings even when strlen() is overloaded by mb_strlen() 2600 if(function_exists('mb_internal_encoding') && (((int)ini_get('mbstring.func_overload')) & 2)) 2601 { 2602 $mbIntEnc = mb_internal_encoding(); 2603 mb_internal_encoding('ASCII'); 2604 } 2605 2606 $out = _safe_unserialize($str, $unlimited); 2607 2608 if(isset($mbIntEnc)) 2609 { 2610 mb_internal_encoding($mbIntEnc); 2611 } 2612 2613 return $out; 2614 } 2615 2616 /** 2617 * Unserializes data using PHP's `unserialize()`, and its safety options if possible. 2618 * This function should only be used for values from trusted sources. 2619 * 2620 * @param string $str 2621 * @return mixed 2622 */ 2623 function native_unserialize($str) 2624 { 2625 if(version_compare(PHP_VERSION, '7.0.0', '>=')) 2626 { 2627 return unserialize($str, array('allowed_classes' => false)); 2628 } 2629 else 2630 { 2631 return unserialize($str); 2632 } 2633 } 2634 2635 /** 2636 * Credits go to https://github.com/piwik 2637 * Safe serialize() replacement 2638 * - output a strict subset of PHP's native serialized representation 2639 * - does not my_serialize objects 2640 * 2641 * @param mixed $value 2642 * @return string 2643 * @throw Exception if $value is malformed or contains unsupported types (e.g., resources, objects) 2644 */ 2645 function _safe_serialize( $value ) 2646 { 2647 if(is_null($value)) 2648 { 2649 return 'N;'; 2650 } 2651 2652 if(is_bool($value)) 2653 { 2654 return 'b:'.(int)$value.';'; 2655 } 2656 2657 if(is_int($value)) 2658 { 2659 return 'i:'.$value.';'; 2660 } 2661 2662 if(is_float($value)) 2663 { 2664 return 'd:'.str_replace(',', '.', $value).';'; 2665 } 2666 2667 if(is_string($value)) 2668 { 2669 return 's:'.strlen($value).':"'.$value.'";'; 2670 } 2671 2672 if(is_array($value)) 2673 { 2674 $out = ''; 2675 foreach($value as $k => $v) 2676 { 2677 $out .= _safe_serialize($k) . _safe_serialize($v); 2678 } 2679 2680 return 'a:'.count($value).':{'.$out.'}'; 2681 } 2682 2683 // safe_serialize cannot my_serialize resources or objects 2684 return false; 2685 } 2686 2687 /** 2688 * Credits go to https://github.com/piwik 2689 * Wrapper for _safe_serialize() that handles exceptions and multibyte encoding issue 2690 * 2691 * @param mixed $value 2692 * @return string 2693 */ 2694 function my_serialize($value) 2695 { 2696 // ensure we use the byte count for strings even when strlen() is overloaded by mb_strlen() 2697 if(function_exists('mb_internal_encoding') && (((int)ini_get('mbstring.func_overload')) & 2)) 2698 { 2699 $mbIntEnc = mb_internal_encoding(); 2700 mb_internal_encoding('ASCII'); 2701 } 2702 2703 $out = _safe_serialize($value); 2704 if(isset($mbIntEnc)) 2705 { 2706 mb_internal_encoding($mbIntEnc); 2707 } 2708 2709 return $out; 2710 } 2711 2712 /** 2713 * Returns the serverload of the system. 2714 * 2715 * @return int The serverload of the system. 2716 */ 2717 function get_server_load() 2718 { 2719 global $mybb, $lang; 2720 2721 $serverload = array(); 2722 2723 // DIRECTORY_SEPARATOR checks if running windows 2724 if(DIRECTORY_SEPARATOR != '\\') 2725 { 2726 if(function_exists("sys_getloadavg")) 2727 { 2728 // sys_getloadavg() will return an array with [0] being load within the last minute. 2729 $serverload = sys_getloadavg(); 2730 $serverload[0] = round($serverload[0], 4); 2731 } 2732 else if(@file_exists("/proc/loadavg") && $load = @file_get_contents("/proc/loadavg")) 2733 { 2734 $serverload = explode(" ", $load); 2735 $serverload[0] = round($serverload[0], 4); 2736 } 2737 if(!is_numeric($serverload[0])) 2738 { 2739 if($mybb->safemode) 2740 { 2741 return $lang->unknown; 2742 } 2743 2744 // Suhosin likes to throw a warning if exec is disabled then die - weird 2745 if($func_blacklist = @ini_get('suhosin.executor.func.blacklist')) 2746 { 2747 if(strpos(",".$func_blacklist.",", 'exec') !== false) 2748 { 2749 return $lang->unknown; 2750 } 2751 } 2752 // PHP disabled functions? 2753 if($func_blacklist = @ini_get('disable_functions')) 2754 { 2755 if(strpos(",".$func_blacklist.",", 'exec') !== false) 2756 { 2757 return $lang->unknown; 2758 } 2759 } 2760 2761 $load = @exec("uptime"); 2762 $load = explode("load average: ", $load); 2763 $serverload = explode(",", $load[1]); 2764 if(!is_array($serverload)) 2765 { 2766 return $lang->unknown; 2767 } 2768 } 2769 } 2770 else 2771 { 2772 return $lang->unknown; 2773 } 2774 2775 $returnload = trim($serverload[0]); 2776 2777 return $returnload; 2778 } 2779 2780 /** 2781 * Returns the amount of memory allocated to the script. 2782 * 2783 * @return int The amount of memory allocated to the script. 2784 */ 2785 function get_memory_usage() 2786 { 2787 if(function_exists('memory_get_peak_usage')) 2788 { 2789 return memory_get_peak_usage(true); 2790 } 2791 elseif(function_exists('memory_get_usage')) 2792 { 2793 return memory_get_usage(true); 2794 } 2795 return false; 2796 } 2797 2798 /** 2799 * Updates the forum statistics with specific values (or addition/subtraction of the previous value) 2800 * 2801 * @param array $changes Array of items being updated (numthreads,numposts,numusers,numunapprovedthreads,numunapprovedposts,numdeletedposts,numdeletedthreads) 2802 * @param boolean $force Force stats update? 2803 */ 2804 function update_stats($changes=array(), $force=false) 2805 { 2806 global $cache, $db; 2807 static $stats_changes; 2808 2809 if(empty($stats_changes)) 2810 { 2811 // Update stats after all changes are done 2812 add_shutdown('update_stats', array(array(), true)); 2813 } 2814 2815 if(empty($stats_changes) || $stats_changes['inserted']) 2816 { 2817 $stats_changes = array( 2818 'numthreads' => '+0', 2819 'numposts' => '+0', 2820 'numusers' => '+0', 2821 'numunapprovedthreads' => '+0', 2822 'numunapprovedposts' => '+0', 2823 'numdeletedposts' => '+0', 2824 'numdeletedthreads' => '+0', 2825 'inserted' => false // Reset after changes are inserted into cache 2826 ); 2827 $stats = $stats_changes; 2828 } 2829 2830 if($force) // Force writing to cache? 2831 { 2832 if(!empty($changes)) 2833 { 2834 // Calculate before writing to cache 2835 update_stats($changes); 2836 } 2837 $stats = $cache->read("stats"); 2838 $changes = $stats_changes; 2839 } 2840 else 2841 { 2842 $stats = $stats_changes; 2843 } 2844 2845 $new_stats = array(); 2846 $counters = array('numthreads', 'numunapprovedthreads', 'numposts', 'numunapprovedposts', 'numusers', 'numdeletedposts', 'numdeletedthreads'); 2847 foreach($counters as $counter) 2848 { 2849 if(array_key_exists($counter, $changes)) 2850 { 2851 if(substr($changes[$counter], 0, 2) == "+-") 2852 { 2853 $changes[$counter] = substr($changes[$counter], 1); 2854 } 2855 // Adding or subtracting from previous value? 2856 if(substr($changes[$counter], 0, 1) == "+" || substr($changes[$counter], 0, 1) == "-") 2857 { 2858 if((int)$changes[$counter] != 0) 2859 { 2860 $new_stats[$counter] = $stats[$counter] + $changes[$counter]; 2861 if(!$force && (substr($stats[$counter], 0, 1) == "+" || substr($stats[$counter], 0, 1) == "-")) 2862 { 2863 // We had relative values? Then it is still relative 2864 if($new_stats[$counter] >= 0) 2865 { 2866 $new_stats[$counter] = "+{$new_stats[$counter]}"; 2867 } 2868 } 2869 // Less than 0? That's bad 2870 elseif($new_stats[$counter] < 0) 2871 { 2872 $new_stats[$counter] = 0; 2873 } 2874 } 2875 } 2876 else 2877 { 2878 $new_stats[$counter] = $changes[$counter]; 2879 // Less than 0? That's bad 2880 if($new_stats[$counter] < 0) 2881 { 2882 $new_stats[$counter] = 0; 2883 } 2884 } 2885 } 2886 } 2887 2888 if(!$force) 2889 { 2890 $stats_changes = array_merge($stats, $new_stats); // Overwrite changed values 2891 return; 2892 } 2893 2894 // Fetch latest user if the user count is changing 2895 if(array_key_exists('numusers', $changes)) 2896 { 2897 $query = $db->simple_select("users", "uid, username", "", array('order_by' => 'regdate', 'order_dir' => 'DESC', 'limit' => 1)); 2898 $lastmember = $db->fetch_array($query); 2899 $new_stats['lastuid'] = $lastmember['uid']; 2900 $new_stats['lastusername'] = $lastmember['username'] = htmlspecialchars_uni($lastmember['username']); 2901 } 2902 2903 if(!empty($new_stats)) 2904 { 2905 if(is_array($stats)) 2906 { 2907 $stats = array_merge($stats, $new_stats); // Overwrite changed values 2908 } 2909 else 2910 { 2911 $stats = $new_stats; 2912 } 2913 } 2914 2915 // Update stats row for today in the database 2916 $todays_stats = array( 2917 "dateline" => mktime(0, 0, 0, date("m"), date("j"), date("Y")), 2918 "numusers" => (int)$stats['numusers'], 2919 "numthreads" => (int)$stats['numthreads'], 2920 "numposts" => (int)$stats['numposts'] 2921 ); 2922 $db->replace_query("stats", $todays_stats, "dateline"); 2923 2924 $cache->update("stats", $stats, "dateline"); 2925 $stats_changes['inserted'] = true; 2926 } 2927 2928 /** 2929 * Updates the forum counters with a specific value (or addition/subtraction of the previous value) 2930 * 2931 * @param int $fid The forum ID 2932 * @param array $changes Array of items being updated (threads, posts, unapprovedthreads, unapprovedposts, deletedposts, deletedthreads) and their value (ex, 1, +1, -1) 2933 */ 2934 function update_forum_counters($fid, $changes=array()) 2935 { 2936 global $db; 2937 2938 $update_query = array(); 2939 2940 $counters = array('threads', 'unapprovedthreads', 'posts', 'unapprovedposts', 'deletedposts', 'deletedthreads'); 2941 2942 // Fetch above counters for this forum 2943 $query = $db->simple_select("forums", implode(",", $counters), "fid='{$fid}'"); 2944 $forum = $db->fetch_array($query); 2945 2946 foreach($counters as $counter) 2947 { 2948 if(array_key_exists($counter, $changes)) 2949 { 2950 if(substr($changes[$counter], 0, 2) == "+-") 2951 { 2952 $changes[$counter] = substr($changes[$counter], 1); 2953 } 2954 // Adding or subtracting from previous value? 2955 if(substr($changes[$counter], 0, 1) == "+" || substr($changes[$counter], 0, 1) == "-") 2956 { 2957 if((int)$changes[$counter] != 0) 2958 { 2959 $update_query[$counter] = $forum[$counter] + $changes[$counter]; 2960 } 2961 } 2962 else 2963 { 2964 $update_query[$counter] = $changes[$counter]; 2965 } 2966 2967 // Less than 0? That's bad 2968 if(isset($update_query[$counter]) && $update_query[$counter] < 0) 2969 { 2970 $update_query[$counter] = 0; 2971 } 2972 } 2973 } 2974 2975 // Only update if we're actually doing something 2976 if(count($update_query) > 0) 2977 { 2978 $db->update_query("forums", $update_query, "fid='".(int)$fid."'"); 2979 } 2980 2981 // Guess we should update the statistics too? 2982 $new_stats = array(); 2983 if(array_key_exists('threads', $update_query)) 2984 { 2985 $threads_diff = $update_query['threads'] - $forum['threads']; 2986 if($threads_diff > -1) 2987 { 2988 $new_stats['numthreads'] = "+{$threads_diff}"; 2989 } 2990 else 2991 { 2992 $new_stats['numthreads'] = "{$threads_diff}"; 2993 } 2994 } 2995 2996 if(array_key_exists('unapprovedthreads', $update_query)) 2997 { 2998 $unapprovedthreads_diff = $update_query['unapprovedthreads'] - $forum['unapprovedthreads']; 2999 if($unapprovedthreads_diff > -1) 3000 { 3001 $new_stats['numunapprovedthreads'] = "+{$unapprovedthreads_diff}"; 3002 } 3003 else 3004 { 3005 $new_stats['numunapprovedthreads'] = "{$unapprovedthreads_diff}"; 3006 } 3007 } 3008 3009 if(array_key_exists('posts', $update_query)) 3010 { 3011 $posts_diff = $update_query['posts'] - $forum['posts']; 3012 if($posts_diff > -1) 3013 { 3014 $new_stats['numposts'] = "+{$posts_diff}"; 3015 } 3016 else 3017 { 3018 $new_stats['numposts'] = "{$posts_diff}"; 3019 } 3020 } 3021 3022 if(array_key_exists('unapprovedposts', $update_query)) 3023 { 3024 $unapprovedposts_diff = $update_query['unapprovedposts'] - $forum['unapprovedposts']; 3025 if($unapprovedposts_diff > -1) 3026 { 3027 $new_stats['numunapprovedposts'] = "+{$unapprovedposts_diff}"; 3028 } 3029 else 3030 { 3031 $new_stats['numunapprovedposts'] = "{$unapprovedposts_diff}"; 3032 } 3033 } 3034 3035 if(array_key_exists('deletedposts', $update_query)) 3036 { 3037 $deletedposts_diff = $update_query['deletedposts'] - $forum['deletedposts']; 3038 if($deletedposts_diff > -1) 3039 { 3040 $new_stats['numdeletedposts'] = "+{$deletedposts_diff}"; 3041 } 3042 else 3043 { 3044 $new_stats['numdeletedposts'] = "{$deletedposts_diff}"; 3045 } 3046 } 3047 3048 if(array_key_exists('deletedthreads', $update_query)) 3049 { 3050 $deletedthreads_diff = $update_query['deletedthreads'] - $forum['deletedthreads']; 3051 if($deletedthreads_diff > -1) 3052 { 3053 $new_stats['numdeletedthreads'] = "+{$deletedthreads_diff}"; 3054 } 3055 else 3056 { 3057 $new_stats['numdeletedthreads'] = "{$deletedthreads_diff}"; 3058 } 3059 } 3060 3061 if(!empty($new_stats)) 3062 { 3063 update_stats($new_stats); 3064 } 3065 } 3066 3067 /** 3068 * Update the last post information for a specific forum 3069 * 3070 * @param int $fid The forum ID 3071 */ 3072 function update_forum_lastpost($fid) 3073 { 3074 global $db; 3075 3076 // Fetch the last post for this forum 3077 $query = $db->query(" 3078 SELECT tid, lastpost, lastposter, lastposteruid, subject 3079 FROM ".TABLE_PREFIX."threads 3080 WHERE fid='{$fid}' AND visible='1' AND closed NOT LIKE 'moved|%' 3081 ORDER BY lastpost DESC 3082 LIMIT 0, 1 3083 "); 3084 3085 if($db->num_rows($query) > 0) 3086 { 3087 $lastpost = $db->fetch_array($query); 3088 3089 $updated_forum = array( 3090 "lastpost" => (int)$lastpost['lastpost'], 3091 "lastposter" => $db->escape_string($lastpost['lastposter']), 3092 "lastposteruid" => (int)$lastpost['lastposteruid'], 3093 "lastposttid" => (int)$lastpost['tid'], 3094 "lastpostsubject" => $db->escape_string($lastpost['subject']), 3095 ); 3096 } 3097 else { 3098 $updated_forum = array( 3099 "lastpost" => 0, 3100 "lastposter" => '', 3101 "lastposteruid" => 0, 3102 "lastposttid" => 0, 3103 "lastpostsubject" => '', 3104 ); 3105 } 3106 3107 $db->update_query("forums", $updated_forum, "fid='{$fid}'"); 3108 } 3109 3110 /** 3111 * Updates the thread counters with a specific value (or addition/subtraction of the previous value) 3112 * 3113 * @param int $tid The thread ID 3114 * @param array $changes Array of items being updated (replies, unapprovedposts, deletedposts, attachmentcount) and their value (ex, 1, +1, -1) 3115 */ 3116 function update_thread_counters($tid, $changes=array()) 3117 { 3118 global $db; 3119 3120 $update_query = array(); 3121 $tid = (int)$tid; 3122 3123 $counters = array('replies', 'unapprovedposts', 'attachmentcount', 'deletedposts', 'attachmentcount'); 3124 3125 // Fetch above counters for this thread 3126 $query = $db->simple_select("threads", implode(",", $counters), "tid='{$tid}'"); 3127 $thread = $db->fetch_array($query); 3128 3129 foreach($counters as $counter) 3130 { 3131 if(array_key_exists($counter, $changes)) 3132 { 3133 if(substr($changes[$counter], 0, 2) == "+-") 3134 { 3135 $changes[$counter] = substr($changes[$counter], 1); 3136 } 3137 // Adding or subtracting from previous value? 3138 if(substr($changes[$counter], 0, 1) == "+" || substr($changes[$counter], 0, 1) == "-") 3139 { 3140 if((int)$changes[$counter] != 0) 3141 { 3142 $update_query[$counter] = $thread[$counter] + $changes[$counter]; 3143 } 3144 } 3145 else 3146 { 3147 $update_query[$counter] = $changes[$counter]; 3148 } 3149 3150 // Less than 0? That's bad 3151 if(isset($update_query[$counter]) && $update_query[$counter] < 0) 3152 { 3153 $update_query[$counter] = 0; 3154 } 3155 } 3156 } 3157 3158 $db->free_result($query); 3159 3160 // Only update if we're actually doing something 3161 if(count($update_query) > 0) 3162 { 3163 $db->update_query("threads", $update_query, "tid='{$tid}'"); 3164 } 3165 } 3166 3167 /** 3168 * Update the first post and lastpost data for a specific thread 3169 * 3170 * @param int $tid The thread ID 3171 */ 3172 function update_thread_data($tid) 3173 { 3174 global $db; 3175 3176 $thread = get_thread($tid); 3177 3178 // If this is a moved thread marker, don't update it - we need it to stay as it is 3179 if(strpos($thread['closed'], 'moved|') !== false) 3180 { 3181 return; 3182 } 3183 3184 $query = $db->query(" 3185 SELECT u.uid, u.username, p.username AS postusername, p.dateline 3186 FROM ".TABLE_PREFIX."posts p 3187 LEFT JOIN ".TABLE_PREFIX."users u ON (u.uid=p.uid) 3188 WHERE p.tid='$tid' AND p.visible='1' 3189 ORDER BY p.dateline DESC, p.pid DESC 3190 LIMIT 1" 3191 ); 3192 $lastpost = $db->fetch_array($query); 3193 3194 $db->free_result($query); 3195 3196 $query = $db->query(" 3197 SELECT u.uid, u.username, p.pid, p.username AS postusername, p.dateline 3198 FROM ".TABLE_PREFIX."posts p 3199 LEFT JOIN ".TABLE_PREFIX."users u ON (u.uid=p.uid) 3200 WHERE p.tid='$tid' 3201 ORDER BY p.dateline ASC, p.pid ASC 3202 LIMIT 1 3203 "); 3204 $firstpost = $db->fetch_array($query); 3205 3206 $db->free_result($query); 3207 3208 if(empty($firstpost['username'])) 3209 { 3210 $firstpost['username'] = $firstpost['postusername']; 3211 } 3212 3213 if(empty($lastpost['username'])) 3214 { 3215 $lastpost['username'] = $lastpost['postusername']; 3216 } 3217 3218 if(empty($lastpost['dateline'])) 3219 { 3220 $lastpost['username'] = $firstpost['username']; 3221 $lastpost['uid'] = $firstpost['uid']; 3222 $lastpost['dateline'] = $firstpost['dateline']; 3223 } 3224 3225 $lastpost['username'] = $db->escape_string($lastpost['username']); 3226 $firstpost['username'] = $db->escape_string($firstpost['username']); 3227 3228 $update_array = array( 3229 'firstpost' => (int)$firstpost['pid'], 3230 'username' => $firstpost['username'], 3231 'uid' => (int)$firstpost['uid'], 3232 'dateline' => (int)$firstpost['dateline'], 3233 'lastpost' => (int)$lastpost['dateline'], 3234 'lastposter' => $lastpost['username'], 3235 'lastposteruid' => (int)$lastpost['uid'], 3236 ); 3237 $db->update_query("threads", $update_array, "tid='{$tid}'"); 3238 } 3239 3240 /** 3241 * Updates the user counters with a specific value (or addition/subtraction of the previous value) 3242 * 3243 * @param int $uid The user ID 3244 * @param array $changes Array of items being updated (postnum, threadnum) and their value (ex, 1, +1, -1) 3245 */ 3246 function update_user_counters($uid, $changes=array()) 3247 { 3248 global $db; 3249 3250 $update_query = array(); 3251 3252 $counters = array('postnum', 'threadnum'); 3253 $uid = (int)$uid; 3254 3255 // Fetch above counters for this user 3256 $query = $db->simple_select("users", implode(",", $counters), "uid='{$uid}'"); 3257 $user = $db->fetch_array($query); 3258 3259 foreach($counters as $counter) 3260 { 3261 if(array_key_exists($counter, $changes)) 3262 { 3263 if(substr($changes[$counter], 0, 2) == "+-") 3264 { 3265 $changes[$counter] = substr($changes[$counter], 1); 3266 } 3267 // Adding or subtracting from previous value? 3268 if(substr($changes[$counter], 0, 1) == "+" || substr($changes[$counter], 0, 1) == "-") 3269 { 3270 if((int)$changes[$counter] != 0) 3271 { 3272 $update_query[$counter] = $user[$counter] + $changes[$counter]; 3273 } 3274 } 3275 else 3276 { 3277 $update_query[$counter] = $changes[$counter]; 3278 } 3279 3280 // Less than 0? That's bad 3281 if(isset($update_query[$counter]) && $update_query[$counter] < 0) 3282 { 3283 $update_query[$counter] = 0; 3284 } 3285 } 3286 } 3287 3288 $db->free_result($query); 3289 3290 // Only update if we're actually doing something 3291 if(count($update_query) > 0) 3292 { 3293 $db->update_query("users", $update_query, "uid='{$uid}'"); 3294 } 3295 } 3296 3297 /** 3298 * Deletes a thread from the database 3299 * 3300 * @param int $tid The thread ID 3301 * @return bool 3302 */ 3303 function delete_thread($tid) 3304 { 3305 global $moderation; 3306 3307 if(!is_object($moderation)) 3308 { 3309 require_once MYBB_ROOT."inc/class_moderation.php"; 3310 $moderation = new Moderation; 3311 } 3312 3313 return $moderation->delete_thread($tid); 3314 } 3315 3316 /** 3317 * Deletes a post from the database 3318 * 3319 * @param int $pid The thread ID 3320 * @return bool 3321 */ 3322 function delete_post($pid) 3323 { 3324 global $moderation; 3325 3326 if(!is_object($moderation)) 3327 { 3328 require_once MYBB_ROOT."inc/class_moderation.php"; 3329 $moderation = new Moderation; 3330 } 3331 3332 return $moderation->delete_post($pid); 3333 } 3334 3335 /** 3336 * Builds a forum jump menu 3337 * 3338 * @param int $pid The parent forum to start with 3339 * @param int $selitem The selected item ID 3340 * @param int $addselect If we need to add select boxes to this cal or not 3341 * @param string $depth The current depth of forums we're at 3342 * @param int $showextras Whether or not to show extra items such as User CP, Forum home 3343 * @param boolean $showall Ignore the showinjump setting and show all forums (for moderation pages) 3344 * @param mixed $permissions deprecated 3345 * @param string $name The name of the forum jump 3346 * @return string Forum jump items 3347 */ 3348 function build_forum_jump($pid=0, $selitem=0, $addselect=1, $depth="", $showextras=1, $showall=false, $permissions="", $name="fid") 3349 { 3350 global $forum_cache, $jumpfcache, $permissioncache, $mybb, $forumjump, $forumjumpbits, $gobutton, $theme, $templates, $lang; 3351 3352 $pid = (int)$pid; 3353 3354 if(!is_array($jumpfcache)) 3355 { 3356 if(!is_array($forum_cache)) 3357 { 3358 cache_forums(); 3359 } 3360 3361 foreach($forum_cache as $fid => $forum) 3362 { 3363 if($forum['active'] != 0) 3364 { 3365 $jumpfcache[$forum['pid']][$forum['disporder']][$forum['fid']] = $forum; 3366 } 3367 } 3368 } 3369 3370 if(!is_array($permissioncache)) 3371 { 3372 $permissioncache = forum_permissions(); 3373 } 3374 3375 if(isset($jumpfcache[$pid]) && is_array($jumpfcache[$pid])) 3376 { 3377 foreach($jumpfcache[$pid] as $main) 3378 { 3379 foreach($main as $forum) 3380 { 3381 $perms = $permissioncache[$forum['fid']]; 3382 3383 if($forum['fid'] != "0" && ($perms['canview'] != 0 || $mybb->settings['hideprivateforums'] == 0) && $forum['linkto'] == '' && ($forum['showinjump'] != 0 || $showall == true)) 3384 { 3385 $optionselected = ""; 3386 3387 if($selitem == $forum['fid']) 3388 { 3389 $optionselected = 'selected="selected"'; 3390 } 3391 3392 $forum['name'] = htmlspecialchars_uni(strip_tags($forum['name'])); 3393 3394 eval("\$forumjumpbits .= \"".$templates->get("forumjump_bit")."\";"); 3395 3396 if($forum_cache[$forum['fid']]) 3397 { 3398 $newdepth = $depth."--"; 3399 $forumjumpbits .= build_forum_jump($forum['fid'], $selitem, 0, $newdepth, $showextras, $showall); 3400 } 3401 } 3402 } 3403 } 3404 } 3405 3406 if($addselect) 3407 { 3408 if($showextras == 0) 3409 { 3410 $template = "special"; 3411 } 3412 else 3413 { 3414 $template = "advanced"; 3415 3416 if(strpos(FORUM_URL, '.html') !== false) 3417 { 3418 $forum_link = "'".str_replace('{fid}', "'+option+'", FORUM_URL)."'"; 3419 } 3420 else 3421 { 3422 $forum_link = "'".str_replace('{fid}', "'+option", FORUM_URL); 3423 } 3424 } 3425 3426 eval("\$forumjump = \"".$templates->get("forumjump_".$template)."\";"); 3427 } 3428 3429 return $forumjump; 3430 } 3431 3432 /** 3433 * Returns the extension of a file. 3434 * 3435 * @param string $file The filename. 3436 * @return string The extension of the file. 3437 */ 3438 function get_extension($file) 3439 { 3440 return my_strtolower(my_substr(strrchr($file, "."), 1)); 3441 } 3442 3443 /** 3444 * Generates a random string. 3445 * 3446 * @param int $length The length of the string to generate. 3447 * @param bool $complex Whether to return complex string. Defaults to false 3448 * @return string The random string. 3449 */ 3450 function random_str($length=8, $complex=false) 3451 { 3452 $set = array_merge(range(0, 9), range('A', 'Z'), range('a', 'z')); 3453 $str = array(); 3454 3455 // Complex strings have always at least 3 characters, even if $length < 3 3456 if($complex == true) 3457 { 3458 // At least one number 3459 $str[] = $set[my_rand(0, 9)]; 3460 3461 // At least one big letter 3462 $str[] = $set[my_rand(10, 35)]; 3463 3464 // At least one small letter 3465 $str[] = $set[my_rand(36, 61)]; 3466 3467 $length -= 3; 3468 } 3469 3470 for($i = 0; $i < $length; ++$i) 3471 { 3472 $str[] = $set[my_rand(0, 61)]; 3473 } 3474 3475 // Make sure they're in random order and convert them to a string 3476 shuffle($str); 3477 3478 return implode($str); 3479 } 3480 3481 /** 3482 * Formats a username based on their display group 3483 * 3484 * @param string $username The username 3485 * @param int $usergroup The usergroup for the user 3486 * @param int $displaygroup The display group for the user 3487 * @return string The formatted username 3488 */ 3489 function format_name($username, $usergroup, $displaygroup=0) 3490 { 3491 global $groupscache, $cache, $plugins; 3492 3493 static $formattednames = array(); 3494 3495 if(!isset($formattednames[$username])) 3496 { 3497 if(!is_array($groupscache)) 3498 { 3499 $groupscache = $cache->read("usergroups"); 3500 } 3501 3502 if($displaygroup != 0) 3503 { 3504 $usergroup = $displaygroup; 3505 } 3506 3507 $format = "{username}"; 3508 3509 if(isset($groupscache[$usergroup])) 3510 { 3511 $ugroup = $groupscache[$usergroup]; 3512 3513 if(strpos($ugroup['namestyle'], "{username}") !== false) 3514 { 3515 $format = $ugroup['namestyle']; 3516 } 3517 } 3518 3519 $format = stripslashes($format); 3520 3521 $parameters = compact('username', 'usergroup', 'displaygroup', 'format'); 3522 3523 $parameters = $plugins->run_hooks('format_name', $parameters); 3524 3525 $format = $parameters['format']; 3526 3527 $formattednames[$username] = str_replace("{username}", $username, $format); 3528 } 3529 3530 return $formattednames[$username]; 3531 } 3532 3533 /** 3534 * Formats an avatar to a certain dimension 3535 * 3536 * @param string $avatar The avatar file name 3537 * @param string $dimensions Dimensions of the avatar, width x height (e.g. 44|44) 3538 * @param string $max_dimensions The maximum dimensions of the formatted avatar 3539 * @return array Information for the formatted avatar 3540 */ 3541 function format_avatar($avatar, $dimensions = '', $max_dimensions = '') 3542 { 3543 global $mybb, $theme; 3544 static $avatars; 3545 3546 if(!isset($avatars)) 3547 { 3548 $avatars = array(); 3549 } 3550 3551 if(my_strpos($avatar, '://') !== false && !$mybb->settings['allowremoteavatars']) 3552 { 3553 // Remote avatar, but remote avatars are disallowed. 3554 $avatar = null; 3555 } 3556 3557 if(!$avatar) 3558 { 3559 // Default avatar 3560 if(defined('IN_ADMINCP')) 3561 { 3562 $theme['imgdir'] = '../images'; 3563 } 3564 3565 $avatar = str_replace('{theme}', $theme['imgdir'], $mybb->settings['useravatar']); 3566 $dimensions = $mybb->settings['useravatardims']; 3567 } 3568 3569 if(!$max_dimensions) 3570 { 3571 $max_dimensions = $mybb->settings['maxavatardims']; 3572 } 3573 3574 // An empty key wouldn't work so we need to add a fall back 3575 $key = $dimensions; 3576 if(empty($key)) 3577 { 3578 $key = 'default'; 3579 } 3580 $key2 = $max_dimensions; 3581 if(empty($key2)) 3582 { 3583 $key2 = 'default'; 3584 } 3585 3586 if(isset($avatars[$avatar][$key][$key2])) 3587 { 3588 return $avatars[$avatar][$key][$key2]; 3589 } 3590 3591 $avatar_width_height = ''; 3592 3593 if($dimensions) 3594 { 3595 $dimensions = preg_split('/[|x]/', $dimensions); 3596 3597 if($dimensions[0] && $dimensions[1]) 3598 { 3599 list($max_width, $max_height) = preg_split('/[|x]/', $max_dimensions); 3600 3601 if(!empty($max_dimensions) && ($dimensions[0] > $max_width || $dimensions[1] > $max_height)) 3602 { 3603 require_once MYBB_ROOT."inc/functions_image.php"; 3604 $scaled_dimensions = scale_image($dimensions[0], $dimensions[1], $max_width, $max_height); 3605 $avatar_width_height = "width=\"{$scaled_dimensions['width']}\" height=\"{$scaled_dimensions['height']}\""; 3606 } 3607 else 3608 { 3609 $avatar_width_height = "width=\"{$dimensions[0]}\" height=\"{$dimensions[1]}\""; 3610 } 3611 } 3612 } 3613 3614 $avatars[$avatar][$key][$key2] = array( 3615 'image' => htmlspecialchars_uni($mybb->get_asset_url($avatar)), 3616 'width_height' => $avatar_width_height 3617 ); 3618 3619 return $avatars[$avatar][$key][$key2]; 3620 } 3621 3622 /** 3623 * Build the javascript based MyCode inserter. 3624 * 3625 * @param string $bind The ID of the textarea to bind to. Defaults to "message". 3626 * @param bool $smilies Whether to include smilies. Defaults to true. 3627 * 3628 * @return string The MyCode inserter 3629 */ 3630 function build_mycode_inserter($bind="message", $smilies = true) 3631 { 3632 global $db, $mybb, $theme, $templates, $lang, $plugins, $smiliecache, $cache; 3633 3634 if($mybb->settings['bbcodeinserter'] != 0) 3635 { 3636 $editor_lang_strings = array( 3637 "editor_bold" => "Bold", 3638 "editor_italic" => "Italic", 3639 "editor_underline" => "Underline", 3640 "editor_strikethrough" => "Strikethrough", 3641 "editor_subscript" => "Subscript", 3642 "editor_superscript" => "Superscript", 3643 "editor_alignleft" => "Align left", 3644 "editor_center" => "Center", 3645 "editor_alignright" => "Align right", 3646 "editor_justify" => "Justify", 3647 "editor_fontname" => "Font Name", 3648 "editor_fontsize" => "Font Size", 3649 "editor_fontcolor" => "Font Color", 3650 "editor_removeformatting" => "Remove Formatting", 3651 "editor_cut" => "Cut", 3652 "editor_cutnosupport" => "Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X", 3653 "editor_copy" => "Copy", 3654 "editor_copynosupport" => "Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C", 3655 "editor_paste" => "Paste", 3656 "editor_pastenosupport" => "Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V", 3657 "editor_pasteentertext" => "Paste your text inside the following box:", 3658 "editor_pastetext" => "PasteText", 3659 "editor_numlist" => "Numbered list", 3660 "editor_bullist" => "Bullet list", 3661 "editor_undo" => "Undo", 3662 "editor_redo" => "Redo", 3663 "editor_rows" => "Rows:", 3664 "editor_cols" => "Cols:", 3665 "editor_inserttable" => "Insert a table", 3666 "editor_inserthr" => "Insert a horizontal rule", 3667 "editor_code" => "Code", 3668 "editor_width" => "Width (optional):", 3669 "editor_height" => "Height (optional):", 3670 "editor_insertimg" => "Insert an image", 3671 "editor_email" => "E-mail:", 3672 "editor_insertemail" => "Insert an email", 3673 "editor_url" => "URL:", 3674 "editor_insertlink" => "Insert a link", 3675 "editor_unlink" => "Unlink", 3676 "editor_more" => "More", 3677 "editor_insertemoticon" => "Insert an emoticon", 3678 "editor_videourl" => "Video URL:", 3679 "editor_videotype" => "Video Type:", 3680 "editor_insert" => "Insert", 3681 "editor_insertyoutubevideo" => "Insert a YouTube video", 3682 "editor_currentdate" => "Insert current date", 3683 "editor_currenttime" => "Insert current time", 3684 "editor_print" => "Print", 3685 "editor_viewsource" => "View source", 3686 "editor_description" => "Description (optional):", 3687 "editor_enterimgurl" => "Enter the image URL:", 3688 "editor_enteremail" => "Enter the e-mail address:", 3689 "editor_enterdisplayedtext" => "Enter the displayed text:", 3690 "editor_enterurl" => "Enter URL:", 3691 "editor_enteryoutubeurl" => "Enter the YouTube video URL or ID:", 3692 "editor_insertquote" => "Insert a Quote", 3693 "editor_invalidyoutube" => "Invalid YouTube video", 3694 "editor_dailymotion" => "Dailymotion", 3695 "editor_metacafe" => "MetaCafe", 3696 "editor_mixer" => "Mixer", 3697 "editor_vimeo" => "Vimeo", 3698 "editor_youtube" => "Youtube", 3699 "editor_facebook" => "Facebook", 3700 "editor_liveleak" => "LiveLeak", 3701 "editor_insertvideo" => "Insert a video", 3702 "editor_php" => "PHP", 3703 "editor_maximize" => "Maximize" 3704 ); 3705 $editor_language = "(function ($) {\n$.sceditor.locale[\"mybblang\"] = {\n"; 3706 3707 $editor_lang_strings = $plugins->run_hooks("mycode_add_codebuttons", $editor_lang_strings); 3708 3709 $editor_languages_count = count($editor_lang_strings); 3710 $i = 0; 3711 foreach($editor_lang_strings as $lang_string => $key) 3712 { 3713 $i++; 3714 $js_lang_string = str_replace("\"", "\\\"", $key); 3715 $string = str_replace("\"", "\\\"", $lang->$lang_string); 3716 $editor_language .= "\t\"{$js_lang_string}\": \"{$string}\""; 3717 3718 if($i < $editor_languages_count) 3719 { 3720 $editor_language .= ","; 3721 } 3722 3723 $editor_language .= "\n"; 3724 } 3725 3726 $editor_language .= "}})(jQuery);"; 3727 3728 if(defined("IN_ADMINCP")) 3729 { 3730 global $page; 3731 $codeinsert = $page->build_codebuttons_editor($bind, $editor_language, $smilies); 3732 } 3733 else 3734 { 3735 // Smilies 3736 $emoticon = ""; 3737 $emoticons_enabled = "false"; 3738 if($smilies) 3739 { 3740 if(!$smiliecache) 3741 { 3742 if(!isset($smilie_cache) || !is_array($smilie_cache)) 3743 { 3744 $smilie_cache = $cache->read("smilies"); 3745 } 3746 foreach($smilie_cache as $smilie) 3747 { 3748 $smilie['image'] = str_replace("{theme}", $theme['imgdir'], $smilie['image']); 3749 $smiliecache[$smilie['sid']] = $smilie; 3750 } 3751 } 3752 3753 if($mybb->settings['smilieinserter'] && $mybb->settings['smilieinsertercols'] && $mybb->settings['smilieinsertertot'] && !empty($smiliecache)) 3754 { 3755 $emoticon = ",emoticon"; 3756 } 3757 $emoticons_enabled = "true"; 3758 3759 unset($smilie); 3760 3761 if(is_array($smiliecache)) 3762 { 3763 reset($smiliecache); 3764 3765 $dropdownsmilies = $moresmilies = $hiddensmilies = ""; 3766 $i = 0; 3767 3768 foreach($smiliecache as $smilie) 3769 { 3770 $finds = explode("\n", $smilie['find']); 3771 $finds_count = count($finds); 3772 3773 // Only show the first text to replace in the box 3774 $smilie['find'] = $finds[0]; 3775 3776 $find = str_replace(array('\\', '"'), array('\\\\', '\"'), htmlspecialchars_uni($smilie['find'])); 3777 $image = htmlspecialchars_uni($mybb->get_asset_url($smilie['image'])); 3778 $image = str_replace(array('\\', '"'), array('\\\\', '\"'), $image); 3779 3780 if(!$mybb->settings['smilieinserter'] || !$mybb->settings['smilieinsertercols'] || !$mybb->settings['smilieinsertertot'] || !$smilie['showclickable']) 3781 { 3782 $hiddensmilies .= '"'.$find.'": "'.$image.'",'; 3783 } 3784 elseif($i < $mybb->settings['smilieinsertertot']) 3785 { 3786 $dropdownsmilies .= '"'.$find.'": "'.$image.'",'; 3787 ++$i; 3788 } 3789 else 3790 { 3791 $moresmilies .= '"'.$find.'": "'.$image.'",'; 3792 } 3793 3794 for($j = 1; $j < $finds_count; ++$j) 3795 { 3796 $find = str_replace(array('\\', '"'), array('\\\\', '\"'), htmlspecialchars_uni($finds[$j])); 3797 $hiddensmilies .= '"'.$find.'": "'.$image.'",'; 3798 } 3799 } 3800 } 3801 } 3802 3803 $basic1 = $basic2 = $align = $font = $size = $color = $removeformat = $email = $link = $list = $code = $sourcemode = ""; 3804 3805 if($mybb->settings['allowbasicmycode'] == 1) 3806 { 3807 $basic1 = "bold,italic,underline,strike|"; 3808 $basic2 = "horizontalrule,"; 3809 } 3810 3811 if($mybb->settings['allowalignmycode'] == 1) 3812 { 3813 $align = "left,center,right,justify|"; 3814 } 3815 3816 if($mybb->settings['allowfontmycode'] == 1) 3817 { 3818 $font = "font,"; 3819 } 3820 3821 if($mybb->settings['allowsizemycode'] == 1) 3822 { 3823 $size = "size,"; 3824 } 3825 3826 if($mybb->settings['allowcolormycode'] == 1) 3827 { 3828 $color = "color,"; 3829 } 3830 3831 if($mybb->settings['allowfontmycode'] == 1 || $mybb->settings['allowsizemycode'] == 1 || $mybb->settings['allowcolormycode'] == 1) 3832 { 3833 $removeformat = "removeformat|"; 3834 } 3835 3836 if($mybb->settings['allowemailmycode'] == 1) 3837 { 3838 $email = "email,"; 3839 } 3840 3841 if($mybb->settings['allowlinkmycode'] == 1) 3842 { 3843 $link = "link,unlink"; 3844 } 3845 3846 if($mybb->settings['allowlistmycode'] == 1) 3847 { 3848 $list = "bulletlist,orderedlist|"; 3849 } 3850 3851 if($mybb->settings['allowcodemycode'] == 1) 3852 { 3853 $code = "code,php,"; 3854 } 3855 3856 if($mybb->user['sourceeditor'] == 1) 3857 { 3858 $sourcemode = "MyBBEditor.sourceMode(true);"; 3859 } 3860 3861 eval("\$codeinsert = \"".$templates->get("codebuttons")."\";"); 3862 } 3863 } 3864 3865 return $codeinsert; 3866 } 3867 3868 /** 3869 * @param int $tid 3870 * @param array $postoptions The options carried with form submit 3871 * 3872 * @return string Predefined / updated subscription method of the thread for the user 3873 */ 3874 function get_subscription_method($tid = 0, $postoptions = array()) 3875 { 3876 global $mybb; 3877 3878 $subscription_methods = array('', 'none', 'email', 'pm'); // Define methods 3879 $subscription_method = (int)$mybb->user['subscriptionmethod']; // Set user default 3880 3881 // If no user default method available then reset method 3882 if(!$subscription_method) 3883 { 3884 $subscription_method = 0; 3885 } 3886 3887 // Return user default if no thread id available, in case 3888 if(!(int)$tid || (int)$tid <= 0) 3889 { 3890 return $subscription_methods[$subscription_method]; 3891 } 3892 3893 // If method not predefined set using data from database 3894 if(isset($postoptions['subscriptionmethod'])) 3895 { 3896 $method = trim($postoptions['subscriptionmethod']); 3897 return (in_array($method, $subscription_methods)) ? $method : $subscription_methods[0]; 3898 } 3899 else 3900 { 3901 global $db; 3902 3903 $query = $db->simple_select("threadsubscriptions", "tid, notification", "tid='".(int)$tid."' AND uid='".$mybb->user['uid']."'", array('limit' => 1)); 3904 $subscription = $db->fetch_array($query); 3905 3906 if(!empty($subscription) && $subscription['tid']) 3907 { 3908 $subscription_method = (int)$subscription['notification'] + 1; 3909 } 3910 } 3911 3912 return $subscription_methods[$subscription_method]; 3913 } 3914 3915 /** 3916 * Build the javascript clickable smilie inserter 3917 * 3918 * @return string The clickable smilies list 3919 */ 3920 function build_clickable_smilies() 3921 { 3922 global $cache, $smiliecache, $theme, $templates, $lang, $mybb, $smiliecount; 3923 3924 if($mybb->settings['smilieinserter'] != 0 && $mybb->settings['smilieinsertercols'] && $mybb->settings['smilieinsertertot']) 3925 { 3926 if(!$smiliecount) 3927 { 3928 $smilie_cache = $cache->read("smilies"); 3929 $smiliecount = count($smilie_cache); 3930 } 3931 3932 if(!$smiliecache) 3933 { 3934 if(!is_array($smilie_cache)) 3935 { 3936 $smilie_cache = $cache->read("smilies"); 3937 } 3938 foreach($smilie_cache as $smilie) 3939 { 3940 $smilie['image'] = str_replace("{theme}", $theme['imgdir'], $smilie['image']); 3941 $smiliecache[$smilie['sid']] = $smilie; 3942 } 3943 } 3944 3945 unset($smilie); 3946 3947 if(is_array($smiliecache)) 3948 { 3949 reset($smiliecache); 3950 3951 $getmore = ''; 3952 if($mybb->settings['smilieinsertertot'] >= $smiliecount) 3953 { 3954 $mybb->settings['smilieinsertertot'] = $smiliecount; 3955 } 3956 else if($mybb->settings['smilieinsertertot'] < $smiliecount) 3957 { 3958 $smiliecount = $mybb->settings['smilieinsertertot']; 3959 eval("\$getmore = \"".$templates->get("smilieinsert_getmore")."\";"); 3960 } 3961 3962 $smilies = $smilie_icons = ''; 3963 $counter = 0; 3964 $i = 0; 3965 3966 $extra_class = ''; 3967 foreach($smiliecache as $smilie) 3968 { 3969 if($i < $mybb->settings['smilieinsertertot'] && $smilie['showclickable'] != 0) 3970 { 3971 $smilie['image'] = str_replace("{theme}", $theme['imgdir'], $smilie['image']); 3972 $smilie['image'] = htmlspecialchars_uni($mybb->get_asset_url($smilie['image'])); 3973 $smilie['name'] = htmlspecialchars_uni($smilie['name']); 3974 3975 // Only show the first text to replace in the box 3976 $temp = explode("\n", $smilie['find']); // assign to temporary variable for php 5.3 compatibility 3977 $smilie['find'] = $temp[0]; 3978 3979 $find = str_replace(array('\\', "'"), array('\\\\', "\'"), htmlspecialchars_uni($smilie['find'])); 3980 3981 $onclick = " onclick=\"MyBBEditor.insertText(' $find ');\""; 3982 $extra_class = ' smilie_pointer'; 3983 eval('$smilie = "'.$templates->get('smilie', 1, 0).'";'); 3984 eval("\$smilie_icons .= \"".$templates->get("smilieinsert_smilie")."\";"); 3985 ++$i; 3986 ++$counter; 3987 3988 if($counter == $mybb->settings['smilieinsertercols']) 3989 { 3990 $counter = 0; 3991 eval("\$smilies .= \"".$templates->get("smilieinsert_row")."\";"); 3992 $smilie_icons = ''; 3993 } 3994 } 3995 } 3996 3997 if($counter != 0) 3998 { 3999 $colspan = $mybb->settings['smilieinsertercols'] - $counter; 4000 eval("\$smilies .= \"".$templates->get("smilieinsert_row_empty")."\";"); 4001 } 4002 4003 eval("\$clickablesmilies = \"".$templates->get("smilieinsert")."\";"); 4004 } 4005 else 4006 { 4007 $clickablesmilies = ""; 4008 } 4009 } 4010 else 4011 { 4012 $clickablesmilies = ""; 4013 } 4014 4015 return $clickablesmilies; 4016 } 4017 4018 /** 4019 * Builds thread prefixes and returns a selected prefix (or all) 4020 * 4021 * @param int $pid The prefix ID (0 to return all) 4022 * @return array The thread prefix's values (or all thread prefixes) 4023 */ 4024 function build_prefixes($pid=0) 4025 { 4026 global $cache; 4027 static $prefixes_cache; 4028 4029 if(is_array($prefixes_cache)) 4030 { 4031 if($pid > 0 && is_array($prefixes_cache[$pid])) 4032 { 4033 return $prefixes_cache[$pid]; 4034 } 4035 4036 return $prefixes_cache; 4037 } 4038 4039 $prefix_cache = $cache->read("threadprefixes"); 4040 4041 if(!is_array($prefix_cache)) 4042 { 4043 // No cache 4044 $prefix_cache = $cache->read("threadprefixes", true); 4045 4046 if(!is_array($prefix_cache)) 4047 { 4048 return array(); 4049 } 4050 } 4051 4052 $prefixes_cache = array(); 4053 foreach($prefix_cache as $prefix) 4054 { 4055 $prefixes_cache[$prefix['pid']] = $prefix; 4056 } 4057 4058 if($pid != 0 && is_array($prefixes_cache[$pid])) 4059 { 4060 return $prefixes_cache[$pid]; 4061 } 4062 else if(!empty($prefixes_cache)) 4063 { 4064 return $prefixes_cache; 4065 } 4066 4067 return false; 4068 } 4069 4070 /** 4071 * Build the thread prefix selection menu for the current user 4072 * 4073 * @param int|string $fid The forum ID (integer ID or string all) 4074 * @param int|string $selected_pid The selected prefix ID (integer ID or string any) 4075 * @param int $multiple Allow multiple prefix selection 4076 * @param int $previous_pid The previously selected prefix ID 4077 * @return string The thread prefix selection menu 4078 */ 4079 function build_prefix_select($fid, $selected_pid=0, $multiple=0, $previous_pid=0) 4080 { 4081 global $cache, $db, $lang, $mybb, $templates; 4082 4083 if($fid != 'all') 4084 { 4085 $fid = (int)$fid; 4086 } 4087 4088 $prefix_cache = build_prefixes(0); 4089 if(empty($prefix_cache)) 4090 { 4091 // We've got no prefixes to show 4092 return ''; 4093 } 4094 4095 // Go through each of our prefixes and decide which ones we can use 4096 $prefixes = array(); 4097 foreach($prefix_cache as $prefix) 4098 { 4099 if($fid != "all" && $prefix['forums'] != "-1") 4100 { 4101 // Decide whether this prefix can be used in our forum 4102 $forums = explode(",", $prefix['forums']); 4103 4104 if(!in_array($fid, $forums) && $prefix['pid'] != $previous_pid) 4105 { 4106 // This prefix is not in our forum list 4107 continue; 4108 } 4109 } 4110 4111 if(is_member($prefix['groups']) || $prefix['pid'] == $previous_pid) 4112 { 4113 // The current user can use this prefix 4114 $prefixes[$prefix['pid']] = $prefix; 4115 } 4116 } 4117 4118 if(empty($prefixes)) 4119 { 4120 return ''; 4121 } 4122 4123 $prefixselect = $prefixselect_prefix = ''; 4124 4125 if($multiple == 1) 4126 { 4127 $any_selected = ""; 4128 if($selected_pid == 'any') 4129 { 4130 $any_selected = " selected=\"selected\""; 4131 } 4132 } 4133 4134 $default_selected = ""; 4135 if(((int)$selected_pid == 0) && $selected_pid != 'any') 4136 { 4137 $default_selected = " selected=\"selected\""; 4138 } 4139 4140 foreach($prefixes as $prefix) 4141 { 4142 $selected = ""; 4143 if($prefix['pid'] == $selected_pid) 4144 { 4145 $selected = " selected=\"selected\""; 4146 } 4147 4148 $prefix['prefix'] = htmlspecialchars_uni($prefix['prefix']); 4149 eval("\$prefixselect_prefix .= \"".$templates->get("post_prefixselect_prefix")."\";"); 4150 } 4151 4152 if($multiple != 0) 4153 { 4154 eval("\$prefixselect = \"".$templates->get("post_prefixselect_multiple")."\";"); 4155 } 4156 else 4157 { 4158 eval("\$prefixselect = \"".$templates->get("post_prefixselect_single")."\";"); 4159 } 4160 4161 return $prefixselect; 4162 } 4163 4164 /** 4165 * Build the thread prefix selection menu for a forum without group permission checks 4166 * 4167 * @param int $fid The forum ID (integer ID) 4168 * @param int $selected_pid The selected prefix ID (integer ID) 4169 * @return string The thread prefix selection menu 4170 */ 4171 function build_forum_prefix_select($fid, $selected_pid=0) 4172 { 4173 global $cache, $db, $lang, $mybb, $templates; 4174 4175 $fid = (int)$fid; 4176 4177 $prefix_cache = build_prefixes(0); 4178 if(empty($prefix_cache)) 4179 { 4180 // We've got no prefixes to show 4181 return ''; 4182 } 4183 4184 // Go through each of our prefixes and decide which ones we can use 4185 $prefixes = array(); 4186 foreach($prefix_cache as $prefix) 4187 { 4188 if($prefix['forums'] != "-1") 4189 { 4190 // Decide whether this prefix can be used in our forum 4191 $forums = explode(",", $prefix['forums']); 4192 4193 if(in_array($fid, $forums)) 4194 { 4195 // This forum can use this prefix! 4196 $prefixes[$prefix['pid']] = $prefix; 4197 } 4198 } 4199 else 4200 { 4201 // This prefix is for anybody to use... 4202 $prefixes[$prefix['pid']] = $prefix; 4203 } 4204 } 4205 4206 if(empty($prefixes)) 4207 { 4208 return ''; 4209 } 4210 4211 $default_selected = array('all' => '', 'none' => '', 'any' => ''); 4212 $selected_pid = (int)$selected_pid; 4213 4214 if($selected_pid == 0) 4215 { 4216 $default_selected['all'] = ' selected="selected"'; 4217 } 4218 else if($selected_pid == -1) 4219 { 4220 $default_selected['none'] = ' selected="selected"'; 4221 } 4222 else if($selected_pid == -2) 4223 { 4224 $default_selected['any'] = ' selected="selected"'; 4225 } 4226 4227 $prefixselect_prefix = ''; 4228 foreach($prefixes as $prefix) 4229 { 4230 $selected = ''; 4231 if($prefix['pid'] == $selected_pid) 4232 { 4233 $selected = ' selected="selected"'; 4234 } 4235 4236 $prefix['prefix'] = htmlspecialchars_uni($prefix['prefix']); 4237 eval('$prefixselect_prefix .= "'.$templates->get("forumdisplay_threadlist_prefixes_prefix").'";'); 4238 } 4239 4240 eval('$prefixselect = "'.$templates->get("forumdisplay_threadlist_prefixes").'";'); 4241 return $prefixselect; 4242 } 4243 4244 /** 4245 * Gzip encodes text to a specified level 4246 * 4247 * @param string $contents The string to encode 4248 * @param int $level The level (1-9) to encode at 4249 * @return string The encoded string 4250 */ 4251 function gzip_encode($contents, $level=1) 4252 { 4253 if(function_exists("gzcompress") && function_exists("crc32") && !headers_sent() && !(ini_get('output_buffering') && my_strpos(' '.ini_get('output_handler'), 'ob_gzhandler'))) 4254 { 4255 $httpaccept_encoding = ''; 4256 4257 if(isset($_SERVER['HTTP_ACCEPT_ENCODING'])) 4258 { 4259 $httpaccept_encoding = $_SERVER['HTTP_ACCEPT_ENCODING']; 4260 } 4261 4262 if(my_strpos(" ".$httpaccept_encoding, "x-gzip")) 4263 { 4264 $encoding = "x-gzip"; 4265 } 4266 4267 if(my_strpos(" ".$httpaccept_encoding, "gzip")) 4268 { 4269 $encoding = "gzip"; 4270 } 4271 4272 if(isset($encoding)) 4273 { 4274 header("Content-Encoding: $encoding"); 4275 4276 if(function_exists("gzencode")) 4277 { 4278 $contents = gzencode($contents, $level); 4279 } 4280 else 4281 { 4282 $size = strlen($contents); 4283 $crc = crc32($contents); 4284 $gzdata = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff"; 4285 $gzdata .= my_substr(gzcompress($contents, $level), 2, -4); 4286 $gzdata .= pack("V", $crc); 4287 $gzdata .= pack("V", $size); 4288 $contents = $gzdata; 4289 } 4290 } 4291 } 4292 4293 return $contents; 4294 } 4295 4296 /** 4297 * Log the actions of a moderator. 4298 * 4299 * @param array $data The data of the moderator's action. 4300 * @param string $action The message to enter for the action the moderator performed. 4301 */ 4302 function log_moderator_action($data, $action="") 4303 { 4304 global $mybb, $db, $session; 4305 4306 $fid = 0; 4307 if(isset($data['fid'])) 4308 { 4309 $fid = (int)$data['fid']; 4310 unset($data['fid']); 4311 } 4312 4313 $tid = 0; 4314 if(isset($data['tid'])) 4315 { 4316 $tid = (int)$data['tid']; 4317 unset($data['tid']); 4318 } 4319 4320 $pid = 0; 4321 if(isset($data['pid'])) 4322 { 4323 $pid = (int)$data['pid']; 4324 unset($data['pid']); 4325 } 4326 4327 $tids = array(); 4328 if(isset($data['tids'])) 4329 { 4330 $tids = (array)$data['tids']; 4331 unset($data['tids']); 4332 } 4333 4334 // Any remaining extra data - we my_serialize and insert in to its own column 4335 if(is_array($data)) 4336 { 4337 $data = my_serialize($data); 4338 } 4339 4340 $sql_array = array( 4341 "uid" => (int)$mybb->user['uid'], 4342 "dateline" => TIME_NOW, 4343 "fid" => (int)$fid, 4344 "tid" => $tid, 4345 "pid" => $pid, 4346 "action" => $db->escape_string($action), 4347 "data" => $db->escape_string($data), 4348 "ipaddress" => $db->escape_binary($session->packedip) 4349 ); 4350 4351 if($tids) 4352 { 4353 $multiple_sql_array = array(); 4354 4355 foreach($tids as $tid) 4356 { 4357 $sql_array['tid'] = (int)$tid; 4358 $multiple_sql_array[] = $sql_array; 4359 } 4360 4361 $db->insert_query_multiple("moderatorlog", $multiple_sql_array); 4362 } 4363 else 4364 { 4365 $db->insert_query("moderatorlog", $sql_array); 4366 } 4367 } 4368 4369 /** 4370 * Get the formatted reputation for a user. 4371 * 4372 * @param int $reputation The reputation value 4373 * @param int $uid The user ID (if not specified, the generated reputation will not be a link) 4374 * @return string The formatted repuation 4375 */ 4376 function get_reputation($reputation, $uid=0) 4377 { 4378 global $theme, $templates; 4379 4380 $display_reputation = $reputation_class = ''; 4381 if($reputation < 0) 4382 { 4383 $reputation_class = "reputation_negative"; 4384 } 4385 elseif($reputation > 0) 4386 { 4387 $reputation_class = "reputation_positive"; 4388 } 4389 else 4390 { 4391 $reputation_class = "reputation_neutral"; 4392 } 4393 4394 $reputation = my_number_format($reputation); 4395 4396 if($uid != 0) 4397 { 4398 eval("\$display_reputation = \"".$templates->get("postbit_reputation_formatted_link")."\";"); 4399 } 4400 else 4401 { 4402 eval("\$display_reputation = \"".$templates->get("postbit_reputation_formatted")."\";"); 4403 } 4404 4405 return $display_reputation; 4406 } 4407 4408 /** 4409 * Fetch a color coded version of a warning level (based on it's percentage) 4410 * 4411 * @param int $level The warning level (percentage of 100) 4412 * @return string Formatted warning level 4413 */ 4414 function get_colored_warning_level($level) 4415 { 4416 global $templates; 4417 4418 $warning_class = ''; 4419 if($level >= 80) 4420 { 4421 $warning_class = "high_warning"; 4422 } 4423 else if($level >= 50) 4424 { 4425 $warning_class = "moderate_warning"; 4426 } 4427 else if($level >= 25) 4428 { 4429 $warning_class = "low_warning"; 4430 } 4431 else 4432 { 4433 $warning_class = "normal_warning"; 4434 } 4435 4436 eval("\$level = \"".$templates->get("postbit_warninglevel_formatted")."\";"); 4437 return $level; 4438 } 4439 4440 /** 4441 * Fetch the IP address of the current user. 4442 * 4443 * @return string The IP address. 4444 */ 4445 function get_ip() 4446 { 4447 global $mybb, $plugins; 4448 4449 $ip = strtolower($_SERVER['REMOTE_ADDR']); 4450 4451 if($mybb->settings['ip_forwarded_check']) 4452 { 4453 $addresses = array(); 4454 4455 if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])) 4456 { 4457 $addresses = explode(',', strtolower($_SERVER['HTTP_X_FORWARDED_FOR'])); 4458 } 4459 elseif(isset($_SERVER['HTTP_X_REAL_IP'])) 4460 { 4461 $addresses = explode(',', strtolower($_SERVER['HTTP_X_REAL_IP'])); 4462 } 4463 4464 if(is_array($addresses)) 4465 { 4466 foreach($addresses as $val) 4467 { 4468 $val = trim($val); 4469 // Validate IP address and exclude private addresses 4470 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)) 4471 { 4472 $ip = $val; 4473 break; 4474 } 4475 } 4476 } 4477 } 4478 4479 if(!$ip) 4480 { 4481 if(isset($_SERVER['HTTP_CLIENT_IP'])) 4482 { 4483 $ip = strtolower($_SERVER['HTTP_CLIENT_IP']); 4484 } 4485 } 4486 4487 if($plugins) 4488 { 4489 $ip_array = array("ip" => &$ip); // Used for backwards compatibility on this hook with the updated run_hooks() function. 4490 $plugins->run_hooks("get_ip", $ip_array); 4491 } 4492 4493 return $ip; 4494 } 4495 4496 /** 4497 * Fetch the friendly size (GB, MB, KB, B) for a specified file size. 4498 * 4499 * @param int $size The size in bytes 4500 * @return string The friendly file size 4501 */ 4502 function get_friendly_size($size) 4503 { 4504 global $lang; 4505 4506 if(!is_numeric($size)) 4507 { 4508 return $lang->na; 4509 } 4510 4511 // Yottabyte (1024 Zettabytes) 4512 if($size >= 1208925819614629174706176) 4513 { 4514 $size = my_number_format(round(($size / 1208925819614629174706176), 2))." ".$lang->size_yb; 4515 } 4516 // Zetabyte (1024 Exabytes) 4517 elseif($size >= 1180591620717411303424) 4518 { 4519 $size = my_number_format(round(($size / 1180591620717411303424), 2))." ".$lang->size_zb; 4520 } 4521 // Exabyte (1024 Petabytes) 4522 elseif($size >= 1152921504606846976) 4523 { 4524 $size = my_number_format(round(($size / 1152921504606846976), 2))." ".$lang->size_eb; 4525 } 4526 // Petabyte (1024 Terabytes) 4527 elseif($size >= 1125899906842624) 4528 { 4529 $size = my_number_format(round(($size / 1125899906842624), 2))." ".$lang->size_pb; 4530 } 4531 // Terabyte (1024 Gigabytes) 4532 elseif($size >= 1099511627776) 4533 { 4534 $size = my_number_format(round(($size / 1099511627776), 2))." ".$lang->size_tb; 4535 } 4536 // Gigabyte (1024 Megabytes) 4537 elseif($size >= 1073741824) 4538 { 4539 $size = my_number_format(round(($size / 1073741824), 2))." ".$lang->size_gb; 4540 } 4541 // Megabyte (1024 Kilobytes) 4542 elseif($size >= 1048576) 4543 { 4544 $size = my_number_format(round(($size / 1048576), 2))." ".$lang->size_mb; 4545 } 4546 // Kilobyte (1024 bytes) 4547 elseif($size >= 1024) 4548 { 4549 $size = my_number_format(round(($size / 1024), 2))." ".$lang->size_kb; 4550 } 4551 elseif($size == 0) 4552 { 4553 $size = "0 ".$lang->size_bytes; 4554 } 4555 else 4556 { 4557 $size = my_number_format($size)." ".$lang->size_bytes; 4558 } 4559 4560 return $size; 4561 } 4562 4563 /** 4564 * Format a decimal number in to microseconds, milliseconds, or seconds. 4565 * 4566 * @param int $time The time in microseconds 4567 * @return string The friendly time duration 4568 */ 4569 function format_time_duration($time) 4570 { 4571 global $lang; 4572 4573 if(!is_numeric($time)) 4574 { 4575 return $lang->na; 4576 } 4577 4578 if(round(1000000 * $time, 2) < 1000) 4579 { 4580 $time = number_format(round(1000000 * $time, 2))." μs"; 4581 } 4582 elseif(round(1000000 * $time, 2) >= 1000 && round(1000000 * $time, 2) < 1000000) 4583 { 4584 $time = number_format(round((1000 * $time), 2))." ms"; 4585 } 4586 else 4587 { 4588 $time = round($time, 3)." seconds"; 4589 } 4590 4591 return $time; 4592 } 4593 4594 /** 4595 * Get the attachment icon for a specific file extension 4596 * 4597 * @param string $ext The file extension 4598 * @return string The attachment icon 4599 */ 4600 function get_attachment_icon($ext) 4601 { 4602 global $cache, $attachtypes, $theme, $templates, $lang, $mybb; 4603 4604 if(!$attachtypes) 4605 { 4606 $attachtypes = $cache->read("attachtypes"); 4607 } 4608 4609 $ext = my_strtolower($ext); 4610 4611 if($attachtypes[$ext]['icon']) 4612 { 4613 static $attach_icons_schemes = array(); 4614 if(!isset($attach_icons_schemes[$ext])) 4615 { 4616 $attach_icons_schemes[$ext] = parse_url($attachtypes[$ext]['icon']); 4617 if(!empty($attach_icons_schemes[$ext]['scheme'])) 4618 { 4619 $attach_icons_schemes[$ext] = $attachtypes[$ext]['icon']; 4620 } 4621 elseif(defined("IN_ADMINCP")) 4622 { 4623 $attach_icons_schemes[$ext] = str_replace("{theme}", "", $attachtypes[$ext]['icon']); 4624 if(my_substr($attach_icons_schemes[$ext], 0, 1) != "/") 4625 { 4626 $attach_icons_schemes[$ext] = "../".