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