[ Index ]

PHP Cross Reference of MyBB 1.8.27

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->settings['mail_parameters']))
 585                  {
 586                      $my_mailhandler_builtin->additional_parameters = $mybb->settings['mail_parameters'];
 587                  }
 588              }
 589          }
 590  
 591          $plugins->run_hooks('my_mailhandler_builtin_after_init', $my_mailhandler_builtin);
 592  
 593          return $my_mailhandler_builtin;
 594      }
 595  
 596      // If our mail handler doesn't exist, create it.
 597      if(!is_object($my_mailhandler))
 598      {
 599          require_once  MYBB_ROOT . "inc/class_mailhandler.php";
 600  
 601          $plugins->run_hooks('my_mailhandler_init', $my_mailhandler);
 602  
 603          // If no plugin has ever created the mail handler, resort to use the built-in one.
 604          if(!is_object($my_mailhandler) || !($my_mailhandler instanceof MailHandler))
 605          {
 606              $my_mailhandler = &get_my_mailhandler(true);
 607          }
 608      }
 609  
 610      return $my_mailhandler;
 611  }
 612  
 613  /**
 614   * Sends an email using PHP's mail function, formatting it appropriately.
 615   *
 616   * @param string $to Address the email should be addressed to.
 617   * @param string $subject The subject of the email being sent.
 618   * @param string $message The message being sent.
 619   * @param string $from The from address of the email, if blank, the board name will be used.
 620   * @param string $charset The chracter set being used to send this email.
 621   * @param string $headers
 622   * @param boolean $keep_alive Do we wish to keep the connection to the mail server alive to send more than one message (SMTP only)
 623   * @param string $format The format of the email to be sent (text or html). text is default
 624   * @param string $message_text The text message of the email if being sent in html format, for email clients that don't support html
 625   * @param string $return_email The email address to return to. Defaults to admin return email address.
 626   * @return bool True if the mail is sent, false otherwise.
 627   */
 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              // If our permissions arn't inherited we need to figure them out
