[ Index ]

PHP Cross Reference of MyBB 1.8.38

title

Body

[close]

/inc/ -> functions.php (source)

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