[ Index ]

PHP Cross Reference of MyBB 1.8.32

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