1698              if(empty($fpermcache[$fid][$gid]))
1699              {
1700                  $parents = explode(',', $forum_cache[$fid]['parentlist']);
1701                  rsort($parents);
1702                  if(!empty($parents))
1703                  {
1704                      foreach($parents as $parent_id)
1705                      {
1706                          if(!empty($fpermcache[$parent_id][$gid]))
1707                          {
1708                              $level_permissions = $fpermcache[$parent_id][$gid];
1709                              break;
1710                          }
1711                      }
1712                  }
1713              }
1714              else
1715              {
1716                  $level_permissions = $fpermcache[$fid][$gid];
1717              }
1718  
1719              // If we STILL don't have forum permissions we use the usergroup itself
1720              if(empty($level_permissions))
1721              {
1722                  $level_permissions = $groupscache[$gid];
1723              }
1724  
1725              foreach($level_permissions as $permission => $access)
1726              {
1727                  if(empty($current_permissions[$permission]) || $access >= $current_permissions[$permission] || ($access == "yes" && $current_permissions[$permission] == "no"))
1728                  {
1729                      $current_permissions[$permission] = $access;
1730                  }
1731              }
1732  
1733              if($level_permissions["canview"] && empty($level_permissions["canonlyviewownthreads"]))
1734              {
1735                  $only_view_own_threads = 0;
1736              }
1737  
1738              if($level_permissions["canpostreplys"] && empty($level_permissions["canonlyreplyownthreads"]))
1739              {
1740                  $only_reply_own_threads = 0;
1741              }
1742          }
1743      }
1744  
1745      // Figure out if we can view more than our own threads
1746      if($only_view_own_threads == 0)
1747      {
1748          $current_permissions["canonlyviewownthreads"] = 0;
1749      }
1750  
1751      // Figure out if we can reply more than our own threads
1752      if($only_reply_own_threads == 0)
1753      {
1754          $current_permissions["canonlyreplyownthreads"] = 0;
1755      }
1756  
1757      if(count($current_permissions) == 0)
1758      {
1759          $current_permissions = $groupperms;
1760      }
1761      return $current_permissions;
1762  }
1763  
1764  /**
1765   * Check whether password for given forum was validated for the current user
1766   *
1767   * @param array $forum The forum data
1768   * @param bool $ignore_empty Whether to treat forum password configured as an empty string as validated
1769   * @param bool $check_parents Whether to check parent forums using `parentlist`
1770   * @return bool
1771   */
1772  function forum_password_validated($forum, $ignore_empty=false, $check_parents=false)
1773  {
1774      global $mybb, $forum_cache;
1775  
1776      if($check_parents && isset($forum['parentlist']))
1777      {
1778          if(!is_array($forum_cache))
1779          {
1780              $forum_cache = cache_forums();
1781              if(!$forum_cache)
1782              {
1783                  return false;
1784              }
1785          }
1786  
1787          $parents = explode(',', $forum['parentlist']);
1788          rsort($parents);
1789  
1790          foreach($parents as $parent_id)
1791          {
1792              if($parent_id != $forum['fid'] && !forum_password_validated($forum_cache[$parent_id], true))
1793              {
1794                  return false;
1795              }
1796          }
1797      }
1798  
1799      return ($ignore_empty && $forum['password'] === '') || (
1800          isset($mybb->cookies['forumpass'][$forum['fid']]) &&
1801          my_hash_equals(
1802              md5($mybb->user['uid'].$forum['password']),
1803              $mybb->cookies['forumpass'][$forum['fid']]
1804          )
1805      );
1806  }
1807  
1808  /**
1809   * Check the password given on a certain forum for validity
1810   *
1811   * @param int $fid The forum ID
1812   * @param int $pid The Parent ID
1813   * @param bool $return
1814   * @return bool
1815   */
1816  function check_forum_password($fid, $pid=0, $return=false)
1817  {
1818      global $mybb, $header, $footer, $headerinclude, $theme, $templates, $lang, $forum_cache;
1819  
1820      $showform = true;
1821  
1822      if(!is_array($forum_cache))
1823      {
1824          $forum_cache = cache_forums();
1825          if(!$forum_cache)
1826          {
1827              return false;
1828          }
1829      }
1830  
1831      // Loop through each of parent forums to ensure we have a password for them too
1832      if(isset($forum_cache[$fid]['parentlist']))
1833      {
1834          $parents = explode(',', $forum_cache[$fid]['parentlist']);
1835          rsort($parents);
1836      }
1837      if(!empty($parents))
1838      {
1839          foreach($parents as $parent_id)
1840          {
1841              if($parent_id == $fid || $parent_id == $pid)
1842              {
1843                  continue;
1844              }
1845  
1846              if($forum_cache[$parent_id]['password'] !== "")
1847              {
1848                  check_forum_password($parent_id, $fid);
1849              }
1850          }
1851      }
1852  
1853      if($forum_cache[$fid]['password'] !== '')
1854      {
1855          if(isset($mybb->input['pwverify']) && $pid == 0)
1856          {
1857              if(my_hash_equals($forum_cache[$fid]['password'], $mybb->get_input('pwverify')))
1858              {
1859                  my_setcookie("forumpass[$fid]", md5($mybb->user['uid'].$mybb->get_input('pwverify')), null, true);
1860                  $showform = false;
1861              }
1862              else
1863              {
1864                  eval("\$pwnote = \"".$templates->get("forumdisplay_password_wrongpass")."\";");
1865                  $showform = true;
1866              }
1867          }
1868          else
1869          {
1870              if(!forum_password_validated($forum_cache[$fid]))
1871              {
1872                  $showform = true;
1873              }
1874              else
1875              {
1876                  $showform = false;
1877              }
1878          }
1879      }
1880      else
1881      {
1882          $showform = false;
1883      }
1884  
1885      if($return)
1886      {
1887          return $showform;
1888      }
1889  
1890      if($showform)
1891      {
1892          if($pid)
1893          {
1894              header("Location: ".$mybb->settings['bburl']."/".get_forum_link($fid));
1895          }
1896          else
1897          {
1898              $_SERVER['REQUEST_URI'] = htmlspecialchars_uni($_SERVER['REQUEST_URI']);
1899              eval("\$pwform = \"".$templates->get("forumdisplay_password")."\";");
1900              output_page($pwform);
1901          }
1902          exit;
1903      }
1904  }
1905  
1906  /**
1907   * Return the permissions for a moderator in a specific forum
1908   *
1909   * @param int $fid The forum ID
1910   * @param int $uid The user ID to fetch permissions for (0 assumes current logged in user)
1911   * @param string $parentslist The parent list for the forum (if blank, will be fetched)
1912   * @return array Array of moderator permissions for the specific forum
1913   */
1914  function get_moderator_permissions($fid, $uid=0, $parentslist="")
1915  {
1916      global $mybb, $cache, $db;
1917      static $modpermscache;
1918  
1919      if($uid < 1)
1920      {
1921          $uid = $mybb->user['uid'];
1922      }
1923  
1924      if($uid == 0)
1925      {
1926          return false;
1927      }
1928  
1929      if(isset($modpermscache[$fid][$uid]))
1930      {
1931          return $modpermscache[$fid][$uid];
1932      }
1933  
1934      if(!$parentslist)
1935      {
1936          $parentslist = explode(',', get_parent_list($fid));
1937      }
1938  
1939      // Get user groups
1940      $perms = array();
1941      $user = get_user($uid);
1942  
1943      $groups = array($user['usergroup']);
1944  
1945      if(!empty($user['additionalgroups']))
1946      {
1947          $extra_groups = explode(",", $user['additionalgroups']);
1948  
1949          foreach($extra_groups as $extra_group)
1950          {
1951              $groups[] = $extra_group;
1952          }
1953      }
1954  
1955      $mod_cache = $cache->read("moderators");
1956  
1957      foreach($mod_cache as $forumid => $forum)
1958      {
1959          if(empty($forum) || !is_array($forum) || !in_array($forumid, $parentslist))
1960          {
1961              // No perms or we're not after this forum
1962              continue;
1963          }
1964  
1965          // User settings override usergroup settings
1966          if(!empty($forum['users'][$uid]))
1967          {
1968              $perm = $forum['users'][$uid];
1969              foreach($perm as $action => $value)
1970              {
1971                  if(strpos($action, "can") === false)
1972                  {
1973                      continue;
1974                  }
1975  
1976                  // Figure out the user permissions
1977                  if($value == 0)
1978                  {
1979                      // The user doesn't have permission to set this action
1980                      $perms[$action] = 0;
1981                  }
1982                  else
1983                  {
1984                      $perms[$action] = max($perm[$action], $perms[$action]);
1985                  }
1986              }
1987          }
1988  
1989          foreach($groups as $group)
1990          {
1991              if(empty($forum['usergroups'][$group]) || !is_array($forum['usergroups'][$group]))
1992              {
1993                  // There are no permissions set for this group
1994                  continue;
1995              }
1996  
1997              $perm = $forum['usergroups'][$group];
1998              foreach($perm as $action => $value)
1999              {
2000                  if(strpos($action, "can") === false)
2001                  {
2002                      continue;
2003                  }
2004  
2005                  $perms[$action] = max($perm[$action], $perms[$action]);
2006              }
2007          }
2008      }
2009  
2010      $modpermscache[$fid][$uid] = $perms;
2011  
2012      return $perms;
2013  }
2014  
2015  /**
2016   * Checks if a moderator has permissions to perform an action in a specific forum
2017   *
2018   * @param int $fid The forum ID (0 assumes global)
2019   * @param string $action The action tyring to be performed. (blank assumes any action at all)
2020   * @param int $uid The user ID (0 assumes current user)
2021   * @return bool Returns true if the user has permission, false if they do not
2022   */
2023  function is_moderator($fid=0, $action="", $uid=0)
2024  {
2025      global $mybb, $cache, $plugins;
2026  
2027      if($uid == 0)
2028      {
2029          $uid = $mybb->user['uid'];
2030      }
2031  
2032      if($uid == 0)
2033      {
2034          return false;
2035      }
2036  
2037      $user_perms = user_permissions($uid);
2038  
2039      $hook_args = array(
2040          'fid' => $fid,
2041          'action' => $action,
2042          'uid' => $uid,
2043      );
2044  
2045      $plugins->run_hooks("is_moderator", $hook_args);
2046      
2047      if(isset($hook_args['is_moderator']))
2048      {
2049          return (boolean) $hook_args['is_moderator'];
2050      }
2051  
2052      if(!empty($user_perms['issupermod']) && $user_perms['issupermod'] == 1)
2053      {
2054          if($fid)
2055          {
2056              $forumpermissions = forum_permissions($fid);
2057              if(!empty($forumpermissions['canview']) && !empty($forumpermissions['canviewthreads']) && empty($forumpermissions['canonlyviewownthreads']))
2058              {
2059                  return true;
2060              }
2061              return false;
2062          }
2063          return true;
2064      }
2065      else
2066      {
2067          if(!$fid)
2068          {
2069              $modcache = $cache->read('moderators');
2070              if(!empty($modcache))
2071              {
2072                  foreach($modcache as $modusers)
2073                  {
2074                      if(isset($modusers['users'][$uid]) && $modusers['users'][$uid]['mid'] && (!$action || !empty($modusers['users'][$uid][$action])))
2075                      {
2076                          return true;
2077                      }
2078  
2079                      $groups = explode(',', $user_perms['all_usergroups']);
2080  
2081                      foreach($groups as $group)
2082                      {
2083                          if(trim($group) != '' && isset($modusers['usergroups'][$group]) && (!$action || !empty($modusers['usergroups'][$group][$action])))
2084                          {
2085                              return true;
2086                          }
2087                      }
2088                  }
2089              }
2090              return false;
2091          }
2092          else
2093          {
2094              $modperms = get_moderator_permissions($fid, $uid);
2095  
2096              if(!$action && $modperms)
2097              {
2098                  return true;
2099              }
2100              else
2101              {
2102                  if(isset($modperms[$action]) && $modperms[$action] == 1)
2103                  {
2104                      return true;
2105                  }
2106                  else
2107                  {
2108                      return false;
2109                  }
2110              }
2111          }
2112      }
2113  }
2114  
2115  /**
2116   * Get an array of fids that the forum moderator has access to.
2117   * Do not use for administraotrs or global moderators as they moderate any forum and the function will return false.
2118   *
2119   * @param int $uid The user ID (0 assumes current user)
2120   * @return array|bool an array of the fids the user has moderator access to or bool if called incorrectly.
2121   */
2122  function get_moderated_fids($uid=0)
2123  {
2124      global $mybb, $cache;
2125  
2126      if($uid == 0)
2127      {
2128          $uid = $mybb->user['uid'];
2129      }
2130  
2131      if($uid == 0)
2132      {
2133          return array();
2134      }
2135  
2136      $user_perms = user_permissions($uid);
2137  
2138      if($user_perms['issupermod'] == 1)
2139      {
2140          return false;
2141      }
2142  
2143      $fids = array();
2144  
2145      $modcache = $cache->read('moderators');
2146      if(!empty($modcache))
2147      {
2148          $groups = explode(',', $user_perms['all_usergroups']);
2149  
2150          foreach($modcache as $fid => $forum)
2151          {
2152              if(isset($forum['users'][$uid]) && $forum['users'][$uid]['mid'])
2153              {
2154                  $fids[] = $fid;
2155                  continue;
2156              }
2157  
2158              foreach($groups as $group)
2159              {
2160                  if(trim($group) != '' && isset($forum['usergroups'][$group]))
2161                  {
2162                      $fids[] = $fid;
2163                  }
2164              }
2165          }
2166      }
2167  
2168      return $fids;
2169  }
2170  
2171  /**
2172   * Generate a list of the posticons.
2173   *
2174   * @return string The template of posticons.
2175   */
2176  function get_post_icons()
2177  {
2178      global $mybb, $cache, $icon, $theme, $templates, $lang;
2179  
2180      if(isset($mybb->input['icon']))
2181      {
2182          $icon = $mybb->get_input('icon');
2183      }
2184  
2185      $iconlist = '';
2186      $no_icons_checked = " checked=\"checked\"";
2187      // read post icons from cache, and sort them accordingly
2188      $posticons_cache = (array)$cache->read("posticons");
2189      $posticons = array();
2190      foreach($posticons_cache as $posticon)
2191      {
2192          $posticons[$posticon['name']] = $posticon;
2193      }
2194      krsort($posticons);
2195  
2196      foreach($posticons as $dbicon)
2197      {
2198          $dbicon['path'] = str_replace("{theme}", $theme['imgdir'], $dbicon['path']);
2199          $dbicon['path'] = htmlspecialchars_uni($mybb->get_asset_url($dbicon['path']));
2200          $dbicon['name'] = htmlspecialchars_uni($dbicon['name']);
2201  
2202          if($icon == $dbicon['iid'])
2203          {
2204              $checked = " checked=\"checked\"";
2205              $no_icons_checked = '';
2206          }
2207          else
2208          {
2209              $checked = '';
2210          }
2211  
2212          eval("\$iconlist .= \"".$templates->get("posticons_icon")."\";");
2213      }
2214  
2215      if(!empty($iconlist))
2216      {
2217          eval("\$posticons = \"".$templates->get("posticons")."\";");
2218      }
2219      else
2220      {
2221          $posticons = '';
2222      }
2223  
2224      return $posticons;
2225  }
2226  
2227  /**
2228   * MyBB setcookie() wrapper.
2229   *
2230   * @param string $name The cookie identifier.
2231   * @param string $value The cookie value.
2232   * @param int|string $expires The timestamp of the expiry date.
2233   * @param boolean $httponly True if setting a HttpOnly cookie (supported by the majority of web browsers)
2234   * @param string $samesite The samesite attribute to prevent CSRF.
2235   */
2236  function my_setcookie($name, $value="", $expires="", $httponly=false, $samesite="")
2237  {
2238      global $mybb;
2239  
2240      if(!$mybb->settings['cookiepath'])
2241      {
2242          $mybb->settings['cookiepath'] = "/";
2243      }
2244  
2245      if($expires == -1)
2246      {
2247          $expires = 0;
2248      }
2249      elseif($expires == "" || $expires == null)
2250      {
2251          $expires = TIME_NOW + (60*60*24*365); // Make the cookie expire in a years time
2252      }
2253      else
2254      {
2255          $expires = TIME_NOW + (int)$expires;
2256      }
2257  
2258      $mybb->settings['cookiepath'] = str_replace(array("\n","\r"), "", $mybb->settings['cookiepath']);
2259      $mybb->settings['cookiedomain'] = str_replace(array("\n","\r"), "", $mybb->settings['cookiedomain']);
2260      $mybb->settings['cookieprefix'] = str_replace(array("\n","\r", " "), "", $mybb->settings['cookieprefix']);
2261  
2262      // 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
2263      $cookie = "Set-Cookie: {$mybb->settings['cookieprefix']}{$name}=".urlencode($value);
2264  
2265      if($expires > 0)
2266      {
2267          $cookie .= "; expires=".@gmdate('D, d-M-Y H:i:s \\G\\M\\T', $expires);
2268      }
2269  
2270      if(!empty($mybb->settings['cookiepath']))
2271      {
2272          $cookie .= "; path={$mybb->settings['cookiepath']}";
2273      }
2274  
2275      if(!empty($mybb->settings['cookiedomain']))
2276      {
2277          $cookie .= "; domain={$mybb->settings['cookiedomain']}";
2278      }
2279  
2280      if($httponly == true)
2281      {
2282          $cookie .= "; HttpOnly";
2283      }
2284  
2285      if($samesite != "" && $mybb->settings['cookiesamesiteflag'])
2286      {
2287          $samesite = strtolower($samesite);
2288  
2289          if($samesite == "lax" || $samesite == "strict")
2290          {
2291              $cookie .= "; SameSite=".$samesite;
2292          }
2293      }
2294  
2295      if($mybb->settings['cookiesecureflag'])
2296      {
2297          $cookie .= "; Secure";
2298      }
2299  
2300      $mybb->cookies[$name] = $value;
2301  
2302      header($cookie, false);
2303  }
2304  
2305  /**
2306   * Unset a cookie set by MyBB.
2307   *
2308   * @param string $name The cookie identifier.
2309   */
2310  function my_unsetcookie($name)
2311  {
2312      global $mybb;
2313  
2314      $expires = -3600;
2315      my_setcookie($name, "", $expires);
2316  
2317      unset($mybb->cookies[$name]);
2318  }
2319  
2320  /**
2321   * Get the contents from a serialised cookie array.
2322   *
2323   * @param string $name The cookie identifier.
2324   * @param int $id The cookie content id.
2325   * @return array|boolean The cookie id's content array or false when non-existent.
2326   */
2327  function my_get_array_cookie($name, $id)
2328  {
2329      global $mybb;
2330  
2331      if(!isset($mybb->cookies['mybb'][$name]))
2332      {
2333          return false;
2334      }
2335  
2336      $cookie = my_unserialize($mybb->cookies['mybb'][$name]);
2337  
2338      if(is_array($cookie) && isset($cookie[$id]))
2339      {
2340          return $cookie[$id];
2341      }
2342      else
2343      {
2344          return 0;
2345      }
2346  }
2347  
2348  /**
2349   * Set a serialised cookie array.
2350   *
2351   * @param string $name The cookie identifier.
2352   * @param int $id The cookie content id.
2353   * @param string $value The value to set the cookie to.
2354   * @param int|string $expires The timestamp of the expiry date.
2355   */
2356  function my_set_array_cookie($name, $id, $value, $expires="")
2357  {
2358      global $mybb;
2359  
2360      if(isset($mybb->cookies['mybb'][$name]))
2361      {
2362          $newcookie = my_unserialize($mybb->cookies['mybb'][$name]);
2363      }
2364      else
2365      {
2366          $newcookie = array();
2367      }
2368  
2369      $newcookie[$id] = $value;
2370      $newcookie = my_serialize($newcookie);
2371      my_setcookie("mybb[$name]", addslashes($newcookie), $expires);
2372  
2373      // Make sure our current viarables are up-to-date as well
2374      $mybb->cookies['mybb'][$name] = $newcookie;
2375  }
2376  
2377  /*
2378   * Arbitrary limits for _safe_unserialize()
2379   */
2380  define('MAX_SERIALIZED_INPUT_LENGTH', 10240);
2381  define('MAX_SERIALIZED_ARRAY_LENGTH', 256);
2382  define('MAX_SERIALIZED_ARRAY_DEPTH', 5);
2383  
2384  /**
2385   * Credits go to https://github.com/piwik
2386   * Safe unserialize() replacement
2387   * - accepts a strict subset of PHP's native my_serialized representation
2388   * - does not unserialize objects
2389   *
2390   * @param string $str
2391   * @return mixed
2392   * @throw Exception if $str is malformed or contains unsupported types (e.g., resources, objects)
2393   */
2394  function _safe_unserialize($str)
2395  {
2396      if(strlen($str) > MAX_SERIALIZED_INPUT_LENGTH)
2397      {
2398          // input exceeds MAX_SERIALIZED_INPUT_LENGTH
2399          return false;
2400      }
2401  
2402      if(empty($str) || !is_string($str))
2403      {
2404          return false;
2405      }
2406  
2407      $stack = $list = $expected = array();
2408  
2409      /*
2410       * states:
2411       *   0 - initial state, expecting a single value or array
2412       *   1 - terminal state
2413       *   2 - in array, expecting end of array or a key
2414       *   3 - in array, expecting value or another array
2415       */
2416      $state = 0;
2417      while($state != 1)
2418      {
2419          $type = isset($str[0]) ? $str[0] : '';
2420  
2421          if($type == '}')
2422          {
2423              $str = substr($str, 1);
2424          }
2425          else if($type == 'N' && $str[1] == ';')
2426          {
2427              $value = null;
2428              $str = substr($str, 2);
2429          }
2430          else if($type == 'b' && preg_match('/^b:([01]);/', $str, $matches))
2431          {
2432              $value = $matches[1] == '1' ? true : false;
2433              $str = substr($str, 4);
2434          }
2435          else if($type == 'i' && preg_match('/^i:(-?[0-9]+);(.*)/s', $str, $matches))
2436          {
2437              $value = (int)$matches[1];
2438              $str = $matches[2];
2439          }
2440          else if($type == 'd' && preg_match('/^d:(-?[0-9]+\.?[0-9]*(E[+-][0-9]+)?);(.*)/s', $str, $matches))
2441          {
2442              $value = (float)$matches[1];
2443              $str = $matches[3];
2444          }
2445          else if($type == 's' && preg_match('/^s:([0-9]+):"(.*)/s', $str, $matches) && substr($matches[2], (int)$matches[1], 2) == '";')
2446          {
2447              $value = substr($matches[2], 0, (int)$matches[1]);
2448              $str = substr($matches[2], (int)$matches[1] + 2);
2449          }
2450          else if($type == 'a' && preg_match('/^a:([0-9]+):{(.*)/s', $str, $matches) && $matches[1] < MAX_SERIALIZED_ARRAY_LENGTH)
2451          {
2452              $expectedLength = (int)$matches[1];
2453              $str = $matches[2];
2454          }
2455          else
2456          {
2457              // object or unknown/malformed type
2458              return false;
2459          }
2460  
2461          switch($state)
2462          {
2463              case 3: // in array, expecting value or another array
2464                  if($type == 'a')
2465                  {
2466                      if(count($stack) >= MAX_SERIALIZED_ARRAY_DEPTH)
2467                      {
2468                          // array nesting exceeds MAX_SERIALIZED_ARRAY_DEPTH
2469                          return false;
2470                      }
2471  
2472                      $stack[] = &$list;
2473                      $list[$key] = array();
2474                      $list = &$list[$key];
2475                      $expected[] = $expectedLength;
2476                      $state = 2;
2477                      break;
2478                  }
2479                  if($type != '}')
2480                  {
2481                      $list[$key] = $value;
2482                      $state = 2;
2483                      break;
2484                  }
2485  
2486                  // missing array value
2487                  return false;
2488  
2489              case 2: // in array, expecting end of array or a key
2490                  if($type == '}')
2491                  {
2492                      if(count($list) < end($expected))
2493                      {
2494                          // array size less than expected
2495                          return false;
2496                      }
2497  
2498                      unset($list);
2499                      $list = &$stack[count($stack)-1];
2500                      array_pop($stack);
2501  
2502                      // go to terminal state if we're at the end of the root array
2503                      array_pop($expected);
2504                      if(count($expected) == 0) {
2505                          $state = 1;
2506                      }
2507                      break;
2508                  }
2509                  if($type == 'i' || $type == 's')
2510                  {
2511                      if(count($list) >= MAX_SERIALIZED_ARRAY_LENGTH)
2512                      {
2513                          // array size exceeds MAX_SERIALIZED_ARRAY_LENGTH
2514                          return false;
2515                      }
2516                      if(count($list) >= end($expected))
2517                      {
2518                          // array size exceeds expected length
2519                          return false;
2520                      }
2521  
2522                      $key = $value;
2523                      $state = 3;
2524                      break;
2525                  }
2526  
2527                  // illegal array index type
2528                  return false;
2529  
2530              case 0: // expecting array or value
2531                  if($type == 'a')
2532                  {
2533                      if(count($stack) >= MAX_SERIALIZED_ARRAY_DEPTH)
2534                      {
2535                          // array nesting exceeds MAX_SERIALIZED_ARRAY_DEPTH
2536                          return false;
2537                      }
2538  
2539                      $data = array();
2540                      $list = &$data;
2541                      $expected[] = $expectedLength;
2542                      $state = 2;
2543                      break;
2544                  }
2545                  if($type != '}')
2546                  {
2547                      $data = $value;
2548                      $state = 1;
2549                      break;
2550                  }
2551  
2552                  // not in array
2553                  return false;
2554          }
2555      }
2556  
2557      if(!empty($str))
2558      {
2559          // trailing data in input
2560          return false;
2561      }
2562      return $data;
2563  }
2564  
2565  /**
2566   * Credits go to https://github.com/piwik
2567   * Wrapper for _safe_unserialize() that handles exceptions and multibyte encoding issue
2568   *
2569   * @param string $str
2570   * @return mixed
2571   */
2572  function my_unserialize($str)
2573  {
2574      // Ensure we use the byte count for strings even when strlen() is overloaded by mb_strlen()
2575      if(function_exists('mb_internal_encoding') && (((int)ini_get('mbstring.func_overload')) & 2))
2576      {
2577          $mbIntEnc = mb_internal_encoding();
2578          mb_internal_encoding('ASCII');
2579      }
2580  
2581      $out = _safe_unserialize($str);
2582  
2583      if(isset($mbIntEnc))
2584      {
2585          mb_internal_encoding($mbIntEnc);
2586      }
2587  
2588      return $out;
2589  }
2590  
2591  /**
2592   * Credits go to https://github.com/piwik
2593   * Safe serialize() replacement
2594   * - output a strict subset of PHP's native serialized representation
2595   * - does not my_serialize objects
2596   *
2597   * @param mixed $value
2598   * @return string
2599   * @throw Exception if $value is malformed or contains unsupported types (e.g., resources, objects)
2600   */
2601  function _safe_serialize( $value )
2602  {
2603      if(is_null($value))
2604      {
2605          return 'N;';
2606      }
2607  
2608      if(is_bool($value))
2609      {
2610          return 'b:'.(int)$value.';';
2611      }
2612  
2613      if(is_int($value))
2614      {
2615          return 'i:'.$value.';';
2616      }
2617  
2618      if(is_float($value))
2619      {
2620          return 'd:'.str_replace(',', '.', $value).';';
2621      }
2622  
2623      if(is_string($value))
2624      {
2625          return 's:'.strlen($value).':"'.$value.'";';
2626      }
2627  
2628      if(is_array($value))
2629      {
2630          $out = '';
2631          foreach($value as $k => $v)
2632          {
2633              $out .= _safe_serialize($k) . _safe_serialize($v);
2634          }
2635  
2636          return 'a:'.count($value).':{'.$out.'}';
2637      }
2638  
2639      // safe_serialize cannot my_serialize resources or objects
2640      return false;
2641  }
2642  
2643  /**
2644   * Credits go to https://github.com/piwik
2645   * Wrapper for _safe_serialize() that handles exceptions and multibyte encoding issue
2646   *
2647   * @param mixed $value
2648   * @return string
2649  */
2650  function my_serialize($value)
2651  {
2652      // ensure we use the byte count for strings even when strlen() is overloaded by mb_strlen()
2653      if(function_exists('mb_internal_encoding') && (((int)ini_get('mbstring.func_overload')) & 2))
2654      {
2655          $mbIntEnc = mb_internal_encoding();
2656          mb_internal_encoding('ASCII');
2657      }
2658  
2659      $out = _safe_serialize($value);
2660      if(isset($mbIntEnc))
2661      {
2662          mb_internal_encoding($mbIntEnc);
2663      }
2664  
2665      return $out;
2666  }
2667  
2668  /**
2669   * Returns the serverload of the system.
2670   *
2671   * @return int The serverload of the system.
2672   */
2673  function get_server_load()
2674  {
2675      global $mybb, $lang;
2676  
2677      $serverload = array();
2678  
2679      // DIRECTORY_SEPARATOR checks if running windows
2680      if(DIRECTORY_SEPARATOR != '\\')
2681      {
2682          if(function_exists("sys_getloadavg"))
2683          {
2684              // sys_getloadavg() will return an array with [0] being load within the last minute.
2685              $serverload = sys_getloadavg();
2686              $serverload[0] = round($serverload[0], 4);
2687          }
2688          else if(@file_exists("/proc/loadavg") && $load = @file_get_contents("/proc/loadavg"))
2689          {
2690              $serverload = explode(" ", $load);
2691              $serverload[0] = round($serverload[0], 4);
2692          }
2693          if(!is_numeric($serverload[0]))
2694          {
2695              if($mybb->safemode)
2696              {
2697                  return $lang->unknown;
2698              }
2699  
2700              // Suhosin likes to throw a warning if exec is disabled then die - weird
2701              if($func_blacklist = @ini_get('suhosin.executor.func.blacklist'))
2702              {
2703                  if(strpos(",".$func_blacklist.",", 'exec') !== false)
2704                  {
2705                      return $lang->unknown;
2706                  }
2707              }
2708              // PHP disabled functions?
2709              if($func_blacklist = @ini_get('disable_functions'))
2710              {
2711                  if(strpos(",".$func_blacklist.",", 'exec') !== false)
2712                  {
2713                      return $lang->unknown;
2714                  }
2715              }
2716  
2717              $load = @exec("uptime");
2718              $load = explode("load average: ", $load);
2719              $serverload = explode(",", $load[1]);
2720              if(!is_array($serverload))
2721              {
2722                  return $lang->unknown;
2723              }
2724          }
2725      }
2726      else
2727      {
2728          return $lang->unknown;
2729      }
2730  
2731      $returnload = trim($serverload[0]);
2732  
2733      return $returnload;
2734  }
2735  
2736  /**
2737   * Returns the amount of memory allocated to the script.
2738   *
2739   * @return int The amount of memory allocated to the script.
2740   */
2741  function get_memory_usage()
2742  {
2743      if(function_exists('memory_get_peak_usage'))
2744      {
2745          return memory_get_peak_usage(true);
2746      }
2747      elseif(function_exists('memory_get_usage'))
2748      {
2749          return memory_get_usage(true);
2750      }
2751      return false;
2752  }
2753  
2754  /**
2755   * Updates the forum statistics with specific values (or addition/subtraction of the previous value)
2756   *
2757   * @param array $changes Array of items being updated (numthreads,numposts,numusers,numunapprovedthreads,numunapprovedposts,numdeletedposts,numdeletedthreads)
2758   * @param boolean $force Force stats update?
2759   */
2760  function update_stats($changes=array(), $force=false)
2761  {
2762      global $cache, $db;
2763      static $stats_changes;
2764  
2765      if(empty($stats_changes))
2766      {
2767          // Update stats after all changes are done
2768          add_shutdown('update_stats', array(array(), true));
2769      }
2770  
2771      if(empty($stats_changes) || $stats_changes['inserted'])
2772      {
2773          $stats_changes = array(
2774              'numthreads' => '+0',
2775              'numposts' => '+0',
2776              'numusers' => '+0',
2777              'numunapprovedthreads' => '+0',
2778              'numunapprovedposts' => '+0',
2779              'numdeletedposts' => '+0',
2780              'numdeletedthreads' => '+0',
2781              'inserted' => false // Reset after changes are inserted into cache
2782          );
2783          $stats = $stats_changes;
2784      }
2785  
2786      if($force) // Force writing to cache?
2787      {
2788          if(!empty($changes))
2789          {
2790              // Calculate before writing to cache
2791              update_stats($changes);
2792          }
2793          $stats = $cache->read("stats");
2794          $changes = $stats_changes;
2795      }
2796      else
2797      {
2798          $stats = $stats_changes;
2799      }
2800  
2801      $new_stats = array();
2802      $counters = array('numthreads', 'numunapprovedthreads', 'numposts', 'numunapprovedposts', 'numusers', 'numdeletedposts', 'numdeletedthreads');
2803      foreach($counters as $counter)
2804      {
2805          if(array_key_exists($counter, $changes))
2806          {
2807              if(substr($changes[$counter], 0, 2) == "+-")
2808              {
2809                  $changes[$counter] = substr($changes[$counter], 1);
2810              }
2811              // Adding or subtracting from previous value?
2812              if(substr($changes[$counter], 0, 1) == "+" || substr($changes[$counter], 0, 1) == "-")
2813              {
2814                  if((int)$changes[$counter] != 0)
2815                  {
2816                      $new_stats[$counter] = $stats[$counter] + $changes[$counter];
2817                      if(!$force && (substr($stats[$counter], 0, 1) == "+" || substr($stats[$counter], 0, 1) == "-"))
2818                      {
2819                          // We had relative values? Then it is still relative
2820                          if($new_stats[$counter] >= 0)
2821                          {
2822                              $new_stats[$counter] = "+{$new_stats[$counter]}";
2823                          }
2824                      }
2825                      // Less than 0? That's bad
2826                      elseif($new_stats[$counter] < 0)
2827                      {
2828                          $new_stats[$counter] = 0;
2829                      }
2830                  }
2831              }
2832              else
2833              {
2834                  $new_stats[$counter] = $changes[$counter];
2835                  // Less than 0? That's bad
2836                  if($new_stats[$counter] < 0)
2837                  {
2838                      $new_stats[$counter] = 0;
2839                  }
2840              }
2841          }
2842      }
2843  
2844      if(!$force)
2845      {
2846          $stats_changes = array_merge($stats, $new_stats); // Overwrite changed values
2847          return;
2848      }
2849  
2850      // Fetch latest user if the user count is changing
2851      if(array_key_exists('numusers', $changes))
2852      {
2853          $query = $db->simple_select("users", "uid, username", "", array('order_by' => 'regdate', 'order_dir' => 'DESC', 'limit' => 1));
2854          $lastmember = $db->fetch_array($query);
2855          $new_stats['lastuid'] = $lastmember['uid'];
2856          $new_stats['lastusername'] = $lastmember['username'] = htmlspecialchars_uni($lastmember['username']);
2857      }
2858  
2859      if(!empty($new_stats))
2860      {
2861          if(is_array($stats))
2862          {
2863              $stats = array_merge($stats, $new_stats); // Overwrite changed values
2864          }
2865          else
2866          {
2867              $stats = $new_stats;
2868          }
2869      }
2870  
2871      // Update stats row for today in the database
2872      $todays_stats = array(
2873          "dateline" => mktime(0, 0, 0, date("m"), date("j"), date("Y")),
2874          "numusers" => (int)$stats['numusers'],
2875          "numthreads" => (int)$stats['numthreads'],
2876          "numposts" => (int)$stats['numposts']
2877      );
2878      $db->replace_query("stats", $todays_stats, "dateline");
2879  
2880      $cache->update("stats", $stats, "dateline");
2881      $stats_changes['inserted'] = true;
2882  }
2883  
2884  /**
2885   * Updates the forum counters with a specific value (or addition/subtraction of the previous value)
2886   *
2887   * @param int $fid The forum ID
2888   * @param array $changes Array of items being updated (threads, posts, unapprovedthreads, unapprovedposts, deletedposts, deletedthreads) and their value (ex, 1, +1, -1)
2889   */
2890  function update_forum_counters($fid, $changes=array())
2891  {
2892      global $db;
2893  
2894      $update_query = array();
2895  
2896      $counters = array('threads', 'unapprovedthreads', 'posts', 'unapprovedposts', 'deletedposts', 'deletedthreads');
2897  
2898      // Fetch above counters for this forum
2899      $query = $db->simple_select("forums", implode(",", $counters), "fid='{$fid}'");
2900      $forum = $db->fetch_array($query);
2901  
2902      foreach($counters as $counter)
2903      {
2904          if(array_key_exists($counter, $changes))
2905          {
2906              if(substr($changes[$counter], 0, 2) == "+-")
2907              {
2908                  $changes[$counter] = substr($changes[$counter], 1);
2909              }
2910              // Adding or subtracting from previous value?
2911              if(substr($changes[$counter], 0, 1) == "+" || substr($changes[$counter], 0, 1) == "-")
2912              {
2913                  if((int)$changes[$counter] != 0)
2914                  {
2915                      $update_query[$counter] = $forum[$counter] + $changes[$counter];
2916                  }
2917              }
2918              else
2919              {
2920                  $update_query[$counter] = $changes[$counter];
2921              }
2922  
2923              // Less than 0? That's bad
2924              if(isset($update_query[$counter]) && $update_query[$counter] < 0)
2925              {
2926                  $update_query[$counter] = 0;
2927              }
2928          }
2929      }
2930  
2931      // Only update if we're actually doing something
2932      if(count($update_query) > 0)
2933      {
2934          $db->update_query("forums", $update_query, "fid='".(int)$fid."'");
2935      }
2936  
2937      // Guess we should update the statistics too?
2938      $new_stats = array();
2939      if(array_key_exists('threads', $update_query))
2940      {
2941          $threads_diff = $update_query['threads'] - $forum['threads'];
2942          if($threads_diff > -1)
2943          {
2944              $new_stats['numthreads'] = "+{$threads_diff}";
2945          }
2946          else
2947          {
2948              $new_stats['numthreads'] = "{$threads_diff}";
2949          }
2950      }
2951  
2952      if(array_key_exists('unapprovedthreads', $update_query))
2953      {
2954          $unapprovedthreads_diff = $update_query['unapprovedthreads'] - $forum['unapprovedthreads'];
2955          if($unapprovedthreads_diff > -1)
2956          {
2957              $new_stats['numunapprovedthreads'] = "+{$unapprovedthreads_diff}";
2958          }
2959          else
2960          {
2961              $new_stats['numunapprovedthreads'] = "{$unapprovedthreads_diff}";
2962          }
2963      }
2964  
2965      if(array_key_exists('posts', $update_query))
2966      {
2967          $posts_diff = $update_query['posts'] - $forum['posts'];
2968          if($posts_diff > -1)
2969          {
2970              $new_stats['numposts'] = "+{$posts_diff}";
2971          }
2972          else
2973          {
2974              $new_stats['numposts'] = "{$posts_diff}";
2975          }
2976      }
2977  
2978      if(array_key_exists('unapprovedposts', $update_query))
2979      {
2980          $unapprovedposts_diff = $update_query['unapprovedposts'] - $forum['unapprovedposts'];
2981          if($unapprovedposts_diff > -1)
2982          {
2983              $new_stats['numunapprovedposts'] = "+{$unapprovedposts_diff}";
2984          }
2985          else
2986          {
2987              $new_stats['numunapprovedposts'] = "{$unapprovedposts_diff}";
2988          }
2989      }
2990  
2991      if(array_key_exists('deletedposts', $update_query))
2992      {
2993          $deletedposts_diff = $update_query['deletedposts'] - $forum['deletedposts'];
2994          if($deletedposts_diff > -1)
2995          {
2996              $new_stats['numdeletedposts'] = "+{$deletedposts_diff}";
2997          }
2998          else
2999          {
3000              $new_stats['numdeletedposts'] = "{$deletedposts_diff}";
3001          }
3002      }
3003  
3004      if(array_key_exists('deletedthreads', $update_query))
3005      {
3006          $deletedthreads_diff = $update_query['deletedthreads'] - $forum['deletedthreads'];
3007          if($deletedthreads_diff > -1)
3008          {
3009              $new_stats['numdeletedthreads'] = "+{$deletedthreads_diff}";
3010          }
3011          else
3012          {
3013              $new_stats['numdeletedthreads'] = "{$deletedthreads_diff}";
3014          }
3015      }
3016  
3017      if(!empty($new_stats))
3018      {
3019          update_stats($new_stats);
3020      }
3021  }
3022  
3023  /**
3024   * Update the last post information for a specific forum
3025   *
3026   * @param int $fid The forum ID
3027   */
3028  function update_forum_lastpost($fid)
3029  {
3030      global $db;
3031  
3032      // Fetch the last post for this forum
3033      $query = $db->query("
3034          SELECT tid, lastpost, lastposter, lastposteruid, subject
3035          FROM ".TABLE_PREFIX."threads
3036          WHERE fid='{$fid}' AND visible='1' AND closed NOT LIKE 'moved|%'
3037          ORDER BY lastpost DESC
3038          LIMIT 0, 1
3039      ");
3040  
3041      if($db->num_rows($query) > 0)
3042      {
3043          $lastpost = $db->fetch_array($query);
3044  
3045          $updated_forum = array(
3046              "lastpost" => (int)$lastpost['lastpost'],
3047              "lastposter" => $db->escape_string($lastpost['lastposter']),
3048              "lastposteruid" => (int)$lastpost['lastposteruid'],
3049              "lastposttid" => (int)$lastpost['tid'],
3050              "lastpostsubject" => $db->escape_string($lastpost['subject']),
3051          );
3052      }
3053      else {
3054          $updated_forum = array(
3055              "lastpost" => 0,
3056              "lastposter" => '',
3057              "lastposteruid" => 0,
3058              "lastposttid" => 0,
3059              "lastpostsubject" => '',
3060          );
3061      }
3062  
3063      $db->update_query("forums", $updated_forum, "fid='{$fid}'");
3064  }
3065  
3066  /**
3067   * Updates the thread counters with a specific value (or addition/subtraction of the previous value)
3068   *
3069   * @param int $tid The thread ID
3070   * @param array $changes Array of items being updated (replies, unapprovedposts, deletedposts, attachmentcount) and their value (ex, 1, +1, -1)
3071   */
3072  function update_thread_counters($tid, $changes=array())
3073  {
3074      global $db;
3075  
3076      $update_query = array();
3077      $tid = (int)$tid;
3078  
3079      $counters = array('replies', 'unapprovedposts', 'attachmentcount', 'deletedposts', 'attachmentcount');
3080  
3081      // Fetch above counters for this thread
3082      $query = $db->simple_select("threads", implode(",", $counters), "tid='{$tid}'");
3083      $thread = $db->fetch_array($query);
3084  
3085      foreach($counters as $counter)
3086      {
3087          if(array_key_exists($counter, $changes))
3088          {
3089              if(substr($changes[$counter], 0, 2) == "+-")
3090              {
3091                  $changes[$counter] = substr($changes[$counter], 1);
3092              }
3093              // Adding or subtracting from previous value?
3094              if(substr($changes[$counter], 0, 1) == "+" || substr($changes[$counter], 0, 1) == "-")
3095              {
3096                  if((int)$changes[$counter] != 0)
3097                  {
3098                      $update_query[$counter] = $thread[$counter] + $changes[$counter];
3099                  }
3100              }
3101              else
3102              {
3103                  $update_query[$counter] = $changes[$counter];
3104              }
3105  
3106              // Less than 0? That's bad
3107              if(isset($update_query[$counter]) && $update_query[$counter] < 0)
3108              {
3109                  $update_query[$counter] = 0;
3110              }
3111          }
3112      }
3113  
3114      $db->free_result($query);
3115  
3116      // Only update if we're actually doing something
3117      if(count($update_query) > 0)
3118      {
3119          $db->update_query("threads", $update_query, "tid='{$tid}'");
3120      }
3121  }
3122  
3123  /**
3124   * Update the first post and lastpost data for a specific thread
3125   *
3126   * @param int $tid The thread ID
3127   */
3128  function update_thread_data($tid)
3129  {
3130      global $db;
3131  
3132      $thread = get_thread($tid);
3133  
3134      // If this is a moved thread marker, don't update it - we need it to stay as it is
3135      if(strpos($thread['closed'], 'moved|') !== false)
3136      {
3137          return;
3138      }
3139  
3140      $query = $db->query("
3141          SELECT u.uid, u.username, p.username AS postusername, p.dateline
3142          FROM ".TABLE_PREFIX."posts p
3143          LEFT JOIN ".TABLE_PREFIX."users u ON (u.uid=p.uid)
3144          WHERE p.tid='$tid' AND p.visible='1'
3145          ORDER BY p.dateline DESC, p.pid DESC
3146          LIMIT 1"
3147      );
3148      $lastpost = $db->fetch_array($query);
3149  
3150      $db->free_result($query);
3151  
3152      $query = $db->query("
3153          SELECT u.uid, u.username, p.pid, p.username AS postusername, p.dateline
3154          FROM ".TABLE_PREFIX."posts p
3155          LEFT JOIN ".TABLE_PREFIX."users u ON (u.uid=p.uid)
3156          WHERE p.tid='$tid'
3157          ORDER BY p.dateline ASC, p.pid ASC
3158          LIMIT 1
3159      ");
3160      $firstpost = $db->fetch_array($query);
3161  
3162      $db->free_result($query);
3163  
3164      if(empty($firstpost['username']))
3165      {
3166          $firstpost['username'] = $firstpost['postusername'];
3167      }
3168  
3169      if(empty($lastpost['username']))
3170      {
3171          $lastpost['username'] = $lastpost['postusername'];
3172      }
3173  
3174      if(empty($lastpost['dateline']))
3175      {
3176          $lastpost['username'] = $firstpost['username'];
3177          $lastpost['uid'] = $firstpost['uid'];
3178          $lastpost['dateline'] = $firstpost['dateline'];
3179      }
3180  
3181      $lastpost['username'] = $db->escape_string($lastpost['username']);
3182      $firstpost['username'] = $db->escape_string($firstpost['username']);
3183  
3184      $update_array = array(
3185          'firstpost' => (int)$firstpost['pid'],
3186          'username' => $firstpost['username'],
3187          'uid' => (int)$firstpost['uid'],
3188          'dateline' => (int)$firstpost['dateline'],
3189          'lastpost' => (int)$lastpost['dateline'],
3190          'lastposter' => $lastpost['username'],
3191          'lastposteruid' => (int)$lastpost['uid'],
3192      );
3193      $db->update_query("threads", $update_array, "tid='{$tid}'");
3194  }
3195  
3196  /**
3197   * Updates the user counters with a specific value (or addition/subtraction of the previous value)
3198   *
3199   * @param int $uid The user ID
3200   * @param array $changes Array of items being updated (postnum, threadnum) and their value (ex, 1, +1, -1)
3201   */
3202  function update_user_counters($uid, $changes=array())
3203  {
3204      global $db;
3205  
3206      $update_query = array();
3207  
3208      $counters = array('postnum', 'threadnum');
3209      $uid = (int)$uid;
3210  
3211      // Fetch above counters for this user
3212      $query = $db->simple_select("users", implode(",", $counters), "uid='{$uid}'");
3213      $user = $db->fetch_array($query);
3214  
3215      foreach($counters as $counter)
3216      {
3217          if(array_key_exists($counter, $changes))
3218          {
3219              if(substr($changes[$counter], 0, 2) == "+-")
3220              {
3221                  $changes[$counter] = substr($changes[$counter], 1);
3222              }
3223              // Adding or subtracting from previous value?
3224              if(substr($changes[$counter], 0, 1) == "+" || substr($changes[$counter], 0, 1) == "-")
3225              {
3226                  if((int)$changes[$counter] != 0)
3227                  {
3228                      $update_query[$counter] = $user[$counter] + $changes[$counter];
3229                  }
3230              }
3231              else
3232              {
3233                  $update_query[$counter] = $changes[$counter];
3234              }
3235  
3236              // Less than 0? That's bad
3237              if(isset($update_query[$counter]) && $update_query[$counter] < 0)
3238              {
3239                  $update_query[$counter] = 0;
3240              }
3241          }
3242      }
3243  
3244      $db->free_result($query);
3245  
3246      // Only update if we're actually doing something
3247      if(count($update_query) > 0)
3248      {
3249          $db->update_query("users", $update_query, "uid='{$uid}'");
3250      }
3251  }
3252  
3253  /**
3254   * Deletes a thread from the database
3255   *
3256   * @param int $tid The thread ID
3257   * @return bool
3258   */
3259  function delete_thread($tid)
3260  {
3261      global $moderation;
3262  
3263      if(!is_object($moderation))
3264      {
3265          require_once  MYBB_ROOT."inc/class_moderation.php";
3266          $moderation = new Moderation;
3267      }
3268  
3269      return $moderation->delete_thread($tid);
3270  }
3271  
3272  /**
3273   * Deletes a post from the database
3274   *
3275   * @param int $pid The thread ID
3276   * @return bool
3277   */
3278  function delete_post($pid)
3279  {
3280      global $moderation;
3281  
3282      if(!is_object($moderation))
3283      {
3284          require_once  MYBB_ROOT."inc/class_moderation.php";
3285          $moderation = new Moderation;
3286      }
3287  
3288      return $moderation->delete_post($pid);
3289  }
3290  
3291  /**
3292   * Builds a forum jump menu
3293   *
3294   * @param int $pid The parent forum to start with
3295   * @param int $selitem The selected item ID
3296   * @param int $addselect If we need to add select boxes to this cal or not
3297   * @param string $depth The current depth of forums we're at
3298   * @param int $showextras Whether or not to show extra items such as User CP, Forum home
3299   * @param boolean $showall Ignore the showinjump setting and show all forums (for moderation pages)
3300   * @param mixed $permissions deprecated
3301   * @param string $name The name of the forum jump
3302   * @return string Forum jump items
3303   */
3304  function build_forum_jump($pid=0, $selitem=0, $addselect=1, $depth="", $showextras=1, $showall=false, $permissions="", $name="fid")
3305  {
3306      global $forum_cache, $jumpfcache, $permissioncache, $mybb, $forumjump, $forumjumpbits, $gobutton, $theme, $templates, $lang;
3307  
3308      $pid = (int)$pid;
3309  
3310      if(!is_array($jumpfcache))
3311      {
3312          if(!is_array($forum_cache))
3313          {
3314              cache_forums();
3315          }
3316  
3317          foreach($forum_cache as $fid => $forum)
3318          {
3319              if($forum['active'] != 0)
3320              {
3321                  $jumpfcache[$forum['pid']][$forum['disporder']][$forum['fid']] = $forum;
3322              }
3323          }
3324      }
3325  
3326      if(!is_array($permissioncache))
3327      {
3328          $permissioncache = forum_permissions();
3329      }
3330  
3331      if(isset($jumpfcache[$pid]) && is_array($jumpfcache[$pid]))
3332      {
3333          foreach($jumpfcache[$pid] as $main)
3334          {
3335              foreach($main as $forum)
3336              {
3337                  $perms = $permissioncache[$forum['fid']];
3338  
3339                  if($forum['fid'] != "0" && ($perms['canview'] != 0 || $mybb->settings['hideprivateforums'] == 0) && $forum['linkto'] == '' && ($forum['showinjump'] != 0 || $showall == true))
3340                  {
3341                      $optionselected = "";
3342  
3343                      if($selitem == $forum['fid'])
3344                      {
3345                          $optionselected = 'selected="selected"';
3346                      }
3347  
3348                      $forum['name'] = htmlspecialchars_uni(strip_tags($forum['name']));
3349  
3350                      eval("\$forumjumpbits .= \"".$templates->get("forumjump_bit")."\";");
3351  
3352                      if($forum_cache[$forum['fid']])
3353                      {
3354                          $newdepth = $depth."--";
3355                          $forumjumpbits .= build_forum_jump($forum['fid'], $selitem, 0, $newdepth, $showextras, $showall);
3356                      }
3357                  }
3358              }
3359          }
3360      }
3361  
3362      if($addselect)
3363      {
3364          if($showextras == 0)
3365          {
3366              $template = "special";
3367          }
3368          else
3369          {
3370              $template = "advanced";
3371  
3372              if(strpos(FORUM_URL, '.html') !== false)
3373              {
3374                  $forum_link = "'".str_replace('{fid}', "'+option+'", FORUM_URL)."'";
3375              }
3376              else
3377              {
3378                  $forum_link = "'".str_replace('{fid}', "'+option", FORUM_URL);
3379              }
3380          }
3381  
3382          eval("\$forumjump = \"".$templates->get("forumjump_".$template)."\";");
3383      }
3384  
3385      return $forumjump;
3386  }
3387  
3388  /**
3389   * Returns the extension of a file.
3390   *
3391   * @param string $file The filename.
3392   * @return string The extension of the file.
3393   */
3394  function get_extension($file)
3395  {
3396      return my_strtolower(my_substr(strrchr($file, "."), 1));
3397  }
3398  
3399  /**
3400   * Generates a random string.
3401   *
3402   * @param int $length The length of the string to generate.
3403   * @param bool $complex Whether to return complex string. Defaults to false
3404   * @return string The random string.
3405   */
3406  function random_str($length=8, $complex=false)
3407  {
3408      $set = array_merge(range(0, 9), range('A', 'Z'), range('a', 'z'));
3409      $str = array();
3410  
3411      // Complex strings have always at least 3 characters, even if $length < 3
3412      if($complex == true)
3413      {
3414          // At least one number
3415          $str[] = $set[my_rand(0, 9)];
3416  
3417          // At least one big letter
3418          $str[] = $set[my_rand(10, 35)];
3419  
3420          // At least one small letter
3421          $str[] = $set[my_rand(36, 61)];
3422  
3423          $length -= 3;
3424      }
3425  
3426      for($i = 0; $i < $length; ++$i)
3427      {
3428          $str[] = $set[my_rand(0, 61)];
3429      }
3430  
3431      // Make sure they're in random order and convert them to a string
3432      shuffle($str);
3433  
3434      return implode($str);
3435  }
3436  
3437  /**
3438   * Formats a username based on their display group
3439   *
3440   * @param string $username The username
3441   * @param int $usergroup The usergroup for the user
3442   * @param int $displaygroup The display group for the user
3443   * @return string The formatted username
3444   */
3445  function format_name($username, $usergroup, $displaygroup=0)
3446  {
3447      global $groupscache, $cache, $plugins;
3448  
3449      static $formattednames = array();
3450  
3451      if(!isset($formattednames[$username]))
3452      {
3453          if(!is_array($groupscache))
3454          {
3455              $groupscache = $cache->read("usergroups");
3456          }
3457  
3458          if($displaygroup != 0)
3459          {
3460              $usergroup = $displaygroup;
3461          }
3462  
3463          $format = "{username}";
3464  
3465          if(isset($groupscache[$usergroup]))
3466          {
3467              $ugroup = $groupscache[$usergroup];
3468  
3469              if(strpos($ugroup['namestyle'], "{username}") !== false)
3470              {
3471                  $format = $ugroup['namestyle'];
3472              }
3473          }
3474  
3475          $format = stripslashes($format);
3476  
3477          $parameters = compact('username', 'usergroup', 'displaygroup', 'format');
3478  
3479          $parameters = $plugins->run_hooks('format_name', $parameters);
3480  
3481          $format = $parameters['format'];
3482  
3483          $formattednames[$username] = str_replace("{username}", $username, $format);
3484      }
3485  
3486      return $formattednames[$username];
3487  }
3488  
3489  /**
3490   * Formats an avatar to a certain dimension
3491   *
3492   * @param string $avatar The avatar file name
3493   * @param string $dimensions Dimensions of the avatar, width x height (e.g. 44|44)
3494   * @param string $max_dimensions The maximum dimensions of the formatted avatar
3495   * @return array Information for the formatted avatar
3496   */
3497  function format_avatar($avatar, $dimensions = '', $max_dimensions = '')
3498  {
3499      global $mybb, $theme;
3500      static $avatars;
3501  
3502      if(!isset($avatars))
3503      {
3504          $avatars = array();
3505      }
3506  
3507      if(my_strpos($avatar, '://') !== false && !$mybb->settings['allowremoteavatars'])
3508      {
3509          // Remote avatar, but remote avatars are disallowed.
3510          $avatar = null;
3511      }
3512  
3513      if(!$avatar)
3514      {
3515          // Default avatar
3516          if(defined('IN_ADMINCP'))
3517          {
3518              $theme['imgdir'] = '../images';
3519          }
3520  
3521          $avatar = str_replace('{theme}', $theme['imgdir'], $mybb->settings['useravatar']);
3522          $dimensions = $mybb->settings['useravatardims'];
3523      }
3524  
3525      if(!$max_dimensions)
3526      {
3527          $max_dimensions = $mybb->settings['maxavatardims'];
3528      }
3529  
3530      // An empty key wouldn't work so we need to add a fall back
3531      $key = $dimensions;
3532      if(empty($key))
3533      {
3534          $key = 'default';
3535      }
3536      $key2 = $max_dimensions;
3537      if(empty($key2))
3538      {
3539          $key2 = 'default';
3540      }
3541  
3542      if(isset($avatars[$avatar][$key][$key2]))
3543      {
3544          return $avatars[$avatar][$key][$key2];
3545      }
3546  
3547      $avatar_width_height = '';
3548  
3549      if($dimensions)
3550      {
3551          $dimensions = preg_split('/[|x]/', $dimensions);
3552  
3553          if($dimensions[0] && $dimensions[1])
3554          {
3555              list($max_width, $max_height) = preg_split('/[|x]/', $max_dimensions);
3556  
3557              if(!empty($max_dimensions) && ($dimensions[0] > $max_width || $dimensions[1] > $max_height))
3558              {
3559                  require_once  MYBB_ROOT."inc/functions_image.php";
3560                  $scaled_dimensions = scale_image($dimensions[0], $dimensions[1], $max_width, $max_height);
3561                  $avatar_width_height = "width=\"{$scaled_dimensions['width']}\" height=\"{$scaled_dimensions['height']}\"";
3562              }
3563              else
3564              {
3565                  $avatar_width_height = "width=\"{$dimensions[0]}\" height=\"{$dimensions[1]}\"";
3566              }
3567          }
3568      }
3569  
3570      $avatars[$avatar][$key][$key2] = array(
3571          'image' => htmlspecialchars_uni($mybb->get_asset_url($avatar)),
3572          'width_height' => $avatar_width_height
3573      );
3574  
3575      return $avatars[$avatar][$key][$key2];
3576  }
3577  
3578  /**
3579   * Build the javascript based MyCode inserter.
3580   *
3581   * @param string $bind The ID of the textarea to bind to. Defaults to "message".
3582   * @param bool $smilies Whether to include smilies. Defaults to true.
3583   *
3584   * @return string The MyCode inserter
3585   */
3586  function build_mycode_inserter($bind="message", $smilies = true)
3587  {
3588      global $db, $mybb, $theme, $templates, $lang, $plugins, $smiliecache, $cache;
3589  
3590      if($mybb->settings['bbcodeinserter'] != 0)
3591      {
3592          $editor_lang_strings = array(
3593              "editor_bold" => "Bold",
3594              "editor_italic" => "Italic",
3595              "editor_underline" => "Underline",
3596              "editor_strikethrough" => "Strikethrough",
3597              "editor_subscript" => "Subscript",
3598              "editor_superscript" => "Superscript",
3599              "editor_alignleft" => "Align left",
3600              "editor_center" => "Center",
3601              "editor_alignright" => "Align right",
3602              "editor_justify" => "Justify",
3603              "editor_fontname" => "Font Name",
3604              "editor_fontsize" => "Font Size",
3605              "editor_fontcolor" => "Font Color",
3606              "editor_removeformatting" => "Remove Formatting",
3607              "editor_cut" => "Cut",
3608              "editor_cutnosupport" => "Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X",
3609              "editor_copy" => "Copy",
3610              "editor_copynosupport" => "Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C",
3611              "editor_paste" => "Paste",
3612              "editor_pastenosupport" => "Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V",
3613              "editor_pasteentertext" => "Paste your text inside the following box:",
3614              "editor_pastetext" => "PasteText",
3615              "editor_numlist" => "Numbered list",
3616              "editor_bullist" => "Bullet list",
3617              "editor_undo" => "Undo",
3618              "editor_redo" => "Redo",
3619              "editor_rows" => "Rows:",
3620              "editor_cols" => "Cols:",
3621              "editor_inserttable" => "Insert a table",
3622              "editor_inserthr" => "Insert a horizontal rule",
3623              "editor_code" => "Code",
3624              "editor_width" => "Width (optional):",
3625              "editor_height" => "Height (optional):",
3626              "editor_insertimg" => "Insert an image",
3627              "editor_email" => "E-mail:",
3628              "editor_insertemail" => "Insert an email",
3629              "editor_url" => "URL:",
3630              "editor_insertlink" => "Insert a link",
3631              "editor_unlink" => "Unlink",
3632              "editor_more" => "More",
3633              "editor_insertemoticon" => "Insert an emoticon",
3634              "editor_videourl" => "Video URL:",
3635              "editor_videotype" => "Video Type:",
3636              "editor_insert" => "Insert",
3637              "editor_insertyoutubevideo" => "Insert a YouTube video",
3638              "editor_currentdate" => "Insert current date",
3639              "editor_currenttime" => "Insert current time",
3640              "editor_print" => "Print",
3641              "editor_viewsource" => "View source",
3642              "editor_description" => "Description (optional):",
3643              "editor_enterimgurl" => "Enter the image URL:",
3644              "editor_enteremail" => "Enter the e-mail address:",
3645              "editor_enterdisplayedtext" => "Enter the displayed text:",
3646              "editor_enterurl" => "Enter URL:",
3647              "editor_enteryoutubeurl" => "Enter the YouTube video URL or ID:",
3648              "editor_insertquote" => "Insert a Quote",
3649              "editor_invalidyoutube" => "Invalid YouTube video",
3650              "editor_dailymotion" => "Dailymotion",
3651              "editor_metacafe" => "MetaCafe",
3652              "editor_mixer" => "Mixer",
3653              "editor_vimeo" => "Vimeo",
3654              "editor_youtube" => "Youtube",
3655              "editor_facebook" => "Facebook",
3656              "editor_liveleak" => "LiveLeak",
3657              "editor_insertvideo" => "Insert a video",
3658              "editor_php" => "PHP",
3659              "editor_maximize" => "Maximize"
3660          );
3661          $editor_language = "(function ($) {\n$.sceditor.locale[\"mybblang\"] = {\n";
3662  
3663          $editor_lang_strings = $plugins->run_hooks("mycode_add_codebuttons", $editor_lang_strings);
3664  
3665          $editor_languages_count = count($editor_lang_strings);
3666          $i = 0;
3667          foreach($editor_lang_strings as $lang_string => $key)
3668          {
3669              $i++;
3670              $js_lang_string = str_replace("\"", "\\\"", $key);
3671              $string = str_replace("\"", "\\\"", $lang->$lang_string);
3672              $editor_language .= "\t\"{$js_lang_string}\": \"{$string}\"";
3673  
3674              if($i < $editor_languages_count)
3675              {
3676                  $editor_language .= ",";
3677              }
3678  
3679              $editor_language .= "\n";
3680          }
3681  
3682          $editor_language .= "}})(jQuery);";
3683  
3684          if(defined("IN_ADMINCP"))
3685          {
3686              global $page;
3687              $codeinsert = $page->build_codebuttons_editor($bind, $editor_language, $smilies);
3688          }
3689          else
3690          {
3691              // Smilies
3692              $emoticon = "";
3693              $emoticons_enabled = "false";
3694              if($smilies)
3695              {
3696                  if(!$smiliecache)
3697                  {
3698                      if(!isset($smilie_cache) || !is_array($smilie_cache))
3699                      {
3700                          $smilie_cache = $cache->read("smilies");
3701                      }
3702                      foreach($smilie_cache as $smilie)
3703                      {
3704                          $smilie['image'] = str_replace("{theme}", $theme['imgdir'], $smilie['image']);
3705                          $smiliecache[$smilie['sid']] = $smilie;
3706                      }
3707                  }
3708  
3709                  if($mybb->settings['smilieinserter'] && $mybb->settings['smilieinsertercols'] && $mybb->settings['smilieinsertertot'] && !empty($smiliecache))
3710                  {
3711                      $emoticon = ",emoticon";
3712                  }
3713                  $emoticons_enabled = "true";
3714  
3715                  unset($smilie);
3716  
3717                  if(is_array($smiliecache))
3718                  {
3719                      reset($smiliecache);
3720  
3721                      $dropdownsmilies = $moresmilies = $hiddensmilies = "";
3722                      $i = 0;
3723  
3724                      foreach($smiliecache as $smilie)
3725                      {
3726                          $finds = explode("\n", $smilie['find']);
3727                          $finds_count = count($finds);
3728  
3729                          // Only show the first text to replace in the box
3730                          $smilie['find'] = $finds[0];
3731  
3732                          $find = str_replace(array('\\', '"'), array('\\\\', '\"'), htmlspecialchars_uni($smilie['find']));
3733                          $image = htmlspecialchars_uni($mybb->get_asset_url($smilie['image']));
3734                          $image = str_replace(array('\\', '"'), array('\\\\', '\"'), $image);
3735  
3736                          if(!$mybb->settings['smilieinserter'] || !$mybb->settings['smilieinsertercols'] || !$mybb->settings['smilieinsertertot'] || !$smilie['showclickable'])
3737                          {
3738                              $hiddensmilies .= '"'.$find.'": "'.$image.'",';
3739                          }
3740                          elseif($i < $mybb->settings['smilieinsertertot'])
3741                          {
3742                              $dropdownsmilies .= '"'.$find.'": "'.$image.'",';
3743                              ++$i;
3744                          }
3745                          else
3746                          {
3747                              $moresmilies .= '"'.$find.'": "'.$image.'",';
3748                          }
3749  
3750                          for($j = 1; $j < $finds_count; ++$j)
3751                          {
3752                              $find = str_replace(array('\\', '"'), array('\\\\', '\"'), htmlspecialchars_uni($finds[$j]));
3753                              $hiddensmilies .= '"'.$find.'": "'.$image.'",';
3754                          }
3755                      }
3756                  }
3757              }
3758  
3759              $basic1 = $basic2 = $align = $font = $size = $color = $removeformat = $email = $link = $list = $code = $sourcemode = "";
3760  
3761              if($mybb->settings['allowbasicmycode'] == 1)
3762              {
3763                  $basic1 = "bold,italic,underline,strike|";
3764                  $basic2 = "horizontalrule,";
3765              }
3766  
3767              if($mybb->settings['allowalignmycode'] == 1)
3768              {
3769                  $align = "left,center,right,justify|";
3770              }
3771  
3772              if($mybb->settings['allowfontmycode'] == 1)
3773              {
3774                  $font = "font,";
3775              }
3776  
3777              if($mybb->settings['allowsizemycode'] == 1)
3778              {
3779                  $size = "size,";
3780              }
3781  
3782              if($mybb->settings['allowcolormycode'] == 1)
3783              {
3784                  $color = "color,";
3785              }
3786  
3787              if($mybb->settings['allowfontmycode'] == 1 || $mybb->settings['allowsizemycode'] == 1 || $mybb->settings['allowcolormycode'] == 1)
3788              {
3789                  $removeformat = "removeformat|";
3790              }
3791  
3792              if($mybb->settings['allowemailmycode'] == 1)
3793              {
3794                  $email = "email,";
3795              }
3796  
3797              if($mybb->settings['allowlinkmycode'] == 1)
3798              {
3799                  $link = "link,unlink";
3800              }
3801  
3802              if($mybb->settings['allowlistmycode'] == 1)
3803              {
3804                  $list = "bulletlist,orderedlist|";
3805              }
3806  
3807              if($mybb->settings['allowcodemycode'] == 1)
3808              {
3809                  $code = "code,php,";
3810              }
3811  
3812              if($mybb->user['sourceeditor'] == 1)
3813              {
3814                  $sourcemode = "MyBBEditor.sourceMode(true);";
3815              }
3816  
3817              eval("\$codeinsert = \"".$templates->get("codebuttons")."\";");
3818          }
3819      }
3820  
3821      return $codeinsert;
3822  }
3823  
3824  /**
3825   * @param int $tid
3826   * @param array $postoptions The options carried with form submit
3827   *
3828   * @return string Predefined / updated subscription method of the thread for the user
3829   */
3830  function get_subscription_method($tid = 0, $postoptions = array())
3831  {
3832      global $mybb;
3833  
3834      $subscription_methods = array('', 'none', 'email', 'pm'); // Define methods
3835      $subscription_method = (int)$mybb->user['subscriptionmethod']; // Set user default
3836  
3837      // If no user default method available then reset method
3838      if(!$subscription_method)
3839      {
3840          $subscription_method = 0;
3841      }
3842  
3843      // Return user default if no thread id available, in case
3844      if(!(int)$tid || (int)$tid <= 0)
3845      {
3846          return $subscription_methods[$subscription_method];
3847      }
3848  
3849      // If method not predefined set using data from database
3850      if(isset($postoptions['subscriptionmethod']))
3851      {
3852          $method = trim($postoptions['subscriptionmethod']);
3853          return (in_array($method, $subscription_methods)) ? $method : $subscription_methods[0];
3854      }
3855      else
3856      {
3857          global $db;
3858  
3859          $query = $db->simple_select("threadsubscriptions", "tid, notification", "tid='".(int)$tid."' AND uid='".$mybb->user['uid']."'", array('limit' => 1));
3860          $subscription = $db->fetch_array($query);
3861  
3862          if(!empty($subscription) && $subscription['tid'])
3863          {
3864              $subscription_method = (int)$subscription['notification'] + 1;
3865          }
3866      }
3867  
3868      return $subscription_methods[$subscription_method];
3869  }
3870  
3871  /**
3872   * Build the javascript clickable smilie inserter
3873   *
3874   * @return string The clickable smilies list
3875   */
3876  function build_clickable_smilies()
3877  {
3878      global $cache, $smiliecache, $theme, $templates, $lang, $mybb, $smiliecount;
3879  
3880      if($mybb->settings['smilieinserter'] != 0 && $mybb->settings['smilieinsertercols'] && $mybb->settings['smilieinsertertot'])
3881      {
3882          if(!$smiliecount)
3883          {
3884              $smilie_cache = $cache->read("smilies");
3885              $smiliecount = count($smilie_cache);
3886          }
3887  
3888          if(!$smiliecache)
3889          {
3890              if(!is_array($smilie_cache))
3891              {
3892                  $smilie_cache = $cache->read("smilies");
3893              }
3894              foreach($smilie_cache as $smilie)
3895              {
3896                  $smilie['image'] = str_replace("{theme}", $theme['imgdir'], $smilie['image']);
3897                  $smiliecache[$smilie['sid']] = $smilie;
3898              }
3899          }
3900  
3901          unset($smilie);
3902  
3903          if(is_array($smiliecache))
3904          {
3905              reset($smiliecache);
3906  
3907              $getmore = '';
3908              if($mybb->settings['smilieinsertertot'] >= $smiliecount)
3909              {
3910                  $mybb->settings['smilieinsertertot'] = $smiliecount;
3911              }
3912              else if($mybb->settings['smilieinsertertot'] < $smiliecount)
3913              {
3914                  $smiliecount = $mybb->settings['smilieinsertertot'];
3915                  eval("\$getmore = \"".$templates->get("smilieinsert_getmore")."\";");
3916              }
3917  
3918              $smilies = $smilie_icons = '';
3919              $counter = 0;
3920              $i = 0;
3921  
3922              $extra_class = '';
3923              foreach($smiliecache as $smilie)
3924              {
3925                  if($i < $mybb->settings['smilieinsertertot'] && $smilie['showclickable'] != 0)
3926                  {
3927                      $smilie['image'] = str_replace("{theme}", $theme['imgdir'], $smilie['image']);
3928                      $smilie['image'] = htmlspecialchars_uni($mybb->get_asset_url($smilie['image']));
3929                      $smilie['name'] = htmlspecialchars_uni($smilie['name']);
3930  
3931                      // Only show the first text to replace in the box
3932                      $temp = explode("\n", $smilie['find']); // assign to temporary variable for php 5.3 compatibility
3933                      $smilie['find'] = $temp[0];
3934  
3935                      $find = str_replace(array('\\', "'"), array('\\\\', "\'"), htmlspecialchars_uni($smilie['find']));
3936  
3937                      $onclick = " onclick=\"MyBBEditor.insertText(' $find ');\"";
3938                      $extra_class = ' smilie_pointer';
3939                      eval('$smilie = "'.$templates->get('smilie', 1, 0).'";');
3940                      eval("\$smilie_icons .= \"".$templates->get("smilieinsert_smilie")."\";");
3941                      ++$i;
3942                      ++$counter;
3943  
3944                      if($counter == $mybb->settings['smilieinsertercols'])
3945                      {
3946                          $counter = 0;
3947                          eval("\$smilies .= \"".$templates->get("smilieinsert_row")."\";");
3948                          $smilie_icons = '';
3949                      }
3950                  }
3951              }
3952  
3953              if($counter != 0)
3954              {
3955                  $colspan = $mybb->settings['smilieinsertercols'] - $counter;
3956                  eval("\$smilies .= \"".$templates->get("smilieinsert_row_empty")."\";");
3957              }
3958  
3959              eval("\$clickablesmilies = \"".$templates->get("smilieinsert")."\";");
3960          }
3961          else
3962          {
3963              $clickablesmilies = "";
3964          }
3965      }
3966      else
3967      {
3968          $clickablesmilies = "";
3969      }
3970  
3971      return $clickablesmilies;
3972  }
3973  
3974  /**
3975   * Builds thread prefixes and returns a selected prefix (or all)
3976   *
3977   *  @param int $pid The prefix ID (0 to return all)
3978   *  @return array The thread prefix's values (or all thread prefixes)
3979   */
3980  function build_prefixes($pid=0)
3981  {
3982      global $cache;
3983      static $prefixes_cache;
3984  
3985      if(is_array($prefixes_cache))
3986      {
3987          if($pid > 0 && is_array($prefixes_cache[$pid]))
3988          {
3989              return $prefixes_cache[$pid];
3990          }
3991  
3992          return $prefixes_cache;
3993      }
3994  
3995      $prefix_cache = $cache->read("threadprefixes");
3996  
3997      if(!is_array($prefix_cache))
3998      {
3999          // No cache
4000          $prefix_cache = $cache->read("threadprefixes", true);
4001  
4002          if(!is_array($prefix_cache))
4003          {
4004              return array();
4005          }
4006      }
4007  
4008      $prefixes_cache = array();
4009      foreach($prefix_cache as $prefix)
4010      {
4011          $prefixes_cache[$prefix['pid']] = $prefix;
4012      }
4013  
4014      if($pid != 0 && is_array($prefixes_cache[$pid]))
4015      {
4016          return $prefixes_cache[$pid];
4017      }
4018      else if(!empty($prefixes_cache))
4019      {
4020          return $prefixes_cache;
4021      }
4022  
4023      return false;
4024  }
4025  
4026  /**
4027   * Build the thread prefix selection menu for the current user
4028   *
4029   *  @param int|string $fid The forum ID (integer ID or string all)
4030   *  @param int|string $selected_pid The selected prefix ID (integer ID or string any)
4031   *  @param int $multiple Allow multiple prefix selection
4032   *  @param int $previous_pid The previously selected prefix ID
4033   *  @return string The thread prefix selection menu
4034   */
4035  function build_prefix_select($fid, $selected_pid=0, $multiple=0, $previous_pid=0)
4036  {
4037      global $cache, $db, $lang, $mybb, $templates;
4038  
4039      if($fid != 'all')
4040      {
4041          $fid = (int)$fid;
4042      }
4043  
4044      $prefix_cache = build_prefixes(0);
4045      if(empty($prefix_cache))
4046      {
4047          // We've got no prefixes to show
4048          return '';
4049      }
4050  
4051      // Go through each of our prefixes and decide which ones we can use
4052      $prefixes = array();
4053      foreach($prefix_cache as $prefix)
4054      {
4055          if($fid != "all" && $prefix['forums'] != "-1")
4056          {
4057              // Decide whether this prefix can be used in our forum
4058              $forums = explode(",", $prefix['forums']);
4059  
4060              if(!in_array($fid, $forums) && $prefix['pid'] != $previous_pid)
4061              {
4062                  // This prefix is not in our forum list
4063                  continue;
4064              }
4065          }
4066  
4067          if(is_member($prefix['groups']) || $prefix['pid'] == $previous_pid)
4068          {
4069              // The current user can use this prefix
4070              $prefixes[$prefix['pid']] = $prefix;
4071          }
4072      }
4073  
4074      if(empty($prefixes))
4075      {
4076          return '';
4077      }
4078  
4079      $prefixselect = $prefixselect_prefix = '';
4080  
4081      if($multiple == 1)
4082      {
4083          $any_selected = "";
4084          if($selected_pid == 'any')
4085          {
4086              $any_selected = " selected=\"selected\"";
4087          }
4088      }
4089  
4090      $default_selected = "";
4091      if(((int)$selected_pid == 0) && $selected_pid != 'any')
4092      {
4093          $default_selected = " selected=\"selected\"";
4094      }
4095  
4096      foreach($prefixes as $prefix)
4097      {
4098          $selected = "";
4099          if($prefix['pid'] == $selected_pid)
4100          {
4101              $selected = " selected=\"selected\"";
4102          }
4103  
4104          $prefix['prefix'] = htmlspecialchars_uni($prefix['prefix']);
4105          eval("\$prefixselect_prefix .= \"".$templates->get("post_prefixselect_prefix")."\";");
4106      }
4107  
4108      if($multiple != 0)
4109      {
4110          eval("\$prefixselect = \"".$templates->get("post_prefixselect_multiple")."\";");
4111      }
4112      else
4113      {
4114          eval("\$prefixselect = \"".$templates->get("post_prefixselect_single")."\";");
4115      }
4116  
4117      return $prefixselect;
4118  }
4119  
4120  /**
4121   * Build the thread prefix selection menu for a forum without group permission checks
4122   *
4123   *  @param int $fid The forum ID (integer ID)
4124   *  @param int $selected_pid The selected prefix ID (integer ID)
4125   *  @return string The thread prefix selection menu
4126   */
4127  function build_forum_prefix_select($fid, $selected_pid=0)
4128  {
4129      global $cache, $db, $lang, $mybb, $templates;
4130  
4131      $fid = (int)$fid;
4132  
4133      $prefix_cache = build_prefixes(0);
4134      if(empty($prefix_cache))
4135      {
4136          // We've got no prefixes to show
4137          return '';
4138      }
4139  
4140      // Go through each of our prefixes and decide which ones we can use
4141      $prefixes = array();
4142      foreach($prefix_cache as $prefix)
4143      {
4144          if($prefix['forums'] != "-1")
4145          {
4146              // Decide whether this prefix can be used in our forum
4147              $forums = explode(",", $prefix['forums']);
4148  
4149              if(in_array($fid, $forums))
4150              {
4151                  // This forum can use this prefix!
4152                  $prefixes[$prefix['pid']] = $prefix;
4153              }
4154          }
4155          else
4156          {
4157              // This prefix is for anybody to use...
4158              $prefixes[$prefix['pid']] = $prefix;
4159          }
4160      }
4161  
4162      if(empty($prefixes))
4163      {
4164          return '';
4165      }
4166  
4167      $default_selected = array('all' => '', 'none' => '', 'any' => '');
4168      $selected_pid = (int)$selected_pid;
4169  
4170      if($selected_pid == 0)
4171      {
4172          $default_selected['all'] = ' selected="selected"';
4173      }
4174      else if($selected_pid == -1)
4175      {
4176          $default_selected['none'] = ' selected="selected"';
4177      }
4178      else if($selected_pid == -2)
4179      {
4180          $default_selected['any'] = ' selected="selected"';
4181      }
4182  
4183      $prefixselect_prefix = '';
4184      foreach($prefixes as $prefix)
4185      {
4186          $selected = '';
4187          if($prefix['pid'] == $selected_pid)
4188          {
4189              $selected = ' selected="selected"';
4190          }
4191  
4192          $prefix['prefix'] = htmlspecialchars_uni($prefix['prefix']);
4193          eval('$prefixselect_prefix .= "'.$templates->get("forumdisplay_threadlist_prefixes_prefix").'";');
4194      }
4195  
4196      eval('$prefixselect = "'.$templates->get("forumdisplay_threadlist_prefixes").'";');
4197      return $prefixselect;
4198  }
4199  
4200  /**
4201   * Gzip encodes text to a specified level
4202   *
4203   * @param string $contents The string to encode
4204   * @param int $level The level (1-9) to encode at
4205   * @return string The encoded string
4206   */
4207  function gzip_encode($contents, $level=1)
4208  {
4209      if(function_exists("gzcompress") && function_exists("crc32") && !headers_sent() && !(ini_get('output_buffering') && my_strpos(' '.ini_get('output_handler'), 'ob_gzhandler')))
4210      {
4211          $httpaccept_encoding = '';
4212  
4213          if(isset($_SERVER['HTTP_ACCEPT_ENCODING']))
4214          {
4215              $httpaccept_encoding = $_SERVER['HTTP_ACCEPT_ENCODING'];
4216          }
4217  
4218          if(my_strpos(" ".$httpaccept_encoding, "x-gzip"))
4219          {
4220              $encoding = "x-gzip";
4221          }
4222  
4223          if(my_strpos(" ".$httpaccept_encoding, "gzip"))
4224          {
4225              $encoding = "gzip";
4226          }
4227  
4228          if(isset($encoding))
4229          {
4230              header("Content-Encoding: $encoding");
4231  
4232              if(function_exists("gzencode"))
4233              {
4234                  $contents = gzencode($contents, $level);
4235              }
4236              else
4237              {
4238                  $size = strlen($contents);
4239                  $crc = crc32($contents);
4240                  $gzdata = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff";
4241                  $gzdata .= my_substr(gzcompress($contents, $level), 2, -4);
4242                  $gzdata .= pack("V", $crc);
4243                  $gzdata .= pack("V", $size);
4244                  $contents = $gzdata;
4245              }
4246          }
4247      }
4248  
4249      return $contents;
4250  }
4251  
4252  /**
4253   * Log the actions of a moderator.
4254   *
4255   * @param array $data The data of the moderator's action.
4256   * @param string $action The message to enter for the action the moderator performed.
4257   */
4258  function log_moderator_action($data, $action="")
4259  {
4260      global $mybb, $db, $session;
4261  
4262      $fid = 0;
4263      if(isset($data['fid']))
4264      {
4265          $fid = (int)$data['fid'];
4266          unset($data['fid']);
4267      }
4268  
4269      $tid = 0;
4270      if(isset($data['tid']))
4271      {
4272          $tid = (int)$data['tid'];
4273          unset($data['tid']);
4274      }
4275  
4276      $pid = 0;
4277      if(isset($data['pid']))
4278      {
4279          $pid = (int)$data['pid'];
4280          unset($data['pid']);
4281      }
4282  
4283      $tids = array();
4284      if(isset($data['tids']))
4285      {
4286          $tids = (array)$data['tids'];
4287          unset($data['tids']);
4288      }
4289  
4290      // Any remaining extra data - we my_serialize and insert in to its own column
4291      if(is_array($data))
4292      {
4293          $data = my_serialize($data);
4294      }
4295  
4296      $sql_array = array(
4297          "uid" => (int)$mybb->user['uid'],
4298          "dateline" => TIME_NOW,
4299          "fid" => (int)$fid,
4300          "tid" => $tid,
4301          "pid" => $pid,
4302          "action" => $db->escape_string($action),
4303          "data" => $db->escape_string($data),
4304          "ipaddress" => $db->escape_binary($session->packedip)
4305      );
4306  
4307      if($tids)
4308      {
4309          $multiple_sql_array = array();
4310  
4311          foreach($tids as $tid)
4312          {
4313              $sql_array['tid'] = (int)$tid;
4314              $multiple_sql_array[] = $sql_array;
4315          }
4316  
4317          $db->insert_query_multiple("moderatorlog", $multiple_sql_array);
4318      }
4319      else
4320      {
4321          $db->insert_query("moderatorlog", $sql_array);
4322      }
4323  }
4324  
4325  /**
4326   * Get the formatted reputation for a user.
4327   *
4328   * @param int $reputation The reputation value
4329   * @param int $uid The user ID (if not specified, the generated reputation will not be a link)
4330   * @return string The formatted repuation
4331   */
4332  function get_reputation($reputation, $uid=0)
4333  {
4334      global $theme, $templates;
4335  
4336      $display_reputation = $reputation_class = '';
4337      if($reputation < 0)
4338      {
4339          $reputation_class = "reputation_negative";
4340      }
4341      elseif($reputation > 0)
4342      {
4343          $reputation_class = "reputation_positive";
4344      }
4345      else
4346      {
4347          $reputation_class = "reputation_neutral";
4348      }
4349  
4350      $reputation = my_number_format($reputation);
4351  
4352      if($uid != 0)
4353      {
4354          eval("\$display_reputation = \"".$templates->get("postbit_reputation_formatted_link")."\";");
4355      }
4356      else
4357      {
4358          eval("\$display_reputation = \"".$templates->get("postbit_reputation_formatted")."\";");
4359      }
4360  
4361      return $display_reputation;
4362  }
4363  
4364  /**
4365   * Fetch a color coded version of a warning level (based on it's percentage)
4366   *
4367   * @param int $level The warning level (percentage of 100)
4368   * @return string Formatted warning level
4369   */
4370  function get_colored_warning_level($level)
4371  {
4372      global $templates;
4373  
4374      $warning_class = '';
4375      if($level >= 80)
4376      {
4377          $warning_class = "high_warning";
4378      }
4379      else if($level >= 50)
4380      {
4381          $warning_class = "moderate_warning";
4382      }
4383      else if($level >= 25)
4384      {
4385          $warning_class = "low_warning";
4386      }
4387      else
4388      {
4389          $warning_class = "normal_warning";
4390      }
4391  
4392      eval("\$level = \"".$templates->get("postbit_warninglevel_formatted")."\";");
4393      return $level;
4394  }
4395  
4396  /**
4397   * Fetch the IP address of the current user.
4398   *
4399   * @return string The IP address.
4400   */
4401  function get_ip()
4402  {
4403      global $mybb, $plugins;
4404  
4405      $ip = strtolower($_SERVER['REMOTE_ADDR']);
4406  
4407      if($mybb->settings['ip_forwarded_check'])
4408      {
4409          $addresses = array();
4410  
4411          if(isset($_SERVER['HTTP_X_FORWARDED_FOR']))
4412          {
4413              $addresses = explode(',', strtolower($_SERVER['HTTP_X_FORWARDED_FOR']));
4414          }
4415          elseif(isset($_SERVER['HTTP_X_REAL_IP']))
4416          {
4417              $addresses = explode(',', strtolower($_SERVER['HTTP_X_REAL_IP']));
4418          }
4419  
4420          if(is_array($addresses))
4421          {
4422              foreach($addresses as $val)
4423              {
4424                  $val = trim($val);
4425                  // Validate IP address and exclude private addresses
4426                  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))
4427                  {
4428                      $ip = $val;
4429                      break;
4430                  }
4431              }
4432          }
4433      }
4434  
4435      if(!$ip)
4436      {
4437          if(isset($_SERVER['HTTP_CLIENT_IP']))
4438          {
4439              $ip = strtolower($_SERVER['HTTP_CLIENT_IP']);
4440          }
4441      }
4442  
4443      if($plugins)
4444      {
4445          $ip_array = array("ip" => &$ip); // Used for backwards compatibility on this hook with the updated run_hooks() function.
4446          $plugins->run_hooks("get_ip", $ip_array);
4447      }
4448  
4449      return $ip;
4450  }
4451  
4452  /**
4453   * Fetch the friendly size (GB, MB, KB, B) for a specified file size.
4454   *
4455   * @param int $size The size in bytes
4456   * @return string The friendly file size
4457   */
4458  function get_friendly_size($size)
4459  {
4460      global $lang;
4461  
4462      if(!is_numeric($size))
4463      {
4464          return $lang->na;
4465      }
4466  
4467      // Yottabyte (1024 Zettabytes)
4468      if($size >= 1208925819614629174706176)
4469      {
4470          $size = my_number_format(round(($size / 1208925819614629174706176), 2))." ".$lang->size_yb;
4471      }
4472      // Zetabyte (1024 Exabytes)
4473      elseif($size >= 1180591620717411303424)
4474      {
4475          $size = my_number_format(round(($size / 1180591620717411303424), 2))." ".$lang->size_zb;
4476      }
4477      // Exabyte (1024 Petabytes)
4478      elseif($size >= 1152921504606846976)
4479      {
4480          $size = my_number_format(round(($size / 1152921504606846976), 2))." ".$lang->size_eb;
4481      }
4482      // Petabyte (1024 Terabytes)
4483      elseif($size >= 1125899906842624)
4484      {
4485          $size = my_number_format(round(($size / 1125899906842624), 2))." ".$lang->size_pb;
4486      }
4487      // Terabyte (1024 Gigabytes)
4488      elseif($size >= 1099511627776)
4489      {
4490          $size = my_number_format(round(($size / 1099511627776), 2))." ".$lang->size_tb;
4491      }
4492      // Gigabyte (1024 Megabytes)
4493      elseif($size >= 1073741824)
4494      {
4495          $size = my_number_format(round(($size / 1073741824), 2))." ".$lang->size_gb;
4496      }
4497      // Megabyte (1024 Kilobytes)
4498      elseif($size >= 1048576)
4499      {
4500          $size = my_number_format(round(($size / 1048576), 2))." ".$lang->size_mb;
4501      }
4502      // Kilobyte (1024 bytes)
4503      elseif($size >= 1024)
4504      {
4505          $size = my_number_format(round(($size / 1024), 2))." ".$lang->size_kb;
4506      }
4507      elseif($size == 0)
4508      {
4509          $size = "0 ".$lang->size_bytes;
4510      }
4511      else
4512      {
4513          $size = my_number_format($size)." ".$lang->size_bytes;
4514      }
4515  
4516      return $size;
4517  }
4518  
4519  /**
4520   * Format a decimal number in to microseconds, milliseconds, or seconds.
4521   *
4522   * @param int $time The time in microseconds
4523   * @return string The friendly time duration
4524   */
4525  function format_time_duration($time)
4526  {
4527      global $lang;
4528  
4529      if(!is_numeric($time))
4530      {
4531          return $lang->na;
4532      }
4533  
4534      if(round(1000000 * $time, 2) < 1000)
4535      {
4536          $time = number_format(round(1000000 * $time, 2))." μs";
4537      }
4538      elseif(round(1000000 * $time, 2) >= 1000 && round(1000000 * $time, 2) < 1000000)
4539      {
4540          $time = number_format(round((1000 * $time), 2))." ms";
4541      }
4542      else
4543      {
4544          $time = round($time, 3)." seconds";
4545      }
4546  
4547      return $time;
4548  }
4549  
4550  /**
4551   * Get the attachment icon for a specific file extension
4552   *
4553   * @param string $ext The file extension
4554   * @return string The attachment icon
4555   */
4556  function get_attachment_icon($ext)
4557  {
4558      global $cache, $attachtypes, $theme, $templates, $lang, $mybb;
4559  
4560      if(!$attachtypes)
4561      {
4562          $attachtypes = $cache->read("attachtypes");
4563      }
4564  
4565      $ext = my_strtolower($ext);
4566  
4567      if($attachtypes[$ext]['icon'])
4568      {
4569          static $attach_icons_schemes = array();
4570          if(!isset($attach_icons_schemes[$ext]))
4571          {
4572              $attach_icons_schemes[$ext] = parse_url($attachtypes[$ext]['icon']);
4573              if(!empty($attach_icons_schemes[$ext]['scheme']))
4574              {
4575                  $attach_icons_schemes[$ext] = $attachtypes[$ext]['icon'];
4576              }
4577              elseif(defined("IN_ADMINCP"))
4578              {
4579                  $attach_icons_schemes[$ext] = str_replace("{theme}", "", $attachtypes[$ext]['icon']);
4580                  if(my_substr($attach_icons_schemes[$ext], 0, 1) != "/")
4581                  {
4582                      $attach_icons_schemes[$ext] = "../".$attach_icons_schemes[$ext];
4583                  }
4584              }
4585              elseif(defined("IN_PORTAL"))
4586              {
4587                  global $change_dir;
4588                  $attach_icons_schemes[$ext] = $change_dir."/".str_replace("{theme}", $theme['imgdir'], $attachtypes[$ext]['icon']);
4589                  $attach_icons_schemes[$ext] = $mybb->get_asset_url($attach_icons_schemes[$ext]);
4590              }
4591              else
4592              {
4593                  $attach_icons_schemes[$ext] = str_replace("{theme}", $theme['imgdir'], $attachtypes[$ext]['icon']);
4594                  $attach_icons_schemes[$ext] = $mybb->get_asset_url($attach_icons_schemes[$ext]);
4595              }
4596          }
4597  
4598          $icon = $attach_icons_schemes[$ext];
4599  
4600          $name = htmlspecialchars_uni($attachtypes[$ext]['name']);
4601      }
4602      else
4603      {
4604          if(defined("IN_ADMINCP"))
4605          {
4606              $theme['imgdir'] = "../images";
4607          }
4608          else if(defined("IN_PORTAL"))
4609          {
4610              global $change_dir;
4611              $theme['imgdir'] = "{$change_dir}/images";
4612          }
4613  
4614          $icon = "{$theme['imgdir']}/attachtypes/unknown.png";
4615  
4616          $name = $lang->unknown;
4617      }
4618  
4619      $icon = htmlspecialchars_uni(