[ Index ]

PHP Cross Reference of MyBB 1.8.28

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