[ Index ]

PHP Cross Reference of MyBB 1.8.37

title

Body

[close]

/inc/ -> functions.php (source)

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


2005 - 2021 © MyBB.de | Alle Rechte vorbehalten! | Sponsor: netcup Cross-referenced by PHPXref