[ Index ]

PHP Cross Reference of MyBB 1.8.36

title

Body

[close]

/inc/ -> functions.php (source)

   1  <?php
   2  /**
   3   * MyBB 1.8
   4   * Copyright 2014 MyBB Group, All Rights Reserved
   5   *
   6   * Website: http://www.mybb.com
   7   * License: http://www.mybb.com/about/license
   8   *
   9   */
  10  
  11  /**
  12   * Outputs a page directly to the browser, parsing anything which needs to be parsed.
  13   *
  14   * @param string $contents The contents of the page.
  15   */
  16  function output_page($contents)
  17  {
  18      global $db, $lang, $theme, $templates, $plugins, $mybb;
  19      global $debug, $templatecache, $templatelist, $maintimer, $globaltime, $parsetime;
  20  
  21      $contents = $plugins->run_hooks("pre_parse_page", $contents);
  22      $contents = parse_page($contents);
  23      $totaltime = format_time_duration($maintimer->stop());
  24      $contents = $plugins->run_hooks("pre_output_page", $contents);
  25  
  26      if($mybb->usergroup['cancp'] == 1 || $mybb->dev_mode == 1)
  27      {
  28          if($mybb->settings['extraadmininfo'] != 0)
  29          {
  30              $phptime = $maintimer->totaltime - $db->query_time;
  31              $query_time = $db->query_time;
  32  
  33              if($maintimer->totaltime > 0)
  34              {
  35                  $percentphp = number_format((($phptime/$maintimer->totaltime) * 100), 2);
  36                  $percentsql = number_format((($query_time/$maintimer->totaltime) * 100), 2);
  37              }
  38              else
  39              {
  40                  // if we've got a super fast script...  all we can do is assume something
  41                  $percentphp = 0;
  42                  $percentsql = 0;
  43              }
  44  
  45              $serverload = get_server_load();
  46  
  47              if(my_strpos(getenv("REQUEST_URI"), "?"))
  48              {
  49                  $debuglink = htmlspecialchars_uni(getenv("REQUEST_URI")) . "&amp;debug=1";
  50              }
  51              else
  52              {
  53                  $debuglink = htmlspecialchars_uni(getenv("REQUEST_URI")) . "?debug=1";
  54              }
  55  
  56              $memory_usage = get_memory_usage();
  57  
  58              if($memory_usage)
  59              {
  60                  $memory_usage = $lang->sprintf($lang->debug_memory_usage, get_friendly_size($memory_usage));
  61              }
  62              else
  63              {
  64                  $memory_usage = '';
  65              }
  66              // MySQLi is still MySQL, so present it that way to the user
  67              $database_server = $db->short_title;
  68  
  69              if($database_server == 'MySQLi')
  70              {
  71                  $database_server = 'MySQL';
  72              }
  73              $generated_in = $lang->sprintf($lang->debug_generated_in, $totaltime);
  74              $debug_weight = $lang->sprintf($lang->debug_weight, $percentphp, $percentsql, $database_server);
  75              $sql_queries = $lang->sprintf($lang->debug_sql_queries, $db->query_count);
  76              $server_load = $lang->sprintf($lang->debug_server_load, $serverload);
  77  
  78              eval("\$debugstuff = \"".$templates->get("debug_summary")."\";");
  79              $contents = str_replace("<debugstuff>", $debugstuff, $contents);
  80          }
  81  
  82          if($mybb->debug_mode == true)
  83          {
  84              debug_page();
  85          }
  86      }
  87  
  88      $contents = str_replace("<debugstuff>", "", $contents);
  89  
  90      if($mybb->settings['gzipoutput'] == 1)
  91      {
  92          $contents = gzip_encode($contents, $mybb->settings['gziplevel']);
  93      }
  94  
  95      @header("Content-type: text/html; charset={$lang->settings['charset']}");
  96  
  97      echo $contents;
  98  
  99      $plugins->run_hooks("post_output_page");
 100  }
 101  
 102  /**
 103   * Adds a function or class to the list of code to run on shutdown.
 104   *
 105   * @param string|array $name The name of the function.
 106   * @param mixed $arguments Either an array of arguments for the function or one argument
 107   * @return boolean True if function exists, otherwise false.
 108   */
 109  function add_shutdown($name, $arguments=array())
 110  {
 111      global $shutdown_functions;
 112  
 113      if(!is_array($shutdown_functions))
 114      {
 115          $shutdown_functions = array();
 116      }
 117  
 118      if(!is_array($arguments))
 119      {
 120          $arguments = array($arguments);
 121      }
 122  
 123      if(is_array($name) && method_exists($name[0], $name[1]))
 124      {
 125          $shutdown_functions[] = array('function' => $name, 'arguments' => $arguments);
 126          return true;
 127      }
 128      else if(!is_array($name) && function_exists($name))
 129      {
 130          $shutdown_functions[] = array('function' => $name, 'arguments' => $arguments);
 131          return true;
 132      }
 133  
 134      return false;
 135  }
 136  
 137  /**
 138   * Runs the shutdown items after the page has been sent to the browser.
 139   *
 140   */
 141  function run_shutdown()
 142  {
 143      global $config, $db, $cache, $plugins, $error_handler, $shutdown_functions, $shutdown_queries, $done_shutdown, $mybb;
 144  
 145      if($done_shutdown == true || !$config || (isset($error_handler) && $error_handler->has_errors))
 146      {
 147          return;
 148      }
 149  
 150      if(empty($shutdown_queries) && empty($shutdown_functions))
 151      {
 152          // Nothing to do
 153          return;
 154      }
 155  
 156      // Missing the core? Build
 157      if(!is_object($mybb))
 158      {
 159          require_once  MYBB_ROOT."inc/class_core.php";
 160          $mybb = new MyBB;
 161  
 162          // Load the settings
 163          require  MYBB_ROOT."inc/settings.php";
 164          $mybb->settings = &$settings;
 165      }
 166  
 167      // If our DB has been deconstructed already (bad PHP 5.2.0), reconstruct
 168      if(!is_object($db))
 169      {
 170          if(!isset($config) || empty($config['database']['type']))
 171          {
 172              require MYBB_ROOT."inc/config.php";
 173          }
 174  
 175          if(isset($config))
 176          {
 177              // Load DB interface
 178              require_once  MYBB_ROOT."inc/db_base.php";
 179              require_once  MYBB_ROOT . 'inc/AbstractPdoDbDriver.php';
 180  
 181              require_once MYBB_ROOT."inc/db_".$config['database']['type'].".php";
 182              switch($config['database']['type'])
 183              {
 184                  case "sqlite":
 185                      $db = new DB_SQLite;
 186                      break;
 187                  case "pgsql":
 188                      $db = new DB_PgSQL;
 189                      break;
 190                  case "pgsql_pdo":
 191                      $db = new PostgresPdoDbDriver();
 192                      break;
 193                  case "mysqli":
 194                      $db = new DB_MySQLi;
 195                      break;
 196                  case "mysql_pdo":
 197                      $db = new MysqlPdoDbDriver();
 198                      break;
 199                  default:
 200                      $db = new DB_MySQL;
 201              }
 202  
 203              $db->connect($config['database']);
 204              if(!defined("TABLE_PREFIX"))
 205              {
 206                  define("TABLE_PREFIX", $config['database']['table_prefix']);
 207              }
 208              $db->set_table_prefix(TABLE_PREFIX);
 209          }
 210      }
 211  
 212      // Cache object deconstructed? reconstruct
 213      if(!is_object($cache))
 214      {
 215          require_once  MYBB_ROOT."inc/class_datacache.php";
 216          $cache = new datacache;
 217          $cache->cache();
 218      }
 219  
 220      // And finally.. plugins
 221      if(!is_object($plugins) && !defined("NO_PLUGINS") && !($mybb->settings['no_plugins'] == 1))
 222      {
 223          require_once  MYBB_ROOT."inc/class_plugins.php";
 224          $plugins = new pluginSystem;
 225          $plugins->load();
 226      }
 227  
 228      // We have some shutdown queries needing to be run
 229      if(is_array($shutdown_queries))
 230      {
 231          // Loop through and run them all
 232          foreach($shutdown_queries as $query)
 233          {
 234              $db->write_query($query);
 235          }
 236      }
 237  
 238      // Run any shutdown functions if we have them
 239      if(is_array($shutdown_functions))
 240      {
 241          foreach($shutdown_functions as $function)
 242          {
 243              call_user_func_array($function['function'], $function['arguments']);
 244          }
 245      }
 246  
 247      $done_shutdown = true;
 248  }
 249  
 250  /**
 251   * Sends a specified amount of messages from the mail queue
 252   *
 253   * @param int $count The number of messages to send (Defaults to 10)
 254   */
 255  function send_mail_queue($count=10)
 256  {
 257      global $db, $cache, $plugins;
 258  
 259      $plugins->run_hooks("send_mail_queue_start");
 260  
 261      // Check to see if the mail queue has messages needing to be sent
 262      $mailcache = $cache->read("mailqueue");
 263      if($mailcache !== false && $mailcache['queue_size'] > 0 && ($mailcache['locked'] == 0 || $mailcache['locked'] < TIME_NOW-300))
 264      {
 265          // Lock the queue so no other messages can be sent whilst these are (for popular boards)
 266          $cache->update_mailqueue(0, TIME_NOW);
 267  
 268          // Fetch emails for this page view - and send them
 269          $query = $db->simple_select("mailqueue", "*", "", array("order_by" => "mid", "order_dir" => "asc", "limit_start" => 0, "limit" => $count));
 270  
 271          while($email = $db->fetch_array($query))
 272          {
 273              // Delete the message from the queue
 274              $db->delete_query("mailqueue", "mid='{$email['mid']}'");
 275  
 276              if($db->affected_rows() == 1)
 277              {
 278                  my_mail($email['mailto'], $email['subject'], $email['message'], $email['mailfrom'], "", $email['headers'], true);
 279              }
 280          }
 281          // Update the mailqueue cache and remove the lock
 282          $cache->update_mailqueue(TIME_NOW, 0);
 283      }
 284  
 285      $plugins->run_hooks("send_mail_queue_end");
 286  }
 287  
 288  /**
 289   * Parses the contents of a page before outputting it.
 290   *
 291   * @param string $contents The contents of the page.
 292   * @return string The parsed page.
 293   */
 294  function parse_page($contents)
 295  {
 296      global $lang, $theme, $mybb, $htmldoctype, $archive_url, $error_handler;
 297  
 298      $contents = str_replace('<navigation>', build_breadcrumb(), $contents);
 299      $contents = str_replace('<archive_url>', $archive_url, $contents);
 300  
 301      if($htmldoctype)
 302      {
 303          $contents = $htmldoctype.$contents;
 304      }
 305      else
 306      {
 307          $contents = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n".$contents;
 308      }
 309  
 310      $contents = str_replace("<html", "<html xmlns=\"http://www.w3.org/1999/xhtml\"", $contents);
 311  
 312      if($lang->settings['rtl'] == 1)
 313      {
 314          $contents = str_replace("<html", "<html dir=\"rtl\"", $contents);
 315      }
 316  
 317      if($lang->settings['htmllang'])
 318      {
 319          $contents = str_replace("<html", "<html xml:lang=\"".$lang->settings['htmllang']."\" lang=\"".$lang->settings['htmllang']."\"", $contents);
 320      }
 321  
 322      if($error_handler->warnings)
 323      {
 324          $contents = str_replace("<body>", "<body>\n".$error_handler->show_warnings(), $contents);
 325      }
 326  
 327      return $contents;
 328  }
 329  
 330  /**
 331   * Turn a unix timestamp in to a "friendly" date/time format for the user.
 332   *
 333   * @param string $format A date format (either relative, normal or PHP's date() structure).
 334   * @param int $stamp The unix timestamp the date should be generated for.
 335   * @param int|string $offset The offset in hours that should be applied to times. (timezones) Or an empty string to determine that automatically
 336   * @param int $ty Whether or not to use today/yesterday formatting.
 337   * @param boolean $adodb Whether or not to use the adodb time class for < 1970 or > 2038 times
 338   * @return string The formatted timestamp.
 339   */
 340  function my_date($format, $stamp=0, $offset="", $ty=1, $adodb=false)
 341  {
 342      global $mybb, $lang, $plugins;
 343  
 344      // If the stamp isn't set, use TIME_NOW
 345      if(empty($stamp))
 346      {
 347          $stamp = TIME_NOW;
 348      }
 349  
 350      if(!$offset && $offset != '0')
 351      {
 352          if(isset($mybb->user['uid']) && $mybb->user['uid'] != 0 && array_key_exists("timezone", $mybb->user))
 353          {
 354              $offset = (float)$mybb->user['timezone'];
 355              $dstcorrection = $mybb->user['dst'];
 356          }
 357          else
 358          {
 359              $offset = (float)$mybb->settings['timezoneoffset'];
 360              $dstcorrection = $mybb->settings['dstcorrection'];
 361          }
 362  
 363          // If DST correction is enabled, add an additional hour to the timezone.
 364          if($dstcorrection == 1)
 365          {
 366              ++$offset;
 367              if(my_substr($offset, 0, 1) != "-")
 368              {
 369                  $offset = "+".$offset;
 370              }
 371          }
 372      }
 373  
 374      if($offset == "-")
 375      {
 376          $offset = 0;
 377      }
 378  
 379      // Using ADOdb?
 380      if($adodb == true && !function_exists('adodb_date'))
 381      {
 382          $adodb = false;
 383      }
 384  
 385      $todaysdate = $yesterdaysdate = '';
 386      if($ty && ($format == $mybb->settings['dateformat'] || $format == 'relative' || $format == 'normal'))
 387      {
 388          $_stamp = TIME_NOW;
 389          if($adodb == true)
 390          {
 391              $date = adodb_date($mybb->settings['dateformat'], $stamp + ($offset * 3600));
 392              $todaysdate = adodb_date($mybb->settings['dateformat'], $_stamp + ($offset * 3600));
 393              $yesterdaysdate = adodb_date($mybb->settings['dateformat'], ($_stamp - 86400) + ($offset * 3600));
 394          }
 395          else
 396          {
 397              $date = gmdate($mybb->settings['dateformat'], $stamp + ($offset * 3600));
 398              $todaysdate = gmdate($mybb->settings['dateformat'], $_stamp + ($offset * 3600));
 399              $yesterdaysdate = gmdate($mybb->settings['dateformat'], ($_stamp - 86400) + ($offset * 3600));
 400          }
 401      }
 402  
 403      if($format == 'relative')
 404      {
 405          // Relative formats both date and time
 406          $real_date = $real_time = '';
 407          if($adodb == true)
 408          {
 409              $real_date = adodb_date($mybb->settings['dateformat'], $stamp + ($offset * 3600));
 410              $real_time = $mybb->settings['datetimesep'];
 411              $real_time .= adodb_date($mybb->settings['timeformat'], $stamp + ($offset * 3600));
 412          }
 413          else
 414          {
 415              $real_date = gmdate($mybb->settings['dateformat'], $stamp + ($offset * 3600));
 416              $real_time = $mybb->settings['datetimesep'];
 417              $real_time .= gmdate($mybb->settings['timeformat'], $stamp + ($offset * 3600));
 418          }
 419  
 420          if($ty != 2 && abs(TIME_NOW - $stamp) < 3600)
 421          {
 422              $diff = TIME_NOW - $stamp;
 423              $relative = array('prefix' => '', 'minute' => 0, 'plural' => $lang->rel_minutes_plural, 'suffix' => $lang->rel_ago);
 424  
 425              if($diff < 0)
 426              {
 427                  $diff = abs($diff);
 428                  $relative['suffix'] = '';
 429                  $relative['prefix'] = $lang->rel_in;
 430              }
 431  
 432              $relative['minute'] = floor($diff / 60);
 433  
 434              if($relative['minute'] <= 1)
 435              {
 436                  $relative['minute'] = 1;
 437                  $relative['plural'] = $lang->rel_minutes_single;
 438              }
 439  
 440              if($diff <= 60)
 441              {
 442                  // Less than a minute
 443                  $relative['prefix'] = $lang->rel_less_than;
 444              }
 445  
 446              $date = $lang->sprintf($lang->rel_time, $relative['prefix'], $relative['minute'], $relative['plural'], $relative['suffix'], $real_date, $real_time);
 447          }
 448          elseif($ty != 2 && abs(TIME_NOW - $stamp) < 43200)
 449          {
 450              $diff = TIME_NOW - $stamp;
 451              $relative = array('prefix' => '', 'hour' => 0, 'plural' => $lang->rel_hours_plural, 'suffix' => $lang->rel_ago);
 452  
 453              if($diff < 0)
 454              {
 455                  $diff = abs($diff);
 456                  $relative['suffix'] = '';
 457                  $relative['prefix'] = $lang->rel_in;
 458              }
 459  
 460              $relative['hour'] = floor($diff / 3600);
 461  
 462              if($relative['hour'] <= 1)
 463              {
 464                  $relative['hour'] = 1;
 465                  $relative['plural'] = $lang->rel_hours_single;
 466              }
 467  
 468              $date = $lang->sprintf($lang->rel_time, $relative['prefix'], $relative['hour'], $relative['plural'], $relative['suffix'], $real_date, $real_time);
 469          }
 470          else
 471          {
 472              if($ty)
 473              {
 474                  if($todaysdate == $date)
 475                  {
 476                      $date = $lang->sprintf($lang->today_rel, $real_date);
 477                  }
 478                  else if($yesterdaysdate == $date)
 479                  {
 480                      $date = $lang->sprintf($lang->yesterday_rel, $real_date);
 481                  }
 482              }
 483  
 484              $date .= $mybb->settings['datetimesep'];
 485              if($adodb == true)
 486              {
 487                  $date .= adodb_date($mybb->settings['timeformat'], $stamp + ($offset * 3600));
 488              }
 489              else
 490              {
 491                  $date .= gmdate($mybb->settings['timeformat'], $stamp + ($offset * 3600));
 492              }
 493          }
 494      }
 495      elseif($format == 'normal')
 496      {
 497          // Normal format both date and time
 498          if($ty != 2)
 499          {
 500              if($todaysdate == $date)
 501              {
 502                  $date = $lang->today;
 503              }
 504              else if($yesterdaysdate == $date)
 505              {
 506                  $date = $lang->yesterday;
 507              }
 508          }
 509  
 510          $date .= $mybb->settings['datetimesep'];
 511          if($adodb == true)
 512          {
 513              $date .= adodb_date($mybb->settings['timeformat'], $stamp + ($offset * 3600));
 514          }
 515          else
 516          {
 517              $date .= gmdate($mybb->settings['timeformat'], $stamp + ($offset * 3600));
 518          }
 519      }
 520      else
 521      {
 522          if($ty && $format == $mybb->settings['dateformat'])
 523          {
 524              if($todaysdate == $date)
 525              {
 526                  $date = $lang->today;
 527              }
 528              else if($yesterdaysdate == $date)
 529              {
 530                  $date = $lang->yesterday;
 531              }
 532          }
 533          else
 534          {
 535              if($adodb == true)
 536              {
 537                  $date = adodb_date($format, $stamp + ($offset * 3600));
 538              }
 539              else
 540              {
 541                  $date = gmdate($format, $stamp + ($offset * 3600));
 542              }
 543          }
 544      }
 545  
 546      if(is_object($plugins))
 547      {
 548          $date = $plugins->run_hooks("my_date", $date);
 549      }
 550  
 551      return $date;
 552  }
 553  
 554  /**
 555   * Get a mail handler instance, a MyBB's built-in SMTP / PHP mail hander or one created by a plugin.
 556   * @param bool $use_buitlin Whether to use MyBB's built-in mail handler.
 557   *
 558   * @return object A MyBB built-in mail handler or one created by plugin(s).
 559   */
 560  function &get_my_mailhandler($use_buitlin = false)
 561  {
 562      global $mybb, $plugins;
 563      static $my_mailhandler;
 564      static $my_mailhandler_builtin;
 565  
 566      if($use_buitlin)
 567      {
 568          // If our built-in mail handler doesn't exist, create it.
 569          if(!is_object($my_mailhandler_builtin))
 570          {
 571              require_once  MYBB_ROOT . "inc/class_mailhandler.php";
 572  
 573              // Using SMTP.
 574              if(isset($mybb->settings['mail_handler']) && $mybb->settings['mail_handler'] == 'smtp')
 575              {
 576                  require_once  MYBB_ROOT . "inc/mailhandlers/smtp.php";
 577                  $my_mailhandler_builtin = new SmtpMail();
 578              }
 579              // Using PHP mail().
 580              else
 581              {
 582                  require_once  MYBB_ROOT . "inc/mailhandlers/php.php";
 583                  $my_mailhandler_builtin = new PhpMail();
 584                  if(!empty($mybb->config['mail_parameters']))
 585                  {
 586                      $my_mailhandler_builtin->additional_parameters = $mybb->config['mail_parameters'];
 587                  }
 588              }
 589          }
 590  
 591          if(isset($plugins) && is_object($plugins))
 592          {
 593              $plugins->run_hooks('my_mailhandler_builtin_after_init', $my_mailhandler_builtin);
 594          }
 595  
 596          return $my_mailhandler_builtin;
 597      }
 598  
 599      // If our mail handler doesn't exist, create it.
 600      if(!is_object($my_mailhandler))
 601      {
 602          require_once  MYBB_ROOT . "inc/class_mailhandler.php";
 603  
 604          if(isset($plugins) && is_object($plugins))
 605          {
 606              $plugins->run_hooks('my_mailhandler_init', $my_mailhandler);
 607          }
 608  
 609          // If no plugin has ever created the mail handler, resort to use the built-in one.
 610          if(!is_object($my_mailhandler) || !($my_mailhandler instanceof MailHandler))
 611          {
 612              $my_mailhandler = &get_my_mailhandler(true);
 613          }
 614      }
 615  
 616      return $my_mailhandler;
 617  }
 618  
 619  /**
 620   * Sends an email using PHP's mail function, formatting it appropriately.
 621   *
 622   * @param string $to Address the email should be addressed to.
 623   * @param string $subject The subject of the email being sent.
 624   * @param string $message The message being sent.
 625   * @param string $from The from address of the email, if blank, the board name will be used.
 626   * @param string $charset The chracter set being used to send this email.
 627   * @param string $headers
 628   * @param boolean $keep_alive Do we wish to keep the connection to the mail server alive to send more than one message (SMTP only)
 629   * @param string $format The format of the email to be sent (text or html). text is default
 630   * @param string $message_text The text message of the email if being sent in html format, for email clients that don't support html
 631   * @param string $return_email The email address to return to. Defaults to admin return email address.
 632   * @return bool True if the mail is sent, false otherwise.
 633   */
 634  function my_mail($to, $subject, $message, $from="", $charset="", $headers="", $keep_alive=false, $format="text", $message_text="", $return_email="")
 635  {
 636      global $mybb, $plugins;
 637  
 638      // Get our mail handler.
 639      $mail = &get_my_mailhandler();
 640  
 641      // If MyBB's built-in SMTP mail handler is used, set the keep alive bit accordingly.
 642      if($keep_alive == true && isset($mail->keep_alive) && isset($mybb->settings['mail_handler']) && $mybb->settings['mail_handler'] == 'smtp')
 643      {
 644          require_once  MYBB_ROOT . "inc/class_mailhandler.php";
 645          require_once  MYBB_ROOT . "inc/mailhandlers/smtp.php";
 646          if($mail instanceof MailHandler && $mail instanceof SmtpMail)
 647          {
 648              $mail->keep_alive = true;
 649          }
 650      }
 651  
 652      // Following variables will help sequential plugins to determine how to process plugin hooks.
 653      // Mark this variable true if the hooked plugin has sent the mail, otherwise don't modify it.
 654      $is_mail_sent = false;
 655      // Mark this variable false if the hooked plugin doesn't suggest sequential plugins to continue processing.
 656      $continue_process = true;
 657  
 658      $my_mail_parameters = array(
 659          'to' => &$to,
 660          'subject' => &$subject,
 661          'message' => &$message,
 662          'from' => &$from,
 663          'charset' => &$charset,
 664          'headers' => &$headers,
 665          'keep_alive' => &$keep_alive,
 666          'format' => &$format,
 667          'message_text' => &$message_text,
 668          'return_email' => &$return_email,
 669          'is_mail_sent' => &$is_mail_sent,
 670          'continue_process' => &$continue_process,
 671      );
 672  
 673      if(isset($plugins) && is_object($plugins))
 674      {
 675          $plugins->run_hooks('my_mail_pre_build_message', $my_mail_parameters);
 676      }
 677  
 678      // Build the mail message.
 679      $mail->build_message($to, $subject, $message, $from, $charset, $headers, $format, $message_text, $return_email);
 680  
 681      if(isset($plugins) && is_object($plugins))
 682      {
 683          $plugins->run_hooks('my_mail_pre_send', $my_mail_parameters);
 684      }
 685  
 686      // Check if the hooked plugins still suggest to send the mail.
 687      if($continue_process)
 688      {
 689          $is_mail_sent = $mail->send();
 690      }
 691  
 692      if(isset($plugins) && is_object($plugins))
 693      {
 694          $plugins->run_hooks('my_mail_post_send', $my_mail_parameters);
 695      }
 696  
 697      return $is_mail_sent;
 698  }
 699  
 700  /**
 701   * Generates a code for POST requests to prevent XSS/CSRF attacks.
 702   * Unique for each user or guest session and rotated every 6 hours.
 703   *
 704   * @param int $rotation_shift Adjustment of the rotation number to generate a past/future code
 705   * @return string The generated code
 706   */
 707  function generate_post_check($rotation_shift=0)
 708  {
 709      global $mybb, $session;
 710  
 711      $rotation_interval = 6 * 3600;
 712      $rotation = floor(TIME_NOW / $rotation_interval) + $rotation_shift;
 713  
 714      $seed = $rotation;
 715  
 716      if($mybb->user['uid'])
 717      {
 718          $seed .= $mybb->user['loginkey'].$mybb->user['salt'].$mybb->user['regdate'];
 719      }
 720      else
 721      {
 722          $seed .= $session->sid;
 723      }
 724  
 725      if(defined('IN_ADMINCP'))
 726      {
 727          $seed .= 'ADMINCP';
 728      }
 729  
 730      $seed .= $mybb->settings['internal']['encryption_key'];
 731  
 732      return md5($seed);
 733  }
 734  
 735  /**
 736   * Verifies a POST check code is valid (i.e. generated using a rotation number from the past 24 hours)
 737   *
 738   * @param string $code The incoming POST check code
 739   * @param boolean $silent Don't show an error to the user
 740   * @return bool|void Result boolean if $silent is true, otherwise shows an error to the user
 741   */
 742  function verify_post_check($code, $silent=false)
 743  {
 744      global $lang;
 745      if(
 746          generate_post_check() !== $code &&
 747          generate_post_check(-1) !== $code &&
 748          generate_post_check(-2) !== $code &&
 749          generate_post_check(-3) !== $code
 750      )
 751      {
 752          if($silent == true)
 753          {
 754              return false;
 755          }
 756          else
 757          {
 758              if(defined("IN_ADMINCP"))
 759              {
 760                  return false;
 761              }
 762              else
 763              {
 764                  error($lang->invalid_post_code);
 765              }
 766          }
 767      }
 768      else
 769      {
 770          return true;
 771      }
 772  }
 773  
 774  /**
 775   * Return a parent list for the specified forum.
 776   *
 777   * @param int $fid The forum id to get the parent list for.
 778   * @return string The comma-separated parent list.
 779   */
 780  function get_parent_list($fid)
 781  {
 782      global $forum_cache;
 783      static $forumarraycache;
 784  
 785      if(!empty($forumarraycache[$fid]))
 786      {
 787          return $forumarraycache[$fid]['parentlist'];
 788      }
 789      elseif(!empty($forum_cache[$fid]))
 790      {
 791          return $forum_cache[$fid]['parentlist'];
 792      }
 793      else
 794      {
 795          cache_forums();
 796          return $forum_cache[$fid]['parentlist'];
 797      }
 798  }
 799  
 800  /**
 801   * Build a parent list of a specific forum, suitable for querying
 802   *
 803   * @param int $fid The forum ID
 804   * @param string $column The column name to add to the query
 805   * @param string $joiner The joiner for each forum for querying (OR | AND | etc)
 806   * @param string $parentlist The parent list of the forum - if you have it
 807   * @return string The query string generated
 808   */
 809  function build_parent_list($fid, $column="fid", $joiner="OR", $parentlist="")
 810  {
 811      if(!$parentlist)
 812      {
 813          $parentlist = get_parent_list($fid);
 814      }
 815  
 816      $parentsexploded = explode(",", $parentlist);
 817      $builtlist = "(";
 818      $sep = '';
 819  
 820      foreach($parentsexploded as $key => $val)
 821      {
 822          $builtlist .= "$sep$column='$val'";
 823          $sep = " $joiner ";
 824      }
 825  
 826      $builtlist .= ")";
 827  
 828      return $builtlist;
 829  }
 830  
 831  /**
 832   * Load the forum cache in to memory
 833   *
 834   * @param boolean $force True to force a reload of the cache
 835   * @return array The forum cache
 836   */
 837  function cache_forums($force=false)
 838  {
 839      global $forum_cache, $cache;
 840  
 841      if($force == true)
 842      {
 843          $forum_cache = $cache->read("forums", 1);
 844          return $forum_cache;
 845      }
 846  
 847      if(!$forum_cache)
 848      {
 849          $forum_cache = $cache->read("forums");
 850          if(!$forum_cache)
 851          {
 852              $cache->update_forums();
 853              $forum_cache = $cache->read("forums", 1);
 854          }
 855      }
 856      return $forum_cache;
 857  }
 858  
 859  /**
 860   * Generate an array of all child and descendant forums for a specific forum.
 861   *
 862   * @param int $fid The forum ID
 863   * @return Array of descendants
 864   */
 865  function get_child_list($fid)
 866  {
 867      static $forums_by_parent;
 868  
 869      $forums = array();
 870      if(!is_array($forums_by_parent))
 871      {
 872          $forum_cache = cache_forums();
 873          foreach($forum_cache as $forum)
 874          {
 875              if($forum['active'] != 0)
 876              {
 877                  $forums_by_parent[$forum['pid']][$forum['fid']] = $forum;
 878              }
 879          }
 880      }
 881      if(isset($forums_by_parent[$fid]))
 882      {
 883          if(!is_array($forums_by_parent[$fid]))
 884          {
 885              return $forums;
 886          }
 887  
 888          foreach($forums_by_parent[$fid] as $forum)
 889          {
 890              $forums[] = (int)$forum['fid'];
 891              $children = get_child_list($forum['fid']);
 892              if(is_array($children))
 893              {
 894                  $forums = array_merge($forums, $children);
 895              }
 896          }
 897      }
 898      return $forums;
 899  }
 900  
 901  /**
 902   * Produce a friendly error message page
 903   *
 904   * @param string $error The error message to be shown
 905   * @param string $title The title of the message shown in the title of the page and the error table
 906   */
 907  function error($error="", $title="")
 908  {
 909      global $header, $footer, $theme, $headerinclude, $db, $templates, $lang, $mybb, $plugins;
 910  
 911      $error = $plugins->run_hooks("error", $error);
 912      if(!$error)
 913      {
 914          $error = $lang->unknown_error;
 915      }
 916  
 917      // AJAX error message?
 918      if($mybb->get_input('ajax', MyBB::INPUT_INT))
 919      {
 920          // Send our headers.
 921          @header("Content-type: application/json; charset={$lang->settings['charset']}");
 922          echo json_encode(array("errors" => array($error)));
 923          exit;
 924      }
 925  
 926      if(!$title)
 927      {
 928          $title = $mybb->settings['bbname'];
 929      }
 930  
 931      $timenow = my_date('relative', TIME_NOW);
 932      reset_breadcrumb();
 933      add_breadcrumb($lang->error);
 934  
 935      eval("\$errorpage = \"".$templates->get("error")."\";");
 936      output_page($errorpage);
 937  
 938      exit;
 939  }
 940  
 941  /**
 942   * Produce an error message for displaying inline on a page
 943   *
 944   * @param array $errors Array of errors to be shown
 945   * @param string $title The title of the error message
 946   * @param array $json_data JSON data to be encoded (we may want to send more data; e.g. newreply.php uses this for CAPTCHA)
 947   * @return string The inline error HTML
 948   */
 949  function inline_error($errors, $title="", $json_data=array())
 950  {
 951      global $theme, $mybb, $db, $lang, $templates;
 952  
 953      if(!$title)
 954      {
 955          $title = $lang->please_correct_errors;
 956      }
 957  
 958      if(!is_array($errors))
 959      {
 960          $errors = array($errors);
 961      }
 962  
 963      // AJAX error message?
 964      if($mybb->get_input('ajax', MyBB::INPUT_INT))
 965      {
 966          // Send our headers.
 967          @header("Content-type: application/json; charset={$lang->settings['charset']}");
 968  
 969          if(empty($json_data))
 970          {
 971              echo json_encode(array("errors" => $errors));
 972          }
 973          else
 974          {
 975              echo json_encode(array_merge(array("errors" => $errors), $json_data));
 976          }
 977          exit;
 978      }
 979  
 980      $errorlist = '';
 981  
 982      foreach($errors as $error)
 983      {
 984          eval("\$errorlist .= \"".$templates->get("error_inline_item")."\";");
 985      }
 986  
 987      eval("\$errors = \"".$templates->get("error_inline")."\";");
 988  
 989      return $errors;
 990  }
 991  
 992  /**
 993   * Presents the user with a "no permission" page
 994   */
 995  function error_no_permission()
 996  {
 997      global $mybb, $theme, $templates, $db, $lang, $plugins, $session;
 998  
 999      $time = TIME_NOW;
1000      $plugins->run_hooks("no_permission");
1001  
1002      $noperm_array = array (
1003          "nopermission" => '1',
1004          "location1" => 0,
1005          "location2" => 0
1006      );
1007  
1008      $db->update_query("sessions", $noperm_array, "sid='{$session->sid}'");
1009  
1010      if($mybb->get_input('ajax', MyBB::INPUT_INT))
1011      {
1012          // Send our headers.
1013          header("Content-type: application/json; charset={$lang->settings['charset']}");
1014          echo json_encode(array("errors" => array($lang->error_nopermission_user_ajax)));
1015          exit;
1016      }
1017  
1018      if($mybb->user['uid'])
1019      {
1020          $lang->error_nopermission_user_username = $lang->sprintf($lang->error_nopermission_user_username, htmlspecialchars_uni($mybb->user['username']));
1021          eval("\$errorpage = \"".$templates->get("error_nopermission_loggedin")."\";");
1022      }
1023      else
1024      {
1025          // Redirect to where the user came from
1026          $redirect_url = $_SERVER['PHP_SELF'];
1027          if($_SERVER['QUERY_STRING'])
1028          {
1029              $redirect_url .= '?'.$_SERVER['QUERY_STRING'];
1030          }
1031  
1032          $redirect_url = htmlspecialchars_uni($redirect_url);
1033  
1034          switch($mybb->settings['username_method'])
1035          {
1036              case 0:
1037                  $lang_username = $lang->username;
1038                  break;
1039              case 1:
1040                  $lang_username = $lang->username1;
1041                  break;
1042              case 2:
1043                  $lang_username = $lang->username2;
1044                  break;
1045              default:
1046                  $lang_username = $lang->username;
1047                  break;
1048          }
1049          eval("\$errorpage = \"".$templates->get("error_nopermission")."\";");
1050      }
1051  
1052      error($errorpage);
1053  }
1054  
1055  /**
1056   * Redirect the user to a given URL with a given message
1057   *
1058   * @param string $url The URL to redirect the user to
1059   * @param string $message The redirection message to be shown
1060   * @param string $title The title of the redirection page
1061   * @param boolean $force_redirect Force the redirect page regardless of settings
1062   */
1063  function redirect($url, $message="", $title="", $force_redirect=false)
1064  {
1065      global $header, $footer, $mybb, $theme, $headerinclude, $templates, $lang, $plugins;
1066  
1067      $redirect_args = array('url' => &$url, 'message' => &$message, 'title' => &$title);
1068  
1069      $plugins->run_hooks("redirect", $redirect_args);
1070  
1071      if($mybb->get_input('ajax', MyBB::INPUT_INT))
1072      {
1073          // Send our headers.
1074          //@header("Content-type: text/html; charset={$lang->settings['charset']}");
1075          $data = "<script type=\"text/javascript\">\n";
1076          if($message != "")
1077          {
1078              $data .=  'alert("'.addslashes($message).'");';
1079          }
1080          $url = str_replace("#", "&#", $url);
1081          $url = htmlspecialchars_decode($url);
1082          $url = str_replace(array("\n","\r",";"), "", $url);
1083          $data .=  'window.location = "'.addslashes($url).'";'."\n";
1084          $data .= "</script>\n";
1085          //exit;
1086  
1087          @header("Content-type: application/json; charset={$lang->settings['charset']}");
1088          echo json_encode(array("data" => $data));
1089          exit;
1090      }
1091  
1092      if(!$message)
1093      {
1094          $message = $lang->redirect;
1095      }
1096  
1097      $time = TIME_NOW;
1098      $timenow = my_date('relative', $time);
1099  
1100      if(!$title)
1101      {
1102          $title = $mybb->settings['bbname'];
1103      }
1104  
1105      // Show redirects only if both ACP and UCP settings are enabled, or ACP is enabled, and user is a guest, or they are forced.
1106      if($force_redirect == true || ($mybb->settings['redirects'] == 1 && (!$mybb->user['uid'] || $mybb->user['showredirect'] == 1)))
1107      {
1108          $url = str_replace("&amp;", "&", $url);
1109          $url = htmlspecialchars_uni($url);
1110  
1111          eval("\$redirectpage = \"".$templates->get("redirect")."\";");
1112          output_page($redirectpage);
1113      }
1114      else
1115      {
1116          $url = htmlspecialchars_decode($url);
1117          $url = str_replace(array("\n","\r",";"), "", $url);
1118  
1119          run_shutdown();
1120  
1121          if(!my_validate_url($url, true, true))
1122          {
1123              header("Location: {$mybb->settings['bburl']}/{$url}");
1124          }
1125          else
1126          {
1127              header("Location: {$url}");
1128          }
1129      }
1130  
1131      exit;
1132  }
1133  
1134  /**
1135   * Generate a listing of page - pagination
1136   *
1137   * @param int $count The number of items
1138   * @param int $perpage The number of items to be shown per page
1139   * @param int $page The current page number
1140   * @param string $url The URL to have page numbers tacked on to (If {page} is specified, the value will be replaced with the page #)
1141   * @param boolean $breadcrumb Whether or not the multipage is being shown in the navigation breadcrumb
1142   * @return string The generated pagination
1143   */
1144  function multipage($count, $perpage, $page, $url, $breadcrumb=false)
1145  {
1146      global $theme, $templates, $lang, $mybb, $plugins;
1147  
1148      if($count <= $perpage)
1149      {
1150          return '';
1151      }
1152  
1153      $args = array(
1154          'count' => &$count,
1155          'perpage' => &$perpage,
1156          'page' => &$page,
1157          'url' => &$url,
1158          'breadcrumb' => &$breadcrumb,
1159      );
1160      $plugins->run_hooks('multipage', $args);
1161  
1162      $page = (int)$page;
1163  
1164      $url = str_replace("&amp;", "&", $url);
1165      $url = htmlspecialchars_uni($url);
1166  
1167      $pages = ceil($count / $perpage);
1168  
1169      $prevpage = '';
1170      if($page > 1)
1171      {
1172          $prev = $page-1;
1173          $page_url = fetch_page_url($url, $prev);
1174          eval("\$prevpage = \"".$templates->get("multipage_prevpage")."\";");
1175      }
1176  
1177      // Maximum number of "page bits" to show
1178      if(!$mybb->settings['maxmultipagelinks'])
1179      {
1180          $mybb->settings['maxmultipagelinks'] = 5;
1181      }
1182  
1183      $from = $page-floor($mybb->settings['maxmultipagelinks']/2);
1184      $to = $page+floor($mybb->settings['maxmultipagelinks']/2);
1185  
1186      if($from <= 0)
1187      {
1188          $from = 1;
1189          $to = $from+$mybb->settings['maxmultipagelinks']-1;
1190      }
1191  
1192      if($to > $pages)
1193      {
1194          $to = $pages;
1195          $from = $pages-$mybb->settings['maxmultipagelinks']+1;
1196          if($from <= 0)
1197          {
1198              $from = 1;
1199          }
1200      }
1201  
1202      if($to == 0)
1203      {
1204          $to = $pages;
1205      }
1206  
1207      $start = '';
1208      if($from > 1)
1209      {
1210          if($from-1 == 1)
1211          {
1212              $lang->multipage_link_start = '';
1213          }
1214  
1215          $page_url = fetch_page_url($url, 1);
1216          eval("\$start = \"".$templates->get("multipage_start")."\";");
1217      }
1218  
1219      $mppage = '';
1220      for($i = $from; $i <= $to; ++$i)
1221      {
1222          $page_url = fetch_page_url($url, $i);
1223          if($page == $i)
1224          {
1225              if($breadcrumb == true)
1226              {
1227                  eval("\$mppage .= \"".$templates->get("multipage_page_link_current")."\";");
1228              }
1229              else
1230              {
1231                  eval("\$mppage .= \"".$templates->get("multipage_page_current")."\";");
1232              }
1233          }
1234          else
1235          {
1236              eval("\$mppage .= \"".$templates->get("multipage_page")."\";");
1237          }
1238      }
1239  
1240      $end = '';
1241      if($to < $pages)
1242      {
1243          if($to+1 == $pages)
1244          {
1245              $lang->multipage_link_end = '';
1246          }
1247  
1248          $page_url = fetch_page_url($url, $pages);
1249          eval("\$end = \"".$templates->get("multipage_end")."\";");
1250      }
1251  
1252      $nextpage = '';
1253      if($page < $pages)
1254      {
1255          $next = $page+1;
1256          $page_url = fetch_page_url($url, $next);
1257          eval("\$nextpage = \"".$templates->get("multipage_nextpage")."\";");
1258      }
1259  
1260      $jumptopage = '';
1261      if($pages > ($mybb->settings['maxmultipagelinks']+1) && $mybb->settings['jumptopagemultipage'] == 1)
1262      {
1263          // When the second parameter is set to 1, fetch_page_url thinks it's the first page and removes it from the URL as it's unnecessary
1264          $jump_url = fetch_page_url($url, 1);
1265          eval("\$jumptopage = \"".$templates->get("multipage_jump_page")."\";");
1266      }
1267  
1268      $multipage_pages = $lang->sprintf($lang->multipage_pages, $pages);
1269  
1270      if($breadcrumb == true)
1271      {
1272          eval("\$multipage = \"".$templates->get("multipage_breadcrumb")."\";");
1273      }
1274      else
1275      {
1276          eval("\$multipage = \"".$templates->get("multipage")."\";");
1277      }
1278  
1279      return $multipage;
1280  }
1281  
1282  /**
1283   * Generate a page URL for use by the multipage function
1284   *
1285   * @param string $url The URL being passed
1286   * @param int $page The page number
1287   * @return string
1288   */
1289  function fetch_page_url($url, $page)
1290  {
1291      if($page <= 1)
1292      {
1293          $find = array(
1294              "-page-{page}",
1295              "&amp;page={page}",
1296              "{page}"
1297          );
1298  
1299          // Remove "Page 1" to the defacto URL
1300          $url = str_replace($find, array("", "", $page), $url);
1301          return $url;
1302      }
1303      else if(strpos($url, "{page}") === false)
1304      {
1305          // If no page identifier is specified we tack it on to the end of the URL
1306          if(strpos($url, "?") === false)
1307          {
1308              $url .= "?";
1309          }
1310          else
1311          {
1312              $url .= "&amp;";
1313          }
1314  
1315          $url .= "page=$page";
1316      }
1317      else
1318      {
1319          $url = str_replace("{page}", $page, $url);
1320      }
1321  
1322      return $url;
1323  }
1324  
1325  /**
1326   * Fetch the permissions for a specific user
1327   *
1328   * @param int $uid The user ID, if no user ID is provided then current user's ID will be considered.
1329   * @return array Array of user permissions for the specified user
1330   */
1331  function user_permissions($uid=null)
1332  {
1333      global $mybb, $cache, $groupscache, $user_cache;
1334  
1335      // If no user id is specified, assume it is the current user
1336      if($uid === null)
1337      {
1338          $uid = $mybb->user['uid'];
1339      }
1340  
1341      // Its a guest. Return the group permissions directly from cache
1342      if($uid == 0)
1343      {
1344          return $groupscache[1];
1345      }
1346  
1347      // User id does not match current user, fetch permissions
1348      if($uid != $mybb->user['uid'])
1349      {
1350          // We've already cached permissions for this user, return them.
1351          if(!empty($user_cache[$uid]['permissions']))
1352          {
1353              return $user_cache[$uid]['permissions'];
1354          }
1355  
1356          // This user was not already cached, fetch their user information.
1357          if(empty($user_cache[$uid]))
1358          {
1359              $user_cache[$uid] = get_user($uid);
1360          }
1361  
1362          // Collect group permissions.
1363          $gid = $user_cache[$uid]['usergroup'].",".$user_cache[$uid]['additionalgroups'];
1364          $groupperms = usergroup_permissions($gid);
1365  
1366          // Store group permissions in user cache.
1367          $user_cache[$uid]['permissions'] = $groupperms;
1368          return $groupperms;
1369      }
1370      // This user is the current user, return their permissions
1371      else
1372      {
1373          return $mybb->usergroup;
1374      }
1375  }
1376  
1377  /**
1378   * Fetch the usergroup permissions for a specific group or series of groups combined
1379   *
1380   * @param int|string $gid A list of groups (Can be a single integer, or a list of groups separated by a comma)
1381   * @return array Array of permissions generated for the groups, containing also a list of comma-separated checked groups under 'all_usergroups' index
1382   */
1383  function usergroup_permissions($gid=0)
1384  {
1385      global $cache, $groupscache, $grouppermignore, $groupzerogreater, $groupzerolesser, $groupxgreater, $grouppermbyswitch;
1386  
1387      if(!is_array($groupscache))
1388      {
1389          $groupscache = $cache->read("usergroups");
1390      }
1391  
1392      $groups = explode(",", $gid);
1393  
1394      if(count($groups) == 1)
1395      {
1396          $groupscache[$gid]['all_usergroups'] = $gid;
1397          return $groupscache[$gid];
1398      }
1399  
1400      $usergroup = array();
1401      $usergroup['all_usergroups'] = $gid;
1402  
1403      // Get those switch permissions from the first valid group.
1404      $permswitches_usergroup = array();
1405      $grouppermswitches = array();
1406      foreach(array_values($grouppermbyswitch) as $permvalue)
1407      {
1408          if(is_array($permvalue))
1409          {
1410              foreach($permvalue as $perm)
1411              {
1412                  $grouppermswitches[] = $perm;
1413              }
1414          }
1415          else
1416          {
1417              $grouppermswitches[] = $permvalue;
1418          }
1419      }
1420      $grouppermswitches = array_unique($grouppermswitches);
1421      foreach($groups as $gid)
1422      {
1423          if(trim($gid) == "" || empty($groupscache[$gid]))
1424          {
1425              continue;
1426          }
1427          foreach($grouppermswitches as $perm)
1428          {
1429              $permswitches_usergroup[$perm] = $groupscache[$gid][$perm];
1430          }
1431          break;    // Only retieve the first available group's permissions as how following action does.
1432      }
1433  
1434      foreach($groups as $gid)
1435      {
1436          if(trim($gid) == "" || empty($groupscache[$gid]))
1437          {
1438              continue;
1439          }
1440  
1441          foreach($groupscache[$gid] as $perm => $access)
1442          {
1443              if(!in_array($perm, $grouppermignore))
1444              {
1445                  if(isset($usergroup[$perm]))
1446                  {
1447                      $permbit = $usergroup[$perm];
1448                  }
1449                  else
1450                  {
1451                      $permbit = "";
1452                  }
1453  
1454                  // permission type: 0 not a numerical permission, otherwise a numerical permission.
1455                  // Positive value is for `greater is more` permission, negative for `lesser is more`.
1456                  $perm_is_numerical = 0;
1457                  $perm_numerical_lowerbound = 0;
1458  
1459                  // 0 represents unlimited for most numerical group permissions (i.e. private message limit) so take that into account.
1460                  if(in_array($perm, $groupzerogreater))
1461                  {
1462                      // 1 means a `0 or greater` permission. Value 0 means unlimited.
1463                      $perm_is_numerical = 1;
1464                  }
1465                  // Less is more for some numerical group permissions (i.e. post count required for using signature) so take that into account, too.
1466                  else if(in_array($perm, $groupzerolesser))
1467                  {
1468                      // -1 means a `0 or lesser` permission. Value 0 means unlimited.
1469                      $perm_is_numerical = -1;
1470                  }
1471                  // Greater is more, but with a lower bound.
1472                  else if(array_key_exists($perm, $groupxgreater))
1473                  {
1474                      // 2 means a general `greater` permission. Value 0 just means 0.
1475                      $perm_is_numerical = 2;
1476                      $perm_numerical_lowerbound = $groupxgreater[$perm];
1477                  }
1478  
1479                  if($perm_is_numerical != 0)
1480                  {
1481                      $update_current_perm = true;
1482  
1483                      // Ensure it's an integer.
1484                      $access = (int)$access;
1485                      // Check if this permission should be activatived by another switch permission in current group.
1486                      if(array_key_exists($perm, $grouppermbyswitch))
1487                      {
1488                          if(!is_array($grouppermbyswitch[$perm]))
1489                          {
1490                              $grouppermbyswitch[$perm] = array($grouppermbyswitch[$perm]);
1491                          }
1492  
1493                          $update_current_perm = $group_current_perm_enabled = $group_perm_enabled = false;
1494                          foreach($grouppermbyswitch[$perm] as $permswitch)
1495                          {
1496                              if(!isset($groupscache[$gid][$permswitch]))
1497                              {
1498                                  continue;
1499                              }
1500                              $permswitches_current = $groupscache[$gid][$permswitch];
1501  
1502                              // Determin if the permission is enabled by switches from current group.
1503                              if($permswitches_current == 1 || $permswitches_current == "yes") // Keep yes/no for compatibility?
1504                              {
1505                                  $group_current_perm_enabled = true;
1506                              }
1507                              // Determin if the permission is enabled by switches from previously handled groups.
1508                              if($permswitches_usergroup[$permswitch] == 1 || $permswitches_usergroup[$permswitch] == "yes") // Keep yes/no for compatibility?
1509                              {
1510                                  $group_perm_enabled = true;
1511                              }
1512                          }
1513  
1514                          // Set this permission if not set yet.
1515                          if(!isset($usergroup[$perm]))
1516                          {
1517                              $usergroup[$perm] = $access;
1518                          }
1519  
1520                          // If current group's setting enables the permission, we may need to update the user's permission.
1521                          if($group_current_perm_enabled)
1522                          {
1523                              // Only update this permission if both its switch and current group switch are on.
1524                              if($group_perm_enabled)
1525                              {
1526                                  $update_current_perm = true;
1527                              }
1528                              // Override old useless value with value from current group.
1529                              else
1530                              {
1531                                  $usergroup[$perm] = $access;
1532                              }
1533                          }
1534                      }
1535  
1536                      // No switch controls this permission, or permission needs an update.
1537                      if($update_current_perm)
1538                      {
1539                          switch($perm_is_numerical)
1540                          {
1541                              case 1:
1542                              case -1:
1543                                  if($access == 0 || $permbit === 0)
1544                                  {
1545                                      $usergroup[$perm] = 0;
1546                                      break;
1547                                  }
1548                              default:
1549                                  if($perm_is_numerical > 0 && $access > $permbit || $perm_is_numerical < 0 && $access < $permbit)
1550                                  {
1551                                      $usergroup[$perm] = $access;
1552                                  }
1553                                  break;
1554                          }
1555                      }
1556  
1557                      // Maybe oversubtle, database uses Unsigned on them, but enables usage of permission value with a lower bound.
1558                      if($usergroup[$perm] < $perm_numerical_lowerbound)
1559                      {
1560                          $usergroup[$perm] = $perm_numerical_lowerbound;
1561                      }
1562  
1563                      // Work is done for numerical permissions.
1564                      continue;
1565                  }
1566  
1567                  if($access > $permbit || ($access == "yes" && $permbit == "no") || !$permbit) // Keep yes/no for compatibility?
1568                  {
1569                      $usergroup[$perm] = $access;
1570                  }
1571              }
1572          }
1573  
1574          foreach($permswitches_usergroup as $perm => $value)
1575          {
1576              $permswitches_usergroup[$perm] = $usergroup[$perm];
1577          }
1578      }
1579  
1580      return $usergroup;
1581  }
1582  
1583  /**
1584   * Fetch the display group properties for a specific display group
1585   *
1586   * @param int $gid The group ID to fetch the display properties for
1587   * @return array Array of display properties for the group
1588   */
1589  function usergroup_displaygroup($gid)
1590  {
1591      global $cache, $groupscache, $displaygroupfields;
1592  
1593      if(!is_array($groupscache))
1594      {
1595          $groupscache = $cache->read("usergroups");
1596      }
1597  
1598      $displaygroup = array();
1599      $group = $groupscache[$gid];
1600  
1601      foreach($displaygroupfields as $field)
1602      {
1603          $displaygroup[$field] = $group[$field];
1604      }
1605  
1606      return $displaygroup;
1607  }
1608  
1609  /**
1610   * Build the forum permissions for a specific forum, user or group
1611   *
1612   * @param int $fid The forum ID to build permissions for (0 builds for all forums)
1613   * @param int $uid The user to build the permissions for (0 will select the uid automatically)
1614   * @param int $gid The group of the user to build permissions for (0 will fetch it)
1615   * @return array Forum permissions for the specific forum or forums
1616   */
1617  function forum_permissions($fid=0, $uid=0, $gid=0)
1618  {
1619      global $db, $cache, $groupscache, $forum_cache, $fpermcache, $mybb, $cached_forum_permissions_permissions, $cached_forum_permissions;
1620  
1621      if($uid == 0)
1622      {
1623          $uid = $mybb->user['uid'];
1624      }
1625  
1626      if(!$gid || $gid == 0) // If no group, we need to fetch it
1627      {
1628          if($uid != 0 && $uid != $mybb->user['uid'])
1629          {
1630              $user = get_user($uid);
1631  
1632              $gid = $user['usergroup'].",".$user['additionalgroups'];
1633              $groupperms = usergroup_permissions($gid);
1634          }
1635          else
1636          {
1637              $gid = $mybb->user['usergroup'];
1638  
1639              if(isset($mybb->user['additionalgroups']))
1640              {
1641                  $gid .= ",".$mybb->user['additionalgroups'];
1642              }
1643  
1644              $groupperms = $mybb->usergroup;
1645          }
1646      }
1647  
1648      if(!is_array($forum_cache))
1649      {
1650          $forum_cache = cache_forums();
1651  
1652          if(!$forum_cache)
1653          {
1654              return false;
1655          }
1656      }
1657  
1658      if(!is_array($fpermcache))
1659      {
1660          $fpermcache = $cache->read("forumpermissions");
1661      }
1662  
1663      if($fid) // Fetch the permissions for a single forum
1664      {
1665          if(empty($cached_forum_permissions_permissions[$gid][$fid]))
1666          {
1667              $cached_forum_permissions_permissions[$gid][$fid] = fetch_forum_permissions($fid, $gid, $groupperms);
1668          }
1669          return $cached_forum_permissions_permissions[$gid][$fid];
1670      }
1671      else
1672      {
1673          if(empty($cached_forum_permissions[$gid]))
1674          {
1675              foreach($forum_cache as $forum)
1676              {
1677                  $cached_forum_permissions[$gid][$forum['fid']] = fetch_forum_permissions($forum['fid'], $gid, $groupperms);
1678              }
1679          }
1680          return $cached_forum_permissions[$gid];
1681      }
1682  }
1683  
1684  /**
1685   * Fetches the permissions for a specific forum/group applying the inheritance scheme.
1686   * Called by forum_permissions()
1687   *
1688   * @param int $fid The forum ID
1689   * @param string $gid A comma separated list of usergroups
1690   * @param array $groupperms Group permissions
1691   * @return array Permissions for this forum
1692  */
1693  function fetch_forum_permissions($fid, $gid, $groupperms)
1694  {
1695      global $groupscache, $forum_cache, $fpermcache, $mybb, $fpermfields;
1696  
1697      $groups = explode(",", $gid);
1698  
1699      $current_permissions = array();
1700      $only_view_own_threads = 1;
1701      $only_reply_own_threads = 1;
1702  
1703      if(empty($fpermcache[$fid])) // This forum has no custom or inherited permissions so lets just return the group permissions
1704      {
1705          $current_permissions = $groupperms;
1706      }
1707      else
1708      {
1709          foreach($groups as $gid)
1710          {
1711              // If this forum has custom or inherited permissions for the currently looped group.
1712              if(!empty($fpermcache[$fid][$gid]))
1713              {
1714                  $level_permissions = $fpermcache[$fid][$gid];
1715              }
1716              // Or, use the group permission instead, if available. Some forum permissions not existing here will be added back later.
1717              else if(!empty($groupscache[$gid]))
1718              {
1719                  $level_permissions = $groupscache[$gid];
1720              }
1721              // No permission is available for the currently looped group, probably we have bad data here.
1722              else
1723              {
1724                  continue;
1725              }
1726  
1727              foreach($level_permissions as $permission => $access)
1728              {
1729                  if(empty($current_permissions[$permission]) || $access >= $current_permissions[$permission] || ($access == "yes" && $current_permissions[$permission] == "no"))
1730                  {
1731                      $current_permissions[$permission] = $access;
1732                  }
1733              }
1734  
1735              if($level_permissions["canview"] && empty($level_permissions["canonlyviewownthreads"]))
1736              {
1737                  $only_view_own_threads = 0;
1738              }
1739  
1740              if($level_permissions["canpostreplys"] && empty($level_permissions["canonlyreplyownthreads"]))
1741              {
1742                  $only_reply_own_threads = 0;
1743              }
1744          }
1745  
1746          if(count($current_permissions) == 0)
1747          {
1748              $current_permissions = $groupperms;
1749          }
1750      }
1751  
1752      // Figure out if we can view more than our own threads
1753      if($only_view_own_threads == 0 || !isset($current_permissions["canonlyviewownthreads"]))
1754      {
1755          $current_permissions["canonlyviewownthreads"] = 0;
1756      }
1757  
1758      // Figure out if we can reply more than our own threads
1759      if($only_reply_own_threads == 0 || !isset($current_permissions["canonlyreplyownthreads"]))
1760      {
1761          $current_permissions["canonlyreplyownthreads"] = 0;
1762      }
1763  
1764      return $current_permissions;
1765  }
1766  
1767  /**
1768   * Check whether password for given forum was validated for the current user
1769   *
1770   * @param array $forum The forum data
1771   * @param bool $ignore_empty Whether to treat forum password configured as an empty string as validated
1772   * @param bool $check_parents Whether to check parent forums using `parentlist`
1773   * @return bool
1774   */
1775  function forum_password_validated($forum, $ignore_empty=false, $check_parents=false)
1776  {
1777      global $mybb, $forum_cache;
1778  
1779      if($check_parents && isset($forum['parentlist']))
1780      {
1781          if(!is_array($forum_cache))
1782          {
1783              $forum_cache = cache_forums();
1784              if(!$forum_cache)
1785              {
1786                  return false;
1787              }
1788          }
1789  
1790          $parents = explode(',', $forum['parentlist']);
1791          rsort($parents);
1792  
1793          foreach($parents as $parent_id)
1794          {
1795              if($parent_id != $forum['fid'] && !forum_password_validated($forum_cache[$parent_id], true))
1796              {
1797                  return false;
1798              }
1799          }
1800      }
1801  
1802      return ($ignore_empty && $forum['password'] === '') || (
1803          isset($mybb->cookies['forumpass'][$forum['fid']]) &&
1804          my_hash_equals(
1805              md5($mybb->user['uid'].$forum['password']),
1806              $mybb->cookies['forumpass'][$forum['fid']]
1807          )
1808      );
1809  }
1810  
1811  /**
1812   * Check the password given on a certain forum for validity
1813   *
1814   * @param int $fid The forum ID
1815   * @param int $pid The Parent ID
1816   * @param bool $return
1817   * @return bool
1818   */
1819  function check_forum_password($fid, $pid=0, $return=false)
1820  {
1821      global $mybb, $header, $footer, $headerinclude, $theme, $templates, $lang, $forum_cache;
1822  
1823      $showform = true;
1824  
1825      if(!is_array($forum_cache))
1826      {
1827          $forum_cache = cache_forums();
1828          if(!$forum_cache)
1829          {
1830              return false;
1831          }
1832      }
1833  
1834      // Loop through each of parent forums to ensure we have a password for them too
1835      if(isset($forum_cache[$fid]['parentlist']))
1836      {
1837          $parents = explode(',', $forum_cache[$fid]['parentlist']);
1838          rsort($parents);
1839      }
1840      if(!empty($parents))
1841      {
1842          foreach($parents as $parent_id)
1843          {
1844              if($parent_id == $fid || $parent_id == $pid)
1845              {
1846                  continue;
1847              }
1848  
1849              if($forum_cache[$parent_id]['password'] !== "")
1850              {
1851                  check_forum_password($parent_id, $fid);
1852              }
1853          }
1854      }
1855  
1856      if($forum_cache[$fid]['password'] !== '')
1857      {
1858          if(isset($mybb->input['pwverify']) && $pid == 0)
1859          {
1860              if(my_hash_equals($forum_cache[$fid]['password'], $mybb->get_input('pwverify')))
1861              {
1862                  my_setcookie("forumpass[$fid]", md5($mybb->user['uid'].$mybb->get_input('pwverify')), null, true);
1863                  $showform = false;
1864              }
1865              else
1866              {
1867                  eval("\$pwnote = \"".$templates->get("forumdisplay_password_wrongpass")."\";");
1868                  $showform = true;
1869              }
1870          }
1871          else
1872          {
1873              if(!forum_password_validated($forum_cache[$fid]))
1874              {
1875                  $showform = true;
1876              }
1877              else
1878              {
1879                  $showform = false;
1880              }
1881          }
1882      }
1883      else
1884      {
1885          $showform = false;
1886      }
1887  
1888      if($return)
1889      {
1890          return $showform;
1891      }
1892  
1893      if($showform)
1894      {
1895          if($pid)
1896          {
1897              header("Location: ".$mybb->settings['bburl']."/".get_forum_link($fid));
1898          }
1899          else
1900          {
1901              $_SERVER['REQUEST_URI'] = htmlspecialchars_uni($_SERVER['REQUEST_URI']);
1902              eval("\$pwform = \"".$templates->get("forumdisplay_password")."\";");
1903              output_page($pwform);
1904          }
1905          exit;
1906      }
1907  }
1908  
1909  /**
1910   * Return the permissions for a moderator in a specific forum
1911   *
1912   * @param int $fid The forum ID
1913   * @param int $uid The user ID to fetch permissions for (0 assumes current logged in user)
1914   * @param string $parentslist The parent list for the forum (if blank, will be fetched)
1915   * @return array Array of moderator permissions for the specific forum
1916   */
1917  function get_moderator_permissions($fid, $uid=0, $parentslist="")
1918  {
1919      global $mybb, $cache, $db;
1920      static $modpermscache;
1921  
1922      if($uid < 1)
1923      {
1924          $uid = $mybb->user['uid'];
1925      }
1926  
1927      if($uid == 0)
1928      {
1929          return false;
1930      }
1931  
1932      if(isset($modpermscache[$fid][$uid]))
1933      {
1934          return $modpermscache[$fid][$uid];
1935      }
1936  
1937      if(!$parentslist)
1938      {
1939          $parentslist = explode(',', get_parent_list($fid));
1940      }
1941  
1942      // Get user groups
1943      $perms = array();
1944      $user = get_user($uid);
1945  
1946      $groups = array($user['usergroup']);
1947  
1948      if(!empty($user['additionalgroups']))
1949      {
1950          $extra_groups = explode(",", $user['additionalgroups']);
1951  
1952          foreach($extra_groups as $extra_group)
1953          {
1954              $groups[] = $extra_group;
1955          }
1956      }
1957  
1958      $mod_cache = $cache->read("moderators");
1959  
1960      foreach($mod_cache as $forumid => $forum)
1961      {
1962          if(empty($forum) || !is_array($forum) || !in_array($forumid, $parentslist))
1963          {
1964              // No perms or we're not after this forum
1965              continue;
1966          }
1967  
1968          // User settings override usergroup settings
1969          if(!empty($forum['users'][$uid]))
1970          {
1971              $perm = $forum['users'][$uid];
1972              foreach($perm as $action => $value)
1973              {
1974                  if(strpos($action, "can") === false)
1975                  {
1976                      continue;
1977                  }
1978  
1979                  if(!isset($perms[$action]))
1980                  {
1981                      $perms[$action] = $value;
1982                  }
1983                  // Figure out the user permissions
1984                  else if($value == 0)
1985                  {
1986                      // The user doesn't have permission to set this action
1987                      $perms[$action] = 0;
1988                  }
1989                  else
1990                  {
1991                      $perms[$action] = max($perm[$action], $perms[$action]);
1992                  }
1993              }
1994          }
1995  
1996          foreach($groups as $group)
1997          {
1998              if(empty($forum['usergroups'][$group]) || !is_array($forum['usergroups'][$group]))
1999              {
2000                  // There are no permissions set for this group
2001                  continue;
2002              }
2003  
2004              $perm = $forum['usergroups'][$group];
2005              foreach($perm as $action => $value)
2006              {
2007                  if(strpos($action, "can") === false)
2008                  {
2009                      continue;
2010                  }
2011  
2012                  if(!isset($perms[$action]))
2013                  {
2014                      $perms[$action] = $value;
2015                  }
2016                  else
2017                  {
2018                      $perms[$action] = max($perm[$action], $perms[$action]);
2019                  }
2020              }
2021          }
2022      }
2023  
2024      $modpermscache[$fid][$uid] = $perms;
2025  
2026      return $perms;
2027  }
2028  
2029  /**
2030   * Checks if a moderator has permissions to perform an action in a specific forum
2031   *
2032   * @param int $fid The forum ID (0 assumes global)
2033   * @param string $action The action tyring to be performed. (blank assumes any action at all)
2034   * @param int $uid The user ID (0 assumes current user)
2035   * @return bool Returns true if the user has permission, false if they do not
2036   */
2037  function is_moderator($fid=0, $action="", $uid=0)
2038  {
2039      global $mybb, $cache, $plugins;
2040  
2041      if($uid == 0)
2042      {
2043          $uid = $mybb->user['uid'];
2044      }
2045  
2046      if($uid == 0)
2047      {
2048          return false;
2049      }
2050  
2051      $user_perms = user_permissions($uid);
2052  
2053      $hook_args = array(
2054          'fid' => $fid,
2055          'action' => $action,
2056          'uid' => $uid,
2057      );
2058  
2059      $plugins->run_hooks("is_moderator", $hook_args);
2060      
2061      if(isset($hook_args['is_moderator']))
2062      {
2063          return (boolean) $hook_args['is_moderator'];
2064      }
2065  
2066      if(!empty($user_perms['issupermod']) && $user_perms['issupermod'] == 1)
2067      {
2068          if($fid)
2069          {
2070              $forumpermissions = forum_permissions($fid);
2071              if(!empty($forumpermissions['canview']) && !empty($forumpermissions['canviewthreads']) && empty($forumpermissions['canonlyviewownthreads']))
2072              {
2073                  return true;
2074              }
2075              return false;
2076          }
2077          return true;
2078      }
2079      else
2080      {
2081          if(!$fid)
2082          {
2083              $modcache = $cache->read('moderators');
2084              if(!empty($modcache))
2085              {
2086                  foreach($modcache as $modusers)
2087                  {
2088                      if(isset($modusers['users'][$uid]) && $modusers['users'][$uid]['mid'] && (!$action || !empty($modusers['users'][$uid][$action])))
2089                      {
2090                          return true;
2091                      }
2092  
2093                      $groups = explode(',', $user_perms['all_usergroups']);
2094  
2095                      foreach($groups as $group)
2096                      {
2097                          if(trim($group) != '' && isset($modusers['usergroups'][$group]) && (!$action || !empty($modusers['usergroups'][$group][$action])))
2098                          {
2099                              return true;
2100                          }
2101                      }
2102                  }
2103              }
2104              return false;
2105          }
2106          else
2107          {
2108              $modperms = get_moderator_permissions($fid, $uid);
2109  
2110              if(!$action && $modperms)
2111              {
2112                  return true;
2113              }
2114              else
2115              {
2116                  if(isset($modperms[$action]) && $modperms[$action] == 1)
2117                  {
2118                      return true;
2119                  }
2120                  else
2121                  {
2122                      return false;
2123                  }
2124              }
2125          }
2126      }
2127  }
2128  
2129  /**
2130   * Get an array of fids that the forum moderator has access to.
2131   * Do not use for administraotrs or global moderators as they moderate any forum and the function will return false.
2132   *
2133   * @param int $uid The user ID (0 assumes current user)
2134   * @return array|bool an array of the fids the user has moderator access to or bool if called incorrectly.
2135   */
2136  function get_moderated_fids($uid=0)
2137  {
2138      global $mybb, $cache;
2139  
2140      if($uid == 0)
2141      {
2142          $uid = $mybb->user['uid'];
2143      }
2144  
2145      if($uid == 0)
2146      {
2147          return array();
2148      }
2149  
2150      $user_perms = user_permissions($uid);
2151  
2152      if($user_perms['issupermod'] == 1)
2153      {
2154          return false;
2155      }
2156  
2157      $fids = array();
2158  
2159      $modcache = $cache->read('moderators');
2160      if(!empty($modcache))
2161      {
2162          $groups = explode(',', $user_perms['all_usergroups']);
2163  
2164          foreach($modcache as $fid => $forum)
2165          {
2166              if(isset($forum['users'][$uid]) && $forum['users'][$uid]['mid'])
2167              {
2168                  $fids[] = $fid;
2169                  continue;
2170              }
2171  
2172              foreach($groups as $group)
2173              {
2174                  if(trim($group) != '' && isset($forum['usergroups'][$group]))
2175                  {
2176                      $fids[] = $fid;
2177                  }
2178              }
2179          }
2180      }
2181  
2182      return $fids;
2183  }
2184  
2185  /**
2186   * Generate a list of the posticons.
2187   *
2188   * @return string The template of posticons.
2189   */
2190  function get_post_icons()
2191  {
2192      global $mybb, $cache, $icon, $theme, $templates, $lang;
2193  
2194      if(isset($mybb->input['icon']))
2195      {
2196          $icon = $mybb->get_input('icon');
2197      }
2198  
2199      $iconlist = '';
2200      $no_icons_checked = " checked=\"checked\"";
2201      // read post icons from cache, and sort them accordingly
2202      $posticons_cache = (array)$cache->read("posticons");
2203      $posticons = array();
2204      foreach($posticons_cache as $posticon)
2205      {
2206          $posticons[$posticon['name']] = $posticon;
2207      }
2208      krsort($posticons);
2209  
2210      foreach($posticons as $dbicon)
2211      {
2212          $dbicon['path'] = str_replace("{theme}", $theme['imgdir'], $dbicon['path']);
2213          $dbicon['path'] = htmlspecialchars_uni($mybb->get_asset_url($dbicon['path']));
2214          $dbicon['name'] = htmlspecialchars_uni($dbicon['name']);
2215  
2216          if($icon == $dbicon['iid'])
2217          {
2218              $checked = " checked=\"checked\"";
2219              $no_icons_checked = '';
2220          }
2221          else
2222          {
2223              $checked = '';
2224          }
2225  
2226          eval("\$iconlist .= \"".$templates->get("posticons_icon")."\";");
2227      }
2228  
2229      if(!empty($iconlist))
2230      {
2231          eval("\$posticons = \"".$templates->get("posticons")."\";");
2232      }
2233      else
2234      {
2235          $posticons = '';
2236      }
2237  
2238      return $posticons;
2239  }
2240  
2241  /**
2242   * MyBB setcookie() wrapper.
2243   *
2244   * @param string $name The cookie identifier.
2245   * @param string $value The cookie value.
2246   * @param int|string $expires The timestamp of the expiry date.
2247   * @param boolean $httponly True if setting a HttpOnly cookie (supported by the majority of web browsers)
2248   * @param string $samesite The samesite attribute to prevent CSRF.
2249   */
2250  function my_setcookie($name, $value="", $expires="", $httponly=false, $samesite="")
2251  {
2252      global $mybb;
2253  
2254      if(!$mybb->settings['cookiepath'])
2255      {
2256          $mybb->settings['cookiepath'] = "/";
2257      }
2258  
2259      if($expires == -1)
2260      {
2261          $expires = 0;
2262      }
2263      elseif($expires == "" || $expires == null)
2264      {
2265          $expires = TIME_NOW + (60*60*24*365); // Make the cookie expire in a years time
2266      }
2267      else
2268      {
2269          $expires = TIME_NOW + (int)$expires;
2270      }
2271  
2272      $mybb->settings['cookiepath'] = str_replace(array("\n","\r"), "", $mybb->settings['cookiepath']);
2273      $mybb->settings['cookiedomain'] = str_replace(array("\n","\r"), "", $mybb->settings['cookiedomain']);
2274      $mybb->settings['cookieprefix'] = str_replace(array("\n","\r", " "), "", $mybb->settings['cookieprefix']);
2275  
2276      // Versions of PHP prior to 5.2 do not support HttpOnly cookies and IE is buggy when specifying a blank domain so set the cookie manually
2277      $cookie = "Set-Cookie: {$mybb->settings['cookieprefix']}{$name}=".urlencode($value);
2278  
2279      if($expires > 0)
2280      {
2281          $cookie .= "; expires=".@gmdate('D, d-M-Y H:i:s \\G\\M\\T', $expires);
2282      }
2283  
2284      if(!empty($mybb->settings['cookiepath']))
2285      {
2286          $cookie .= "; path={$mybb->settings['cookiepath']}";
2287      }
2288  
2289      if(!empty($mybb->settings['cookiedomain']))
2290      {
2291          $cookie .= "; domain={$mybb->settings['cookiedomain']}";
2292      }
2293  
2294      if($httponly == true)
2295      {
2296          $cookie .= "; HttpOnly";
2297      }
2298  
2299      if($samesite != "" && $mybb->settings['cookiesamesiteflag'])
2300      {
2301          $samesite = strtolower($samesite);
2302  
2303          if($samesite == "lax" || $samesite == "strict")
2304          {
2305              $cookie .= "; SameSite=".$samesite;
2306          }
2307      }
2308  
2309      if($mybb->settings['cookiesecureflag'])
2310      {
2311          $cookie .= "; Secure";
2312      }
2313  
2314      $mybb->cookies[$name] = $value;
2315  
2316      header($cookie, false);
2317  }
2318  
2319  /**
2320   * Unset a cookie set by MyBB.
2321   *
2322   * @param string $name The cookie identifier.
2323   */
2324  function my_unsetcookie($name)
2325  {
2326      global $mybb;
2327  
2328      $expires = -3600;
2329      my_setcookie($name, "", $expires);
2330  
2331      unset($mybb->cookies[$name]);
2332  }
2333  
2334  /**
2335   * Get the contents from a serialised cookie array.
2336   *
2337   * @param string $name The cookie identifier.
2338   * @param int $id The cookie content id.
2339   * @return array|boolean The cookie id's content array or false when non-existent.
2340   */
2341  function my_get_array_cookie($name, $id)
2342  {
2343      global $mybb;
2344  
2345      if(!isset($mybb->cookies['mybb'][$name]))
2346      {
2347          return false;
2348      }
2349  
2350      $cookie = my_unserialize($mybb->cookies['mybb'][$name], false);
2351  
2352      if(is_array($cookie) && isset($cookie[$id]))
2353      {
2354          return $cookie[$id];
2355      }
2356      else
2357      {
2358          return 0;
2359      }
2360  }
2361  
2362  /**
2363   * Set a serialised cookie array.
2364   *
2365   * @param string $name The cookie identifier.
2366   * @param int $id The cookie content id.
2367   * @param string $value The value to set the cookie to.
2368   * @param int|string $expires The timestamp of the expiry date.
2369   */
2370  function my_set_array_cookie($name, $id, $value, $expires="")
2371  {
2372      global $mybb;
2373  
2374      if(isset($mybb->cookies['mybb'][$name]))
2375      {
2376          $newcookie = my_unserialize($mybb->cookies['mybb'][$name], false);
2377      }
2378      else
2379      {
2380          $newcookie = array();
2381      }
2382  
2383      $newcookie[$id] = $value;
2384      $newcookie = my_serialize($newcookie);
2385      my_setcookie("mybb[$name]", addslashes($newcookie), $expires);
2386  
2387      if(isset($mybb->cookies['mybb']) && !is_array($mybb->cookies['mybb']))
2388      {
2389          $mybb->cookies['mybb'] = array();
2390      }
2391  
2392      // Make sure our current viarables are up-to-date as well
2393      $mybb->cookies['mybb'][$name] = $newcookie;
2394  }
2395  
2396  /*
2397   * Arbitrary limits for _safe_unserialize()
2398   */
2399  define('MAX_SERIALIZED_INPUT_LENGTH', 10240);
2400  define('MAX_SERIALIZED_ARRAY_LENGTH', 256);
2401  define('MAX_SERIALIZED_ARRAY_DEPTH', 5);
2402  
2403  /**
2404   * Credits go to https://github.com/piwik
2405   * Safe unserialize() replacement
2406   * - accepts a strict subset of PHP's native my_serialized representation
2407   * - does not unserialize objects
2408   *
2409   * @param string $str
2410   * @param bool $unlimited Whether to apply limits defined in MAX_SERIALIZED_* constants
2411   * @return mixed
2412   * @throw Exception if $str is malformed or contains unsupported types (e.g., resources, objects)
2413   */
2414  function _safe_unserialize($str, $unlimited = true)
2415  {
2416      if(!$unlimited && strlen($str) > MAX_SERIALIZED_INPUT_LENGTH)
2417      {
2418          // input exceeds MAX_SERIALIZED_INPUT_LENGTH
2419          return false;
2420      }
2421  
2422      if(empty($str) || !is_string($str))
2423      {
2424          return false;
2425      }
2426  
2427      $stack = $list = $expected = array();
2428  
2429      /*
2430       * states:
2431       *   0 - initial state, expecting a single value or array
2432       *   1 - terminal state
2433       *   2 - in array, expecting end of array or a key
2434       *   3 - in array, expecting value or another array
2435       */
2436      $state = 0;
2437      while($state != 1)
2438      {
2439          $type = isset($str[0]) ? $str[0] : '';
2440  
2441          if($type == '}')
2442          {
2443              $str = substr($str, 1);
2444          }
2445          else if($type == 'N' && $str[1] == ';')
2446          {
2447              $value = null;
2448              $str = substr($str, 2);
2449          }
2450          else if($type == 'b' && preg_match('/^b:([01]);/', $str, $matches))
2451          {
2452              $value = $matches[1] == '1' ? true : false;
2453              $str = substr($str, 4);
2454          }
2455          else if($type == 'i' && preg_match('/^i:(-?[0-9]+);(.*)/s', $str, $matches))
2456          {
2457              $value = (int)$matches[1];
2458              $str = $matches[2];
2459          }
2460          else if($type == 'd' && preg_match('/^d:(-?[0-9]+\.?[0-9]*(E[+-][0-9]+)?);(.*)/s', $str, $matches))
2461          {
2462              $value = (float)$matches[1];
2463              $str = $matches[3];
2464          }
2465          else if($type == 's' && preg_match('/^s:([0-9]+):"(.*)/s', $str, $matches) && substr($matches[2], (int)$matches[1], 2) == '";')
2466          {
2467              $value = substr($matches[2], 0, (int)$matches[1]);
2468              $str = substr($matches[2], (int)$matches[1] + 2);
2469          }
2470          else if(
2471              $type == 'a' &&
2472              preg_match('/^a:([0-9]+):{(.*)/s', $str, $matches) &&
2473              ($unlimited || $matches[1] < MAX_SERIALIZED_ARRAY_LENGTH)
2474          )
2475          {
2476              $expectedLength = (int)$matches[1];
2477              $str = $matches[2];
2478          }
2479          else
2480          {
2481              // object or unknown/malformed type
2482              return false;
2483          }
2484  
2485          switch($state)
2486          {
2487              case 3: // in array, expecting value or another array
2488                  if($type == 'a')
2489                  {
2490                      if(!$unlimited && count($stack) >= MAX_SERIALIZED_ARRAY_DEPTH)
2491                      {
2492                          // array nesting exceeds MAX_SERIALIZED_ARRAY_DEPTH
2493                          return false;
2494                      }
2495  
2496                      $stack[] = &$list;
2497                      $list[$key] = array();
2498                      $list = &$list[$key];
2499                      $expected[] = $expectedLength;
2500                      $state = 2;
2501                      break;
2502                  }
2503                  if($type != '}')
2504                  {
2505                      $list[$key] = $value;
2506                      $state = 2;
2507                      break;
2508                  }
2509  
2510                  // missing array value
2511                  return false;
2512  
2513              case 2: // in array, expecting end of array or a key
2514                  if($type == '}')
2515                  {
2516                      if(count($list) < end($expected))
2517                      {
2518                          // array size less than expected
2519                          return false;
2520                      }
2521  
2522                      unset($list);
2523                      $list = &$stack[count($stack)-1];
2524                      array_pop($stack);
2525  
2526                      // go to terminal state if we're at the end of the root array
2527                      array_pop($expected);
2528                      if(count($expected) == 0) {
2529                          $state = 1;
2530                      }
2531                      break;
2532                  }
2533                  if($type == 'i' || $type == 's')
2534                  {
2535                      if(!$unlimited && count($list) >= MAX_SERIALIZED_ARRAY_LENGTH)
2536                      {
2537                          // array size exceeds MAX_SERIALIZED_ARRAY_LENGTH
2538                          return false;
2539                      }
2540                      if(count($list) >= end($expected))
2541                      {
2542                          // array size exceeds expected length
2543                          return false;
2544                      }
2545  
2546                      $key = $value;
2547                      $state = 3;
2548                      break;
2549                  }
2550  
2551                  // illegal array index type
2552                  return false;
2553  
2554              case 0: // expecting array or value
2555                  if($type == 'a')
2556                  {
2557                      if(!$unlimited && count($stack) >= MAX_SERIALIZED_ARRAY_DEPTH)
2558                      {
2559                          // array nesting exceeds MAX_SERIALIZED_ARRAY_DEPTH
2560                          return false;
2561                      }
2562  
2563                      $data = array();
2564                      $list = &$data;
2565                      $expected[] = $expectedLength;
2566                      $state = 2;
2567                      break;
2568                  }
2569                  if($type != '}')
2570                  {
2571                      $data = $value;
2572                      $state = 1;
2573                      break;
2574                  }
2575  
2576                  // not in array
2577                  return false;
2578          }
2579      }
2580  
2581      if(!empty($str))
2582      {
2583          // trailing data in input
2584          return false;
2585      }
2586      return $data;
2587  }
2588  
2589  /**
2590   * Credits go to https://github.com/piwik
2591   * Wrapper for _safe_unserialize() that handles exceptions and multibyte encoding issue
2592   *
2593   * @param string $str
2594   * @param bool $unlimited
2595   * @return mixed
2596   */
2597  function my_unserialize($str, $unlimited = true)
2598  {
2599      // Ensure we use the byte count for strings even when strlen() is overloaded by mb_strlen()
2600      if(function_exists('mb_internal_encoding') && (((int)ini_get('mbstring.func_overload')) & 2))
2601      {
2602          $mbIntEnc = mb_internal_encoding();
2603          mb_internal_encoding('ASCII');
2604      }
2605  
2606      $out = _safe_unserialize($str, $unlimited);
2607  
2608      if(isset($mbIntEnc))
2609      {
2610          mb_internal_encoding($mbIntEnc);
2611      }
2612  
2613      return $out;
2614  }
2615  
2616  /**
2617   * Unserializes data using PHP's `unserialize()`, and its safety options if possible.
2618   * This function should only be used for values from trusted sources.
2619   *
2620   * @param string $str
2621   * @return mixed
2622   */
2623  function native_unserialize($str)
2624  {
2625      if(version_compare(PHP_VERSION, '7.0.0', '>='))
2626      {
2627          return unserialize($str, array('allowed_classes' => false));
2628      }
2629      else
2630      {
2631          return unserialize($str);
2632      }
2633  }
2634  
2635  /**
2636   * Credits go to https://github.com/piwik
2637   * Safe serialize() replacement
2638   * - output a strict subset of PHP's native serialized representation
2639   * - does not my_serialize objects
2640   *
2641   * @param mixed $value
2642   * @return string
2643   * @throw Exception if $value is malformed or contains unsupported types (e.g., resources, objects)
2644   */
2645  function _safe_serialize( $value )
2646  {
2647      if(is_null($value))
2648      {
2649          return 'N;';
2650      }
2651  
2652      if(is_bool($value))
2653      {
2654          return 'b:'.(int)$value.';';
2655      }
2656  
2657      if(is_int($value))
2658      {
2659          return 'i:'.$value.';';
2660      }
2661  
2662      if(is_float($value))
2663      {
2664          return 'd:'.str_replace(',', '.', $value).';';
2665      }
2666  
2667      if(is_string($value))
2668      {
2669          return 's:'.strlen($value).':"'.$value.'";';
2670      }
2671  
2672      if(is_array($value))
2673      {
2674          $out = '';
2675          foreach($value as $k => $v)
2676          {
2677              $out .= _safe_serialize($k) . _safe_serialize($v);
2678          }
2679  
2680          return 'a:'.count($value).':{'.$out.'}';
2681      }
2682  
2683      // safe_serialize cannot my_serialize resources or objects
2684      return false;
2685  }
2686  
2687  /**
2688   * Credits go to https://github.com/piwik
2689   * Wrapper for _safe_serialize() that handles exceptions and multibyte encoding issue
2690   *
2691   * @param mixed $value
2692   * @return string
2693  */
2694  function my_serialize($value)
2695  {
2696      // ensure we use the byte count for strings even when strlen() is overloaded by mb_strlen()
2697      if(function_exists('mb_internal_encoding') && (((int)ini_get('mbstring.func_overload')) & 2))
2698      {
2699          $mbIntEnc = mb_internal_encoding();
2700          mb_internal_encoding('ASCII');
2701      }
2702  
2703      $out = _safe_serialize($value);
2704      if(isset($mbIntEnc))
2705      {
2706          mb_internal_encoding($mbIntEnc);
2707      }
2708  
2709      return $out;
2710  }
2711  
2712  /**
2713   * Returns the serverload of the system.
2714   *
2715   * @return int The serverload of the system.
2716   */
2717  function get_server_load()
2718  {
2719      global $mybb, $lang;
2720  
2721      $serverload = array();
2722  
2723      // DIRECTORY_SEPARATOR checks if running windows
2724      if(DIRECTORY_SEPARATOR != '\\')
2725      {
2726          if(function_exists("sys_getloadavg"))
2727          {
2728              // sys_getloadavg() will return an array with [0] being load within the last minute.
2729              $serverload = sys_getloadavg();
2730              $serverload[0] = round($serverload[0], 4);
2731          }
2732          else if(@file_exists("/proc/loadavg") && $load = @file_get_contents("/proc/loadavg"))
2733          {
2734              $serverload = explode(" ", $load);
2735              $serverload[0] = round($serverload[0], 4);
2736          }
2737          if(!is_numeric($serverload[0]))
2738          {
2739              if($mybb->safemode)
2740              {
2741                  return $lang->unknown;
2742              }
2743  
2744              // Suhosin likes to throw a warning if exec is disabled then die - weird
2745              if($func_blacklist = @ini_get('suhosin.executor.func.blacklist'))
2746              {
2747                  if(strpos(",".$func_blacklist.",", 'exec') !== false)
2748                  {
2749                      return $lang->unknown;
2750                  }
2751              }
2752              // PHP disabled functions?
2753              if($func_blacklist = @ini_get('disable_functions'))
2754              {
2755                  if(strpos(",".$func_blacklist.",", 'exec') !== false)
2756                  {
2757                      return $lang->unknown;
2758                  }
2759              }
2760  
2761              $load = @exec("uptime");
2762              $load = explode("load average: ", $load);
2763              $serverload = explode(",", $load[1]);
2764              if(!is_array($serverload))
2765              {
2766                  return $lang->unknown;
2767              }
2768          }
2769      }
2770      else
2771      {
2772          return $lang->unknown;
2773      }
2774  
2775      $returnload = trim($serverload[0]);
2776  
2777      return $returnload;
2778  }
2779  
2780  /**
2781   * Returns the amount of memory allocated to the script.
2782   *
2783   * @return int The amount of memory allocated to the script.
2784   */
2785  function get_memory_usage()
2786  {
2787      if(function_exists('memory_get_peak_usage'))
2788      {
2789          return memory_get_peak_usage(true);
2790      }
2791      elseif(function_exists('memory_get_usage'))
2792      {
2793          return memory_get_usage(true);
2794      }
2795      return false;
2796  }
2797  
2798  /**
2799   * Updates the forum statistics with specific values (or addition/subtraction of the previous value)
2800   *
2801   * @param array $changes Array of items being updated (numthreads,numposts,numusers,numunapprovedthreads,numunapprovedposts,numdeletedposts,numdeletedthreads)
2802   * @param boolean $force Force stats update?
2803   */
2804  function update_stats($changes=array(), $force=false)
2805  {
2806      global $cache, $db;
2807      static $stats_changes;
2808  
2809      if(empty($stats_changes))
2810      {
2811          // Update stats after all changes are done
2812          add_shutdown('update_stats', array(array(), true));
2813      }
2814  
2815      if(empty($stats_changes) || $stats_changes['inserted'])
2816      {
2817          $stats_changes = array(
2818              'numthreads' => '+0',
2819              'numposts' => '+0',
2820              'numusers' => '+0',
2821              'numunapprovedthreads' => '+0',
2822              'numunapprovedposts' => '+0',
2823              'numdeletedposts' => '+0',
2824              'numdeletedthreads' => '+0',
2825              'inserted' => false // Reset after changes are inserted into cache
2826          );
2827          $stats = $stats_changes;
2828      }
2829  
2830      if($force) // Force writing to cache?
2831      {
2832          if(!empty($changes))
2833          {
2834              // Calculate before writing to cache
2835              update_stats($changes);
2836          }
2837          $stats = $cache->read("stats");
2838          $changes = $stats_changes;
2839      }
2840      else
2841      {
2842          $stats = $stats_changes;
2843      }
2844  
2845      $new_stats = array();
2846      $counters = array('numthreads', 'numunapprovedthreads', 'numposts', 'numunapprovedposts', 'numusers', 'numdeletedposts', 'numdeletedthreads');
2847      foreach($counters as $counter)
2848      {
2849          if(array_key_exists($counter, $changes))
2850          {
2851              if(substr($changes[$counter], 0, 2) == "+-")
2852              {
2853                  $changes[$counter] = substr($changes[$counter], 1);
2854              }
2855              // Adding or subtracting from previous value?
2856              if(substr($changes[$counter], 0, 1) == "+" || substr($changes[$counter], 0, 1) == "-")
2857              {
2858                  if((int)$changes[$counter] != 0)
2859                  {
2860                      $new_stats[$counter] = $stats[$counter] + $changes[$counter];
2861                      if(!$force && (substr($stats[$counter], 0, 1) == "+" || substr($stats[$counter], 0, 1) == "-"))
2862                      {
2863                          // We had relative values? Then it is still relative
2864                          if($new_stats[$counter] >= 0)
2865                          {
2866                              $new_stats[$counter] = "+{$new_stats[$counter]}";
2867                          }
2868                      }
2869                      // Less than 0? That's bad
2870                      elseif($new_stats[$counter] < 0)
2871                      {
2872                          $new_stats[$counter] = 0;
2873                      }
2874                  }
2875              }
2876              else
2877              {
2878                  $new_stats[$counter] = $changes[$counter];
2879                  // Less than 0? That's bad
2880                  if($new_stats[$counter] < 0)
2881                  {
2882                      $new_stats[$counter] = 0;
2883                  }
2884              }
2885          }
2886      }
2887  
2888      if(!$force)
2889      {
2890          $stats_changes = array_merge($stats, $new_stats); // Overwrite changed values
2891          return;
2892      }
2893  
2894      // Fetch latest user if the user count is changing
2895      if(array_key_exists('numusers', $changes))
2896      {
2897          $query = $db->simple_select("users", "uid, username", "", array('order_by' => 'regdate', 'order_dir' => 'DESC', 'limit' => 1));
2898          $lastmember = $db->fetch_array($query);
2899          $new_stats['lastuid'] = $lastmember['uid'];
2900          $new_stats['lastusername'] = $lastmember['username'] = htmlspecialchars_uni($lastmember['username']);
2901      }
2902  
2903      if(!empty($new_stats))
2904      {
2905          if(is_array($stats))
2906          {
2907              $stats = array_merge($stats, $new_stats); // Overwrite changed values
2908          }
2909          else
2910          {
2911              $stats = $new_stats;
2912          }
2913      }
2914  
2915      // Update stats row for today in the database
2916      $todays_stats = array(
2917          "dateline" => mktime(0, 0, 0, date("m"), date("j"), date("Y")),
2918          "numusers" => (int)$stats['numusers'],
2919          "numthreads" => (int)$stats['numthreads'],
2920          "numposts" => (int)$stats['numposts']
2921      );
2922      $db->replace_query("stats", $todays_stats, "dateline");
2923  
2924      $cache->update("stats", $stats, "dateline");
2925      $stats_changes['inserted'] = true;
2926  }
2927  
2928  /**
2929   * Updates the forum counters with a specific value (or addition/subtraction of the previous value)
2930   *
2931   * @param int $fid The forum ID
2932   * @param array $changes Array of items being updated (threads, posts, unapprovedthreads, unapprovedposts, deletedposts, deletedthreads) and their value (ex, 1, +1, -1)
2933   */
2934  function update_forum_counters($fid, $changes=array())
2935  {
2936      global $db;
2937  
2938      $update_query = array();
2939  
2940      $counters = array('threads', 'unapprovedthreads', 'posts', 'unapprovedposts', 'deletedposts', 'deletedthreads');
2941  
2942      // Fetch above counters for this forum
2943      $query = $db->simple_select("forums", implode(",", $counters), "fid='{$fid}'");
2944      $forum = $db->fetch_array($query);
2945  
2946      foreach($counters as $counter)
2947      {
2948          if(array_key_exists($counter, $changes))
2949          {
2950              if(substr($changes[$counter], 0, 2) == "+-")
2951              {
2952                  $changes[$counter] = substr($changes[$counter], 1);
2953              }
2954              // Adding or subtracting from previous value?
2955              if(substr($changes[$counter], 0, 1) == "+" || substr($changes[$counter], 0, 1) == "-")
2956              {
2957                  if((int)$changes[$counter] != 0)
2958                  {
2959                      $update_query[$counter] = $forum[$counter] + $changes[$counter];
2960                  }
2961              }
2962              else
2963              {
2964                  $update_query[$counter] = $changes[$counter];
2965              }
2966  
2967              // Less than 0? That's bad
2968              if(isset($update_query[$counter]) && $update_query[$counter] < 0)
2969              {
2970                  $update_query[$counter] = 0;
2971              }
2972          }
2973      }
2974  
2975      // Only update if we're actually doing something
2976      if(count($update_query) > 0)
2977      {
2978          $db->update_query("forums", $update_query, "fid='".(int)$fid."'");
2979      }
2980  
2981      // Guess we should update the statistics too?
2982      $new_stats = array();
2983      if(array_key_exists('threads', $update_query))
2984      {
2985          $threads_diff = $update_query['threads'] - $forum['threads'];
2986          if($threads_diff > -1)
2987          {
2988              $new_stats['numthreads'] = "+{$threads_diff}";
2989          }
2990          else
2991          {
2992              $new_stats['numthreads'] = "{$threads_diff}";
2993          }
2994      }
2995  
2996      if(array_key_exists('unapprovedthreads', $update_query))
2997      {
2998          $unapprovedthreads_diff = $update_query['unapprovedthreads'] - $forum['unapprovedthreads'];
2999          if($unapprovedthreads_diff > -1)
3000          {
3001              $new_stats['numunapprovedthreads'] = "+{$unapprovedthreads_diff}";
3002          }
3003          else
3004          {
3005              $new_stats['numunapprovedthreads'] = "{$unapprovedthreads_diff}";
3006          }
3007      }
3008  
3009      if(array_key_exists('posts', $update_query))
3010      {
3011          $posts_diff = $update_query['posts'] - $forum['posts'];
3012          if($posts_diff > -1)
3013          {
3014              $new_stats['numposts'] = "+{$posts_diff}";
3015          }
3016          else
3017          {
3018              $new_stats['numposts'] = "{$posts_diff}";
3019          }
3020      }
3021  
3022      if(array_key_exists('unapprovedposts', $update_query))
3023      {
3024          $unapprovedposts_diff = $update_query['unapprovedposts'] - $forum['unapprovedposts'];
3025          if($unapprovedposts_diff > -1)
3026          {
3027              $new_stats['numunapprovedposts'] = "+{$unapprovedposts_diff}";
3028          }
3029          else
3030          {
3031              $new_stats['numunapprovedposts'] = "{$unapprovedposts_diff}";
3032          }
3033      }
3034  
3035      if(array_key_exists('deletedposts', $update_query))
3036      {
3037          $deletedposts_diff = $update_query['deletedposts'] - $forum['deletedposts'];
3038          if($deletedposts_diff > -1)
3039          {
3040              $new_stats['numdeletedposts'] = "+{$deletedposts_diff}";
3041          }
3042          else
3043          {
3044              $new_stats['numdeletedposts'] = "{$deletedposts_diff}";
3045          }
3046      }
3047  
3048      if(array_key_exists('deletedthreads', $update_query))
3049      {
3050          $deletedthreads_diff = $update_query['deletedthreads'] - $forum['deletedthreads'];
3051          if($deletedthreads_diff > -1)
3052          {
3053              $new_stats['numdeletedthreads'] = "+{$deletedthreads_diff}";
3054          }
3055          else
3056          {
3057              $new_stats['numdeletedthreads'] = "{$deletedthreads_diff}";
3058          }
3059      }
3060  
3061      if(!empty($new_stats))
3062      {
3063          update_stats($new_stats);
3064      }
3065  }
3066  
3067  /**
3068   * Update the last post information for a specific forum
3069   *
3070   * @param int $fid The forum ID
3071   */
3072  function update_forum_lastpost($fid)
3073  {
3074      global $db;
3075  
3076      // Fetch the last post for this forum
3077      $query = $db->query("
3078          SELECT tid, lastpost, lastposter, lastposteruid, subject
3079          FROM ".TABLE_PREFIX."threads
3080          WHERE fid='{$fid}' AND visible='1' AND closed NOT LIKE 'moved|%'
3081          ORDER BY lastpost DESC
3082          LIMIT 0, 1
3083      ");
3084  
3085      if($db->num_rows($query) > 0)
3086      {
3087          $lastpost = $db->fetch_array($query);
3088  
3089          $updated_forum = array(
3090              "lastpost" => (int)$lastpost['lastpost'],
3091              "lastposter" => $db->escape_string($lastpost['lastposter']),
3092              "lastposteruid" => (int)$lastpost['lastposteruid'],
3093              "lastposttid" => (int)$lastpost['tid'],
3094              "lastpostsubject" => $db->escape_string($lastpost['subject']),
3095          );
3096      }
3097      else {
3098          $updated_forum = array(
3099              "lastpost" => 0,
3100              "lastposter" => '',
3101              "lastposteruid" => 0,
3102              "lastposttid" => 0,
3103              "lastpostsubject" => '',
3104          );
3105      }
3106  
3107      $db->update_query("forums", $updated_forum, "fid='{$fid}'");
3108  }
3109  
3110  /**
3111   * Updates the thread counters with a specific value (or addition/subtraction of the previous value)
3112   *
3113   * @param int $tid The thread ID
3114   * @param array $changes Array of items being updated (replies, unapprovedposts, deletedposts, attachmentcount) and their value (ex, 1, +1, -1)
3115   */
3116  function update_thread_counters($tid, $changes=array())
3117  {
3118      global $db;
3119  
3120      $update_query = array();
3121      $tid = (int)$tid;
3122  
3123      $counters = array('replies', 'unapprovedposts', 'attachmentcount', 'deletedposts', 'attachmentcount');
3124  
3125      // Fetch above counters for this thread
3126      $query = $db->simple_select("threads", implode(",", $counters), "tid='{$tid}'");
3127      $thread = $db->fetch_array($query);
3128  
3129      foreach($counters as $counter)
3130      {
3131          if(array_key_exists($counter, $changes))
3132          {
3133              if(substr($changes[$counter], 0, 2) == "+-")
3134              {
3135                  $changes[$counter] = substr($changes[$counter], 1);
3136              }
3137              // Adding or subtracting from previous value?
3138              if(substr($changes[$counter], 0, 1) == "+" || substr($changes[$counter], 0, 1) == "-")
3139              {
3140                  if((int)$changes[$counter] != 0)
3141                  {
3142                      $update_query[$counter] = $thread[$counter] + $changes[$counter];
3143                  }
3144              }
3145              else
3146              {
3147                  $update_query[$counter] = $changes[$counter];
3148              }
3149  
3150              // Less than 0? That's bad
3151              if(isset($update_query[$counter]) && $update_query[$counter] < 0)
3152              {
3153                  $update_query[$counter] = 0;
3154              }
3155          }
3156      }
3157  
3158      $db->free_result($query);
3159  
3160      // Only update if we're actually doing something
3161      if(count($update_query) > 0)
3162      {
3163          $db->update_query("threads", $update_query, "tid='{$tid}'");
3164      }
3165  }
3166  
3167  /**
3168   * Update the first post and lastpost data for a specific thread
3169   *
3170   * @param int $tid The thread ID
3171   */
3172  function update_thread_data($tid)
3173  {
3174      global $db;
3175  
3176      $thread = get_thread($tid);
3177  
3178      // If this is a moved thread marker, don't update it - we need it to stay as it is
3179      if(strpos($thread['closed'], 'moved|') !== false)
3180      {
3181          return;
3182      }
3183  
3184      $query = $db->query("
3185          SELECT u.uid, u.username, p.username AS postusername, p.dateline
3186          FROM ".TABLE_PREFIX."posts p
3187          LEFT JOIN ".TABLE_PREFIX."users u ON (u.uid=p.uid)
3188          WHERE p.tid='$tid' AND p.visible='1'
3189          ORDER BY p.dateline DESC, p.pid DESC
3190          LIMIT 1"
3191      );
3192      $lastpost = $db->fetch_array($query);
3193  
3194      $db->free_result($query);
3195  
3196      $query = $db->query("
3197          SELECT u.uid, u.username, p.pid, p.username AS postusername, p.dateline
3198          FROM ".TABLE_PREFIX."posts p
3199          LEFT JOIN ".TABLE_PREFIX."users u ON (u.uid=p.uid)
3200          WHERE p.tid='$tid'
3201          ORDER BY p.dateline ASC, p.pid ASC
3202          LIMIT 1
3203      ");
3204      $firstpost = $db->fetch_array($query);
3205  
3206      $db->free_result($query);
3207  
3208      if(empty($firstpost['username']))
3209      {
3210          $firstpost['username'] = $firstpost['postusername'];
3211      }
3212  
3213      if(empty($lastpost['username']))
3214      {
3215          $lastpost['username'] = $lastpost['postusername'];
3216      }
3217  
3218      if(empty($lastpost['dateline']))
3219      {
3220          $lastpost['username'] = $firstpost['username'];
3221          $lastpost['uid'] = $firstpost['uid'];
3222          $lastpost['dateline'] = $firstpost['dateline'];
3223      }
3224  
3225      $lastpost['username'] = $db->escape_string($lastpost['username']);
3226      $firstpost['username'] = $db->escape_string($firstpost['username']);
3227  
3228      $update_array = array(
3229          'firstpost' => (int)$firstpost['pid'],
3230          'username' => $firstpost['username'],
3231          'uid' => (int)$firstpost['uid'],
3232          'dateline' => (int)$firstpost['dateline'],
3233          'lastpost' => (int)$lastpost['dateline'],
3234          'lastposter' => $lastpost['username'],
3235          'lastposteruid' => (int)$lastpost['uid'],
3236      );
3237      $db->update_query("threads", $update_array, "tid='{$tid}'");
3238  }
3239  
3240  /**
3241   * Updates the user counters with a specific value (or addition/subtraction of the previous value)
3242   *
3243   * @param int $uid The user ID
3244   * @param array $changes Array of items being updated (postnum, threadnum) and their value (ex, 1, +1, -1)
3245   */
3246  function update_user_counters($uid, $changes=array())
3247  {
3248      global $db;
3249  
3250      $update_query = array();
3251  
3252      $counters = array('postnum', 'threadnum');
3253      $uid = (int)$uid;
3254  
3255      // Fetch above counters for this user
3256      $query = $db->simple_select("users", implode(",", $counters), "uid='{$uid}'");
3257      $user = $db->fetch_array($query);
3258  
3259      foreach($counters as $counter)
3260      {
3261          if(array_key_exists($counter, $changes))
3262          {
3263              if(substr($changes[$counter], 0, 2) == "+-")
3264              {
3265                  $changes[$counter] = substr($changes[$counter], 1);
3266              }
3267              // Adding or subtracting from previous value?
3268              if(substr($changes[$counter], 0, 1) == "+" || substr($changes[$counter], 0, 1) == "-")
3269              {
3270                  if((int)$changes[$counter] != 0)
3271                  {
3272                      $update_query[$counter] = $user[$counter] + $changes[$counter];
3273                  }
3274              }
3275              else
3276              {
3277                  $update_query[$counter] = $changes[$counter];
3278              }
3279  
3280              // Less than 0? That's bad
3281              if(isset($update_query[$counter]) && $update_query[$counter] < 0)
3282              {
3283                  $update_query[$counter] = 0;
3284              }
3285          }
3286      }
3287  
3288      $db->free_result($query);
3289  
3290      // Only update if we're actually doing something
3291      if(count($update_query) > 0)
3292      {
3293          $db->update_query("users", $update_query, "uid='{$uid}'");
3294      }
3295  }
3296  
3297  /**
3298   * Deletes a thread from the database
3299   *
3300   * @param int $tid The thread ID
3301   * @return bool
3302   */
3303  function delete_thread($tid)
3304  {
3305      global $moderation;
3306  
3307      if(!is_object($moderation))
3308      {
3309          require_once  MYBB_ROOT."inc/class_moderation.php";
3310          $moderation = new Moderation;
3311      }
3312  
3313      return $moderation->delete_thread($tid);
3314  }
3315  
3316  /**
3317   * Deletes a post from the database
3318   *
3319   * @param int $pid The thread ID
3320   * @return bool
3321   */
3322  function delete_post($pid)
3323  {
3324      global $moderation;
3325  
3326      if(!is_object($moderation))
3327      {
3328          require_once  MYBB_ROOT."inc/class_moderation.php";
3329          $moderation = new Moderation;
3330      }
3331  
3332      return $moderation->delete_post($pid);
3333  }
3334  
3335  /**
3336   * Builds a forum jump menu
3337   *
3338   * @param int $pid The parent forum to start with
3339   * @param int $selitem The selected item ID
3340   * @param int $addselect If we need to add select boxes to this cal or not
3341   * @param string $depth The current depth of forums we're at
3342   * @param int $showextras Whether or not to show extra items such as User CP, Forum home
3343   * @param boolean $showall Ignore the showinjump setting and show all forums (for moderation pages)
3344   * @param mixed $permissions deprecated
3345   * @param string $name The name of the forum jump
3346   * @return string Forum jump items
3347   */
3348  function build_forum_jump($pid=0, $selitem=0, $addselect=1, $depth="", $showextras=1, $showall=false, $permissions="", $name="fid")
3349  {
3350      global $forum_cache, $jumpfcache, $permissioncache, $mybb, $forumjump, $forumjumpbits, $gobutton, $theme, $templates, $lang;
3351  
3352      $pid = (int)$pid;
3353  
3354      if(!is_array($jumpfcache))
3355      {
3356          if(!is_array($forum_cache))
3357          {
3358              cache_forums();
3359          }
3360  
3361          foreach($forum_cache as $fid => $forum)
3362          {
3363              if($forum['active'] != 0)
3364              {
3365                  $jumpfcache[$forum['pid']][$forum['disporder']][$forum['fid']] = $forum;
3366              }
3367          }
3368      }
3369  
3370      if(!is_array($permissioncache))
3371      {
3372          $permissioncache = forum_permissions();
3373      }
3374  
3375      if(isset($jumpfcache[$pid]) && is_array($jumpfcache[$pid]))
3376      {
3377          foreach($jumpfcache[$pid] as $main)
3378          {
3379              foreach($main as $forum)
3380              {
3381                  $perms = $permissioncache[$forum['fid']];
3382  
3383                  if($forum['fid'] != "0" && ($perms['canview'] != 0 || $mybb->settings['hideprivateforums'] == 0) && $forum['linkto'] == '' && ($forum['showinjump'] != 0 || $showall == true))
3384                  {
3385                      $optionselected = "";
3386  
3387                      if($selitem == $forum['fid'])
3388                      {
3389                          $optionselected = 'selected="selected"';
3390                      }
3391  
3392                      $forum['name'] = htmlspecialchars_uni(strip_tags($forum['name']));
3393  
3394                      eval("\$forumjumpbits .= \"".$templates->get("forumjump_bit")."\";");
3395  
3396                      if($forum_cache[$forum['fid']])
3397                      {
3398                          $newdepth = $depth."--";
3399                          $forumjumpbits .= build_forum_jump($forum['fid'], $selitem, 0, $newdepth, $showextras, $showall);
3400                      }
3401                  }
3402              }
3403          }
3404      }
3405  
3406      if($addselect)
3407      {
3408          if($showextras == 0)
3409          {
3410              $template = "special";
3411          }
3412          else
3413          {
3414              $template = "advanced";
3415  
3416              if(strpos(FORUM_URL, '.html') !== false)
3417              {
3418                  $forum_link = "'".str_replace('{fid}', "'+option+'", FORUM_URL)."'";
3419              }
3420              else
3421              {
3422                  $forum_link = "'".str_replace('{fid}', "'+option", FORUM_URL);
3423              }
3424          }
3425  
3426          eval("\$forumjump = \"".$templates->get("forumjump_".$template)."\";");
3427      }
3428  
3429      return $forumjump;
3430  }
3431  
3432  /**
3433   * Returns the extension of a file.
3434   *
3435   * @param string $file The filename.
3436   * @return string The extension of the file.
3437   */
3438  function get_extension($file)
3439  {
3440      return my_strtolower(my_substr(strrchr($file, "."), 1));
3441  }
3442  
3443  /**
3444   * Generates a random string.
3445   *
3446   * @param int $length The length of the string to generate.
3447   * @param bool $complex Whether to return complex string. Defaults to false
3448   * @return string The random string.
3449   */
3450  function random_str($length=8, $complex=false)
3451  {
3452      $set = array_merge(range(0, 9), range('A', 'Z'), range('a', 'z'));
3453      $str = array();
3454  
3455      // Complex strings have always at least 3 characters, even if $length < 3
3456      if($complex == true)
3457      {
3458          // At least one number
3459          $str[] = $set[my_rand(0, 9)];
3460  
3461          // At least one big letter
3462          $str[] = $set[my_rand(10, 35)];
3463  
3464          // At least one small letter
3465          $str[] = $set[my_rand(36, 61)];
3466  
3467          $length -= 3;
3468      }
3469  
3470      for($i = 0; $i < $length; ++$i)
3471      {
3472          $str[] = $set[my_rand(0, 61)];
3473      }
3474  
3475      // Make sure they're in random order and convert them to a string
3476      shuffle($str);
3477  
3478      return implode($str);
3479  }
3480  
3481  /**
3482   * Formats a username based on their display group
3483   *
3484   * @param string $username The username
3485   * @param int $usergroup The usergroup for the user
3486   * @param int $displaygroup The display group for the user
3487   * @return string The formatted username
3488   */
3489  function format_name($username, $usergroup, $displaygroup=0)
3490  {
3491      global $groupscache, $cache, $plugins;
3492  
3493      static $formattednames = array();
3494  
3495      if(!isset($formattednames[$username]))
3496      {
3497          if(!is_array($groupscache))
3498          {
3499              $groupscache = $cache->read("usergroups");
3500          }
3501  
3502          if($displaygroup != 0)
3503          {
3504              $usergroup = $displaygroup;
3505          }
3506  
3507          $format = "{username}";
3508  
3509          if(isset($groupscache[$usergroup]))
3510          {
3511              $ugroup = $groupscache[$usergroup];
3512  
3513              if(strpos($ugroup['namestyle'], "{username}") !== false)
3514              {
3515                  $format = $ugroup['namestyle'];
3516              }
3517          }
3518  
3519          $format = stripslashes($format);
3520  
3521          $parameters = compact('username', 'usergroup', 'displaygroup', 'format');
3522  
3523          $parameters = $plugins->run_hooks('format_name', $parameters);
3524  
3525          $format = $parameters['format'];
3526  
3527          $formattednames[$username] = str_replace("{username}", $username, $format);
3528      }
3529  
3530      return $formattednames[$username];
3531  }
3532  
3533  /**
3534   * Formats an avatar to a certain dimension
3535   *
3536   * @param string $avatar The avatar file name
3537   * @param string $dimensions Dimensions of the avatar, width x height (e.g. 44|44)
3538   * @param string $max_dimensions The maximum dimensions of the formatted avatar
3539   * @return array Information for the formatted avatar
3540   */
3541  function format_avatar($avatar, $dimensions = '', $max_dimensions = '')
3542  {
3543      global $mybb, $theme;
3544      static $avatars;
3545  
3546      if(!isset($avatars))
3547      {
3548          $avatars = array();
3549      }
3550  
3551      if(my_strpos($avatar, '://') !== false && !$mybb->settings['allowremoteavatars'])
3552      {
3553          // Remote avatar, but remote avatars are disallowed.
3554          $avatar = null;
3555      }
3556  
3557      if(!$avatar)
3558      {
3559          // Default avatar
3560          if(defined('IN_ADMINCP'))
3561          {
3562              $theme['imgdir'] = '../images';
3563          }
3564  
3565          $avatar = str_replace('{theme}', $theme['imgdir'], $mybb->settings['useravatar']);
3566          $dimensions = $mybb->settings['useravatardims'];
3567      }
3568  
3569      if(!$max_dimensions)
3570      {
3571          $max_dimensions = $mybb->settings['maxavatardims'];
3572      }
3573  
3574      // An empty key wouldn't work so we need to add a fall back
3575      $key = $dimensions;
3576      if(empty($key))
3577      {
3578          $key = 'default';
3579      }
3580      $key2 = $max_dimensions;
3581      if(empty($key2))
3582      {
3583          $key2 = 'default';
3584      }
3585  
3586      if(isset($avatars[$avatar][$key][$key2]))
3587      {
3588          return $avatars[$avatar][$key][$key2];
3589      }
3590  
3591      $avatar_width_height = '';
3592  
3593      if($dimensions)
3594      {
3595          $dimensions = preg_split('/[|x]/', $dimensions);
3596  
3597          if($dimensions[0] && $dimensions[1])
3598          {
3599              list($max_width, $max_height) = preg_split('/[|x]/', $max_dimensions);
3600  
3601              if(!empty($max_dimensions) && ($dimensions[0] > $max_width || $dimensions[1] > $max_height))
3602              {
3603                  require_once  MYBB_ROOT."inc/functions_image.php";
3604                  $scaled_dimensions = scale_image($dimensions[0], $dimensions[1], $max_width, $max_height);
3605                  $avatar_width_height = "width=\"{$scaled_dimensions['width']}\" height=\"{$scaled_dimensions['height']}\"";
3606              }
3607              else
3608              {
3609                  $avatar_width_height = "width=\"{$dimensions[0]}\" height=\"{$dimensions[1]}\"";
3610              }
3611          }
3612      }
3613  
3614      $avatars[$avatar][$key][$key2] = array(
3615          'image' => htmlspecialchars_uni($mybb->get_asset_url($avatar)),
3616          'width_height' => $avatar_width_height
3617      );
3618  
3619      return $avatars[$avatar][$key][$key2];
3620  }
3621  
3622  /**
3623   * Build the javascript based MyCode inserter.
3624   *
3625   * @param string $bind The ID of the textarea to bind to. Defaults to "message".
3626   * @param bool $smilies Whether to include smilies. Defaults to true.
3627   *
3628   * @return string The MyCode inserter
3629   */
3630  function build_mycode_inserter($bind="message", $smilies = true)
3631  {
3632      global $db, $mybb, $theme, $templates, $lang, $plugins, $smiliecache, $cache;
3633  
3634      if($mybb->settings['bbcodeinserter'] != 0)
3635      {
3636          $editor_lang_strings = array(
3637              "editor_bold" => "Bold",
3638              "editor_italic" => "Italic",
3639              "editor_underline" => "Underline",
3640              "editor_strikethrough" => "Strikethrough",
3641              "editor_subscript" => "Subscript",
3642              "editor_superscript" => "Superscript",
3643              "editor_alignleft" => "Align left",
3644              "editor_center" => "Center",
3645              "editor_alignright" => "Align right",
3646              "editor_justify" => "Justify",
3647              "editor_fontname" => "Font Name",
3648              "editor_fontsize" => "Font Size",
3649              "editor_fontcolor" => "Font Color",
3650              "editor_removeformatting" => "Remove Formatting",
3651              "editor_cut" => "Cut",
3652              "editor_cutnosupport" => "Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X",
3653              "editor_copy" => "Copy",
3654              "editor_copynosupport" => "Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C",
3655              "editor_paste" => "Paste",
3656              "editor_pastenosupport" => "Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V",
3657              "editor_pasteentertext" => "Paste your text inside the following box:",
3658              "editor_pastetext" => "PasteText",
3659              "editor_numlist" => "Numbered list",
3660              "editor_bullist" => "Bullet list",
3661              "editor_undo" => "Undo",
3662              "editor_redo" => "Redo",
3663              "editor_rows" => "Rows:",
3664              "editor_cols" => "Cols:",
3665              "editor_inserttable" => "Insert a table",
3666              "editor_inserthr" => "Insert a horizontal rule",
3667              "editor_code" => "Code",
3668              "editor_width" => "Width (optional):",
3669              "editor_height" => "Height (optional):",
3670              "editor_insertimg" => "Insert an image",
3671              "editor_email" => "E-mail:",
3672              "editor_insertemail" => "Insert an email",
3673              "editor_url" => "URL:",
3674              "editor_insertlink" => "Insert a link",
3675              "editor_unlink" => "Unlink",
3676              "editor_more" => "More",
3677              "editor_insertemoticon" => "Insert an emoticon",
3678              "editor_videourl" => "Video URL:",
3679              "editor_videotype" => "Video Type:",
3680              "editor_insert" => "Insert",
3681              "editor_insertyoutubevideo" => "Insert a YouTube video",
3682              "editor_currentdate" => "Insert current date",
3683              "editor_currenttime" => "Insert current time",
3684              "editor_print" => "Print",
3685              "editor_viewsource" => "View source",
3686              "editor_description" => "Description (optional):",
3687              "editor_enterimgurl" => "Enter the image URL:",
3688              "editor_enteremail" => "Enter the e-mail address:",
3689              "editor_enterdisplayedtext" => "Enter the displayed text:",
3690              "editor_enterurl" => "Enter URL:",
3691              "editor_enteryoutubeurl" => "Enter the YouTube video URL or ID:",
3692              "editor_insertquote" => "Insert a Quote",
3693              "editor_invalidyoutube" => "Invalid YouTube video",
3694              "editor_dailymotion" => "Dailymotion",
3695              "editor_metacafe" => "MetaCafe",
3696              "editor_mixer" => "Mixer",
3697              "editor_vimeo" => "Vimeo",
3698              "editor_youtube" => "Youtube",
3699              "editor_facebook" => "Facebook",
3700              "editor_liveleak" => "LiveLeak",
3701              "editor_insertvideo" => "Insert a video",
3702              "editor_php" => "PHP",
3703              "editor_maximize" => "Maximize"
3704          );
3705          $editor_language = "(function ($) {\n$.sceditor.locale[\"mybblang\"] = {\n";
3706  
3707          $editor_lang_strings = $plugins->run_hooks("mycode_add_codebuttons", $editor_lang_strings);
3708  
3709          $editor_languages_count = count($editor_lang_strings);
3710          $i = 0;
3711          foreach($editor_lang_strings as $lang_string => $key)
3712          {
3713              $i++;
3714              $js_lang_string = str_replace("\"", "\\\"", $key);
3715              $string = str_replace("\"", "\\\"", $lang->$lang_string);
3716              $editor_language .= "\t\"{$js_lang_string}\": \"{$string}\"";
3717  
3718              if($i < $editor_languages_count)
3719              {
3720                  $editor_language .= ",";
3721              }
3722  
3723              $editor_language .= "\n";
3724          }
3725  
3726          $editor_language .= "}})(jQuery);";
3727  
3728          if(defined("IN_ADMINCP"))
3729          {
3730              global $page;
3731              $codeinsert = $page->build_codebuttons_editor($bind, $editor_language, $smilies);
3732          }
3733          else
3734          {
3735              // Smilies
3736              $emoticon = "";
3737              $emoticons_enabled = "false";
3738              if($smilies)
3739              {
3740                  if(!$smiliecache)
3741                  {
3742                      if(!isset($smilie_cache) || !is_array($smilie_cache))
3743                      {
3744                          $smilie_cache = $cache->read("smilies");
3745                      }
3746                      foreach($smilie_cache as $smilie)
3747                      {
3748                          $smilie['image'] = str_replace("{theme}", $theme['imgdir'], $smilie['image']);
3749                          $smiliecache[$smilie['sid']] = $smilie;
3750                      }
3751                  }
3752  
3753                  if($mybb->settings['smilieinserter'] && $mybb->settings['smilieinsertercols'] && $mybb->settings['smilieinsertertot'] && !empty($smiliecache))
3754                  {
3755                      $emoticon = ",emoticon";
3756                  }
3757                  $emoticons_enabled = "true";
3758  
3759                  unset($smilie);
3760  
3761                  if(is_array($smiliecache))
3762                  {
3763                      reset($smiliecache);
3764  
3765                      $dropdownsmilies = $moresmilies = $hiddensmilies = "";
3766                      $i = 0;
3767  
3768                      foreach($smiliecache as $smilie)
3769                      {
3770                          $finds = explode("\n", $smilie['find']);
3771                          $finds_count = count($finds);
3772  
3773                          // Only show the first text to replace in the box
3774                          $smilie['find'] = $finds[0];
3775  
3776                          $find = str_replace(array('\\', '"'), array('\\\\', '\"'), htmlspecialchars_uni($smilie['find']));
3777                          $image = htmlspecialchars_uni($mybb->get_asset_url($smilie['image']));
3778                          $image = str_replace(array('\\', '"'), array('\\\\', '\"'), $image);
3779  
3780                          if(!$mybb->settings['smilieinserter'] || !$mybb->settings['smilieinsertercols'] || !$mybb->settings['smilieinsertertot'] || !$smilie['showclickable'])
3781                          {
3782                              $hiddensmilies .= '"'.$find.'": "'.$image.'",';
3783                          }
3784                          elseif($i < $mybb->settings['smilieinsertertot'])
3785                          {
3786                              $dropdownsmilies .= '"'.$find.'": "'.$image.'",';
3787                              ++$i;
3788                          }
3789                          else
3790                          {
3791                              $moresmilies .= '"'.$find.'": "'.$image.'",';
3792                          }
3793  
3794                          for($j = 1; $j < $finds_count; ++$j)
3795                          {
3796                              $find = str_replace(array('\\', '"'), array('\\\\', '\"'), htmlspecialchars_uni($finds[$j]));
3797                              $hiddensmilies .= '"'.$find.'": "'.$image.'",';
3798                          }
3799                      }
3800                  }
3801              }
3802  
3803              $basic1 = $basic2 = $align = $font = $size = $color = $removeformat = $email = $link = $list = $code = $sourcemode = "";
3804  
3805              if($mybb->settings['allowbasicmycode'] == 1)
3806              {
3807                  $basic1 = "bold,italic,underline,strike|";
3808                  $basic2 = "horizontalrule,";
3809              }
3810  
3811              if($mybb->settings['allowalignmycode'] == 1)
3812              {
3813                  $align = "left,center,right,justify|";
3814              }
3815  
3816              if($mybb->settings['allowfontmycode'] == 1)
3817              {
3818                  $font = "font,";
3819              }
3820  
3821              if($mybb->settings['allowsizemycode'] == 1)
3822              {
3823                  $size = "size,";
3824              }
3825  
3826              if($mybb->settings['allowcolormycode'] == 1)
3827              {
3828                  $color = "color,";
3829              }
3830  
3831              if($mybb->settings['allowfontmycode'] == 1 || $mybb->settings['allowsizemycode'] == 1 || $mybb->settings['allowcolormycode'] == 1)
3832              {
3833                  $removeformat = "removeformat|";
3834              }
3835  
3836              if($mybb->settings['allowemailmycode'] == 1)
3837              {
3838                  $email = "email,";
3839              }
3840  
3841              if($mybb->settings['allowlinkmycode'] == 1)
3842              {
3843                  $link = "link,unlink";
3844              }
3845  
3846              if($mybb->settings['allowlistmycode'] == 1)
3847              {
3848                  $list = "bulletlist,orderedlist|";
3849              }
3850  
3851              if($mybb->settings['allowcodemycode'] == 1)
3852              {
3853                  $code = "code,php,";
3854              }
3855  
3856              if($mybb->user['sourceeditor'] == 1)
3857              {
3858                  $sourcemode = "MyBBEditor.sourceMode(true);";
3859              }
3860  
3861              eval("\$codeinsert = \"".$templates->get("codebuttons")."\";");
3862          }
3863      }
3864  
3865      return $codeinsert;
3866  }
3867  
3868  /**
3869   * @param int $tid
3870   * @param array $postoptions The options carried with form submit
3871   *
3872   * @return string Predefined / updated subscription method of the thread for the user
3873   */
3874  function get_subscription_method($tid = 0, $postoptions = array())
3875  {
3876      global $mybb;
3877  
3878      $subscription_methods = array('', 'none', 'email', 'pm'); // Define methods
3879      $subscription_method = (int)$mybb->user['subscriptionmethod']; // Set user default
3880  
3881      // If no user default method available then reset method
3882      if(!$subscription_method)
3883      {
3884          $subscription_method = 0;
3885      }
3886  
3887      // Return user default if no thread id available, in case
3888      if(!(int)$tid || (int)$tid <= 0)
3889      {
3890          return $subscription_methods[$subscription_method];
3891      }
3892  
3893      // If method not predefined set using data from database
3894      if(isset($postoptions['subscriptionmethod']))
3895      {
3896          $method = trim($postoptions['subscriptionmethod']);
3897          return (in_array($method, $subscription_methods)) ? $method : $subscription_methods[0];
3898      }
3899      else
3900      {
3901          global $db;
3902  
3903          $query = $db->simple_select("threadsubscriptions", "tid, notification", "tid='".(int)$tid."' AND uid='".$mybb->user['uid']."'", array('limit' => 1));
3904          $subscription = $db->fetch_array($query);
3905  
3906          if(!empty($subscription) && $subscription['tid'])
3907          {
3908              $subscription_method = (int)$subscription['notification'] + 1;
3909          }
3910      }
3911  
3912      return $subscription_methods[$subscription_method];
3913  }
3914  
3915  /**
3916   * Build the javascript clickable smilie inserter
3917   *
3918   * @return string The clickable smilies list
3919   */
3920  function build_clickable_smilies()
3921  {
3922      global $cache, $smiliecache, $theme, $templates, $lang, $mybb, $smiliecount;
3923  
3924      if($mybb->settings['smilieinserter'] != 0 && $mybb->settings['smilieinsertercols'] && $mybb->settings['smilieinsertertot'])
3925      {
3926          if(!$smiliecount)
3927          {
3928              $smilie_cache = $cache->read("smilies");
3929              $smiliecount = count($smilie_cache);
3930          }
3931  
3932          if(!$smiliecache)
3933          {
3934              if(!is_array($smilie_cache))
3935              {
3936                  $smilie_cache = $cache->read("smilies");
3937              }
3938              foreach($smilie_cache as $smilie)
3939              {
3940                  $smilie['image'] = str_replace("{theme}", $theme['imgdir'], $smilie['image']);
3941                  $smiliecache[$smilie['sid']] = $smilie;
3942              }
3943          }
3944  
3945          unset($smilie);
3946  
3947          if(is_array($smiliecache))
3948          {
3949              reset($smiliecache);
3950  
3951              $getmore = '';
3952              if($mybb->settings['smilieinsertertot'] >= $smiliecount)
3953              {
3954                  $mybb->settings['smilieinsertertot'] = $smiliecount;
3955              }
3956              else if($mybb->settings['smilieinsertertot'] < $smiliecount)
3957              {
3958                  $smiliecount = $mybb->settings['smilieinsertertot'];
3959                  eval("\$getmore = \"".$templates->get("smilieinsert_getmore")."\";");
3960              }
3961  
3962              $smilies = $smilie_icons = '';
3963              $counter = 0;
3964              $i = 0;
3965  
3966              $extra_class = '';
3967              foreach($smiliecache as $smilie)
3968              {
3969                  if($i < $mybb->settings['smilieinsertertot'] && $smilie['showclickable'] != 0)
3970                  {
3971                      $smilie['image'] = str_replace("{theme}", $theme['imgdir'], $smilie['image']);
3972                      $smilie['image'] = htmlspecialchars_uni($mybb->get_asset_url($smilie['image']));
3973                      $smilie['name'] = htmlspecialchars_uni($smilie['name']);
3974  
3975                      // Only show the first text to replace in the box
3976                      $temp = explode("\n", $smilie['find']); // assign to temporary variable for php 5.3 compatibility
3977                      $smilie['find'] = $temp[0];
3978  
3979                      $find = str_replace(array('\\', "'"), array('\\\\', "\'"), htmlspecialchars_uni($smilie['find']));
3980  
3981                      $onclick = " onclick=\"MyBBEditor.insertText(' $find ');\"";
3982                      $extra_class = ' smilie_pointer';
3983                      eval('$smilie = "'.$templates->get('smilie', 1, 0).'";');
3984                      eval("\$smilie_icons .= \"".$templates->get("smilieinsert_smilie")."\";");
3985                      ++$i;
3986                      ++$counter;
3987  
3988                      if($counter == $mybb->settings['smilieinsertercols'])
3989                      {
3990                          $counter = 0;
3991                          eval("\$smilies .= \"".$templates->get("smilieinsert_row")."\";");
3992                          $smilie_icons = '';
3993                      }
3994                  }
3995              }
3996  
3997              if($counter != 0)
3998              {
3999                  $colspan = $mybb->settings['smilieinsertercols'] - $counter;
4000                  eval("\$smilies .= \"".$templates->get("smilieinsert_row_empty")."\";");
4001              }
4002  
4003              eval("\$clickablesmilies = \"".$templates->get("smilieinsert")."\";");
4004          }
4005          else
4006          {
4007              $clickablesmilies = "";
4008          }
4009      }
4010      else
4011      {
4012          $clickablesmilies = "";
4013      }
4014  
4015      return $clickablesmilies;
4016  }
4017  
4018  /**
4019   * Builds thread prefixes and returns a selected prefix (or all)
4020   *
4021   *  @param int $pid The prefix ID (0 to return all)
4022   *  @return array The thread prefix's values (or all thread prefixes)
4023   */
4024  function build_prefixes($pid=0)
4025  {
4026      global $cache;
4027      static $prefixes_cache;
4028  
4029      if(is_array($prefixes_cache))
4030      {
4031          if($pid > 0 && is_array($prefixes_cache[$pid]))
4032          {
4033              return $prefixes_cache[$pid];
4034          }
4035  
4036          return $prefixes_cache;
4037      }
4038  
4039      $prefix_cache = $cache->read("threadprefixes");
4040  
4041      if(!is_array($prefix_cache))
4042      {
4043          // No cache
4044          $prefix_cache = $cache->read("threadprefixes", true);
4045  
4046          if(!is_array($prefix_cache))
4047          {
4048              return array();
4049          }
4050      }
4051  
4052      $prefixes_cache = array();
4053      foreach($prefix_cache as $prefix)
4054      {
4055          $prefixes_cache[$prefix['pid']] = $prefix;
4056      }
4057  
4058      if($pid != 0 && is_array($prefixes_cache[$pid]))
4059      {
4060          return $prefixes_cache[$pid];
4061      }
4062      else if(!empty($prefixes_cache))
4063      {
4064          return $prefixes_cache;
4065      }
4066  
4067      return false;
4068  }
4069  
4070  /**
4071   * Build the thread prefix selection menu for the current user
4072   *
4073   *  @param int|string $fid The forum ID (integer ID or string all)
4074   *  @param int|string $selected_pid The selected prefix ID (integer ID or string any)
4075   *  @param int $multiple Allow multiple prefix selection
4076   *  @param int $previous_pid The previously selected prefix ID
4077   *  @return string The thread prefix selection menu
4078   */
4079  function build_prefix_select($fid, $selected_pid=0, $multiple=0, $previous_pid=0)
4080  {
4081      global $cache, $db, $lang, $mybb, $templates;
4082  
4083      if($fid != 'all')
4084      {
4085          $fid = (int)$fid;
4086      }
4087  
4088      $prefix_cache = build_prefixes(0);
4089      if(empty($prefix_cache))
4090      {
4091          // We've got no prefixes to show
4092          return '';
4093      }
4094  
4095      // Go through each of our prefixes and decide which ones we can use
4096      $prefixes = array();
4097      foreach($prefix_cache as $prefix)
4098      {
4099          if($fid != "all" && $prefix['forums'] != "-1")
4100          {
4101              // Decide whether this prefix can be used in our forum
4102              $forums = explode(",", $prefix['forums']);
4103  
4104              if(!in_array($fid, $forums) && $prefix['pid'] != $previous_pid)
4105              {
4106                  // This prefix is not in our forum list
4107                  continue;
4108              }
4109          }
4110  
4111          if(is_member($prefix['groups']) || $prefix['pid'] == $previous_pid)
4112          {
4113              // The current user can use this prefix
4114              $prefixes[$prefix['pid']] = $prefix;
4115          }
4116      }
4117  
4118      if(empty($prefixes))
4119      {
4120          return '';
4121      }
4122  
4123      $prefixselect = $prefixselect_prefix = '';
4124  
4125      if($multiple == 1)
4126      {
4127          $any_selected = "";
4128          if($selected_pid == 'any')
4129          {
4130              $any_selected = " selected=\"selected\"";
4131          }
4132      }
4133  
4134      $default_selected = "";
4135      if(((int)$selected_pid == 0) && $selected_pid != 'any')
4136      {
4137          $default_selected = " selected=\"selected\"";
4138      }
4139  
4140      foreach($prefixes as $prefix)
4141      {
4142          $selected = "";
4143          if($prefix['pid'] == $selected_pid)
4144          {
4145              $selected = " selected=\"selected\"";
4146          }
4147  
4148          $prefix['prefix'] = htmlspecialchars_uni($prefix['prefix']);
4149          eval("\$prefixselect_prefix .= \"".$templates->get("post_prefixselect_prefix")."\";");
4150      }
4151  
4152      if($multiple != 0)
4153      {
4154          eval("\$prefixselect = \"".$templates->get("post_prefixselect_multiple")."\";");
4155      }
4156      else
4157      {
4158          eval("\$prefixselect = \"".$templates->get("post_prefixselect_single")."\";");
4159      }
4160  
4161      return $prefixselect;
4162  }
4163  
4164  /**
4165   * Build the thread prefix selection menu for a forum without group permission checks
4166   *
4167   *  @param int $fid The forum ID (integer ID)
4168   *  @param int $selected_pid The selected prefix ID (integer ID)
4169   *  @return string The thread prefix selection menu
4170   */
4171  function build_forum_prefix_select($fid, $selected_pid=0)
4172  {
4173      global $cache, $db, $lang, $mybb, $templates;
4174  
4175      $fid = (int)$fid;
4176  
4177      $prefix_cache = build_prefixes(0);
4178      if(empty($prefix_cache))
4179      {
4180          // We've got no prefixes to show
4181          return '';
4182      }
4183  
4184      // Go through each of our prefixes and decide which ones we can use
4185      $prefixes = array();
4186      foreach($prefix_cache as $prefix)
4187      {
4188          if($prefix['forums'] != "-1")
4189          {
4190              // Decide whether this prefix can be used in our forum
4191              $forums = explode(",", $prefix['forums']);
4192  
4193              if(in_array($fid, $forums))
4194              {
4195                  // This forum can use this prefix!
4196                  $prefixes[$prefix['pid']] = $prefix;
4197              }
4198          }
4199          else
4200          {
4201              // This prefix is for anybody to use...
4202              $prefixes[$prefix['pid']] = $prefix;
4203          }
4204      }
4205  
4206      if(empty($prefixes))
4207      {
4208          return '';
4209      }
4210  
4211      $default_selected = array('all' => '', 'none' => '', 'any' => '');
4212      $selected_pid = (int)$selected_pid;
4213  
4214      if($selected_pid == 0)
4215      {
4216          $default_selected['all'] = ' selected="selected"';
4217      }
4218      else if($selected_pid == -1)
4219      {
4220          $default_selected['none'] = ' selected="selected"';
4221      }
4222      else if($selected_pid == -2)
4223      {
4224          $default_selected['any'] = ' selected="selected"';
4225      }
4226  
4227      $prefixselect_prefix = '';
4228      foreach($prefixes as $prefix)
4229      {
4230          $selected = '';
4231          if($prefix['pid'] == $selected_pid)
4232          {
4233              $selected = ' selected="selected"';
4234          }
4235  
4236          $prefix['prefix'] = htmlspecialchars_uni($prefix['prefix']);
4237          eval('$prefixselect_prefix .= "'.$templates->get("forumdisplay_threadlist_prefixes_prefix").'";');
4238      }
4239  
4240      eval('$prefixselect = "'.$templates->get("forumdisplay_threadlist_prefixes").'";');
4241      return $prefixselect;
4242  }
4243  
4244  /**
4245   * Gzip encodes text to a specified level
4246   *
4247   * @param string $contents The string to encode
4248   * @param int $level The level (1-9) to encode at
4249   * @return string The encoded string
4250   */
4251  function gzip_encode($contents, $level=1)
4252  {
4253      if(function_exists("gzcompress") && function_exists("crc32") && !headers_sent() && !(ini_get('output_buffering') && my_strpos(' '.ini_get('output_handler'), 'ob_gzhandler')))
4254      {
4255          $httpaccept_encoding = '';
4256  
4257          if(isset($_SERVER['HTTP_ACCEPT_ENCODING']))
4258          {
4259              $httpaccept_encoding = $_SERVER['HTTP_ACCEPT_ENCODING'];
4260          }
4261  
4262          if(my_strpos(" ".$httpaccept_encoding, "x-gzip"))
4263          {
4264              $encoding = "x-gzip";
4265          }
4266  
4267          if(my_strpos(" ".$httpaccept_encoding, "gzip"))
4268          {
4269              $encoding = "gzip";
4270          }
4271  
4272          if(isset($encoding))
4273          {
4274              header("Content-Encoding: $encoding");
4275  
4276              if(function_exists("gzencode"))
4277              {
4278                  $contents = gzencode($contents, $level);
4279              }
4280              else
4281              {
4282                  $size = strlen($contents);
4283                  $crc = crc32($contents);
4284                  $gzdata = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff";
4285                  $gzdata .= my_substr(gzcompress($contents, $level), 2, -4);
4286                  $gzdata .= pack("V", $crc);
4287                  $gzdata .= pack("V", $size);
4288                  $contents = $gzdata;
4289              }
4290          }
4291      }
4292  
4293      return $contents;
4294  }
4295  
4296  /**
4297   * Log the actions of a moderator.
4298   *
4299   * @param array $data The data of the moderator's action.
4300   * @param string $action The message to enter for the action the moderator performed.
4301   */
4302  function log_moderator_action($data, $action="")
4303  {
4304      global $mybb, $db, $session;
4305  
4306      $fid = 0;
4307      if(isset($data['fid']))
4308      {
4309          $fid = (int)$data['fid'];
4310          unset($data['fid']);
4311      }
4312  
4313      $tid = 0;
4314      if(isset($data['tid']))
4315      {
4316          $tid = (int)$data['tid'];
4317          unset($data['tid']);
4318      }
4319  
4320      $pid = 0;
4321      if(isset($data['pid']))
4322      {
4323          $pid = (int)$data['pid'];
4324          unset($data['pid']);
4325      }
4326  
4327      $tids = array();
4328      if(isset($data['tids']))
4329      {
4330          $tids = (array)$data['tids'];
4331          unset($data['tids']);
4332      }
4333  
4334      // Any remaining extra data - we my_serialize and insert in to its own column
4335      if(is_array($data))
4336      {
4337          $data = my_serialize($data);
4338      }
4339  
4340      $sql_array = array(
4341          "uid" => (int)$mybb->user['uid'],
4342          "dateline" => TIME_NOW,
4343          "fid" => (int)$fid,
4344          "tid" => $tid,
4345          "pid" => $pid,
4346          "action" => $db->escape_string($action),
4347          "data" => $db->escape_string($data),
4348          "ipaddress" => $db->escape_binary($session->packedip)
4349      );
4350  
4351      if($tids)
4352      {
4353          $multiple_sql_array = array();
4354  
4355          foreach($tids as $tid)
4356          {
4357              $sql_array['tid'] = (int)$tid;
4358              $multiple_sql_array[] = $sql_array;
4359          }
4360  
4361          $db->insert_query_multiple("moderatorlog", $multiple_sql_array);
4362      }
4363      else
4364      {
4365          $db->insert_query("moderatorlog", $sql_array);
4366      }
4367  }
4368  
4369  /**
4370   * Get the formatted reputation for a user.
4371   *
4372   * @param int $reputation The reputation value
4373   * @param int $uid The user ID (if not specified, the generated reputation will not be a link)
4374   * @return string The formatted repuation
4375   */
4376  function get_reputation($reputation, $uid=0)
4377  {
4378      global $theme, $templates;
4379  
4380      $display_reputation = $reputation_class = '';
4381      if($reputation < 0)
4382      {
4383          $reputation_class = "reputation_negative";
4384      }
4385      elseif($reputation > 0)
4386      {
4387          $reputation_class = "reputation_positive";
4388      }
4389      else
4390      {
4391          $reputation_class = "reputation_neutral";
4392      }
4393  
4394      $reputation = my_number_format($reputation);
4395  
4396      if($uid != 0)
4397      {
4398          eval("\$display_reputation = \"".$templates->get("postbit_reputation_formatted_link")."\";");
4399      }
4400      else
4401      {
4402          eval("\$display_reputation = \"".$templates->get("postbit_reputation_formatted")."\";");
4403      }
4404  
4405      return $display_reputation;
4406  }
4407  
4408  /**
4409   * Fetch a color coded version of a warning level (based on it's percentage)
4410   *
4411   * @param int $level The warning level (percentage of 100)
4412   * @return string Formatted warning level
4413   */
4414  function get_colored_warning_level($level)
4415  {
4416      global $templates;
4417  
4418      $warning_class = '';
4419      if($level >= 80)
4420      {
4421          $warning_class = "high_warning";
4422      }
4423      else if($level >= 50)
4424      {
4425          $warning_class = "moderate_warning";
4426      }
4427      else if($level >= 25)
4428      {
4429          $warning_class = "low_warning";
4430      }
4431      else
4432      {
4433          $warning_class = "normal_warning";
4434      }
4435  
4436      eval("\$level = \"".$templates->get("postbit_warninglevel_formatted")."\";");
4437      return $level;
4438  }
4439  
4440  /**
4441   * Fetch the IP address of the current user.
4442   *
4443   * @return string The IP address.
4444   */
4445  function get_ip()
4446  {
4447      global $mybb, $plugins;
4448  
4449      $ip = strtolower($_SERVER['REMOTE_ADDR']);
4450  
4451      if($mybb->settings['ip_forwarded_check'])
4452      {
4453          $addresses = array();
4454  
4455          if(isset($_SERVER['HTTP_X_FORWARDED_FOR']))
4456          {
4457              $addresses = explode(',', strtolower($_SERVER['HTTP_X_FORWARDED_FOR']));
4458          }
4459          elseif(isset($_SERVER['HTTP_X_REAL_IP']))
4460          {
4461              $addresses = explode(',', strtolower($_SERVER['HTTP_X_REAL_IP']));
4462          }
4463  
4464          if(is_array($addresses))
4465          {
4466              foreach($addresses as $val)
4467              {
4468                  $val = trim($val);
4469                  // Validate IP address and exclude private addresses
4470                  if(my_inet_ntop(my_inet_pton($val)) == $val && !preg_match("#^(10\.|172\.(1[6-9]|2[0-9]|3[0-1])\.|192\.168\.|fe80:|fe[c-f][0-f]:|f[c-d][0-f]{2}:)#", $val))
4471                  {
4472                      $ip = $val;
4473                      break;
4474                  }
4475              }
4476          }
4477      }
4478  
4479      if(!$ip)
4480      {
4481          if(isset($_SERVER['HTTP_CLIENT_IP']))
4482          {
4483              $ip = strtolower($_SERVER['HTTP_CLIENT_IP']);
4484          }
4485      }
4486  
4487      if($plugins)
4488      {
4489          $ip_array = array("ip" => &$ip); // Used for backwards compatibility on this hook with the updated run_hooks() function.
4490          $plugins->run_hooks("get_ip", $ip_array);
4491      }
4492  
4493      return $ip;
4494  }
4495  
4496  /**
4497   * Fetch the friendly size (GB, MB, KB, B) for a specified file size.
4498   *
4499   * @param int $size The size in bytes
4500   * @return string The friendly file size
4501   */
4502  function get_friendly_size($size)
4503  {
4504      global $lang;
4505  
4506      if(!is_numeric($size))
4507      {
4508          return $lang->na;
4509      }
4510  
4511      // Yottabyte (1024 Zettabytes)
4512      if($size >= 1208925819614629174706176)
4513      {
4514          $size = my_number_format(round(($size / 1208925819614629174706176), 2))." ".$lang->size_yb;
4515      }
4516      // Zetabyte (1024 Exabytes)
4517      elseif($size >= 1180591620717411303424)
4518      {
4519          $size = my_number_format(round(($size / 1180591620717411303424), 2))." ".$lang->size_zb;
4520      }
4521      // Exabyte (1024 Petabytes)
4522      elseif($size >= 1152921504606846976)
4523      {
4524          $size = my_number_format(round(($size / 1152921504606846976), 2))." ".$lang->size_eb;
4525      }
4526      // Petabyte (1024 Terabytes)
4527      elseif($size >= 1125899906842624)
4528      {
4529          $size = my_number_format(round(($size / 1125899906842624), 2))." ".$lang->size_pb;
4530      }
4531      // Terabyte (1024 Gigabytes)
4532      elseif($size >= 1099511627776)
4533      {
4534          $size = my_number_format(round(($size / 1099511627776), 2))." ".$lang->size_tb;
4535      }
4536      // Gigabyte (1024 Megabytes)
4537      elseif($size >= 1073741824)
4538      {
4539          $size = my_number_format(round(($size / 1073741824), 2))." ".$lang->size_gb;
4540      }
4541      // Megabyte (1024 Kilobytes)
4542      elseif($size >= 1048576)
4543      {
4544          $size = my_number_format(round(($size / 1048576), 2))." ".$lang->size_mb;
4545      }
4546      // Kilobyte (1024 bytes)
4547      elseif($size >= 1024)
4548      {
4549          $size = my_number_format(round(($size / 1024), 2))." ".$lang->size_kb;
4550      }
4551      elseif($size == 0)
4552      {
4553          $size = "0 ".$lang->size_bytes;
4554      }
4555      else
4556      {
4557          $size = my_number_format($size)." ".$lang->size_bytes;
4558      }
4559  
4560      return $size;
4561  }
4562  
4563  /**
4564   * Format a decimal number in to microseconds, milliseconds, or seconds.
4565   *
4566   * @param int $time The time in microseconds
4567   * @return string The friendly time duration
4568   */
4569  function format_time_duration($time)
4570  {
4571      global $lang;
4572  
4573      if(!is_numeric($time))
4574      {
4575          return $lang->na;
4576      }
4577  
4578      if(round(1000000 * $time, 2) < 1000)
4579      {
4580          $time = number_format(round(1000000 * $time, 2))." μs";
4581      }
4582      elseif(round(1000000 * $time, 2) >= 1000 && round(1000000 * $time, 2) < 1000000)
4583      {
4584          $time = number_format(round((1000 * $time), 2))." ms";
4585      }
4586      else
4587      {
4588          $time = round($time, 3)." seconds";
4589      }
4590  
4591      return $time;
4592  }
4593  
4594  /**
4595   * Get the attachment icon for a specific file extension
4596   *
4597   * @param string $ext The file extension
4598   * @return string The attachment icon
4599   */
4600  function get_attachment_icon($ext)
4601  {
4602      global $cache, $attachtypes, $theme, $templates, $lang, $mybb;
4603  
4604      if(!$attachtypes)
4605      {
4606          $attachtypes = $cache->read("attachtypes");
4607      }
4608  
4609      $ext = my_strtolower($ext);
4610  
4611      if($attachtypes[$ext]['icon'])
4612      {
4613          static $attach_icons_schemes = array();
4614          if(!isset($attach_icons_schemes[$ext]))
4615          {
4616              $attach_icons_schemes[$ext] = parse_url($attachtypes[$ext]['icon']);
4617              if(!empty($attach_icons_schemes[$ext]['scheme']))
4618              {
4619                  $attach_icons_schemes[$ext] = $attachtypes[$ext]['icon'];
4620              }
4621              elseif(defined("IN_ADMINCP"))
4622              {
4623                  $attach_icons_schemes[$ext] = str_replace("{theme}", "", $attachtypes[$ext]['icon']);
4624                  if(my_substr($attach_icons_schemes[$ext], 0, 1) != "/")
4625                  {
4626                      $attach_icons_schemes[$ext] = "../".