[ Index ]

PHP Cross Reference of MyBB 1.8.39

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


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