[ Index ] |
PHP Cross Reference of MyBB 1.8.37 |
[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 if($user) 3260 { 3261 foreach($counters as $counter) 3262 { 3263 if(array_key_exists($counter, $changes)) 3264 { 3265 if(substr($changes[$counter], 0, 2) == "+-") 3266 { 3267 $changes[$counter] = substr($changes[$counter], 1); 3268 } 3269 // Adding or subtracting from previous value? 3270 if(substr($changes[$counter], 0, 1) == "+" || substr($changes[$counter], 0, 1) == "-") 3271 { 3272 if((int)$changes[$counter] != 0) 3273 { 3274 $update_query[$counter] = $user[$counter] + $changes[$counter]; 3275 } 3276 } 3277 else 3278 { 3279 $update_query[$counter] = $changes[$counter]; 3280 } 3281 3282 // Less than 0? That's bad 3283 if(isset($update_query[$counter]) && $update_query[$counter] < 0) 3284 { 3285 $update_query[$counter] = 0; 3286 } 3287 } 3288 } 3289 } 3290 3291 $db->free_result($query); 3292 3293 // Only update if we're actually doing something 3294 if(count($update_query) > 0) 3295 { 3296 $db->update_query("users", $update_query, "uid='{$uid}'"); 3297 } 3298 } 3299 3300 /** 3301 * Deletes a thread from the database 3302 * 3303 * @param int $tid The thread ID 3304 * @return bool 3305 */ 3306 function delete_thread($tid) 3307 { 3308 global $moderation; 3309 3310 if(!is_object($moderation)) 3311 { 3312 require_once MYBB_ROOT."inc/class_moderation.php"; 3313 $moderation = new Moderation; 3314 } 3315 3316 return $moderation->delete_thread($tid); 3317 } 3318 3319 /** 3320 * Deletes a post from the database 3321 * 3322 * @param int $pid The thread ID 3323 * @return bool 3324 */ 3325 function delete_post($pid) 3326 { 3327 global $moderation; 3328 3329 if(!is_object($moderation)) 3330 { 3331 require_once MYBB_ROOT."inc/class_moderation.php"; 3332 $moderation = new Moderation; 3333 } 3334 3335 return $moderation->delete_post($pid); 3336 } 3337 3338 /** 3339 * Builds a forum jump menu 3340 * 3341 * @param int $pid The parent forum to start with 3342 * @param int $selitem The selected item ID 3343 * @param int $addselect If we need to add select boxes to this cal or not 3344 * @param string $depth The current depth of forums we're at 3345 * @param int $showextras Whether or not to show extra items such as User CP, Forum home 3346 * @param boolean $showall Ignore the showinjump setting and show all forums (for moderation pages) 3347 * @param mixed $permissions deprecated 3348 * @param string $name The name of the forum jump 3349 * @return string Forum jump items 3350 */ 3351 function build_forum_jump($pid=0, $selitem=0, $addselect=1, $depth="", $showextras=1, $showall=false, $permissions="", $name="fid") 3352 { 3353 global $forum_cache, $jumpfcache, $permissioncache, $mybb, $forumjump, $forumjumpbits, $gobutton, $theme, $templates, $lang; 3354 3355 $pid = (int)$pid; 3356 3357 if(!is_array($jumpfcache)) 3358 { 3359 if(!is_array($forum_cache)) 3360 { 3361 cache_forums(); 3362 } 3363 3364 foreach($forum_cache as $fid => $forum) 3365 { 3366 if($forum['active'] != 0) 3367 { 3368 $jumpfcache[$forum['pid']][$forum['disporder']][$forum['fid']] = $forum; 3369 } 3370 } 3371 } 3372 3373 if(!is_array($permissioncache)) 3374 { 3375 $permissioncache = forum_permissions(); 3376 } 3377 3378 if(isset($jumpfcache[$pid]) && is_array($jumpfcache[$pid])) 3379 { 3380 foreach($jumpfcache[$pid] as $main) 3381 { 3382 foreach($main as $forum) 3383 { 3384 $perms = $permissioncache[$forum['fid']]; 3385 3386 if($forum['fid'] != "0" && ($perms['canview'] != 0 || $mybb->settings['hideprivateforums'] == 0) && $forum['linkto'] == '' && ($forum['showinjump'] != 0 || $showall == true)) 3387 { 3388 $optionselected = ""; 3389 3390 if($selitem == $forum['fid']) 3391 { 3392 $optionselected = 'selected="selected"'; 3393 } 3394 3395 $forum['name'] = htmlspecialchars_uni(strip_tags($forum['name'])); 3396 3397 eval("\$forumjumpbits .= \"".$templates->get("forumjump_bit")."\";"); 3398 3399 if($forum_cache[$forum['fid']]) 3400 { 3401 $newdepth = $depth."--"; 3402 $forumjumpbits .= build_forum_jump($forum['fid'], $selitem, 0, $newdepth, $showextras, $showall); 3403 } 3404 } 3405 } 3406 } 3407 } 3408 3409 if($addselect) 3410 { 3411 if($showextras == 0) 3412 { 3413 $template = "special"; 3414 } 3415 else 3416 { 3417 $template = "advanced"; 3418 3419 if(strpos(FORUM_URL, '.html') !== false) 3420 { 3421 $forum_link = "'".str_replace('{fid}', "'+option+'", FORUM_URL)."'"; 3422 } 3423 else 3424 { 3425 $forum_link = "'".str_replace('{fid}', "'+option", FORUM_URL); 3426 } 3427 } 3428 3429 eval("\$forumjump = \"".$templates->get("forumjump_".$template)."\";"); 3430 } 3431 3432 return $forumjump; 3433 } 3434 3435 /** 3436 * Returns the extension of a file. 3437 * 3438 * @param string $file The filename. 3439 * @return string The extension of the file. 3440 */ 3441 function get_extension($file) 3442 { 3443 return my_strtolower(my_substr(strrchr($file, "."), 1)); 3444 } 3445 3446 /** 3447 * Generates a random string. 3448 * 3449 * @param int $length The length of the string to generate. 3450 * @param bool $complex Whether to return complex string. Defaults to false 3451 * @return string The random string. 3452 */ 3453 function random_str($length=8, $complex=false) 3454 { 3455 $set = array_merge(range(0, 9), range('A', 'Z'), range('a', 'z')); 3456 $str = array(); 3457 3458 // Complex strings have always at least 3 characters, even if $length < 3 3459 if($complex == true) 3460 { 3461 // At least one number 3462 $str[] = $set[my_rand(0, 9)]; 3463 3464 // At least one big letter 3465 $str[] = $set[my_rand(10, 35)]; 3466 3467 // At least one small letter 3468 $str[] = $set[my_rand(36, 61)]; 3469 3470 $length -= 3; 3471 } 3472 3473 for($i = 0; $i < $length; ++$i) 3474 { 3475 $str[] = $set[my_rand(0, 61)]; 3476 } 3477 3478 // Make sure they're in random order and convert them to a string 3479 shuffle($str); 3480 3481 return implode($str); 3482 } 3483 3484 /** 3485 * Formats a username based on their display group 3486 * 3487 * @param string $username The username 3488 * @param int $usergroup The usergroup for the user 3489 * @param int $displaygroup The display group for the user 3490 * @return string The formatted username 3491 */ 3492 function format_name($username, $usergroup, $displaygroup=0) 3493 { 3494 global $groupscache, $cache, $plugins; 3495 3496 static $formattednames = array(); 3497 3498 if(!isset($formattednames[$username])) 3499 { 3500 if(!is_array($groupscache)) 3501 { 3502 $groupscache = $cache->read("usergroups"); 3503 } 3504 3505 if($displaygroup != 0) 3506 { 3507 $usergroup = $displaygroup; 3508 } 3509 3510 $format = "{username}"; 3511 3512 if(isset($groupscache[$usergroup])) 3513 { 3514 $ugroup = $groupscache[$usergroup]; 3515 3516 if(strpos($ugroup['namestyle'], "{username}") !== false) 3517 { 3518 $format = $ugroup['namestyle']; 3519 } 3520 } 3521 3522 $format = stripslashes($format); 3523 3524 $parameters = compact('username', 'usergroup', 'displaygroup', 'format'); 3525 3526 $parameters = $plugins->run_hooks('format_name', $parameters); 3527 3528 $format = $parameters['format']; 3529 3530 $formattednames[$username] = str_replace("{username}", $username, $format); 3531 } 3532 3533 return $formattednames[$username]; 3534 } 3535 3536 /** 3537 * Formats an avatar to a certain dimension 3538 * 3539 * @param string $avatar The avatar file name 3540 * @param string $dimensions Dimensions of the avatar, width x height (e.g. 44|44) 3541 * @param string $max_dimensions The maximum dimensions of the formatted avatar 3542 * @return array Information for the formatted avatar 3543 */ 3544 function format_avatar($avatar, $dimensions = '', $max_dimensions = '') 3545 { 3546 global $mybb, $theme; 3547 static $avatars; 3548 3549 if(!isset($avatars)) 3550 { 3551 $avatars = array(); 3552 } 3553 3554 if(my_strpos($avatar, '://') !== false && !$mybb->settings['allowremoteavatars']) 3555 { 3556 // Remote avatar, but remote avatars are disallowed. 3557 $avatar = null; 3558 } 3559 3560 if(!$avatar) 3561 { 3562 // Default avatar 3563 if(defined('IN_ADMINCP')) 3564 { 3565 $theme['imgdir'] = '../images'; 3566 } 3567 3568 $avatar = str_replace('{theme}', $theme['imgdir'], $mybb->settings['useravatar']); 3569 $dimensions = $mybb->settings['useravatardims']; 3570 } 3571 3572 if(!$max_dimensions) 3573 { 3574 $max_dimensions = $mybb->settings['maxavatardims']; 3575 } 3576 3577 // An empty key wouldn't work so we need to add a fall back 3578 $key = $dimensions; 3579 if(empty($key)) 3580 { 3581 $key = 'default'; 3582 } 3583 $key2 = $max_dimensions; 3584 if(empty($key2)) 3585 { 3586 $key2 = 'default'; 3587 } 3588 3589 if(isset($avatars[$avatar][$key][$key2])) 3590 { 3591 return $avatars[$avatar][$key][$key2]; 3592 } 3593 3594 $avatar_width_height = ''; 3595 3596 if($dimensions) 3597 { 3598 $dimensions = preg_split('/[|x]/', $dimensions); 3599 3600 if($dimensions[0] && $dimensions[1]) 3601 { 3602 list($max_width, $max_height) = preg_split('/[|x]/', $max_dimensions); 3603 3604 if(!empty($max_dimensions) && ($dimensions[0] > $max_width || $dimensions[1] > $max_height)) 3605 { 3606 require_once MYBB_ROOT."inc/functions_image.php"; 3607 $scaled_dimensions = scale_image($dimensions[0], $dimensions[1], $max_width, $max_height); 3608 $avatar_width_height = "width=\"{$scaled_dimensions['width']}\" height=\"{$scaled_dimensions['height']}\""; 3609 } 3610 else 3611 { 3612 $avatar_width_height = "width=\"{$dimensions[0]}\" height=\"{$dimensions[1]}\""; 3613 } 3614 } 3615 } 3616 3617 $avatars[$avatar][$key][$key2] = array( 3618 'image' => htmlspecialchars_uni($mybb->get_asset_url($avatar)), 3619 'width_height' => $avatar_width_height 3620 ); 3621 3622 return $avatars[$avatar][$key][$key2]; 3623 } 3624 3625 /** 3626 * Build the javascript based MyCode inserter. 3627 * 3628 * @param string $bind The ID of the textarea to bind to. Defaults to "message". 3629 * @param bool $smilies Whether to include smilies. Defaults to true. 3630 * 3631 * @return string The MyCode inserter 3632 */ 3633 function build_mycode_inserter($bind="message", $smilies = true) 3634 { 3635 global $db, $mybb, $theme, $templates, $lang, $plugins, $smiliecache, $cache; 3636 3637 if($mybb->settings['bbcodeinserter'] != 0) 3638 { 3639 $editor_lang_strings = array( 3640 "editor_bold" => "Bold", 3641 "editor_italic" => "Italic", 3642 "editor_underline" => "Underline", 3643 "editor_strikethrough" => "Strikethrough", 3644 "editor_subscript" => "Subscript", 3645 "editor_superscript" => "Superscript", 3646 "editor_alignleft" => "Align left", 3647 "editor_center" => "Center", 3648 "editor_alignright" => "Align right", 3649 "editor_justify" => "Justify", 3650 "editor_fontname" => "Font Name", 3651 "editor_fontsize" => "Font Size", 3652 "editor_fontcolor" => "Font Color", 3653 "editor_removeformatting" => "Remove Formatting", 3654 "editor_cut" => "Cut", 3655 "editor_cutnosupport" => "Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X", 3656 "editor_copy" => "Copy", 3657 "editor_copynosupport" => "Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C", 3658 "editor_paste" => "Paste", 3659 "editor_pastenosupport" => "Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V", 3660 "editor_pasteentertext" => "Paste your text inside the following box:", 3661 "editor_pastetext" => "PasteText", 3662 "editor_numlist" => "Numbered list", 3663 "editor_bullist" => "Bullet list", 3664 "editor_undo" => "Undo", 3665 "editor_redo" => "Redo", 3666 "editor_rows" => "Rows:", 3667 "editor_cols" => "Cols:", 3668 "editor_inserttable" => "Insert a table", 3669 "editor_inserthr" => "Insert a horizontal rule", 3670 "editor_code" => "Code", 3671 "editor_width" => "Width (optional):", 3672 "editor_height" => "Height (optional):", 3673 "editor_insertimg" => "Insert an image", 3674 "editor_email" => "E-mail:", 3675 "editor_insertemail" => "Insert an email", 3676 "editor_url" => "URL:", 3677 "editor_insertlink" => "Insert a link", 3678 "editor_unlink" => "Unlink", 3679 "editor_more" => "More", 3680 "editor_insertemoticon" => "Insert an emoticon", 3681 "editor_videourl" => "Video URL:", 3682 "editor_videotype" => "Video Type:", 3683 "editor_insert" => "Insert", 3684 "editor_insertyoutubevideo" => "Insert a YouTube video", 3685 "editor_currentdate" => "Insert current date", 3686 "editor_currenttime" => "Insert current time", 3687 "editor_print" => "Print", 3688 "editor_viewsource" => "View source", 3689 "editor_description" => "Description (optional):", 3690 "editor_enterimgurl" => "Enter the image URL:", 3691 "editor_enteremail" => "Enter the e-mail address:", 3692 "editor_enterdisplayedtext" => "Enter the displayed text:", 3693 "editor_enterurl" => "Enter URL:", 3694 "editor_enteryoutubeurl" => "Enter the YouTube video URL or ID:", 3695 "editor_insertquote" => "Insert a Quote", 3696 "editor_invalidyoutube" => "Invalid YouTube video", 3697 "editor_dailymotion" => "Dailymotion", 3698 "editor_metacafe" => "MetaCafe", 3699 "editor_mixer" => "Mixer", 3700 "editor_vimeo" => "Vimeo", 3701 "editor_youtube" => "Youtube", 3702 "editor_facebook" => "Facebook", 3703 "editor_liveleak" => "LiveLeak", 3704 "editor_insertvideo" => "Insert a video", 3705 "editor_php" => "PHP", 3706 "editor_maximize" => "Maximize" 3707 ); 3708 $editor_language = "(function ($) {\n$.sceditor.locale[\"mybblang\"] = {\n"; 3709 3710 $editor_lang_strings = $plugins->run_hooks("mycode_add_codebuttons", $editor_lang_strings); 3711 3712 $editor_languages_count = count($editor_lang_strings); 3713 $i = 0; 3714 foreach($editor_lang_strings as $lang_string => $key) 3715 { 3716 $i++; 3717 $js_lang_string = str_replace("\"", "\\\"", $key); 3718 $string = str_replace("\"", "\\\"", $lang->$lang_string); 3719 $editor_language .= "\t\"{$js_lang_string}\": \"{$string}\""; 3720 3721 if($i < $editor_languages_count) 3722 { 3723 $editor_language .= ","; 3724 } 3725 3726 $editor_language .= "\n"; 3727 } 3728 3729 $editor_language .= "}})(jQuery);"; 3730 3731 if(defined("IN_ADMINCP")) 3732 { 3733 global $page; 3734 $codeinsert = $page->build_codebuttons_editor($bind, $editor_language, $smilies); 3735 } 3736 else 3737 { 3738 // Smilies 3739 $emoticon = ""; 3740 $emoticons_enabled = "false"; 3741 if($smilies) 3742 { 3743 if(!$smiliecache) 3744 { 3745 if(!isset($smilie_cache) || !is_array($smilie_cache)) 3746 { 3747 $smilie_cache = $cache->read("smilies"); 3748 } 3749 foreach($smilie_cache as $smilie) 3750 { 3751 $smilie['image'] = str_replace("{theme}", $theme['imgdir'], $smilie['image']); 3752 $smiliecache[$smilie['sid']] = $smilie; 3753 } 3754 } 3755 3756 if($mybb->settings['smilieinserter'] && $mybb->settings['smilieinsertercols'] && $mybb->settings['smilieinsertertot'] && !empty($smiliecache)) 3757 { 3758 $emoticon = ",emoticon"; 3759 } 3760 $emoticons_enabled = "true"; 3761 3762 unset($smilie); 3763 3764 if(is_array($smiliecache)) 3765 { 3766 reset($smiliecache); 3767 3768 $dropdownsmilies = $moresmilies = $hiddensmilies = ""; 3769 $i = 0; 3770 3771 foreach($smiliecache as $smilie) 3772 { 3773 $finds = explode("\n", $smilie['find']); 3774 $finds_count = count($finds); 3775 3776 // Only show the first text to replace in the box 3777 $smilie['find'] = $finds[0]; 3778 3779 $find = str_replace(array('\\', '"'), array('\\\\', '\"'), htmlspecialchars_uni($smilie['find'])); 3780 $image = htmlspecialchars_uni($mybb->get_asset_url($smilie['image'])); 3781 $image = str_replace(array('\\', '"'), array('\\\\', '\"'), $image); 3782 3783 if(!$mybb->settings['smilieinserter'] || !$mybb->settings['smilieinsertercols'] || !$mybb->settings['smilieinsertertot'] || !$smilie['showclickable']) 3784 { 3785 $hiddensmilies .= '"'.$find.'": "'.$image.'",'; 3786 } 3787 elseif($i < $mybb->settings['smilieinsertertot']) 3788 { 3789 $dropdownsmilies .= '"'.$find.'": "'.$image.'",'; 3790 ++$i; 3791 } 3792 else 3793 { 3794 $moresmilies .= '"'.$find.'": "'.$image.'",'; 3795 } 3796 3797 for($j = 1; $j < $finds_count; ++$j) 3798 { 3799 $find = str_replace(array('\\', '"'), array('\\\\', '\"'), htmlspecialchars_uni($finds[$j])); 3800 $hiddensmilies .= '"'.$find.'": "'.$image.'",'; 3801 } 3802 } 3803 } 3804 } 3805 3806 $basic1 = $basic2 = $align = $font = $size = $color = $removeformat = $email = $link = $list = $code = $sourcemode = ""; 3807 3808 if($mybb->settings['allowbasicmycode'] == 1) 3809 { 3810 $basic1 = "bold,italic,underline,strike|"; 3811 $basic2 = "horizontalrule,"; 3812 } 3813 3814 if($mybb->settings['allowalignmycode'] == 1) 3815 { 3816 $align = "left,center,right,justify|"; 3817 } 3818 3819 if($mybb->settings['allowfontmycode'] == 1) 3820 { 3821 $font = "font,"; 3822 } 3823 3824 if($mybb->settings['allowsizemycode'] == 1) 3825 { 3826 $size = "size,"; 3827 } 3828 3829 if($mybb->settings['allowcolormycode'] == 1) 3830 { 3831 $color = "color,"; 3832 } 3833 3834 if($mybb->settings['allowfontmycode'] == 1 || $mybb->settings['allowsizemycode'] == 1 || $mybb->settings['allowcolormycode'] == 1) 3835 { 3836 $removeformat = "removeformat|"; 3837 } 3838 3839 if($mybb->settings['allowemailmycode'] == 1) 3840 { 3841 $email = "email,"; 3842 } 3843 3844 if($mybb->settings['allowlinkmycode'] == 1) 3845 { 3846 $link = "link,unlink"; 3847 } 3848 3849 if($mybb->settings['allowlistmycode'] == 1) 3850 { 3851 $list = "bulletlist,orderedlist|"; 3852 } 3853 3854 if($mybb->settings['allowcodemycode'] == 1) 3855 { 3856 $code = "code,php,"; 3857 } 3858 3859 if($mybb->user['sourceeditor'] == 1) 3860 { 3861 $sourcemode = "MyBBEditor.sourceMode(true);"; 3862 } 3863 3864 eval("\$codeinsert = \"".$templates->get("codebuttons")."\";"); 3865 } 3866 } 3867 3868 return $codeinsert; 3869 } 3870 3871 /** 3872 * @param int $tid 3873 * @param array $postoptions The options carried with form submit 3874 * 3875 * @return string Predefined / updated subscription method of the thread for the user 3876 */ 3877 function get_subscription_method($tid = 0, $postoptions = array()) 3878 { 3879 global $mybb; 3880 3881 $subscription_methods = array('', 'none', 'email', 'pm'); // Define methods 3882 $subscription_method = (int)$mybb->user['subscriptionmethod']; // Set user default 3883 3884 // If no user default method available then reset method 3885 if(!$subscription_method) 3886 { 3887 $subscription_method = 0; 3888 } 3889 3890 // Return user default if no thread id available, in case 3891 if(!(int)$tid || (int)$tid <= 0) 3892 { 3893 return $subscription_methods[$subscription_method]; 3894 } 3895 3896 // If method not predefined set using data from database 3897 if(isset($postoptions['subscriptionmethod'])) 3898 { 3899 $method = trim($postoptions['subscriptionmethod']); 3900 return (in_array($method, $subscription_methods)) ? $method : $subscription_methods[0]; 3901 } 3902 else 3903 { 3904 global $db; 3905 3906 $query = $db->simple_select("threadsubscriptions", "tid, notification", "tid='".(int)$tid."' AND uid='".$mybb->user['uid']."'", array('limit' => 1)); 3907 $subscription = $db->fetch_array($query); 3908 3909 if($subscription) 3910 { 3911 $subscription_method = (int)$subscription['notification'] + 1; 3912 } 3913 } 3914 3915 return $subscription_methods[$subscription_method]; 3916 } 3917 3918 /** 3919 * Build the javascript clickable smilie inserter 3920 * 3921 * @return string The clickable smilies list 3922 */ 3923 function build_clickable_smilies() 3924 { 3925 global $cache, $smiliecache, $theme, $templates, $lang, $mybb, $smiliecount; 3926 3927 if($mybb->settings['smilieinserter'] != 0 && $mybb->settings['smilieinsertercols'] && $mybb->settings['smilieinsertertot']) 3928 { 3929 if(!$smiliecount) 3930 { 3931 $smilie_cache = $cache->read("smilies"); 3932 $smiliecount = count($smilie_cache); 3933 } 3934 3935 if(!$smiliecache) 3936 { 3937 if(!is_array($smilie_cache)) 3938 { 3939 $smilie_cache = $cache->read("smilies"); 3940 } 3941 foreach($smilie_cache as $smilie) 3942 { 3943 $smilie['image'] = str_replace("{theme}", $theme['imgdir'], $smilie['image']); 3944 $smiliecache[$smilie['sid']] = $smilie; 3945 } 3946 } 3947 3948 unset($smilie); 3949 3950 if(is_array($smiliecache)) 3951 { 3952 reset($smiliecache); 3953 3954 $getmore = ''; 3955 if($mybb->settings['smilieinsertertot'] >= $smiliecount) 3956 { 3957 $mybb->settings['smilieinsertertot'] = $smiliecount; 3958 } 3959 else if($mybb->settings['smilieinsertertot'] < $smiliecount) 3960 { 3961 $smiliecount = $mybb->settings['smilieinsertertot']; 3962 eval("\$getmore = \"".$templates->get("smilieinsert_getmore")."\";"); 3963 } 3964 3965 $smilies = $smilie_icons = ''; 3966 $counter = 0; 3967 $i = 0; 3968 3969 $extra_class = ''; 3970 foreach($smiliecache as $smilie) 3971 { 3972 if($i < $mybb->settings['smilieinsertertot'] && $smilie['showclickable'] != 0) 3973 { 3974 $smilie['image'] = str_replace("{theme}", $theme['imgdir'], $smilie['image']); 3975 $smilie['image'] = htmlspecialchars_uni($mybb->get_asset_url($smilie['image'])); 3976 $smilie['name'] = htmlspecialchars_uni($smilie['name']); 3977 3978 // Only show the first text to replace in the box 3979 $temp = explode("\n", $smilie['find']); // assign to temporary variable for php 5.3 compatibility 3980 $smilie['find'] = $temp[0]; 3981 3982 $find = str_replace(array('\\', "'"), array('\\\\', "\'"), htmlspecialchars_uni($smilie['find'])); 3983 3984 $onclick = " onclick=\"MyBBEditor.insertText(' $find ');\""; 3985 $extra_class = ' smilie_pointer'; 3986 eval('$smilie = "'.$templates->get('smilie', 1, 0).'";'); 3987 eval("\$smilie_icons .= \"".$templates->get("smilieinsert_smilie")."\";"); 3988 ++$i; 3989 ++$counter; 3990 3991 if($counter == $mybb->settings['smilieinsertercols']) 3992 { 3993 $counter = 0; 3994 eval("\$smilies .= \"".$templates->get("smilieinsert_row")."\";"); 3995 $smilie_icons = ''; 3996 } 3997 } 3998 } 3999 4000 if($counter != 0) 4001 { 4002 $colspan = $mybb->settings['smilieinsertercols'] - $counter; 4003 eval("\$smilies .= \"".$templates->get("smilieinsert_row_empty")."\";"); 4004 } 4005 4006 eval("\$clickablesmilies = \"".$templates->get("smilieinsert")."\";"); 4007 } 4008 else 4009 { 4010 $clickablesmilies = ""; 4011 } 4012 } 4013 else 4014 { 4015 $clickablesmilies = ""; 4016 } 4017 4018 return $clickablesmilies; 4019 } 4020 4021 /** 4022 * Builds thread prefixes and returns a selected prefix (or all) 4023 * 4024 * @param int $pid The prefix ID (0 to return all) 4025 * @return array The thread prefix's values (or all thread prefixes) 4026 */ 4027 function build_prefixes($pid=0) 4028 { 4029 global $cache; 4030 static $prefixes_cache; 4031 4032 if(is_array($prefixes_cache)) 4033 { 4034 if($pid > 0 && is_array($prefixes_cache[$pid])) 4035 { 4036 return $prefixes_cache[$pid]; 4037 } 4038 4039 return $prefixes_cache; 4040 } 4041 4042 $prefix_cache = $cache->read("threadprefixes"); 4043 4044 if(!is_array($prefix_cache)) 4045 { 4046 // No cache 4047 $prefix_cache = $cache->read("threadprefixes", true); 4048 4049 if(!is_array($prefix_cache)) 4050 { 4051 return array(); 4052 } 4053 } 4054 4055 $prefixes_cache = array(); 4056 foreach($prefix_cache as $prefix) 4057 { 4058 $prefixes_cache[$prefix['pid']] = $prefix; 4059 } 4060 4061 if($pid != 0 && is_array($prefixes_cache[$pid])) 4062 { 4063 return $prefixes_cache[$pid]; 4064 } 4065 else if(!empty($prefixes_cache)) 4066 { 4067 return $prefixes_cache; 4068 } 4069 4070 return false; 4071 } 4072 4073 /** 4074 * Build the thread prefix selection menu for the current user 4075 * 4076 * @param int|string $fid The forum ID (integer ID or string all) 4077 * @param int|string $selected_pid The selected prefix ID (integer ID or string any) 4078 * @param int $multiple Allow multiple prefix selection 4079 * @param int $previous_pid The previously selected prefix ID 4080 * @return string The thread prefix selection menu 4081 */ 4082 function build_prefix_select($fid, $selected_pid=0, $multiple=0, $previous_pid=0) 4083 { 4084 global $cache, $db, $lang, $mybb, $templates; 4085 4086 if($fid != 'all') 4087 { 4088 $fid = (int)$fid; 4089 } 4090 4091 $prefix_cache = build_prefixes(0); 4092 if(empty($prefix_cache)) 4093 { 4094 // We've got no prefixes to show 4095 return ''; 4096 } 4097 4098 // Go through each of our prefixes and decide which ones we can use 4099 $prefixes = array(); 4100 foreach($prefix_cache as $prefix) 4101 { 4102 if($fid != "all" && $prefix['forums'] != "-1") 4103 { 4104 // Decide whether this prefix can be used in our forum 4105 $forums = explode(",", $prefix['forums']); 4106 4107 if(!in_array($fid, $forums) && $prefix['pid'] != $previous_pid) 4108 { 4109 // This prefix is not in our forum list 4110 continue; 4111 } 4112 } 4113 4114 if(is_member($prefix['groups']) || $prefix['pid'] == $previous_pid) 4115 { 4116 // The current user can use this prefix 4117 $prefixes[$prefix['pid']] = $prefix; 4118 } 4119 } 4120 4121 if(empty($prefixes)) 4122 { 4123 return ''; 4124 } 4125 4126 $prefixselect = $prefixselect_prefix = ''; 4127 4128 if($multiple == 1) 4129 { 4130 $any_selected = ""; 4131 if($selected_pid == 'any') 4132 { 4133 $any_selected = " selected=\"selected\""; 4134 } 4135 } 4136 4137 $default_selected = ""; 4138 if(((int)$selected_pid == 0) && $selected_pid != 'any') 4139 { 4140 $default_selected = " selected=\"selected\""; 4141 } 4142 4143 foreach($prefixes as $prefix) 4144 { 4145 $selected = ""; 4146 if($prefix['pid'] == $selected_pid) 4147 { 4148 $selected = " selected=\"selected\""; 4149 } 4150 4151 $prefix['prefix'] = htmlspecialchars_uni($prefix['prefix']); 4152 eval("\$prefixselect_prefix .= \"".$templates->get("post_prefixselect_prefix")."\";"); 4153 } 4154 4155 if($multiple != 0) 4156 { 4157 eval("\$prefixselect = \"".$templates->get("post_prefixselect_multiple")."\";"); 4158 } 4159 else 4160 { 4161 eval("\$prefixselect = \"".$templates->get("post_prefixselect_single")."\";"); 4162 } 4163 4164 return $prefixselect; 4165 } 4166 4167 /** 4168 * Build the thread prefix selection menu for a forum without group permission checks 4169 * 4170 * @param int $fid The forum ID (integer ID) 4171 * @param int $selected_pid The selected prefix ID (integer ID) 4172 * @return string The thread prefix selection menu 4173 */ 4174 function build_forum_prefix_select($fid, $selected_pid=0) 4175 { 4176 global $cache, $db, $lang, $mybb, $templates; 4177 4178 $fid = (int)$fid; 4179 4180 $prefix_cache = build_prefixes(0); 4181 if(empty($prefix_cache)) 4182 { 4183 // We've got no prefixes to show 4184 return ''; 4185 } 4186 4187 // Go through each of our prefixes and decide which ones we can use 4188 $prefixes = array(); 4189 foreach($prefix_cache as $prefix) 4190 { 4191 if($prefix['forums'] != "-1") 4192 { 4193 // Decide whether this prefix can be used in our forum 4194 $forums = explode(",", $prefix['forums']); 4195 4196 if(in_array($fid, $forums)) 4197 { 4198 // This forum can use this prefix! 4199 $prefixes[$prefix['pid']] = $prefix; 4200 } 4201 } 4202 else 4203 { 4204 // This prefix is for anybody to use... 4205 $prefixes[$prefix['pid']] = $prefix; 4206 } 4207 } 4208 4209 if(empty($prefixes)) 4210 { 4211 return ''; 4212 } 4213 4214 $default_selected = array('all' => '', 'none' => '', 'any' => ''); 4215 $selected_pid = (int)$selected_pid; 4216 4217 if($selected_pid == 0) 4218 { 4219 $default_selected['all'] = ' selected="selected"'; 4220 } 4221 else if($selected_pid == -1) 4222 { 4223 $default_selected['none'] = ' selected="selected"'; 4224 } 4225 else if($selected_pid == -2) 4226 { 4227 $default_selected['any'] = ' selected="selected"'; 4228 } 4229 4230 $prefixselect_prefix = ''; 4231 foreach($prefixes as $prefix) 4232 { 4233 $selected = ''; 4234 if($prefix['pid'] == $selected_pid) 4235 { 4236 $selected = ' selected="selected"'; 4237 } 4238 4239 $prefix['prefix'] = htmlspecialchars_uni($prefix['prefix']); 4240 eval('$prefixselect_prefix .= "'.$templates->get("forumdisplay_threadlist_prefixes_prefix").'";'); 4241 } 4242 4243 eval('$prefixselect = "'.$templates->get("forumdisplay_threadlist_prefixes").'";'); 4244 return $prefixselect; 4245 } 4246 4247 /** 4248 * Gzip encodes text to a specified level 4249 * 4250 * @param string $contents The string to encode 4251 * @param int $level The level (1-9) to encode at 4252 * @return string The encoded string 4253 */ 4254 function gzip_encode($contents, $level=1) 4255 { 4256 if(function_exists("gzcompress") && function_exists("crc32") && !headers_sent() && !(ini_get('output_buffering') && my_strpos(' '.ini_get('output_handler'), 'ob_gzhandler'))) 4257 { 4258 $httpaccept_encoding = ''; 4259 4260 if(isset($_SERVER['HTTP_ACCEPT_ENCODING'])) 4261 { 4262 $httpaccept_encoding = $_SERVER['HTTP_ACCEPT_ENCODING']; 4263 } 4264 4265 if(my_strpos(" ".$httpaccept_encoding, "x-gzip")) 4266 { 4267 $encoding = "x-gzip"; 4268 } 4269 4270 if(my_strpos(" ".$httpaccept_encoding, "gzip")) 4271 { 4272 $encoding = "gzip"; 4273 } 4274 4275 if(isset($encoding)) 4276 { 4277 header("Content-Encoding: $encoding"); 4278 4279 if(function_exists("gzencode")) 4280 { 4281 $contents = gzencode($contents, $level); 4282 } 4283 else 4284 { 4285 $size = strlen($contents); 4286 $crc = crc32($contents); 4287 $gzdata = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff"; 4288 $gzdata .= my_substr(gzcompress($contents, $level), 2, -4); 4289 $gzdata .= pack("V", $crc); 4290 $gzdata .= pack("V", $size); 4291 $contents = $gzdata; 4292 } 4293 } 4294 } 4295 4296 return $contents; 4297 } 4298 4299 /** 4300 * Log the actions of a moderator. 4301 * 4302 * @param array $data The data of the moderator's action. 4303 * @param string $action The message to enter for the action the moderator performed. 4304 */ 4305 function log_moderator_action($data, $action="") 4306 { 4307 global $mybb, $db, $session; 4308 4309 $fid = 0; 4310 if(isset($data['fid'])) 4311 { 4312 $fid = (int)$data['fid']; 4313 unset($data['fid']); 4314 } 4315 4316 $tid = 0; 4317 if(isset($data['tid'])) 4318 { 4319 $tid = (int)$data['tid']; 4320 unset($data['tid']); 4321 } 4322 4323 $pid = 0; 4324 if(isset($data['pid'])) 4325 { 4326 $pid = (int)$data['pid']; 4327 unset($data['pid']); 4328 } 4329 4330 $tids = array(); 4331 if(isset($data['tids'])) 4332 { 4333 $tids = (array)$data['tids']; 4334 unset($data['tids']); 4335 } 4336 4337 // Any remaining extra data - we my_serialize and insert in to its own column 4338 if(is_array($data)) 4339 { 4340 $data = my_serialize($data); 4341 } 4342 4343 $sql_array = array( 4344 "uid" => (int)$mybb->user['uid'], 4345 "dateline" => TIME_NOW, 4346 "fid" => (int)$fid, 4347 "tid" => $tid, 4348 "pid" => $pid, 4349 "action" => $db->escape_string($action), 4350 "data" => $db->escape_string($data), 4351 "ipaddress" => $db->escape_binary($session->packedip) 4352 ); 4353 4354 if($tids) 4355 { 4356 $multiple_sql_array = array(); 4357 4358 foreach($tids as $tid) 4359 { 4360 $sql_array['tid'] = (int)$tid; 4361 $multiple_sql_array[] = $sql_array; 4362 } 4363 4364 $db->insert_query_multiple("moderatorlog", $multiple_sql_array); 4365 } 4366 else 4367 { 4368 $db->insert_query("moderatorlog", $sql_array); 4369 } 4370 } 4371 4372 /** 4373 * Get the formatted reputation for a user. 4374 * 4375 * @param int $reputation The reputation value 4376 * @param int $uid The user ID (if not specified, the generated reputation will not be a link) 4377 * @return string The formatted repuation 4378 */ 4379 function get_reputation($reputation, $uid=0) 4380 { 4381 global $theme, $templates; 4382 4383 $display_reputation = $reputation_class = ''; 4384 if($reputation < 0) 4385 { 4386 $reputation_class = "reputation_negative"; 4387 } 4388 elseif($reputation > 0) 4389 { 4390 $reputation_class = "reputation_positive"; 4391 } 4392 else 4393 { 4394 $reputation_class = "reputation_neutral"; 4395 } 4396 4397 $reputation = my_number_format($reputation); 4398 4399 if($uid != 0) 4400 { 4401 eval("\$display_reputation = \"".$templates->get("postbit_reputation_formatted_link")."\";"); 4402 } 4403 else 4404 { 4405 eval("\$display_reputation = \"".$templates->get("postbit_reputation_formatted")."\";"); 4406 } 4407 4408 return $display_reputation; 4409 } 4410 4411 /** 4412 * Fetch a color coded version of a warning level (based on it's percentage) 4413 * 4414 * @param int $level The warning level (percentage of 100) 4415 * @return string Formatted warning level 4416 */ 4417 function get_colored_warning_level($level) 4418 { 4419 global $templates; 4420 4421 $warning_class = ''; 4422 if($level >= 80) 4423 { 4424 $warning_class = "high_warning"; 4425 } 4426 else if($level >= 50) 4427 { 4428 $warning_class = "moderate_warning"; 4429 } 4430 else if($level >= 25) 4431 { 4432 $warning_class = "low_warning"; 4433 } 4434 else 4435 { 4436 $warning_class = "normal_warning"; 4437 } 4438 4439 eval("\$level = \"".$templates->get("postbit_warninglevel_formatted")."\";"); 4440 return $level; 4441 } 4442 4443 /** 4444 * Fetch the IP address of the current user. 4445 * 4446 * @return string The IP address. 4447 */ 4448 function get_ip() 4449 { 4450 global $mybb, $plugins; 4451 4452 $ip = strtolower($_SERVER['REMOTE_ADDR']); 4453 4454 if($mybb->settings['ip_forwarded_check']) 4455 { 4456 $addresses = array(); 4457 4458 if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])) 4459 { 4460 $addresses = explode(',', strtolower($_SERVER['HTTP_X_FORWARDED_FOR'])); 4461 } 4462 elseif(isset($_SERVER['HTTP_X_REAL_IP'])) 4463 { 4464 $addresses = explode(',', strtolower($_SERVER['HTTP_X_REAL_IP'])); 4465 } 4466 4467 if(is_array($addresses)) 4468 { 4469 foreach($addresses as $val) 4470 { 4471 $val = trim($val); 4472 // Validate IP address and exclude private addresses 4473 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)) 4474 { 4475 $ip = $val; 4476 break; 4477 } 4478 } 4479 } 4480 } 4481 4482 if(!$ip) 4483 { 4484 if(isset($_SERVER['HTTP_CLIENT_IP'])) 4485 { 4486 $ip = strtolower($_SERVER['HTTP_CLIENT_IP']); 4487 } 4488 } 4489 4490 if($plugins) 4491 { 4492 $ip_array = array("ip" => &$ip); // Used for backwards compatibility on this hook with the updated run_hooks() function. 4493 $plugins->run_hooks("get_ip", $ip_array); 4494 } 4495 4496 return $ip; 4497 } 4498 4499 /** 4500 * Fetch the friendly size (GB, MB, KB, B) for a specified file size. 4501 * 4502 * @param int $size The size in bytes 4503 * @return string The friendly file size 4504 */ 4505 function get_friendly_size($size) 4506 { 4507 global $lang; 4508 4509 if(!is_numeric($size)) 4510 { 4511 return $lang->na; 4512 } 4513 4514 // Yottabyte (1024 Zettabytes) 4515 if($size >= 1208925819614629174706176) 4516 { 4517 $size = my_number_format(round(($size / 1208925819614629174706176), 2))." ".$lang->size_yb; 4518 } 4519 // Zetabyte (1024 Exabytes) 4520 elseif($size >= 1180591620717411303424) 4521 { 4522 $size = my_number_format(round(($size / 1180591620717411303424), 2))." ".$lang->size_zb; 4523 } 4524 // Exabyte (1024 Petabytes) 4525 elseif($size >= 1152921504606846976) 4526 { 4527 $size = my_number_format(round(($size / 1152921504606846976), 2))." ".$lang->size_eb; 4528 } 4529 // Petabyte (1024 Terabytes) 4530 elseif($size >= 1125899906842624) 4531 { 4532 $size = my_number_format(round(($size / 1125899906842624), 2))." ".$lang->size_pb; 4533 } 4534 // Terabyte (1024 Gigabytes) 4535 elseif($size >= 1099511627776) 4536 { 4537 $size = my_number_format(round(($size / 1099511627776), 2))." ".$lang->size_tb; 4538 } 4539 // Gigabyte (1024 Megabytes) 4540 elseif($size >= 1073741824) 4541 { 4542 $size = my_number_format(round(($size / 1073741824), 2))." ".$lang->size_gb; 4543 } 4544 // Megabyte (1024 Kilobytes) 4545 elseif($size >= 1048576) 4546 { 4547 $size = my_number_format(round(($size / 1048576), 2))." ".$lang->size_mb; 4548 } 4549 // Kilobyte (1024 bytes) 4550 elseif($size >= 1024) 4551 { 4552 $size = my_number_format(round(($size / 1024), 2))." ".$lang->size_kb; 4553 } 4554 elseif($size == 0) 4555 { 4556 $size = "0 ".$lang->size_bytes; 4557 } 4558 else 4559 { 4560 $size = my_number_format($size)." ".$lang->size_bytes; 4561 } 4562 4563 return $size; 4564 } 4565 4566 /** 4567 * Format a decimal number in to microseconds, milliseconds, or seconds. 4568 * 4569 * @param int $time The time in microseconds 4570 * @return string The friendly time duration 4571 */ 4572 function format_time_duration($time) 4573 { 4574 global $lang; 4575 4576 if(!is_numeric($time)) 4577 { 4578 return $lang->na; 4579 } 4580 4581 if(round(1000000 * $time, 2) < 1000) 4582 { 4583 $time = number_format(round(1000000 * $time, 2))." μs"; 4584 } 4585 elseif(round(1000000 * $time, 2) >= 1000 && round(1000000 * $time, 2) < 1000000) 4586 { 4587 $time = number_format(round((1000 * $time), 2))." ms"; 4588 } 4589 else 4590 { 4591 $time = round($time, 3)." seconds"; 4592 } 4593 4594 return $time; 4595 } 4596 4597 /** 4598 * Get the attachment icon for a specific file extension 4599 * 4600 * @param string $ext The file extension 4601 * @return string The attachment icon 4602 */ 4603 function get_attachment_icon($ext) 4604 { 4605 global $cache, $attachtypes, $theme, $templates, $lang, $mybb; 4606 4607 if(!$attachtypes) 4608 { 4609 $attachtypes = $cache->read("attachtypes"); 4610 } 4611 4612 $ext = my_strtolower($ext); 4613 4614 if($attachtypes[$ext]['icon']) 4615 { 4616 static $attach_icons_schemes = array(); 4617 if(!isset($attach_icons_schemes[$ext])) 4618 { 4619 $attach_icons_schemes[$ext] = parse_url($attachtypes[$ext]['icon']); 4620 if(!empty($attach_icons_schemes[$ext]['scheme'])) 4621 { 4622 $attach_icons_schemes[$ext] = $attachtypes[$ext]['icon']; 4623 } 4624 elseif(defined("IN_ADMINCP")) 4625 { 4626 $attach_icons_schemes[$ext] = str_replace("{theme}", "", $attachtypes[$ext]['icon']); 4627 if(my_substr($attach_icons_schemes[$ext], 0, 1) != "/") 4628 { 4629 $attach_icons_schemes[$ext] = "../".$attach_icons_schemes[$ext]; 4630 } 4631 } 4632 elseif(defined("IN_PORTAL")) 4633 { 4634 global $change_dir; 4635 $attach_icons_schemes[$ext] = $change_dir."/".str_replace("{theme}", $theme['imgdir'], $attachtypes[$ext]['icon']); 4636 $attach_icons_schemes[$ext] = $mybb->get_asset_url($attach_icons_schemes[$ext]); 4637 } 4638 else 4639 { 4640 $attach_icons_schemes[$ext] = str_replace("{theme}", $theme['imgdir'], $attachtypes[$ext]['icon']); 4641 $attach_icons_schemes[$ext] = $mybb->get_asset_url($attach_icons_schemes[$ext]); 4642 } 4643 } 4644 4645 $icon = $attach_icons_schemes[$ext]; 4646 4647 $name = htmlspecialchars_uni($attachtypes[$ext]['name']); 4648 } 4649 else 4650 { 4651 if(defined("IN_ADMINCP")) 4652 { 4653 $theme['imgdir'] = "../images"; 4654 } 4655 else if(defined("IN_PORTAL")) 4656 { 4657 global $change_dir; 4658 $theme['imgdir'] = "{$change_dir}/images"; 4659 } 4660 4661 $icon = "{$theme['imgdir']}/attachtypes/unknown.png"; 4662 4663 $name = $lang->unknown; 4664 } 4665 4666 $icon = htmlspecialchars_uni($icon); 4667 eval("\$attachment_icon = \"".$templates->get("attachment_icon")."\";"); 4668 return $attachment_icon; 4669 } 4670 4671 /** 4672 * Get a list of the unviewable forums for the current user 4673 * 4674 * @param boolean $only_readable_threads Set to true to only fetch those forums for which users can actually read a thread in. 4675 * @return string Comma separated values list of the forum IDs which the user cannot view 4676 */ 4677 function get_unviewable_forums($only_readable_threads=false) 4678 { 4679 global $forum_cache, $permissioncache, $mybb; 4680 4681 if(!is_array($forum_cache)) 4682 { 4683 cache_forums(); 4684 } 4685 4686 if(!is_array($permissioncache)) 4687 { 4688 $permissioncache = forum_permissions(); 4689 } 4690 4691 $unviewable = array(); 4692 foreach($forum_cache as $fid => $forum) 4693 { 4694 if($permissioncache[$forum['fid']]) 4695 { 4696 $perms = $permissioncache[$forum['fid']]; 4697 } 4698 else 4699 { 4700 $perms = $mybb->usergroup; 4701 } 4702 4703 $pwverified = 1; 4704 4705 4706 if(!forum_password_validated($forum, true)) 4707 { 4708 $pwverified = 0; 4709 } 4710 else 4711 { 4712 // Check parents for passwords 4713 $parents = explode(",", $forum['parentlist']); 4714 foreach($parents as $parent) 4715 { 4716 if(!forum_password_validated($forum_cache[$parent], true)) 4717 { 4718 $pwverified = 0; 4719 break; 4720 } 4721 } 4722 } 4723 4724 if($perms['canview'] == 0 || $pwverified == 0 || ($only_readable_threads == true && $perms['canviewthreads'] == 0)) 4725 { 4726 $unviewable[] = $forum['fid']; 4727 } 4728 } 4729 4730 $unviewableforums = implode(',', $unviewable); 4731 4732 return $unviewableforums; 4733 } 4734 4735 /** 4736 * Fixes mktime for dates earlier than 1970 4737 * 4738 * @param string $format The date format to use 4739 * @param int $year The year of the date 4740 * @return string The correct date format 4741 */ 4742 function fix_mktime($format, $year) 4743 { 4744 // Our little work around for the date < 1970 thing. 4745 // -2 idea provided by Matt Light (http://www.mephex.com) 4746 $format = str_replace("Y", $year, $format); 4747 $format = str_replace("y", my_substr($year, -2), $format); 4748 4749 return $format; 4750 } 4751 4752 /** 4753 * Build the breadcrumb navigation trail from the specified items 4754 * 4755 * @return string The formatted breadcrumb navigation trail 4756 */ 4757 function build_breadcrumb() 4758 { 4759 global $nav, $navbits, $templates, $theme, $lang, $mybb; 4760 4761 eval("\$navsep = \"".$templates->get("nav_sep")."\";"); 4762 4763 $i = 0; 4764 $activesep = ''; 4765 4766 if(is_array($navbits)) 4767 { 4768 reset($navbits); 4769 foreach($navbits as $key => $navbit) 4770 { 4771 if(isset($navbits[$key+1])) 4772 { 4773 if(isset($navbits[$key+2])) 4774 { 4775 $sep = $navsep; 4776 } 4777 else 4778 { 4779 $sep = ""; 4780 } 4781 4782 $multipage = null; 4783 $multipage_dropdown = null; 4784 if(!empty($navbit['multipage'])) 4785 { 4786 if(!$mybb->settings['threadsperpage'] || (int)$mybb->settings['threadsperpage'] < 1) 4787 { 4788 $mybb->settings['threadsperpage'] = 20; 4789 } 4790 4791 $multipage = multipage($navbit['multipage']['num_threads'], $mybb->settings['threadsperpage'], $navbit['multipage']['current_page'], $navbit['multipage']['url'], true); 4792 if($multipage) 4793 { 4794 ++$i; 4795 eval("\$multipage_dropdown = \"".$templates->get("nav_dropdown")."\";"); 4796 $sep = $multipage_dropdown.$sep; 4797 } 4798 } 4799 4800 // Replace page 1 URLs 4801 $navbit['url'] = str_replace("-page-1.html", ".html", $navbit['url']); 4802 $navbit['url'] = preg_replace("/&page=1$/", "", $navbit['url']); 4803 4804 eval("\$nav .= \"".$templates->get("nav_bit")."\";"); 4805 } 4806 } 4807 $navsize = count($navbits); 4808 $navbit = $navbits[$navsize-1]; 4809 } 4810 4811 if($nav) 4812 { 4813 eval("\$activesep = \"".$templates->get("nav_sep_active")."\";"); 4814 } 4815 4816 eval("\$activebit = \"".$templates->get("nav_bit_active")."\";"); 4817 eval("\$donenav = \"".$templates->get("nav")."\";"); 4818 4819 return $donenav; 4820 } 4821 4822 /** 4823 * Add a breadcrumb menu item to the list. 4824 * 4825 * @param string $name The name of the item to add 4826 * @param string $url The URL of the item to add 4827 */ 4828 function add_breadcrumb($name, $url="") 4829 { 4830 global $navbits; 4831 4832 $navsize = count($navbits); 4833 $navbits[$navsize]['name'] = $name; 4834 $navbits[$navsize]['url'] = $url; 4835 } 4836 4837 /** 4838 * Build the forum breadcrumb nagiation (the navigation to a specific forum including all parent forums) 4839 * 4840 * @param int $fid The forum ID to build the navigation for 4841 * @param array $multipage The multipage drop down array of information 4842 * @return int Returns 1 in every case. Kept for compatibility 4843 */ 4844 function build_forum_breadcrumb($fid, $multipage=array()) 4845 { 4846 global $pforumcache, $currentitem, $forum_cache, $navbits, $lang, $base_url, $archiveurl; 4847 4848 if(!$pforumcache) 4849 { 4850 if(!is_array($forum_cache)) 4851 { 4852 cache_forums(); 4853 } 4854 4855 foreach($forum_cache as $key => $val) 4856 { 4857 $pforumcache[$val['fid']][$val['pid']] = $val; 4858 } 4859 } 4860 4861 if(is_array($pforumcache[$fid])) 4862 { 4863 foreach($pforumcache[$fid] as $key => $forumnav) 4864 { 4865 if($fid == $forumnav['fid']) 4866 { 4867 if(!empty($pforumcache[$forumnav['pid']])) 4868 { 4869 build_forum_breadcrumb($forumnav['pid']); 4870 } 4871 4872 $navsize = count($navbits); 4873 // Convert & to & 4874 $navbits[$navsize]['name'] = preg_replace("#&(?!\#[0-9]+;)#si", "&", $forumnav['name']); 4875 4876 if(defined("IN_ARCHIVE")) 4877 { 4878 // Set up link to forum in breadcrumb. 4879 if($pforumcache[$fid][$forumnav['pid']]['type'] == 'f' || $pforumcache[$fid][$forumnav['pid']]['type'] == 'c') 4880 { 4881 $navbits[$navsize]['url'] = "{$base_url}forum-".$forumnav['fid'].".html"; 4882 } 4883 else 4884 { 4885 $navbits[$navsize]['url'] = $archiveurl."/index.php"; 4886 } 4887 } 4888 elseif(!empty($multipage)) 4889 { 4890 $navbits[$navsize]['url'] = get_forum_link($forumnav['fid'], $multipage['current_page']); 4891 4892 $navbits[$navsize]['multipage'] = $multipage; 4893 $navbits[$navsize]['multipage']['url'] = str_replace('{fid}', $forumnav['fid'], FORUM_URL_PAGED); 4894 } 4895 else 4896 { 4897 $navbits[$navsize]['url'] = get_forum_link($forumnav['fid']); 4898 } 4899 } 4900 } 4901 } 4902 4903 return 1; 4904 } 4905 4906 /** 4907 * Resets the breadcrumb navigation to the first item, and clears the rest 4908 */ 4909 function reset_breadcrumb() 4910 { 4911 global $navbits; 4912 4913 $newnav[0]['name'] = $navbits[0]['name']; 4914 $newnav[0]['url'] = $navbits[0]['url']; 4915 if(!empty($navbits[0]['options'])) 4916 { 4917 $newnav[0]['options'] = $navbits[0]['options']; 4918 } 4919 4920 unset($GLOBALS['navbits']); 4921 $GLOBALS['navbits'] = $newnav; 4922 } 4923 4924 /** 4925 * Builds a URL to an archive mode page 4926 * 4927 * @param string $type The type of page (thread|announcement|forum) 4928 * @param int $id The ID of the item 4929 * @return string The URL 4930 */ 4931 function build_archive_link($type="", $id=0) 4932 { 4933 global $mybb; 4934 4935 // If the server OS is not Windows and not Apache or the PHP is running as a CGI or we have defined ARCHIVE_QUERY_STRINGS, use query strings - DIRECTORY_SEPARATOR checks if running windows 4936 //if((DIRECTORY_SEPARATOR == '\\' && is_numeric(stripos($_SERVER['SERVER_SOFTWARE'], "apache")) == false) || is_numeric(stripos(SAPI_NAME, "cgi")) !== false || defined("ARCHIVE_QUERY_STRINGS")) 4937 if($mybb->settings['seourls_archive'] == 1) 4938 { 4939 $base_url = $mybb->settings['bburl']."/archive/index.php/"; 4940 } 4941 else 4942 { 4943 $base_url = $mybb->settings['bburl']."/archive/index.php?"; 4944 } 4945 4946 switch($type) 4947 { 4948 case "thread": 4949 $url = "{$base_url}thread-{$id}.html"; 4950 break; 4951 case "announcement": 4952 $url = "{$base_url}announcement-{$id}.html"; 4953 break; 4954 case "forum": 4955 $url = "{$base_url}forum-{$id}.html"; 4956 break; 4957 default: 4958 $url = $mybb->settings['bburl']."/archive/index.php"; 4959 } 4960 4961 return $url; 4962 } 4963 4964 /** 4965 * Prints a debug information page 4966 */ 4967 function debug_page() 4968 { 4969 global $db, $debug, $templates, $templatelist, $mybb, $maintimer, $globaltime, $ptimer, $parsetime, $lang, $cache; 4970 4971 $totaltime = format_time_duration($maintimer->totaltime); 4972 $phptime = $maintimer->totaltime - $db->query_time; 4973 $query_time = $db->query_time; 4974 $globaltime = format_time_duration($globaltime); 4975 4976 $percentphp = number_format((($phptime/$maintimer->totaltime)*100), 2); 4977 $percentsql = number_format((($query_time/$maintimer->totaltime)*100), 2); 4978 4979 $phptime = format_time_duration($maintimer->totaltime - $db->query_time); 4980 $query_time = format_time_duration($db->query_time); 4981 4982 $call_time = format_time_duration($cache->call_time); 4983 4984 $phpversion = PHP_VERSION; 4985 4986 $serverload = get_server_load(); 4987 4988 if($mybb->settings['gzipoutput'] != 0) 4989 { 4990 $gzipen = "Enabled"; 4991 } 4992 else 4993 { 4994 $gzipen = "Disabled"; 4995 } 4996 4997 echo "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"; 4998 echo "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">"; 4999 echo "<head>"; 5000 echo "<meta name=\"robots\" content=\"noindex\" />"; 5001 echo "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />"; 5002 echo "<title>MyBB Debug Information</title>"; 5003 echo "</head>"; 5004 echo "<body>"; 5005 echo "<h1>MyBB Debug Information</h1>\n"; 5006 echo "<h2>Page Generation</h2>\n"; 5007 echo "<table bgcolor=\"#666666\" width=\"95%\" cellpadding=\"4\" cellspacing=\"1\" align=\"center\">\n"; 5008 echo "<tr>\n"; 5009 echo "<td bgcolor=\"#cccccc\" colspan=\"4\"><b><span style=\"size:2;\">Page Generation Statistics</span></b></td>\n"; 5010 echo "</tr>\n"; 5011 echo "<tr>\n"; 5012 echo "<td bgcolor=\"#efefef\" width=\"25%\"><b><span style=\"font-family: tahoma; font-size: 12px;\">Page Generation Time:</span></b></td>\n"; 5013 echo "<td bgcolor=\"#fefefe\" width=\"25%\"><span style=\"font-family: tahoma; font-size: 12px;\">$totaltime</span></td>\n"; 5014 echo "<td bgcolor=\"#efefef\" width=\"25%\"><b><span style=\"font-family: tahoma; font-size: 12px;\">No. DB Queries:</span></b></td>\n"; 5015 echo "<td bgcolor=\"#fefefe\" width=\"25%\"><span style=\"font-family: tahoma; font-size: 12px;\">$db->query_count</span></td>\n"; 5016 echo "</tr>\n"; 5017 echo "<tr>\n"; 5018 echo "<td bgcolor=\"#efefef\" width=\"25%\"><b><span style=\"font-family: tahoma; font-size: 12px;\">PHP Processing Time:</span></b></td>\n"; 5019 echo "<td bgcolor=\"#fefefe\" width=\"25%\"><span style=\"font-family: tahoma; font-size: 12px;\">$phptime ($percentphp%)</span></td>\n"; 5020 echo "<td bgcolor=\"#efefef\" width=\"25%\"><b><span style=\"font-family: tahoma; font-size: 12px;\">DB Processing Time:</span></b></td>\n"; 5021 echo "<td bgcolor=\"#fefefe\" width=\"25%\"><span style=\"font-family: tahoma; font-size: 12px;\">$query_time ($percentsql%)</span></td>\n"; 5022 echo "</tr>\n"; 5023 echo "<tr>\n"; 5024 echo "<td bgcolor=\"#efefef\" width=\"25%\"><b><span style=\"font-family: tahoma; font-size: 12px;\">Extensions Used:</span></b></td>\n"; 5025 echo "<td bgcolor=\"#fefefe\" width=\"25%\"><span style=\"font-family: tahoma; font-size: 12px;\">{$mybb->config['database']['type']}, xml</span></td>\n"; 5026 echo "<td bgcolor=\"#efefef\" width=\"25%\"><b><span style=\"font-family: tahoma; font-size: 12px;\">Global.php Processing Time:</span></b></td>\n"; 5027 echo "<td bgcolor=\"#fefefe\" width=\"25%\"><span style=\"font-family: tahoma; font-size: 12px;\">$globaltime</span></td>\n"; 5028 echo "</tr>\n"; 5029 echo "<tr>\n"; 5030 echo "<td bgcolor=\"#efefef\" width=\"25%\"><b><span style=\"font-family: tahoma; font-size: 12px;\">PHP Version:</span></b></td>\n"; 5031 echo "<td bgcolor=\"#fefefe\" width=\"25%\"><span style=\"font-family: tahoma; font-size: 12px;\">$phpversion</span></td>\n"; 5032 echo "<td bgcolor=\"#efefef\" width=\"25%\"><b><span style=\"font-family: tahoma; font-size: 12px;\">Server Load:</span></b></td>\n"; 5033 echo "<td bgcolor=\"#fefefe\" width=\"25%\"><span style=\"font-family: tahoma; font-size: 12px;\">$serverload</span></td>\n"; 5034 echo "</tr>\n"; 5035 echo "<tr>\n"; 5036 echo "<td bgcolor=\"#efefef\" width=\"25%\"><b><span style=\"font-family: tahoma; font-size: 12px;\">GZip Encoding Status:</span></b></td>\n"; 5037 echo "<td bgcolor=\"#fefefe\" width=\"25%\"><span style=\"font-family: tahoma; font-size: 12px;\">$gzipen</span></td>\n"; 5038 echo "<td bgcolor=\"#efefef\" width=\"25%\"><b><span style=\"font-family: tahoma; font-size: 12px;\">No. Templates Used:</span></b></td>\n"; 5039 echo "<td bgcolor=\"#fefefe\" width=\"25%\"><span style=\"font-family: tahoma; font-size: 12px;\">".count($templates->cache)." (".(int)count(explode(",", $templatelist))." Cached / ".(int)count($templates->uncached_templates)." Manually Loaded)</span></td>\n"; 5040 echo "</tr>\n"; 5041 5042 $memory_usage = get_memory_usage(); 5043 if(!$memory_usage) 5044 { 5045 $memory_usage = $lang->unknown; 5046 } 5047 else 5048 { 5049 $memory_usage = get_friendly_size($memory_usage)." ({$memory_usage} bytes)"; 5050 } 5051 $memory_limit = @ini_get("memory_limit"); 5052 echo "<tr>\n"; 5053 echo "<td bgcolor=\"#EFEFEF\" width=\"25%\"><b><span style=\"font-family: tahoma; font-size: 12px;\">Memory Usage:</span></b></td>\n"; 5054 echo "<td bgcolor=\"#FEFEFE\" width=\"25%\"><span style=\"font-family: tahoma; font-size: 12px;\">{$memory_usage}</span></td>\n"; 5055 echo "<td bgcolor=\"#EFEFEF\" width=\"25%\"><b><span style=\"font-family: tahoma; font-size: 12px;\">Memory Limit:</span></b></td>\n"; 5056 echo "<td bgcolor=\"#FEFEFE\" width=\"25%\"><span style=\"font-family: tahoma; font-size: 12px;\">{$memory_limit}</span></td>\n"; 5057 echo "</tr>\n"; 5058 5059 echo "</table>\n"; 5060 5061 echo "<h2>Database Connections (".count($db->connections)." Total) </h2>\n"; 5062 echo "<table style=\"background-color: #666;\" width=\"95%\" cellpadding=\"4\" cellspacing=\"1\" align=\"center\">\n"; 5063 echo "<tr>\n"; 5064 echo "<td style=\"background: #fff;\">".implode("<br />", $db->connections)."</td>\n"; 5065 echo "</tr>\n"; 5066 echo "</table>\n"; 5067 echo "<br />\n"; 5068 5069 echo "<h2>Database Queries (".$db->query_count." Total) </h2>\n"; 5070 echo $db->explain; 5071 5072 if($cache->call_count > 0) 5073 { 5074 echo "<h2>Cache Calls (".$cache->call_count." Total, ".$call_time.") </h2>\n"; 5075 echo $cache->cache_debug; 5076 } 5077 5078 echo "<h2>Template Statistics</h2>\n"; 5079 5080 if(count($templates->cache) > 0) 5081 { 5082 echo "<table style=\"background-color: #666;\" width=\"95%\" cellpadding=\"4\" cellspacing=\"1\" align=\"center\">\n"; 5083 echo "<tr>\n"; 5084 echo "<td style=\"background-color: #ccc;\"><strong>Templates Used (Loaded for this Page) - ".count($templates->cache)." Total</strong></td>\n"; 5085 echo "</tr>\n"; 5086 echo "<tr>\n"; 5087 echo "<td style=\"background: #fff;\">".implode(", ", array_keys($templates->cache))."</td>\n"; 5088 echo "</tr>\n"; 5089 echo "</table>\n"; 5090 echo "<br />\n"; 5091 } 5092 5093 if(count($templates->uncached_templates) > 0) 5094 { 5095 echo "<table style=\"background-color: #666;\" width=\"95%\" cellpadding=\"4\" cellspacing=\"1\" align=\"center\">\n"; 5096 echo "<tr>\n"; 5097 echo "<td style=\"background-color: #ccc;\"><strong>Templates Requiring Additional Calls (Not Cached at Startup) - ".count($templates->uncached_templates)." Total</strong></td>\n"; 5098 echo "</tr>\n"; 5099 echo "<tr>\n"; 5100 echo "<td style=\"background: #fff;\">".implode(", ", $templates->uncached_templates)."</td>\n"; 5101 echo "</tr>\n"; 5102 echo "</table>\n"; 5103 echo "<br />\n"; 5104 } 5105 echo "</body>"; 5106 echo "</html>"; 5107 exit; 5108 } 5109 5110 /** 5111 * Outputs the correct page headers. 5112 */ 5113 function send_page_headers() 5114 { 5115 global $mybb; 5116 5117 if($mybb->settings['nocacheheaders'] == 1) 5118 { 5119 header("Cache-Control: no-cache, private"); 5120 } 5121 } 5122 5123 /** 5124 * Mark specific reported posts of a certain type as dealt with 5125 * 5126 * @param array|int $id An array or int of the ID numbers you're marking as dealt with 5127 * @param string $type The type of item the above IDs are for - post, posts, thread, threads, forum, all 5128 */ 5129 function mark_reports($id, $type="post") 5130 { 5131 global $db, $cache, $plugins; 5132 5133 switch($type) 5134 { 5135 case "posts": 5136 if(is_array($id)) 5137 { 5138 $rids = implode("','", $id); 5139 $rids = "'0','$rids'"; 5140 $db->update_query("reportedcontent", array('reportstatus' => 1), "id IN($rids) AND reportstatus='0' AND (type = 'post' OR type = '')"); 5141 } 5142 break; 5143 case "post": 5144 $db->update_query("reportedcontent", array('reportstatus' => 1), "id='$id' AND reportstatus='0' AND (type = 'post' OR type = '')"); 5145 break; 5146 case "threads": 5147 if(is_array($id)) 5148 { 5149 $rids = implode("','", $id); 5150 $rids = "'0','$rids'"; 5151 $db->update_query("reportedcontent", array('reportstatus' => 1), "id2 IN($rids) AND reportstatus='0' AND (type = 'post' OR type = '')"); 5152 } 5153 break; 5154 case "thread": 5155 $db->update_query("reportedcontent", array('reportstatus' => 1), "id2='$id' AND reportstatus='0' AND (type = 'post' OR type = '')"); 5156 break; 5157 case "forum": 5158 $db->update_query("reportedcontent", array('reportstatus' => 1), "id3='$id' AND reportstatus='0' AND (type = 'post' OR type = '')"); 5159 break; 5160 case "all": 5161 $db->update_query("reportedcontent", array('reportstatus' => 1), "reportstatus='0' AND (type = 'post' OR type = '')"); 5162 break; 5163 } 5164 5165 $arguments = array('id' => $id, 'type' => $type); 5166 $plugins->run_hooks("mark_reports", $arguments); 5167 $cache->update_reportedcontent(); 5168 } 5169 5170 /** 5171 * Fetch a friendly x days, y months etc date stamp from a timestamp 5172 * 5173 * @param int $stamp The timestamp 5174 * @param array $options Array of options 5175 * @return string The friendly formatted timestamp 5176 */ 5177 function nice_time($stamp, $options=array()) 5178 { 5179 global $lang; 5180 5181 $ysecs = 365*24*60*60; 5182 $mosecs = 31*24*60*60; 5183 $wsecs = 7*24*60*60; 5184 $dsecs = 24*60*60; 5185 $hsecs = 60*60; 5186 $msecs = 60; 5187 5188 if(isset($options['short'])) 5189 { 5190 $lang_year = $lang->year_short; 5191 $lang_years = $lang->years_short; 5192 $lang_month = $lang->month_short; 5193 $lang_months = $lang->months_short; 5194 $lang_week = $lang->week_short; 5195 $lang_weeks = $lang->weeks_short; 5196 $lang_day = $lang->day_short; 5197 $lang_days = $lang->days_short; 5198 $lang_hour = $lang->hour_short; 5199 $lang_hours = $lang->hours_short; 5200 $lang_minute = $lang->minute_short; 5201 $lang_minutes = $lang->minutes_short; 5202 $lang_second = $lang->second_short; 5203 $lang_seconds = $lang->seconds_short; 5204 } 5205 else 5206 { 5207 $lang_year = " ".$lang->year; 5208 $lang_years = " ".$lang->years; 5209 $lang_month = " ".$lang->month; 5210 $lang_months = " ".$lang->months; 5211 $lang_week = " ".$lang->week; 5212 $lang_weeks = " ".$lang->weeks; 5213 $lang_day = " ".$lang->day; 5214 $lang_days = " ".$lang->days; 5215 $lang_hour = " ".$lang->hour; 5216 $lang_hours = " ".$lang->hours; 5217 $lang_minute = " ".$lang->minute; 5218 $lang_minutes = " ".$lang->minutes; 5219 $lang_second = " ".$lang->second; 5220 $lang_seconds = " ".$lang->seconds; 5221 } 5222 5223 $years = floor($stamp/$ysecs); 5224 $stamp %= $ysecs; 5225 $months = floor($stamp/$mosecs); 5226 $stamp %= $mosecs; 5227 $weeks = floor($stamp/$wsecs); 5228 $stamp %= $wsecs; 5229 $days = floor($stamp/$dsecs); 5230 $stamp %= $dsecs; 5231 $hours = floor($stamp/$hsecs); 5232 $stamp %= $hsecs; 5233 $minutes = floor($stamp/$msecs); 5234 $stamp %= $msecs; 5235 $seconds = $stamp; 5236 5237 // Prevent gross over accuracy ($options parameter will override these) 5238 if($years > 0) 5239 { 5240 $options = array_merge(array( 5241 'days' => false, 5242 'hours' => false, 5243 'minutes' => false, 5244 'seconds' => false 5245 ), $options); 5246 } 5247 elseif($months > 0) 5248 { 5249 $options = array_merge(array( 5250 'hours' => false, 5251 'minutes' => false, 5252 'seconds' => false 5253 ), $options); 5254 } 5255 elseif($weeks > 0) 5256 { 5257 $options = array_merge(array( 5258 'minutes' => false, 5259 'seconds' => false 5260 ), $options); 5261 } 5262 elseif($days > 0) 5263 { 5264 $options = array_merge(array( 5265 'seconds' => false 5266 ), $options); 5267 } 5268 5269 $nicetime = array(); 5270 5271 if(!isset($options['years']) || $options['years'] !== false) 5272 { 5273 if($years == 1) 5274 { 5275 $nicetime['years'] = "1".$lang_year; 5276 } 5277 else if($years > 1) 5278 { 5279 $nicetime['years'] = $years.$lang_years; 5280 } 5281 } 5282 5283 if(!isset($options['months']) || $options['months'] !== false) 5284 { 5285 if($months == 1) 5286 { 5287 $nicetime['months'] = "1".$lang_month; 5288 } 5289 else if($months > 1) 5290 { 5291 $nicetime['months'] = $months.$lang_months; 5292 } 5293 } 5294 5295 if(!isset($options['weeks']) || $options['weeks'] !== false) 5296 { 5297 if($weeks == 1) 5298 { 5299 $nicetime['weeks'] = "1".$lang_week; 5300 } 5301 else if($weeks > 1) 5302 { 5303 $nicetime['weeks'] = $weeks.$lang_weeks; 5304 } 5305 } 5306 5307 if(!isset($options['days']) || $options['days'] !== false) 5308 { 5309 if($days == 1) 5310 { 5311 $nicetime['days'] = "1".$lang_day; 5312 } 5313 else if($days > 1) 5314 { 5315 $nicetime['days'] = $days.$lang_days; 5316 } 5317 } 5318 5319 if(!isset($options['hours']) || $options['hours'] !== false) 5320 { 5321 if($hours == 1) 5322 { 5323 $nicetime['hours'] = "1".$lang_hour; 5324 } 5325 else if($hours > 1) 5326 { 5327 $nicetime['hours'] = $hours.$lang_hours; 5328 } 5329 } 5330 5331 if(!isset($options['minutes']) || $options['minutes'] !== false) 5332 { 5333 if($minutes == 1) 5334 { 5335 $nicetime['minutes'] = "1".$lang_minute; 5336 } 5337 else if($minutes > 1) 5338 { 5339 $nicetime['minutes'] = $minutes.$lang_minutes; 5340 } 5341 } 5342 5343 if(!isset($options['seconds']) || $options['seconds'] !== false) 5344 { 5345 if($seconds == 1) 5346 { 5347 $nicetime['seconds'] = "1".$lang_second; 5348 } 5349 else if($seconds > 1) 5350 { 5351 $nicetime['seconds'] = $seconds.$lang_seconds; 5352 } 5353 } 5354 5355 if(!empty($nicetime)) 5356 { 5357 return implode(", ", $nicetime); 5358 } 5359 } 5360 5361 /** 5362 * Select an alternating row colour based on the previous call to this function 5363 * 5364 * @param int $reset 1 to reset the row to trow1. 5365 * @return string trow1 or trow2 depending on the previous call 5366 */ 5367 function alt_trow($reset=0) 5368 { 5369 global $alttrow; 5370 5371 if($alttrow == "trow1" && !$reset) 5372 { 5373 $trow = "trow2"; 5374 } 5375 else 5376 { 5377 $trow = "trow1"; 5378 } 5379 5380 $alttrow = $trow; 5381 5382 return $trow; 5383 } 5384 5385 /** 5386 * Add a user to a specific additional user group. 5387 * 5388 * @param int $uid The user ID 5389 * @param int $joingroup The user group ID to join 5390 * @return bool 5391 */ 5392 function join_usergroup($uid, $joingroup) 5393 { 5394 global $db, $mybb; 5395 5396 if($uid == $mybb->user['uid']) 5397 { 5398 $user = $mybb->user; 5399 } 5400 else 5401 { 5402 $query = $db->simple_select("users", "additionalgroups, usergroup", "uid='".(int)$uid."'"); 5403 $user = $db->fetch_array($query); 5404 } 5405 5406 // Build the new list of additional groups for this user and make sure they're in the right format 5407 $groups = array_map( 5408 'intval', 5409 explode(',', $user['additionalgroups']) 5410 ); 5411 5412 if(!in_array((int)$joingroup, $groups)) 5413 { 5414 $groups[] = (int)$joingroup; 5415 $groups = array_diff($groups, array($user['usergroup'])); 5416 $groups = array_unique($groups); 5417 5418 $groupslist = implode(',', $groups); 5419 5420 $db->update_query("users", array('additionalgroups' => $groupslist), "uid='".(int)$uid."'"); 5421 return true; 5422 } 5423 else 5424 { 5425 return false; 5426 } 5427 } 5428 5429 /** 5430 * Remove a user from a specific additional user group 5431 * 5432 * @param int $uid The user ID 5433 * @param int $leavegroup The user group ID 5434 */ 5435 function leave_usergroup($uid, $leavegroup) 5436 { 5437 global $db, $mybb, $cache; 5438 5439 $user = get_user($uid); 5440 5441 if($user['usergroup'] == $leavegroup) 5442 { 5443 return false; 5444 } 5445 5446 $groups = array_map( 5447 'intval', 5448 explode(',', $user['additionalgroups']) 5449 ); 5450 $groups = array_diff($groups, array($leavegroup)); 5451 $groups = array_unique($groups); 5452 5453 $groupslist = implode(',', $groups); 5454 5455 $dispupdate = ""; 5456 if($leavegroup == $user['displaygroup']) 5457 { 5458 $dispupdate = ", displaygroup=usergroup"; 5459 } 5460 5461 $db->write_query(" 5462 UPDATE ".TABLE_PREFIX."users 5463 SET additionalgroups='$groupslist' $dispupdate 5464 WHERE uid='".(int)$uid."' 5465 "); 5466 5467 $cache->update_moderators(); 5468 } 5469 5470 /** 5471 * Get the current location taking in to account different web serves and systems 5472 * 5473 * @param boolean $fields True to return as "hidden" fields 5474 * @param array $ignore Array of fields to ignore for returning "hidden" fields or URL being accessed 5475 * @param boolean $quick True to skip all inputs and return only the file path part of the URL 5476 * @return string|array The current URL being accessed or form data if $fields is true 5477 */ 5478 function get_current_location($fields=false, $ignore=array(), $quick=false) 5479 { 5480 global $mybb; 5481 5482 if(defined("MYBB_LOCATION")) 5483 { 5484 return MYBB_LOCATION; 5485 } 5486 5487 if(!empty($_SERVER['SCRIPT_NAME'])) 5488 { 5489 $location = htmlspecialchars_uni($_SERVER['SCRIPT_NAME']); 5490 } 5491 elseif(!empty($_SERVER['PHP_SELF'])) 5492 { 5493 $location = htmlspecialchars_uni($_SERVER['PHP_SELF']); 5494 } 5495 elseif(!empty($_ENV['PHP_SELF'])) 5496 { 5497 $location = htmlspecialchars_uni($_ENV['PHP_SELF']); 5498 } 5499 elseif(!empty($_SERVER['PATH_INFO'])) 5500 { 5501 $location = htmlspecialchars_uni($_SERVER['PATH_INFO']); 5502 } 5503 else 5504 { 5505 $location = htmlspecialchars_uni($_ENV['PATH_INFO']); 5506 } 5507 5508 if($quick) 5509 { 5510 return $location; 5511 } 5512 5513 if(!is_array($ignore)) 5514 { 5515 $ignore = array($ignore); 5516 } 5517 5518 if($fields == true) 5519 { 5520 5521 $form_html = ''; 5522 if(!empty($mybb->input)) 5523 { 5524 foreach($mybb->input as $name => $value) 5525 { 5526 if(in_array($name, $ignore) || is_array($name) || is_array($value)) 5527 { 5528 continue; 5529 } 5530 5531 $form_html .= "<input type=\"hidden\" name=\"".htmlspecialchars_uni($name)."\" value=\"".htmlspecialchars_uni($value)."\" />\n"; 5532 } 5533 } 5534 5535 return array('location' => $location, 'form_html' => $form_html, 'form_method' => $mybb->request_method); 5536 } 5537 else 5538 { 5539 $parameters = array(); 5540 5541 if(isset($_SERVER['QUERY_STRING'])) 5542 { 5543 $current_query_string = $_SERVER['QUERY_STRING']; 5544 } 5545 else if(isset($_ENV['QUERY_STRING'])) 5546 { 5547 $current_query_string = $_ENV['QUERY_STRING']; 5548 } else 5549 { 5550 $current_query_string = ''; 5551 } 5552 5553 parse_str($current_query_string, $current_parameters); 5554 5555 foreach($current_parameters as $name => $value) 5556 { 5557 if(!in_array($name, $ignore)) 5558 { 5559 $parameters[$name] = $value; 5560 } 5561 } 5562 5563 if($mybb->request_method === 'post') 5564 { 5565 $post_array = array('action', 'fid', 'pid', 'tid', 'uid', 'eid'); 5566 5567 foreach($post_array as $var) 5568 { 5569 if(isset($_POST[$var]) && !in_array($var, $ignore)) 5570 { 5571 $parameters[$var] = $_POST[$var]; 5572 } 5573 } 5574 } 5575 5576 if(!empty($parameters)) 5577 { 5578 $location .= '?'.http_build_query($parameters, '', '&'); 5579 } 5580 5581 return $location; 5582 } 5583 } 5584 5585 /** 5586 * Build a theme selection menu 5587 * 5588 * @param string $name The name of the menu 5589 * @param int $selected The ID of the selected theme 5590 * @param int $tid The ID of the parent theme to select from 5591 * @param string $depth The current selection depth 5592 * @param boolean $usergroup_override Whether or not to override usergroup permissions (true to override) 5593 * @param boolean $footer Whether or not theme select is in the footer (true if it is) 5594 * @param boolean $count_override Whether or not to override output based on theme count (true to override) 5595 * @return string The theme selection list 5596 */ 5597 function build_theme_select($name, $selected=-1, $tid=0, $depth="", $usergroup_override=false, $footer=false, $count_override=false) 5598 { 5599 global $db, $themeselect, $tcache, $lang, $mybb, $limit, $templates, $num_themes, $themeselect_option; 5600 5601 if($tid == 0) 5602 { 5603 $tid = 1; 5604 $num_themes = 0; 5605 $themeselect_option = ''; 5606 } 5607 5608 if(!is_array($tcache)) 5609 { 5610 $query = $db->simple_select('themes', 'tid, name, pid, allowedgroups', "pid!='0'"); 5611 5612 while($theme = $db->fetch_array($query)) 5613 { 5614 $tcache[$theme['pid']][$theme['tid']] = $theme; 5615 } 5616 } 5617 5618 if(is_array($tcache[$tid])) 5619 { 5620 foreach($tcache[$tid] as $theme) 5621 { 5622 $sel = ""; 5623 // Show theme if allowed, or if override is on 5624 if(is_member($theme['allowedgroups']) || $theme['allowedgroups'] == "all" || $usergroup_override == true) 5625 { 5626 if($theme['tid'] == $selected) 5627 { 5628 $sel = " selected=\"selected\""; 5629 } 5630 5631 if($theme['pid'] != 0) 5632 { 5633 $theme['name'] = htmlspecialchars_uni($theme['name']); 5634 eval("\$themeselect_option .= \"".$templates->get("usercp_themeselector_option")."\";"); 5635 ++$num_themes; 5636 $depthit = $depth."--"; 5637 } 5638 5639 if(array_key_exists($theme['tid'], $tcache)) 5640 { 5641 build_theme_select($name, $selected, $theme['tid'], $depthit, $usergroup_override, $footer, $count_override); 5642 } 5643 } 5644 } 5645 } 5646 5647 if($tid == 1 && ($num_themes > 1 || $count_override == true)) 5648 { 5649 if($footer == true) 5650 { 5651 eval("\$themeselect = \"".$templates->get("footer_themeselector")."\";"); 5652 } 5653 else 5654 { 5655 eval("\$themeselect = \"".$templates->get("usercp_themeselector")."\";"); 5656 } 5657 5658 return $themeselect; 5659 } 5660 else 5661 { 5662 return false; 5663 } 5664 } 5665 5666 /** 5667 * Get the theme data of a theme id. 5668 * 5669 * @param int $tid The theme id of the theme. 5670 * @return boolean|array False if no valid theme, Array with the theme data otherwise 5671 */ 5672 function get_theme($tid) 5673 { 5674 global $tcache, $db; 5675 5676 if(!is_array($tcache)) 5677 { 5678 $query = $db->simple_select('themes', 'tid, name, pid, allowedgroups', "pid!='0'"); 5679 5680 while($theme = $db->fetch_array($query)) 5681 { 5682 $tcache[$theme['pid']][$theme['tid']] = $theme; 5683 } 5684 } 5685 5686 $s_theme = false; 5687 5688 foreach($tcache as $themes) 5689 { 5690 foreach($themes as $theme) 5691 { 5692 if($tid == $theme['tid']) 5693 { 5694 $s_theme = $theme; 5695 break 2; 5696 } 5697 } 5698 } 5699 5700 return $s_theme; 5701 } 5702 5703 /** 5704 * Custom function for htmlspecialchars which takes in to account unicode 5705 * 5706 * @param string $message The string to format 5707 * @return string The string with htmlspecialchars applied 5708 */ 5709 function htmlspecialchars_uni($message) 5710 { 5711 $message = preg_replace("#&(?!\#[0-9]+;)#si", "&", $message); // Fix & but allow unicode 5712 $message = str_replace("<", "<", $message); 5713 $message = str_replace(">", ">", $message); 5714 $message = str_replace("\"", """, $message); 5715 return $message; 5716 } 5717 5718 /** 5719 * Custom function for formatting numbers. 5720 * 5721 * @param int $number The number to format. 5722 * @return int The formatted number. 5723 */ 5724 function my_number_format($number) 5725 { 5726 global $mybb; 5727 5728 if($number == "-") 5729 { 5730 return $number; 5731 } 5732 5733 if(is_int($number)) 5734 { 5735 return number_format($number, 0, $mybb->settings['decpoint'], $mybb->settings['thousandssep']); 5736 } 5737 else 5738 { 5739 $parts = explode('.', $number); 5740 5741 if(isset($parts[1])) 5742 { 5743 $decimals = my_strlen($parts[1]); 5744 } 5745 else 5746 { 5747 $decimals = 0; 5748 } 5749 5750 return number_format((double)$number, $decimals, $mybb->settings['decpoint'], $mybb->settings['thousandssep']); 5751 } 5752 } 5753 5754 /** 5755 * Converts a string of text to or from UTF-8. 5756 * 5757 * @param string $str The string of text to convert 5758 * @param boolean $to Whether or not the string is being converted to or from UTF-8 (true if converting to) 5759 * @return string The converted string 5760 */ 5761 function convert_through_utf8($str, $to=true) 5762 { 5763 global $lang; 5764 static $charset; 5765 static $use_mb; 5766 static $use_iconv; 5767 5768 if(!isset($charset)) 5769 { 5770 $charset = my_strtolower($lang->settings['charset']); 5771 } 5772 5773 if($charset == "utf-8") 5774 { 5775 return $str; 5776 } 5777 5778 if(!isset($use_iconv)) 5779 { 5780 $use_iconv = function_exists("iconv"); 5781 } 5782 5783 if(!isset($use_mb)) 5784 { 5785 $use_mb = function_exists("mb_convert_encoding"); 5786 } 5787 5788 if($use_iconv || $use_mb) 5789 { 5790 if($to) 5791 { 5792 $from_charset = $lang->settings['charset']; 5793 $to_charset = "UTF-8"; 5794 } 5795 else 5796 { 5797 $from_charset = "UTF-8"; 5798 $to_charset = $lang->settings['charset']; 5799 } 5800 if($use_iconv) 5801 { 5802 return iconv($from_charset, $to_charset."//IGNORE", $str); 5803 } 5804 else 5805 { 5806 return @mb_convert_encoding($str, $to_charset, $from_charset); 5807 } 5808 } 5809 elseif($charset == "iso-8859-1" && function_exists("utf8_encode")) 5810 { 5811 if($to) 5812 { 5813 return utf8_encode($str); 5814 } 5815 else 5816 { 5817 return utf8_decode($str); 5818 } 5819 } 5820 else 5821 { 5822 return $str; 5823 } 5824 } 5825 5826 /** 5827 * DEPRECATED! Please use other alternatives. 5828 * 5829 * @deprecated 5830 * @param string $message 5831 * 5832 * @return string 5833 */ 5834 function my_wordwrap($message) 5835 { 5836 return $message; 5837 } 5838 5839 /** 5840 * Workaround for date limitation in PHP to establish the day of a birthday (Provided by meme) 5841 * 5842 * @param int $month The month of the birthday 5843 * @param int $day The day of the birthday 5844 * @param int $year The year of the bithday 5845 * @return int The numeric day of the week for the birthday 5846 */ 5847 function get_weekday($month, $day, $year) 5848 { 5849 $h = 4; 5850 5851 for($i = 1969; $i >= $year; $i--) 5852 { 5853 $j = get_bdays($i); 5854 5855 for($k = 11; $k >= 0; $k--) 5856 { 5857 $l = ($k + 1); 5858 5859 for($m = $j[$k]; $m >= 1; $m--) 5860 { 5861 $h--; 5862 5863 if($i == $year && $l == $month && $m == $day) 5864 { 5865 return $h; 5866 } 5867 5868 if($h == 0) 5869 { 5870 $h = 7; 5871 } 5872 } 5873 } 5874 } 5875 } 5876 5877 /** 5878 * Workaround for date limitation in PHP to establish the day of a birthday (Provided by meme) 5879 * 5880 * @param int $in The year. 5881 * @return array The number of days in each month of that year 5882 */ 5883 function get_bdays($in) 5884 { 5885 return array( 5886 31, 5887 ($in % 4 == 0 && ($in % 100 > 0 || $in % 400 == 0) ? 29 : 28), 5888 31, 5889 30, 5890 31, 5891 30, 5892 31, 5893 31, 5894 30, 5895 31, 5896 30, 5897 31 5898 ); 5899 } 5900 5901 /** 5902 * DEPRECATED! Please use mktime()! 5903 * Formats a birthday appropriately 5904 * 5905 * @deprecated 5906 * @param string $display The PHP date format string 5907 * @param int $bm The month of the birthday 5908 * @param int $bd The day of the birthday 5909 * @param int $by The year of the birthday 5910 * @param int $wd The weekday of the birthday 5911 * @return string The formatted birthday 5912 */ 5913 function format_bdays($display, $bm, $bd, $by, $wd) 5914 { 5915 global $lang; 5916 5917 $bdays = array( 5918 $lang->sunday, 5919 $lang->monday, 5920 $lang->tuesday, 5921 $lang->wednesday, 5922 $lang->thursday, 5923 $lang->friday, 5924 $lang->saturday 5925 ); 5926 5927 $bmonth = array( 5928 $lang->month_1, 5929 $lang->month_2, 5930 $lang->month_3, 5931 $lang->month_4, 5932 $lang->month_5, 5933 $lang->month_6, 5934 $lang->month_7, 5935 $lang->month_8, 5936 $lang->month_9, 5937 $lang->month_10, 5938 $lang->month_11, 5939 $lang->month_12 5940 ); 5941 5942 // This needs to be in this specific order 5943 $find = array( 5944 'm', 5945 'n', 5946 'd', 5947 'D', 5948 'y', 5949 'Y', 5950 'j', 5951 'S', 5952 'F', 5953 'l', 5954 'M', 5955 ); 5956 5957 $html = array( 5958 'm', 5959 'n', 5960 'c', 5961 'D', 5962 'y', 5963 'Y', 5964 'j', 5965 'S', 5966 'F', 5967 'l', 5968 'M', 5969 ); 5970 5971 $bdays = str_replace($find, $html, $bdays); 5972 $bmonth = str_replace($find, $html, $bmonth); 5973 5974 $replace = array( 5975 sprintf('%02s', $bm), 5976 $bm, 5977 sprintf('%02s', $bd), 5978 ($wd == 2 ? my_substr($bdays[$wd], 0, 4) : ($wd == 4 ? my_substr($bdays[$wd], 0, 5) : my_substr($bdays[$wd], 0, 3))), 5979 my_substr($by, 2), 5980 $by, 5981 ($bd[0] == 0 ? my_substr($bd, 1) : $bd), 5982 ($bd == 1 || $bd == 21 || $bd == 31 ? 'st' : ($bd == 2 || $bd == 22 ? 'nd' : ($bd == 3 || $bd == 23 ? 'rd' : 'th'))), 5983 $bmonth[$bm-1], 5984 $wd, 5985 ($bm == 9 ? my_substr($bmonth[$bm-1], 0, 4) : my_substr($bmonth[$bm-1], 0, 3)), 5986 ); 5987 5988 // Do we have the full month in our output? 5989 // If so there's no need for the short month 5990 if(strpos($display, 'F') !== false) 5991 { 5992 array_pop($find); 5993 array_pop($replace); 5994 } 5995 5996 return str_replace($find, $replace, $display); 5997 } 5998 5999 /** 6000 * Returns the age of a user with specified birthday. 6001 * 6002 * @param string $birthday The birthday of a user. 6003 * @return int The age of a user with that birthday. 6004 */ 6005 function get_age($birthday) 6006 { 6007 $bday = explode("-", $birthday); 6008 if(!$bday[2]) 6009 { 6010 return; 6011 } 6012 6013 list($day, $month, $year) = explode("-", my_date("j-n-Y", TIME_NOW, 0, 0)); 6014 6015 $age = $year-$bday[2]; 6016 6017 if(($month == $bday[1] && $day < $bday[0]) || $month < $bday[1]) 6018 { 6019 --$age; 6020 } 6021 return $age; 6022 } 6023 6024 /** 6025 * Updates the first posts in a thread. 6026 * 6027 * @param int $tid The thread id for which to update the first post id. 6028 */ 6029 function update_first_post($tid) 6030 { 6031 global $db; 6032 6033 $query = $db->query(" 6034 SELECT u.uid, u.username, p.pid, p.username AS postusername, p.dateline 6035 FROM ".TABLE_PREFIX."posts p 6036 LEFT JOIN ".TABLE_PREFIX."users u ON (u.uid=p.uid) 6037 WHERE p.tid='$tid' 6038 ORDER BY p.dateline ASC, p.pid ASC 6039 LIMIT 1 6040 "); 6041 $firstpost = $db->fetch_array($query); 6042 6043 if(empty($firstpost['username'])) 6044 { 6045 $firstpost['username'] = $firstpost['postusername']; 6046 } 6047 $firstpost['username'] = $db->escape_string($firstpost['username']); 6048 6049 $update_array = array( 6050 'firstpost' => (int)$firstpost['pid'], 6051 'username' => $firstpost['username'], 6052 'uid' => (int)$firstpost['uid'], 6053 'dateline' => (int)$firstpost['dateline'] 6054 ); 6055 $db->update_query("threads", $update_array, "tid='{$tid}'"); 6056 } 6057 6058 /** 6059 * Updates the last posts in a thread. 6060 * 6061 * @param int $tid The thread id for which to update the last post id. 6062 */ 6063 function update_last_post($tid) 6064 { 6065 global $db; 6066 6067 $query = $db->query(" 6068 SELECT u.uid, u.username, p.username AS postusername, p.dateline 6069 FROM ".TABLE_PREFIX."posts p 6070 LEFT JOIN ".TABLE_PREFIX."users u ON (u.uid=p.uid) 6071 WHERE p.tid='$tid' AND p.visible='1' 6072 ORDER BY p.dateline DESC, p.pid DESC 6073 LIMIT 1" 6074 ); 6075 $lastpost = $db->fetch_array($query); 6076 6077 if(!$lastpost) 6078 { 6079 return false; 6080 } 6081 6082 if(empty($lastpost['username'])) 6083 { 6084 $lastpost['username'] = $lastpost['postusername']; 6085 } 6086 6087 if(empty($lastpost['dateline'])) 6088 { 6089 $query = $db->query(" 6090 SELECT u.uid, u.username, p.pid, p.username AS postusername, p.dateline 6091 FROM ".TABLE_PREFIX."posts p 6092 LEFT JOIN ".TABLE_PREFIX."users u ON (u.uid=p.uid) 6093 WHERE p.tid='$tid' 6094 ORDER BY p.dateline ASC, p.pid ASC 6095 LIMIT 1 6096 "); 6097 $firstpost = $db->fetch_array($query); 6098 6099 $lastpost['username'] = $firstpost['username']; 6100 $lastpost['uid'] = $firstpost['uid']; 6101 $lastpost['dateline'] = $firstpost['dateline']; 6102 } 6103 6104 $lastpost['username'] = $db->escape_string($lastpost['username']); 6105 6106 $update_array = array( 6107 'lastpost' => (int)$lastpost['dateline'], 6108 'lastposter' => $lastpost['username'], 6109 'lastposteruid' => (int)$lastpost['uid'] 6110 ); 6111 $db->update_query("threads", $update_array, "tid='{$tid}'"); 6112 } 6113 6114 /** 6115 * Checks for the length of a string, mb strings accounted for 6116 * 6117 * @param string $string The string to check the length of. 6118 * @return int The length of the string. 6119 */ 6120 function my_strlen($string) 6121 { 6122 global $lang; 6123 6124 $string = preg_replace("#&\#([0-9]+);#", "-", $string); 6125 6126 if(strtolower($lang->settings['charset']) == "utf-8") 6127 { 6128 // Get rid of any excess RTL and LTR override for they are the workings of the devil 6129 $string = str_replace(dec_to_utf8(8238), "", $string); 6130 $string = str_replace(dec_to_utf8(8237), "", $string); 6131 6132 // Remove dodgy whitespaces 6133 $string = str_replace(chr(0xCA), "", $string); 6134 } 6135 $string = trim($string); 6136 6137 if(function_exists("mb_strlen")) 6138 { 6139 $string_length = mb_strlen($string); 6140 } 6141 else 6142 { 6143 $string_length = strlen($string); 6144 } 6145 6146 return $string_length; 6147 } 6148 6149 /** 6150 * Cuts a string at a specified point, mb strings accounted for 6151 * 6152 * @param string $string The string to cut. 6153 * @param int $start Where to cut 6154 * @param int $length (optional) How much to cut 6155 * @param bool $handle_entities (optional) Properly handle HTML entities? 6156 * @return string The cut part of the string. 6157 */ 6158 function my_substr($string, $start, $length=null, $handle_entities = false) 6159 { 6160 if($handle_entities) 6161 { 6162 $string = unhtmlentities($string); 6163 } 6164 if(function_exists("mb_substr")) 6165 { 6166 if($length != null) 6167 { 6168 $cut_string = mb_substr($string, $start, $length); 6169 } 6170 else 6171 { 6172 $cut_string = mb_substr($string, $start); 6173 } 6174 } 6175 else 6176 { 6177 if($length != null) 6178 { 6179 $cut_string = substr($string, $start, $length); 6180 } 6181 else 6182 { 6183 $cut_string = substr($string, $start); 6184 } 6185 } 6186 6187 if($handle_entities) 6188 { 6189 $cut_string = htmlspecialchars_uni($cut_string); 6190 } 6191 return $cut_string; 6192 } 6193 6194 /** 6195 * Lowers the case of a string, mb strings accounted for 6196 * 6197 * @param string $string The string to lower. 6198 * @return string The lowered string. 6199 */ 6200 function my_strtolower($string) 6201 { 6202 if(function_exists("mb_strtolower")) 6203 { 6204 $string = mb_strtolower($string); 6205 } 6206 else 6207 { 6208 $string = strtolower($string); 6209 } 6210 6211 return $string; 6212 } 6213 6214 /** 6215 * Finds a needle in a haystack and returns it position, mb strings accounted for, case insensitive 6216 * 6217 * @param string $haystack String to look in (haystack) 6218 * @param string $needle What to look for (needle) 6219 * @param int $offset (optional) How much to offset 6220 * @return int|bool false on needle not found, integer position if found 6221 */ 6222 function my_stripos($haystack, $needle, $offset=0) 6223 { 6224 if($needle == '') 6225 { 6226 return false; 6227 } 6228 6229 if(function_exists("mb_stripos")) 6230 { 6231 $position = mb_stripos($haystack, $needle, $offset); 6232 } 6233 else 6234 { 6235 $position = stripos($haystack, $needle, $offset); 6236 } 6237 6238 return $position; 6239 } 6240 6241 /** 6242 * Finds a needle in a haystack and returns it position, mb strings accounted for 6243 * 6244 * @param string $haystack String to look in (haystack) 6245 * @param string $needle What to look for (needle) 6246 * @param int $offset (optional) How much to offset 6247 * @return int|bool false on needle not found, integer position if found 6248 */ 6249 function my_strpos($haystack, $needle, $offset=0) 6250 { 6251 if($needle == '') 6252 { 6253 return false; 6254 } 6255 6256 if(function_exists("mb_strpos")) 6257 { 6258 $position = mb_strpos($haystack, $needle, $offset); 6259 } 6260 else 6261 { 6262 $position = strpos($haystack, $needle, $offset); 6263 } 6264 6265 return $position; 6266 } 6267 6268 /** 6269 * Ups the case of a string, mb strings accounted for 6270 * 6271 * @param string $string The string to up. 6272 * @return string The uped string. 6273 */ 6274 function my_strtoupper($string) 6275 { 6276 if(function_exists("mb_strtoupper")) 6277 { 6278 $string = mb_strtoupper($string); 6279 } 6280 else 6281 { 6282 $string = strtoupper($string); 6283 } 6284 6285 return $string; 6286 } 6287 6288 /** 6289 * Returns any html entities to their original character 6290 * 6291 * @param string $string The string to un-htmlentitize. 6292 * @return string The un-htmlentitied' string. 6293 */ 6294 function unhtmlentities($string) 6295 { 6296 // Replace numeric entities 6297 $string = preg_replace_callback('~&#x([0-9a-f]+);~i', 'unichr_callback1', $string); 6298 $string = preg_replace_callback('~&#([0-9]+);~', 'unichr_callback2', $string); 6299 6300 // Replace literal entities 6301 $trans_tbl = get_html_translation_table(HTML_ENTITIES); 6302 $trans_tbl = array_flip($trans_tbl); 6303 6304 return strtr($string, $trans_tbl); 6305 } 6306 6307 /** 6308 * Returns any ascii to it's character (utf-8 safe). 6309 * 6310 * @param int $c The ascii to characterize. 6311 * @return string|bool The characterized ascii. False on failure 6312 */ 6313 function unichr($c) 6314 { 6315 if($c <= 0x7F) 6316 { 6317 return chr($c); 6318 } 6319 else if($c <= 0x7FF) 6320 { 6321 return chr(0xC0 | $c >> 6) . chr(0x80 | $c & 0x3F); 6322 } 6323 else if($c <= 0xFFFF) 6324 { 6325 return chr(0xE0 | $c >> 12) . chr(0x80 | $c >> 6 & 0x3F) 6326 . chr(0x80 | $c & 0x3F); 6327 } 6328 else if($c <= 0x10FFFF) 6329 { 6330 return chr(0xF0 | $c >> 18) . chr(0x80 | $c >> 12 & 0x3F) 6331 . chr(0x80 | $c >> 6 & 0x3F) 6332 . chr(0x80 | $c & 0x3F); 6333 } 6334 else 6335 { 6336 return false; 6337 } 6338 } 6339 6340 /** 6341 * Returns any ascii to it's character (utf-8 safe). 6342 * 6343 * @param array $matches Matches. 6344 * @return string|bool The characterized ascii. False on failure 6345 */ 6346 function unichr_callback1($matches) 6347 { 6348 return unichr(hexdec($matches[1])); 6349 } 6350 6351 /** 6352 * Returns any ascii to it's character (utf-8 safe). 6353 * 6354 * @param array $matches Matches. 6355 * @return string|bool The characterized ascii. False on failure 6356 */ 6357 function unichr_callback2($matches) 6358 { 6359 return unichr($matches[1]); 6360 } 6361 6362 /** 6363 * Get the event poster. 6364 * 6365 * @param array $event The event data array. 6366 * @return string The link to the event poster. 6367 */ 6368 function get_event_poster($event) 6369 { 6370 $event['username'] = htmlspecialchars_uni($event['username']); 6371 $event['username'] = format_name($event['username'], $event['usergroup'], $event['displaygroup']); 6372 $event_poster = build_profile_link($event['username'], $event['author']); 6373 return $event_poster; 6374 } 6375 6376 /** 6377 * Get the event date. 6378 * 6379 * @param array $event The event data array. 6380 * @return string The event date. 6381 */ 6382 function get_event_date($event) 6383 { 6384 global $mybb; 6385 6386 $event_date = explode("-", $event['date']); 6387 $event_date = gmmktime(0, 0, 0, $event_date[1], $event_date[0], $event_date[2]); 6388 $event_date = my_date($mybb->settings['dateformat'], $event_date); 6389 6390 return $event_date; 6391 } 6392 6393 /** 6394 * Get the profile link. 6395 * 6396 * @param int $uid The user id of the profile. 6397 * @return string The url to the profile. 6398 */ 6399 function get_profile_link($uid=0) 6400 { 6401 $link = str_replace("{uid}", $uid, PROFILE_URL); 6402 return htmlspecialchars_uni($link); 6403 } 6404 6405 /** 6406 * Get the announcement link. 6407 * 6408 * @param int $aid The announement id of the announcement. 6409 * @return string The url to the announcement. 6410 */ 6411 function get_announcement_link($aid=0) 6412 { 6413 $link = str_replace("{aid}", $aid, ANNOUNCEMENT_URL); 6414 return htmlspecialchars_uni($link); 6415 } 6416 6417 /** 6418 * Build the profile link. 6419 * 6420 * @param string $username The Username of the profile. 6421 * @param int $uid The user id of the profile. 6422 * @param string $target The target frame 6423 * @param string $onclick Any onclick javascript. 6424 * @return string The complete profile link. 6425 */ 6426 function build_profile_link($username="", $uid=0, $target="", $onclick="") 6427 { 6428 global $mybb, $lang; 6429 6430 if(!$username && $uid == 0) 6431 { 6432 // Return Guest phrase for no UID, no guest nickname 6433 return htmlspecialchars_uni($lang->guest); 6434 } 6435 elseif($uid == 0) 6436 { 6437 // Return the guest's nickname if user is a guest but has a nickname 6438 return $username; 6439 } 6440 else 6441 { 6442 // Build the profile link for the registered user 6443 if(!empty($target)) 6444 { 6445 $target = " target=\"{$target}\""; 6446 } 6447 6448 if(!empty($onclick)) 6449 { 6450 $onclick = " onclick=\"{$onclick}\""; 6451 } 6452 6453 return "<a href=\"{$mybb->settings['bburl']}/".get_profile_link($uid)."\"{$target}{$onclick}>{$username}</a>"; 6454 } 6455 } 6456 6457 /** 6458 * Build the forum link. 6459 * 6460 * @param int $fid The forum id of the forum. 6461 * @param int $page (Optional) The page number of the forum. 6462 * @return string The url to the forum. 6463 */ 6464 function get_forum_link($fid, $page=0) 6465 { 6466 if($page > 0) 6467 { 6468 $link = str_replace("{fid}", $fid, FORUM_URL_PAGED); 6469 $link = str_replace("{page}", $page, $link); 6470 return htmlspecialchars_uni($link); 6471 } 6472 else 6473 { 6474 $link = str_replace("{fid}", $fid, FORUM_URL); 6475 return htmlspecialchars_uni($link); 6476 } 6477 } 6478 6479 /** 6480 * Build the thread link. 6481 * 6482 * @param int $tid The thread id of the thread. 6483 * @param int $page (Optional) The page number of the thread. 6484 * @param string $action (Optional) The action we're performing (ex, lastpost, newpost, etc) 6485 * @return string The url to the thread. 6486 */ 6487 function get_thread_link($tid, $page=0, $action='') 6488 { 6489 if($page > 1) 6490 { 6491 if($action) 6492 { 6493 $link = THREAD_URL_ACTION; 6494 $link = str_replace("{action}", $action, $link); 6495 } 6496 else 6497 { 6498 $link = THREAD_URL_PAGED; 6499 } 6500 $link = str_replace("{tid}", $tid, $link); 6501 $link = str_replace("{page}", $page, $link); 6502 return htmlspecialchars_uni($link); 6503 } 6504 else 6505 { 6506 if($action) 6507 { 6508 $link = THREAD_URL_ACTION; 6509 $link = str_replace("{action}", $action, $link); 6510 } 6511 else 6512 { 6513 $link = THREAD_URL; 6514 } 6515 $link = str_replace("{tid}", $tid, $link); 6516 return htmlspecialchars_uni($link); 6517 } 6518 } 6519 6520 /** 6521 * Build the post link. 6522 * 6523 * @param int $pid The post ID of the post 6524 * @param int $tid The thread id of the post. 6525 * @return string The url to the post. 6526 */ 6527 function get_post_link($pid, $tid=0) 6528 { 6529 if($tid > 0) 6530 { 6531 $link = str_replace("{tid}", $tid, THREAD_URL_POST); 6532 $link = str_replace("{pid}", $pid, $link); 6533 return htmlspecialchars_uni($link); 6534 } 6535 else 6536 { 6537 $link = str_replace("{pid}", $pid, POST_URL); 6538 return htmlspecialchars_uni($link); 6539 } 6540 } 6541 6542 /** 6543 * Build the event link. 6544 * 6545 * @param int $eid The event ID of the event 6546 * @return string The URL of the event 6547 */ 6548 function get_event_link($eid) 6549 { 6550 $link = str_replace("{eid}", $eid, EVENT_URL); 6551 return htmlspecialchars_uni($link); 6552 } 6553 6554 /** 6555 * Build the link to a specified date on the calendar 6556 * 6557 * @param int $calendar The ID of the calendar 6558 * @param int $year The year 6559 * @param int $month The month 6560 * @param int $day The day (optional) 6561 * @return string The URL of the calendar 6562 */ 6563 function get_calendar_link($calendar, $year=0, $month=0, $day=0) 6564 { 6565 if($day > 0) 6566 { 6567 $link = str_replace("{month}", $month, CALENDAR_URL_DAY); 6568 $link = str_replace("{year}", $year, $link); 6569 $link = str_replace("{day}", $day, $link); 6570 $link = str_replace("{calendar}", $calendar, $link); 6571 return htmlspecialchars_uni($link); 6572 } 6573 else if($month > 0) 6574 { 6575 $link = str_replace("{month}", $month, CALENDAR_URL_MONTH); 6576 $link = str_replace("{year}", $year, $link); 6577 $link = str_replace("{calendar}", $calendar, $link); 6578 return htmlspecialchars_uni($link); 6579 } 6580 /* Not implemented 6581 else if($year > 0) 6582 { 6583 }*/ 6584 else 6585 { 6586 $link = str_replace("{calendar}", $calendar, CALENDAR_URL); 6587 return htmlspecialchars_uni($link); 6588 } 6589 } 6590 6591 /** 6592 * Build the link to a specified week on the calendar 6593 * 6594 * @param int $calendar The ID of the calendar 6595 * @param int $week The week 6596 * @return string The URL of the calendar 6597 */ 6598 function get_calendar_week_link($calendar, $week) 6599 { 6600 if($week < 0) 6601 { 6602 $week = str_replace('-', "n", $week); 6603 } 6604 $link = str_replace("{week}", $week, CALENDAR_URL_WEEK); 6605 $link = str_replace("{calendar}", $calendar, $link); 6606 return htmlspecialchars_uni($link); 6607 } 6608 6609 /** 6610 * Get the user data of an user id. 6611 * 6612 * @param int $uid The user id of the user. 6613 * @return array The users data 6614 */ 6615 function get_user($uid) 6616 { 6617 global $mybb, $db; 6618 static $user_cache; 6619 6620 $uid = (int)$uid; 6621 6622 if(!empty($mybb->user) && $uid == $mybb->user['uid']) 6623 { 6624 return $mybb->user; 6625 } 6626 elseif(isset($user_cache[$uid])) 6627 { 6628 return $user_cache[$uid]; 6629 } 6630 elseif($uid > 0) 6631 { 6632 $query = $db->simple_select("users", "*", "uid = '{$uid}'"); 6633 $user_cache[$uid] = $db->fetch_array($query); 6634 6635 return $user_cache[$uid]; 6636 } 6637 return array(); 6638 } 6639 6640 /** 6641 * Get the user data of an user username. 6642 * 6643 * @param string $username The user username of the user. 6644 * @param array $options 6645 * @return array The users data 6646 */ 6647 function get_user_by_username($username, $options=array()) 6648 { 6649 global $mybb, $db; 6650 6651 $username = $db->escape_string(my_strtolower($username)); 6652 6653 if(!isset($options['username_method'])) 6654 { 6655 $options['username_method'] = 0; 6656 } 6657 6658 switch($db->type) 6659 { 6660 case 'mysql': 6661 case 'mysqli': 6662 $field = 'username'; 6663 $efield = 'email'; 6664 break; 6665 default: 6666 $field = 'LOWER(username)'; 6667 $efield = 'LOWER(email)'; 6668 break; 6669 } 6670 6671 switch($options['username_method']) 6672 { 6673 case 1: 6674 $sqlwhere = "{$efield}='{$username}'"; 6675 break; 6676 case 2: 6677 $sqlwhere = "{$field}='{$username}' OR {$efield}='{$username}'"; 6678 break; 6679 default: 6680 $sqlwhere = "{$field}='{$username}'"; 6681 break; 6682 } 6683 6684 $fields = array('uid'); 6685 if(isset($options['fields'])) 6686 { 6687 $fields = array_merge((array)$options['fields'], $fields); 6688 } 6689 6690 $query = $db->simple_select('users', implode(',', array_unique($fields)), $sqlwhere, array('limit' => 1)); 6691 6692 if(isset($options['exists'])) 6693 { 6694 return (bool)$db->num_rows($query); 6695 } 6696 6697 return $db->fetch_array($query); 6698 } 6699 6700 /** 6701 * Get the forum of a specific forum id. 6702 * 6703 * @param int $fid The forum id of the forum. 6704 * @param int $active_override (Optional) If set to 1, will override the active forum status 6705 * @return array|bool The database row of a forum. False on failure 6706 */ 6707 function get_forum($fid, $active_override=0) 6708 { 6709 global $cache; 6710 static $forum_cache; 6711 6712 if(!isset($forum_cache) || !is_array($forum_cache)) 6713 { 6714 $forum_cache = $cache->read("forums"); 6715 } 6716 6717 if(empty($forum_cache[$fid])) 6718 { 6719 return false; 6720 } 6721 6722 if($active_override != 1) 6723 { 6724 $parents = explode(",", $forum_cache[$fid]['parentlist']); 6725 if(is_array($parents)) 6726 { 6727 foreach($parents as $parent) 6728 { 6729 if($forum_cache[$parent]['active'] == 0) 6730 { 6731 return false; 6732 } 6733 } 6734 } 6735 } 6736 6737 return $forum_cache[$fid]; 6738 } 6739 6740 /** 6741 * Get the thread of a thread id. 6742 * 6743 * @param int $tid The thread id of the thread. 6744 * @param boolean $recache Whether or not to recache the thread. 6745 * @return array|bool The database row of the thread. False on failure 6746 */ 6747 function get_thread($tid, $recache = false) 6748 { 6749 global $db; 6750 static $thread_cache; 6751 6752 $tid = (int)$tid; 6753 6754 if(isset($thread_cache[$tid]) && !$recache) 6755 { 6756 return $thread_cache[$tid]; 6757 } 6758 else 6759 { 6760 $query = $db->simple_select("threads", "*", "tid = '{$tid}'"); 6761 $thread = $db->fetch_array($query); 6762 6763 if($thread) 6764 { 6765 $thread_cache[$tid] = $thread; 6766 return $thread; 6767 } 6768 else 6769 { 6770 $thread_cache[$tid] = false; 6771 return false; 6772 } 6773 } 6774 } 6775 6776 /** 6777 * Get the post of a post id. 6778 * 6779 * @param int $pid The post id of the post. 6780 * @return array|bool The database row of the post. False on failure 6781 */ 6782 function get_post($pid) 6783 { 6784 global $db; 6785 static $post_cache; 6786 6787 $pid = (int)$pid; 6788 6789 if(isset($post_cache[$pid])) 6790 { 6791 return $post_cache[$pid]; 6792 } 6793 else 6794 { 6795 $query = $db->simple_select("posts", "*", "pid = '{$pid}'"); 6796 $post = $db->fetch_array($query); 6797 6798 if($post) 6799 { 6800 $post_cache[$pid] = $post; 6801 return $post; 6802 } 6803 else 6804 { 6805 $post_cache[$pid] = false; 6806 return false; 6807 } 6808 } 6809 } 6810 6811 /** 6812 * Get inactivate forums. 6813 * 6814 * @return string The comma separated values of the inactivate forum. 6815 */ 6816 function get_inactive_forums() 6817 { 6818 global $forum_cache, $cache; 6819 6820 if(!$forum_cache) 6821 { 6822 cache_forums(); 6823 } 6824 6825 $inactive = array(); 6826 6827 foreach($forum_cache as $fid => $forum) 6828 { 6829 if($forum['active'] == 0) 6830 { 6831 $inactive[] = $fid; 6832 foreach($forum_cache as $fid1 => $forum1) 6833 { 6834 if(my_strpos(",".$forum1['parentlist'].",", ",".$fid.",") !== false && !in_array($fid1, $inactive)) 6835 { 6836 $inactive[] = $fid1; 6837 } 6838 } 6839 } 6840 } 6841 6842 $inactiveforums = implode(",", $inactive); 6843 6844 return $inactiveforums; 6845 } 6846 6847 /** 6848 * Checks to make sure a user has not tried to login more times than permitted 6849 * 6850 * @param bool $fatal (Optional) Stop execution if it finds an error with the login. Default is True 6851 * @return bool|int Number of logins when success, false if failed. 6852 */ 6853 function login_attempt_check($uid = 0, $fatal = true) 6854 { 6855 global $mybb, $lang, $db; 6856 6857 $attempts = array(); 6858 $uid = (int)$uid; 6859 $now = TIME_NOW; 6860 6861 // Get this user's login attempts and eventual lockout, if a uid is provided 6862 if($uid > 0) 6863 { 6864 $query = $db->simple_select("users", "loginattempts, loginlockoutexpiry", "uid='{$uid}'", 1); 6865 $attempts = $db->fetch_array($query); 6866 6867 if($attempts['loginattempts'] <= 0) 6868 { 6869 return 0; 6870 } 6871 } 6872 // This user has a cookie lockout, show waiting time 6873 elseif(!empty($mybb->cookies['lockoutexpiry']) && $mybb->cookies['lockoutexpiry'] > $now) 6874 { 6875 if($fatal) 6876 { 6877 $secsleft = (int)($mybb->cookies['lockoutexpiry'] - $now); 6878 $hoursleft = floor($secsleft / 3600); 6879 $minsleft = floor(($secsleft / 60) % 60); 6880 $secsleft = floor($secsleft % 60); 6881 6882 error($lang->sprintf($lang->failed_login_wait, $hoursleft, $minsleft, $secsleft)); 6883 } 6884 6885 return false; 6886 } 6887 6888 if($mybb->settings['failedlogincount'] > 0 && isset($attempts['loginattempts']) && $attempts['loginattempts'] >= $mybb->settings['failedlogincount']) 6889 { 6890 // Set the expiry dateline if not set yet 6891 if($attempts['loginlockoutexpiry'] == 0) 6892 { 6893 $attempts['loginlockoutexpiry'] = $now + ((int)$mybb->settings['failedlogintime'] * 60); 6894 6895 // Add a cookie lockout. This is used to prevent access to the login page immediately. 6896 // A deep lockout is issued if he tries to login into a locked out account 6897 my_setcookie('lockoutexpiry', $attempts['loginlockoutexpiry']); 6898 6899 $db->update_query("users", array( 6900 "loginlockoutexpiry" => $attempts['loginlockoutexpiry'] 6901 ), "uid='{$uid}'"); 6902 } 6903 6904 if(empty($mybb->cookies['lockoutexpiry'])) 6905 { 6906 $failedtime = $attempts['loginlockoutexpiry']; 6907 } 6908 else 6909 { 6910 $failedtime = $mybb->cookies['lockoutexpiry']; 6911 } 6912 6913 // Are we still locked out? 6914 if($attempts['loginlockoutexpiry'] > $now) 6915 { 6916 if($fatal) 6917 { 6918 $secsleft = (int)($attempts['loginlockoutexpiry'] - $now); 6919 $hoursleft = floor($secsleft / 3600); 6920 $minsleft = floor(($secsleft / 60) % 60); 6921 $secsleft = floor($secsleft % 60); 6922 6923 error($lang->sprintf($lang->failed_login_wait, $hoursleft, $minsleft, $secsleft)); 6924 } 6925 6926 return false; 6927 } 6928 // Unlock if enough time has passed 6929 else { 6930 6931 if($uid > 0) 6932 { 6933 $db->update_query("users", array( 6934 "loginattempts" => 0, 6935 "loginlockoutexpiry" => 0 6936 ), "uid='{$uid}'"); 6937 } 6938 6939 // Wipe the cookie, no matter if a guest or a member 6940 my_unsetcookie('lockoutexpiry'); 6941 6942 return 0; 6943 } 6944 } 6945 6946 if(!isset($attempts['loginattempts'])) 6947 { 6948 $attempts['loginattempts'] = 0; 6949 } 6950 6951 // User can attempt another login 6952 return $attempts['loginattempts']; 6953 } 6954 6955 /** 6956 * Validates the format of an email address. 6957 * 6958 * @param string $email The string to check. 6959 * @return boolean True when valid, false when invalid. 6960 */ 6961 function validate_email_format($email) 6962 { 6963 return filter_var($email, FILTER_VALIDATE_EMAIL) !== false; 6964 } 6965 6966 /** 6967 * Checks to see if the email is already in use by another 6968 * 6969 * @param string $email The email to check. 6970 * @param int $uid User ID of the user (updating only) 6971 * @return boolean True when in use, false when not. 6972 */ 6973 function email_already_in_use($email, $uid=0) 6974 { 6975 global $db; 6976 6977 $uid_string = ""; 6978 if($uid) 6979 { 6980 $uid_string = " AND uid != '".(int)$uid."'"; 6981 } 6982 $query = $db->simple_select("users", "COUNT(email) as emails", "email = '".$db->escape_string($email)."'{$uid_string}"); 6983 6984 if($db->fetch_field($query, "emails") > 0) 6985 { 6986 return true; 6987 } 6988 6989 return false; 6990 } 6991 6992 /** 6993 * Rebuilds settings.php 6994 * 6995 */ 6996 function rebuild_settings() 6997 { 6998 global $db, $mybb; 6999 7000 $query = $db->simple_select("settings", "value, name", "", array( 7001 'order_by' => 'title', 7002 'order_dir' => 'ASC', 7003 )); 7004 7005 $settings = ''; 7006 while($setting = $db->fetch_array($query)) 7007 { 7008 $mybb->settings[$setting['name']] = $setting['value']; 7009 7010 $setting['name'] = addcslashes($setting['name'], "\\'"); 7011 $setting['value'] = addcslashes($setting['value'], '\\"$'); 7012 $settings .= "\$settings['{$setting['name']}'] = \"{$setting['value']}\";\n"; 7013 } 7014 7015 $settings = "<"."?php\n/*********************************\ \n DO NOT EDIT THIS FILE, PLEASE USE\n THE SETTINGS EDITOR\n\*********************************/\n\n$settings\n"; 7016 7017 file_put_contents(MYBB_ROOT.'inc/settings.php', $settings, LOCK_EX); 7018 7019 $GLOBALS['settings'] = &$mybb->settings; 7020 } 7021 7022 /** 7023 * Build a PREG compatible array of search highlight terms to replace in posts. 7024 * 7025 * @param string $terms Incoming terms to highlight 7026 * @return array PREG compatible array of terms 7027 */ 7028 function build_highlight_array($terms) 7029 { 7030 global $mybb; 7031 7032 if($mybb->settings['minsearchword'] < 1) 7033 { 7034 $mybb->settings['minsearchword'] = 3; 7035 } 7036 7037 if(is_array($terms)) 7038 { 7039 $terms = implode(' ', $terms); 7040 } 7041 7042 // Strip out any characters that shouldn't be included 7043 $bad_characters = array( 7044 "(", 7045 ")", 7046 "+", 7047 "-", 7048 "~" 7049 ); 7050 $terms = str_replace($bad_characters, '', $terms); 7051 $words = array(); 7052 7053 // Check if this is a "series of words" - should be treated as an EXACT match 7054 if(my_strpos($terms, "\"") !== false) 7055 { 7056 $inquote = false; 7057 $terms = explode("\"", $terms); 7058 foreach($terms as $phrase) 7059 { 7060 $phrase = htmlspecialchars_uni($phrase); 7061 if($phrase != "") 7062 { 7063 if($inquote) 7064 { 7065 $words[] = trim($phrase); 7066 } 7067 else 7068 { 7069 $split_words = preg_split("#\s{1,}#", $phrase, -1); 7070 if(!is_array($split_words)) 7071 { 7072 continue; 7073 } 7074 foreach($split_words as $word) 7075 { 7076 if(!$word || strlen($word) < $mybb->settings['minsearchword']) 7077 { 7078 continue; 7079 } 7080 $words[] = trim($word); 7081 } 7082 } 7083 } 7084 $inquote = !$inquote; 7085 } 7086 } 7087 // Otherwise just a simple search query with no phrases 7088 else 7089 { 7090 $terms = htmlspecialchars_uni($terms); 7091 $split_words = preg_split("#\s{1,}#", $terms, -1); 7092 if(is_array($split_words)) 7093 { 7094 foreach($split_words as $word) 7095 { 7096 if(!$word || strlen($word) < $mybb->settings['minsearchword']) 7097 { 7098 continue; 7099 } 7100 $words[] = trim($word); 7101 } 7102 } 7103 } 7104 7105 // Sort the word array by length. Largest terms go first and work their way down to the smallest term. 7106 // This resolves problems like "test tes" where "tes" will be highlighted first, then "test" can't be highlighted because of the changed html 7107 usort($words, 'build_highlight_array_sort'); 7108 7109 $highlight_cache = array(); 7110 7111 // Loop through our words to build the PREG compatible strings 7112 foreach($words as $word) 7113 { 7114 $word = trim($word); 7115 7116 $word = my_strtolower($word); 7117 7118 // Special boolean operators should be stripped 7119 if($word == "" || $word == "or" || $word == "not" || $word == "and") 7120 { 7121 continue; 7122 } 7123 7124 // Now make PREG compatible 7125 $find = "/(?<!&|&#)\b([[:alnum:]]*)(".preg_quote($word, "/").")(?![^<>]*?>)/ui"; 7126 $replacement = "$1<span class=\"highlight\" style=\"padding-left: 0px; padding-right: 0px;\">$2</span>"; 7127 $highlight_cache[$find] = $replacement; 7128 } 7129 7130 return $highlight_cache; 7131 } 7132 7133 /** 7134 * Sort the word array by length. Largest terms go first and work their way down to the smallest term. 7135 * 7136 * @param string $a First word. 7137 * @param string $b Second word. 7138 * @return integer Result of comparison function. 7139 */ 7140 function build_highlight_array_sort($a, $b) 7141 { 7142 return strlen($b) - strlen($a); 7143 } 7144 7145 /** 7146 * Converts a decimal reference of a character to its UTF-8 equivalent 7147 * (Code by Anne van Kesteren, http://annevankesteren.nl/2005/05/character-references) 7148 * 7149 * @param int $src Decimal value of a character reference 7150 * @return string|bool 7151 */ 7152 function dec_to_utf8($src) 7153 { 7154 $dest = ''; 7155 7156 if($src < 0) 7157 { 7158 return false; 7159 } 7160 elseif($src <= 0x007f) 7161 { 7162 $dest .= chr($src); 7163 } 7164 elseif($src <= 0x07ff) 7165 { 7166 $dest .= chr(0xc0 | ($src >> 6)); 7167 $dest .= chr(0x80 | ($src & 0x003f)); 7168 } 7169 elseif($src <= 0xffff) 7170 { 7171 $dest .= chr(0xe0 | ($src >> 12)); 7172 $dest .= chr(0x80 | (($src >> 6) & 0x003f)); 7173 $dest .= chr(0x80 | ($src & 0x003f)); 7174 } 7175 elseif($src <= 0x10ffff) 7176 { 7177 $dest .= chr(0xf0 | ($src >> 18)); 7178 $dest .= chr(0x80 | (($src >> 12) & 0x3f)); 7179 $dest .= chr(0x80 | (($src >> 6) & 0x3f)); 7180 $dest .= chr(0x80 | ($src & 0x3f)); 7181 } 7182 else 7183 { 7184 // Out of range 7185 return false; 7186 } 7187 7188 return $dest; 7189 } 7190 7191 /** 7192 * Checks if a username has been disallowed for registration/use. 7193 * 7194 * @param string $username The username 7195 * @param boolean $update_lastuse True if the 'last used' dateline should be updated if a match is found. 7196 * @return boolean True if banned, false if not banned 7197 */ 7198 function is_banned_username($username, $update_lastuse=false) 7199 { 7200 global $db; 7201 $query = $db->simple_select('banfilters', 'filter, fid', "type='2'"); 7202 while($banned_username = $db->fetch_array($query)) 7203 { 7204 // Make regular expression * match 7205 $banned_username['filter'] = str_replace('\*', '(.*)', preg_quote($banned_username['filter'], '#')); 7206 if(preg_match("#(^|\b){$banned_username['filter']}($|\b)#i", $username)) 7207 { 7208 // Updating last use 7209 if($update_lastuse == true) 7210 { 7211 $db->update_query("banfilters", array("lastuse" => TIME_NOW), "fid='{$banned_username['fid']}'"); 7212 } 7213 return true; 7214 } 7215 } 7216 // Still here - good username 7217 return false; 7218 } 7219 7220 /** 7221 * Check if a specific email address has been banned. 7222 * 7223 * @param string $email The email address. 7224 * @param boolean $update_lastuse True if the 'last used' dateline should be updated if a match is found. 7225 * @return boolean True if banned, false if not banned 7226 */ 7227 function is_banned_email($email, $update_lastuse=false) 7228 { 7229 global $cache, $db; 7230 7231 $banned_cache = $cache->read("bannedemails"); 7232 7233 if($banned_cache === false) 7234 { 7235 // Failed to read cache, see if we can rebuild it 7236 $cache->update_bannedemails(); 7237 $banned_cache = $cache->read("bannedemails"); 7238 } 7239 7240 if(is_array($banned_cache) && !empty($banned_cache)) 7241 { 7242 foreach($banned_cache as $banned_email) 7243 { 7244 // Make regular expression * match 7245 $banned_email['filter'] = str_replace('\*', '(.*)', preg_quote($banned_email['filter'], '#')); 7246 7247 if(preg_match("#{$banned_email['filter']}#i", $email)) 7248 { 7249 // Updating last use 7250 if($update_lastuse == true) 7251 { 7252 $db->update_query("banfilters", array("lastuse" => TIME_NOW), "fid='{$banned_email['fid']}'"); 7253 } 7254 return true; 7255 } 7256 } 7257 } 7258 7259 // Still here - good email 7260 return false; 7261 } 7262 7263 /** 7264 * Checks if a specific IP address has been banned. 7265 * 7266 * @param string $ip_address The IP address. 7267 * @param boolean $update_lastuse True if the 'last used' dateline should be updated if a match is found. 7268 * @return boolean True if banned, false if not banned. 7269 */ 7270 function is_banned_ip($ip_address, $update_lastuse=false) 7271 { 7272 global $db, $cache; 7273 7274 $banned_ips = $cache->read("bannedips"); 7275 if(!is_array($banned_ips)) 7276 { 7277 return false; 7278 } 7279 7280 $ip_address = my_inet_pton($ip_address); 7281 foreach($banned_ips as $banned_ip) 7282 { 7283 if(!$banned_ip['filter']) 7284 { 7285 continue; 7286 } 7287 7288 $banned = false; 7289 7290 $ip_range = fetch_ip_range($banned_ip['filter']); 7291 if(is_array($ip_range)) 7292 { 7293 if(strcmp($ip_range[0], $ip_address) <= 0 && strcmp($ip_range[1], $ip_address) >= 0) 7294 { 7295 $banned = true; 7296 } 7297 } 7298 elseif($ip_address == $ip_range) 7299 { 7300 $banned = true; 7301 } 7302 if($banned) 7303 { 7304 // Updating last use 7305 if($update_lastuse == true) 7306 { 7307 $db->update_query("banfilters", array("lastuse" => TIME_NOW), "fid='{$banned_ip['fid']}'"); 7308 } 7309 return true; 7310 } 7311 } 7312 7313 // Still here - good ip 7314 return false; 7315 } 7316 7317 /** 7318 * Returns an array of supported timezones 7319 * 7320 * @return string[] Key is timezone offset, Value the language description 7321 */ 7322 function get_supported_timezones() 7323 { 7324 global $lang; 7325 $timezones = array( 7326 "-12" => $lang->timezone_gmt_minus_1200, 7327 "-11" => $lang->timezone_gmt_minus_1100, 7328 "-10" => $lang->timezone_gmt_minus_1000, 7329 "-9.5" => $lang->timezone_gmt_minus_950, 7330 "-9" => $lang->timezone_gmt_minus_900, 7331 "-8" => $lang->timezone_gmt_minus_800, 7332 "-7" => $lang->timezone_gmt_minus_700, 7333 "-6" => $lang->timezone_gmt_minus_600, 7334 "-5" => $lang->timezone_gmt_minus_500, 7335 "-4.5" => $lang->timezone_gmt_minus_450, 7336 "-4" => $lang->timezone_gmt_minus_400, 7337 "-3.5" => $lang->timezone_gmt_minus_350, 7338 "-3" => $lang->timezone_gmt_minus_300, 7339 "-2" => $lang->timezone_gmt_minus_200, 7340 "-1" => $lang->timezone_gmt_minus_100, 7341 "0" => $lang->timezone_gmt, 7342 "1" => $lang->timezone_gmt_100, 7343 "2" => $lang->timezone_gmt_200, 7344 "3" => $lang->timezone_gmt_300, 7345 "3.5" => $lang->timezone_gmt_350, 7346 "4" => $lang->timezone_gmt_400, 7347 "4.5" => $lang->timezone_gmt_450, 7348 "5" => $lang->timezone_gmt_500, 7349 "5.5" => $lang->timezone_gmt_550, 7350 "5.75" => $lang->timezone_gmt_575, 7351 "6" => $lang->timezone_gmt_600, 7352 "6.5" => $lang->timezone_gmt_650, 7353 "7" => $lang->timezone_gmt_700, 7354 "8" => $lang->timezone_gmt_800, 7355 "8.5" => $lang->timezone_gmt_850, 7356 "8.75" => $lang->timezone_gmt_875, 7357 "9" => $lang->timezone_gmt_900, 7358 "9.5" => $lang->timezone_gmt_950, 7359 "10" => $lang->timezone_gmt_1000, 7360 "10.5" => $lang->timezone_gmt_1050, 7361 "11" => $lang->timezone_gmt_1100, 7362 "11.5" => $lang->timezone_gmt_1150, 7363 "12" => $lang->timezone_gmt_1200, 7364 "12.75" => $lang->timezone_gmt_1275, 7365 "13" => $lang->timezone_gmt_1300, 7366 "14" => $lang->timezone_gmt_1400 7367 ); 7368 return $timezones; 7369 } 7370 7371 /** 7372 * Build a time zone selection list. 7373 * 7374 * @param string $name The name of the select 7375 * @param int $selected The selected time zone (defaults to GMT) 7376 * @param boolean $short True to generate a "short" list with just timezone and current time 7377 * @return string 7378 */ 7379 function build_timezone_select($name, $selected=0, $short=false) 7380 { 7381 global $mybb, $lang, $templates; 7382 7383 $timezones = get_supported_timezones(); 7384 7385 $selected = str_replace("+", "", $selected); 7386 $timezone_option = ''; 7387 foreach($timezones as $timezone => $label) 7388 { 7389 $selected_add = ""; 7390 if($selected == $timezone) 7391 { 7392 $selected_add = " selected=\"selected\""; 7393 } 7394 if($short == true) 7395 { 7396 $label = ''; 7397 if($timezone != 0) 7398 { 7399 $label = $timezone; 7400 if($timezone > 0) 7401 { 7402 $label = "+{$label}"; 7403 } 7404 if(strpos($timezone, ".") !== false) 7405 { 7406 $label = str_replace(".", ":", $label); 7407 $label = str_replace(":5", ":30", $label); 7408 $label = str_replace(":75", ":45", $label); 7409 } 7410 else 7411 { 7412 $label .= ":00"; 7413 } 7414 } 7415 $time_in_zone = my_date($mybb->settings['timeformat'], TIME_NOW, $timezone); 7416 $label = $lang->sprintf($lang->timezone_gmt_short, $label." ", $time_in_zone); 7417 } 7418 7419 eval("\$timezone_option .= \"".$templates->get("usercp_options_timezone_option")."\";"); 7420 } 7421 7422 eval("\$select = \"".$templates->get("usercp_options_timezone")."\";"); 7423 return $select; 7424 } 7425 7426 /** 7427 * Fetch the contents of a remote file. 7428 * 7429 * @param string $url The URL of the remote file 7430 * @param array $post_data The array of post data 7431 * @param int $max_redirects Number of maximum redirects 7432 * @return string|bool The remote file contents. False on failure 7433 */ 7434 function fetch_remote_file($url, $post_data=array(), $max_redirects=20) 7435 { 7436 global $mybb, $config; 7437 7438 if(!my_validate_url($url, true)) 7439 { 7440 return false; 7441 } 7442 7443 $url_components = @parse_url($url); 7444 7445 if(!isset($url_components['scheme'])) 7446 { 7447 $url_components['scheme'] = 'https'; 7448 } 7449 if(!isset($url_components['port'])) 7450 { 7451 $url_components['port'] = $url_components['scheme'] == 'https' ? 443 : 80; 7452 } 7453 7454 if( 7455 !$url_components || 7456 empty($url_components['host']) || 7457 (!empty($url_components['scheme']) && !in_array($url_components['scheme'], array('http', 'https'))) || 7458 (!in_array($url_components['port'], array(80, 8080, 443))) || 7459 (!empty($config['disallowed_remote_hosts']) && in_array($url_components['host'], $config['disallowed_remote_hosts'])) 7460 ) 7461 { 7462 return false; 7463 } 7464 7465 $addresses = get_ip_by_hostname($url_components['host']); 7466 $destination_address = $addresses[0]; 7467 7468 if(!empty($config['disallowed_remote_addresses'])) 7469 { 7470 foreach($config['disallowed_remote_addresses'] as $disallowed_address) 7471 { 7472 $ip_range = fetch_ip_range($disallowed_address); 7473 7474 $packed_address = my_inet_pton($destination_address); 7475 7476 if(is_array($ip_range)) 7477 { 7478 if(strcmp($ip_range[0], $packed_address) <= 0 && strcmp($ip_range[1], $packed_address) >= 0) 7479 { 7480 return false; 7481 } 7482 } 7483 elseif($destination_address == $disallowed_address) 7484 { 7485 return false; 7486 } 7487 } 7488 } 7489 7490 $post_body = ''; 7491 if(!empty($post_data)) 7492 { 7493 foreach($post_data as $key => $val) 7494 { 7495 $post_body .= '&'.urlencode($key).'='.urlencode($val); 7496 } 7497 $post_body = ltrim($post_body, '&'); 7498 } 7499 7500 if(function_exists("curl_init")) 7501 { 7502 $fetch_header = $max_redirects > 0; 7503 7504 $ch = curl_init(); 7505 7506 $curlopt = array( 7507 CURLOPT_URL => $url, 7508 CURLOPT_HEADER => $fetch_header, 7509 CURLOPT_TIMEOUT => 10, 7510 CURLOPT_RETURNTRANSFER => 1, 7511 CURLOPT_FOLLOWLOCATION => 0, 7512 ); 7513 7514 if($ca_bundle_path = get_ca_bundle_path()) 7515 { 7516 $curlopt[CURLOPT_SSL_VERIFYPEER] = 1; 7517 $curlopt[CURLOPT_CAINFO] = $ca_bundle_path; 7518 } 7519 else 7520 { 7521 $curlopt[CURLOPT_SSL_VERIFYPEER] = 0; 7522 } 7523 7524 $curl_version_info = curl_version(); 7525 $curl_version = $curl_version_info['version']; 7526 7527 if(version_compare(PHP_VERSION, '7.0.7', '>=') && version_compare($curl_version, '7.49', '>=')) 7528 { 7529 // CURLOPT_CONNECT_TO 7530 $curlopt[10243] = array( 7531 $url_components['host'].':'.$url_components['port'].':'.$destination_address 7532 ); 7533 } 7534 elseif(version_compare(PHP_VERSION, '5.5', '>=') && version_compare($curl_version, '7.21.3', '>=')) 7535 { 7536 // CURLOPT_RESOLVE 7537 $curlopt[10203] = array( 7538 $url_components['host'].':'.$url_components['port'].':'.$destination_address 7539 ); 7540 } 7541 7542 if(!empty($post_body)) 7543 { 7544 $curlopt[CURLOPT_POST] = 1; 7545 $curlopt[CURLOPT_POSTFIELDS] = $post_body; 7546 } 7547 7548 curl_setopt_array($ch, $curlopt); 7549 7550 $response = curl_exec($ch); 7551 7552 if($fetch_header) 7553 { 7554 $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); 7555 $header = substr($response, 0, $header_size); 7556 $body = substr($response, $header_size); 7557 7558 if(in_array(curl_getinfo($ch, CURLINFO_HTTP_CODE), array(301, 302))) 7559 { 7560 preg_match('/^Location:(.*?)(?:\n|$)/im', $header, $matches); 7561 7562 if($matches) 7563 { 7564 $data = fetch_remote_file(trim(array_pop($matches)), $post_data, --$max_redirects); 7565 } 7566 } 7567 else 7568 { 7569 $data = $body; 7570 } 7571 } 7572 else 7573 { 7574 $data = $response; 7575 } 7576 7577 curl_close($ch); 7578 return $data; 7579 } 7580 else if(function_exists("fsockopen")) 7581 { 7582 if(!isset($url_components['path'])) 7583 { 7584 $url_components['path'] = "/"; 7585 } 7586 if(isset($url_components['query'])) 7587 { 7588 $url_components['path'] .= "?{$url_components['query']}"; 7589 } 7590 7591 $scheme = ''; 7592 7593 if($url_components['scheme'] == 'https') 7594 { 7595 $scheme = 'ssl://'; 7596 if($url_components['port'] == 80) 7597 { 7598 $url_components['port'] = 443; 7599 } 7600 } 7601 7602 if(function_exists('stream_context_create')) 7603 { 7604 if($url_components['scheme'] == 'https' && $ca_bundle_path = get_ca_bundle_path()) 7605 { 7606 $context = stream_context_create(array( 7607 'ssl' => array( 7608 'verify_peer' => true, 7609 'verify_peer_name' => true, 7610 'peer_name' => $url_components['host'], 7611 'cafile' => $ca_bundle_path, 7612 ), 7613 )); 7614 } 7615 else 7616 { 7617 $context = stream_context_create(array( 7618 'ssl' => array( 7619 'verify_peer' => false, 7620 'verify_peer_name' => false, 7621 'peer_name' => $url_components['host'], 7622 ), 7623 )); 7624 } 7625 7626 $fp = @stream_socket_client($scheme.$destination_address.':'.(int)$url_components['port'], $error_no, $error, 10, STREAM_CLIENT_CONNECT, $context); 7627 } 7628 else 7629 { 7630 $fp = @fsockopen($scheme.$url_components['host'], (int)$url_components['port'], $error_no, $error, 10); 7631 } 7632 7633 if(!$fp) 7634 { 7635 return false; 7636 } 7637 @stream_set_timeout($fp, 10); 7638 $headers = array(); 7639 if(!empty($post_body)) 7640 { 7641 $headers[] = "POST {$url_components['path']} HTTP/1.0"; 7642 $headers[] = "Content-Length: ".strlen($post_body); 7643 $headers[] = "Content-Type: application/x-www-form-urlencoded"; 7644 } 7645 else 7646 { 7647 $headers[] = "GET {$url_components['path']} HTTP/1.0"; 7648 } 7649 7650 $headers[] = "Host: {$url_components['host']}"; 7651 $headers[] = "Connection: Close"; 7652 $headers[] = ''; 7653 7654 if(!empty($post_body)) 7655 { 7656 $headers[] = $post_body; 7657 } 7658 else 7659 { 7660 // If we have no post body, we need to add an empty element to make sure we've got \r\n\r\n before the (non-existent) body starts 7661 $headers[] = ''; 7662 } 7663 7664 $headers = implode("\r\n", $headers); 7665 if(!@fwrite($fp, $headers)) 7666 { 7667 return false; 7668 } 7669 7670 $data = null; 7671 7672 while(!feof($fp)) 7673 { 7674 $data .= fgets($fp, 12800); 7675 } 7676 fclose($fp); 7677 7678 $data = explode("\r\n\r\n", $data, 2); 7679 7680 $header = $data[0]; 7681 $status_line = current(explode("\n\n", $header, 1)); 7682 $body = $data[1]; 7683 7684 if($max_redirects > 0 && (strstr($status_line, ' 301 ') || strstr($status_line, ' 302 '))) 7685 { 7686 preg_match('/^Location:(.*?)(?:\n|$)/im', $header, $matches); 7687 7688 if($matches) 7689 { 7690 $data = fetch_remote_file(trim(array_pop($matches)), $post_data, --$max_redirects); 7691 } 7692 } 7693 else 7694 { 7695 $data = $body; 7696 } 7697 7698 return $data; 7699 } 7700 else 7701 { 7702 return false; 7703 } 7704 } 7705 7706 /** 7707 * Resolves a hostname into a set of IP addresses. 7708 * 7709 * @param string $hostname The hostname to be resolved 7710 * @return array|bool The resulting IP addresses. False on failure 7711 */ 7712 function get_ip_by_hostname($hostname) 7713 { 7714 $addresses = @gethostbynamel($hostname); 7715 7716 if(!$addresses) 7717 { 7718 $result_set = @dns_get_record($hostname, DNS_A | DNS_AAAA); 7719 7720 if($result_set) 7721 { 7722 $addresses = array_column($result_set, 'ip'); 7723 } 7724 else 7725 { 7726 return false; 7727 } 7728 } 7729 7730 return $addresses; 7731 } 7732 7733 /** 7734 * Returns the location of the CA bundle defined in the PHP configuration. 7735 * 7736 * @return string|bool The location of the CA bundle, false if not set 7737 */ 7738 function get_ca_bundle_path() 7739 { 7740 if($path = ini_get('openssl.cafile')) 7741 { 7742 return $path; 7743 } 7744 if($path = ini_get('curl.cainfo')) 7745 { 7746 return $path; 7747 } 7748 7749 return false; 7750 } 7751 7752 /** 7753 * Checks if a particular user is a super administrator. 7754 * 7755 * @param int $uid The user ID to check against the list of super admins 7756 * @return boolean True if a super admin, false if not 7757 */ 7758 function is_super_admin($uid) 7759 { 7760 static $super_admins; 7761 7762 if(!isset($super_admins)) 7763 { 7764 global $mybb; 7765 $super_admins = str_replace(" ", "", $mybb->config['super_admins']); 7766 } 7767 7768 if(my_strpos(",{$super_admins},", ",{$uid},") === false) 7769 { 7770 return false; 7771 } 7772 else 7773 { 7774 return true; 7775 } 7776 } 7777 7778 /** 7779 * Checks if a user is a member of a particular group 7780 * Originates from frostschutz's PluginLibrary 7781 * github.com/frostschutz 7782 * 7783 * @param array|int|string A selection of groups (as array or comma seperated) to check or -1 for any group 7784 * @param bool|array|int False assumes the current user. Otherwise an user array or an id can be passed 7785 * @return array Array of groups specified in the first param to which the user belongs 7786 */ 7787 function is_member($groups, $user = false) 7788 { 7789 global $mybb; 7790 7791 if(empty($groups)) 7792 { 7793 return array(); 7794 } 7795 7796 if($user == false) 7797 { 7798 $user = $mybb->user; 7799 } 7800 else if(!is_array($user)) 7801 { 7802 // Assume it's a UID 7803 $user = get_user($user); 7804 } 7805 7806 $memberships = array_map('intval', explode(',', $user['additionalgroups'])); 7807 $memberships[] = $user['usergroup']; 7808 7809 if(!is_array($groups)) 7810 { 7811 if((int)$groups == -1) 7812 { 7813 return $memberships; 7814 } 7815 else 7816 { 7817 if(is_string($groups)) 7818 { 7819 $groups = explode(',', $groups); 7820 } 7821 else 7822 { 7823 $groups = (array)$groups; 7824 } 7825 } 7826 } 7827 7828 $groups = array_filter(array_map('intval', $groups)); 7829 7830 return array_intersect($groups, $memberships); 7831 } 7832 7833 /** 7834 * Split a string based on the specified delimeter, ignoring said delimeter in escaped strings. 7835 * Ex: the "quick brown fox" jumped, could return 1 => the, 2 => quick brown fox, 3 => jumped 7836 * 7837 * @param string $delimeter The delimeter to split by 7838 * @param string $string The string to split 7839 * @param string $escape The escape character or string if we have one. 7840 * @return array Array of split string 7841 */ 7842 function escaped_explode($delimeter, $string, $escape="") 7843 { 7844 $strings = array(); 7845 $original = $string; 7846 $in_escape = false; 7847 if($escape) 7848 { 7849 if(is_array($escape)) 7850 { 7851 function escaped_explode_escape($string) 7852 { 7853 return preg_quote($string, "#"); 7854 } 7855 $escape_preg = "(".implode("|", array_map("escaped_explode_escape", $escape)).")"; 7856 } 7857 else 7858 { 7859 $escape_preg = preg_quote($escape, "#"); 7860 } 7861 $quoted_strings = preg_split("#(?<!\\\){$escape_preg}#", $string); 7862 } 7863 else 7864 { 7865 $quoted_strings = array($string); 7866 } 7867 foreach($quoted_strings as $string) 7868 { 7869 if($string != "") 7870 { 7871 if($in_escape) 7872 { 7873 $strings[] = trim($string); 7874 } 7875 else 7876 { 7877 $split_strings = explode($delimeter, $string); 7878 foreach($split_strings as $string) 7879 { 7880 if($string == "") continue; 7881 $strings[] = trim($string); 7882 } 7883 } 7884 } 7885 $in_escape = !$in_escape; 7886 } 7887 if(!count($strings)) 7888 { 7889 return $original; 7890 } 7891 return $strings; 7892 } 7893 7894 /** 7895 * DEPRECATED! Please use IPv6 compatible fetch_ip_range! 7896 * Fetch an IPv4 long formatted range for searching IPv4 IP addresses. 7897 * 7898 * @deprecated 7899 * @param string $ip The IP address to convert to a range based LONG 7900 * @return string|array If a full IP address is provided, the ip2long equivalent, otherwise an array of the upper & lower extremities of the IP 7901 */ 7902 function fetch_longipv4_range($ip) 7903 { 7904 $ip_bits = explode(".", $ip); 7905 $ip_string1 = $ip_string2 = ""; 7906 7907 if($ip == "*") 7908 { 7909 return array(ip2long('0.0.0.0'), ip2long('255.255.255.255')); 7910 } 7911 7912 if(strpos($ip, ".*") === false) 7913 { 7914 $ip = str_replace("*", "", $ip); 7915 if(count($ip_bits) == 4) 7916 { 7917 return ip2long($ip); 7918 } 7919 else 7920 { 7921 return array(ip2long($ip.".0"), ip2long($ip.".255")); 7922 } 7923 } 7924 // Wildcard based IP provided 7925 else 7926 { 7927 $sep = ""; 7928 foreach($ip_bits as $piece) 7929 { 7930 if($piece == "*") 7931 { 7932 $ip_string1 .= $sep."0"; 7933 $ip_string2 .= $sep."255"; 7934 } 7935 else 7936 { 7937 $ip_string1 .= $sep.$piece; 7938 $ip_string2 .= $sep.$piece; 7939 } 7940 $sep = "."; 7941 } 7942 return array(ip2long($ip_string1), ip2long($ip_string2)); 7943 } 7944 } 7945 7946 /** 7947 * Fetch a list of ban times for a user account. 7948 * 7949 * @return array Array of ban times 7950 */ 7951 function fetch_ban_times() 7952 { 7953 global $plugins, $lang; 7954 7955 // Days-Months-Years 7956 $ban_times = array( 7957 "1-0-0" => "1 {$lang->day}", 7958 "2-0-0" => "2 {$lang->days}", 7959 "3-0-0" => "3 {$lang->days}", 7960 "4-0-0" => "4 {$lang->days}", 7961 "5-0-0" => "5 {$lang->days}", 7962 "6-0-0" => "6 {$lang->days}", 7963 "7-0-0" => "1 {$lang->week}", 7964 "14-0-0" => "2 {$lang->weeks}", 7965 "21-0-0" => "3 {$lang->weeks}", 7966 "0-1-0" => "1 {$lang->month}", 7967 "0-2-0" => "2 {$lang->months}", 7968 "0-3-0" => "3 {$lang->months}", 7969 "0-4-0" => "4 {$lang->months}", 7970 "0-5-0" => "5 {$lang->months}", 7971 "0-6-0" => "6 {$lang->months}", 7972 "0-0-1" => "1 {$lang->year}", 7973 "0-0-2" => "2 {$lang->years}" 7974 ); 7975 7976 $ban_times = $plugins->run_hooks("functions_fetch_ban_times", $ban_times); 7977 7978 $ban_times['---'] = $lang->permanent; 7979 return $ban_times; 7980 } 7981 7982 /** 7983 * Format a ban length in to a UNIX timestamp. 7984 * 7985 * @param string $date The ban length string 7986 * @param int $stamp The optional UNIX timestamp, if 0, current time is used. 7987 * @return int The UNIX timestamp when the ban will be lifted 7988 */ 7989 function ban_date2timestamp($date, $stamp=0) 7990 { 7991 if($stamp == 0) 7992 { 7993 $stamp = TIME_NOW; 7994 } 7995 $d = explode('-', $date); 7996 $nowdate = date("H-j-n-Y", $stamp); 7997 $n = explode('-', $nowdate); 7998 $n[1] += $d[0]; 7999 $n[2] += $d[1]; 8000 $n[3] += $d[2]; 8001 return mktime(date("G", $stamp), date("i", $stamp), 0, $n[2], $n[1], $n[3]); 8002 } 8003 8004 /** 8005 * Expire old warnings in the database. 8006 * 8007 * @return bool 8008 */ 8009 function expire_warnings() 8010 { 8011 global $warningshandler; 8012 8013 if(!is_object($warningshandler)) 8014 { 8015 require_once MYBB_ROOT.'inc/datahandlers/warnings.php'; 8016 $warningshandler = new WarningsHandler('update'); 8017 } 8018 8019 return $warningshandler->expire_warnings(); 8020 } 8021 8022 /** 8023 * Custom chmod function to fix problems with hosts who's server configurations screw up umasks 8024 * 8025 * @param string $file The file to chmod 8026 * @param string $mode The mode to chmod(i.e. 0666) 8027 * @return bool 8028 */ 8029 function my_chmod($file, $mode) 8030 { 8031 // Passing $mode as an octal number causes strlen and substr to return incorrect values. Instead pass as a string 8032 if(substr($mode, 0, 1) != '0' || strlen($mode) !== 4) 8033 { 8034 return false; 8035 } 8036 $old_umask = umask(0); 8037 8038 // We convert the octal string to a decimal number because passing a octal string doesn't work with chmod 8039 // and type casting subsequently removes the prepended 0 which is needed for octal numbers 8040 $result = chmod($file, octdec($mode)); 8041 umask($old_umask); 8042 return $result; 8043 } 8044 8045 /** 8046 * Custom rmdir function to loop through an entire directory and delete all files/folders within 8047 * 8048 * @param string $path The path to the directory 8049 * @param array $ignore Any files you wish to ignore (optional) 8050 * @return bool 8051 */ 8052 function my_rmdir_recursive($path, $ignore=array()) 8053 { 8054 global $orig_dir; 8055 8056 if(!isset($orig_dir)) 8057 { 8058 $orig_dir = $path; 8059 } 8060 8061 if(@is_dir($path) && !@is_link($path)) 8062 { 8063 if($dh = @opendir($path)) 8064 { 8065 while(($file = @readdir($dh)) !== false) 8066 { 8067 if($file == '.' || $file == '..' || $file == '.svn' || in_array($path.'/'.$file, $ignore) || !my_rmdir_recursive($path.'/'.$file)) 8068 { 8069 continue; 8070 } 8071 } 8072 @closedir($dh); 8073 } 8074 8075 // Are we done? Don't delete the main folder too and return true 8076 if($path == $orig_dir) 8077 { 8078 return true; 8079 } 8080 8081 return @rmdir($path); 8082 } 8083 8084 return @unlink($path); 8085 } 8086 8087 /** 8088 * Counts the number of subforums in a array([pid][disporder][fid]) starting from the pid 8089 * 8090 * @param array $array The array of forums 8091 * @return integer The number of sub forums 8092 */ 8093 function subforums_count($array=array()) 8094 { 8095 $count = 0; 8096 foreach($array as $array2) 8097 { 8098 $count += count($array2); 8099 } 8100 8101 return $count; 8102 } 8103 8104 /** 8105 * DEPRECATED! Please use IPv6 compatible my_inet_pton! 8106 * Fix for PHP's ip2long to guarantee a 32-bit signed integer value is produced (this is aimed 8107 * at 64-bit versions of PHP) 8108 * 8109 * @deprecated 8110 * @param string $ip The IP to convert 8111 * @return integer IP in 32-bit signed format 8112 */ 8113 function my_ip2long($ip) 8114 { 8115 $ip_long = ip2long($ip); 8116 8117 if(!$ip_long) 8118 { 8119 $ip_long = sprintf("%u", ip2long($ip)); 8120 8121 if(!$ip_long) 8122 { 8123 return 0; 8124 } 8125 } 8126 8127 if($ip_long >= 2147483648) // Won't occur on 32-bit PHP 8128 { 8129 $ip_long -= 4294967296; 8130 } 8131 8132 return $ip_long; 8133 } 8134 8135 /** 8136 * DEPRECATED! Please use IPv6 compatible my_inet_ntop! 8137 * As above, fix for PHP's long2ip on 64-bit versions 8138 * 8139 * @deprecated 8140 * @param integer $long The IP to convert (will accept 64-bit IPs as well) 8141 * @return string IP in IPv4 format 8142 */ 8143 function my_long2ip($long) 8144 { 8145 // On 64-bit machines is_int will return true. On 32-bit it will return false 8146 if($long < 0 && is_int(2147483648)) 8147 { 8148 // We have a 64-bit system 8149 $long += 4294967296; 8150 } 8151 return long2ip($long); 8152 } 8153 8154 /** 8155 * Converts a human readable IP address to its packed in_addr representation 8156 * 8157 * @param string $ip The IP to convert 8158 * @return string IP in 32bit or 128bit binary format 8159 */ 8160 function my_inet_pton($ip) 8161 { 8162 if(function_exists('inet_pton')) 8163 { 8164 return @inet_pton($ip); 8165 } 8166 else 8167 { 8168 /** 8169 * Replace inet_pton() 8170 * 8171 * @category PHP 8172 * @package PHP_Compat 8173 * @license LGPL - http://www.gnu.org/licenses/lgpl.html 8174 * @copyright 2004-2007 Aidan Lister <aidan@php.net>, Arpad Ray <arpad@php.net> 8175 * @link http://php.net/inet_pton 8176 * @author Arpad Ray <arpad@php.net> 8177 * @version $Revision: 269597 $ 8178 */ 8179 $r = ip2long($ip); 8180 if($r !== false && $r != -1) 8181 { 8182 return pack('N', $r); 8183 } 8184 8185 $delim_count = substr_count($ip, ':'); 8186 if($delim_count < 1 || $delim_count > 7) 8187 { 8188 return false; 8189 } 8190 8191 $r = explode(':', $ip); 8192 $rcount = count($r); 8193 if(($doub = array_search('', $r, 1)) !== false) 8194 { 8195 $length = (!$doub || $doub == $rcount - 1 ? 2 : 1); 8196 array_splice($r, $doub, $length, array_fill(0, 8 + $length - $rcount, 0)); 8197 } 8198 8199 $r = array_map('hexdec', $r); 8200 array_unshift($r, 'n*'); 8201 $r = call_user_func_array('pack', $r); 8202 8203 return $r; 8204 } 8205 } 8206 8207 /** 8208 * Converts a packed internet address to a human readable representation 8209 * 8210 * @param string $ip IP in 32bit or 128bit binary format 8211 * @return string IP in human readable format 8212 */ 8213 function my_inet_ntop($ip) 8214 { 8215 if(function_exists('inet_ntop')) 8216 { 8217 return @inet_ntop($ip); 8218 } 8219 else 8220 { 8221 /** 8222 * Replace inet_ntop() 8223 * 8224 * @category PHP 8225 * @package PHP_Compat 8226 * @license LGPL - http://www.gnu.org/licenses/lgpl.html 8227 * @copyright 2004-2007 Aidan Lister <aidan@php.net>, Arpad Ray <arpad@php.net> 8228 * @link http://php.net/inet_ntop 8229 * @author Arpad Ray <arpad@php.net> 8230 * @version $Revision: 269597 $ 8231 */ 8232 switch(strlen($ip)) 8233 { 8234 case 4: 8235 list(,$r) = unpack('N', $ip); 8236 return long2ip($r); 8237 case 16: 8238 $r = substr(chunk_split(bin2hex($ip), 4, ':'), 0, -1); 8239 $r = preg_replace( 8240 array('/(?::?\b0+\b:?){2,}/', '/\b0+([^0])/e'), 8241 array('::', '(int)"$1"?"$1":"0$1"'), 8242 $r); 8243 return $r; 8244 } 8245 return false; 8246 } 8247 } 8248 8249 /** 8250 * Fetch an binary formatted range for searching IPv4 and IPv6 IP addresses. 8251 * 8252 * @param string $ipaddress The IP address to convert to a range 8253 * @return string|array|bool If a full IP address is provided, the in_addr representation, otherwise an array of the upper & lower extremities of the IP. False on failure 8254 */ 8255 function fetch_ip_range($ipaddress) 8256 { 8257 // Wildcard 8258 if(strpos($ipaddress, '*') !== false) 8259 { 8260 if(strpos($ipaddress, ':') !== false) 8261 { 8262 // IPv6 8263 $upper = str_replace('*', 'ffff', $ipaddress); 8264 $lower = str_replace('*', '0', $ipaddress); 8265 } 8266 else 8267 { 8268 // IPv4 8269 $ip_bits = count(explode('.', $ipaddress)); 8270 if($ip_bits < 4) 8271 { 8272 // Support for 127.0.* 8273 $replacement = str_repeat('.*', 4-$ip_bits); 8274 $ipaddress = substr_replace($ipaddress, $replacement, strrpos($ipaddress, '*')+1, 0); 8275 } 8276 $upper = str_replace('*', '255', $ipaddress); 8277 $lower = str_replace('*', '0', $ipaddress); 8278 } 8279 $upper = my_inet_pton($upper); 8280 $lower = my_inet_pton($lower); 8281 if($upper === false || $lower === false) 8282 { 8283 return false; 8284 } 8285 return array($lower, $upper); 8286 } 8287 // CIDR notation 8288 elseif(strpos($ipaddress, '/') !== false) 8289 { 8290 $ipaddress = explode('/', $ipaddress); 8291 $ip_address = $ipaddress[0]; 8292 $ip_range = (int)$ipaddress[1]; 8293 8294 if(empty($ip_address) || empty($ip_range)) 8295 { 8296 // Invalid input 8297 return false; 8298 } 8299 else 8300 { 8301 $ip_address = my_inet_pton($ip_address); 8302 8303 if(!$ip_address) 8304 { 8305 // Invalid IP address 8306 return false; 8307 } 8308 } 8309 8310 /** 8311 * Taken from: https://github.com/NewEraCracker/php_work/blob/master/ipRangeCalculate.php 8312 * Author: NewEraCracker 8313 * License: Public Domain 8314 */ 8315 8316 // Pack IP, Set some vars 8317 $ip_pack = $ip_address; 8318 $ip_pack_size = strlen($ip_pack); 8319 $ip_bits_size = $ip_pack_size*8; 8320 8321 // IP bits (lots of 0's and 1's) 8322 $ip_bits = ''; 8323 for($i = 0; $i < $ip_pack_size; $i = $i+1) 8324 { 8325 $bit = decbin(ord($ip_pack[$i])); 8326 $bit = str_pad($bit, 8, '0', STR_PAD_LEFT); 8327 $ip_bits .= $bit; 8328 } 8329 8330 // Significative bits (from the ip range) 8331 $ip_bits = substr($ip_bits, 0, $ip_range); 8332 8333 // Some calculations 8334 $ip_lower_bits = str_pad($ip_bits, $ip_bits_size, '0', STR_PAD_RIGHT); 8335 $ip_higher_bits = str_pad($ip_bits, $ip_bits_size, '1', STR_PAD_RIGHT); 8336 8337 // Lower IP 8338 $ip_lower_pack = ''; 8339 for($i=0; $i < $ip_bits_size; $i=$i+8) 8340 { 8341 $chr = substr($ip_lower_bits, $i, 8); 8342 $chr = chr(bindec($chr)); 8343 $ip_lower_pack .= $chr; 8344 } 8345 8346 // Higher IP 8347 $ip_higher_pack = ''; 8348 for($i=0; $i < $ip_bits_size; $i=$i+8) 8349 { 8350 $chr = substr($ip_higher_bits, $i, 8); 8351 $chr = chr( bindec($chr) ); 8352 $ip_higher_pack .= $chr; 8353 } 8354 8355 return array($ip_lower_pack, $ip_higher_pack); 8356 } 8357 // Just on IP address 8358 else 8359 { 8360 return my_inet_pton($ipaddress); 8361 } 8362 } 8363 8364 /** 8365 * Time how long it takes for a particular piece of code to run. Place calls above & below the block of code. 8366 * 8367 * @return float The time taken 8368 */ 8369 function get_execution_time() 8370 { 8371 static $time_start; 8372 8373 $time = microtime(true); 8374 8375 // Just starting timer, init and return 8376 if(!$time_start) 8377 { 8378 $time_start = $time; 8379 return; 8380 } 8381 // Timer has run, return execution time 8382 else 8383 { 8384 $total = $time-$time_start; 8385 if($total < 0) $total = 0; 8386 $time_start = 0; 8387 return $total; 8388 } 8389 } 8390 8391 /** 8392 * Processes a checksum list on MyBB files and returns a result set 8393 * 8394 * @param string $path The base path 8395 * @param int $count The count of files 8396 * @return array The bad files 8397 */ 8398 function verify_files($path=MYBB_ROOT, $count=0) 8399 { 8400 global $mybb, $checksums, $bad_verify_files; 8401 8402 // We don't need to check these types of files 8403 $ignore = array(".", "..", ".svn", "config.php", "settings.php", "Thumb.db", "config.default.php", "lock", "htaccess.txt", "htaccess-nginx.txt", "logo.gif", "logo.png"); 8404 $ignore_ext = array("attach"); 8405 8406 if(substr($path, -1, 1) == "/") 8407 { 8408 $path = substr($path, 0, -1); 8409 } 8410 8411 if(!is_array($bad_verify_files)) 8412 { 8413 $bad_verify_files = array(); 8414 } 8415 8416 // Make sure that we're in a directory and it's not a symbolic link 8417 if(@is_dir($path) && !@is_link($path)) 8418 { 8419 if($dh = @opendir($path)) 8420 { 8421 // Loop through all the files/directories in this directory 8422 while(($file = @readdir($dh)) !== false) 8423 { 8424 if(in_array($file, $ignore) || in_array(get_extension($file), $ignore_ext)) 8425 { 8426 continue; 8427 } 8428 8429 // Recurse through the directory tree 8430 if(is_dir($path."/".$file)) 8431 { 8432 verify_files($path."/".$file, ($count+1)); 8433 continue; 8434 } 8435 8436 // We only need the last part of the path (from the MyBB directory to the file. i.e. inc/functions.php) 8437 $file_path = ".".str_replace(substr(MYBB_ROOT, 0, -1), "", $path)."/".$file; 8438 8439 // Does this file even exist in our official list? Perhaps it's a plugin 8440 if(array_key_exists($file_path, $checksums)) 8441 { 8442 $filename = $path."/".$file; 8443 $handle = fopen($filename, "rb"); 8444 $hashingContext = hash_init('sha512'); 8445 while(!feof($handle)) 8446 { 8447 hash_update($hashingContext, fread($handle, 8192)); 8448 } 8449 fclose($handle); 8450 8451 $checksum = hash_final($hashingContext); 8452 8453 // Does it match any of our hashes (unix/windows new lines taken into consideration with the hashes) 8454 if(!in_array($checksum, $checksums[$file_path])) 8455 { 8456 $bad_verify_files[] = array("status" => "changed", "path" => $file_path); 8457 } 8458 } 8459 unset($checksums[$file_path]); 8460 } 8461 @closedir($dh); 8462 } 8463 } 8464 8465 if($count == 0) 8466 { 8467 if(!empty($checksums)) 8468 { 8469 foreach($checksums as $file_path => $hashes) 8470 { 8471 if(in_array(basename($file_path), $ignore)) 8472 { 8473 continue; 8474 } 8475 $bad_verify_files[] = array("status" => "missing", "path" => $file_path); 8476 } 8477 } 8478 } 8479 8480 // uh oh 8481 if($count == 0) 8482 { 8483 return $bad_verify_files; 8484 } 8485 } 8486 8487 /** 8488 * Returns a signed value equal to an integer 8489 * 8490 * @param int $int The integer 8491 * @return string The signed equivalent 8492 */ 8493 function signed($int) 8494 { 8495 if($int < 0) 8496 { 8497 return "$int"; 8498 } 8499 else 8500 { 8501 return "+$int"; 8502 } 8503 } 8504 8505 /** 8506 * Returns a securely generated seed 8507 * 8508 * @return string A secure binary seed 8509 */ 8510 function secure_binary_seed_rng($bytes) 8511 { 8512 $output = null; 8513 8514 if(version_compare(PHP_VERSION, '7.0', '>=')) 8515 { 8516 try 8517 { 8518 $output = random_bytes($bytes); 8519 } catch (Exception $e) { 8520 } 8521 } 8522 8523 if(strlen($output) < $bytes) 8524 { 8525 if(@is_readable('/dev/urandom') && ($handle = @fopen('/dev/urandom', 'rb'))) 8526 { 8527 $output = @fread($handle, $bytes); 8528 @fclose($handle); 8529 } 8530 } 8531 else 8532 { 8533 return $output; 8534 } 8535 8536 if(strlen($output) < $bytes) 8537 { 8538 if(function_exists('mcrypt_create_iv')) 8539 { 8540 if (DIRECTORY_SEPARATOR == '/') 8541 { 8542 $source = MCRYPT_DEV_URANDOM; 8543 } 8544 else 8545 { 8546 $source = MCRYPT_RAND; 8547 } 8548 8549 $output = @mcrypt_create_iv($bytes, $source); 8550 } 8551 } 8552 else 8553 { 8554 return $output; 8555 } 8556 8557 if(strlen($output) < $bytes) 8558 { 8559 if(function_exists('openssl_random_pseudo_bytes')) 8560 { 8561 // PHP <5.3.4 had a bug which makes that function unusable on Windows 8562 if ((DIRECTORY_SEPARATOR == '/') || version_compare(PHP_VERSION, '5.3.4', '>=')) 8563 { 8564 $output = openssl_random_pseudo_bytes($bytes, $crypto_strong); 8565 if ($crypto_strong == false) 8566 { 8567 $output = null; 8568 } 8569 } 8570 } 8571 } 8572 else 8573 { 8574 return $output; 8575 } 8576 8577 if(strlen($output) < $bytes) 8578 { 8579 if(class_exists('COM')) 8580 { 8581 try 8582 { 8583 $CAPI_Util = new COM('CAPICOM.Utilities.1'); 8584 if(is_callable(array($CAPI_Util, 'GetRandom'))) 8585 { 8586 $output = $CAPI_Util->GetRandom($bytes, 0); 8587 } 8588 } catch (Exception $e) { 8589 } 8590 } 8591 } 8592 else 8593 { 8594 return $output; 8595 } 8596 8597 if(strlen($output) < $bytes) 8598 { 8599 // Close to what PHP basically uses internally to seed, but not quite. 8600 $unique_state = microtime().@getmypid(); 8601 8602 $rounds = ceil($bytes / 16); 8603 8604 for($i = 0; $i < $rounds; $i++) 8605 { 8606 $unique_state = md5(microtime().$unique_state); 8607 $output .= md5($unique_state); 8608 } 8609 8610 $output = substr($output, 0, ($bytes * 2)); 8611 8612 $output = pack('H*', $output); 8613 8614 return $output; 8615 } 8616 else 8617 { 8618 return $output; 8619 } 8620 } 8621 8622 /** 8623 * Returns a securely generated seed integer 8624 * 8625 * @return int An integer equivalent of a secure hexadecimal seed 8626 */ 8627 function secure_seed_rng() 8628 { 8629 $bytes = PHP_INT_SIZE; 8630 8631 do 8632 { 8633 8634 $output = secure_binary_seed_rng($bytes); 8635 8636 // convert binary data to a decimal number 8637 if ($bytes == 4) 8638 { 8639 $elements = unpack('i', $output); 8640 $output = abs($elements[1]); 8641 } 8642 else 8643 { 8644 $elements = unpack('N2', $output); 8645 $output = abs($elements[1] << 32 | $elements[2]); 8646 } 8647 8648 } while($output > PHP_INT_MAX); 8649 8650 return $output; 8651 } 8652 8653 /** 8654 * Generates a cryptographically secure random number. 8655 * 8656 * @param int $min Optional lowest value to be returned (default: 0) 8657 * @param int $max Optional highest value to be returned (default: PHP_INT_MAX) 8658 */ 8659 function my_rand($min=0, $max=PHP_INT_MAX) 8660 { 8661 // backward compatibility 8662 if($min === null || $max === null || $max < $min) 8663 { 8664 $min = 0; 8665 $max = PHP_INT_MAX; 8666 } 8667 8668 if(version_compare(PHP_VERSION, '7.0', '>=')) 8669 { 8670 try 8671 { 8672 $result = random_int($min, $max); 8673 } catch (Exception $e) { 8674 } 8675 8676 if(isset($result)) 8677 { 8678 return $result; 8679 } 8680 } 8681 8682 $seed = secure_seed_rng(); 8683 8684 $distance = $max - $min; 8685 return $min + floor($distance * ($seed / PHP_INT_MAX) ); 8686 } 8687 8688 /** 8689 * More robust version of PHP's trim() function. It includes a list of UTF-8 blank characters 8690 * from http://kb.mozillazine.org/Network.IDN.blacklist_chars 8691 * 8692 * @param string $string The string to trim from 8693 * @param string $charlist Optional. The stripped characters can also be specified using the charlist parameter 8694 * @return string The trimmed string 8695 */ 8696 function trim_blank_chrs($string, $charlist="") 8697 { 8698 $hex_chrs = array( 8699 0x09 => 1, // \x{0009} 8700 0x0A => 1, // \x{000A} 8701 0x0B => 1, // \x{000B} 8702 0x0D => 1, // \x{000D} 8703 0x20 => 1, // \x{0020} 8704 0xC2 => array(0x81 => 1, 0x8D => 1, 0x90 => 1, 0x9D => 1, 0xA0 => 1, 0xAD => 1), // \x{0081}, \x{008D}, \x{0090}, \x{009D}, \x{00A0}, \x{00AD} 8705 0xCC => array(0xB7 => 1, 0xB8 => 1), // \x{0337}, \x{0338} 8706 0xE1 => array(0x85 => array(0x9F => 1, 0xA0 => 1), 0x9A => array(0x80 => 1), 0xA0 => array(0x8E => 1)), // \x{115F}, \x{1160}, \x{1680}, \x{180E} 8707 0xE2 => array(0x80 => array(0x80 => 1, 0x81 => 1, 0x82 => 1, 0x83 => 1, 0x84 => 1, 0x85 => 1, 0x86 => 1, 0x87 => 1, 0x88 => 1, 0x89 => 1, 0x8A => 1, 0x8B => 1, 0x8C => 1, 0x8D => 1, 0x8E => 1, 0x8F => 1, // \x{2000} - \x{200F} 8708 0xA8 => 1, 0xA9 => 1, 0xAA => 1, 0xAB => 1, 0xAC => 1, 0xAD => 1, 0xAE => 1, 0xAF => 1), // \x{2028} - \x{202F} 8709 0x81 => array(0x9F => 1)), // \x{205F} 8710 0xE3 => array(0x80 => array(0x80 => 1), // \x{3000} 8711 0x85 => array(0xA4 => 1)), // \x{3164} 8712 0xEF => array(0xBB => array(0xBF => 1), // \x{FEFF} 8713 0xBE => array(0xA0 => 1), // \x{FFA0} 8714 0xBF => array(0xB9 => 1, 0xBA => 1, 0xBB => 1)), // \x{FFF9} - \x{FFFB} 8715 ); 8716 8717 $hex_chrs_rev = array( 8718 0x09 => 1, // \x{0009} 8719 0x0A => 1, // \x{000A} 8720 0x0B => 1, // \x{000B} 8721 0x0D => 1, // \x{000D} 8722 0x20 => 1, // \x{0020} 8723 0x81 => array(0xC2 => 1, 0x80 => array(0xE2 => 1)), // \x{0081}, \x{2001} 8724 0x8D => array(0xC2 => 1, 0x80 => array(0xE2 => 1)), // \x{008D}, \x{200D} 8725 0x90 => array(0xC2 => 1), // \x{0090} 8726 0x9D => array(0xC2 => 1), // \x{009D} 8727 0xA0 => array(0xC2 => 1, 0x85 => array(0xE1 => 1), 0x81 => array(0xE2 => 1), 0xBE => array(0xEF => 1)), // \x{00A0}, \x{1160}, \x{2060}, \x{FFA0} 8728 0xAD => array(0xC2 => 1, 0x80 => array(0xE2 => 1)), // \x{00AD}, \x{202D} 8729 0xB8 => array(0xCC => 1), // \x{0338} 8730 0xB7 => array(0xCC => 1), // \x{0337} 8731 0x9F => array(0x85 => array(0xE1 => 1), 0x81 => array(0xE2 => 1)), // \x{115F}, \x{205F} 8732 0x80 => array(0x9A => array(0xE1 => 1), 0x80 => array(0xE2 => 1, 0xE3 => 1)), // \x{1680}, \x{2000}, \x{3000} 8733 0x8E => array(0xA0 => array(0xE1 => 1), 0x80 => array(0xE2 => 1)), // \x{180E}, \x{200E} 8734 0x82 => array(0x80 => array(0xE2 => 1)), // \x{2002} 8735 0x83 => array(0x80 => array(0xE2 => 1)), // \x{2003} 8736 0x84 => array(0x80 => array(0xE2 => 1)), // \x{2004} 8737 0x85 => array(0x80 => array(0xE2 => 1)), // \x{2005} 8738 0x86 => array(0x80 => array(0xE2 => 1)), // \x{2006} 8739 0x87 => array(0x80 => array(0xE2 => 1)), // \x{2007} 8740 0x88 => array(0x80 => array(0xE2 => 1)), // \x{2008} 8741 0x89 => array(0x80 => array(0xE2 => 1)), // \x{2009} 8742 0x8A => array(0x80 => array(0xE2 => 1)), // \x{200A} 8743 0x8B => array(0x80 => array(0xE2 => 1)), // \x{200B} 8744 0x8C => array(0x80 => array(0xE2 => 1)), // \x{200C} 8745 0x8F => array(0x80 => array(0xE2 => 1)), // \x{200F} 8746 0xA8 => array(0x80 => array(0xE2 => 1)), // \x{2028} 8747 0xA9 => array(0x80 => array(0xE2 => 1)), // \x{2029} 8748 0xAA => array(0x80 => array(0xE2 => 1)), // \x{202A} 8749 0xAB => array(0x80 => array(0xE2 => 1)), // \x{202B} 8750 0xAC => array(0x80 => array(0xE2 => 1)), // \x{202C} 8751 0xAE => array(0x80 => array(0xE2 => 1)), // \x{202E} 8752 0xAF => array(0x80 => array(0xE2 => 1)), // \x{202F} 8753 0xA4 => array(0x85 => array(0xE3 => 1)), // \x{3164} 8754 0xBF => array(0xBB => array(0xEF => 1)), // \x{FEFF} 8755 0xB9 => array(0xBF => array(0xEF => 1)), // \x{FFF9} 8756 0xBA => array(0xBF => array(0xEF => 1)), // \x{FFFA} 8757 0xBB => array(0xBF => array(0xEF => 1)), // \x{FFFB} 8758 ); 8759 8760 // Start from the beginning and work our way in 8761 $i = 0; 8762 do 8763 { 8764 // Check to see if we have matched a first character in our utf-8 array 8765 $offset = match_sequence($string, $hex_chrs); 8766 if(!$offset) 8767 { 8768 // If not, then we must have a "good" character and we don't need to do anymore processing 8769 break; 8770 } 8771 $string = substr($string, $offset); 8772 } 8773 while(++$i); 8774 8775 // Start from the end and work our way in 8776 $string = strrev($string); 8777 $i = 0; 8778 do 8779 { 8780 // Check to see if we have matched a first character in our utf-8 array 8781 $offset = match_sequence($string, $hex_chrs_rev); 8782 if(!$offset) 8783 { 8784 // If not, then we must have a "good" character and we don't need to do anymore processing 8785 break; 8786 } 8787 $string = substr($string, $offset); 8788 } 8789 while(++$i); 8790 $string = strrev($string); 8791 8792 if($charlist) 8793 { 8794 $string = trim($string, $charlist); 8795 } 8796 else 8797 { 8798 $string = trim($string); 8799 } 8800 8801 return $string; 8802 } 8803 8804 /** 8805 * Match a sequence 8806 * 8807 * @param string $string The string to match from 8808 * @param array $array The array to match from 8809 * @param int $i Number in the string 8810 * @param int $n Number of matches 8811 * @return int The number matched 8812 */ 8813 function match_sequence($string, $array, $i=0, $n=0) 8814 { 8815 if($string === "") 8816 { 8817 return 0; 8818 } 8819 8820 $ord = ord($string[$i]); 8821 if(array_key_exists($ord, $array)) 8822 { 8823 $level = $array[$ord]; 8824 ++$n; 8825 if(is_array($level)) 8826 { 8827 ++$i; 8828 return match_sequence($string, $level, $i, $n); 8829 } 8830 return $n; 8831 } 8832 8833 return 0; 8834 } 8835 8836 /** 8837 * Obtain the version of GD installed. 8838 * 8839 * @return float|null Version of GD 8840 */ 8841 function gd_version() 8842 { 8843 static $gd_version; 8844 8845 if($gd_version) 8846 { 8847 return $gd_version; 8848 } 8849 8850 if(!extension_loaded('gd')) 8851 { 8852 return null; 8853 } 8854 8855 if(function_exists("gd_info")) 8856 { 8857 $gd_info = gd_info(); 8858 preg_match('/\d/', $gd_info['GD Version'], $gd); 8859 $gd_version = $gd[0]; 8860 } 8861 else 8862 { 8863 ob_start(); 8864 phpinfo(8); 8865 $info = ob_get_contents(); 8866 ob_end_clean(); 8867 $info = stristr($info, 'gd version'); 8868 preg_match('/\d/', $info, $gd); 8869 $gd_version = $gd[0]; 8870 } 8871 8872 return $gd_version; 8873 } 8874 8875 /* 8876 * Validates an UTF-8 string. 8877 * 8878 * @param string $input The string to be checked 8879 * @param boolean $allow_mb4 Allow 4 byte UTF-8 characters? 8880 * @param boolean $return Return the cleaned string? 8881 * @return string|boolean Cleaned string or boolean 8882 */ 8883 function validate_utf8_string($input, $allow_mb4=true, $return=true) 8884 { 8885 // Valid UTF-8 sequence? 8886 if(!preg_match('##u', $input)) 8887 { 8888 $string = ''; 8889 $len = strlen($input); 8890 for($i = 0; $i < $len; $i++) 8891 { 8892 $c = ord($input[$i]); 8893 if($c > 128) 8894 { 8895 if($c > 247 || $c <= 191) 8896 { 8897 if($return) 8898 { 8899 $string .= '?'; 8900 continue; 8901 } 8902 else 8903 { 8904 return false; 8905 } 8906 } 8907 elseif($c > 239) 8908 { 8909 $bytes = 4; 8910 } 8911 elseif($c > 223) 8912 { 8913 $bytes = 3; 8914 } 8915 elseif($c > 191) 8916 { 8917 $bytes = 2; 8918 } 8919 if(($i + $bytes) > $len) 8920 { 8921 if($return) 8922 { 8923 $string .= '?'; 8924 break; 8925 } 8926 else 8927 { 8928 return false; 8929 } 8930 } 8931 $valid = true; 8932 $multibytes = $input[$i]; 8933 while($bytes > 1) 8934 { 8935 $i++; 8936 $b = ord($input[$i]); 8937 if($b < 128 || $b > 191) 8938 { 8939 if($return) 8940 { 8941 $valid = false; 8942 $string .= '?'; 8943 break; 8944 } 8945 else 8946 { 8947 return false; 8948 } 8949 } 8950 else 8951 { 8952 $multibytes .= $input[$i]; 8953 } 8954 $bytes--; 8955 } 8956 if($valid) 8957 { 8958 $string .= $multibytes; 8959 } 8960 } 8961 else 8962 { 8963 $string .= $input[$i]; 8964 } 8965 } 8966 $input = $string; 8967 } 8968 if($return) 8969 { 8970 if($allow_mb4) 8971 { 8972 return $input; 8973 } 8974 else 8975 { 8976 return preg_replace("#[^\\x00-\\x7F][\\x80-\\xBF]{3,}#", '?', $input); 8977 } 8978 } 8979 else 8980 { 8981 if($allow_mb4) 8982 { 8983 return true; 8984 } 8985 else 8986 { 8987 return !preg_match("#[^\\x00-\\x7F][\\x80-\\xBF]{3,}#", $input); 8988 } 8989 } 8990 } 8991 8992 /** 8993 * Send a Private Message to a user. 8994 * 8995 * @param array $pm Array containing: 'subject', 'message', 'touid' and 'receivepms' (the latter should reflect the value found in the users table: receivepms and receivefrombuddy) 8996 * @param int $fromid Sender UID (0 if you want to use $mybb->user['uid'] or -1 to use MyBB Engine) 8997 * @param bool $admin_override Whether or not do override user defined options for receiving PMs 8998 * @return bool True if PM sent 8999 */ 9000 function send_pm($pm, $fromid = 0, $admin_override=false) 9001 { 9002 global $lang, $mybb, $db, $session; 9003 9004 if($mybb->settings['enablepms'] == 0) 9005 { 9006 return false; 9007 } 9008 9009 if(!is_array($pm)) 9010 { 9011 return false; 9012 } 9013 9014 if(isset($pm['language'])) 9015 { 9016 if($pm['language'] != $mybb->user['language'] && $lang->language_exists($pm['language'])) 9017 { 9018 // Load user language 9019 $lang->set_language($pm['language']); 9020 $lang->load($pm['language_file']); 9021 9022 $revert = true; 9023 } 9024 9025 foreach(array('subject', 'message') as $key) 9026 { 9027 if(is_array($pm[$key])) 9028 { 9029 $lang_string = $lang->{$pm[$key][0]}; 9030 $num_args = count($pm[$key]); 9031 9032 for($i = 1; $i < $num_args; $i++) 9033 { 9034 $lang_string = str_replace('{'.$i.'}', $pm[$key][$i], $lang_string); 9035 } 9036 } 9037 else 9038 { 9039 $lang_string = $lang->{$pm[$key]}; 9040 } 9041 9042 $pm[$key] = $lang_string; 9043 } 9044 9045 if(isset($revert)) 9046 { 9047 // Revert language 9048 $lang->set_language($mybb->user['language']); 9049 $lang->load($pm['language_file']); 9050 } 9051 } 9052 9053 if(empty($pm['subject']) || empty($pm['message']) || empty($pm['touid']) || (empty($pm['receivepms']) && !$admin_override)) 9054 { 9055 return false; 9056 } 9057 9058 require_once MYBB_ROOT."inc/datahandlers/pm.php"; 9059 9060 $pmhandler = new PMDataHandler(); 9061 9062 $subject = $pm['subject']; 9063 $message = $pm['message']; 9064 $toid = $pm['touid']; 9065 9066 // Our recipients 9067 if(is_array($toid)) 9068 { 9069 $recipients_to = $toid; 9070 } 9071 else 9072 { 9073 $recipients_to = array($toid); 9074 } 9075 9076 $recipients_bcc = array(); 9077 9078 // Workaround for eliminating PHP warnings in PHP 8. Ref: https://github.com/mybb/mybb/issues/4630#issuecomment-1369144163 9079 if(isset($pm['sender']['uid']) && $pm['sender']['uid'] === -1 && $fromid === -1) 9080 { 9081 $sender = array( 9082 "uid" => 0, 9083 "username" => '' 9084 ); 9085 } 9086 9087 // Determine user ID 9088 if((int)$fromid == 0) 9089 { 9090 $fromid = (int)$mybb->user['uid']; 9091 } 9092 elseif((int)$fromid < 0) 9093 { 9094 $fromid = 0; 9095 } 9096 9097 // Build our final PM array 9098 $pm = array( 9099 "subject" => $subject, 9100 "message" => $message, 9101 "icon" => -1, 9102 "fromid" => $fromid, 9103 "toid" => $recipients_to, 9104 "bccid" => $recipients_bcc, 9105 "do" => '', 9106 "pmid" => '' 9107 ); 9108 9109 // (continued) Workaround for eliminating PHP warnings in PHP 8. Ref: https://github.com/mybb/mybb/issues/4630#issuecomment-1369144163 9110 if(isset($sender)) 9111 { 9112 $pm['sender'] = $sender; 9113 } 9114 9115 if(isset($session)) 9116 { 9117 $pm['ipaddress'] = $session->packedip; 9118 } 9119 9120 $pm['options'] = array( 9121 "disablesmilies" => 0, 9122 "savecopy" => 0, 9123 "readreceipt" => 0 9124 ); 9125 9126 $pm['saveasdraft'] = 0; 9127 9128 // Admin override 9129 $pmhandler->admin_override = (int)$admin_override; 9130 9131 $pmhandler->set_data($pm); 9132 9133 if($pmhandler->validate_pm()) 9134 { 9135 $pmhandler->insert_pm(); 9136 return true; 9137 } 9138 9139 return false; 9140 } 9141 9142 /** 9143 * Log a user spam block from StopForumSpam (or other spam service providers...) 9144 * 9145 * @param string $username The username that the user was using. 9146 * @param string $email The email address the user was using. 9147 * @param string $ip_address The IP addres of the user. 9148 * @param array $data An array of extra data to go with the block (eg: confidence rating). 9149 * @return bool Whether the action was logged successfully. 9150 */ 9151 function log_spam_block($username = '', $email = '', $ip_address = '', $data = array()) 9152 { 9153 global $db, $session; 9154 9155 if(!is_array($data)) 9156 { 9157 $data = array($data); 9158 } 9159 9160 if(!$ip_address) 9161 { 9162 $ip_address = get_ip(); 9163 } 9164 9165 $ip_address = my_inet_pton($ip_address); 9166 9167 $insert_array = array( 9168 'username' => $db->escape_string($username), 9169 'email' => $db->escape_string($email), 9170 'ipaddress' => $db->escape_binary($ip_address), 9171 'dateline' => (int)TIME_NOW, 9172 'data' => $db->escape_string(@my_serialize($data)), 9173 ); 9174 9175 return (bool)$db->insert_query('spamlog', $insert_array); 9176 } 9177 9178 /** 9179 * Copy a file to the CDN. 9180 * 9181 * @param string $file_path The path to the file to upload to the CDN. 9182 * 9183 * @param string $uploaded_path The path the file was uploaded to, reference parameter for when this may be needed. 9184 * 9185 * @return bool Whether the file was copied successfully. 9186 */ 9187 function copy_file_to_cdn($file_path = '', &$uploaded_path = null) 9188 { 9189 global $mybb, $plugins; 9190 9191 $success = false; 9192 9193 $file_path = (string)$file_path; 9194 9195 $real_file_path = realpath($file_path); 9196 9197 $file_dir_path = dirname($real_file_path); 9198 $file_dir_path = str_replace(MYBB_ROOT, '', $file_dir_path); 9199 $file_dir_path = ltrim($file_dir_path, './\\'); 9200 9201 $file_name = basename($real_file_path); 9202 9203 if(file_exists($file_path)) 9204 { 9205 9206 if(is_object($plugins)) 9207 { 9208 $hook_args = array( 9209 'file_path' => &$file_path, 9210 'real_file_path' => &$real_file_path, 9211 'file_name' => &$file_name, 9212 'file_dir_path' => &$file_dir_path 9213 ); 9214 $plugins->run_hooks('copy_file_to_cdn_start', $hook_args); 9215 } 9216 9217 if(!empty($mybb->settings['usecdn']) && !empty($mybb->settings['cdnpath'])) 9218 { 9219 $cdn_path = rtrim($mybb->settings['cdnpath'], '/\\'); 9220 9221 if(substr($file_dir_path, 0, my_strlen(MYBB_ROOT)) == MYBB_ROOT) 9222 { 9223 $file_dir_path = str_replace(MYBB_ROOT, '', $file_dir_path); 9224 } 9225 9226 $cdn_upload_path = $cdn_path . DIRECTORY_SEPARATOR . $file_dir_path; 9227 9228 if(!($dir_exists = is_dir($cdn_upload_path))) 9229 { 9230 $dir_exists = @mkdir($cdn_upload_path, 0777, true); 9231 } 9232 9233 if($dir_exists) 9234 { 9235 if(($cdn_upload_path = realpath($cdn_upload_path)) !== false) 9236 { 9237 $success = @copy($file_path, $cdn_upload_path.DIRECTORY_SEPARATOR.$file_name); 9238 9239 if($success) 9240 { 9241 $uploaded_path = $cdn_upload_path; 9242 } 9243 } 9244 } 9245 } 9246 9247 if(is_object($plugins)) 9248 { 9249 $hook_args = array( 9250 'file_path' => &$file_path, 9251 'real_file_path' => &$real_file_path, 9252 'file_name' => &$file_name, 9253 'uploaded_path' => &$uploaded_path, 9254 'success' => &$success, 9255 ); 9256 9257 $plugins->run_hooks('copy_file_to_cdn_end', $hook_args); 9258 } 9259 } 9260 9261 return $success; 9262 } 9263 9264 /** 9265 * Validate an url 9266 * 9267 * @param string $url The url to validate. 9268 * @param bool $relative_path Whether or not the url could be a relative path. 9269 * @param bool $allow_local Whether or not the url could be pointing to local networks. 9270 * 9271 * @return bool Whether this is a valid url. 9272 */ 9273 function my_validate_url($url, $relative_path=false, $allow_local=false) 9274 { 9275 if($allow_local) 9276 { 9277 $regex = '_^(?:(?:https?|ftp)://)(?:\S+(?::\S*)?@)?(?:(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:localhost|(?:(?:[a-z\x{00a1}-\x{ffff}0-9]-*)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.(?:[a-z\x{00a1}-\x{ffff}0-9]-*)*[a-z\x{00a1}-\x{ffff}0-9]+)*(?:\.(?:[a-z\x{00a1}-\x{ffff}]{2,}))\.?))(?::\d{2,5})?(?:[/?#]\S*)?$_iuS'; 9278 } 9279 else 9280 { 9281 $regex = '_^(?:(?:https?|ftp)://)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\x{00a1}-\x{ffff}0-9]-*)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.(?:[a-z\x{00a1}-\x{ffff}0-9]-*)*[a-z\x{00a1}-\x{ffff}0-9]+)*(?:\.(?:[a-z\x{00a1}-\x{ffff}]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?$_iuS'; 9282 } 9283 9284 if($relative_path && my_substr($url, 0, 1) == '/' || preg_match($regex, $url)) 9285 { 9286 return true; 9287 } 9288 return false; 9289 } 9290 9291 /** 9292 * Strip html tags from string, also removes <script> and <style> contents. 9293 * 9294 * @deprecated 9295 * @param string $string String to stripe 9296 * @param string $allowable_tags Allowed html tags 9297 * 9298 * @return string Striped string 9299 */ 9300 function my_strip_tags($string, $allowable_tags = '') 9301 { 9302 $pattern = array( 9303 '@(<)style[^(>)]*?(>).*?(<)/style(>)@siu', 9304 '@(<)script[^(>)]*?.*?(<)/script(>)@siu', 9305 '@<style[^>]*?>.*?</style>@siu', 9306 '@<script[^>]*?.*?</script>@siu', 9307 ); 9308 $string = preg_replace($pattern, '', $string); 9309 return strip_tags($string, $allowable_tags); 9310 } 9311 9312 /** 9313 * Escapes a RFC 4180-compliant CSV string. 9314 * Based on https://github.com/Automattic/camptix/blob/f80725094440bf09861383b8f11e96c177c45789/camptix.php#L2867 9315 * 9316 * @param string $string The string to be escaped 9317 * @param boolean $escape_active_content Whether or not to escape active content trigger characters 9318 * @return string The escaped string 9319 */ 9320 function my_escape_csv($string, $escape_active_content=true) 9321 { 9322 if($escape_active_content) 9323 { 9324 $active_content_triggers = array('=', '+', '-', '@'); 9325 $delimiters = array(',', ';', ':', '|', '^', "\n", "\t", " "); 9326 9327 $first_character = mb_substr($string, 0, 1); 9328 9329 if( 9330 in_array($first_character, $active_content_triggers, true) || 9331 in_array($first_character, $delimiters, true) 9332 ) 9333 { 9334 $string = "'".$string; 9335 } 9336 9337 foreach($delimiters as $delimiter) 9338 { 9339 foreach($active_content_triggers as $trigger) 9340 { 9341 $string = str_replace($delimiter.$trigger, $delimiter."'".$trigger, $string); 9342 } 9343 } 9344 } 9345 9346 $string = str_replace('"', '""', $string); 9347 9348 return $string; 9349 } 9350 9351 // Fallback function for 'array_column', PHP < 5.5.0 compatibility 9352 if(!function_exists('array_column')) 9353 { 9354 function array_column($input, $column_key) 9355 { 9356 $values = array(); 9357 if(!is_array($input)) 9358 { 9359 $input = array($input); 9360 } 9361 foreach($input as $val) 9362 { 9363 if(is_array($val) && isset($val[$column_key])) 9364 { 9365 $values[] = $val[$column_key]; 9366 } 9367 elseif(is_object($val) && isset($val->$column_key)) 9368 { 9369 $values[] = $val->$column_key; 9370 } 9371 } 9372 return $values; 9373 } 9374 } 9375 9376 /** 9377 * Performs a timing attack safe string comparison. 9378 * 9379 * @param string $known_string The first string to be compared. 9380 * @param string $user_string The second, user-supplied string to be compared. 9381 * @return bool Result of the comparison. 9382 */ 9383 function my_hash_equals($known_string, $user_string) 9384 { 9385 if(version_compare(PHP_VERSION, '5.6.0', '>=')) 9386 { 9387 return hash_equals($known_string, $user_string); 9388 } 9389 else 9390 { 9391 $known_string_length = my_strlen($known_string); 9392 $user_string_length = my_strlen($user_string); 9393 9394 if($user_string_length != $known_string_length) 9395 { 9396 return false; 9397 } 9398 9399 $result = 0; 9400 9401 for($i = 0; $i < $known_string_length; $i++) 9402 { 9403 $result |= ord($known_string[$i]) ^ ord($user_string[$i]); 9404 } 9405 9406 return $result === 0; 9407 } 9408 } 9409 9410 /** 9411 * Retrieves all referrals for a specified user 9412 * 9413 * @param int uid 9414 * @param int start position 9415 * @param int total entries 9416 * @param bool false (default) only return display info, true for all info 9417 * @return array 9418 */ 9419 function get_user_referrals($uid, $start=0, $limit=0, $full=false) 9420 { 9421 global $db; 9422 9423 $referrals = $query_options = array(); 9424 $uid = (int) $uid; 9425 9426 if($uid === 0) 9427 { 9428 return $referrals; 9429 } 9430 9431 if($start && $limit) 9432 { 9433 $query_options['limit_start'] = $start; 9434 } 9435 9436 if($limit) 9437 { 9438 $query_options['limit'] = $limit; 9439 } 9440 9441 $fields = 'uid, username, usergroup, displaygroup, regdate'; 9442 if($full === true) 9443 { 9444 $fields = '*'; 9445 } 9446 9447 $query = $db->simple_select('users', $fields, "referrer='{$uid}'", $query_options); 9448 9449 while($referral = $db->fetch_array($query)) 9450 { 9451 $referrals[] = $referral; 9452 } 9453 9454 return $referrals; 9455 } 9456 9457 /** 9458 * Initialize the parser and store the XML data to be parsed. 9459 * 9460 * @param string $data 9461 * @return MyBBXMLParser The constructed XML parser. 9462 */ 9463 function create_xml_parser($data) 9464 { 9465 if(version_compare(PHP_VERSION, '8.0', '>=')) 9466 { 9467 require_once MYBB_ROOT."inc/class_xmlparser.php"; 9468 9469 return new MyBBXMLParser($data); 9470 } 9471 else 9472 { 9473 require_once MYBB_ROOT."inc/class_xml.php"; 9474 9475 return new XMLParser($data); 9476 } 9477 } 9478 9479 /** 9480 * Make a filesystem path absolute. 9481 * 9482 * Returns as-is paths which are already absolute. 9483 * 9484 * @param string $path The input path. Can be either absolute or relative. 9485 * @param string $base The absolute base to which to append $path if $path is 9486 * relative. Must end in DIRECTORY_SEPARATOR or a forward 9487 * slash. 9488 * @return string An absolute filesystem path corresponding to the input path. 9489 */ 9490 function mk_path_abs($path, $base = MYBB_ROOT) 9491 { 9492 $iswin = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'; 9493 $char1 = my_substr($path, 0, 1); 9494 if($char1 != '/' && !($iswin && ($char1 == '\\' || preg_match('(^[a-zA-Z]:\\\\)', $path)))) 9495 { 9496 $path = $base.$path; 9497 } 9498 9499 return $path; 9500 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
2005 - 2021 © MyBB.de | Alle Rechte vorbehalten! | Sponsor: netcup | Cross-referenced by PHPXref |