[ Index ]

PHP Cross Reference of MyBB 1.8.40

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   * Additionally, if the SameSite Cookie Flag setting is enabled, verifies same-site request origin.
 739   *
 740   * @param string $code The incoming POST check code
 741   * @param boolean $silent Don't show an error to the user
 742   * @return bool|void Result boolean if $silent is true, otherwise shows an error to the user
 743   */
 744  function verify_post_check($code, $silent=false)
 745  {
 746      global $mybb, $lang;
 747      if(
 748          (
 749              generate_post_check() !== $code &&
 750              generate_post_check(-1) !== $code &&
 751              generate_post_check(-2) !== $code &&
 752              generate_post_check(-3) !== $code
 753          ) ||
 754          (
 755              $mybb->settings['cookiesamesiteflag'] == 1 &&
 756              isset($_SERVER['HTTP_SEC_FETCH_SITE']) &&
 757              !in_array(
 758                  $_SERVER['HTTP_SEC_FETCH_SITE'],
 759                  array('same-origin', 'same-site')
 760              )
 761          )
 762      )
 763      {
 764          if($silent == true)
 765          {
 766              return false;
 767          }
 768          else
 769          {
 770              if(defined("IN_ADMINCP"))
 771              {
 772                  return false;
 773              }
 774              else
 775              {
 776                  error($lang->invalid_post_code);
 777              }
 778          }
 779      }
 780      else
 781      {
 782          return true;
 783      }
 784  }
 785  
 786  /**
 787   * Return a parent list for the specified forum.
 788   *
 789   * @param int $fid The forum id to get the parent list for.
 790   * @return string The comma-separated parent list.
 791   */
 792  function get_parent_list($fid)
 793  {
 794      global $forum_cache;
 795      static $forumarraycache;
 796  
 797      if(!empty($forumarraycache[$fid]))
 798      {
 799          return $forumarraycache[$fid]['parentlist'];
 800      }
 801      elseif(!empty($forum_cache[$fid]))
 802      {
 803          return $forum_cache[$fid]['parentlist'];
 804      }
 805      else
 806      {
 807          cache_forums();
 808          return $forum_cache[$fid]['parentlist'];
 809      }
 810  }
 811  
 812  /**
 813   * Build a parent list of a specific forum, suitable for querying
 814   *
 815   * @param int $fid The forum ID
 816   * @param string $column The column name to add to the query
 817   * @param string $joiner The joiner for each forum for querying (OR | AND | etc)
 818   * @param string $parentlist The parent list of the forum - if you have it
 819   * @return string The query string generated
 820   */
 821  function build_parent_list($fid, $column="fid", $joiner="OR", $parentlist="")
 822  {
 823      if(!$parentlist)
 824      {
 825          $parentlist = get_parent_list($fid);
 826      }
 827  
 828      $parentsexploded = explode(",", $parentlist);
 829      $builtlist = "(";
 830      $sep = '';
 831  
 832      foreach($parentsexploded as $key => $val)
 833      {
 834          $builtlist .= "$sep$column='$val'";
 835          $sep = " $joiner ";
 836      }
 837  
 838      $builtlist .= ")";
 839  
 840      return $builtlist;
 841  }
 842  
 843  /**
 844   * Load the forum cache in to memory
 845   *
 846   * @param boolean $force True to force a reload of the cache
 847   * @return array The forum cache
 848   */
 849  function cache_forums($force=false)
 850  {
 851      global $forum_cache, $cache;
 852  
 853      if($force == true)
 854      {
 855          $forum_cache = $cache->read("forums", 1);
 856          return $forum_cache;
 857      }
 858  
 859      if(!$forum_cache)
 860      {
 861          $forum_cache = $cache->read("forums");
 862          if(!$forum_cache)
 863          {
 864              $cache->update_forums();
 865              $forum_cache = $cache->read("forums", 1);
 866          }
 867      }
 868      return $forum_cache;
 869  }
 870  
 871  /**
 872   * Generate an array of all child and descendant forums for a specific forum.
 873   *
 874   * @param int $fid The forum ID
 875   * @return Array of descendants
 876   */
 877  function get_child_list($fid)
 878  {
 879      static $forums_by_parent;
 880  
 881      $forums = array();
 882      if(!is_array($forums_by_parent))
 883      {
 884          $forum_cache = cache_forums();
 885          foreach($forum_cache as $forum)
 886          {
 887              if($forum['active'] != 0)
 888              {
 889                  $forums_by_parent[$forum['pid']][$forum['fid']] = $forum;
 890              }
 891          }
 892      }
 893      if(isset($forums_by_parent[$fid]))
 894      {
 895          if(!is_array($forums_by_parent[$fid]))
 896          {
 897              return $forums;
 898          }
 899  
 900          foreach($forums_by_parent[$fid] as $forum)
 901          {
 902              $forums[] = (int)$forum['fid'];
 903              $children = get_child_list($forum['fid']);
 904              if(is_array($children))
 905              {
 906                  $forums = array_merge($forums, $children);
 907              }
 908          }
 909      }
 910      return $forums;
 911  }
 912  
 913  /**
 914   * Produce a friendly error message page
 915   *
 916   * @param string $error The error message to be shown
 917   * @param string $title The title of the message shown in the title of the page and the error table
 918   */
 919  function error($error="", $title="")
 920  {
 921      global $header, $footer, $theme, $headerinclude, $db, $templates, $lang, $mybb, $plugins;
 922  
 923      $error = $plugins->run_hooks("error", $error);
 924      if(!$error)
 925      {
 926          $error = $lang->unknown_error;
 927      }
 928  
 929      // AJAX error message?
 930      if($mybb->get_input('ajax', MyBB::INPUT_INT))
 931      {
 932          // Send our headers.
 933          @header("Content-type: application/json; charset={$lang->settings['charset']}");
 934          echo json_encode(array("errors" => array($error)));
 935          exit;
 936      }
 937  
 938      if(!$title)
 939      {
 940          $title = $mybb->settings['bbname'];
 941      }
 942  
 943      $timenow = my_date('relative', TIME_NOW);
 944      reset_breadcrumb();
 945      add_breadcrumb($lang->error);
 946  
 947      eval("\$errorpage = \"".$templates->get("error")."\";");
 948      output_page($errorpage);
 949  
 950      exit;
 951  }
 952  
 953  /**
 954   * Produce an error message for displaying inline on a page
 955   *
 956   * @param array $errors Array of errors to be shown
 957   * @param string $title The title of the error message
 958   * @param array $json_data JSON data to be encoded (we may want to send more data; e.g. newreply.php uses this for CAPTCHA)
 959   * @return string The inline error HTML
 960   */
 961  function inline_error($errors, $title="", $json_data=array())
 962  {
 963      global $theme, $mybb, $db, $lang, $templates;
 964  
 965      if(!$title)
 966      {
 967          $title = $lang->please_correct_errors;
 968      }
 969  
 970      if(!is_array($errors))
 971      {
 972          $errors = array($errors);
 973      }
 974  
 975      // AJAX error message?
 976      if($mybb->get_input('ajax', MyBB::INPUT_INT))
 977      {
 978          // Send our headers.
 979          @header("Content-type: application/json; charset={$lang->settings['charset']}");
 980  
 981          if(empty($json_data))
 982          {
 983              echo json_encode(array("errors" => $errors));
 984          }
 985          else
 986          {
 987              echo json_encode(array_merge(array("errors" => $errors), $json_data));
 988          }
 989          exit;
 990      }
 991  
 992      $errorlist = '';
 993  
 994      foreach($errors as $error)
 995      {
 996          eval("\$errorlist .= \"".$templates->get("error_inline_item")."\";");
 997      }
 998  
 999      eval("\$errors = \"".$templates->get("error_inline")."\";");
1000  
1001      return $errors;
1002  }
1003  
1004  /**
1005   * Presents the user with a "no permission" page
1006   */
1007  function error_no_permission()
1008  {
1009      global $mybb, $theme, $templates, $db, $lang, $plugins, $session;
1010  
1011      $time = TIME_NOW;
1012      $plugins->run_hooks("no_permission");
1013  
1014      $noperm_array = array (
1015          "nopermission" => '1',
1016          "location1" => 0,
1017          "location2" => 0
1018      );
1019  
1020      $db->update_query("sessions", $noperm_array, "sid='{$session->sid}'");
1021  
1022      if($mybb->get_input('ajax', MyBB::INPUT_INT))
1023      {
1024          // Send our headers.
1025          header("Content-type: application/json; charset={$lang->settings['charset']}");
1026          echo json_encode(array("errors" => array($lang->error_nopermission_user_ajax)));
1027          exit;
1028      }
1029  
1030      if($mybb->user['uid'])
1031      {
1032          $lang->error_nopermission_user_username = $lang->sprintf($lang->error_nopermission_user_username, htmlspecialchars_uni($mybb->user['username']));
1033          eval("\$errorpage = \"".$templates->get("error_nopermission_loggedin")."\";");
1034      }
1035      else
1036      {
1037          // Redirect to where the user came from
1038          $redirect_url = $_SERVER['PHP_SELF'];
1039          if($_SERVER['QUERY_STRING'])
1040          {
1041              $redirect_url .= '?'.$_SERVER['QUERY_STRING'];
1042          }
1043  
1044          $redirect_url = htmlspecialchars_uni($redirect_url);
1045  
1046          switch($mybb->settings['username_method'])
1047          {
1048              case 0:
1049                  $lang_username = $lang->username;
1050                  break;
1051              case 1:
1052                  $lang_username = $lang->username1;
1053                  break;
1054              case 2:
1055                  $lang_username = $lang->username2;
1056                  break;
1057              default:
1058                  $lang_username = $lang->username;
1059                  break;
1060          }
1061          eval("\$errorpage = \"".$templates->get("error_nopermission")."\";");
1062      }
1063  
1064      error($errorpage);
1065  }
1066  
1067  /**
1068   * Redirect the user to a given URL with a given message
1069   *
1070   * @param string $url The URL to redirect the user to
1071   * @param string $message The redirection message to be shown
1072   * @param string $title The title of the redirection page
1073   * @param boolean $force_redirect Force the redirect page regardless of settings
1074   */
1075  function redirect($url, $message="", $title="", $force_redirect=false)
1076  {
1077      global $header, $footer, $mybb, $theme, $headerinclude, $templates, $lang, $plugins;
1078  
1079      $redirect_args = array('url' => &$url, 'message' => &$message, 'title' => &$title);
1080  
1081      $plugins->run_hooks("redirect", $redirect_args);
1082  
1083      if($mybb->get_input('ajax', MyBB::INPUT_INT))
1084      {
1085          // Send our headers.
1086          //@header("Content-type: text/html; charset={$lang->settings['charset']}");
1087          $data = "<script type=\"text/javascript\">\n";
1088          if($message != "")
1089          {
1090              $data .=  'alert("'.addslashes($message).'");';
1091          }
1092          $url = str_replace("#", "&#", $url);
1093          $url = htmlspecialchars_decode($url);
1094          $url = str_replace(array("\n","\r",";"), "", $url);
1095          $data .=  'window.location = "'.addslashes($url).'";'."\n";
1096          $data .= "</script>\n";
1097          //exit;
1098  
1099          @header("Content-type: application/json; charset={$lang->settings['charset']}");
1100          echo json_encode(array("data" => $data));
1101          exit;
1102      }
1103  
1104      if(!$message)
1105      {
1106          $message = $lang->redirect;
1107      }
1108  
1109      $time = TIME_NOW;
1110      $timenow = my_date('relative', $time);
1111  
1112      if(!$title)
1113      {
1114          $title = $mybb->settings['bbname'];
1115      }
1116  
1117      // Show redirects only if both ACP and UCP settings are enabled, or ACP is enabled, and user is a guest, or they are forced.
1118      if($force_redirect == true || ($mybb->settings['redirects'] == 1 && (!$mybb->user['uid'] || $mybb->user['showredirect'] == 1)))
1119      {
1120          $url = str_replace("&amp;", "&", $url);
1121          $url = htmlspecialchars_uni($url);
1122  
1123          eval("\$redirectpage = \"".$templates->get("redirect")."\";");
1124          output_page($redirectpage);
1125      }
1126      else
1127      {
1128          $url = htmlspecialchars_decode($url);
1129          $url = str_replace(array("\n","\r",";"), "", $url);
1130  
1131          run_shutdown();
1132  
1133          if(!my_validate_url($url, true, true))
1134          {
1135              header("Location: {$mybb->settings['bburl']}/{$url}");
1136          }
1137          else
1138          {
1139              header("Location: {$url}");
1140          }
1141      }
1142  
1143      exit;
1144  }
1145  
1146  /**
1147   * Generate a listing of page - pagination
1148   *
1149   * @param int $count The number of items
1150   * @param int $perpage The number of items to be shown per page
1151   * @param int $page The current page number
1152   * @param string $url The URL to have page numbers tacked on to (If {page} is specified, the value will be replaced with the page #)
1153   * @param boolean $breadcrumb Whether or not the multipage is being shown in the navigation breadcrumb
1154   * @return string The generated pagination
1155   */
1156  function multipage($count, $perpage, $page, $url, $breadcrumb=false)
1157  {
1158      global $theme, $templates, $lang, $mybb, $plugins;
1159  
1160      if($count <= $perpage)
1161      {
1162          return '';
1163      }
1164  
1165      $args = array(
1166          'count' => &$count,
1167          'perpage' => &$perpage,
1168          'page' => &$page,
1169          'url' => &$url,
1170          'breadcrumb' => &$breadcrumb,
1171      );
1172      $plugins->run_hooks('multipage', $args);
1173  
1174      $page = (int)$page;
1175  
1176      $url = str_replace("&amp;", "&", $url);
1177      $url = htmlspecialchars_uni($url);
1178  
1179      $pages = ceil($count / $perpage);
1180  
1181      $prevpage = '';
1182      if($page > 1)
1183      {
1184          $prev = $page-1;
1185          $page_url = fetch_page_url($url, $prev);
1186          eval("\$prevpage = \"".$templates->get("multipage_prevpage")."\";");
1187      }
1188  
1189      // Maximum number of "page bits" to show
1190      if(!$mybb->settings['maxmultipagelinks'])
1191      {
1192          $mybb->settings['maxmultipagelinks'] = 5;
1193      }
1194  
1195      $from = $page-floor($mybb->settings['maxmultipagelinks']/2);
1196      $to = $page+floor($mybb->settings['maxmultipagelinks']/2);
1197  
1198      if($from <= 0)
1199      {
1200          $from = 1;
1201          $to = $from+$mybb->settings['maxmultipagelinks']-1;
1202      }
1203  
1204      if($to > $pages)
1205      {
1206          $to = $pages;
1207          $from = $pages-$mybb->settings['maxmultipagelinks']+1;
1208          if($from <= 0)
1209          {
1210              $from = 1;
1211          }
1212      }
1213  
1214      if($to == 0)
1215      {
1216          $to = $pages;
1217      }
1218  
1219      $start = '';
1220      if($from > 1)
1221      {
1222          if($from-1 == 1)
1223          {
1224              $lang->multipage_link_start = '';
1225          }
1226  
1227          $page_url = fetch_page_url($url, 1);
1228          eval("\$start = \"".$templates->get("multipage_start")."\";");
1229      }
1230  
1231      $mppage = '';
1232      for($i = $from; $i <= $to; ++$i)
1233      {
1234          $page_url = fetch_page_url($url, $i);
1235          if($page == $i)
1236          {
1237              if($breadcrumb == true)
1238              {
1239                  eval("\$mppage .= \"".$templates->get("multipage_page_link_current")."\";");
1240              }
1241              else
1242              {
1243                  eval("\$mppage .= \"".$templates->get("multipage_page_current")."\";");
1244              }
1245          }
1246          else
1247          {
1248              eval("\$mppage .= \"".$templates->get("multipage_page")."\";");
1249          }
1250      }
1251  
1252      $end = '';
1253      if($to < $pages)
1254      {
1255          if($to+1 == $pages)
1256          {
1257              $lang->multipage_link_end = '';
1258          }
1259  
1260          $page_url = fetch_page_url($url, $pages);
1261          eval("\$end = \"".$templates->get("multipage_end")."\";");
1262      }
1263  
1264      $nextpage = '';
1265      if($page < $pages)
1266      {
1267          $next = $page+1;
1268          $page_url = fetch_page_url($url, $next);
1269          eval("\$nextpage = \"".$templates->get("multipage_nextpage")."\";");
1270      }
1271  
1272      $jumptopage = '';
1273      if($pages > ($mybb->settings['maxmultipagelinks']+1) && $mybb->settings['jumptopagemultipage'] == 1)
1274      {
1275          // 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
1276          $jump_url = fetch_page_url($url, 1);
1277          eval("\$jumptopage = \"".$templates->get("multipage_jump_page")."\";");
1278      }
1279  
1280      $multipage_pages = $lang->sprintf($lang->multipage_pages, $pages);
1281  
1282      if($breadcrumb == true)
1283      {
1284          eval("\$multipage = \"".$templates->get("multipage_breadcrumb")."\";");
1285      }
1286      else
1287      {
1288          eval("\$multipage = \"".$templates->get("multipage")."\";");
1289      }
1290  
1291      return $multipage;
1292  }
1293  
1294  /**
1295   * Generate a page URL for use by the multipage function
1296   *
1297   * @param string $url The URL being passed
1298   * @param int $page The page number
1299   * @return string
1300   */
1301  function fetch_page_url($url, $page)
1302  {
1303      if($page <= 1)
1304      {
1305          $find = array(
1306              "-page-{page}",
1307              "&amp;page={page}",
1308              "{page}"
1309          );
1310  
1311          // Remove "Page 1" to the defacto URL
1312          $url = str_replace($find, array("", "", $page), $url);
1313          return $url;
1314      }
1315      else if(strpos($url, "{page}") === false)
1316      {
1317          // If no page identifier is specified we tack it on to the end of the URL
1318          if(strpos($url, "?") === false)
1319          {
1320              $url .= "?";
1321          }
1322          else
1323          {
1324              $url .= "&amp;";
1325          }
1326  
1327          $url .= "page=$page";
1328      }
1329      else
1330      {
1331          $url = str_replace("{page}", $page, $url);
1332      }
1333  
1334      return $url;
1335  }
1336  
1337  /**
1338   * Fetch the permissions for a specific user
1339   *
1340   * @param int $uid The user ID, if no user ID is provided then current user's ID will be considered.
1341   * @return array Array of user permissions for the specified user
1342   */
1343  function user_permissions($uid=null)
1344  {
1345      global $mybb, $cache, $groupscache, $user_cache;
1346  
1347      // If no user id is specified, assume it is the current user
1348      if($uid === null)
1349      {
1350          $uid = $mybb->user['uid'];
1351      }
1352  
1353      // Its a guest. Return the group permissions directly from cache
1354      if($uid == 0)
1355      {
1356          return $groupscache[1];
1357      }
1358  
1359      // User id does not match current user, fetch permissions
1360      if($uid != $mybb->user['uid'])
1361      {
1362          // We've already cached permissions for this user, return them.
1363          if(!empty($user_cache[$uid]['permissions']))
1364          {
1365              return $user_cache[$uid]['permissions'];
1366          }
1367  
1368          // This user was not already cached, fetch their user information.
1369          if(empty($user_cache[$uid]))
1370          {
1371              $user_cache[$uid] = get_user($uid);
1372          }
1373  
1374          // Collect group permissions.
1375          $gid = $user_cache[$uid]['usergroup'].",".$user_cache[$uid]['additionalgroups'];
1376          $groupperms = usergroup_permissions($gid);
1377  
1378          // Store group permissions in user cache.
1379          $user_cache[$uid]['permissions'] = $groupperms;
1380          return $groupperms;
1381      }
1382      // This user is the current user, return their permissions
1383      else
1384      {
1385          return $mybb->usergroup;
1386      }
1387  }
1388  
1389  /**
1390   * Fetch the usergroup permissions for a specific group or series of groups combined
1391   *
1392   * @param int|string $gid A list of groups (Can be a single integer, or a list of groups separated by a comma)
1393   * @return array Array of permissions generated for the groups, containing also a list of comma-separated checked groups under 'all_usergroups' index
1394   */
1395  function usergroup_permissions($gid=0)
1396  {
1397      global $cache, $groupscache, $grouppermignore, $groupzerogreater, $groupzerolesser, $groupxgreater, $grouppermbyswitch;
1398  
1399      if(!is_array($groupscache))
1400      {
1401          $groupscache = $cache->read("usergroups");
1402      }
1403  
1404      $groups = explode(",", $gid);
1405  
1406      if(count($groups) == 1)
1407      {
1408          $groupscache[$gid]['all_usergroups'] = $gid;
1409          return $groupscache[$gid];
1410      }
1411  
1412      $usergroup = array();
1413      $usergroup['all_usergroups'] = $gid;
1414  
1415      // Get those switch permissions from the first valid group.
1416      $permswitches_usergroup = array();
1417      $grouppermswitches = array();
1418      foreach(array_values($grouppermbyswitch) as $permvalue)
1419      {
1420          if(is_array($permvalue))
1421          {
1422              foreach($permvalue as $perm)
1423              {
1424                  $grouppermswitches[] = $perm;
1425              }
1426          }
1427          else
1428          {
1429              $grouppermswitches[] = $permvalue;
1430          }
1431      }
1432      $grouppermswitches = array_unique($grouppermswitches);
1433      foreach($groups as $gid)
1434      {
1435          if(trim($gid) == "" || empty($groupscache[$gid]))
1436          {
1437              continue;
1438          }
1439          foreach($grouppermswitches as $perm)
1440          {
1441              $permswitches_usergroup[$perm] = $groupscache[$gid][$perm];
1442          }
1443          break;    // Only retieve the first available group's permissions as how following action does.
1444      }
1445  
1446      foreach($groups as $gid)
1447      {
1448          if(trim($gid) == "" || empty($groupscache[$gid]))
1449          {
1450              continue;
1451          }
1452  
1453          foreach($groupscache[$gid] as $perm => $access)
1454          {
1455              if(!in_array($perm, $grouppermignore))
1456              {
1457                  if(isset($usergroup[$perm]))
1458                  {
1459                      $permbit = $usergroup[$perm];
1460                  }
1461                  else
1462                  {
1463                      $permbit = "";
1464                  }
1465  
1466                  // permission type: 0 not a numerical permission, otherwise a numerical permission.
1467                  // Positive value is for `greater is more` permission, negative for `lesser is more`.
1468                  $perm_is_numerical = 0;
1469                  $perm_numerical_lowerbound = 0;
1470  
1471                  // 0 represents unlimited for most numerical group permissions (i.e. private message limit) so take that into account.
1472                  if(in_array($perm, $groupzerogreater))
1473                  {
1474                      // 1 means a `0 or greater` permission. Value 0 means unlimited.
1475                      $perm_is_numerical = 1;
1476                  }
1477                  // Less is more for some numerical group permissions (i.e. post count required for using signature) so take that into account, too.
1478                  else if(in_array($perm, $groupzerolesser))
1479                  {
1480                      // -1 means a `0 or lesser` permission. Value 0 means unlimited.
1481                      $perm_is_numerical = -1;
1482                  }
1483                  // Greater is more, but with a lower bound.
1484                  else if(array_key_exists($perm, $groupxgreater))
1485                  {
1486                      // 2 means a general `greater` permission. Value 0 just means 0.
1487                      $perm_is_numerical = 2;
1488                      $perm_numerical_lowerbound = $groupxgreater[$perm];
1489                  }
1490  
1491                  if($perm_is_numerical != 0)
1492                  {
1493                      $update_current_perm = true;
1494  
1495                      // Ensure it's an integer.
1496                      $access = (int)$access;
1497                      // Check if this permission should be activatived by another switch permission in current group.
1498                      if(array_key_exists($perm, $grouppermbyswitch))
1499                      {
1500                          if(!is_array($grouppermbyswitch[$perm]))
1501                          {
1502                              $grouppermbyswitch[$perm] = array($grouppermbyswitch[$perm]);
1503                          }
1504  
1505                          $update_current_perm = $group_current_perm_enabled = $group_perm_enabled = false;
1506                          foreach($grouppermbyswitch[$perm] as $permswitch)
1507                          {
1508                              if(!isset($groupscache[$gid][$permswitch]))
1509                              {
1510                                  continue;
1511                              }
1512                              $permswitches_current = $groupscache[$gid][$permswitch];
1513  
1514                              // Determin if the permission is enabled by switches from current group.
1515                              if($permswitches_current == 1 || $permswitches_current == "yes") // Keep yes/no for compatibility?
1516                              {
1517                                  $group_current_perm_enabled = true;
1518                              }
1519                              // Determin if the permission is enabled by switches from previously handled groups.
1520                              if($permswitches_usergroup[$permswitch] == 1 || $permswitches_usergroup[$permswitch] == "yes") // Keep yes/no for compatibility?
1521                              {
1522                                  $group_perm_enabled = true;
1523                              }
1524                          }
1525  
1526                          // Set this permission if not set yet.
1527                          if(!isset($usergroup[$perm]))
1528                          {
1529                              $usergroup[$perm] = $access;
1530                          }
1531  
1532                          // If current group's setting enables the permission, we may need to update the user's permission.
1533                          if($group_current_perm_enabled)
1534                          {
1535                              // Only update this permission if both its switch and current group switch are on.
1536                              if($group_perm_enabled)
1537                              {
1538                                  $update_current_perm = true;
1539                              }
1540                              // Override old useless value with value from current group.
1541                              else
1542                              {
1543                                  $usergroup[$perm] = $access;
1544                              }
1545                          }
1546                      }
1547  
1548                      // No switch controls this permission, or permission needs an update.
1549                      if($update_current_perm)
1550                      {
1551                          switch($perm_is_numerical)
1552                          {
1553                              case 1:
1554                              case -1:
1555                                  if($access == 0 || $permbit === 0)
1556                                  {
1557                                      $usergroup[$perm] = 0;
1558                                      break;
1559                                  }
1560                              default:
1561                                  if($perm_is_numerical > 0 && $access > $permbit || $perm_is_numerical < 0 && $access < $permbit)
1562                                  {
1563                                      $usergroup[$perm] = $access;
1564                                  }
1565                                  break;
1566                          }
1567                      }
1568  
1569                      // Maybe oversubtle, database uses Unsigned on them, but enables usage of permission value with a lower bound.
1570                      if($usergroup[$perm] < $perm_numerical_lowerbound)
1571                      {
1572                          $usergroup[$perm] = $perm_numerical_lowerbound;
1573                      }
1574  
1575                      // Work is done for numerical permissions.
1576                      continue;
1577                  }
1578  
1579                  if($access > $permbit || ($access == "yes" && $permbit == "no") || !$permbit) // Keep yes/no for compatibility?
1580                  {
1581                      $usergroup[$perm] = $access;
1582                  }
1583              }
1584          }
1585  
1586          foreach($permswitches_usergroup as $perm => $value)
1587          {
1588              $permswitches_usergroup[$perm] = $usergroup[$perm];
1589          }
1590      }
1591  
1592      return $usergroup;
1593  }
1594  
1595  /**
1596   * Fetch the display group properties for a specific display group
1597   *
1598   * @param int $gid The group ID to fetch the display properties for
1599   * @return array Array of display properties for the group
1600   */
1601  function usergroup_displaygroup($gid)
1602  {
1603      global $cache, $groupscache, $displaygroupfields;
1604  
1605      if(!is_array($groupscache))
1606      {
1607          $groupscache = $cache->read("usergroups");
1608      }
1609  
1610      $displaygroup = array();
1611      $group = $groupscache[$gid];
1612  
1613      foreach($displaygroupfields as $field)
1614      {
1615          $displaygroup[$field] = $group[$field];
1616      }
1617  
1618      return $displaygroup;
1619  }
1620  
1621  /**
1622   * Build the forum permissions for a specific forum, user or group
1623   *
1624   * @param int $fid The forum ID to build permissions for (0 builds for all forums)
1625   * @param int $uid The user to build the permissions for (0 will select the uid automatically)
1626   * @param int $gid The group of the user to build permissions for (0 will fetch it)
1627   * @return array|false Forum permissions for the specific forum or forums
1628   */
1629  function forum_permissions($fid=0, $uid=0, $gid=0)
1630  {
1631      global $db, $cache, $groupscache, $forum_cache, $fpermcache, $mybb, $cached_forum_permissions_permissions, $cached_forum_permissions;
1632  
1633      if($uid == 0)
1634      {
1635          $uid = $mybb->user['uid'];
1636      }
1637  
1638      if(!$gid || $gid == 0) // If no group, we need to fetch it
1639      {
1640          if($uid != 0 && $uid != $mybb->user['uid'])
1641          {
1642              $user = get_user($uid);
1643  
1644              $gid = $user['usergroup'].",".$user['additionalgroups'];
1645              $groupperms = usergroup_permissions($gid);
1646          }
1647          else
1648          {
1649              $gid = $mybb->user['usergroup'];
1650  
1651              if(isset($mybb->user['additionalgroups']))
1652              {
1653                  $gid .= ",".$mybb->user['additionalgroups'];
1654              }
1655  
1656              $groupperms = $mybb->usergroup;
1657          }
1658      }
1659      else 
1660      {
1661          $groupperms = usergroup_permissions($gid);
1662      }    
1663  
1664      if(!is_array($forum_cache))
1665      {
1666          $forum_cache = cache_forums();
1667  
1668          if(!$forum_cache)
1669          {
1670              return false;
1671          }
1672      }
1673  
1674      if(!is_array($fpermcache))
1675      {
1676          $fpermcache = $cache->read("forumpermissions");
1677      }
1678  
1679      if($fid) // Fetch the permissions for a single forum
1680      {
1681          if(empty($cached_forum_permissions_permissions[$gid][$fid]))
1682          {
1683              $cached_forum_permissions_permissions[$gid][$fid] = fetch_forum_permissions($fid, $gid, $groupperms);
1684          }
1685          return $cached_forum_permissions_permissions[$gid][$fid];
1686      }
1687      else
1688      {
1689          if(empty($cached_forum_permissions[$gid]))
1690          {
1691              foreach($forum_cache as $forum)
1692              {
1693                  $cached_forum_permissions[$gid][$forum['fid']] = fetch_forum_permissions($forum['fid'], $gid, $groupperms);
1694              }
1695          }
1696          return $cached_forum_permissions[$gid];
1697      }
1698  }
1699  
1700  /**
1701   * Fetches the permissions for a specific forum/group applying the inheritance scheme.
1702   * Called by forum_permissions()
1703   *
1704   * @param int $fid The forum ID
1705   * @param string $gid A comma separated list of usergroups
1706   * @param array $groupperms Group permissions
1707   * @return array Permissions for this forum
1708  */
1709  function fetch_forum_permissions($fid, $gid, $groupperms)
1710  {
1711      global $groupscache, $forum_cache, $fpermcache, $mybb, $fpermfields;
1712  
1713      if(isset($gid))
1714      {
1715          $groups = explode(",", $gid);
1716      }
1717      else
1718      {
1719          $groups = array();
1720      }
1721  
1722      $current_permissions = array();
1723      $only_view_own_threads = 1;
1724      $only_reply_own_threads = 1;
1725  
1726      if(empty($fpermcache[$fid])) // This forum has no custom or inherited permissions so lets just return the group permissions
1727      {
1728          $current_permissions = $groupperms;
1729      }
1730      else
1731      {
1732          foreach($groups as $gid)
1733          {
1734              // If this forum has custom or inherited permissions for the currently looped group.
1735              if(!empty($fpermcache[$fid][$gid]))
1736              {
1737                  $level_permissions = $fpermcache[$fid][$gid];
1738              }
1739              // Or, use the group permission instead, if available. Some forum permissions not existing here will be added back later.
1740              else if(!empty($groupscache[$gid]))
1741              {
1742                  $level_permissions = $groupscache[$gid];
1743              }
1744              // No permission is available for the currently looped group, probably we have bad data here.
1745              else
1746              {
1747                  continue;
1748              }
1749  
1750              foreach($level_permissions as $permission => $access)
1751              {
1752                  if(empty($current_permissions[$permission]) || $access >= $current_permissions[$permission] || ($access == "yes" && $current_permissions[$permission] == "no"))
1753                  {
1754                      $current_permissions[$permission] = $access;
1755                  }
1756              }
1757  
1758              if($level_permissions["canview"] && empty($level_permissions["canonlyviewownthreads"]))
1759              {
1760                  $only_view_own_threads = 0;
1761              }
1762  
1763              if($level_permissions["canpostreplys"] && empty($level_permissions["canonlyreplyownthreads"]))
1764              {
1765                  $only_reply_own_threads = 0;
1766              }
1767          }
1768  
1769          if(count($current_permissions) == 0)
1770          {
1771              $current_permissions = $groupperms;
1772          }
1773      }
1774  
1775      // Figure out if we can view more than our own threads
1776      if($only_view_own_threads == 0 || !isset($current_permissions["canonlyviewownthreads"]))
1777      {
1778          $current_permissions["canonlyviewownthreads"] = 0;
1779      }
1780  
1781      // Figure out if we can reply more than our own threads
1782      if($only_reply_own_threads == 0 || !isset($current_permissions["canonlyreplyownthreads"]))
1783      {
1784          $current_permissions["canonlyreplyownthreads"] = 0;
1785      }
1786  
1787      return $current_permissions;
1788  }
1789  
1790  /**
1791   * Check whether password for given forum was validated for the current user
1792   *
1793   * @param array $forum The forum data
1794   * @param bool $ignore_empty Whether to treat forum password configured as an empty string as validated
1795   * @param bool $check_parents Whether to check parent forums using `parentlist`
1796   * @return bool
1797   */
1798  function forum_password_validated($forum, $ignore_empty=false, $check_parents=false)
1799  {
1800      global $mybb, $forum_cache;
1801  
1802      if($check_parents && isset($forum['parentlist']))
1803      {
1804          if(!is_array($forum_cache))
1805          {
1806              $forum_cache = cache_forums();
1807              if(!$forum_cache)
1808              {
1809                  return false;
1810              }
1811          }
1812  
1813          $parents = explode(',', $forum['parentlist']);
1814          rsort($parents);
1815  
1816          foreach($parents as $parent_id)
1817          {
1818              if($parent_id != $forum['fid'] && !forum_password_validated($forum_cache[$parent_id], true))
1819              {
1820                  return false;
1821              }
1822          }
1823      }
1824  
1825      return ($ignore_empty && $forum['password'] === '') || (
1826          isset($mybb->cookies['forumpass'][$forum['fid']]) &&
1827          my_hash_equals(
1828              md5($mybb->user['uid'].$forum['password']),
1829              $mybb->cookies['forumpass'][$forum['fid']]
1830          )
1831      );
1832  }
1833  
1834  /**
1835   * Check the password given on a certain forum for validity
1836   *
1837   * @param int $fid The forum ID
1838   * @param int $pid The Parent ID
1839   * @param bool $return
1840   * @return bool
1841   */
1842  function check_forum_password($fid, $pid=0, $return=false)
1843  {
1844      global $mybb, $header, $footer, $headerinclude, $theme, $templates, $lang, $forum_cache;
1845  
1846      $showform = true;
1847  
1848      if(!is_array($forum_cache))
1849      {
1850          $forum_cache = cache_forums();
1851          if(!$forum_cache)
1852          {
1853              return false;
1854          }
1855      }
1856  
1857      // Loop through each of parent forums to ensure we have a password for them too
1858      if(isset($forum_cache[$fid]['parentlist']))
1859      {
1860          $parents = explode(',', $forum_cache[$fid]['parentlist']);
1861          rsort($parents);
1862      }
1863      if(!empty($parents))
1864      {
1865          foreach($parents as $parent_id)
1866          {
1867              if($parent_id == $fid || $parent_id == $pid)
1868              {
1869                  continue;
1870              }
1871  
1872              if($forum_cache[$parent_id]['password'] !== "")
1873              {
1874                  check_forum_password($parent_id, $fid);
1875              }
1876          }
1877      }
1878  
1879      if($forum_cache[$fid]['password'] !== '')
1880      {
1881          if(isset($mybb->input['pwverify']) && $pid == 0)
1882          {
1883              if(my_hash_equals($forum_cache[$fid]['password'], $mybb->get_input('pwverify')))
1884              {
1885                  my_setcookie("forumpass[$fid]", md5($mybb->user['uid'].$mybb->get_input('pwverify')), null, true);
1886                  $showform = false;
1887              }
1888              else
1889              {
1890                  eval("\$pwnote = \"".$templates->get("forumdisplay_password_wrongpass")."\";");
1891                  $showform = true;
1892              }
1893          }
1894          else
1895          {
1896              if(!forum_password_validated($forum_cache[$fid]))
1897              {
1898                  $showform = true;
1899              }
1900              else
1901              {
1902                  $showform = false;
1903              }
1904          }
1905      }
1906      else
1907      {
1908          $showform = false;
1909      }
1910  
1911      if($return)
1912      {
1913          return $showform;
1914      }
1915  
1916      if($showform)
1917      {
1918          if($pid)
1919          {
1920              header("Location: ".$mybb->settings['bburl']."/".get_forum_link($fid));
1921          }
1922          else
1923          {
1924              $_SERVER['REQUEST_URI'] = htmlspecialchars_uni($_SERVER['REQUEST_URI']);
1925              eval("\$pwform = \"".$templates->get("forumdisplay_password")."\";");
1926              output_page($pwform);
1927          }
1928          exit;
1929      }
1930  }
1931  
1932  /**
1933   * Return the permissions for a moderator in a specific forum
1934   *
1935   * @param int $fid The forum ID
1936   * @param int $uid The user ID to fetch permissions for (0 assumes current logged in user)
1937   * @param string $parentslist The parent list for the forum (if blank, will be fetched)
1938   * @return array|false Array of moderator permissions for the specific forum
1939   */
1940  function get_moderator_permissions($fid, $uid=0, $parentslist="")
1941  {
1942      global $mybb, $cache, $db;
1943      static $modpermscache;
1944  
1945      if($uid < 1)
1946      {
1947          $uid = $mybb->user['uid'];
1948      }
1949  
1950      if($uid == 0)
1951      {
1952          return false;
1953      }
1954  
1955      if(isset($modpermscache[$fid][$uid]))
1956      {
1957          return $modpermscache[$fid][$uid];
1958      }
1959  
1960      if(!$parentslist)
1961      {
1962          $parentslist = explode(',', get_parent_list($fid));
1963      }
1964  
1965      // Get user groups
1966      $perms = array();
1967      $user = get_user($uid);
1968  
1969      $groups = array($user['usergroup']);
1970  
1971      if(!empty($user['additionalgroups']))
1972      {
1973          $extra_groups = explode(",", $user['additionalgroups']);
1974  
1975          foreach($extra_groups as $extra_group)
1976          {
1977              $groups[] = $extra_group;
1978          }
1979      }
1980  
1981      $mod_cache = $cache->read("moderators");
1982  
1983      foreach($mod_cache as $forumid => $forum)
1984      {
1985          if(empty($forum) || !is_array($forum) || !in_array($forumid, $parentslist))
1986          {
1987              // No perms or we're not after this forum
1988              continue;
1989          }
1990  
1991          // User settings override usergroup settings
1992          if(!empty($forum['users'][$uid]))
1993          {
1994              $perm = $forum['users'][$uid];
1995              foreach($perm as $action => $value)
1996              {
1997                  if(strpos($action, "can") === false)
1998                  {
1999                      continue;
2000                  }
2001  
2002                  if(!isset($perms[$action]))
2003                  {
2004                      $perms[$action] = $value;
2005                  }
2006                  // Figure out the user permissions
2007                  else if($value == 0)
2008                  {
2009                      // The user doesn't have permission to set this action
2010                      $perms[$action] = 0;
2011                  }
2012                  else
2013                  {
2014                      $perms[$action] = max($perm[$action], $perms[$action]);
2015                  }
2016              }
2017          }
2018  
2019          foreach($groups as $group)
2020          {
2021              if(empty($forum['usergroups'][$group]) || !is_array($forum['usergroups'][$group]))
2022              {
2023                  // There are no permissions set for this group
2024                  continue;
2025              }
2026  
2027              $perm = $forum['usergroups'][$group];
2028              foreach($perm as $action => $value)
2029              {
2030                  if(strpos($action, "can") === false)
2031                  {
2032                      continue;
2033                  }
2034  
2035                  if(!isset($perms[$action]))
2036                  {
2037                      $perms[$action] = $value;
2038                  }
2039                  else
2040                  {
2041                      $perms[$action] = max($perm[$action], $perms[$action]);
2042                  }
2043              }
2044          }
2045      }
2046  
2047      $modpermscache[$fid][$uid] = $perms;
2048  
2049      return $perms;
2050  }
2051  
2052  /**
2053   * Checks if a moderator has permissions to perform an action in a specific forum
2054   *
2055   * @param int $fid The forum ID (0 assumes global)
2056   * @param string $action The action tyring to be performed. (blank assumes any action at all)
2057   * @param int $uid The user ID (0 assumes current user)
2058   * @return bool Returns true if the user has permission, false if they do not
2059   */
2060  function is_moderator($fid=0, $action="", $uid=0)
2061  {
2062      global $mybb, $cache, $plugins;
2063  
2064      if($uid == 0)
2065      {
2066          $uid = $mybb->user['uid'];
2067      }
2068  
2069      if($uid == 0)
2070      {
2071          return false;
2072      }
2073  
2074      $user_perms = user_permissions($uid);
2075  
2076      $hook_args = array(
2077          'fid' => $fid,
2078          'action' => $action,
2079          'uid' => $uid,
2080      );
2081  
2082      $plugins->run_hooks("is_moderator", $hook_args);
2083      
2084      if(isset($hook_args['is_moderator']))
2085      {
2086          return (boolean) $hook_args['is_moderator'];
2087      }
2088  
2089      if(!empty($user_perms['issupermod']) && $user_perms['issupermod'] == 1)
2090      {
2091          if($fid)
2092          {
2093              $forumpermissions = forum_permissions($fid);
2094              if(!empty($forumpermissions['canview']) && !empty($forumpermissions['canviewthreads']) && empty($forumpermissions['canonlyviewownthreads']))
2095              {
2096                  return true;
2097              }
2098              return false;
2099          }
2100          return true;
2101      }
2102      else
2103      {
2104          if(!$fid)
2105          {
2106              $modcache = $cache->read('moderators');
2107              if(!empty($modcache))
2108              {
2109                  foreach($modcache as $modusers)
2110                  {
2111                      if(isset($modusers['users'][$uid]) && $modusers['users'][$uid]['mid'] && (!$action || !empty($modusers['users'][$uid][$action])))
2112                      {
2113                          return true;
2114                      }
2115  
2116                      $groups = explode(',', $user_perms['all_usergroups']);
2117  
2118                      foreach($groups as $group)
2119                      {
2120                          if(trim($group) != '' && isset($modusers['usergroups'][$group]) && (!$action || !empty($modusers['usergroups'][$group][$action])))
2121                          {
2122                              return true;
2123                          }
2124                      }
2125                  }
2126              }
2127              return false;
2128          }
2129          else
2130          {
2131              $modperms = get_moderator_permissions($fid, $uid);
2132  
2133              if(!$action && $modperms)
2134              {
2135                  return true;
2136              }
2137              else
2138              {
2139                  if(isset($modperms[$action]) && $modperms[$action] == 1)
2140                  {
2141                      return true;
2142                  }
2143                  else
2144                  {
2145                      return false;
2146                  }
2147              }
2148          }
2149      }
2150  }
2151  
2152  /**
2153   * Get an array of fids that the forum moderator has access to.
2154   * Do not use for administraotrs or global moderators as they moderate any forum and the function will return false.
2155   *
2156   * @param int $uid The user ID (0 assumes current user)
2157   * @return array|bool an array of the fids the user has moderator access to or bool if called incorrectly.
2158   */
2159  function get_moderated_fids($uid=0)
2160  {
2161      global $mybb, $cache;
2162  
2163      if($uid == 0)
2164      {
2165          $uid = $mybb->user['uid'];
2166      }
2167  
2168      if($uid == 0)
2169      {
2170          return array();
2171      }
2172  
2173      $user_perms = user_permissions($uid);
2174  
2175      if($user_perms['issupermod'] == 1)
2176      {
2177          return false;
2178      }
2179  
2180      $fids = array();
2181  
2182      $modcache = $cache->read('moderators');
2183      if(!empty($modcache))
2184      {
2185          $groups = explode(',', $user_perms['all_usergroups']);
2186  
2187          foreach($modcache as $fid => $forum)
2188          {
2189              if(isset($forum['users'][$uid]) && $forum['users'][$uid]['mid'])
2190              {
2191                  $fids[] = $fid;
2192                  continue;
2193              }
2194  
2195              foreach($groups as $group)
2196              {
2197                  if(trim($group) != '' && isset($forum['usergroups'][$group]))
2198                  {
2199                      $fids[] = $fid;
2200                  }
2201              }
2202          }
2203      }
2204  
2205      return $fids;
2206  }
2207  
2208  /**
2209   * Generate a list of the posticons.
2210   *
2211   * @return string The template of posticons.
2212   */
2213  function get_post_icons()
2214  {
2215      global $mybb, $cache, $icon, $theme, $templates, $lang;
2216  
2217      if(isset($mybb->input['icon']))
2218      {
2219          $icon = $mybb->get_input('icon');
2220      }
2221  
2222      $iconlist = '';
2223      $no_icons_checked = " checked=\"checked\"";
2224      // read post icons from cache, and sort them accordingly
2225      $posticons_cache = (array)$cache->read("posticons");
2226      $posticons = array();
2227      foreach($posticons_cache as $posticon)
2228      {
2229          $posticons[$posticon['name']] = $posticon;
2230      }
2231      krsort($posticons);
2232  
2233      foreach($posticons as $dbicon)
2234      {
2235          $dbicon['path'] = str_replace("{theme}", $theme['imgdir'], $dbicon['path']);
2236          $dbicon['path'] = htmlspecialchars_uni($mybb->get_asset_url($dbicon['path']));
2237          $dbicon['name'] = htmlspecialchars_uni($dbicon['name']);
2238  
2239          if($icon == $dbicon['iid'])
2240          {
2241              $checked = " checked=\"checked\"";
2242              $no_icons_checked = '';
2243          }
2244          else
2245          {
2246              $checked = '';
2247          }
2248  
2249          eval("\$iconlist .= \"".$templates->get("posticons_icon")."\";");
2250      }
2251  
2252      if(!empty($iconlist))
2253      {
2254          eval("\$posticons = \"".$templates->get("posticons")."\";");
2255      }
2256      else
2257      {
2258          $posticons = '';
2259      }
2260  
2261      return $posticons;
2262  }
2263  
2264  /**
2265   * MyBB setcookie() wrapper.
2266   *
2267   * @param string $name The cookie identifier.
2268   * @param string $value The cookie value.
2269   * @param int|string $expires The timestamp of the expiry date.
2270   * @param boolean $httponly True if setting a HttpOnly cookie (supported by the majority of web browsers)
2271   * @param string $samesite The samesite attribute to prevent CSRF.
2272   */
2273  function my_setcookie($name, $value="", $expires="", $httponly=false, $samesite="")
2274  {
2275      global $mybb;
2276  
2277      if(!$mybb->settings['cookiepath'])
2278      {
2279          $mybb->settings['cookiepath'] = "/";
2280      }
2281  
2282      if($expires == -1)
2283      {
2284          $expires = 0;
2285      }
2286      elseif($expires == "" || $expires == null)
2287      {
2288          $expires = TIME_NOW + (60*60*24*365); // Make the cookie expire in a years time
2289      }
2290      else
2291      {
2292          $expires = TIME_NOW + (int)$expires;
2293      }
2294  
2295      $mybb->settings['cookiepath'] = str_replace(array("\n","\r"), "", $mybb->settings['cookiepath']);
2296      $mybb->settings['cookiedomain'] = str_replace(array("\n","\r"), "", $mybb->settings['cookiedomain']);
2297      $mybb->settings['cookieprefix'] = str_replace(array("\n","\r", " "), "", $mybb->settings['cookieprefix']);
2298  
2299      // 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
2300      $cookie = "Set-Cookie: {$mybb->settings['cookieprefix']}{$name}=".urlencode($value);
2301  
2302      if($expires > 0)
2303      {
2304          $cookie .= "; expires=".@gmdate('D, d-M-Y H:i:s \\G\\M\\T', $expires);
2305      }
2306  
2307      if(!empty($mybb->settings['cookiepath']))
2308      {
2309          $cookie .= "; path={$mybb->settings['cookiepath']}";
2310      }
2311  
2312      if(!empty($mybb->settings['cookiedomain']))
2313      {
2314          $cookie .= "; domain={$mybb->settings['cookiedomain']}";
2315      }
2316  
2317      if($httponly == true)
2318      {
2319          $cookie .= "; HttpOnly";
2320      }
2321  
2322      if($samesite != "" && $mybb->settings['cookiesamesiteflag'])
2323      {
2324          $samesite = strtolower($samesite);
2325  
2326          if($samesite == "lax" || $samesite == "strict")
2327          {
2328              $cookie .= "; SameSite=".$samesite;
2329          }
2330      }
2331  
2332      if($mybb->settings['cookiesecureflag'])
2333      {
2334          $cookie .= "; Secure";
2335      }
2336  
2337      $mybb->cookies[$name] = $value;
2338  
2339      header($cookie, false);
2340  }
2341  
2342  /**
2343   * Unset a cookie set by MyBB.
2344   *
2345   * @param string $name The cookie identifier.
2346   */
2347  function my_unsetcookie($name)
2348  {
2349      global $mybb;
2350  
2351      $expires = -3600;
2352      my_setcookie($name, "", $expires);
2353  
2354      unset($mybb->cookies[$name]);
2355  }
2356  
2357  /**
2358   * Get the contents from a serialised cookie array.
2359   *
2360   * @param string $name The cookie identifier.
2361   * @param int $id The cookie content id.
2362   * @return array|boolean The cookie id's content array or false when non-existent.
2363   */
2364  function my_get_array_cookie($name, $id)
2365  {
2366      global $mybb;
2367  
2368      if(!isset($mybb->cookies['mybb'][$name]))
2369      {
2370          return false;
2371      }
2372  
2373      $cookie = my_unserialize($mybb->cookies['mybb'][$name], false);
2374  
2375      if(is_array($cookie) && isset($cookie[$id]))
2376      {
2377          return $cookie[$id];
2378      }
2379      else
2380      {
2381          return 0;
2382      }
2383  }
2384  
2385  /**
2386   * Set a serialised cookie array.
2387   *
2388   * @param string $name The cookie identifier.
2389   * @param int $id The cookie content id.
2390   * @param string $value The value to set the cookie to.
2391   * @param int|string $expires The timestamp of the expiry date.
2392   */
2393  function my_set_array_cookie($name, $id, $value, $expires="")
2394  {
2395      global $mybb;
2396  
2397      if(isset($mybb->cookies['mybb'][$name]))
2398      {
2399          $newcookie = my_unserialize($mybb->cookies['mybb'][$name], false);
2400      }
2401      else
2402      {
2403          $newcookie = array();
2404      }
2405  
2406      $newcookie[$id] = $value;
2407      $newcookie = my_serialize($newcookie);
2408      my_setcookie("mybb[$name]", addslashes($newcookie), $expires);
2409  
2410      if(isset($mybb->cookies['mybb']) && !is_array($mybb->cookies['mybb']))
2411      {
2412          $mybb->cookies['mybb'] = array();
2413      }
2414  
2415      // Make sure our current viarables are up-to-date as well
2416      $mybb->cookies['mybb'][$name] = $newcookie;
2417  }
2418  
2419  /*
2420   * Arbitrary limits for _safe_unserialize()
2421   */
2422  define('MAX_SERIALIZED_INPUT_LENGTH', 10240);
2423  define('MAX_SERIALIZED_ARRAY_LENGTH', 256);
2424  define('MAX_SERIALIZED_ARRAY_DEPTH', 5);
2425  
2426  /**
2427   * Credits go to https://github.com/piwik
2428   * Safe unserialize() replacement
2429   * - accepts a strict subset of PHP's native my_serialized representation
2430   * - does not unserialize objects
2431   *
2432   * @param string $str
2433   * @param bool $unlimited Whether to apply limits defined in MAX_SERIALIZED_* constants
2434   * @return mixed
2435   * @throw Exception if $str is malformed or contains unsupported types (e.g., resources, objects)
2436   */
2437  function _safe_unserialize($str, $unlimited = true)
2438  {
2439      if(!$unlimited && strlen($str) > MAX_SERIALIZED_INPUT_LENGTH)
2440      {
2441          // input exceeds MAX_SERIALIZED_INPUT_LENGTH
2442          return false;
2443      }
2444  
2445      if(empty($str) || !is_string($str))
2446      {
2447          return false;
2448      }
2449  
2450      $stack = $list = $expected = array();
2451  
2452      /*
2453       * states:
2454       *   0 - initial state, expecting a single value or array
2455       *   1 - terminal state
2456       *   2 - in array, expecting end of array or a key
2457       *   3 - in array, expecting value or another array
2458       */
2459      $state = 0;
2460      while($state != 1)
2461      {
2462          $type = isset($str[0]) ? $str[0] : '';
2463  
2464          if($type == '}')
2465          {
2466              $str = substr($str, 1);
2467          }
2468          else if($type == 'N' && $str[1] == ';')
2469          {
2470              $value = null;
2471              $str = substr($str, 2);
2472          }
2473          else if($type == 'b' && preg_match('/^b:([01]);/', $str, $matches))
2474          {
2475              $value = $matches[1] == '1' ? true : false;
2476              $str = substr($str, 4);
2477          }
2478          else if($type == 'i' && preg_match('/^i:(-?[0-9]+);(.*)/s', $str, $matches))
2479          {
2480              $value = (int)$matches[1];
2481              $str = $matches[2];
2482          }
2483          else if($type == 'd' && preg_match('/^d:(-?[0-9]+\.?[0-9]*(E[+-][0-9]+)?);(.*)/s', $str, $matches))
2484          {
2485              $value = (float)$matches[1];
2486              $str = $matches[3];
2487          }
2488          else if($type == 's' && preg_match('/^s:([0-9]+):"(.*)/s', $str, $matches) && substr($matches[2], (int)$matches[1], 2) == '";')
2489          {
2490              $value = substr($matches[2], 0, (int)$matches[1]);
2491              $str = substr($matches[2], (int)$matches[1] + 2);
2492          }
2493          else if(
2494              $type == 'a' &&
2495              preg_match('/^a:([0-9]+):{(.*)/s', $str, $matches) &&
2496              ($unlimited || $matches[1] < MAX_SERIALIZED_ARRAY_LENGTH)
2497          )
2498          {
2499              $expectedLength = (int)$matches[1];
2500              $str = $matches[2];
2501          }
2502          else
2503          {
2504              // object or unknown/malformed type
2505              return false;
2506          }
2507  
2508          switch($state)
2509          {
2510              case 3: // in array, expecting value or another array
2511                  if($type == 'a')
2512                  {
2513                      if(!$unlimited && count($stack) >= MAX_SERIALIZED_ARRAY_DEPTH)
2514                      {
2515                          // array nesting exceeds MAX_SERIALIZED_ARRAY_DEPTH
2516                          return false;
2517                      }
2518  
2519                      $stack[] = &$list;
2520                      $list[$key] = array();
2521                      $list = &$list[$key];
2522                      $expected[] = $expectedLength;
2523                      $state = 2;
2524                      break;
2525                  }
2526                  if($type != '}')
2527                  {
2528                      $list[$key] = $value;
2529                      $state = 2;
2530                      break;
2531                  }
2532  
2533                  // missing array value
2534                  return false;
2535  
2536              case 2: // in array, expecting end of array or a key
2537                  if($type == '}')
2538                  {
2539                      if(count($list) < end($expected))
2540                      {
2541                          // array size less than expected
2542                          return false;
2543                      }
2544  
2545                      unset($list);
2546                      $list = &$stack[count($stack)-1];
2547                      array_pop($stack);
2548  
2549                      // go to terminal state if we're at the end of the root array
2550                      array_pop($expected);
2551                      if(count($expected) == 0) {
2552                          $state = 1;
2553                      }
2554                      break;
2555                  }
2556                  if($type == 'i' || $type == 's')
2557                  {
2558                      if(!$unlimited && count($list) >= MAX_SERIALIZED_ARRAY_LENGTH)
2559                      {
2560                          // array size exceeds MAX_SERIALIZED_ARRAY_LENGTH
2561                          return false;
2562                      }
2563                      if(count($list) >= end($expected))
2564                      {
2565                          // array size exceeds expected length
2566                          return false;
2567                      }
2568  
2569                      $key = $value;
2570                      $state = 3;
2571                      break;
2572                  }
2573  
2574                  // illegal array index type
2575                  return false;
2576  
2577              case 0: // expecting array or value
2578                  if($type == 'a')
2579                  {
2580                      if(!$unlimited && count($stack) >= MAX_SERIALIZED_ARRAY_DEPTH)
2581                      {
2582                          // array nesting exceeds MAX_SERIALIZED_ARRAY_DEPTH
2583                          return false;
2584                      }
2585  
2586                      $data = array();
2587                      $list = &$data;
2588                      $expected[] = $expectedLength;
2589                      $state = 2;
2590                      break;
2591                  }
2592                  if($type != '}')
2593                  {
2594                      $data = $value;
2595                      $state = 1;
2596                      break;
2597                  }
2598  
2599                  // not in array
2600                  return false;
2601          }
2602      }
2603  
2604      if(!empty($str))
2605      {
2606          // trailing data in input
2607          return false;
2608      }
2609      return $data;
2610  }
2611  
2612  /**
2613   * Credits go to https://github.com/piwik
2614   * Wrapper for _safe_unserialize() that handles exceptions and multibyte encoding issue
2615   *
2616   * @param string $str
2617   * @param bool $unlimited
2618   * @return mixed
2619   */
2620  function my_unserialize($str, $unlimited = true)
2621  {
2622      // Ensure we use the byte count for strings even when strlen() is overloaded by mb_strlen()
2623      if(function_exists('mb_internal_encoding') && (((int)ini_get('mbstring.func_overload')) & 2))
2624      {
2625          $mbIntEnc = mb_internal_encoding();
2626          mb_internal_encoding('ASCII');
2627      }
2628  
2629      $out = _safe_unserialize($str, $unlimited);
2630  
2631      if(isset($mbIntEnc))
2632      {
2633          mb_internal_encoding($mbIntEnc);
2634      }
2635  
2636      return $out;
2637  }
2638  
2639  /**
2640   * Unserializes data using PHP's `unserialize()`, and its safety options if possible.
2641   * This function should only be used for values from trusted sources.
2642   *
2643   * @param string $str
2644   * @return mixed
2645   */
2646  function native_unserialize($str)
2647  {
2648      if(version_compare(PHP_VERSION, '7.0.0', '>='))
2649      {
2650          return unserialize($str, array('allowed_classes' => false));
2651      }
2652      else
2653      {
2654          return unserialize($str);
2655      }
2656  }
2657  
2658  /**
2659   * Credits go to https://github.com/piwik
2660   * Safe serialize() replacement
2661   * - output a strict subset of PHP's native serialized representation
2662   * - does not my_serialize objects
2663   *
2664   * @param mixed $value
2665   * @return string
2666   * @throw Exception if $value is malformed or contains unsupported types (e.g., resources, objects)
2667   */
2668  function _safe_serialize( $value )
2669  {
2670      if(is_null($value))
2671      {
2672          return 'N;';
2673      }
2674  
2675      if(is_bool($value))
2676      {
2677          return 'b:'.(int)$value.';';
2678      }
2679  
2680      if(is_int($value))
2681      {
2682          return 'i:'.$value.';';
2683      }
2684  
2685      if(is_float($value))
2686      {
2687          return 'd:'.str_replace(',', '.', $value).';';
2688      }
2689  
2690      if(is_string($value))
2691      {
2692          return 's:'.strlen($value).':"'.$value.'";';
2693      }
2694  
2695      if(is_array($value))
2696      {
2697          $out = '';
2698          foreach($value as $k => $v)
2699          {
2700              $out .= _safe_serialize($k) . _safe_serialize($v);
2701          }
2702  
2703          return 'a:'.count($value).':{'.$out.'}';
2704      }
2705  
2706      // safe_serialize cannot my_serialize resources or objects
2707      return false;
2708  }
2709  
2710  /**
2711   * Credits go to https://github.com/piwik
2712   * Wrapper for _safe_serialize() that handles exceptions and multibyte encoding issue
2713   *
2714   * @param mixed $value
2715   * @return string
2716  */
2717  function my_serialize($value)
2718  {
2719      // ensure we use the byte count for strings even when strlen() is overloaded by mb_strlen()
2720      if(function_exists('mb_internal_encoding') && (((int)ini_get('mbstring.func_overload')) & 2))
2721      {
2722          $mbIntEnc = mb_internal_encoding();
2723          mb_internal_encoding('ASCII');
2724      }
2725  
2726      $out = _safe_serialize($value);
2727      if(isset($mbIntEnc))
2728      {
2729          mb_internal_encoding($mbIntEnc);
2730      }
2731  
2732      return $out;
2733  }
2734  
2735  /**
2736   * Returns the serverload of the system.
2737   *
2738   * @return int The serverload of the system.
2739   */
2740  function get_server_load()
2741  {
2742      global $mybb, $lang;
2743  
2744      $serverload = array();
2745  
2746      // DIRECTORY_SEPARATOR checks if running windows
2747      if(DIRECTORY_SEPARATOR != '\\')
2748      {
2749          if(function_exists("sys_getloadavg"))
2750          {
2751              // sys_getloadavg() will return an array with [0] being load within the last minute.
2752              $serverload = sys_getloadavg();
2753  
2754              if(!is_array($serverload))
2755              {
2756                  return $lang->unknown;
2757              }
2758  
2759              $serverload[0] = round($serverload[0], 4);
2760          }
2761          else if(@file_exists("/proc/loadavg") && $load = @file_get_contents("/proc/loadavg"))
2762          {
2763              $serverload = explode(" ", $load);
2764              $serverload[0] = round($serverload[0], 4);
2765          }
2766          if(!is_numeric($serverload[0]))
2767          {
2768              if($mybb->safemode)
2769              {
2770                  return $lang->unknown;
2771              }
2772  
2773              // Suhosin likes to throw a warning if exec is disabled then die - weird
2774              if($func_blacklist = @ini_get('suhosin.executor.func.blacklist'))
2775              {
2776                  if(strpos(",".$func_blacklist.",", 'exec') !== false)
2777                  {
2778                      return $lang->unknown;
2779                  }
2780              }
2781              // PHP disabled functions?
2782              if($func_blacklist = @ini_get('disable_functions'))
2783              {
2784                  if(strpos(",".$func_blacklist.",", 'exec') !== false)
2785                  {
2786                      return $lang->unknown;
2787                  }
2788              }
2789  
2790              $load = @exec("uptime");
2791              $load = explode("load average: ", $load);
2792              $serverload = explode(",", $load[1]);
2793              if(!is_array($serverload))
2794              {
2795                  return $lang->unknown;
2796              }
2797          }
2798      }
2799      else
2800      {
2801          return $lang->unknown;
2802      }
2803  
2804      $returnload = trim($serverload[0]);
2805  
2806      return $returnload;
2807  }
2808  
2809  /**
2810   * Returns the amount of memory allocated to the script.
2811   *
2812   * @return int The amount of memory allocated to the script.
2813   */
2814  function get_memory_usage()
2815  {
2816      if(function_exists('memory_get_peak_usage'))
2817      {
2818          return memory_get_peak_usage(true);
2819      }
2820      elseif(function_exists('memory_get_usage'))
2821      {
2822          return memory_get_usage(true);
2823      }
2824      return false;
2825  }
2826  
2827  /**
2828   * Updates the forum statistics with specific values (or addition/subtraction of the previous value)
2829   *
2830   * @param array $changes Array of items being updated (numthreads,numposts,numusers,numunapprovedthreads,numunapprovedposts,numdeletedposts,numdeletedthreads)
2831   * @param boolean $force Force stats update?
2832   */
2833  function update_stats($changes=array(), $force=false)
2834  {
2835      global $cache, $db;
2836      static $stats_changes;
2837  
2838      if(empty($stats_changes))
2839      {
2840          // Update stats after all changes are done
2841          add_shutdown('update_stats', array(array(), true));
2842      }
2843  
2844      if(empty($stats_changes) || $stats_changes['inserted'])
2845      {
2846          $stats_changes = array(
2847              'numthreads' => '+0',
2848              'numposts' => '+0',
2849              'numusers' => '+0',
2850              'numunapprovedthreads' => '+0',
2851              'numunapprovedposts' => '+0',
2852              'numdeletedposts' => '+0',
2853              'numdeletedthreads' => '+0',
2854              'inserted' => false // Reset after changes are inserted into cache
2855          );
2856          $stats = $stats_changes;
2857      }
2858  
2859      if($force) // Force writing to cache?
2860      {
2861          if(!empty($changes))
2862          {
2863              // Calculate before writing to cache
2864              update_stats($changes);
2865          }
2866          $stats = $cache->read("stats");
2867          $changes = $stats_changes;
2868      }
2869      else
2870      {
2871          $stats = $stats_changes;
2872      }
2873  
2874      $new_stats = array();
2875      $counters = array('numthreads', 'numunapprovedthreads', 'numposts', 'numunapprovedposts', 'numusers', 'numdeletedposts', 'numdeletedthreads');
2876      foreach($counters as $counter)
2877      {
2878          if(array_key_exists($counter, $changes))
2879          {
2880              if(substr($changes[$counter], 0, 2) == "+-")
2881              {
2882                  $changes[$counter] = substr($changes[$counter], 1);
2883              }
2884              // Adding or subtracting from previous value?
2885              if(substr($changes[$counter], 0, 1) == "+" || substr($changes[$counter], 0, 1) == "-")
2886              {
2887                  if((int)$changes[$counter] != 0)
2888                  {
2889                      $new_stats[$counter] = $stats[$counter] + $changes[$counter];
2890                      if(!$force && (substr($stats[$counter], 0, 1) == "+" || substr($stats[$counter], 0, 1) == "-"))
2891                      {
2892                          // We had relative values? Then it is still relative
2893                          if($new_stats[$counter] >= 0)
2894                          {
2895                              $new_stats[$counter] = "+{$new_stats[$counter]}";
2896                          }
2897                      }
2898                      // Less than 0? That's bad
2899                      elseif($new_stats[$counter] < 0)
2900                      {
2901                          $new_stats[$counter] = 0;
2902                      }
2903                  }
2904              }
2905              else
2906              {
2907                  $new_stats[$counter] = $changes[$counter];
2908                  // Less than 0? That's bad
2909                  if($new_stats[$counter] < 0)
2910                  {
2911                      $new_stats[$counter] = 0;
2912                  }
2913              }
2914          }
2915      }
2916  
2917      if(!$force)
2918      {
2919          $stats_changes = array_merge($stats, $new_stats); // Overwrite changed values
2920          return;
2921      }
2922  
2923      // Fetch latest user if the user count is changing
2924      if(array_key_exists('numusers', $changes))
2925      {
2926          $query = $db->simple_select("users", "uid, username", "", array('order_by' => 'regdate', 'order_dir' => 'DESC', 'limit' => 1));
2927          $lastmember = $db->fetch_array($query);
2928          $new_stats['lastuid'] = $lastmember['uid'];
2929          $new_stats['lastusername'] = $lastmember['username'] = htmlspecialchars_uni($lastmember['username']);
2930      }
2931  
2932      if(!empty($new_stats))
2933      {
2934          if(is_array($stats))
2935          {
2936              $stats = array_merge($stats, $new_stats); // Overwrite changed values
2937          }
2938          else
2939          {
2940              $stats = $new_stats;
2941          }
2942      }
2943  
2944      // Update stats row for today in the database
2945      $todays_stats = array(
2946          "dateline" => mktime(0, 0, 0, date("m"), date("j"), date("Y")),
2947          "numusers" => (int)$stats['numusers'],
2948          "numthreads" => (int)$stats['numthreads'],
2949          "numposts" => (int)$stats['numposts']
2950      );
2951      $db->replace_query("stats", $todays_stats, "dateline");
2952  
2953      $cache->update("stats", $stats, "dateline");
2954      $stats_changes['inserted'] = true;
2955  }
2956  
2957  /**
2958   * Updates the forum counters with a specific value (or addition/subtraction of the previous value)
2959   *
2960   * @param int $fid The forum ID
2961   * @param array $changes Array of items being updated (threads, posts, unapprovedthreads, unapprovedposts, deletedposts, deletedthreads) and their value (ex, 1, +1, -1)
2962   */
2963  function update_forum_counters($fid, $changes=array())
2964  {
2965      global $db;
2966  
2967      $update_query = array();
2968  
2969      $counters = array('threads', 'unapprovedthreads', 'posts', 'unapprovedposts', 'deletedposts', 'deletedthreads');
2970  
2971      // Fetch above counters for this forum
2972      $query = $db->simple_select("forums", implode(",", $counters), "fid='{$fid}'");
2973      $forum = $db->fetch_array($query);
2974  
2975      foreach($counters as $counter)
2976      {
2977          if(array_key_exists($counter, $changes))
2978          {
2979              if(substr($changes[$counter], 0, 2) == "+-")
2980              {
2981                  $changes[$counter] = substr($changes[$counter], 1);
2982              }
2983              // Adding or subtracting from previous value?
2984              if(substr($changes[$counter], 0, 1) == "+" || substr($changes[$counter], 0, 1) == "-")
2985              {
2986                  if((int)$changes[$counter] != 0)
2987                  {
2988                      $update_query[$counter] = $forum[$counter] + $changes[$counter];
2989                  }
2990              }
2991              else
2992              {
2993                  $update_query[$counter] = $changes[$counter];
2994              }
2995  
2996              // Less than 0? That's bad
2997              if(isset($update_query[$counter]) && $update_query[$counter] < 0)
2998              {
2999                  $update_query[$counter] = 0;
3000              }
3001          }
3002      }
3003  
3004      // Only update if we're actually doing something
3005      if(count($update_query) > 0)
3006      {
3007          $db->update_query("forums", $update_query, "fid='".(int)$fid."'");
3008      }
3009  
3010      // Guess we should update the statistics too?
3011      $new_stats = array();
3012      if(array_key_exists('threads', $update_query))
3013      {
3014          $threads_diff = $update_query['threads'] - $forum['threads'];
3015          if($threads_diff > -1)
3016          {
3017              $new_stats['numthreads'] = "+{$threads_diff}";
3018          }
3019          else
3020          {
3021              $new_stats['numthreads'] = "{$threads_diff}";
3022          }
3023      }
3024  
3025      if(array_key_exists('unapprovedthreads', $update_query))
3026      {
3027          $unapprovedthreads_diff = $update_query['unapprovedthreads'] - $forum['unapprovedthreads'];
3028          if($unapprovedthreads_diff > -1)
3029          {
3030              $new_stats['numunapprovedthreads'] = "+{$unapprovedthreads_diff}";
3031          }
3032          else
3033          {
3034              $new_stats['numunapprovedthreads'] = "{$unapprovedthreads_diff}";
3035          }
3036      }
3037  
3038      if(array_key_exists('posts', $update_query))
3039      {
3040          $posts_diff = $update_query['posts'] - $forum['posts'];
3041          if($posts_diff > -1)
3042          {
3043              $new_stats['numposts'] = "+{$posts_diff}";
3044          }
3045          else
3046          {
3047              $new_stats['numposts'] = "{$posts_diff}";
3048          }
3049      }
3050  
3051      if(array_key_exists('unapprovedposts', $update_query))
3052      {
3053          $unapprovedposts_diff = $update_query['unapprovedposts'] - $forum['unapprovedposts'];
3054          if($unapprovedposts_diff > -1)
3055          {
3056              $new_stats['numunapprovedposts'] = "+{$unapprovedposts_diff}";
3057          }
3058          else
3059          {
3060              $new_stats['numunapprovedposts'] = "{$unapprovedposts_diff}";
3061          }
3062      }
3063  
3064      if(array_key_exists('deletedposts', $update_query))
3065      {
3066          $deletedposts_diff = $update_query['deletedposts'] - $forum['deletedposts'];
3067          if($deletedposts_diff > -1)
3068          {
3069              $new_stats['numdeletedposts'] = "+{$deletedposts_diff}";
3070          }
3071          else
3072          {
3073              $new_stats['numdeletedposts'] = "{$deletedposts_diff}";
3074          }
3075      }
3076  
3077      if(array_key_exists('deletedthreads', $update_query))
3078      {
3079          $deletedthreads_diff = $update_query['deletedthreads'] - $forum['deletedthreads'];
3080          if($deletedthreads_diff > -1)
3081          {
3082              $new_stats['numdeletedthreads'] = "+{$deletedthreads_diff}";
3083          }
3084          else
3085          {
3086              $new_stats['numdeletedthreads'] = "{$deletedthreads_diff}";
3087          }
3088      }
3089  
3090      if(!empty($new_stats))
3091      {
3092          update_stats($new_stats);
3093      }
3094  }
3095  
3096  /**
3097   * Update the last post information for a specific forum
3098   *
3099   * @param int $fid The forum ID
3100   */
3101  function update_forum_lastpost($fid)
3102  {
3103      global $db;
3104  
3105      // Fetch the last post for this forum
3106      $query = $db->query("
3107          SELECT tid, lastpost, lastposter, lastposteruid, subject
3108          FROM ".TABLE_PREFIX."threads
3109          WHERE fid='{$fid}' AND visible='1' AND closed NOT LIKE 'moved|%'
3110          ORDER BY lastpost DESC
3111          LIMIT 0, 1
3112      ");
3113  
3114      if($db->num_rows($query) > 0)
3115      {
3116          $lastpost = $db->fetch_array($query);
3117  
3118          $updated_forum = array(
3119              "lastpost" => (int)$lastpost['lastpost'],
3120              "lastposter" => $db->escape_string($lastpost['lastposter']),
3121              "lastposteruid" => (int)$lastpost['lastposteruid'],
3122              "lastposttid" => (int)$lastpost['tid'],
3123              "lastpostsubject" => $db->escape_string($lastpost['subject']),
3124          );
3125      }
3126      else {
3127          $updated_forum = array(
3128              "lastpost" => 0,
3129              "lastposter" => '',
3130              "lastposteruid" => 0,
3131              "lastposttid" => 0,
3132              "lastpostsubject" => '',
3133          );
3134      }
3135  
3136      $db->update_query("forums", $updated_forum, "fid='{$fid}'");
3137  }
3138  
3139  /**
3140   * Updates the thread counters with a specific value (or addition/subtraction of the previous value)
3141   *
3142   * @param int $tid The thread ID
3143   * @param array $changes Array of items being updated (replies, unapprovedposts, deletedposts, attachmentcount) and their value (ex, 1, +1, -1)
3144   */
3145  function update_thread_counters($tid, $changes=array())
3146  {
3147      global $db;
3148  
3149      $update_query = array();
3150      $tid = (int)$tid;
3151  
3152      $counters = array('replies', 'unapprovedposts', 'attachmentcount', 'deletedposts', 'attachmentcount');
3153  
3154      // Fetch above counters for this thread
3155      $query = $db->simple_select("threads", implode(",", $counters), "tid='{$tid}'");
3156      $thread = $db->fetch_array($query);
3157  
3158      foreach($counters as $counter)
3159      {
3160          if(array_key_exists($counter, $changes))
3161          {
3162              if(substr($changes[$counter], 0, 2) == "+-")
3163              {
3164                  $changes[$counter] = substr($changes[$counter], 1);
3165              }
3166              // Adding or subtracting from previous value?
3167              if(substr($changes[$counter], 0, 1) == "+" || substr($changes[$counter], 0, 1) == "-")
3168              {
3169                  if((int)$changes[$counter] != 0)
3170                  {
3171                      $update_query[$counter] = $thread[$counter] + $changes[$counter];
3172                  }
3173              }
3174              else
3175              {
3176                  $update_query[$counter] = $changes[$counter];
3177              }
3178  
3179              // Less than 0? That's bad
3180              if(isset($update_query[$counter]) && $update_query[$counter] < 0)
3181              {
3182                  $update_query[$counter] = 0;
3183              }
3184          }
3185      }
3186  
3187      $db->free_result($query);
3188  
3189      // Only update if we're actually doing something
3190      if(count($update_query) > 0)
3191      {
3192          $db->update_query("threads", $update_query, "tid='{$tid}'");
3193      }
3194  }
3195  
3196  /**
3197   * Update the first post and lastpost data for a specific thread
3198   *
3199   * @param int $tid The thread ID
3200   */
3201  function update_thread_data($tid)
3202  {
3203      global $db;
3204  
3205      $thread = get_thread($tid);
3206  
3207      // If this is a moved thread marker, don't update it - we need it to stay as it is
3208      if(strpos($thread['closed'], 'moved|') !== false)
3209      {
3210          return;
3211      }
3212  
3213      $query = $db->query("
3214          SELECT u.uid, u.username, p.username AS postusername, p.dateline
3215          FROM ".TABLE_PREFIX."posts p
3216          LEFT JOIN ".TABLE_PREFIX."users u ON (u.uid=p.uid)
3217          WHERE p.tid='$tid' AND p.visible='1'
3218          ORDER BY p.dateline DESC, p.pid DESC
3219          LIMIT 1"
3220      );
3221      $lastpost = $db->fetch_array($query);
3222  
3223      $db->free_result($query);
3224  
3225      $query = $db->query("
3226          SELECT u.uid, u.username, p.pid, p.username AS postusername, p.dateline
3227          FROM ".TABLE_PREFIX."posts p
3228          LEFT JOIN ".TABLE_PREFIX."users u ON (u.uid=p.uid)
3229          WHERE p.tid='$tid'
3230          ORDER BY p.dateline ASC, p.pid ASC
3231          LIMIT 1
3232      ");
3233      $firstpost = $db->fetch_array($query);
3234  
3235      $db->free_result($query);
3236  
3237      if(empty($firstpost['username']))
3238      {
3239          $firstpost['username'] = $firstpost['postusername'];
3240      }
3241  
3242      if(empty($lastpost['username']))
3243      {
3244          $lastpost['username'] = $lastpost['postusername'];
3245      }
3246  
3247      if(empty($lastpost['dateline']))
3248      {
3249          $lastpost['username'] = $firstpost['username'];
3250          $lastpost['uid'] = $firstpost['uid'];
3251          $lastpost['dateline'] = $firstpost['dateline'];
3252      }
3253  
3254      $lastpost['username'] = $db->escape_string($lastpost['username']);
3255      $firstpost['username'] = $db->escape_string($firstpost['username']);
3256  
3257      $update_array = array(
3258          'firstpost' => (int)$firstpost['pid'],
3259          'username' => $firstpost['username'],
3260          'uid' => (int)$firstpost['uid'],
3261          'dateline' => (int)$firstpost['dateline'],
3262          'lastpost' => (int)$lastpost['dateline'],
3263          'lastposter' => $lastpost['username'],
3264          'lastposteruid' => (int)$lastpost['uid'],
3265      );
3266      $db->update_query("threads", $update_array, "tid='{$tid}'");
3267  }
3268  
3269  /**
3270   * Updates the user counters with a specific value (or addition/subtraction of the previous value)
3271   *
3272   * @param int $uid The user ID
3273   * @param array $changes Array of items being updated (postnum, threadnum) and their value (ex, 1, +1, -1)
3274   */
3275  function update_user_counters($uid, $changes=array())
3276  {
3277      global $db;
3278  
3279      $update_query = array();
3280  
3281      $counters = array('postnum', 'threadnum');
3282      $uid = (int)$uid;
3283  
3284      // Fetch above counters for this user
3285      $query = $db->simple_select("users", implode(",", $counters), "uid='{$uid}'");
3286      $user = $db->fetch_array($query);
3287      
3288      if($user)
3289      {
3290          foreach($counters as $counter)
3291          {
3292              if(array_key_exists($counter, $changes))
3293              {
3294                  if(substr($changes[$counter], 0, 2) == "+-")
3295                  {
3296                      $changes[$counter] = substr($changes[$counter], 1);
3297                  }
3298                  // Adding or subtracting from previous value?
3299                  if(substr($changes[$counter], 0, 1) == "+" || substr($changes[$counter], 0, 1) == "-")
3300                  {
3301                      if((int)$changes[$counter] != 0)
3302                      {
3303                          $update_query[$counter] = $user[$counter] + $changes[$counter];
3304                      }
3305                  }
3306                  else
3307                  {
3308                      $update_query[$counter] = $changes[$counter];
3309                  }
3310  
3311                  // Less than 0? That's bad
3312                  if(isset($update_query[$counter]) && $update_query[$counter] < 0)
3313                  {
3314                      $update_query[$counter] = 0;
3315                  }
3316              }
3317          }
3318      }
3319  
3320      $db->free_result($query);
3321  
3322      // Only update if we're actually doing something
3323      if(count($update_query) > 0)
3324      {
3325          $db->update_query("users", $update_query, "uid='{$uid}'");
3326      }
3327  }
3328  
3329  /**
3330   * Deletes a thread from the database
3331   *
3332   * @param int $tid The thread ID
3333   * @return bool
3334   */
3335  function delete_thread($tid)
3336  {
3337      global $moderation;
3338  
3339      if(!is_object($moderation))
3340      {
3341          require_once  MYBB_ROOT."inc/class_moderation.php";
3342          $moderation = new Moderation;
3343      }
3344  
3345      return $moderation->delete_thread($tid);
3346  }
3347  
3348  /**
3349   * Deletes a post from the database
3350   *
3351   * @param int $pid The thread ID
3352   * @return bool
3353   */
3354  function delete_post($pid)
3355  {
3356      global $moderation;
3357  
3358      if(!is_object($moderation))
3359      {
3360          require_once  MYBB_ROOT."inc/class_moderation.php";
3361          $moderation = new Moderation;
3362      }
3363  
3364      return $moderation->delete_post($pid);
3365  }
3366  
3367  /**
3368   * Builds a forum jump menu
3369   *
3370   * @param int $pid The parent forum to start with
3371   * @param int $selitem The selected item ID
3372   * @param int $addselect If we need to add select boxes to this cal or not
3373   * @param string $depth The current depth of forums we're at
3374   * @param int $showextras Whether or not to show extra items such as User CP, Forum home
3375   * @param boolean $showall Ignore the showinjump setting and show all forums (for moderation pages)
3376   * @param mixed $permissions deprecated
3377   * @param string $name The name of the forum jump
3378   * @return string Forum jump items
3379   */
3380  function build_forum_jump($pid=0, $selitem=0, $addselect=1, $depth="", $showextras=1, $showall=false, $permissions="", $name="fid")
3381  {
3382      global $forum_cache, $jumpfcache, $permissioncache, $mybb, $forumjump, $forumjumpbits, $gobutton, $theme, $templates, $lang;
3383  
3384      $pid = (int)$pid;
3385  
3386      if(!is_array($jumpfcache))
3387      {
3388          if(!is_array($forum_cache))
3389          {
3390              cache_forums();
3391          }
3392  
3393          foreach($forum_cache as $fid => $forum)
3394          {
3395              if($forum['active'] != 0)
3396              {
3397                  $jumpfcache[$forum['pid']][$forum['disporder']][$forum['fid']] = $forum;
3398              }
3399          }
3400      }
3401  
3402      if(!is_array($permissioncache))
3403      {
3404          $permissioncache = forum_permissions();
3405      }
3406  
3407      if(isset($jumpfcache[$pid]) && is_array($jumpfcache[$pid]))
3408      {
3409          foreach($jumpfcache[$pid] as $main)
3410          {
3411              foreach($main as $forum)
3412              {
3413                  $perms = $permissioncache[$forum['fid']];
3414  
3415                  if($forum['fid'] != "0" && ($perms['canview'] != 0 || $mybb->settings['hideprivateforums'] == 0) && $forum['linkto'] == '' && ($forum['showinjump'] != 0 || $showall == true))
3416                  {
3417                      $optionselected = "";
3418  
3419                      if($selitem == $forum['fid'])
3420                      {
3421                          $optionselected = 'selected="selected"';
3422                      }
3423  
3424                      $forum['name'] = htmlspecialchars_uni(strip_tags($forum['name']));
3425  
3426                      eval("\$forumjumpbits .= \"".$templates->get("forumjump_bit")."\";");
3427  
3428                      if($forum_cache[$forum['fid']])
3429                      {
3430                          $newdepth = $depth."--";
3431                          $forumjumpbits .= build_forum_jump($forum['fid'], $selitem, 0, $newdepth, $showextras, $showall);
3432                      }
3433                  }
3434              }
3435          }
3436      }
3437  
3438      if($addselect)
3439      {
3440          if($showextras == 0)
3441          {
3442              $template = "special";
3443          }
3444          else
3445          {
3446              $template = "advanced";
3447  
3448              if(strpos(FORUM_URL, '.html') !== false)
3449              {
3450                  $forum_link = "'".str_replace('{fid}', "'+option+'", FORUM_URL)."'";
3451              }
3452              else
3453              {
3454                  $forum_link = "'".str_replace('{fid}', "'+option", FORUM_URL);
3455              }
3456          }
3457  
3458          eval("\$forumjump = \"".$templates->get("forumjump_".$template)."\";");
3459      }
3460  
3461      return $forumjump;
3462  }
3463  
3464  /**
3465   * Returns the extension of a file.
3466   *
3467   * @param string $file The filename.
3468   * @return string The extension of the file.
3469   */
3470  function get_extension($file)
3471  {
3472      return my_strtolower(my_substr(strrchr($file, "."), 1));
3473  }
3474  
3475  /**
3476   * Generates a random string.
3477   *
3478   * @param int $length The length of the string to generate.
3479   * @param bool $complex Whether to return complex string. Defaults to false
3480   * @return string The random string.
3481   */
3482  function random_str($length=8, $complex=false)
3483  {
3484      $set = array_merge(range(0, 9), range('A', 'Z'), range('a', 'z'));
3485      $str = array();
3486  
3487      // Complex strings have always at least 3 characters, even if $length < 3
3488      if($complex == true)
3489      {
3490          // At least one number
3491          $str[] = $set[my_rand(0, 9)];
3492  
3493          // At least one big letter
3494          $str[] = $set[my_rand(10, 35)];
3495  
3496          // At least one small letter
3497          $str[] = $set[my_rand(36, 61)];
3498  
3499          $length -= 3;
3500      }
3501  
3502      for($i = 0; $i < $length; ++$i)
3503      {
3504          $str[] = $set[my_rand(0, 61)];
3505      }
3506  
3507      // Make sure they're in random order and convert them to a string
3508      shuffle($str);
3509  
3510      return implode($str);
3511  }
3512  
3513  /**
3514   * Formats a username based on their display group
3515   *
3516   * @param string $username The username
3517   * @param int $usergroup The usergroup for the user
3518   * @param int $displaygroup The display group for the user
3519   * @return string The formatted username
3520   */
3521  function format_name($username, $usergroup, $displaygroup=0)
3522  {
3523      global $groupscache, $cache, $plugins;
3524  
3525      static $formattednames = array();
3526  
3527      if(!isset($formattednames[$username]))
3528      {
3529          if(!is_array($groupscache))
3530          {
3531              $groupscache = $cache->read("usergroups");
3532          }
3533  
3534          if($displaygroup != 0)
3535          {
3536              $usergroup = $displaygroup;
3537          }
3538  
3539          $format = "{username}";
3540  
3541          if(isset($groupscache[$usergroup]))
3542          {
3543              $ugroup = $groupscache[$usergroup];
3544  
3545              if(strpos($ugroup['namestyle'], "{username}") !== false)
3546              {
3547                  $format = $ugroup['namestyle'];
3548              }
3549          }
3550  
3551          $format = stripslashes($format);
3552  
3553          $parameters = compact('username', 'usergroup', 'displaygroup', 'format');
3554  
3555          $parameters = $plugins->run_hooks('format_name', $parameters);
3556  
3557          $format = $parameters['format'];
3558  
3559          $formattednames[$username] = str_replace("{username}", $username, $format);
3560      }
3561  
3562      return $formattednames[$username];
3563  }
3564  
3565  /**
3566   * Formats an avatar to a certain dimension
3567   *
3568   * @param string $avatar The avatar file name
3569   * @param string $dimensions Dimensions of the avatar, width x height (e.g. 44|44)
3570   * @param string $max_dimensions The maximum dimensions of the formatted avatar
3571   * @return array Information for the formatted avatar
3572   */
3573  function format_avatar($avatar, $dimensions = '', $max_dimensions = '')
3574  {
3575      global $mybb, $theme;
3576      static $avatars;
3577  
3578      if(!isset($avatars))
3579      {
3580          $avatars = array();
3581      }
3582  
3583      if(my_strpos($avatar, '://') !== false && !$mybb->settings['allowremoteavatars'])
3584      {
3585          // Remote avatar, but remote avatars are disallowed.
3586          $avatar = null;
3587      }
3588  
3589      if(!$avatar)
3590      {
3591          // Default avatar
3592          if(defined('IN_ADMINCP'))
3593          {
3594              $theme['imgdir'] = '../images';
3595          }
3596  
3597          $avatar = str_replace('{theme}', $theme['imgdir'], $mybb->settings['useravatar']);
3598          $dimensions = $mybb->settings['useravatardims'];
3599      }
3600  
3601      if(!$max_dimensions)
3602      {
3603          $max_dimensions = $mybb->settings['maxavatardims'];
3604      }
3605  
3606      // An empty key wouldn't work so we need to add a fall back
3607      $key = $dimensions;
3608      if(empty($key))
3609      {
3610          $key = 'default';
3611      }
3612      $key2 = $max_dimensions;
3613      if(empty($key2))
3614      {
3615          $key2 = 'default';
3616      }
3617  
3618      if(isset($avatars[$avatar][$key][$key2]))
3619      {
3620          return $avatars[$avatar][$key][$key2];
3621      }
3622  
3623      $avatar_width_height = '';
3624  
3625      if($dimensions)
3626      {
3627          $dimensions = preg_split('/[|x]/', $dimensions);
3628  
3629          if($dimensions[0] && $dimensions[1])
3630          {
3631              $dims_arr = preg_split('/[|x]/', $max_dimensions);
3632              if (count($dims_arr) == 2)
3633              {
3634                  list($max_width, $max_height) = $dims_arr;
3635              }
3636  
3637              if(count($dims_arr) == 2 && ($dimensions[0] > $max_width || $dimensions[1] > $max_height))
3638              {
3639                  require_once  MYBB_ROOT."inc/functions_image.php";
3640                  $scaled_dimensions = scale_image($dimensions[0], $dimensions[1], $max_width, $max_height);
3641                  $avatar_width_height = "width=\"{$scaled_dimensions['width']}\" height=\"{$scaled_dimensions['height']}\"";
3642              }
3643              else
3644              {
3645                  $avatar_width_height = "width=\"{$dimensions[0]}\" height=\"{$dimensions[1]}\"";
3646              }
3647          }
3648      }
3649  
3650      $avatars[$avatar][$key][$key2] = array(
3651          'image' => htmlspecialchars_uni($mybb->get_asset_url($avatar)),
3652          'width_height' => $avatar_width_height
3653      );
3654  
3655      return $avatars[$avatar][$key][$key2];
3656  }
3657  
3658  /**
3659   * Build the javascript based MyCode inserter.
3660   *
3661   * @param string $bind The ID of the textarea to bind to. Defaults to "message".
3662   * @param bool $smilies Whether to include smilies. Defaults to true.
3663   *
3664   * @return string The MyCode inserter
3665   */
3666  function build_mycode_inserter($bind="message", $smilies = true)
3667  {
3668      global $db, $mybb, $theme, $templates, $lang, $plugins, $smiliecache, $cache;
3669  
3670      $codeinsert  = '';
3671  
3672      if($mybb->settings['bbcodeinserter'] != 0)
3673      {
3674          $editor_lang_strings = array(
3675              "editor_bold" => "Bold",
3676              "editor_italic" => "Italic",
3677              "editor_underline" => "Underline",
3678              "editor_strikethrough" => "Strikethrough",
3679              "editor_subscript" => "Subscript",
3680              "editor_superscript" => "Superscript",
3681              "editor_alignleft" => "Align left",
3682              "editor_center" => "Center",
3683              "editor_alignright" => "Align right",
3684              "editor_justify" => "Justify",
3685              "editor_fontname" => "Font Name",
3686              "editor_fontsize" => "Font Size",
3687              "editor_fontcolor" => "Font Color",
3688              "editor_removeformatting" => "Remove Formatting",
3689              "editor_cut" => "Cut",
3690              "editor_cutnosupport" => "Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X",
3691              "editor_copy" => "Copy",
3692              "editor_copynosupport" => "Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C",
3693              "editor_paste" => "Paste",
3694              "editor_pastenosupport" => "Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V",
3695              "editor_pasteentertext" => "Paste your text inside the following box:",
3696              "editor_pastetext" => "PasteText",
3697              "editor_numlist" => "Numbered list",
3698              "editor_bullist" => "Bullet list",
3699              "editor_undo" => "Undo",
3700              "editor_redo" => "Redo",
3701              "editor_rows" => "Rows:",
3702              "editor_cols" => "Cols:",
3703              "editor_inserttable" => "Insert a table",
3704              "editor_inserthr" => "Insert a horizontal rule",
3705              "editor_code" => "Code",
3706              "editor_width" => "Width (optional):",
3707              "editor_height" => "Height (optional):",
3708              "editor_insertimg" => "Insert an image",
3709              "editor_email" => "E-mail:",
3710              "editor_insertemail" => "Insert an email",
3711              "editor_url" => "URL:",
3712              "editor_insertlink" => "Insert a link",
3713              "editor_unlink" => "Unlink",
3714              "editor_more" => "More",
3715              "editor_insertemoticon" => "Insert an emoticon",
3716              "editor_videourl" => "Video URL:",
3717              "editor_videotype" => "Video Type:",
3718              "editor_insert" => "Insert",
3719              "editor_insertyoutubevideo" => "Insert a YouTube video",
3720              "editor_currentdate" => "Insert current date",
3721              "editor_currenttime" => "Insert current time",
3722              "editor_print" => "Print",
3723              "editor_viewsource" => "View source",
3724              "editor_description" => "Description (optional):",
3725              "editor_enterimgurl" => "Enter the image URL:",
3726              "editor_enteremail" => "Enter the e-mail address:",
3727              "editor_enterdisplayedtext" => "Enter the displayed text:",
3728              "editor_enterurl" => "Enter URL:",
3729              "editor_enteryoutubeurl" => "Enter the YouTube video URL or ID:",
3730              "editor_insertquote" => "Insert a Quote",
3731              "editor_invalidyoutube" => "Invalid YouTube video",
3732              "editor_dailymotion" => "Dailymotion",
3733              "editor_metacafe" => "MetaCafe",
3734              "editor_mixer" => "Mixer",
3735              "editor_vimeo" => "Vimeo",
3736              "editor_youtube" => "Youtube",
3737              "editor_facebook" => "Facebook",
3738              "editor_liveleak" => "LiveLeak",
3739              "editor_insertvideo" => "Insert a video",
3740              "editor_php" => "PHP",
3741              "editor_maximize" => "Maximize"
3742          );
3743          $editor_language = "(function ($) {\n$.sceditor.locale[\"mybblang\"] = {\n";
3744  
3745          $editor_lang_strings = $plugins->run_hooks("mycode_add_codebuttons", $editor_lang_strings);
3746  
3747          $editor_languages_count = count($editor_lang_strings);
3748          $i = 0;
3749          foreach($editor_lang_strings as $lang_string => $key)
3750          {
3751              $i++;
3752              $js_lang_string = str_replace("\"", "\\\"", $key);
3753              $string = str_replace("\"", "\\\"", $lang->$lang_string);
3754              $editor_language .= "\t\"{$js_lang_string}\": \"{$string}\"";
3755  
3756              if($i < $editor_languages_count)
3757              {
3758                  $editor_language .= ",";
3759              }
3760  
3761              $editor_language .= "\n";
3762          }
3763  
3764          $editor_language .= "}})(jQuery);";
3765  
3766          if(defined("IN_ADMINCP"))
3767          {
3768              global $page;
3769              $codeinsert = $page->build_codebuttons_editor($bind, $editor_language, $smilies);
3770          }
3771          else
3772          {
3773              // Smilies
3774              $emoticon = "";
3775              $emoticons_enabled = "false";
3776              if($smilies)
3777              {
3778                  if(!$smiliecache)
3779                  {
3780                      if(!isset($smilie_cache) || !is_array($smilie_cache))
3781                      {
3782                          $smilie_cache = $cache->read("smilies");
3783                      }
3784                      foreach($smilie_cache as $smilie)
3785                      {
3786                          $smilie['image'] = str_replace("{theme}", $theme['imgdir'], $smilie['image']);
3787                          $smiliecache[$smilie['sid']] = $smilie;
3788                      }
3789                  }
3790  
3791                  if($mybb->settings['smilieinserter'] && $mybb->settings['smilieinsertercols'] && $mybb->settings['smilieinsertertot'] && !empty($smiliecache))
3792                  {
3793                      $emoticon = ",emoticon";
3794                  }
3795                  $emoticons_enabled = "true";
3796  
3797                  unset($smilie);
3798  
3799                  if(is_array($smiliecache))
3800                  {
3801                      reset($smiliecache);
3802  
3803                      $dropdownsmilies = $moresmilies = $hiddensmilies = "";
3804                      $i = 0;
3805  
3806                      foreach($smiliecache as $smilie)
3807                      {
3808                          $finds = explode("\n", $smilie['find']);
3809                          $finds_count = count($finds);
3810  
3811                          // Only show the first text to replace in the box
3812                          $smilie['find'] = $finds[0];
3813  
3814                          $find = str_replace(array('\\', '"'), array('\\\\', '\"'), htmlspecialchars_uni($smilie['find']));
3815                          $image = htmlspecialchars_uni($mybb->get_asset_url($smilie['image']));
3816                          $image = str_replace(array('\\', '"'), array('\\\\', '\"'), $image);
3817  
3818                          if(!$mybb->settings['smilieinserter'] || !$mybb->settings['smilieinsertercols'] || !$mybb->settings['smilieinsertertot'] || !$smilie['showclickable'])
3819                          {
3820                              $hiddensmilies .= '"'.$find.'": "'.$image.'",';
3821                          }
3822                          elseif($i < $mybb->settings['smilieinsertertot'])
3823                          {
3824                              $dropdownsmilies .= '"'.$find.'": "'.$image.'",';
3825                              ++$i;
3826                          }
3827                          else
3828                          {
3829                              $moresmilies .= '"'.$find.'": "'.$image.'",';
3830                          }
3831  
3832                          for($j = 1; $j < $finds_count; ++$j)
3833                          {
3834                              $find = str_replace(array('\\', '"'), array('\\\\', '\"'), htmlspecialchars_uni($finds[$j]));
3835                              $hiddensmilies .= '"'.$find.'": "'.$image.'",';
3836                          }
3837                      }
3838                  }
3839              }
3840  
3841              $basic1 = $basic2 = $align = $font = $size = $color = $removeformat = $email = $link = $list = $code = $sourcemode = "";
3842  
3843              if($mybb->settings['allowbasicmycode'] == 1)
3844              {
3845                  $basic1 = "bold,italic,underline,strike|";
3846                  $basic2 = "horizontalrule,";
3847              }
3848  
3849              if($mybb->settings['allowalignmycode'] == 1)
3850              {
3851                  $align = "left,center,right,justify|";
3852              }
3853  
3854              if($mybb->settings['allowfontmycode'] == 1)
3855              {
3856                  $font = "font,";
3857              }
3858  
3859              if($mybb->settings['allowsizemycode'] == 1)
3860              {
3861                  $size = "size,";
3862              }
3863  
3864              if($mybb->settings['allowcolormycode'] == 1)
3865              {
3866                  $color = "color,";
3867              }
3868  
3869              if($mybb->settings['allowfontmycode'] == 1 || $mybb->settings['allowsizemycode'] == 1 || $mybb->settings['allowcolormycode'] == 1)
3870              {
3871                  $removeformat = "removeformat|";
3872              }
3873  
3874              if($mybb->settings['allowemailmycode'] == 1)
3875              {
3876                  $email = "email,";
3877              }
3878  
3879              if($mybb->settings['allowlinkmycode'] == 1)
3880              {
3881                  $link = "link,unlink";
3882              }
3883  
3884              if($mybb->settings['allowlistmycode'] == 1)
3885              {
3886                  $list = "bulletlist,orderedlist|";
3887              }
3888  
3889              if($mybb->settings['allowcodemycode'] == 1)
3890              {
3891                  $code = "code,php,";
3892              }
3893  
3894              if($mybb->user['sourceeditor'] == 1)
3895              {
3896                  $sourcemode = "MyBBEditor.sourceMode(true);";
3897              }
3898  
3899              eval("\$codeinsert = \"".$templates->get("codebuttons")."\";");
3900          }
3901      }
3902  
3903      return $codeinsert;
3904  }
3905  
3906  /**
3907   * @param int $tid
3908   * @param array $postoptions The options carried with form submit
3909   *
3910   * @return string Predefined / updated subscription method of the thread for the user
3911   */
3912  function get_subscription_method($tid = 0, $postoptions = array())
3913  {
3914      global $mybb;
3915  
3916      $subscription_methods = array('', 'none', 'email', 'pm'); // Define methods
3917      $subscription_method = (int)$mybb->user['subscriptionmethod']; // Set user default
3918  
3919      // If no user default method available then reset method
3920      if(!$subscription_method)
3921      {
3922          $subscription_method = 0;
3923      }
3924  
3925      // Return user default if no thread id available, in case
3926      if(!(int)$tid || (int)$tid <= 0)
3927      {
3928          return $subscription_methods[$subscription_method];
3929      }
3930  
3931      // If method not predefined set using data from database
3932      if(isset($postoptions['subscriptionmethod']))
3933      {
3934          $method = trim($postoptions['subscriptionmethod']);
3935          return (in_array($method, $subscription_methods)) ? $method : $subscription_methods[0];
3936      }
3937      else
3938      {
3939          global $db;
3940  
3941          $query = $db->simple_select("threadsubscriptions", "tid, notification", "tid='".(int)$tid."' AND uid='".$mybb->user['uid']."'", array('limit' => 1));
3942          $subscription = $db->fetch_array($query);
3943  
3944          if($subscription)
3945          {
3946              $subscription_method = (int)$subscription['notification'] + 1;
3947          }
3948      }
3949  
3950      return $subscription_methods[$subscription_method];
3951  }
3952  
3953  /**
3954   * Build the javascript clickable smilie inserter
3955   *
3956   * @return string The clickable smilies list
3957   */
3958  function build_clickable_smilies()
3959  {
3960      global $cache, $smiliecache, $theme, $templates, $lang, $mybb, $smiliecount;
3961  
3962      if($mybb->settings['smilieinserter'] != 0 && $mybb->settings['smilieinsertercols'] && $mybb->settings['smilieinsertertot'])
3963      {
3964          if(!$smiliecount)
3965          {
3966              $smilie_cache = $cache->read("smilies");
3967              $smiliecount = count($smilie_cache);
3968          }
3969  
3970          if(!$smiliecache)
3971          {
3972              if(!is_array($smilie_cache))
3973              {
3974                  $smilie_cache = $cache->read("smilies");
3975              }
3976              foreach($smilie_cache as $smilie)
3977              {
3978                  $smilie['image'] = str_replace("{theme}", $theme['imgdir'], $smilie['image']);
3979                  $smiliecache[$smilie['sid']] = $smilie;
3980              }
3981          }
3982  
3983          unset($smilie);
3984  
3985          if(is_array($smiliecache))
3986          {
3987              reset($smiliecache);
3988  
3989              $getmore = '';
3990              if($mybb->settings['smilieinsertertot'] >= $smiliecount)
3991              {
3992                  $mybb->settings['smilieinsertertot'] = $smiliecount;
3993              }
3994              else if($mybb->settings['smilieinsertertot'] < $smiliecount)
3995              {
3996                  $smiliecount = $mybb->settings['smilieinsertertot'];
3997                  eval("\$getmore = \"".$templates->get("smilieinsert_getmore")."\";");
3998              }
3999  
4000              $smilies = $smilie_icons = '';
4001              $counter = 0;
4002              $i = 0;
4003  
4004              $extra_class = '';
4005              foreach($smiliecache as $smilie)
4006              {
4007                  if($i < $mybb->settings['smilieinsertertot'] && $smilie['showclickable'] != 0)
4008                  {
4009                      $smilie['image'] = str_replace("{theme}", $theme['imgdir'], $smilie['image']);
4010                      $smilie['image'] = htmlspecialchars_uni($mybb->get_asset_url($smilie['image']));
4011                      $smilie['name'] = htmlspecialchars_uni($smilie['name']);
4012  
4013                      // Only show the first text to replace in the box
4014                      $temp = explode("\n", $smilie['find']); // assign to temporary variable for php 5.3 compatibility
4015                      $smilie['find'] = $temp[0];
4016  
4017                      $find = str_replace(array('\\', "'"), array('\\\\', "\'"), htmlspecialchars_uni($smilie['find']));
4018  
4019                      $onclick = " onclick=\"MyBBEditor.insertText(' $find ');\"";
4020                      $extra_class = ' smilie_pointer';
4021                      eval('$smilie = "'.$templates->get('smilie', 1, 0).'";');
4022                      eval("\$smilie_icons .= \"".$templates->get("smilieinsert_smilie")."\";");
4023                      ++$i;
4024                      ++$counter;
4025  
4026                      if($counter == $mybb->settings['smilieinsertercols'])
4027                      {
4028                          $counter = 0;
4029                          eval("\$smilies .= \"".$templates->get("smilieinsert_row")."\";");
4030                          $smilie_icons = '';
4031                      }
4032                  }
4033              }
4034  
4035              if($counter != 0)
4036              {
4037                  $colspan = $mybb->settings['smilieinsertercols'] - $counter;
4038                  eval("\$smilies .= \"".$templates->get("smilieinsert_row_empty")."\";");
4039              }
4040  
4041              eval("\$clickablesmilies = \"".$templates->get("smilieinsert")."\";");
4042          }
4043          else
4044          {
4045              $clickablesmilies = "";
4046          }
4047      }
4048      else
4049      {
4050          $clickablesmilies = "";
4051      }
4052  
4053      return $clickablesmilies;
4054  }
4055  
4056  /**
4057   * Builds thread prefixes and returns a selected prefix (or all)
4058   *
4059   *  @param int $pid The prefix ID (0 to return all)
4060   *  @return array|false The thread prefix's values (or all thread prefixes)
4061   */
4062  function build_prefixes($pid=0)
4063  {
4064      global $cache;
4065      static $prefixes_cache;
4066  
4067      if(is_array($prefixes_cache))
4068      {
4069          if($pid > 0 && isset($prefixes_cache[$pid]) && is_array($prefixes_cache[$pid]))
4070          {
4071              return $prefixes_cache[$pid];
4072          }
4073  
4074          return $prefixes_cache;
4075      }
4076  
4077      $prefix_cache = $cache->read("threadprefixes");
4078  
4079      if(!is_array($prefix_cache))
4080      {
4081          // No cache
4082          $prefix_cache = $cache->read("threadprefixes", true);
4083  
4084          if(!is_array($prefix_cache))
4085          {
4086              return array();
4087          }
4088      }
4089  
4090      $prefixes_cache = array();
4091      foreach($prefix_cache as $prefix)
4092      {
4093          $prefixes_cache[$prefix['pid']] = $prefix;
4094      }
4095  
4096      if($pid != 0 && isset($prefixes_cache[$pid]) && is_array($prefixes_cache[$pid]))
4097      {
4098          return $prefixes_cache[$pid];
4099      }
4100      else if(!empty($prefixes_cache))
4101      {
4102          return $prefixes_cache;
4103      }
4104  
4105      return false;
4106  }
4107  
4108  /**
4109   * Build the thread prefix selection menu for the current user
4110   *
4111   *  @param int|string $fid The forum ID (integer ID or string all)
4112   *  @param int|string $selected_pid The selected prefix ID (integer ID or string any)
4113   *  @param int $multiple Allow multiple prefix selection
4114   *  @param int $previous_pid The previously selected prefix ID
4115   *  @return string The thread prefix selection menu
4116   */
4117  function build_prefix_select($fid, $selected_pid=0, $multiple=0, $previous_pid=0)
4118  {
4119      global $cache, $db, $lang, $mybb, $templates;
4120  
4121      if($fid != 'all')
4122      {
4123          $fid = (int)$fid;
4124      }
4125  
4126      $prefix_cache = build_prefixes(0);
4127      if(empty($prefix_cache))
4128      {
4129          // We've got no prefixes to show
4130          return '';
4131      }
4132  
4133      // Go through each of our prefixes and decide which ones we can use
4134      $prefixes = array();
4135      foreach($prefix_cache as $prefix)
4136      {
4137          if($fid != "all" && $prefix['forums'] != "-1")
4138          {
4139              // Decide whether this prefix can be used in our forum
4140              $forums = explode(",", $prefix['forums']);
4141  
4142              if(!in_array($fid, $forums) && $prefix['pid'] != $previous_pid)
4143              {
4144                  // This prefix is not in our forum list
4145                  continue;
4146              }
4147          }
4148  
4149          if(is_member($prefix['groups']) || $prefix['pid'] == $previous_pid)
4150          {
4151              // The current user can use this prefix
4152              $prefixes[$prefix['pid']] = $prefix;
4153          }
4154      }
4155  
4156      if(empty($prefixes))
4157      {
4158          return '';
4159      }
4160  
4161      $prefixselect = $prefixselect_prefix = '';
4162  
4163      if($multiple == 1)
4164      {
4165          $any_selected = "";
4166          if($selected_pid == 'any')
4167          {
4168              $any_selected = " selected=\"selected\"";
4169          }
4170      }
4171  
4172      $default_selected = "";
4173      if(((int)$selected_pid == 0) && $selected_pid != 'any')
4174      {
4175          $default_selected = " selected=\"selected\"";
4176      }
4177  
4178      foreach($prefixes as $prefix)
4179      {
4180          $selected = "";
4181          if($prefix['pid'] == $selected_pid)
4182          {
4183              $selected = " selected=\"selected\"";
4184          }
4185  
4186          $prefix['prefix'] = htmlspecialchars_uni($prefix['prefix']);
4187          eval("\$prefixselect_prefix .= \"".$templates->get("post_prefixselect_prefix")."\";");
4188      }
4189  
4190      if($multiple != 0)
4191      {
4192          eval("\$prefixselect = \"".$templates->get("post_prefixselect_multiple")."\";");
4193      }
4194      else
4195      {
4196          eval("\$prefixselect = \"".$templates->get("post_prefixselect_single")."\";");
4197      }
4198  
4199      return $prefixselect;
4200  }
4201  
4202  /**
4203   * Build the thread prefix selection menu for a forum without group permission checks
4204   *
4205   *  @param int $fid The forum ID (integer ID)
4206   *  @param int $selected_pid The selected prefix ID (integer ID)
4207   *  @return string The thread prefix selection menu
4208   */
4209  function build_forum_prefix_select($fid, $selected_pid=0)
4210  {
4211      global $cache, $db, $lang, $mybb, $templates;
4212  
4213      $fid = (int)$fid;
4214  
4215      $prefix_cache = build_prefixes(0);
4216      if(empty($prefix_cache))
4217      {
4218          // We've got no prefixes to show
4219          return '';
4220      }
4221  
4222      // Go through each of our prefixes and decide which ones we can use
4223      $prefixes = array();
4224      foreach($prefix_cache as $prefix)
4225      {
4226          if($prefix['forums'] != "-1")
4227          {
4228              // Decide whether this prefix can be used in our forum
4229              $forums = explode(",", $prefix['forums']);
4230  
4231              if(in_array($fid, $forums))
4232              {
4233                  // This forum can use this prefix!
4234                  $prefixes[$prefix['pid']] = $prefix;
4235              }
4236          }
4237          else
4238          {
4239              // This prefix is for anybody to use...
4240              $prefixes[$prefix['pid']] = $prefix;
4241          }
4242      }
4243  
4244      if(empty($prefixes))
4245      {
4246          return '';
4247      }
4248  
4249      $default_selected = array('all' => '', 'none' => '', 'any' => '');
4250      $selected_pid = (int)$selected_pid;
4251  
4252      if($selected_pid == 0)
4253      {
4254          $default_selected['all'] = ' selected="selected"';
4255      }
4256      else if($selected_pid == -1)
4257      {
4258          $default_selected['none'] = ' selected="selected"';
4259      }
4260      else if($selected_pid == -2)
4261      {
4262          $default_selected['any'] = ' selected="selected"';
4263      }
4264  
4265      $prefixselect_prefix = '';
4266      foreach($prefixes as $prefix)
4267      {
4268          $selected = '';
4269          if($prefix['pid'] == $selected_pid)
4270          {
4271              $selected = ' selected="selected"';
4272          }
4273  
4274          $prefix['prefix'] = htmlspecialchars_uni($prefix['prefix']);
4275          eval('$prefixselect_prefix .= "'.$templates->get("forumdisplay_threadlist_prefixes_prefix").'";');
4276      }
4277  
4278      eval('$prefixselect = "'.$templates->get("forumdisplay_threadlist_prefixes").'";');
4279      return $prefixselect;
4280  }
4281  
4282  /**
4283   * Gzip encodes text to a specified level
4284   *
4285   * @param string $contents The string to encode
4286   * @param int $level The level (1-9) to encode at
4287   * @return string The encoded string
4288   */
4289  function gzip_encode($contents, $level=1)
4290  {
4291      if(function_exists("gzcompress") && function_exists("crc32") && !headers_sent() && !(ini_get('output_buffering') && my_strpos(' '.ini_get('output_handler'), 'ob_gzhandler')))
4292      {
4293          $httpaccept_encoding = '';
4294  
4295          if(isset($_SERVER['HTTP_ACCEPT_ENCODING']))
4296          {
4297              $httpaccept_encoding = $_SERVER['HTTP_ACCEPT_ENCODING'];
4298          }
4299  
4300          if(my_strpos(" ".$httpaccept_encoding, "x-gzip"))
4301          {
4302              $encoding = "x-gzip";
4303          }
4304  
4305          if(my_strpos(" ".$httpaccept_encoding, "gzip"))
4306          {
4307              $encoding = "gzip";
4308          }
4309  
4310          if(isset($encoding))
4311          {
4312              header("Content-Encoding: $encoding");
4313  
4314              if(function_exists("gzencode"))
4315              {
4316                  $contents = gzencode($contents, $level);
4317              }
4318              else
4319              {
4320                  $size = strlen($contents);
4321                  $crc = crc32($contents);
4322                  $gzdata = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff";
4323                  $gzdata .= my_substr(gzcompress($contents, $level), 2, -4);
4324                  $gzdata .= pack("V", $crc);
4325                  $gzdata .= pack("V", $size);
4326                  $contents = $gzdata;
4327              }
4328          }
4329      }
4330  
4331      return $contents;
4332  }
4333  
4334  /**
4335   * Log the actions of a moderator.
4336   *
4337   * @param array $data The data of the moderator's action.
4338   * @param string $action The message to enter for the action the moderator performed.
4339   */
4340  function log_moderator_action($data, $action="")
4341  {
4342      global $mybb, $db, $session;
4343  
4344      $fid = 0;
4345      if(isset($data['fid']))
4346      {
4347          $fid = (int)$data['fid'];
4348          unset($data['fid']);
4349      }
4350  
4351      $tid = 0;
4352      if(isset($data['tid']))
4353      {
4354          $tid = (int)$data['tid'];
4355          unset($data['tid']);
4356      }
4357  
4358      $pid = 0;
4359      if(isset($data['pid']))
4360      {
4361          $pid = (int)$data['pid'];
4362          unset($data['pid']);
4363      }
4364  
4365      $tids = array();
4366      if(isset($data['tids']))
4367      {
4368          $tids = (array)$data['tids'];
4369          unset($data['tids']);
4370      }
4371  
4372      // Any remaining extra data - we my_serialize and insert in to its own column
4373      if(is_array($data))
4374      {
4375          $data = my_serialize($data);
4376      }
4377  
4378      $sql_array = array(
4379          "uid" => (int)$mybb->user['uid'],
4380          "dateline" => TIME_NOW,
4381          "fid" => (int)$fid,
4382          "tid" => $tid,
4383          "pid" => $pid,
4384          "action" => $db->escape_string($action),
4385          "data" => $db->escape_string($data),
4386          "ipaddress" => $db->escape_binary($session->packedip)
4387      );
4388  
4389      if($tids)
4390      {
4391          $multiple_sql_array = array();
4392  
4393          foreach($tids as $tid)
4394          {
4395              $sql_array['tid'] = (int)$tid;
4396              $multiple_sql_array[] = $sql_array;
4397          }
4398  
4399          $db->insert_query_multiple("moderatorlog", $multiple_sql_array);
4400      }
4401      else
4402      {
4403          $db->insert_query("moderatorlog", $sql_array);
4404      }
4405  }
4406  
4407  /**
4408   * Get the formatted reputation for a user.
4409   *
4410   * @param int $reputation The reputation value
4411   * @param int $uid The user ID (if not specified, the generated reputation will not be a link)
4412   * @return string The formatted repuation
4413   */
4414  function get_reputation($reputation, $uid=0)
4415  {
4416      global $theme, $templates;
4417  
4418      $display_reputation = $reputation_class = '';
4419      if($reputation < 0)
4420      {
4421          $reputation_class = "reputation_negative";
4422      }
4423      elseif($reputation > 0)
4424      {
4425          $reputation_class = "reputation_positive";
4426      }
4427      else
4428      {
4429          $reputation_class = "reputation_neutral";
4430      }
4431  
4432      $reputation = my_number_format($reputation);
4433  
4434      if($uid != 0)
4435      {
4436          eval("\$display_reputation = \"".$templates->get("postbit_reputation_formatted_link")."\";");
4437      }
4438      else
4439      {
4440          eval("\$display_reputation = \"".$templates->get("postbit_reputation_formatted")."\";");
4441      }
4442  
4443      return $display_reputation;
4444  }
4445  
4446  /**
4447   * Fetch a color coded version of a warning level (based on it's percentage)
4448   *
4449   * @param int $level The warning level (percentage of 100)
4450   * @return string Formatted warning level
4451   */
4452  function get_colored_warning_level($level)
4453  {
4454      global $templates;
4455  
4456      $warning_class = '';
4457      if($level >= 80)
4458      {
4459          $warning_class = "high_warning";
4460      }
4461      else if($level >= 50)
4462      {
4463          $warning_class = "moderate_warning";
4464      }
4465      else if($level >= 25)
4466      {
4467          $warning_class = "low_warning";
4468      }
4469      else
4470      {
4471          $warning_class = "normal_warning";
4472      }
4473  
4474      eval("\$level = \"".$templates->get("postbit_warninglevel_formatted")."\";");
4475      return $level;
4476  }
4477  
4478  /**
4479   * Fetch the IP address of the current user.
4480   *
4481   * @return string The IP address.
4482   */
4483  function get_ip()
4484  {
4485      global $mybb, $plugins;
4486  
4487      $ip = strtolower($_SERVER['REMOTE_ADDR']);
4488  
4489      if($mybb->settings['ip_forwarded_check'])
4490      {
4491          $addresses = array();
4492  
4493          if(isset($_SERVER['HTTP_X_FORWARDED_FOR']))
4494          {
4495              $addresses = explode(',', strtolower($_SERVER['HTTP_X_FORWARDED_FOR']));
4496          }
4497          elseif(isset($_SERVER['HTTP_X_REAL_IP']))
4498          {
4499              $addresses = explode(',', strtolower($_SERVER['HTTP_X_REAL_IP']));
4500          }
4501  
4502          if(is_array($addresses))
4503          {
4504              foreach($addresses as $val)
4505              {
4506                  $val = trim($val);
4507                  // Validate IP address and exclude private addresses
4508                  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))
4509                  {
4510                      $ip = $val;
4511                      break;
4512                  }
4513              }
4514          }
4515      }
4516  
4517      if(!$ip)
4518      {
4519          if(isset($_SERVER['HTTP_CLIENT_IP']))
4520          {
4521              $ip = strtolower($_SERVER['HTTP_CLIENT_IP']);
4522          }
4523      }
4524  
4525      if($plugins)
4526      {
4527          $ip_array = array("ip" => &$ip); // Used for backwards compatibility on this hook with the updated run_hooks() function.
4528          $plugins->run_hooks("get_ip", $ip_array);
4529      }
4530  
4531      return $ip;
4532  }
4533  
4534  /**
4535   * Fetch the friendly size (GB, MB, KB, B) for a specified file size.
4536   *
4537   * @param int $size The size in bytes
4538   * @return string The friendly file size
4539   */
4540  function get_friendly_size($size)
4541  {
4542      global $lang;
4543  
4544      if(!is_numeric($size))
4545      {
4546          return $lang->na;
4547      }
4548  
4549      // Yottabyte (1024 Zettabytes)
4550      if($size >= 1208925819614629174706176)
4551      {
4552          $size = my_number_format(round(($size / 1208925819614629174706176), 2))." ".$lang->size_yb;
4553      }
4554      // Zetabyte (1024 Exabytes)
4555      elseif($size >= 1180591620717411303424)
4556      {
4557          $size = my_number_format(round(($size / 1180591620717411303424), 2))." ".$lang->size_zb;
4558      }
4559      // Exabyte (1024 Petabytes)
4560      elseif($size >= 1152921504606846976)
4561      {
4562          $size = my_number_format(round(($size / 1152921504606846976), 2))." ".$lang->size_eb;
4563      }
4564      // Petabyte (1024 Terabytes)
4565      elseif($size >= 1125899906842624)
4566      {
4567          $size = my_number_format(round(($size / 1125899906842624), 2))." ".$lang->size_pb;
4568      }
4569      // Terabyte (1024 Gigabytes)
4570      elseif($size >= 1099511627776)
4571      {
4572          $size = my_number_format(round(($size / 1099511627776), 2))." ".$lang->size_tb;
4573      }
4574      // Gigabyte (1024 Megabytes)
4575      elseif($size >= 1073741824)
4576      {
4577          $size = my_number_format(round(($size / 1073741824), 2))." ".$lang->size_gb;
4578      }
4579      // Megabyte (1024 Kilobytes)
4580      elseif($size >= 1048576)
4581      {
4582          $size = my_number_format(round(($size / 1048576), 2))." ".$lang->size_mb;
4583      }
4584      // Kilobyte (1024 bytes)
4585      elseif($size >= 1024)
4586      {
4587          $size = my_number_format(round(($size / 1024), 2))." ".$lang->size_kb;
4588      }
4589      elseif($size == 0)
4590      {
4591          $size = "0 ".$lang->size_bytes;
4592      }
4593      else
4594      {
4595          $size = my_number_format($size)." ".$lang->size_bytes;
4596      }
4597  
4598      return $size;
4599  }
4600  
4601  /**
4602   * Format a decimal number in to microseconds, milliseconds, or seconds.
4603   *
4604   * @param int $time The time in microseconds
4605   * @return string The friendly time duration
4606   */
4607  function format_time_duration($time)
4608  {
4609      global $lang;
4610  
4611      if(!is_numeric($time))
4612      {
4613          return $lang->na;
4614      }
4615  
4616      if(round(1000000 * $time, 2) < 1000)
4617      {
4618          $time = number_format(round(1000000 * $time, 2))." μs";
4619      }
4620      elseif(round(1000000 * $time, 2) >= 1000 && round(1000000 * $time, 2) < 1000000)
4621      {
4622          $time = number_format(round((1000 * $time), 2))." ms";
4623      }
4624      else
4625      {
4626          $time = round($time, 3)." seconds";
4627      }
4628  
4629      return $time;
4630  }
4631  
4632  /**
4633   * Get the attachment icon for a specific file extension
4634   *
4635   * @param string $ext The file extension
4636   * @return string The attachment icon
4637   */
4638  function get_attachment_icon($ext)
4639  {
4640      global $cache, $attachtypes, $theme, $templates, $lang, $mybb;
4641  
4642      if(!$attachtypes)
4643      {
4644          $attachtypes = $cache->read("attachtypes");
4645      }
4646  
4647      $ext = my_strtolower($ext);
4648  
4649      if($attachtypes[$ext]['icon'])
4650      {
4651          static $attach_icons_schemes = array();
4652          if(!isset($attach_icons_schemes[$ext]))
4653          {
4654              $attach_icons_schemes[$ext] = parse_url($attachtypes[$ext]['icon']);
4655              if(!empty($attach_icons_schemes[$ext]['scheme']))
4656              {
4657                  $attach_icons_schemes[$ext] = $attachtypes[$ext]['icon'];
4658              }
4659              elseif(defined("IN_ADMINCP"))
4660              {
4661                  $attach_icons_schemes[$ext] = str_replace("{theme}", "", $attachtypes[$ext]['icon']);
4662                  if(my_substr($attach_icons_schemes[$ext], 0, 1) != "/")
4663                  {
4664                      $attach_icons_schemes[$ext] = "../".$attach_icons_schemes[$ext];
4665                  }
4666              }
4667              elseif(defined("IN_PORTAL"))
4668              {
4669                  global $change_dir;
4670                  $attach_icons_schemes[$ext] = $change_dir."/".str_replace("{theme}", $theme['imgdir'], $attachtypes[$ext]['icon']);
4671                  $attach_icons_schemes[$ext] = $mybb->get_asset_url($attach_icons_schemes[$ext]);
4672              }
4673              else
4674              {
4675                  $attach_icons_schemes[$ext] = str_replace("{theme}", $theme['imgdir'], $attachtypes[$ext]['icon']);
4676                  $attach_icons_schemes[$ext] = $mybb->get_asset_url($attach_icons_schemes[$ext]);
4677              }
4678          }
4679  
4680          $icon = $attach_icons_schemes[$ext];
4681  
4682          $name = htmlspecialchars_uni($attachtypes[$ext]['name']);
4683      }
4684      else
4685      {
4686          if(defined("IN_ADMINCP"))
4687          {
4688              $theme['imgdir'] = "../images";
4689          }
4690          else if(defined("IN_PORTAL"))
4691          {
4692              global $change_dir;
4693              $theme['imgdir'] = "{$change_dir}/images";
4694          }
4695  
4696          $icon = "{$theme['imgdir']}/attachtypes/unknown.png";
4697  
4698          $name = $lang->unknown;
4699      }
4700  
4701      $icon = htmlspecialchars_uni($icon);
4702      eval("\$attachment_icon = \"".$templates->get("attachment_icon")."\";");
4703      return $attachment_icon;
4704  }
4705  
4706  /**
4707   * Get a list of the unviewable forums for the current user
4708   *
4709   * @param boolean $only_readable_threads Set to true to only fetch those forums for which users can actually read a thread in.
4710   * @return string Comma separated values list of the forum IDs which the user cannot view
4711   */
4712  function get_unviewable_forums($only_readable_threads=false)
4713  {
4714      global $forum_cache, $permissioncache, $mybb;
4715  
4716      if(!is_array($forum_cache))
4717      {
4718          cache_forums();
4719      }
4720  
4721      if(!is_array($permissioncache))
4722      {
4723          $permissioncache = forum_permissions();
4724      }
4725  
4726      $unviewable = array();
4727      foreach($forum_cache as $fid => $forum)
4728      {
4729          if($permissioncache[$forum['fid']])
4730          {
4731              $perms = $permissioncache[$forum['fid']];
4732          }
4733          else
4734          {
4735              $perms = $mybb->usergroup;
4736          }
4737  
4738          $pwverified = 1;
4739  
4740  
4741          if(!forum_password_validated($forum, true))
4742          {
4743              $pwverified = 0;
4744          }
4745          else
4746          {
4747              // Check parents for passwords
4748              $parents = explode(",", $forum['parentlist']);
4749              foreach($parents as $parent)
4750              {
4751                  if(!forum_password_validated($forum_cache[$parent], true))
4752                  {
4753                      $pwverified = 0;
4754                      break;
4755                  }
4756              }
4757          }
4758  
4759          if($perms['canview'] == 0 || $pwverified == 0 || ($only_readable_threads == true && $perms['canviewthreads'] == 0))
4760          {
4761              $unviewable[] = $forum['fid'];
4762          }
4763      }
4764  
4765      $unviewableforums = implode(',', $unviewable);
4766  
4767      return $unviewableforums;
4768  }
4769  
4770  /**
4771   * Fixes mktime for dates earlier than 1970
4772   *
4773   * @param string $format The date format to use
4774   * @param int $year The year of the date
4775   * @return string The correct date format
4776   */
4777  function fix_mktime($format, $year)
4778  {
4779      // Our little work around for the date < 1970 thing.
4780      // -2 idea provided by Matt Light (http://www.mephex.com)
4781      $format = str_replace("Y", $year, $format);
4782      $format = str_replace("y", my_substr($year, -2), $format);
4783  
4784      return $format;
4785  }
4786  
4787  /**
4788   * Build the breadcrumb navigation trail from the specified items
4789   *
4790   * @return string The formatted breadcrumb navigation trail
4791   */
4792  function build_breadcrumb()
4793  {
4794      global $nav, $navbits, $templates, $theme, $lang, $mybb;
4795  
4796      eval("\$navsep = \"".$templates->get("nav_sep")."\";");
4797  
4798      $i = 0;
4799      $activesep = '';
4800  
4801      if(is_array($navbits))
4802      {
4803          reset($navbits);
4804          foreach($navbits as $key => $navbit)
4805          {
4806              if(isset($navbits[$key+1]))
4807              {
4808                  if(isset($navbits[$key+2]))
4809                  {
4810                      $sep = $navsep;
4811                  }
4812                  else
4813                  {
4814                      $sep = "";
4815                  }
4816  
4817                  $multipage = null;
4818                  $multipage_dropdown = null;
4819                  if(!empty($navbit['multipage']))
4820                  {
4821                      if(!$mybb->settings['threadsperpage'] || (int)$mybb->settings['threadsperpage'] < 1)
4822                      {
4823                          $mybb->settings['threadsperpage'] = 20;
4824                      }
4825  
4826                      $multipage = multipage($navbit['multipage']['num_threads'], $mybb->settings['threadsperpage'], $navbit['multipage']['current_page'], $navbit['multipage']['url'], true);
4827                      if($multipage)
4828                      {
4829                          ++$i;
4830                          eval("\$multipage_dropdown = \"".$templates->get("nav_dropdown")."\";");
4831                          $sep = $multipage_dropdown.$sep;
4832                      }
4833                  }
4834  
4835                  // Replace page 1 URLs
4836                  $navbit['url'] = str_replace("-page-1.html", ".html", $navbit['url']);
4837                  $navbit['url'] = preg_replace("/&amp;page=1$/", "", $navbit['url']);
4838  
4839                  eval("\$nav .= \"".$templates->get("nav_bit")."\";");
4840              }
4841          }
4842          $navsize = count($navbits);
4843          $navbit = $navbits[$navsize-1];
4844      }
4845  
4846      if($nav)
4847      {
4848          eval("\$activesep = \"".$templates->get("nav_sep_active")."\";");
4849      }
4850  
4851      eval("\$activebit = \"".$templates->get("nav_bit_active")."\";");
4852      eval("\$donenav = \"".$templates->get("nav")."\";");
4853  
4854      return $donenav;
4855  }
4856  
4857  /**
4858   * Add a breadcrumb menu item to the list.
4859   *
4860   * @param string $name The name of the item to add
4861   * @param string $url The URL of the item to add
4862   */
4863  function add_breadcrumb($name, $url="")
4864  {
4865      global $navbits;
4866  
4867      $navsize = count($navbits);
4868      $navbits[$navsize]['name'] = $name;
4869      $navbits[$navsize]['url'] = $url;
4870  }
4871  
4872  /**
4873   * Build the forum breadcrumb nagiation (the navigation to a specific forum including all parent forums)
4874   *
4875   * @param int $fid The forum ID to build the navigation for
4876   * @param array $multipage The multipage drop down array of information
4877   * @return int Returns 1 in every case. Kept for compatibility
4878   */
4879  function build_forum_breadcrumb($fid, $multipage=array())
4880  {
4881      global $pforumcache, $currentitem, $forum_cache, $navbits, $lang, $base_url, $archiveurl;
4882  
4883      if(!$pforumcache)
4884      {
4885          if(!is_array($forum_cache))
4886          {
4887              cache_forums();
4888          }
4889  
4890          foreach($forum_cache as $key => $val)
4891          {
4892              $pforumcache[$val['fid']][$val['pid']] = $val;
4893          }
4894      }
4895  
4896      if(is_array($pforumcache[$fid]))
4897      {
4898          foreach($pforumcache[$fid] as $key => $forumnav)
4899          {
4900              if($fid == $forumnav['fid'])
4901              {
4902                  if(!empty($pforumcache[$forumnav['pid']]))
4903                  {
4904                      build_forum_breadcrumb($forumnav['pid']);
4905                  }
4906  
4907                  $navsize = count($navbits);
4908                  // Convert & to &amp;
4909                  $navbits[$navsize]['name'] = preg_replace("#&(?!\#[0-9]+;)#si", "&amp;", $forumnav['name']);
4910  
4911                  if(defined("IN_ARCHIVE"))
4912                  {
4913                      // Set up link to forum in breadcrumb.
4914                      if($pforumcache[$fid][$forumnav['pid']]['type'] == 'f' || $pforumcache[$fid][$forumnav['pid']]['type'] == 'c')
4915                      {
4916                          $navbits[$navsize]['url'] = "{$base_url}forum-".$forumnav['fid'].".html";
4917                      }
4918                      else
4919                      {
4920                          $navbits[$navsize]['url'] = $archiveurl."/index.php";
4921                      }
4922                  }
4923                  elseif(!empty($multipage))
4924                  {
4925                      $navbits[$navsize]['url'] = get_forum_link($forumnav['fid'], $multipage['current_page']);
4926  
4927                      $navbits[$navsize]['multipage'] = $multipage;
4928                      $navbits[$navsize]['multipage']['url'] = str_replace('{fid}', $forumnav['fid'], FORUM_URL_PAGED);
4929                  }
4930                  else
4931                  {
4932                      $navbits[$navsize]['url'] = get_forum_link($forumnav['fid']);
4933                  }
4934              }
4935          }
4936      }
4937  
4938      return 1;
4939  }
4940  
4941  /**
4942   * Resets the breadcrumb navigation to the first item, and clears the rest
4943   */
4944  function reset_breadcrumb()
4945  {
4946      global $navbits;
4947  
4948      $newnav[0]['name'] = $navbits[0]['name'];
4949      $newnav[0]['url'] = $navbits[0]['url'];
4950      if(!empty($navbits[0]['options']))
4951      {
4952          $newnav[0]['options'] = $navbits[0]['options'];
4953      }
4954  
4955      unset($GLOBALS['navbits']);
4956      $GLOBALS['navbits'] = $newnav;
4957  }
4958  
4959  /**
4960   * Builds a URL to an archive mode page
4961   *
4962   * @param string $type The type of page (thread|announcement|forum)
4963   * @param int $id The ID of the item
4964   * @return string The URL
4965   */
4966  function build_archive_link($type="", $id=0)
4967  {
4968      global $mybb;
4969  
4970      // If the server OS is not Windows and not Apache or the PHP is running as a CGI or we have defined ARCHIVE_QUERY_STRINGS, use query strings - DIRECTORY_SEPARATOR checks if running windows
4971      //if((DIRECTORY_SEPARATOR == '\\' && is_numeric(stripos($_SERVER['SERVER_SOFTWARE'], "apache")) == false) || is_numeric(stripos(SAPI_NAME, "cgi")) !== false || defined("ARCHIVE_QUERY_STRINGS"))
4972      if($mybb->settings['seourls_archive'] == 1)
4973      {
4974          $base_url = $mybb->settings['bburl']."/archive/index.php/";
4975      }
4976      else
4977      {
4978          $base_url = $mybb->settings['bburl']."/archive/index.php?";
4979      }
4980  
4981      switch($type)
4982      {
4983          case "thread":
4984              $url = "{$base_url}thread-{$id}.html";
4985              break;
4986          case "announcement":
4987              $url = "{$base_url}announcement-{$id}.html";
4988              break;
4989          case "forum":
4990              $url = "{$base_url}forum-{$id}.html";
4991              break;
4992          default:
4993              $url = $mybb->settings['bburl']."/archive/index.php";
4994      }
4995  
4996      return $url;
4997  }
4998  
4999  /**
5000   * Prints a debug information page
5001   */
5002  function debug_page()
5003  {
5004      global $db, $debug, $templates, $templatelist, $mybb, $maintimer, $globaltime, $ptimer, $parsetime, $lang, $cache;
5005  
5006      $totaltime = format_time_duration($maintimer->totaltime);
5007      $phptime = $maintimer->totaltime - $db->query_time;
5008      $query_time = $db->query_time;
5009      $globaltime = format_time_duration($globaltime);
5010  
5011      $percentphp = number_format((($phptime/$maintimer->totaltime)*100), 2);
5012      $percentsql = number_format((($query_time/$maintimer->totaltime)*100), 2);
5013  
5014      $phptime = format_time_duration($maintimer->totaltime - $db->query_time);
5015      $query_time = format_time_duration($db->query_time);
5016  
5017      $call_time = format_time_duration($cache->call_time);
5018  
5019      $phpversion = PHP_VERSION;
5020  
5021      $serverload = get_server_load();
5022  
5023      if($mybb->settings['gzipoutput'] != 0)
5024      {
5025          $gzipen = "Enabled";
5026      }
5027      else
5028      {
5029          $gzipen = "Disabled";
5030      }
5031  
5032      echo "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
5033      echo "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">";
5034      echo "<head>";
5035      echo "<meta name=\"robots\" content=\"noindex\" />";
5036      echo "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />";
5037      echo "<title>MyBB Debug Information</title>";
5038      echo "</head>";
5039      echo "<body>";
5040      echo "<h1>MyBB Debug Information</h1>\n";
5041      echo "<h2>Page Generation</h2>\n";
5042      echo "<table bgcolor=\"#666666\" width=\"95%\" cellpadding=\"4\" cellspacing=\"1\" align=\"center\">\n";
5043      echo "<tr>\n";
5044      echo "<td bgcolor=\"#cccccc\" colspan=\"4\"><b><span style=\"size:2;\">Page Generation Statistics</span></b></td>\n";
5045      echo "</tr>\n";
5046      echo "<tr>\n";
5047      echo "<td bgcolor=\"#efefef\" width=\"25%\"><b><span style=\"font-family: tahoma; font-size: 12px;\">Page Generation Time:</span></b></td>\n";
5048      echo "<td bgcolor=\"#fefefe\" width=\"25%\"><span style=\"font-family: tahoma; font-size: 12px;\">$totaltime</span></td>\n";
5049      echo "<td bgcolor=\"#efefef\" width=\"25%\"><b><span style=\"font-family: tahoma; font-size: 12px;\">No. DB Queries:</span></b></td>\n";
5050      echo "<td bgcolor=\"#fefefe\" width=\"25%\"><span style=\"font-family: tahoma; font-size: 12px;\">$db->query_count</span></td>\n";
5051      echo "</tr>\n";
5052      echo "<tr>\n";
5053      echo "<td bgcolor=\"#efefef\" width=\"25%\"><b><span style=\"font-family: tahoma; font-size: 12px;\">PHP Processing Time:</span></b></td>\n";
5054      echo "<td bgcolor=\"#fefefe\" width=\"25%\"><span style=\"font-family: tahoma; font-size: 12px;\">$phptime ($percentphp%)</span></td>\n";
5055      echo "<td bgcolor=\"#efefef\" width=\"25%\"><b><span style=\"font-family: tahoma; font-size: 12px;\">DB Processing Time:</span></b></td>\n";
5056      echo "<td bgcolor=\"#fefefe\" width=\"25%\"><span style=\"font-family: tahoma; font-size: 12px;\">$query_time ($percentsql%)</span></td>\n";
5057      echo "</tr>\n";
5058      echo "<tr>\n";
5059      echo "<td bgcolor=\"#efefef\" width=\"25%\"><b><span style=\"font-family: tahoma; font-size: 12px;\">Extensions Used:</span></b></td>\n";
5060      echo "<td bgcolor=\"#fefefe\" width=\"25%\"><span style=\"font-family: tahoma; font-size: 12px;\">{$mybb->config['database']['type']}, xml</span></td>\n";
5061      echo "<td bgcolor=\"#efefef\" width=\"25%\"><b><span style=\"font-family: tahoma; font-size: 12px;\">Global.php Processing Time:</span></b></td>\n";
5062      echo "<td bgcolor=\"#fefefe\" width=\"25%\"><span style=\"font-family: tahoma; font-size: 12px;\">$globaltime</span></td>\n";
5063      echo "</tr>\n";
5064      echo "<tr>\n";
5065      echo "<td bgcolor=\"#efefef\" width=\"25%\"><b><span style=\"font-family: tahoma; font-size: 12px;\">PHP Version:</span></b></td>\n";
5066      echo "<td bgcolor=\"#fefefe\" width=\"25%\"><span style=\"font-family: tahoma; font-size: 12px;\">$phpversion</span></td>\n";
5067      echo "<td bgcolor=\"#efefef\" width=\"25%\"><b><span style=\"font-family: tahoma; font-size: 12px;\">Server Load:</span></b></td>\n";
5068      echo "<td bgcolor=\"#fefefe\" width=\"25%\"><span style=\"font-family: tahoma; font-size: 12px;\">$serverload</span></td>\n";
5069      echo "</tr>\n";
5070      echo "<tr>\n";
5071      echo "<td bgcolor=\"#efefef\" width=\"25%\"><b><span style=\"font-family: tahoma; font-size: 12px;\">GZip Encoding Status:</span></b></td>\n";
5072      echo "<td bgcolor=\"#fefefe\" width=\"25%\"><span style=\"font-family: tahoma; font-size: 12px;\">$gzipen</span></td>\n";
5073      echo "<td bgcolor=\"#efefef\" width=\"25%\"><b><span style=\"font-family: tahoma; font-size: 12px;\">No. Templates Used:</span></b></td>\n";
5074      echo "<td bgcolor=\"#fefefe\" width=\"25%\"><span style=\"font-family: tahoma; font-size: 12px;\">".count($templates->cache)." (".(int)count(explode(",", $templatelist))." Cached / ".(int)count($templates->uncached_templates)." Manually Loaded)</span></td>\n";
5075      echo "</tr>\n";
5076  
5077      $memory_usage = get_memory_usage();
5078      if(!$memory_usage)
5079      {
5080          $memory_usage = $lang->unknown;
5081      }
5082      else
5083      {
5084          $memory_usage = get_friendly_size($memory_usage)." ({$memory_usage} bytes)";
5085      }
5086      $memory_limit = @ini_get("memory_limit");
5087      echo "<tr>\n";
5088      echo "<td bgcolor=\"#EFEFEF\" width=\"25%\"><b><span style=\"font-family: tahoma; font-size: 12px;\">Memory Usage:</span></b></td>\n";
5089      echo "<td bgcolor=\"#FEFEFE\" width=\"25%\"><span style=\"font-family: tahoma; font-size: 12px;\">{$memory_usage}</span></td>\n";
5090      echo "<td bgcolor=\"#EFEFEF\" width=\"25%\"><b><span style=\"font-family: tahoma; font-size: 12px;\">Memory Limit:</span></b></td>\n";
5091      echo "<td bgcolor=\"#FEFEFE\" width=\"25%\"><span style=\"font-family: tahoma; font-size: 12px;\">{$memory_limit}</span></td>\n";
5092      echo "</tr>\n";
5093  
5094      echo "</table>\n";
5095  
5096      echo "<h2>Database Connections (".count($db->connections)." Total) </h2>\n";
5097      echo "<table style=\"background-color: #666;\" width=\"95%\" cellpadding=\"4\" cellspacing=\"1\" align=\"center\">\n";
5098      echo "<tr>\n";
5099      echo "<td style=\"background: #fff;\">".implode("<br />", $db->connections)."</td>\n";
5100      echo "</tr>\n";
5101      echo "</table>\n";
5102      echo "<br />\n";
5103  
5104      echo "<h2>Database Queries (".$db->query_count." Total) </h2>\n";
5105      echo $db->explain;
5106  
5107      if($cache->call_count > 0)
5108      {
5109          echo "<h2>Cache Calls (".$cache->call_count." Total, ".$call_time.") </h2>\n";
5110          echo $cache->cache_debug;
5111      }
5112  
5113      echo "<h2>Template Statistics</h2>\n";
5114  
5115      if(count($templates->cache) > 0)
5116      {
5117          echo "<table style=\"background-color: #666;\" width=\"95%\" cellpadding=\"4\" cellspacing=\"1\" align=\"center\">\n";
5118          echo "<tr>\n";
5119          echo "<td style=\"background-color: #ccc;\"><strong>Templates Used (Loaded for this Page) - ".count($templates->cache)." Total</strong></td>\n";
5120          echo "</tr>\n";
5121          echo "<tr>\n";
5122          echo "<td style=\"background: #fff;\">".implode(", ", array_keys($templates->cache))."</td>\n";
5123          echo "</tr>\n";
5124          echo "</table>\n";
5125          echo "<br />\n";
5126      }
5127  
5128      if(count($templates->uncached_templates) > 0)
5129      {
5130          echo "<table style=\"background-color: #666;\" width=\"95%\" cellpadding=\"4\" cellspacing=\"1\" align=\"center\">\n";
5131          echo "<tr>\n";
5132          echo "<td style=\"background-color: #ccc;\"><strong>Templates Requiring Additional Calls (Not Cached at Startup) - ".count($templates->uncached_templates)." Total</strong></td>\n";
5133          echo "</tr>\n";
5134          echo "<tr>\n";
5135          echo "<td style=\"background: #fff;\">".implode(", ", $templates->uncached_templates)."</td>\n";
5136          echo "</tr>\n";
5137          echo "</table>\n";
5138          echo "<br />\n";
5139      }
5140      echo "</body>";
5141      echo "</html>";
5142      exit;
5143  }
5144  
5145  /**
5146   * Outputs the correct page headers.
5147   */
5148  function send_page_headers()
5149  {
5150      global $mybb;
5151  
5152      if($mybb->settings['nocacheheaders'] == 1)
5153      {
5154          header("Cache-Control: no-cache, private");
5155      }
5156  }
5157  
5158  /**
5159   * Mark specific reported posts of a certain type as dealt with
5160   *
5161   * @param array|int $id An array or int of the ID numbers you're marking as dealt with
5162   * @param string $type The type of item the above IDs are for - post, posts, thread, threads, forum, all
5163   */
5164  function mark_reports($id, $type="post")
5165  {
5166      global $db, $cache, $plugins;
5167  
5168      switch($type)
5169      {
5170          case "posts":
5171              if(is_array($id))
5172              {
5173                  $rids = implode("','", $id);
5174                  $rids = "'0','$rids'";
5175                  $db->update_query("reportedcontent", array('reportstatus' => 1), "id IN($rids) AND reportstatus='0' AND (type = 'post' OR type = '')");
5176              }
5177              break;
5178          case "post":
5179              $db->update_query("reportedcontent", array('reportstatus' => 1), "id='$id' AND reportstatus='0' AND (type = 'post' OR type = '')");
5180              break;
5181          case "threads":
5182              if(is_array($id))
5183              {
5184                  $rids = implode("','", $id);
5185                  $rids = "'0','$rids'";
5186                  $db->update_query("reportedcontent", array('reportstatus' => 1), "id2 IN($rids) AND reportstatus='0' AND (type = 'post' OR type = '')");
5187              }
5188              break;
5189          case "thread":
5190              $db->update_query("reportedcontent", array('reportstatus' => 1), "id2='$id' AND reportstatus='0' AND (type = 'post' OR type = '')");
5191              break;
5192          case "forum":
5193              $db->update_query("reportedcontent", array('reportstatus' => 1), "id3='$id' AND reportstatus='0' AND (type = 'post' OR type = '')");
5194              break;
5195          case "all":
5196              $db->update_query("reportedcontent", array('reportstatus' => 1), "reportstatus='0' AND (type = 'post' OR type = '')");
5197              break;
5198      }
5199  
5200      $arguments = array('id' => $id, 'type' => $type);
5201      $plugins->run_hooks("mark_reports", $arguments);
5202      $cache->update_reportedcontent();
5203  }
5204  
5205  /**
5206   * Fetch a friendly x days, y months etc date stamp from a timestamp
5207   *
5208   * @param int $stamp The timestamp
5209   * @param array $options Array of options
5210   * @return string The friendly formatted timestamp
5211   */
5212  function nice_time($stamp, $options=array())
5213  {
5214      global $lang;
5215  
5216      $ysecs = 365*24*60*60;
5217      $mosecs = 31*24*60*60;
5218      $wsecs = 7*24*60*60;
5219      $dsecs = 24*60*60;
5220      $hsecs = 60*60;
5221      $msecs = 60;
5222  
5223      if(isset($options['short']))
5224      {
5225          $lang_year = $lang->year_short;
5226          $lang_years = $lang->years_short;
5227          $lang_month = $lang->month_short;
5228          $lang_months = $lang->months_short;
5229          $lang_week = $lang->week_short;
5230          $lang_weeks = $lang->weeks_short;
5231          $lang_day = $lang->day_short;
5232          $lang_days = $lang->days_short;
5233          $lang_hour = $lang->hour_short;
5234          $lang_hours = $lang->hours_short;
5235          $lang_minute = $lang->minute_short;
5236          $lang_minutes = $lang->minutes_short;
5237          $lang_second = $lang->second_short;
5238          $lang_seconds = $lang->seconds_short;
5239      }
5240      else
5241      {
5242          $lang_year = " ".$lang->year;
5243          $lang_years = " ".$lang->years;
5244          $lang_month = " ".$lang->month;
5245          $lang_months = " ".$lang->months;
5246          $lang_week = " ".$lang->week;
5247          $lang_weeks = " ".$lang->weeks;
5248          $lang_day = " ".$lang->day;
5249          $lang_days = " ".$lang->days;
5250          $lang_hour = " ".$lang->hour;
5251          $lang_hours = " ".$lang->hours;
5252          $lang_minute = " ".$lang->minute;
5253          $lang_minutes = " ".$lang->minutes;
5254          $lang_second = " ".$lang->second;
5255          $lang_seconds = " ".$lang->seconds;
5256      }
5257  
5258      $years = floor($stamp/$ysecs);
5259      $stamp %= $ysecs;
5260      $months = floor($stamp/$mosecs);
5261      $stamp %= $mosecs;
5262      $weeks = floor($stamp/$wsecs);
5263      $stamp %= $wsecs;
5264      $days = floor($stamp/$dsecs);
5265      $stamp %= $dsecs;
5266      $hours = floor($stamp/$hsecs);
5267      $stamp %= $hsecs;
5268      $minutes = floor($stamp/$msecs);
5269      $stamp %= $msecs;
5270      $seconds = $stamp;
5271  
5272      // Prevent gross over accuracy ($options parameter will override these)
5273      if($years > 0)
5274      {
5275          $options = array_merge(array(
5276              'days' => false,
5277              'hours' => false,
5278              'minutes' => false,
5279              'seconds' => false
5280          ), $options);
5281      }
5282      elseif($months > 0)
5283      {
5284          $options = array_merge(array(
5285              'hours' => false,
5286              'minutes' => false,
5287              'seconds' => false
5288          ), $options);
5289      }
5290      elseif($weeks > 0)
5291      {
5292          $options = array_merge(array(
5293              'minutes' => false,
5294              'seconds' => false
5295          ), $options);
5296      }
5297      elseif($days > 0)
5298      {
5299          $options = array_merge(array(
5300              'seconds' => false
5301          ), $options);
5302      }
5303  
5304      $nicetime = array();
5305  
5306      if(!isset($options['years']) || $options['years'] !== false)
5307      {
5308          if($years == 1)
5309          {
5310              $nicetime['years'] = "1".$lang_year;
5311          }
5312          else if($years > 1)
5313          {
5314              $nicetime['years'] = $years.$lang_years;
5315          }
5316      }
5317  
5318      if(!isset($options['months']) || $options['months'] !== false)
5319      {
5320          if($months == 1)
5321          {
5322              $nicetime['months'] = "1".$lang_month;
5323          }
5324          else if($months > 1)
5325          {
5326              $nicetime['months'] = $months.$lang_months;
5327          }
5328      }
5329  
5330      if(!isset($options['weeks']) || $options['weeks'] !== false)
5331      {
5332          if($weeks == 1)
5333          {
5334              $nicetime['weeks'] = "1".$lang_week;
5335          }
5336          else if($weeks > 1)
5337          {
5338              $nicetime['weeks'] = $weeks.$lang_weeks;
5339          }
5340      }
5341  
5342      if(!isset($options['days']) || $options['days'] !== false)
5343      {
5344          if($days == 1)
5345          {
5346              $nicetime['days'] = "1".$lang_day;
5347          }
5348          else if($days > 1)
5349          {
5350              $nicetime['days'] = $days.$lang_days;
5351          }
5352      }
5353  
5354      if(!isset($options['hours']) || $options['hours'] !== false)
5355      {
5356          if($hours == 1)
5357          {
5358              $nicetime['hours'] = "1".$lang_hour;
5359          }
5360          else if($hours > 1)
5361          {
5362              $nicetime['hours'] = $hours.$lang_hours;
5363          }
5364      }
5365  
5366      if(!isset($options['minutes']) || $options['minutes'] !== false)
5367      {
5368          if($minutes == 1)
5369          {
5370              $nicetime['minutes'] = "1".$lang_minute;
5371          }
5372          else if($minutes > 1)
5373          {
5374              $nicetime['minutes'] = $minutes.$lang_minutes;
5375          }
5376      }
5377  
5378      if(!isset($options['seconds']) || $options['seconds'] !== false)
5379      {
5380          if($seconds == 1)
5381          {
5382              $nicetime['seconds'] = "1".$lang_second;
5383          }
5384          else if($seconds > 1)
5385          {
5386              $nicetime['seconds'] = $seconds.$lang_seconds;
5387          }
5388      }
5389  
5390      if(!empty($nicetime))
5391      {
5392          return implode(", ", $nicetime);
5393      }
5394  }
5395  
5396  /**
5397   * Select an alternating row colour based on the previous call to this function
5398   *
5399   * @param int $reset 1 to reset the row to trow1.
5400   * @return string trow1 or trow2 depending on the previous call
5401   */
5402  function alt_trow($reset=0)
5403  {
5404      global $alttrow;
5405  
5406      if($alttrow == "trow1" && !$reset)
5407      {
5408          $trow = "trow2";
5409      }
5410      else
5411      {
5412          $trow = "trow1";
5413      }
5414  
5415      $alttrow = $trow;
5416  
5417      return $trow;
5418  }
5419  
5420  /**
5421   * Add a user to a specific additional user group.
5422   *
5423   * @param int $uid The user ID
5424   * @param int $joingroup The user group ID to join
5425   * @return bool
5426   */
5427  function join_usergroup($uid, $joingroup)
5428  {
5429      global $db, $mybb;
5430  
5431      if($uid == $mybb->user['uid'])
5432      {
5433          $user = $mybb->user;
5434      }
5435      else
5436      {
5437          $query = $db->simple_select("users", "additionalgroups, usergroup", "uid='".(int)$uid."'");
5438          $user = $db->fetch_array($query);
5439      }
5440  
5441      // Build the new list of additional groups for this user and make sure they're in the right format
5442      $groups = array_map(
5443          'intval',
5444          explode(',', $user['additionalgroups'])
5445      );
5446  
5447      if(!in_array((int)$joingroup, $groups))
5448      {
5449          $groups[] = (int)$joingroup;
5450          $groups = array_diff($groups, array($user['usergroup']));
5451          $groups = array_unique($groups);
5452  
5453          $groupslist = implode(',', $groups);
5454  
5455          $db->update_query("users", array('additionalgroups' => $groupslist), "uid='".(int)$uid."'");
5456          return true;
5457      }
5458      else
5459      {
5460          return false;
5461      }
5462  }
5463  
5464  /**
5465   * Remove a user from a specific additional user group
5466   *
5467   * @param int $uid The user ID
5468   * @param int $leavegroup The user group ID
5469   */
5470  function leave_usergroup($uid, $leavegroup)
5471  {
5472      global $db, $mybb, $cache;
5473  
5474      $user = get_user($uid);
5475  
5476      if($user['usergroup'] == $leavegroup)
5477      {
5478          return false;
5479      }
5480  
5481      $groups = array_map(
5482          'intval',
5483          explode(',', $user['additionalgroups'])
5484      );
5485      $groups = array_diff($groups, array($leavegroup));
5486      $groups = array_unique($groups);
5487  
5488      $groupslist = implode(',', $groups);
5489  
5490      $dispupdate = "";
5491      if($leavegroup == $user['displaygroup'])
5492      {
5493          $dispupdate = ", displaygroup=usergroup";
5494      }
5495  
5496      $db->write_query("
5497          UPDATE ".TABLE_PREFIX."users
5498          SET additionalgroups='$groupslist' $dispupdate
5499          WHERE uid='".(int)$uid."'
5500      ");
5501  
5502      $cache->update_moderators();
5503  }
5504  
5505  /**
5506   * Get the current location taking in to account different web serves and systems
5507   *
5508   * @param boolean $fields True to return as "hidden" fields
5509   * @param array $ignore Array of fields to ignore for returning "hidden" fields or URL being accessed
5510   * @param boolean $quick True to skip all inputs and return only the file path part of the URL
5511   * @return string|array The current URL being accessed or form data if $fields is true
5512   */
5513  function get_current_location($fields=false, $ignore=array(), $quick=false)
5514  {
5515      global $mybb;
5516  
5517      if(defined("MYBB_LOCATION"))
5518      {
5519          return MYBB_LOCATION;
5520      }
5521  
5522      if(!empty($_SERVER['SCRIPT_NAME']))
5523      {
5524          $location = htmlspecialchars_uni($_SERVER['SCRIPT_NAME']);
5525      }
5526      elseif(!empty($_SERVER['PHP_SELF']))
5527      {
5528          $location = htmlspecialchars_uni($_SERVER['PHP_SELF']);
5529      }
5530      elseif(!empty($_ENV['PHP_SELF']))
5531      {
5532          $location = htmlspecialchars_uni($_ENV['PHP_SELF']);
5533      }
5534      elseif(!empty($_SERVER['PATH_INFO']))
5535      {
5536          $location = htmlspecialchars_uni($_SERVER['PATH_INFO']);
5537      }
5538      else
5539      {
5540          $location = htmlspecialchars_uni($_ENV['PATH_INFO']);
5541      }
5542  
5543      if($quick)
5544      {
5545          return $location;
5546      }
5547  
5548      if(!is_array($ignore))
5549      {
5550          $ignore = array($ignore);
5551      }
5552  
5553      if($fields == true)
5554      {
5555  
5556          $form_html = '';
5557          if(!empty($mybb->input))
5558          {
5559              foreach($mybb->input as $name => $value)
5560              {
5561                  if(in_array($name, $ignore) || is_array($name) || is_array($value))
5562                  {
5563                      continue;
5564                  }
5565  
5566                  $form_html .= "<input type=\"hidden\" name=\"".htmlspecialchars_uni($name)."\" value=\"".htmlspecialchars_uni($value)."\" />\n";
5567              }
5568          }
5569  
5570          return array('location' => $location, 'form_html' => $form_html, 'form_method' => $mybb->request_method);
5571      }
5572      else
5573      {
5574          $parameters = array();
5575  
5576          if(isset($_SERVER['QUERY_STRING']))
5577          {
5578              $current_query_string = $_SERVER['QUERY_STRING'];
5579          }
5580          else if(isset($_ENV['QUERY_STRING']))
5581          {
5582              $current_query_string = $_ENV['QUERY_STRING'];
5583          } else
5584          {
5585              $current_query_string = '';
5586          }
5587  
5588          parse_str($current_query_string, $current_parameters);
5589  
5590          foreach($current_parameters as $name => $value)
5591          {
5592              if(!in_array($name, $ignore))
5593              {
5594                  $parameters[$name] = $value;
5595              }
5596          }
5597  
5598          if($mybb->request_method === 'post')
5599          {
5600              $post_array = array('action', 'fid', 'pid', 'tid', 'uid', 'eid');
5601  
5602              foreach($post_array as $var)
5603              {
5604                  if(isset($_POST[$var]) && !in_array($var, $ignore))
5605                  {
5606                      $parameters[$var] = $_POST[$var];
5607                  }
5608              }
5609          }
5610  
5611          if(!empty($parameters))
5612          {
5613              $location .= '?'.http_build_query($parameters, '', '&amp;');
5614          }
5615  
5616          return $location;
5617      }
5618  }
5619  
5620  /**
5621   * Build a theme selection menu
5622   *
5623   * @param string $name The name of the menu
5624   * @param int $selected The ID of the selected theme
5625   * @param int $tid The ID of the parent theme to select from
5626   * @param string $depth The current selection depth
5627   * @param boolean $usergroup_override Whether or not to override usergroup permissions (true to override)
5628   * @param boolean $footer Whether or not theme select is in the footer (true if it is)
5629   * @param boolean $count_override Whether or not to override output based on theme count (true to override)
5630   * @return string The theme selection list
5631   */
5632  function build_theme_select($name, $selected=-1, $tid=0, $depth="", $usergroup_override=false, $footer=false, $count_override=false)
5633  {
5634      global $db, $themeselect, $tcache, $lang, $mybb, $limit, $templates, $num_themes, $themeselect_option;
5635  
5636      if($tid == 0)
5637      {
5638          $tid = 1;
5639          $num_themes = 0;
5640          $themeselect_option = '';
5641      }
5642  
5643      if(!is_array($tcache))
5644      {
5645          $query = $db->simple_select('themes', 'tid, name, pid, allowedgroups', "pid!='0'");
5646  
5647          while($theme = $db->fetch_array($query))
5648          {
5649              $tcache[$theme['pid']][$theme['tid']] = $theme;
5650          }
5651      }
5652  
5653      if(is_array($tcache[$tid]))
5654      {
5655          foreach($tcache[$tid] as $theme)
5656          {
5657              $sel = "";
5658              // Show theme if allowed, or if override is on
5659              if(is_member($theme['allowedgroups']) || $theme['allowedgroups'] == "all" || $usergroup_override == true)
5660              {
5661                  if($theme['tid'] == $selected)
5662                  {
5663                      $sel = " selected=\"selected\"";
5664                  }
5665  
5666                  if($theme['pid'] != 0)
5667                  {
5668                      $theme['name'] = htmlspecialchars_uni($theme['name']);
5669                      eval("\$themeselect_option .= \"".$templates->get("usercp_themeselector_option")."\";");
5670                      ++$num_themes;
5671                      $depthit = $depth."--";
5672                  }
5673  
5674                  if(array_key_exists($theme['tid'], $tcache))
5675                  {
5676                      build_theme_select($name, $selected, $theme['tid'], $depthit, $usergroup_override, $footer, $count_override);
5677                  }
5678              }
5679          }
5680      }
5681  
5682      if($tid == 1 && ($num_themes > 1 || $count_override == true))
5683      {
5684          if($footer == true)
5685          {
5686              eval("\$themeselect = \"".$templates->get("footer_themeselector")."\";");
5687          }
5688          else
5689          {
5690              eval("\$themeselect = \"".$templates->get("usercp_themeselector")."\";");
5691          }
5692  
5693          return $themeselect;
5694      }
5695      else
5696      {
5697          return false;
5698      }
5699  }
5700  
5701  /**
5702   * Get the theme data of a theme id.
5703   *
5704   * @param int $tid The theme id of the theme.
5705   * @return boolean|array False if no valid theme, Array with the theme data otherwise
5706   */
5707  function get_theme($tid)
5708  {
5709      global $tcache, $db;
5710  
5711      if(!is_array($tcache))
5712      {
5713          $query = $db->simple_select('themes', 'tid, name, pid, allowedgroups', "pid!='0'");
5714  
5715          while($theme = $db->fetch_array($query))
5716          {
5717              $tcache[$theme['pid']][$theme['tid']] = $theme;
5718          }
5719      }
5720  
5721      $s_theme = false;
5722  
5723      foreach($tcache as $themes)
5724      {
5725          foreach($themes as $theme)
5726          {
5727              if($tid == $theme['tid'])
5728              {
5729                  $s_theme = $theme;
5730                  break 2;
5731              }
5732          }
5733      }
5734  
5735      return $s_theme;
5736  }
5737  
5738  /**
5739   * Custom function for htmlspecialchars which takes in to account unicode
5740   *
5741   * @param string $message The string to format
5742   * @return string The string with htmlspecialchars applied
5743   */
5744  function htmlspecialchars_uni($message)
5745  {
5746      $message = preg_replace("#&(?!\#[0-9]+;)#si", "&amp;", $message); // Fix & but allow unicode
5747      $message = str_replace("<", "&lt;", $message);
5748      $message = str_replace(">", "&gt;", $message);
5749      $message = str_replace("\"", "&quot;", $message);
5750      return $message;
5751  }
5752  
5753  /**
5754   * Custom function for formatting numbers.
5755   *
5756   * @param int $number The number to format.
5757   * @return int The formatted number.
5758   */
5759  function my_number_format($number)
5760  {
5761      global $mybb;
5762  
5763      if($number == "-")
5764      {
5765          return $number;
5766      }
5767  
5768      if(is_int($number))
5769      {
5770          return number_format($number, 0, $mybb->settings['decpoint'], $mybb->settings['thousandssep']);
5771      }
5772      else
5773      {
5774          if(isset($number))
5775          {
5776              $parts = explode('.', $number);
5777          }
5778          else
5779          {
5780              $parts = array();
5781          }
5782  
5783          if(isset($parts[1]))
5784          {
5785              $decimals = my_strlen($parts[1]);
5786          }
5787          else
5788          {
5789              $decimals = 0;
5790          }
5791  
5792          return number_format((double)$number, $decimals, $mybb->settings['decpoint'], $mybb->settings['thousandssep']);
5793      }
5794  }
5795  
5796  /**
5797   * Converts a string of text to or from UTF-8.
5798   *
5799   * @param string $str The string of text to convert
5800   * @param boolean $to Whether or not the string is being converted to or from UTF-8 (true if converting to)
5801   * @return string The converted string
5802   */
5803  function convert_through_utf8($str, $to=true)
5804  {
5805      global $lang;
5806      static $charset;
5807      static $use_mb;
5808      static $use_iconv;
5809  
5810      if(!isset($charset))
5811      {
5812          $charset = my_strtolower($lang->settings['charset']);
5813      }
5814  
5815      if($charset == "utf-8")
5816      {
5817          return $str;
5818      }
5819  
5820      if(!isset($use_iconv))
5821      {
5822          $use_iconv = function_exists("iconv");
5823      }
5824  
5825      if(!isset($use_mb))
5826      {
5827          $use_mb = function_exists("mb_convert_encoding");
5828      }
5829  
5830      if($use_iconv || $use_mb)
5831      {
5832          if($to)
5833          {
5834              $from_charset = $lang->settings['charset'];
5835              $to_charset = "UTF-8";
5836          }
5837          else
5838          {
5839              $from_charset = "UTF-8";
5840              $to_charset = $lang->settings['charset'];
5841          }
5842          if($use_iconv)
5843          {
5844              return iconv($from_charset, $to_charset."//IGNORE", $str);
5845          }
5846          else
5847          {
5848              return @mb_convert_encoding($str, $to_charset, $from_charset);
5849          }
5850      }
5851      elseif($charset == "iso-8859-1" && function_exists("utf8_encode"))
5852      {
5853          if($to)
5854          {
5855              return utf8_encode($str);
5856          }
5857          else
5858          {
5859              return utf8_decode($str);
5860          }
5861      }
5862      else
5863      {
5864          return $str;
5865      }
5866  }
5867  
5868  /**
5869   * DEPRECATED! Please use other alternatives.
5870   *
5871   * @deprecated
5872   * @param string $message
5873   *
5874   * @return string
5875   */
5876  function my_wordwrap($message)
5877  {
5878      return $message;
5879  }
5880  
5881  /**
5882   * Workaround for date limitation in PHP to establish the day of a birthday (Provided by meme)
5883   *
5884   * @param int $month The month of the birthday
5885   * @param int $day The day of the birthday
5886   * @param int $year The year of the bithday
5887   * @return int The numeric day of the week for the birthday
5888   */
5889  function get_weekday($month, $day, $year)
5890  {
5891      $h = 4;
5892  
5893      for($i = 1969; $i >= $year; $i--)
5894      {
5895          $j = get_bdays($i);
5896  
5897          for($k = 11; $k >= 0; $k--)
5898          {
5899              $l = ($k + 1);
5900  
5901              for($m = $j[$k]; $m >= 1; $m--)
5902              {
5903                  $h--;
5904  
5905                  if($i == $year && $l == $month && $m == $day)
5906                  {
5907                      return $h;
5908                  }
5909  
5910                  if($h == 0)
5911                  {
5912                      $h = 7;
5913                  }
5914              }
5915          }
5916      }
5917  }
5918  
5919  /**
5920   * Workaround for date limitation in PHP to establish the day of a birthday (Provided by meme)
5921   *
5922   * @param int $in The year.
5923   * @return array The number of days in each month of that year
5924   */
5925  function get_bdays($in)
5926  {
5927      return array(
5928          31,
5929          ($in % 4 == 0 && ($in % 100 > 0 || $in % 400 == 0) ? 29 : 28),
5930          31,
5931          30,
5932          31,
5933          30,
5934          31,
5935          31,
5936          30,
5937          31,
5938          30,
5939          31
5940      );
5941  }
5942  
5943  /**
5944   * DEPRECATED! Please use mktime()!
5945   * Formats a birthday appropriately
5946   *
5947   * @deprecated
5948   * @param string $display The PHP date format string
5949   * @param int $bm The month of the birthday
5950   * @param int $bd The day of the birthday
5951   * @param int $by The year of the birthday
5952   * @param int $wd The weekday of the birthday
5953   * @return string The formatted birthday
5954   */
5955  function format_bdays($display, $bm, $bd, $by, $wd)
5956  {
5957      global $lang;
5958  
5959      $bdays = array(
5960          $lang->sunday,
5961          $lang->monday,
5962          $lang->tuesday,
5963          $lang->wednesday,
5964          $lang->thursday,
5965          $lang->friday,
5966          $lang->saturday
5967      );
5968  
5969      $bmonth = array(
5970          $lang->month_1,
5971          $lang->month_2,
5972          $lang->month_3,
5973          $lang->month_4,
5974          $lang->month_5,
5975          $lang->month_6,
5976          $lang->month_7,
5977          $lang->month_8,
5978          $lang->month_9,
5979          $lang->month_10,
5980          $lang->month_11,
5981          $lang->month_12
5982      );
5983  
5984      // This needs to be in this specific order
5985      $find = array(
5986          'm',
5987          'n',
5988          'd',
5989          'D',
5990          'y',
5991          'Y',
5992          'j',
5993          'S',
5994          'F',
5995          'l',
5996          'M',
5997      );
5998  
5999      $html = array(
6000          '&#109;',
6001          '&#110;',
6002          '&#99;',
6003          '&#68;',
6004          '&#121;',
6005          '&#89;',
6006          '&#106;',
6007          '&#83;',
6008          '&#70;',
6009          '&#108;',
6010          '&#77;',
6011      );
6012  
6013      $bdays = str_replace($find, $html, $bdays);
6014      $bmonth = str_replace($find, $html, $bmonth);
6015  
6016      $replace = array(
6017          sprintf('%02s', $bm),
6018          $bm,
6019          sprintf('%02s', $bd),
6020          ($wd == 2 ? my_substr($bdays[$wd], 0, 4) : ($wd == 4 ? my_substr($bdays[$wd], 0, 5) : my_substr($bdays[$wd], 0, 3))),
6021          my_substr($by, 2),
6022          $by,
6023          ($bd[0] == 0 ? my_substr($bd, 1) : $bd),
6024          ($bd == 1 || $bd == 21 || $bd == 31 ? 'st' : ($bd == 2 || $bd == 22 ? 'nd' : ($bd == 3 || $bd == 23 ? 'rd' : 'th'))),
6025          $bmonth[$bm-1],
6026          $wd,
6027          ($bm == 9 ? my_substr($bmonth[$bm-1], 0, 4) :  my_substr($bmonth[$bm-1], 0, 3)),
6028      );
6029  
6030      // Do we have the full month in our output?
6031      // If so there's no need for the short month
6032      if(strpos($display, 'F') !== false)
6033      {
6034          array_pop($find);
6035          array_pop($replace);
6036      }
6037  
6038      return str_replace($find, $replace, $display);
6039  }
6040  
6041  /**
6042   * Returns the age of a user with specified birthday.
6043   *
6044   * @param string $birthday The birthday of a user.
6045   * @return int|void The age of a user with that birthday.
6046   */
6047  function get_age($birthday)
6048  {
6049      $bday = explode("-", $birthday);
6050      if(!$bday[2])
6051      {
6052          return;
6053      }
6054  
6055      list($day, $month, $year) = explode("-", my_date("j-n-Y", TIME_NOW, 0, 0));
6056  
6057      $age = $year-$bday[2];
6058  
6059      if(($month == $bday[1] && $day < $bday[0]) || $month < $bday[1])
6060      {
6061          --$age;
6062      }
6063      return $age;
6064  }
6065  
6066  /**
6067   * Updates the first posts in a thread.
6068   *
6069   * @param int $tid The thread id for which to update the first post id.
6070   */
6071  function update_first_post($tid)
6072  {
6073      global $db;
6074  
6075      $query = $db->query("
6076          SELECT u.uid, u.username, p.pid, p.username AS postusername, p.dateline
6077          FROM ".TABLE_PREFIX."posts p
6078          LEFT JOIN ".TABLE_PREFIX."users u ON (u.uid=p.uid)
6079          WHERE p.tid='$tid'
6080          ORDER BY p.dateline ASC, p.pid ASC
6081          LIMIT 1
6082      ");
6083      $firstpost = $db->fetch_array($query);
6084  
6085      if(empty($firstpost['username']))
6086      {
6087          $firstpost['username'] = $firstpost['postusername'];
6088      }
6089      $firstpost['username'] = $db->escape_string($firstpost['username']);
6090  
6091      $update_array = array(
6092          'firstpost' => (int)$firstpost['pid'],
6093          'username' => $firstpost['username'],
6094          'uid' => (int)$firstpost['uid'],
6095          'dateline' => (int)$firstpost['dateline']
6096      );
6097      $db->update_query("threads", $update_array, "tid='{$tid}'");
6098  }
6099  
6100  /**
6101   * Updates the last posts in a thread.
6102   *
6103   * @param int $tid The thread id for which to update the last post id.
6104   */
6105  function update_last_post($tid)
6106  {
6107      global $db;
6108  
6109      $query = $db->query("
6110          SELECT u.uid, u.username, p.username AS postusername, p.dateline
6111          FROM ".TABLE_PREFIX."posts p
6112          LEFT JOIN ".TABLE_PREFIX."users u ON (u.uid=p.uid)
6113          WHERE p.tid='$tid' AND p.visible='1'
6114          ORDER BY p.dateline DESC, p.pid DESC
6115          LIMIT 1"
6116      );
6117      $lastpost = $db->fetch_array($query);
6118  
6119      if(!$lastpost)
6120      {
6121          return false;
6122      }
6123  
6124      if(empty($lastpost['username']))
6125      {
6126          $lastpost['username'] = $lastpost['postusername'];
6127      }
6128  
6129      if(empty($lastpost['dateline']))
6130      {
6131          $query = $db->query("
6132              SELECT u.uid, u.username, p.pid, p.username AS postusername, p.dateline
6133              FROM ".TABLE_PREFIX."posts p
6134              LEFT JOIN ".TABLE_PREFIX."users u ON (u.uid=p.uid)
6135              WHERE p.tid='$tid'
6136              ORDER BY p.dateline ASC, p.pid ASC
6137              LIMIT 1
6138          ");
6139          $firstpost = $db->fetch_array($query);
6140  
6141          $lastpost['username'] = $firstpost['username'];
6142          $lastpost['uid'] = $firstpost['uid'];
6143          $lastpost['dateline'] = $firstpost['dateline'];
6144      }
6145  
6146      $lastpost['username'] = $db->escape_string($lastpost['username']);
6147  
6148      $update_array = array(
6149          'lastpost' => (int)$lastpost['dateline'],
6150          'lastposter' => $lastpost['username'],
6151          'lastposteruid' => (int)$lastpost['uid']
6152      );
6153      $db->update_query("threads", $update_array, "tid='{$tid}'");
6154  }
6155  
6156  /**
6157   * Checks for the length of a string, mb strings accounted for
6158   *
6159   * @param string $string The string to check the length of.
6160   * @return int The length of the string.
6161   */
6162  function my_strlen($string)
6163  {
6164      global $lang;
6165  
6166      $string = preg_replace("#&\#([0-9]+);#", "-", $string);
6167  
6168      if(isset($lang->settings['charset']) && strtolower($lang->settings['charset']) == "utf-8")
6169      {
6170          // Get rid of any excess RTL and LTR override for they are the workings of the devil
6171          $string = str_replace(dec_to_utf8(8238), "", $string);
6172          $string = str_replace(dec_to_utf8(8237), "", $string);
6173  
6174          // Remove dodgy whitespaces
6175          $string = str_replace(chr(0xCA), "", $string);
6176      }
6177      $string = trim($string);
6178  
6179      if(function_exists("mb_strlen"))
6180      {
6181          $string_length = mb_strlen($string);
6182      }
6183      else
6184      {
6185          $string_length = strlen($string);
6186      }
6187  
6188      return $string_length;
6189  }
6190  
6191  /**
6192   * Cuts a string at a specified point, mb strings accounted for
6193   *
6194   * @param string $string The string to cut.
6195   * @param int $start Where to cut
6196   * @param int $length (optional) How much to cut
6197   * @param bool $handle_entities (optional) Properly handle HTML entities?
6198   * @return string The cut part of the string.
6199   */
6200  function my_substr($string, $start, $length=null, $handle_entities = false)
6201  {
6202      if($handle_entities)
6203      {
6204          $string = unhtmlentities($string);
6205      }
6206      if(function_exists("mb_substr"))
6207      {
6208          if($length != null)
6209          {
6210              $cut_string = mb_substr($string, $start, $length);
6211          }
6212          else
6213          {
6214              $cut_string = mb_substr($string, $start);
6215          }
6216      }
6217      else
6218      {
6219          if($length != null)
6220          {
6221              $cut_string = substr($string, $start, $length);
6222          }
6223          else
6224          {
6225              $cut_string = substr($string, $start);
6226          }
6227      }
6228  
6229      if($handle_entities)
6230      {
6231          $cut_string = htmlspecialchars_uni($cut_string);
6232      }
6233      return $cut_string;
6234  }
6235  
6236  /**
6237   * Lowers the case of a string, mb strings accounted for
6238   *
6239   * @param string $string The string to lower.
6240   * @return string The lowered string.
6241   */
6242  function my_strtolower($string)
6243  {
6244      if(function_exists("mb_strtolower"))
6245      {
6246          $string = mb_strtolower($string);
6247      }
6248      else
6249      {
6250          $string = strtolower($string);
6251      }
6252  
6253      return $string;
6254  }
6255  
6256  /**
6257   * Finds a needle in a haystack and returns it position, mb strings accounted for, case insensitive
6258   *
6259   * @param string $haystack String to look in (haystack)
6260   * @param string $needle What to look for (needle)
6261   * @param int $offset (optional) How much to offset
6262   * @return int|bool false on needle not found, integer position if found
6263   */
6264  function my_stripos($haystack, $needle, $offset=0)
6265  {
6266      if($needle == '')
6267      {
6268          return false;
6269      }
6270  
6271      if(function_exists("mb_stripos"))
6272      {
6273          $position = mb_stripos($haystack, $needle, $offset);
6274      }
6275      else
6276      {
6277          $position = stripos($haystack, $needle, $offset);
6278      }
6279  
6280      return $position;
6281  }
6282  
6283  /**
6284   * Finds a needle in a haystack and returns it position, mb strings accounted for
6285   *
6286   * @param string $haystack String to look in (haystack)
6287   * @param string $needle What to look for (needle)
6288   * @param int $offset (optional) How much to offset
6289   * @return int|bool false on needle not found, integer position if found
6290   */
6291  function my_strpos($haystack, $needle, $offset=0)
6292  {
6293      if($needle == '')
6294      {
6295          return false;
6296      }
6297  
6298      if(function_exists("mb_strpos"))
6299      {
6300          $position = mb_strpos($haystack, $needle, $offset);
6301      }
6302      else
6303      {
6304          $position = strpos($haystack, $needle, $offset);
6305      }
6306  
6307      return $position;
6308  }
6309  
6310  /**
6311   * Ups the case of a string, mb strings accounted for
6312   *
6313   * @param string $string The string to up.
6314   * @return string The uped string.
6315   */
6316  function my_strtoupper($string)
6317  {
6318      if(function_exists("mb_strtoupper"))
6319      {
6320          $string = mb_strtoupper($string);
6321      }
6322      else
6323      {
6324          $string = strtoupper($string);
6325      }
6326  
6327      return $string;
6328  }
6329  
6330  /**
6331   * Returns any html entities to their original character
6332   *
6333   * @param string $string The string to un-htmlentitize.
6334   * @return string The un-htmlentitied' string.
6335   */
6336  function unhtmlentities($string)
6337  {
6338      // Replace numeric entities
6339      $string = preg_replace_callback('~&#x([0-9a-f]+);~i', 'unichr_callback1', $string);
6340      $string = preg_replace_callback('~&#([0-9]+);~', 'unichr_callback2', $string);
6341  
6342      // Replace literal entities
6343      $trans_tbl = get_html_translation_table(HTML_ENTITIES);
6344      $trans_tbl = array_flip($trans_tbl);
6345  
6346      return strtr($string, $trans_tbl);
6347  }
6348  
6349  /**
6350   * Returns any ascii to it's character (utf-8 safe).
6351   *
6352   * @param int $c The ascii to characterize.
6353   * @return string|bool The characterized ascii. False on failure
6354   */
6355  function unichr($c)
6356  {
6357      if($c <= 0x7F)
6358      {
6359          return chr($c);
6360      }
6361      else if($c <= 0x7FF)
6362      {
6363          return chr(0xC0 | $c >> 6) . chr(0x80 | $c & 0x3F);
6364      }
6365      else if($c <= 0xFFFF)
6366      {
6367          return chr(0xE0 | $c >> 12) . chr(0x80 | $c >> 6 & 0x3F)
6368                                      . chr(0x80 | $c & 0x3F);
6369      }
6370      else if($c <= 0x10FFFF)
6371      {
6372          return chr(0xF0 | $c >> 18) . chr(0x80 | $c >> 12 & 0x3F)
6373                                      . chr(0x80 | $c >> 6 & 0x3F)
6374                                      . chr(0x80 | $c & 0x3F);
6375      }
6376      else
6377      {
6378          return false;
6379      }
6380  }
6381  
6382  /**
6383   * Returns any ascii to it's character (utf-8 safe).
6384   *
6385   * @param array $matches Matches.
6386   * @return string|bool The characterized ascii. False on failure
6387   */
6388  function unichr_callback1($matches)
6389  {
6390      return unichr(hexdec($matches[1]));
6391  }
6392  
6393  /**
6394   * Returns any ascii to it's character (utf-8 safe).
6395   *
6396   * @param array $matches Matches.
6397   * @return string|bool The characterized ascii. False on failure
6398   */
6399  function unichr_callback2($matches)
6400  {
6401      return unichr($matches[1]);
6402  }
6403  
6404  /**
6405   * Get the event poster.
6406   *
6407   * @param array $event The event data array.
6408   * @return string The link to the event poster.
6409   */
6410  function get_event_poster($event)
6411  {
6412      $event['username'] = htmlspecialchars_uni($event['username']);
6413      $event['username'] = format_name($event['username'], $event['usergroup'], $event['displaygroup']);
6414      $event_poster = build_profile_link($event['username'], $event['author']);
6415      return $event_poster;
6416  }
6417  
6418  /**
6419   * Get the event date.
6420   *
6421   * @param array $event The event data array.
6422   * @return string The event date.
6423   */
6424  function get_event_date($event)
6425  {
6426      global $mybb;
6427  
6428      $event_date = explode("-", $event['date']);
6429      $event_date = gmmktime(0, 0, 0, $event_date[1], $event_date[0], $event_date[2]);
6430      $event_date = my_date($mybb->settings['dateformat'], $event_date);
6431  
6432      return $event_date;
6433  }
6434  
6435  /**
6436   * Get the profile link.
6437   *
6438   * @param int $uid The user id of the profile.
6439   * @return string The url to the profile.
6440   */
6441  function get_profile_link($uid=0)
6442  {
6443      $link = str_replace("{uid}", $uid, PROFILE_URL);
6444      return htmlspecialchars_uni($link);
6445  }
6446  
6447  /**
6448   * Get the announcement link.
6449   *
6450   * @param int $aid The announement id of the announcement.
6451   * @return string The url to the announcement.
6452   */
6453  function get_announcement_link($aid=0)
6454  {
6455      $link = str_replace("{aid}", $aid, ANNOUNCEMENT_URL);
6456      return htmlspecialchars_uni($link);
6457  }
6458  
6459  /**
6460   * Build the profile link.
6461   *
6462   * @param string $username The Username of the profile.
6463   * @param int $uid The user id of the profile.
6464   * @param string $target The target frame
6465   * @param string $onclick Any onclick javascript.
6466   * @return string The complete profile link.
6467   */
6468  function build_profile_link($username="", $uid=0, $target="", $onclick="")
6469  {
6470      global $mybb, $lang;
6471  
6472      if(!$username && $uid == 0)
6473      {
6474          // Return Guest phrase for no UID, no guest nickname
6475          return htmlspecialchars_uni($lang->guest);
6476      }
6477      elseif($uid == 0)
6478      {
6479          // Return the guest's nickname if user is a guest but has a nickname
6480          return $username;
6481      }
6482      else
6483      {
6484          // Build the profile link for the registered user
6485          if(!empty($target))
6486          {
6487              $target = " target=\"{$target}\"";
6488          }
6489  
6490          if(!empty($onclick))
6491          {
6492              $onclick = " onclick=\"{$onclick}\"";
6493          }
6494  
6495          return "<a href=\"{$mybb->settings['bburl']}/".get_profile_link($uid)."\"{$target}{$onclick}>{$username}</a>";
6496      }
6497  }
6498  
6499  /**
6500   * Build the forum link.
6501   *
6502   * @param int $fid The forum id of the forum.
6503   * @param int $page (Optional) The page number of the forum.
6504   * @return string The url to the forum.
6505   */
6506  function get_forum_link($fid, $page=0)
6507  {
6508      if($page > 0)
6509      {
6510          $link = str_replace("{fid}", $fid, FORUM_URL_PAGED);
6511          $link = str_replace("{page}", $page, $link);
6512          return htmlspecialchars_uni($link);
6513      }
6514      else
6515      {
6516          $link = str_replace("{fid}", $fid, FORUM_URL);
6517          return htmlspecialchars_uni($link);
6518      }
6519  }
6520  
6521  /**
6522   * Build the thread link.
6523   *
6524   * @param int $tid The thread id of the thread.
6525   * @param int $page (Optional) The page number of the thread.
6526   * @param string $action (Optional) The action we're performing (ex, lastpost, newpost, etc)
6527   * @return string The url to the thread.
6528   */
6529  function get_thread_link($tid, $page=0, $action='')
6530  {
6531      if($page > 1)
6532      {
6533          if($action)
6534          {
6535              $link = THREAD_URL_ACTION;
6536              $link = str_replace("{action}", $action, $link);
6537          }
6538          else
6539          {
6540              $link = THREAD_URL_PAGED;
6541          }
6542          $link = str_replace("{tid}", $tid, $link);
6543          $link = str_replace("{page}", $page, $link);
6544          return htmlspecialchars_uni($link);
6545      }
6546      else
6547      {
6548          if($action)
6549          {
6550              $link = THREAD_URL_ACTION;
6551              $link = str_replace("{action}", $action, $link);
6552          }
6553          else
6554          {
6555              $link = THREAD_URL;
6556          }
6557          $link = str_replace("{tid}", $tid, $link);
6558          return htmlspecialchars_uni($link);
6559      }
6560  }
6561  
6562  /**
6563   * Build the post link.
6564   *
6565   * @param int $pid The post ID of the post
6566   * @param int $tid The thread id of the post.
6567   * @return string The url to the post.
6568   */
6569  function get_post_link($pid, $tid=0)
6570  {
6571      if($tid > 0)
6572      {
6573          $link = str_replace("{tid}", $tid, THREAD_URL_POST);
6574          $link = str_replace("{pid}", $pid, $link);
6575          return htmlspecialchars_uni($link);
6576      }
6577      else
6578      {
6579          $link = str_replace("{pid}", $pid, POST_URL);
6580          return htmlspecialchars_uni($link);
6581      }
6582  }
6583  
6584  /**
6585   * Build the event link.
6586   *
6587   * @param int $eid The event ID of the event
6588   * @return string The URL of the event
6589   */
6590  function get_event_link($eid)
6591  {
6592      $link = str_replace("{eid}", $eid, EVENT_URL);
6593      return htmlspecialchars_uni($link);
6594  }
6595  
6596  /**
6597   * Build the link to a specified date on the calendar
6598   *
6599   * @param int $calendar The ID of the calendar
6600   * @param int $year The year
6601   * @param int $month The month
6602   * @param int $day The day (optional)
6603   * @return string The URL of the calendar
6604   */
6605  function get_calendar_link($calendar, $year=0, $month=0, $day=0)
6606  {
6607      if($day > 0)
6608      {
6609          $link = str_replace("{month}", $month, CALENDAR_URL_DAY);
6610          $link = str_replace("{year}", $year, $link);
6611          $link = str_replace("{day}", $day, $link);
6612          $link = str_replace("{calendar}", $calendar, $link);
6613          return htmlspecialchars_uni($link);
6614      }
6615      else if($month > 0)
6616      {
6617          $link = str_replace("{month}", $month, CALENDAR_URL_MONTH);
6618          $link = str_replace("{year}", $year, $link);
6619          $link = str_replace("{calendar}", $calendar, $link);
6620          return htmlspecialchars_uni($link);
6621      }
6622      /* Not implemented
6623      else if($year > 0)
6624      {
6625      }*/
6626      else
6627      {
6628          $link = str_replace("{calendar}", $calendar, CALENDAR_URL);
6629          return htmlspecialchars_uni($link);
6630      }
6631  }
6632  
6633  /**
6634   * Build the link to a specified week on the calendar
6635   *
6636   * @param int $calendar The ID of the calendar
6637   * @param int $week The week
6638   * @return string The URL of the calendar
6639   */
6640  function get_calendar_week_link($calendar, $week)
6641  {
6642      if($week < 0)
6643      {
6644          $week = str_replace('-', "n", $week);
6645      }
6646      $link = str_replace("{week}", $week, CALENDAR_URL_WEEK);
6647      $link = str_replace("{calendar}", $calendar, $link);
6648      return htmlspecialchars_uni($link);
6649  }
6650  
6651  /**
6652   * Get the user data of an user id.
6653   *
6654   * @param int $uid The user id of the user.
6655   * @return array The users data
6656   */
6657  function get_user($uid)
6658  {
6659      global $mybb, $db;
6660      static $user_cache;
6661  
6662      $uid = (int)$uid;
6663  
6664      if(!empty($mybb->user) && $uid == $mybb->user['uid'])
6665      {
6666          return $mybb->user;
6667      }
6668      elseif(isset($user_cache[$uid]))
6669      {
6670          return $user_cache[$uid];
6671      }
6672      elseif($uid > 0)
6673      {
6674          $query = $db->simple_select("users", "*", "uid = '{$uid}'");
6675          $user_cache[$uid] = $db->fetch_array($query);
6676  
6677          return $user_cache[$uid];
6678      }
6679      return array();
6680  }
6681  
6682  /**
6683   * Get the user data of an user username.
6684   *
6685   * @param string $username The user username of the user.
6686   * @param array $options
6687   * @return array|bool The users data
6688   */
6689  function get_user_by_username($username, $options=array())
6690  {
6691      global $mybb, $db;
6692  
6693      $username = $db->escape_string(my_strtolower($username));
6694  
6695      if(!isset($options['username_method']))
6696      {
6697          $options['username_method'] = 0;
6698      }
6699  
6700      switch($db->type)
6701      {
6702          case 'mysql':
6703          case 'mysqli':
6704              $field = 'username';
6705              $efield = 'email';
6706              break;
6707          default:
6708              $field = 'LOWER(username)';
6709              $efield = 'LOWER(email)';
6710              break;
6711      }
6712  
6713      switch($options['username_method'])
6714      {
6715          case 1:
6716              $sqlwhere = "{$efield}='{$username}'";
6717              break;
6718          case 2:
6719              $sqlwhere = "{$field}='{$username}' OR {$efield}='{$username}'";
6720              break;
6721          default:
6722              $sqlwhere = "{$field}='{$username}'";
6723              break;
6724      }
6725  
6726      $fields = array('uid');
6727      if(isset($options['fields']))
6728      {
6729          $fields = array_merge((array)$options['fields'], $fields);
6730      }
6731  
6732      $query = $db->simple_select('users', implode(',', array_unique($fields)), $sqlwhere, array('limit' => 1));
6733  
6734      if(isset($options['exists']))
6735      {
6736          return (bool)$db->num_rows($query);
6737      }
6738  
6739      return $db->fetch_array($query);
6740  }
6741  
6742  /**
6743   * Get the forum of a specific forum id.
6744   *
6745   * @param int $fid The forum id of the forum.
6746   * @param int $active_override (Optional) If set to 1, will override the active forum status
6747   * @return array|bool The database row of a forum. False on failure
6748   */
6749  function get_forum($fid, $active_override=0)
6750  {
6751      global $cache;
6752      static $forum_cache;
6753  
6754      if(!isset($forum_cache) || !is_array($forum_cache))
6755      {
6756          $forum_cache = $cache->read("forums");
6757      }
6758  
6759      if(empty($forum_cache[$fid]))
6760      {
6761          return false;
6762      }
6763  
6764      if($active_override != 1)
6765      {
6766          $parents = explode(",", $forum_cache[$fid]['parentlist']);
6767          if(is_array($parents))
6768          {
6769              foreach($parents as $parent)
6770              {
6771                  if($forum_cache[$parent]['active'] == 0)
6772                  {
6773                      return false;
6774                  }
6775              }
6776          }
6777      }
6778  
6779      return $forum_cache[$fid];
6780  }
6781  
6782  /**
6783   * Get the thread of a thread id.
6784   *
6785   * @param int $tid The thread id of the thread.
6786   * @param boolean $recache Whether or not to recache the thread.
6787   * @return array|bool The database row of the thread. False on failure
6788   */
6789  function get_thread($tid, $recache = false)
6790  {
6791      global $db;
6792      static $thread_cache;
6793  
6794      $tid = (int)$tid;
6795  
6796      if(isset($thread_cache[$tid]) && !$recache)
6797      {
6798          return $thread_cache[$tid];
6799      }
6800      else
6801      {
6802          $query = $db->simple_select("threads", "*", "tid = '{$tid}'");
6803          $thread = $db->fetch_array($query);
6804  
6805          if($thread)
6806          {
6807              $thread_cache[$tid] = $thread;
6808              return $thread;
6809          }
6810          else
6811          {
6812              $thread_cache[$tid] = false;
6813              return false;
6814          }
6815      }
6816  }
6817  
6818  /**
6819   * Get the post of a post id.
6820   *
6821   * @param int $pid The post id of the post.
6822   * @return array|bool The database row of the post. False on failure
6823   */
6824  function get_post($pid)
6825  {
6826      global $db;
6827      static $post_cache;
6828  
6829      $pid = (int)$pid;
6830  
6831      if(isset($post_cache[$pid]))
6832      {
6833          return $post_cache[$pid];
6834      }
6835      else
6836      {
6837          $query = $db->simple_select("posts", "*", "pid = '{$pid}'");
6838          $post = $db->fetch_array($query);
6839  
6840          if($post)
6841          {
6842              $post_cache[$pid] = $post;
6843              return $post;
6844          }
6845          else
6846          {
6847              $post_cache[$pid] = false;
6848              return false;
6849          }
6850      }
6851  }
6852  
6853  /**
6854   * Get inactivate forums.
6855   *
6856   * @return string The comma separated values of the inactivate forum.
6857   */
6858  function get_inactive_forums()
6859  {
6860      global $forum_cache, $cache;
6861  
6862      if(!$forum_cache)
6863      {
6864          cache_forums();
6865      }
6866  
6867      $inactive = array();
6868  
6869      foreach($forum_cache as $fid => $forum)
6870      {
6871          if($forum['active'] == 0)
6872          {
6873              $inactive[] = $fid;
6874              foreach($forum_cache as $fid1 => $forum1)
6875              {
6876                  if(my_strpos(",".$forum1['parentlist'].",", ",".$fid.",") !== false && !in_array($fid1, $inactive))
6877                  {
6878                      $inactive[] = $fid1;
6879                  }
6880              }
6881          }
6882      }
6883  
6884      $inactiveforums = implode(",", $inactive);
6885  
6886      return $inactiveforums;
6887  }
6888  
6889  /**
6890   * Checks to make sure a user has not tried to login more times than permitted
6891   *
6892   * @param bool $fatal (Optional) Stop execution if it finds an error with the login. Default is True
6893   * @return bool|int Number of logins when success, false if failed.
6894   */
6895  function login_attempt_check($uid = 0, $fatal = true)
6896  {
6897      global $mybb, $lang, $db;
6898  
6899      $attempts = array();
6900      $uid = (int)$uid;
6901      $now = TIME_NOW;
6902  
6903      // Get this user's login attempts and eventual lockout, if a uid is provided
6904      if($uid > 0)
6905      {
6906          $query = $db->simple_select("users", "loginattempts, loginlockoutexpiry", "uid='{$uid}'", 1);
6907          $attempts = $db->fetch_array($query);
6908  
6909          if($attempts['loginattempts'] <= 0)
6910          {
6911              return 0;
6912          }
6913      }
6914      // This user has a cookie lockout, show waiting time
6915      elseif(!empty($mybb->cookies['lockoutexpiry']) && $mybb->cookies['lockoutexpiry'] > $now)
6916      {
6917          if($fatal)
6918          {
6919              $secsleft = (int)($mybb->cookies['lockoutexpiry'] - $now);
6920              $hoursleft = floor($secsleft / 3600);
6921              $minsleft = floor(($secsleft / 60) % 60);
6922              $secsleft = floor($secsleft % 60);
6923  
6924              error($lang->sprintf($lang->failed_login_wait, $hoursleft, $minsleft, $secsleft));
6925          }
6926  
6927          return false;
6928      }
6929  
6930      if($mybb->settings['failedlogincount'] > 0 && isset($attempts['loginattempts']) && $attempts['loginattempts'] >= $mybb->settings['failedlogincount'])
6931      {
6932          // Set the expiry dateline if not set yet
6933          if($attempts['loginlockoutexpiry'] == 0)
6934          {
6935              $attempts['loginlockoutexpiry'] = $now + ((int)$mybb->settings['failedlogintime'] * 60);
6936  
6937              // Add a cookie lockout. This is used to prevent access to the login page immediately.
6938              // A deep lockout is issued if he tries to login into a locked out account
6939              my_setcookie('lockoutexpiry', $attempts['loginlockoutexpiry']);
6940  
6941              $db->update_query("users", array(
6942                  "loginlockoutexpiry" => $attempts['loginlockoutexpiry']
6943              ), "uid='{$uid}'");
6944          }
6945  
6946          if(empty($mybb->cookies['lockoutexpiry']))
6947          {
6948              $failedtime = $attempts['loginlockoutexpiry'];
6949          }
6950          else
6951          {
6952              $failedtime = $mybb->cookies['lockoutexpiry'];
6953          }
6954  
6955          // Are we still locked out?
6956          if($attempts['loginlockoutexpiry'] > $now)
6957          {
6958              if($fatal)
6959              {
6960                  $secsleft = (int)($attempts['loginlockoutexpiry'] - $now);
6961                  $hoursleft = floor($secsleft / 3600);
6962                  $minsleft = floor(($secsleft / 60) % 60);
6963                  $secsleft = floor($secsleft % 60);
6964  
6965                  error($lang->sprintf($lang->failed_login_wait, $hoursleft, $minsleft, $secsleft));
6966              }
6967  
6968              return false;
6969          }
6970          // Unlock if enough time has passed
6971          else {
6972  
6973              if($uid > 0)
6974              {
6975                  $db->update_query("users", array(
6976                      "loginattempts" => 0,
6977                      "loginlockoutexpiry" => 0
6978                  ), "uid='{$uid}'");
6979              }
6980  
6981              // Wipe the cookie, no matter if a guest or a member
6982              my_unsetcookie('lockoutexpiry');
6983  
6984              return 0;
6985          }
6986      }
6987  
6988      if(!isset($attempts['loginattempts']))
6989      {
6990          $attempts['loginattempts'] = 0;
6991      }
6992  
6993      // User can attempt another login
6994      return $attempts['loginattempts'];
6995  }
6996  
6997  /**
6998   * Validates the format of an email address.
6999   *
7000   * @param string $email The string to check.
7001   * @return boolean True when valid, false when invalid.
7002   */
7003  function validate_email_format($email)
7004  {
7005      return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
7006  }
7007  
7008  /**
7009   * Checks to see if the email is already in use by another
7010   *
7011   * @param string $email The email to check.
7012   * @param int $uid User ID of the user (updating only)
7013   * @return boolean True when in use, false when not.
7014   */
7015  function email_already_in_use($email, $uid=0)
7016  {
7017      global $db;
7018  
7019      $uid_string = "";
7020      if($uid)
7021      {
7022          $uid_string = " AND uid != '".(int)$uid."'";
7023      }
7024      $query = $db->simple_select("users", "COUNT(email) as emails", "email = '".$db->escape_string($email)."'{$uid_string}");
7025  
7026      if($db->fetch_field($query, "emails") > 0)
7027      {
7028          return true;
7029      }
7030  
7031      return false;
7032  }
7033  
7034  /**
7035   * Rebuilds settings.php
7036   *
7037   */
7038  function rebuild_settings()
7039  {
7040      global $db, $mybb;
7041  
7042      $query = $db->simple_select("settings", "value, name", "", array(
7043          'order_by' => 'title',
7044          'order_dir' => 'ASC',
7045      ));
7046  
7047      $settings = '';
7048      while($setting = $db->fetch_array($query))
7049      {
7050          $mybb->settings[$setting['name']] = $setting['value'];
7051  
7052          $setting['name'] = addcslashes($setting['name'], "\\'");
7053          $setting['value'] = addcslashes($setting['value'], '\\"$');
7054          $settings .= "\$settings['{$setting['name']}'] = \"{$setting['value']}\";\n";
7055      }
7056  
7057      $settings = "<"."?php\n/*********************************\ \n  DO NOT EDIT THIS FILE, PLEASE USE\n  THE SETTINGS EDITOR\n\*********************************/\n\n$settings\n";
7058  
7059      file_put_contents(MYBB_ROOT.'inc/settings.php', $settings, LOCK_EX);
7060  
7061      $GLOBALS['settings'] = &$mybb->settings;
7062  }
7063  
7064  /**
7065   * Build a PREG compatible array of search highlight terms to replace in posts.
7066   *
7067   * @param string $terms Incoming terms to highlight
7068   * @return array|false PREG compatible array of terms
7069   */
7070  function build_highlight_array($terms)
7071  {
7072      global $mybb;
7073  
7074      if($mybb->settings['minsearchword'] < 1)
7075      {
7076          $mybb->settings['minsearchword'] = 3;
7077      }
7078  
7079      if(is_array($terms))
7080      {
7081          $terms = implode(' ', $terms);
7082      }
7083  
7084      // Strip out any characters that shouldn't be included
7085      $bad_characters = array(
7086          "(",
7087          ")",
7088          "+",
7089          "-",
7090          "~"
7091      );
7092      $terms = str_replace($bad_characters, '', $terms);
7093      $words = array();
7094  
7095      // Check if this is a "series of words" - should be treated as an EXACT match
7096      if(my_strpos($terms, "\"") !== false)
7097      {
7098          $inquote = false;
7099          $terms = explode("\"", $terms);
7100          foreach($terms as $phrase)
7101          {
7102              $phrase = htmlspecialchars_uni($phrase);
7103              if($phrase != "")
7104              {
7105                  if($inquote)
7106                  {
7107                      $words[] = trim($phrase);
7108                  }
7109                  else
7110                  {
7111                      $split_words = preg_split("#\s{1,}#", $phrase, -1);
7112                      if(!is_array($split_words))
7113                      {
7114                          continue;
7115                      }
7116                      foreach($split_words as $word)
7117                      {
7118                          if(!$word || strlen($word) < $mybb->settings['minsearchword'])
7119                          {
7120                              continue;
7121                          }
7122                          $words[] = trim($word);
7123                      }
7124                  }
7125              }
7126              $inquote = !$inquote;
7127          }
7128      }
7129      // Otherwise just a simple search query with no phrases
7130      else
7131      {
7132          $terms = htmlspecialchars_uni($terms);
7133          $split_words = preg_split("#\s{1,}#", $terms, -1);
7134          if(is_array($split_words))
7135          {
7136              foreach($split_words as $word)
7137              {
7138                  if(!$word || strlen($word) < $mybb->settings['minsearchword'])
7139                  {
7140                      continue;
7141                  }
7142                  $words[] = trim($word);
7143              }
7144          }
7145      }
7146  
7147      // Sort the word array by length. Largest terms go first and work their way down to the smallest term.
7148      // This resolves problems like "test tes" where "tes" will be highlighted first, then "test" can't be highlighted because of the changed html
7149      usort($words, 'build_highlight_array_sort');
7150  
7151      $highlight_cache = array();
7152  
7153      // Loop through our words to build the PREG compatible strings
7154      foreach($words as $word)
7155      {
7156          $word = trim($word);
7157  
7158          $word = my_strtolower($word);
7159  
7160          // Special boolean operators should be stripped
7161          if($word == "" || $word == "or" || $word == "not" || $word == "and")
7162          {
7163              continue;
7164          }
7165  
7166          // Now make PREG compatible
7167          $find = "/(?<!&|&#)\b([[:alnum:]]*)(".preg_quote($word, "/").")(?![^<>]*?>)/ui";
7168          $replacement = "$1<span class=\"highlight\" style=\"padding-left: 0px; padding-right: 0px;\">$2</span>";
7169          $highlight_cache[$find] = $replacement;
7170      }
7171  
7172      return $highlight_cache;
7173  }
7174  
7175  /**
7176   * Sort the word array by length. Largest terms go first and work their way down to the smallest term.
7177   *
7178   * @param string $a First word.
7179   * @param string $b Second word.
7180   * @return integer Result of comparison function.
7181   */
7182  function build_highlight_array_sort($a, $b)
7183  {
7184      return strlen($b) - strlen($a);
7185  }
7186  
7187  /**
7188   * Converts a decimal reference of a character to its UTF-8 equivalent
7189   * (Code by Anne van Kesteren, http://annevankesteren.nl/2005/05/character-references)
7190   *
7191   * @param int $src Decimal value of a character reference
7192   * @return string|bool
7193   */
7194  function dec_to_utf8($src)
7195  {
7196      $dest = '';
7197  
7198      if($src < 0)
7199      {
7200          return false;
7201      }
7202      elseif($src <= 0x007f)
7203      {
7204          $dest .= chr($src);
7205      }
7206      elseif($src <= 0x07ff)
7207      {
7208          $dest .= chr(0xc0 | ($src >> 6));
7209          $dest .= chr(0x80 | ($src & 0x003f));
7210      }
7211      elseif($src <= 0xffff)
7212      {
7213          $dest .= chr(0xe0 | ($src >> 12));
7214          $dest .= chr(0x80 | (($src >> 6) & 0x003f));
7215          $dest .= chr(0x80 | ($src & 0x003f));
7216      }
7217      elseif($src <= 0x10ffff)
7218      {
7219          $dest .= chr(0xf0 | ($src >> 18));
7220          $dest .= chr(0x80 | (($src >> 12) & 0x3f));
7221          $dest .= chr(0x80 | (($src >> 6) & 0x3f));
7222          $dest .= chr(0x80 | ($src & 0x3f));
7223      }
7224      else
7225      {
7226          // Out of range
7227          return false;
7228      }
7229  
7230      return $dest;
7231  }
7232  
7233  /**
7234   * Checks if a username has been disallowed for registration/use.
7235   *
7236   * @param string $username The username
7237   * @param boolean $update_lastuse True if the 'last used' dateline should be updated if a match is found.
7238   * @return boolean True if banned, false if not banned
7239   */
7240  function is_banned_username($username, $update_lastuse=false)
7241  {
7242      global $db;
7243      $query = $db->simple_select('banfilters', 'filter, fid', "type='2'");
7244      while($banned_username = $db->fetch_array($query))
7245      {
7246          // Make regular expression * match
7247          $banned_username['filter'] = str_replace('\*', '(.*)', preg_quote($banned_username['filter'], '#'));
7248          if(preg_match("#(^|\b){$banned_username['filter']}($|\b)#i", $username))
7249          {
7250              // Updating last use
7251              if($update_lastuse == true)
7252              {
7253                  $db->update_query("banfilters", array("lastuse" => TIME_NOW), "fid='{$banned_username['fid']}'");
7254              }
7255              return true;
7256          }
7257      }
7258      // Still here - good username
7259      return false;
7260  }
7261  
7262  /**
7263   * Check if a specific email address has been banned.
7264   *
7265   * @param string $email The email address.
7266   * @param boolean $update_lastuse True if the 'last used' dateline should be updated if a match is found.
7267   * @return boolean True if banned, false if not banned
7268   */
7269  function is_banned_email($email, $update_lastuse=false)
7270  {
7271      global $cache, $db;
7272  
7273      $banned_cache = $cache->read("bannedemails");
7274  
7275      if($banned_cache === false)
7276      {
7277          // Failed to read cache, see if we can rebuild it
7278          $cache->update_bannedemails();
7279          $banned_cache = $cache->read("bannedemails");
7280      }
7281  
7282      if(is_array($banned_cache) && !empty($banned_cache))
7283      {
7284          foreach($banned_cache as $banned_email)
7285          {
7286              // Make regular expression * match
7287              $banned_email['filter'] = str_replace('\*', '(.*)', preg_quote($banned_email['filter'], '#'));
7288  
7289              if(preg_match("#{$banned_email['filter']}#i", $email))
7290              {
7291                  // Updating last use
7292                  if($update_lastuse == true)
7293                  {
7294                      $db->update_query("banfilters", array("lastuse" => TIME_NOW), "fid='{$banned_email['fid']}'");
7295                  }
7296                  return true;
7297              }
7298          }
7299      }
7300  
7301      // Still here - good email
7302      return false;
7303  }
7304  
7305  /**
7306   * Checks if a specific IP address has been banned.
7307   *
7308   * @param string $ip_address The IP address.
7309   * @param boolean $update_lastuse True if the 'last used' dateline should be updated if a match is found.
7310   * @return boolean True if banned, false if not banned.
7311   */
7312  function is_banned_ip($ip_address, $update_lastuse=false)
7313  {
7314      global $db, $cache;
7315  
7316      $banned_ips = $cache->read("bannedips");
7317      if(!is_array($banned_ips))
7318      {
7319          return false;
7320      }
7321  
7322      $ip_address = my_inet_pton($ip_address);
7323      foreach($banned_ips as $banned_ip)
7324      {
7325          if(!$banned_ip['filter'])
7326          {
7327              continue;
7328          }
7329  
7330          $banned = false;
7331  
7332          $ip_range = fetch_ip_range($banned_ip['filter']);
7333          if(is_array($ip_range))
7334          {
7335              if(strcmp($ip_range[0], $ip_address) <= 0 && strcmp($ip_range[1], $ip_address) >= 0)
7336              {
7337                  $banned = true;
7338              }
7339          }
7340          elseif($ip_address == $ip_range)
7341          {
7342              $banned = true;
7343          }
7344          if($banned)
7345          {
7346              // Updating last use
7347              if($update_lastuse == true)
7348              {
7349                  $db->update_query("banfilters", array("lastuse" => TIME_NOW), "fid='{$banned_ip['fid']}'");
7350              }
7351              return true;
7352          }
7353      }
7354  
7355      // Still here - good ip
7356      return false;
7357  }
7358  
7359  /**
7360   * Returns an array of supported timezones
7361   *
7362   * @return string[] Key is timezone offset, Value the language description
7363   */
7364  function get_supported_timezones()
7365  {
7366      global $lang;
7367      $timezones = array(
7368          "-12" => $lang->timezone_gmt_minus_1200,
7369          "-11" => $lang->timezone_gmt_minus_1100,
7370          "-10" => $lang->timezone_gmt_minus_1000,
7371          "-9.5" => $lang->timezone_gmt_minus_950,
7372          "-9" => $lang->timezone_gmt_minus_900,
7373          "-8" => $lang->timezone_gmt_minus_800,
7374          "-7" => $lang->timezone_gmt_minus_700,
7375          "-6" => $lang->timezone_gmt_minus_600,
7376          "-5" => $lang->timezone_gmt_minus_500,
7377          "-4.5" => $lang->timezone_gmt_minus_450,
7378          "-4" => $lang->timezone_gmt_minus_400,
7379          "-3.5" => $lang->timezone_gmt_minus_350,
7380          "-3" => $lang->timezone_gmt_minus_300,
7381          "-2" => $lang->timezone_gmt_minus_200,
7382          "-1" => $lang->timezone_gmt_minus_100,
7383          "0" => $lang->timezone_gmt,
7384          "1" => $lang->timezone_gmt_100,
7385          "2" => $lang->timezone_gmt_200,
7386          "3" => $lang->timezone_gmt_300,
7387          "3.5" => $lang->timezone_gmt_350,
7388          "4" => $lang->timezone_gmt_400,
7389          "4.5" => $lang->timezone_gmt_450,
7390          "5" => $lang->timezone_gmt_500,
7391          "5.5" => $lang->timezone_gmt_550,
7392          "5.75" => $lang->timezone_gmt_575,
7393          "6" => $lang->timezone_gmt_600,
7394          "6.5" => $lang->timezone_gmt_650,
7395          "7" => $lang->timezone_gmt_700,
7396          "8" => $lang->timezone_gmt_800,
7397          "8.5" => $lang->timezone_gmt_850,
7398          "8.75" => $lang->timezone_gmt_875,
7399          "9" => $lang->timezone_gmt_900,
7400          "9.5" => $lang->timezone_gmt_950,
7401          "10" => $lang->timezone_gmt_1000,
7402          "10.5" => $lang->timezone_gmt_1050,
7403          "11" => $lang->timezone_gmt_1100,
7404          "11.5" => $lang->timezone_gmt_1150,
7405          "12" => $lang->timezone_gmt_1200,
7406          "12.75" => $lang->timezone_gmt_1275,
7407          "13" => $lang->timezone_gmt_1300,
7408          "14" => $lang->timezone_gmt_1400
7409      );
7410      return $timezones;
7411  }
7412  
7413  /**
7414   * Build a time zone selection list.
7415   *
7416   * @param string $name The name of the select
7417   * @param int $selected The selected time zone (defaults to GMT)
7418   * @param boolean $short True to generate a "short" list with just timezone and current time
7419   * @return string
7420   */
7421  function build_timezone_select($name, $selected=0, $short=false)
7422  {
7423      global $mybb, $lang, $templates;
7424  
7425      $timezones = get_supported_timezones();
7426  
7427      $selected = str_replace("+", "", $selected);
7428      $timezone_option = '';
7429      foreach($timezones as $timezone => $label)
7430      {
7431          $selected_add = "";
7432          if($selected == $timezone)
7433          {
7434              $selected_add = " selected=\"selected\"";
7435          }
7436          if($short == true)
7437          {
7438              $label = '';
7439              if($timezone != 0)
7440              {
7441                  $label = $timezone;
7442                  if($timezone > 0)
7443                  {
7444                      $label = "+{$label}";
7445                  }
7446                  if(strpos($timezone, ".") !== false)
7447                  {
7448                      $label = str_replace(".", ":", $label);
7449                      $label = str_replace(":5", ":30", $label);
7450                      $label = str_replace(":75", ":45", $label);
7451                  }
7452                  else
7453                  {
7454                      $label .= ":00";
7455                  }
7456              }
7457              $time_in_zone = my_date($mybb->settings['timeformat'], TIME_NOW, $timezone);
7458              $label = $lang->sprintf($lang->timezone_gmt_short, $label." ", $time_in_zone);
7459          }
7460  
7461          eval("\$timezone_option .= \"".$templates->get("usercp_options_timezone_option")."\";");
7462      }
7463  
7464      eval("\$select = \"".$templates->get("usercp_options_timezone")."\";");
7465      return $select;
7466  }
7467  
7468  /**
7469   * Fetch the contents of a remote file.
7470   *
7471   * @param string $url The URL of the remote file
7472   * @param array $post_data The array of post data
7473   * @param int $max_redirects Number of maximum redirects
7474   * @return string|bool The remote file contents. False on failure
7475   */
7476  function fetch_remote_file($url, $post_data=array(), $max_redirects=20)
7477  {
7478      global $mybb, $config;
7479  
7480      if(!my_validate_url($url, true))
7481      {
7482          return false;
7483      }
7484  
7485      $url_components = @parse_url($url);
7486  
7487      if(!isset($url_components['scheme']))
7488      {
7489          $url_components['scheme'] = 'https';
7490      }
7491      if(!isset($url_components['port']))
7492      {
7493          $url_components['port'] = $url_components['scheme'] == 'https' ? 443 : 80;
7494      }
7495  
7496      if(
7497          !$url_components ||
7498          empty($url_components['host']) ||
7499          (!empty($url_components['scheme']) && !in_array($url_components['scheme'], array('http', 'https'))) ||
7500          (!in_array($url_components['port'], array(80, 8080, 443))) ||
7501          (!empty($config['disallowed_remote_hosts']) && in_array($url_components['host'], $config['disallowed_remote_hosts']))
7502      )
7503      {
7504          return false;
7505      }
7506  
7507      $addresses = get_ip_by_hostname($url_components['host']);
7508  
7509      if(empty($addresses))
7510      {
7511          return false;
7512      }
7513  
7514      $destination_address = $addresses[0];
7515  
7516      if(!empty($config['disallowed_remote_addresses']))
7517      {
7518          foreach($config['disallowed_remote_addresses'] as $disallowed_address)
7519          {
7520              $ip_range = fetch_ip_range($disallowed_address);
7521  
7522              $packed_address = my_inet_pton($destination_address);
7523  
7524              if(is_array($ip_range))
7525              {
7526                  if(strcmp($ip_range[0], $packed_address) <= 0 && strcmp($ip_range[1], $packed_address) >= 0)
7527                  {
7528                      return false;
7529                  }
7530              }
7531              elseif($destination_address == $disallowed_address)
7532              {
7533                  return false;
7534              }
7535          }
7536      }
7537  
7538      $post_body = '';
7539      if(!empty($post_data))
7540      {
7541          foreach($post_data as $key => $val)
7542          {
7543              $post_body .= '&'.urlencode($key).'='.urlencode($val);
7544          }
7545          $post_body = ltrim($post_body, '&');
7546      }
7547  
7548      if(function_exists("curl_init"))
7549      {
7550          $fetch_header = $max_redirects > 0;
7551  
7552          $ch = curl_init();
7553  
7554          $curlopt = array(
7555              CURLOPT_URL => $url,
7556              CURLOPT_HEADER => $fetch_header,
7557              CURLOPT_TIMEOUT => 10,
7558              CURLOPT_RETURNTRANSFER => 1,
7559              CURLOPT_FOLLOWLOCATION => 0,
7560          );
7561  
7562          if($ca_bundle_path = get_ca_bundle_path())
7563          {
7564              $curlopt[CURLOPT_SSL_VERIFYPEER] = 1;
7565              $curlopt[CURLOPT_CAINFO] = $ca_bundle_path;
7566          }
7567          else
7568          {
7569              $curlopt[CURLOPT_SSL_VERIFYPEER] = 0;
7570          }
7571  
7572          $curl_version_info = curl_version();
7573          $curl_version = $curl_version_info['version'];
7574  
7575          if(version_compare(PHP_VERSION, '7.0.7', '>=') && version_compare($curl_version, '7.49', '>='))
7576          {
7577              // CURLOPT_CONNECT_TO
7578              $curlopt[10243] = array(
7579                  $url_components['host'].':'.$url_components['port'].':'.$destination_address
7580              );
7581          }
7582          elseif(version_compare(PHP_VERSION, '5.5', '>=') && version_compare($curl_version, '7.21.3', '>='))
7583          {
7584              // CURLOPT_RESOLVE
7585              $curlopt[10203] = array(
7586                  $url_components['host'].':'.$url_components['port'].':'.$destination_address
7587              );
7588          }
7589  
7590          if(defined('CURLOPT_DISALLOW_USERNAME_IN_URL'))
7591          {
7592              $curlopt[CURLOPT_DISALLOW_USERNAME_IN_URL] = true;
7593          }
7594  
7595          if(!empty($post_body))
7596          {
7597              $curlopt[CURLOPT_POST] = 1;
7598              $curlopt[CURLOPT_POSTFIELDS] = $post_body;
7599          }
7600  
7601          curl_setopt_array($ch, $curlopt);
7602  
7603          $response = curl_exec($ch);
7604  
7605          if($fetch_header)
7606          {
7607              $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
7608              $header = substr($response, 0, $header_size);
7609              $body = substr($response, $header_size);
7610  
7611              if(in_array(curl_getinfo($ch, CURLINFO_HTTP_CODE), array(301, 302)))
7612              {
7613                  preg_match('/^Location:(.*?)(?:\n|$)/im', $header, $matches);
7614  
7615                  if($matches)
7616                  {
7617                      $data = fetch_remote_file(trim(array_pop($matches)), $post_data, --$max_redirects);
7618                  }
7619              }
7620              else
7621              {
7622                  $data = $body;
7623              }
7624          }
7625          else
7626          {
7627              $data = $response;
7628          }
7629  
7630          curl_close($ch);
7631          return $data;
7632      }
7633      else if(function_exists("fsockopen"))
7634      {
7635          if(!isset($url_components['path']))
7636          {
7637              $url_components['path'] = "/";
7638          }
7639          if(isset($url_components['query']))
7640          {
7641              $url_components['path'] .= "?{$url_components['query']}";
7642          }
7643  
7644          $scheme = '';
7645  
7646          if($url_components['scheme'] == 'https')
7647          {
7648              $scheme = 'ssl://';
7649              if($url_components['port'] == 80)
7650              {
7651                  $url_components['port'] = 443;
7652              }
7653          }
7654  
7655          if(function_exists('stream_context_create'))
7656          {
7657              if($url_components['scheme'] == 'https' && $ca_bundle_path = get_ca_bundle_path())
7658              {
7659                  $context = stream_context_create(array(
7660                      'ssl' => array(
7661                          'verify_peer' => true,
7662                          'verify_peer_name' => true,
7663                          'peer_name' => $url_components['host'],
7664                          'cafile' => $ca_bundle_path,
7665                      ),
7666                  ));
7667              }
7668              else
7669              {
7670                  $context = stream_context_create(array(
7671                      'ssl' => array(
7672                          'verify_peer' => false,
7673                          'verify_peer_name' => false,
7674                          'peer_name' => $url_components['host'],
7675                      ),
7676                  ));
7677              }
7678  
7679              $fp = @stream_socket_client($scheme.$destination_address.':'.(int)$url_components['port'], $error_no, $error, 10, STREAM_CLIENT_CONNECT, $context);
7680          }
7681          else
7682          {
7683              $fp = @fsockopen($scheme.$url_components['host'], (int)$url_components['port'], $error_no, $error, 10);
7684          }
7685  
7686          if(!$fp)
7687          {
7688              return false;
7689          }
7690          @stream_set_timeout($fp, 10);
7691          $headers = array();
7692          if(!empty($post_body))
7693          {
7694              $headers[] = "POST {$url_components['path']} HTTP/1.0";
7695              $headers[] = "Content-Length: ".strlen($post_body);
7696              $headers[] = "Content-Type: application/x-www-form-urlencoded";
7697          }
7698          else
7699          {
7700              $headers[] = "GET {$url_components['path']} HTTP/1.0";
7701          }
7702  
7703          $headers[] = "Host: {$url_components['host']}";
7704          $headers[] = "Connection: Close";
7705          $headers[] = '';
7706  
7707          if(!empty($post_body))
7708          {
7709              $headers[] = $post_body;
7710          }
7711          else
7712          {
7713              // If we have no post body, we need to add an empty element to make sure we've got \r\n\r\n before the (non-existent) body starts
7714              $headers[] = '';
7715          }
7716  
7717          $headers = implode("\r\n", $headers);
7718          if(!@fwrite($fp, $headers))
7719          {
7720              return false;
7721          }
7722  
7723          $data = null;
7724  
7725          while(!feof($fp))
7726          {
7727              $data .= fgets($fp, 12800);
7728          }
7729          fclose($fp);
7730  
7731          $data = explode("\r\n\r\n", $data, 2);
7732  
7733          $header = $data[0];
7734          $status_line = current(explode("\n\n", $header, 1));
7735          $body = $data[1];
7736  
7737          if($max_redirects > 0 && (strstr($status_line, ' 301 ') || strstr($status_line, ' 302 ')))
7738          {
7739              preg_match('/^Location:(.*?)(?:\n|$)/im', $header, $matches);
7740  
7741              if($matches)
7742              {
7743                  $data = fetch_remote_file(trim(array_pop($matches)), $post_data, --$max_redirects);
7744              }
7745          }
7746          else
7747          {
7748              $data = $body;
7749          }
7750  
7751          return $data;
7752      }
7753      else
7754      {
7755          return false;
7756      }
7757  }
7758  
7759  /**
7760   * Resolves a hostname into a set of IP addresses.
7761   *
7762   * @param string $hostname The hostname to be resolved
7763   * @return array|bool The resulting IP addresses. False on failure
7764   */
7765  function get_ip_by_hostname($hostname)
7766  {
7767      $addresses = @gethostbynamel($hostname);
7768  
7769      if(!$addresses)
7770      {
7771          $result_set = @dns_get_record($hostname, DNS_A | DNS_AAAA);
7772  
7773          if($result_set)
7774          {
7775              $addresses = array();
7776  
7777              foreach($result_set as $result)
7778              {
7779                  if(array_key_exists('ip', $result))
7780                  {
7781                      $addresses[] = $result['ip'];
7782                  }
7783                  elseif(array_key_exists('ipv6', $result))
7784                  {
7785                      $addresses[] = $result['ipv6'];
7786                  }
7787              }
7788  
7789              if(empty($addresses))
7790              {
7791                  return false;
7792              }
7793          }
7794          else
7795          {
7796              return false;
7797          }
7798      }
7799  
7800      return $addresses;
7801  }
7802  
7803  /**
7804   * Returns the location of the CA bundle defined in the PHP configuration.
7805   *
7806   * @return string|bool The location of the CA bundle, false if not set
7807   */
7808  function get_ca_bundle_path()
7809  {
7810      if($path = ini_get('openssl.cafile'))
7811      {
7812          return $path;
7813      }
7814      if($path = ini_get('curl.cainfo'))
7815      {
7816          return $path;
7817      }
7818  
7819      return false;
7820  }
7821  
7822  /**
7823   * Checks if a particular user is a super administrator.
7824   *
7825   * @param int $uid The user ID to check against the list of super admins
7826   * @return boolean True if a super admin, false if not
7827   */
7828  function is_super_admin($uid)
7829  {
7830      static $super_admins;
7831  
7832      if(!isset($super_admins))
7833      {
7834          global $mybb;
7835          $super_admins = str_replace(" ", "", $mybb->config['super_admins']);
7836      }
7837  
7838      if(my_strpos(",{$super_admins},", ",{$uid},") === false)
7839      {
7840          return false;
7841      }
7842      else
7843      {
7844          return true;
7845      }
7846  }
7847  
7848  /**
7849   * Checks if a user is a member of a particular group
7850   * Originates from frostschutz's PluginLibrary
7851   * github.com/frostschutz
7852   *
7853   * @param array|int|string A selection of groups (as array or comma seperated) to check or -1 for any group
7854   * @param bool|array|int False assumes the current user. Otherwise an user array or an id can be passed
7855   * @return array Array of groups specified in the first param to which the user belongs
7856   */
7857  function is_member($groups, $user = false)
7858  {
7859      global $mybb;
7860  
7861      if(empty($groups))
7862      {
7863          return array();
7864      }
7865  
7866      if($user == false)
7867      {
7868          $user = $mybb->user;
7869      }
7870      else if(!is_array($user))
7871      {
7872          // Assume it's a UID
7873          $user = get_user($user);
7874      }
7875  
7876      $memberships = array_map('intval', explode(',', $user['additionalgroups']));
7877      $memberships[] = $user['usergroup'];
7878  
7879      if(!is_array($groups))
7880      {
7881          if((int)$groups == -1)
7882          {
7883              return $memberships;
7884          }
7885          else
7886          {
7887              if(is_string($groups))
7888              {
7889                  $groups = explode(',', $groups);
7890              }
7891              else
7892              {
7893                  $groups = (array)$groups;
7894              }
7895          }
7896      }
7897  
7898      $groups = array_filter(array_map('intval', $groups));
7899  
7900      return array_intersect($groups, $memberships);
7901  }
7902  
7903  /**
7904   * Split a string based on the specified delimeter, ignoring said delimeter in escaped strings.
7905   * Ex: the "quick brown fox" jumped, could return 1 => the, 2 => quick brown fox, 3 => jumped
7906   *
7907   * @param string $delimeter The delimeter to split by
7908   * @param string $string The string to split
7909   * @param string $escape The escape character or string if we have one.
7910   * @return array|string Array of split string
7911   */
7912  function escaped_explode($delimeter, $string, $escape="")
7913  {
7914      $strings = array();
7915      $original = $string;
7916      $in_escape = false;
7917      if($escape)
7918      {
7919          if(is_array($escape))
7920          {
7921  			function escaped_explode_escape($string)
7922              {
7923                  return preg_quote($string, "#");
7924              }
7925              $escape_preg = "(".implode("|", array_map("escaped_explode_escape", $escape)).")";
7926          }
7927          else
7928          {
7929              $escape_preg = preg_quote($escape, "#");
7930          }
7931          $quoted_strings = preg_split("#(?<!\\\){$escape_preg}#", $string);
7932      }
7933      else
7934      {
7935          $quoted_strings = array($string);
7936      }
7937      foreach($quoted_strings as $string)
7938      {
7939          if($string != "")
7940          {
7941              if($in_escape)
7942              {
7943                  $strings[] = trim($string);
7944              }
7945              else
7946              {
7947                  $split_strings = explode($delimeter, $string);
7948                  foreach($split_strings as $string)
7949                  {
7950                      if($string == "") continue;
7951                      $strings[] = trim($string);
7952                  }
7953              }
7954          }
7955          $in_escape = !$in_escape;
7956      }
7957      if(!count($strings))
7958      {
7959          return $original;
7960      }
7961      return $strings;
7962  }
7963  
7964  /**
7965   * DEPRECATED! Please use IPv6 compatible fetch_ip_range!
7966   * Fetch an IPv4 long formatted range for searching IPv4 IP addresses.
7967   *
7968   * @deprecated
7969   * @param string $ip The IP address to convert to a range based LONG
7970   * @return string|array If a full IP address is provided, the ip2long equivalent, otherwise an array of the upper & lower extremities of the IP
7971   */
7972  function fetch_longipv4_range($ip)
7973  {
7974      $ip_bits = explode(".", $ip);
7975      $ip_string1 = $ip_string2 = "";
7976  
7977      if($ip == "*")
7978      {
7979          return array(ip2long('0.0.0.0'), ip2long('255.255.255.255'));
7980      }
7981  
7982      if(strpos($ip, ".*") === false)
7983      {
7984          $ip = str_replace("*", "", $ip);
7985          if(count($ip_bits) == 4)
7986          {
7987              return ip2long($ip);
7988          }
7989          else
7990          {
7991              return array(ip2long($ip.".0"), ip2long($ip.".255"));
7992          }
7993      }
7994      // Wildcard based IP provided
7995      else
7996      {
7997          $sep = "";
7998          foreach($ip_bits as $piece)
7999          {
8000              if($piece == "*")
8001              {
8002                  $ip_string1 .= $sep."0";
8003                  $ip_string2 .= $sep."255";
8004              }
8005              else
8006              {
8007                  $ip_string1 .= $sep.$piece;
8008                  $ip_string2 .= $sep.$piece;
8009              }
8010              $sep = ".";
8011          }
8012          return array(ip2long($ip_string1), ip2long($ip_string2));
8013      }
8014  }
8015  
8016  /**
8017   * Fetch a list of ban times for a user account.
8018   *
8019   * @return array Array of ban times
8020   */
8021  function fetch_ban_times()
8022  {
8023      global $plugins, $lang;
8024  
8025      // Days-Months-Years
8026      $ban_times = array(
8027          "1-0-0" => "1 {$lang->day}",
8028          "2-0-0" => "2 {$lang->days}",
8029          "3-0-0" => "3 {$lang->days}",
8030          "4-0-0" => "4 {$lang->days}",
8031          "5-0-0" => "5 {$lang->days}",
8032          "6-0-0" => "6 {$lang->days}",
8033          "7-0-0" => "1 {$lang->week}",
8034          "14-0-0" => "2 {$lang->weeks}",
8035          "21-0-0" => "3 {$lang->weeks}",
8036          "0-1-0" => "1 {$lang->month}",
8037          "0-2-0" => "2 {$lang->months}",
8038          "0-3-0" => "3 {$lang->months}",
8039          "0-4-0" => "4 {$lang->months}",
8040          "0-5-0" => "5 {$lang->months}",
8041          "0-6-0" => "6 {$lang->months}",
8042          "0-0-1" => "1 {$lang->year}",
8043          "0-0-2" => "2 {$lang->years}"
8044      );
8045  
8046      $ban_times = $plugins->run_hooks("functions_fetch_ban_times", $ban_times);
8047  
8048      $ban_times['---'] = $lang->permanent;
8049      return $ban_times;
8050  }
8051  
8052  /**
8053   * Format a ban length in to a UNIX timestamp.
8054   *
8055   * @param string $date The ban length string
8056   * @param int $stamp The optional UNIX timestamp, if 0, current time is used.
8057   * @return int The UNIX timestamp when the ban will be lifted
8058   */
8059  function ban_date2timestamp($date, $stamp=0)
8060  {
8061      if($stamp == 0)
8062      {
8063          $stamp = TIME_NOW;
8064      }
8065      $d = explode('-', $date);
8066      $nowdate = date("H-j-n-Y", $stamp);
8067      $n = explode('-', $nowdate);
8068      $n[1] += $d[0];
8069      $n[2] += $d[1];
8070      $n[3] += $d[2];
8071      return mktime(date("G", $stamp), date("i", $stamp), 0, $n[2], $n[1], $n[3]);
8072  }
8073  
8074  /**
8075   * Expire old warnings in the database.
8076   *
8077   * @return bool
8078   */
8079  function expire_warnings()
8080  {
8081      global $warningshandler;
8082  
8083      if(!is_object($warningshandler))
8084      {
8085          require_once  MYBB_ROOT.'inc/datahandlers/warnings.php';
8086          $warningshandler = new WarningsHandler('update');
8087      }
8088  
8089      return $warningshandler->expire_warnings();
8090  }
8091  
8092  /**
8093   * Custom chmod function to fix problems with hosts who's server configurations screw up umasks
8094   *
8095   * @param string $file The file to chmod
8096   * @param string $mode The mode to chmod(i.e. 0666)
8097   * @return bool
8098   */
8099  function my_chmod($file, $mode)
8100  {
8101      // Passing $mode as an octal number causes strlen and substr to return incorrect values. Instead pass as a string
8102      if(substr($mode, 0, 1) != '0' || strlen($mode) !== 4)
8103      {
8104          return false;
8105      }
8106      $old_umask = umask(0);
8107  
8108      // We convert the octal string to a decimal number because passing a octal string doesn't work with chmod
8109      // and type casting subsequently removes the prepended 0 which is needed for octal numbers
8110      $result = chmod($file, octdec($mode));
8111      umask($old_umask);
8112      return $result;
8113  }
8114  
8115  /**
8116   * Custom rmdir function to loop through an entire directory and delete all files/folders within
8117   *
8118   * @param string $path The path to the directory
8119   * @param array $ignore Any files you wish to ignore (optional)
8120   * @return bool
8121   */
8122  function my_rmdir_recursive($path, $ignore=array())
8123  {
8124      global $orig_dir;
8125  
8126      if(!isset($orig_dir))
8127      {
8128          $orig_dir = $path;
8129      }
8130  
8131      if(@is_dir($path) && !@is_link($path))
8132      {
8133          if($dh = @opendir($path))
8134          {
8135              while(($file = @readdir($dh)) !== false)
8136              {
8137                  if($file == '.' || $file == '..' || $file == '.svn' || in_array($path.'/'.$file, $ignore) || !my_rmdir_recursive($path.'/'.$file))
8138                  {
8139                      continue;
8140                  }
8141              }
8142             @closedir($dh);
8143          }
8144  
8145          // Are we done? Don't delete the main folder too and return true
8146          if($path == $orig_dir)
8147          {
8148              return true;
8149          }
8150  
8151          return @rmdir($path);
8152      }
8153  
8154      return @unlink($path);
8155  }
8156  
8157  /**
8158   * Counts the number of subforums in a array([pid][disporder][fid]) starting from the pid
8159   *
8160   * @param array $array The array of forums
8161   * @return integer The number of sub forums
8162   */
8163  function subforums_count($array=array())
8164  {
8165      $count = 0;
8166      foreach($array as $array2)
8167      {
8168          $count += count($array2);
8169      }
8170  
8171      return $count;
8172  }
8173  
8174  /**
8175   * DEPRECATED! Please use IPv6 compatible my_inet_pton!
8176   * Fix for PHP's ip2long to guarantee a 32-bit signed integer value is produced (this is aimed
8177   * at 64-bit versions of PHP)
8178   *
8179   * @deprecated
8180   * @param string $ip The IP to convert
8181   * @return integer IP in 32-bit signed format
8182   */
8183  function my_ip2long($ip)
8184  {
8185      $ip_long = ip2long($ip);
8186  
8187      if(!$ip_long)
8188      {
8189          $ip_long = sprintf("%u", ip2long($ip));
8190  
8191          if(!$ip_long)
8192          {
8193              return 0;
8194          }
8195      }
8196  
8197      if($ip_long >= 2147483648) // Won't occur on 32-bit PHP
8198      {
8199          $ip_long -= 4294967296;
8200      }
8201  
8202      return $ip_long;
8203  }
8204  
8205  /**
8206   * DEPRECATED! Please use IPv6 compatible my_inet_ntop!
8207   * As above, fix for PHP's long2ip on 64-bit versions
8208   *
8209   * @deprecated
8210   * @param integer $long The IP to convert (will accept 64-bit IPs as well)
8211   * @return string IP in IPv4 format
8212   */
8213  function my_long2ip($long)
8214  {
8215      // On 64-bit machines is_int will return true. On 32-bit it will return false
8216      if($long < 0 && is_int(2147483648))
8217      {
8218          // We have a 64-bit system
8219          $long += 4294967296;
8220      }
8221      return long2ip($long);
8222  }
8223  
8224  /**
8225   * Converts a human readable IP address to its packed in_addr representation
8226   *
8227   * @param string $ip The IP to convert
8228   * @return string IP in 32bit or 128bit binary format
8229   */
8230  function my_inet_pton($ip)
8231  {
8232      if(function_exists('inet_pton'))
8233      {
8234          return @inet_pton($ip);
8235      }
8236      else
8237      {
8238          /**
8239           * Replace inet_pton()
8240           *
8241           * @category    PHP
8242           * @package     PHP_Compat
8243           * @license     LGPL - http://www.gnu.org/licenses/lgpl.html
8244           * @copyright   2004-2007 Aidan Lister <[email protected]>, Arpad Ray <[email protected]>
8245           * @link        http://php.net/inet_pton
8246           * @author      Arpad Ray <[email protected]>
8247           * @version     $Revision: 269597 $
8248           */
8249          $r = ip2long($ip);
8250          if($r !== false && $r != -1)
8251          {
8252              return pack('N', $r);
8253          }
8254  
8255          $delim_count = substr_count($ip, ':');
8256          if($delim_count < 1 || $delim_count > 7)
8257          {
8258              return false;
8259          }
8260  
8261          $r = explode(':', $ip);
8262          $rcount = count($r);
8263          if(($doub = array_search('', $r, 1)) !== false)
8264          {
8265              $length = (!$doub || $doub == $rcount - 1 ? 2 : 1);
8266              array_splice($r, $doub, $length, array_fill(0, 8 + $length - $rcount, 0));
8267          }
8268  
8269          $r = array_map('hexdec', $r);
8270          array_unshift($r, 'n*');
8271          $r = call_user_func_array('pack', $r);
8272  
8273          return $r;
8274      }
8275  }
8276  
8277  /**
8278   * Converts a packed internet address to a human readable representation
8279   *
8280   * @param string $ip IP in 32bit or 128bit binary format
8281   * @return string IP in human readable format
8282   */
8283  function my_inet_ntop($ip)
8284  {
8285      if(function_exists('inet_ntop'))
8286      {
8287          return @inet_ntop($ip);
8288      }
8289      else
8290      {
8291          /**
8292           * Replace inet_ntop()
8293           *
8294           * @category    PHP
8295           * @package     PHP_Compat
8296           * @license     LGPL - http://www.gnu.org/licenses/lgpl.html
8297           * @copyright   2004-2007 Aidan Lister <[email protected]>, Arpad Ray <[email protected]>
8298           * @link        http://php.net/inet_ntop
8299           * @author      Arpad Ray <[email protected]>
8300           * @version     $Revision: 269597 $
8301           */
8302          switch(strlen($ip))
8303          {
8304              case 4:
8305                  list(,$r) = unpack('N', $ip);
8306                  return long2ip($r);
8307              case 16:
8308                  $r = substr(chunk_split(bin2hex($ip), 4, ':'), 0, -1);
8309                  $r = preg_replace(
8310                      array('/(?::?\b0+\b:?){2,}/', '/\b0+([^0])/e'),
8311                      array('::', '(int)"$1"?"$1":"0$1"'),
8312                      $r);
8313                  return $r;
8314          }
8315          return false;
8316      }
8317  }
8318  
8319  /**
8320   * Fetch an binary formatted range for searching IPv4 and IPv6 IP addresses.
8321   *
8322   * @param string $ipaddress The IP address to convert to a range
8323   * @return string|array|bool If a full IP address is provided, the in_addr representation, otherwise an array of the upper & lower extremities of the IP. False on failure
8324   */
8325  function fetch_ip_range($ipaddress)
8326  {
8327      // Wildcard
8328      if(strpos($ipaddress, '*') !== false)
8329      {
8330          if(strpos($ipaddress, ':') !== false)
8331          {
8332              // IPv6
8333              $upper = str_replace('*', 'ffff', $ipaddress);
8334              $lower = str_replace('*', '0', $ipaddress);
8335          }
8336          else
8337          {
8338              // IPv4
8339              $ip_bits = count(explode('.', $ipaddress));
8340              if($ip_bits < 4)
8341              {
8342                  // Support for 127.0.*
8343                  $replacement = str_repeat('.*', 4-$ip_bits);
8344                  $ipaddress = substr_replace($ipaddress, $replacement, strrpos($ipaddress, '*')+1, 0);
8345              }
8346              $upper = str_replace('*', '255', $ipaddress);
8347              $lower = str_replace('*', '0', $ipaddress);
8348          }
8349          $upper = my_inet_pton($upper);
8350          $lower = my_inet_pton($lower);
8351          if($upper === false || $lower === false)
8352          {
8353              return false;
8354          }
8355          return array($lower, $upper);
8356      }
8357      // CIDR notation
8358      elseif(strpos($ipaddress, '/') !== false)
8359      {
8360          $ipaddress = explode('/', $ipaddress);
8361          $ip_address = $ipaddress[0];
8362          $ip_range = (int)$ipaddress[1];
8363  
8364          if(empty($ip_address) || empty($ip_range))
8365          {
8366              // Invalid input
8367              return false;
8368          }
8369          else
8370          {
8371              $ip_address = my_inet_pton($ip_address);
8372  
8373              if(!$ip_address)
8374              {
8375                  // Invalid IP address
8376                  return false;
8377              }
8378          }
8379  
8380          /**
8381           * Taken from: https://github.com/NewEraCracker/php_work/blob/master/ipRangeCalculate.php
8382           * Author: NewEraCracker
8383           * License: Public Domain
8384           */
8385  
8386          // Pack IP, Set some vars
8387          $ip_pack = $ip_address;
8388          $ip_pack_size = strlen($ip_pack);
8389          $ip_bits_size = $ip_pack_size*8;
8390  
8391          // IP bits (lots of 0's and 1's)
8392          $ip_bits = '';
8393          for($i = 0; $i < $ip_pack_size; $i = $i+1)
8394          {
8395              $bit = decbin(ord($ip_pack[$i]));
8396              $bit = str_pad($bit, 8, '0', STR_PAD_LEFT);
8397              $ip_bits .= $bit;
8398          }
8399  
8400          // Significative bits (from the ip range)
8401          $ip_bits = substr($ip_bits, 0, $ip_range);
8402  
8403          // Some calculations
8404          $ip_lower_bits = str_pad($ip_bits, $ip_bits_size, '0', STR_PAD_RIGHT);
8405          $ip_higher_bits = str_pad($ip_bits, $ip_bits_size, '1', STR_PAD_RIGHT);
8406  
8407          // Lower IP
8408          $ip_lower_pack = '';
8409          for($i=0; $i < $ip_bits_size; $i=$i+8)
8410          {
8411              $chr = substr($ip_lower_bits, $i, 8);
8412              $chr = chr(bindec($chr));
8413              $ip_lower_pack .= $chr;
8414          }
8415  
8416          // Higher IP
8417          $ip_higher_pack = '';
8418          for($i=0; $i < $ip_bits_size; $i=$i+8)
8419          {
8420              $chr = substr($ip_higher_bits, $i, 8);
8421              $chr = chr( bindec($chr) );
8422              $ip_higher_pack .= $chr;
8423          }
8424  
8425          return array($ip_lower_pack, $ip_higher_pack);
8426      }
8427      // Just on IP address
8428      else
8429      {
8430          return my_inet_pton($ipaddress);
8431      }
8432  }
8433  
8434  /**
8435   * Time how long it takes for a particular piece of code to run. Place calls above & below the block of code.
8436   *
8437   * @return float|void The time taken
8438   */
8439  function get_execution_time()
8440  {
8441      static $time_start;
8442  
8443      $time = microtime(true);
8444  
8445      // Just starting timer, init and return
8446      if(!$time_start)
8447      {
8448          $time_start = $time;
8449          return;
8450      }
8451      // Timer has run, return execution time
8452      else
8453      {
8454          $total = $time-$time_start;
8455          if($total < 0) $total = 0;
8456          $time_start = 0;
8457          return $total;
8458      }
8459  }
8460  
8461  /**
8462   * Processes a checksum list on MyBB files and returns a result set
8463   *
8464   * @param string $path The base path
8465   * @param int $count The count of files
8466   * @return array The bad files
8467   */
8468  function verify_files($path=MYBB_ROOT, $count=0)
8469  {
8470      global $mybb, $checksums, $bad_verify_files;
8471  
8472      // We don't need to check these types of files
8473      $ignore = array(".", "..", ".svn", "config.php", "settings.php", "Thumb.db", "config.default.php", "lock", "htaccess.txt", "htaccess-nginx.txt", "logo.gif", "logo.png");
8474      $ignore_ext = array("attach");
8475  
8476      if(substr($path, -1, 1) == "/")
8477      {
8478          $path = substr($path, 0, -1);
8479      }
8480  
8481      if(!is_array($bad_verify_files))
8482      {
8483          $bad_verify_files = array();
8484      }
8485  
8486      // Make sure that we're in a directory and it's not a symbolic link
8487      if(@is_dir($path) && !@is_link($path))
8488      {
8489          if($dh = @opendir($path))
8490          {
8491              // Loop through all the files/directories in this directory
8492              while(($file = @readdir($dh)) !== false)
8493              {
8494                  if(in_array($file, $ignore) || in_array(get_extension($file), $ignore_ext))
8495                  {
8496                      continue;
8497                  }
8498  
8499                  // Recurse through the directory tree
8500                  if(is_dir($path."/".$file))
8501                  {
8502                      verify_files($path."/".$file, ($count+1));
8503                      continue;
8504                  }
8505  
8506                  // We only need the last part of the path (from the MyBB directory to the file. i.e. inc/functions.php)
8507                  $file_path = ".".str_replace(substr(MYBB_ROOT, 0, -1), "", $path)."/".$file;
8508  
8509                  // Does this file even exist in our official list? Perhaps it's a plugin
8510                  if(array_key_exists($file_path, $checksums))
8511                  {
8512                      $filename = $path."/".$file;
8513                      $handle = fopen($filename, "rb");
8514                      $hashingContext = hash_init('sha512');
8515                      while(!feof($handle))
8516                      {
8517                          hash_update($hashingContext, fread($handle, 8192));
8518                      }
8519                      fclose($handle);
8520  
8521                      $checksum = hash_final($hashingContext);
8522  
8523                      // Does it match any of our hashes (unix/windows new lines taken into consideration with the hashes)
8524                      if(!in_array($checksum, $checksums[$file_path]))
8525                      {
8526                          $bad_verify_files[] = array("status" => "changed", "path" => $file_path);
8527                      }
8528                  }
8529                  unset($checksums[$file_path]);
8530              }
8531             @closedir($dh);
8532          }
8533      }
8534  
8535      if($count == 0)
8536      {
8537          if(!empty($checksums))
8538          {
8539              foreach($checksums as $file_path => $hashes)
8540              {
8541                  if(in_array(basename($file_path), $ignore))
8542                  {
8543                      continue;
8544                  }
8545                  $bad_verify_files[] = array("status" => "missing", "path" => $file_path);
8546              }
8547          }
8548      }
8549  
8550      // uh oh
8551      if($count == 0)
8552      {
8553          return $bad_verify_files;
8554      }
8555  }
8556  
8557  /**
8558   * Returns a signed value equal to an integer
8559   *
8560   * @param int $int The integer
8561   * @return string The signed equivalent
8562   */
8563  function signed($int)
8564  {
8565      if($int < 0)
8566      {
8567          return "$int";
8568      }
8569      else
8570      {
8571          return "+$int";
8572      }
8573  }
8574  
8575  /**
8576   * Returns a securely generated seed
8577   *
8578   * @return string A secure binary seed
8579   */
8580  function secure_binary_seed_rng($bytes)
8581  {
8582      $output = null;
8583  
8584      if(version_compare(PHP_VERSION, '7.0', '>='))
8585      {
8586          try
8587          {
8588              $output = random_bytes($bytes);
8589          } catch (Exception $e) {
8590          }
8591      }
8592  
8593      if(strlen($output) < $bytes)
8594      {
8595          if(@is_readable('/dev/urandom') && ($handle = @fopen('/dev/urandom', 'rb')))
8596          {
8597              $output = @fread($handle, $bytes);
8598              @fclose($handle);
8599          }
8600      }
8601      else
8602      {
8603          return $output;
8604      }
8605  
8606      if(strlen($output) < $bytes)
8607      {
8608          if(function_exists('mcrypt_create_iv'))
8609          {
8610              if (DIRECTORY_SEPARATOR == '/')
8611              {
8612                  $source = MCRYPT_DEV_URANDOM;
8613              }
8614              else
8615              {
8616                  $source = MCRYPT_RAND;
8617              }
8618  
8619              $output = @mcrypt_create_iv($bytes, $source);
8620          }
8621      }
8622      else
8623      {
8624          return $output;
8625      }
8626  
8627      if(strlen($output) < $bytes)
8628      {
8629          if(function_exists('openssl_random_pseudo_bytes'))
8630          {
8631              // PHP <5.3.4 had a bug which makes that function unusable on Windows
8632              if ((DIRECTORY_SEPARATOR == '/') || version_compare(PHP_VERSION, '5.3.4', '>='))
8633              {
8634                  $output = openssl_random_pseudo_bytes($bytes, $crypto_strong);
8635                  if ($crypto_strong == false)
8636                  {
8637                      $output = null;
8638                  }
8639              }
8640          }
8641      }
8642      else
8643      {
8644          return $output;
8645      }
8646  
8647      if(strlen($output) < $bytes)
8648      {
8649          if(class_exists('COM'))
8650          {
8651              try
8652              {
8653                  $CAPI_Util = new COM('CAPICOM.Utilities.1');
8654                  if(is_callable(array($CAPI_Util, 'GetRandom')))
8655                  {
8656                      $output = $CAPI_Util->GetRandom($bytes, 0);
8657                  }
8658              } catch (Exception $e) {
8659              }
8660          }
8661      }
8662      else
8663      {
8664          return $output;
8665      }
8666  
8667      if(strlen($output) < $bytes)
8668      {
8669          // Close to what PHP basically uses internally to seed, but not quite.
8670          $unique_state = microtime().@getmypid();
8671  
8672          $rounds = ceil($bytes / 16);
8673  
8674          for($i = 0; $i < $rounds; $i++)
8675          {
8676              $unique_state = md5(microtime().$unique_state);
8677              $output .= md5($unique_state);
8678          }
8679  
8680          $output = substr($output, 0, ($bytes * 2));
8681  
8682          $output = pack('H*', $output);
8683  
8684          return $output;
8685      }
8686      else
8687      {
8688          return $output;
8689      }
8690  }
8691  
8692  /**
8693   * Returns a securely generated seed integer
8694   *
8695   * @return int An integer equivalent of a secure hexadecimal seed
8696   */
8697  function secure_seed_rng()
8698  {
8699      $bytes = PHP_INT_SIZE;
8700  
8701      do
8702      {
8703  
8704          $output = secure_binary_seed_rng($bytes);
8705  
8706          // convert binary data to a decimal number
8707          if ($bytes == 4)
8708          {
8709              $elements = unpack('i', $output);
8710              $output = abs($elements[1]);
8711          }
8712          else
8713          {
8714              $elements = unpack('N2', $output);
8715              $output = abs($elements[1] << 32 | $elements[2]);
8716          }
8717  
8718      } while($output > PHP_INT_MAX);
8719  
8720      return $output;
8721  }
8722  
8723  /**
8724   * Generates a cryptographically secure random number.
8725   *
8726   * @param int $min Optional lowest value to be returned (default: 0)
8727   * @param int $max Optional highest value to be returned (default: PHP_INT_MAX)
8728   */
8729  function my_rand($min=0, $max=PHP_INT_MAX)
8730  {
8731      // backward compatibility
8732      if($min === null || $max === null || $max < $min)
8733      {
8734          $min = 0;
8735          $max = PHP_INT_MAX;
8736      }
8737  
8738      if(version_compare(PHP_VERSION, '7.0', '>='))
8739      {
8740          try
8741          {
8742              $result = random_int($min, $max);
8743          } catch (Exception $e) {
8744          }
8745  
8746          if(isset($result))
8747          {
8748              return $result;
8749          }
8750      }
8751  
8752      $seed = secure_seed_rng();
8753  
8754      $distance = $max - $min;
8755      return $min + floor($distance * ($seed / PHP_INT_MAX) );
8756  }
8757  
8758  /**
8759   * More robust version of PHP's trim() function. It includes a list of UTF-8 blank characters
8760   * from http://kb.mozillazine.org/Network.IDN.blacklist_chars
8761   *
8762   * @param string $string The string to trim from
8763   * @param string $charlist Optional. The stripped characters can also be specified using the charlist parameter
8764   * @return string The trimmed string
8765   */
8766  function trim_blank_chrs($string, $charlist="")
8767  {
8768      $hex_chrs = array(
8769          0x09 => 1, // \x{0009}
8770          0x0A => 1, // \x{000A}
8771          0x0B => 1, // \x{000B}
8772          0x0D => 1, // \x{000D}
8773          0x20 => 1, // \x{0020}
8774          0xC2 => array(0x81 => 1, 0x8D => 1, 0x90 => 1, 0x9D => 1, 0xA0 => 1, 0xAD => 1), // \x{0081}, \x{008D}, \x{0090}, \x{009D}, \x{00A0}, \x{00AD}
8775          0xCC => array(0xB7 => 1, 0xB8 => 1), // \x{0337}, \x{0338}
8776          0xE1 => array(0x85 => array(0x9F => 1, 0xA0 => 1), 0x9A => array(0x80 => 1), 0xA0 => array(0x8E => 1)), // \x{115F}, \x{1160}, \x{1680}, \x{180E}
8777          0xE2 => array(0x80 => array(0x80 => 1, 0x81 => 1, 0x82 => 1, 0x83 => 1, 0x84 => 1, 0x85 => 1, 0x86 => 1, 0x87 => 1, 0x88 => 1, 0x89 => 1, 0x8A => 1, 0x8B => 1, 0x8C => 1, 0x8D => 1, 0x8E => 1, 0x8F => 1, // \x{2000} - \x{200F}
8778              0xA8 => 1, 0xA9 => 1, 0xAA => 1, 0xAB => 1, 0xAC => 1, 0xAD => 1, 0xAE => 1, 0xAF => 1), // \x{2028} - \x{202F}
8779              0x81 => array(0x9F => 1)), // \x{205F}
8780          0xE3 => array(0x80 => array(0x80 => 1), // \x{3000}
8781              0x85 => array(0xA4 => 1)), // \x{3164}
8782          0xEF => array(0xBB => array(0xBF => 1), // \x{FEFF}
8783              0xBE => array(0xA0 => 1), // \x{FFA0}
8784              0xBF => array(0xB9 => 1, 0xBA => 1, 0xBB => 1)), // \x{FFF9} - \x{FFFB}
8785      );
8786  
8787      $hex_chrs_rev = array(
8788          0x09 => 1, // \x{0009}
8789          0x0A => 1, // \x{000A}
8790          0x0B => 1, // \x{000B}
8791          0x0D => 1, // \x{000D}
8792          0x20 => 1, // \x{0020}
8793          0x81 => array(0xC2 => 1, 0x80 => array(0xE2 => 1)), // \x{0081}, \x{2001}
8794          0x8D => array(0xC2 => 1, 0x80 => array(0xE2 => 1)), // \x{008D}, \x{200D}
8795          0x90 => array(0xC2 => 1), // \x{0090}
8796          0x9D => array(0xC2 => 1), // \x{009D}
8797          0xA0 => array(0xC2 => 1, 0x85 => array(0xE1 => 1), 0x81 => array(0xE2 => 1), 0xBE => array(0xEF => 1)), // \x{00A0}, \x{1160}, \x{2060}, \x{FFA0}
8798          0xAD => array(0xC2 => 1, 0x80 => array(0xE2 => 1)), // \x{00AD}, \x{202D}
8799          0xB8 => array(0xCC => 1), // \x{0338}
8800          0xB7 => array(0xCC => 1), // \x{0337}
8801          0x9F => array(0x85 => array(0xE1 => 1), 0x81 => array(0xE2 => 1)), // \x{115F}, \x{205F}
8802          0x80 => array(0x9A => array(0xE1 => 1), 0x80 => array(0xE2 => 1, 0xE3 => 1)), // \x{1680}, \x{2000}, \x{3000}
8803          0x8E => array(0xA0 => array(0xE1 => 1), 0x80 => array(0xE2 => 1)), // \x{180E}, \x{200E}
8804          0x82 => array(0x80 => array(0xE2 => 1)), // \x{2002}
8805          0x83 => array(0x80 => array(0xE2 => 1)), // \x{2003}
8806          0x84 => array(0x80 => array(0xE2 => 1)), // \x{2004}
8807          0x85 => array(0x80 => array(0xE2 => 1)), // \x{2005}
8808          0x86 => array(0x80 => array(0xE2 => 1)), // \x{2006}
8809          0x87 => array(0x80 => array(0xE2 => 1)), // \x{2007}
8810          0x88 => array(0x80 => array(0xE2 => 1)), // \x{2008}
8811          0x89 => array(0x80 => array(0xE2 => 1)), // \x{2009}
8812          0x8A => array(0x80 => array(0xE2 => 1)), // \x{200A}
8813          0x8B => array(0x80 => array(0xE2 => 1)), // \x{200B}
8814          0x8C => array(0x80 => array(0xE2 => 1)), // \x{200C}
8815          0x8F => array(0x80 => array(0xE2 => 1)), // \x{200F}
8816          0xA8 => array(0x80 => array(0xE2 => 1)), // \x{2028}
8817          0xA9 => array(0x80 => array(0xE2 => 1)), // \x{2029}
8818          0xAA => array(0x80 => array(0xE2 => 1)), // \x{202A}
8819          0xAB => array(0x80 => array(0xE2 => 1)), // \x{202B}
8820          0xAC => array(0x80 => array(0xE2 => 1)), // \x{202C}
8821          0xAE => array(0x80 => array(0xE2 => 1)), // \x{202E}
8822          0xAF => array(0x80 => array(0xE2 => 1)), // \x{202F}
8823          0xA4 => array(0x85 => array(0xE3 => 1)), // \x{3164}
8824          0xBF => array(0xBB => array(0xEF => 1)), // \x{FEFF}
8825          0xB9 => array(0xBF => array(0xEF => 1)), // \x{FFF9}
8826          0xBA => array(0xBF => array(0xEF => 1)), // \x{FFFA}
8827          0xBB => array(0xBF => array(0xEF => 1)), // \x{FFFB}
8828      );
8829  
8830      // Start from the beginning and work our way in
8831      $i = 0;
8832      do
8833      {
8834          // Check to see if we have matched a first character in our utf-8 array
8835          $offset = match_sequence($string, $hex_chrs);
8836          if(!$offset)
8837          {
8838              // If not, then we must have a "good" character and we don't need to do anymore processing
8839              break;
8840          }
8841          $string = substr($string, $offset);
8842      }
8843      while(++$i);
8844  
8845      // Start from the end and work our way in
8846      $string = strrev($string);
8847      $i = 0;
8848      do
8849      {
8850          // Check to see if we have matched a first character in our utf-8 array
8851          $offset = match_sequence($string, $hex_chrs_rev);
8852          if(!$offset)
8853          {
8854              // If not, then we must have a "good" character and we don't need to do anymore processing
8855              break;
8856          }
8857          $string = substr($string, $offset);
8858      }
8859      while(++$i);
8860      $string = strrev($string);
8861  
8862      if($charlist)
8863      {
8864          $string = trim($string, $charlist);
8865      }
8866      else
8867      {
8868          $string = trim($string);
8869      }
8870  
8871      return $string;
8872  }
8873  
8874  /**
8875   * Match a sequence
8876   *
8877   * @param string $string The string to match from
8878   * @param array $array The array to match from
8879   * @param int $i Number in the string
8880   * @param int $n Number of matches
8881   * @return int The number matched
8882   */
8883  function match_sequence($string, $array, $i=0, $n=0)
8884  {
8885      if($string === "")
8886      {
8887          return 0;
8888      }
8889  
8890      $ord = ord($string[$i]);
8891      if(array_key_exists($ord, $array))
8892      {
8893          $level = $array[$ord];
8894          ++$n;
8895          if(is_array($level))
8896          {
8897              ++$i;
8898              return match_sequence($string, $level, $i, $n);
8899          }
8900          return $n;
8901      }
8902  
8903      return 0;
8904  }
8905  
8906  /**
8907   * Obtain the version of GD installed.
8908   *
8909   * @return float|null Version of GD
8910   */
8911  function gd_version()
8912  {
8913      static $gd_version;
8914  
8915      if($gd_version)
8916      {
8917          return $gd_version;
8918      }
8919  
8920      if(!extension_loaded('gd'))
8921      {
8922          return null;
8923      }
8924  
8925      if(function_exists("gd_info"))
8926      {
8927          $gd_info = gd_info();
8928          preg_match('/\d/', $gd_info['GD Version'], $gd);
8929          $gd_version = $gd[0];
8930      }
8931      else
8932      {
8933          ob_start();
8934          phpinfo(8);
8935          $info = ob_get_contents();
8936          ob_end_clean();
8937          $info = stristr($info, 'gd version');
8938          preg_match('/\d/', $info, $gd);
8939          $gd_version = $gd[0];
8940      }
8941  
8942      return $gd_version;
8943  }
8944  
8945  /*
8946   * Validates an UTF-8 string.
8947   *
8948   * @param string $input The string to be checked
8949   * @param boolean $allow_mb4 Allow 4 byte UTF-8 characters?
8950   * @param boolean $return Return the cleaned string?
8951   * @return string|boolean Cleaned string or boolean
8952   */
8953  function validate_utf8_string($input, $allow_mb4=true, $return=true)
8954  {
8955      // Valid UTF-8 sequence?
8956      if(!preg_match('##u', $input))
8957      {
8958          $string = '';
8959          $len = strlen($input);
8960          for($i = 0; $i < $len; $i++)
8961          {
8962              $c = ord($input[$i]);
8963              if($c > 128)
8964              {
8965                  if($c > 247 || $c <= 191)
8966                  {
8967                      if($return)
8968                      {
8969                          $string .= '?';
8970                          continue;
8971                      }
8972                      else
8973                      {
8974                          return false;
8975                      }
8976                  }
8977                  elseif($c > 239)
8978                  {
8979                      $bytes = 4;
8980                  }
8981                  elseif($c > 223)
8982                  {
8983                      $bytes = 3;
8984                  }
8985                  elseif($c > 191)
8986                  {
8987                      $bytes = 2;
8988                  }
8989                  if(($i + $bytes) > $len)
8990                  {
8991                      if($return)
8992                      {
8993                          $string .= '?';
8994                          break;
8995                      }
8996                      else
8997                      {
8998                          return false;
8999                      }
9000                  }
9001                  $valid = true;
9002                  $multibytes = $input[$i];
9003                  while($bytes > 1)
9004                  {
9005                      $i++;
9006                      $b = ord($input[$i]);
9007                      if($b < 128 || $b > 191)
9008                      {
9009                          if($return)
9010                          {
9011                              $valid = false;
9012                              $string .= '?';
9013                              break;
9014                          }
9015                          else
9016                          {
9017                              return false;
9018                          }
9019                      }
9020                      else
9021                      {
9022                          $multibytes .= $input[$i];
9023                      }
9024                      $bytes--;
9025                  }
9026                  if($valid)
9027                  {
9028                      $string .= $multibytes;
9029                  }
9030              }
9031              else
9032              {
9033                  $string .= $input[$i];
9034              }
9035          }
9036          $input = $string;
9037      }
9038      if($return)
9039      {
9040          if($allow_mb4)
9041          {
9042              return $input;
9043          }
9044          else
9045          {
9046              return preg_replace("#[^\\x00-\\x7F][\\x80-\\xBF]{3,}#", '?', $input);
9047          }
9048      }
9049      else
9050      {
9051          if($allow_mb4)
9052          {
9053              return true;
9054          }
9055          else
9056          {
9057              return !preg_match("#[^\\x00-\\x7F][\\x80-\\xBF]{3,}#", $input);
9058          }
9059      }
9060  }
9061  
9062  /**
9063   * Send a Private Message to a user.
9064   *
9065   * @param array $pm Array containing: 'subject', 'message', 'touid' and 'receivepms' (the latter should reflect the value found in the users table: receivepms and receivefrombuddy)
9066   * @param int $fromid Sender UID (0 if you want to use $mybb->user['uid'] or -1 to use MyBB Engine)
9067   * @param bool $admin_override Whether or not do override user defined options for receiving PMs
9068   * @return bool True if PM sent
9069   */
9070  function send_pm($pm, $fromid = 0, $admin_override=false)
9071  {
9072      global $lang, $mybb, $db, $session;
9073  
9074      if($mybb->settings['enablepms'] == 0)
9075      {
9076          return false;
9077      }
9078  
9079      if(!is_array($pm))
9080      {
9081          return false;
9082      }
9083  
9084      if(isset($pm['language']))
9085      {
9086          if($pm['language'] != $mybb->user['language'] && $lang->language_exists($pm['language']))
9087          {
9088              // Load user language
9089              $lang->set_language($pm['language']);
9090              $lang->load($pm['language_file']);
9091  
9092              $revert = true;
9093          }
9094  
9095          foreach(array('subject', 'message') as $key)
9096          {
9097              if(is_array($pm[$key]))
9098              {
9099                  $lang_string = $lang->{$pm[$key][0]};
9100                  $num_args = count($pm[$key]);
9101  
9102                  for($i = 1; $i < $num_args; $i++)
9103                  {
9104                      $lang_string = str_replace('{'.$i.'}', $pm[$key][$i], $lang_string);
9105                  }
9106              }
9107              else
9108              {
9109                  $lang_string = $lang->{$pm[$key]};
9110              }
9111  
9112              $pm[$key] = $lang_string;
9113          }
9114  
9115          if(isset($revert))
9116          {
9117              // Revert language
9118              $lang->set_language($mybb->user['language']);
9119              $lang->load($pm['language_file']);
9120          }
9121      }
9122  
9123      if(empty($pm['subject']) || empty($pm['message']) || empty($pm['touid']) || (empty($pm['receivepms']) && !$admin_override))
9124      {
9125          return false;
9126      }
9127  
9128      require_once  MYBB_ROOT."inc/datahandlers/pm.php";
9129  
9130      $pmhandler = new PMDataHandler();
9131  
9132      $subject = $pm['subject'];
9133      $message = $pm['message'];
9134      $toid = $pm['touid'];
9135  
9136      // Our recipients
9137      if(is_array($toid))
9138      {
9139          $recipients_to = $toid;
9140      }
9141      else
9142      {
9143          $recipients_to = array($toid);
9144      }
9145  
9146      $recipients_bcc = array();
9147  
9148      // Workaround for eliminating PHP warnings in PHP 8. Ref: https://github.com/mybb/mybb/issues/4630#issuecomment-1369144163
9149      if(isset($pm['sender']['uid']) && $pm['sender']['uid'] === -1 && $fromid === -1)
9150      {
9151          $sender = array(
9152              "uid" => 0,
9153              "username" => ''
9154          );
9155      }
9156  
9157      // Determine user ID
9158      if((int)$fromid == 0)
9159      {
9160          $fromid = (int)$mybb->user['uid'];
9161      }
9162      elseif((int)$fromid < 0)
9163      {
9164          $fromid = 0;
9165      }
9166  
9167      // Build our final PM array
9168      $pm = array(
9169          "subject" => $subject,
9170          "message" => $message,
9171          "icon" => -1,
9172          "fromid" => $fromid,
9173          "toid" => $recipients_to,
9174          "bccid" => $recipients_bcc,
9175          "do" => '',
9176          "pmid" => ''
9177      );
9178  
9179      // (continued) Workaround for eliminating PHP warnings in PHP 8. Ref: https://github.com/mybb/mybb/issues/4630#issuecomment-1369144163
9180      if(isset($sender))
9181      {
9182          $pm['sender'] = $sender;
9183      }
9184  
9185      if(isset($session))
9186      {
9187          $pm['ipaddress'] = $session->packedip;
9188      }
9189  
9190      $pm['options'] = array(
9191          "disablesmilies" => 0,
9192          "savecopy" => 0,
9193          "readreceipt" => 0
9194      );
9195  
9196      $pm['saveasdraft'] = 0;
9197  
9198      // Admin override
9199      $pmhandler->admin_override = (int)$admin_override;
9200  
9201      $pmhandler->set_data($pm);
9202  
9203      if($pmhandler->validate_pm())
9204      {
9205          $pmhandler->insert_pm();
9206          return true;
9207      }
9208  
9209      return false;
9210  }
9211  
9212  /**
9213   * Log a user spam block from StopForumSpam (or other spam service providers...)
9214   *
9215   * @param string $username The username that the user was using.
9216   * @param string $email    The email address the user was using.
9217   * @param string $ip_address The IP addres of the user.
9218   * @param array  $data     An array of extra data to go with the block (eg: confidence rating).
9219   * @return bool Whether the action was logged successfully.
9220   */
9221  function log_spam_block($username = '', $email = '', $ip_address = '', $data = array())
9222  {
9223      global $db, $session;
9224  
9225      if(!is_array($data))
9226      {
9227          $data = array($data);
9228      }
9229  
9230      if(!$ip_address)
9231      {
9232          $ip_address = get_ip();
9233      }
9234  
9235      $ip_address = my_inet_pton($ip_address);
9236  
9237      $insert_array = array(
9238          'username'  => $db->escape_string(my_substr($username, 0, 120)),
9239          'email'     => $db->escape_string($email),
9240          'ipaddress' => $db->escape_binary($ip_address),
9241          'dateline'  => (int)TIME_NOW,
9242          'data'      => $db->escape_string(@my_serialize($data)),
9243      );
9244  
9245      return (bool)$db->insert_query('spamlog', $insert_array);
9246  }
9247  
9248  /**
9249   * Copy a file to the CDN.
9250   *
9251   * @param string $file_path     The path to the file to upload to the CDN.
9252   *
9253   * @param string $uploaded_path The path the file was uploaded to, reference parameter for when this may be needed.
9254   *
9255   * @return bool Whether the file was copied successfully.
9256   */
9257  function copy_file_to_cdn($file_path = '', &$uploaded_path = null)
9258  {
9259      global $mybb, $plugins;
9260  
9261      $success = false;
9262  
9263      $file_path = (string)$file_path;
9264  
9265      $real_file_path = realpath($file_path);
9266  
9267      $file_dir_path = dirname($real_file_path);
9268      $file_dir_path = str_replace(MYBB_ROOT, '', $file_dir_path);
9269      $file_dir_path = ltrim($file_dir_path, './\\');
9270  
9271      $file_name = basename($real_file_path);
9272  
9273      if(file_exists($file_path))
9274      {
9275  
9276          if(is_object($plugins))
9277          {
9278              $hook_args = array(
9279                  'file_path' => &$file_path,
9280                  'real_file_path' => &$real_file_path,
9281                  'file_name' => &$file_name,
9282                  'file_dir_path'    => &$file_dir_path
9283              );
9284              $plugins->run_hooks('copy_file_to_cdn_start', $hook_args);
9285          }
9286  
9287          if(!empty($mybb->settings['usecdn']) && !empty($mybb->settings['cdnpath']))
9288          {
9289              $cdn_path = rtrim($mybb->settings['cdnpath'], '/\\');
9290  
9291              if(substr($file_dir_path, 0, my_strlen(MYBB_ROOT)) == MYBB_ROOT)
9292              {
9293                  $file_dir_path = str_replace(MYBB_ROOT, '', $file_dir_path);
9294              }
9295  
9296              $cdn_upload_path = $cdn_path . DIRECTORY_SEPARATOR . $file_dir_path;
9297  
9298              if(!($dir_exists = is_dir($cdn_upload_path)))
9299              {
9300                  $dir_exists = @mkdir($cdn_upload_path, 0777, true);
9301              }
9302  
9303              if($dir_exists)
9304              {
9305                  if(($cdn_upload_path = realpath($cdn_upload_path)) !== false)
9306                  {
9307                      $success = @copy($file_path, $cdn_upload_path.DIRECTORY_SEPARATOR.$file_name);
9308  
9309                      if($success)
9310                      {
9311                          $uploaded_path = $cdn_upload_path;
9312                      }
9313                  }
9314              }
9315          }
9316  
9317          if(is_object($plugins))
9318          {
9319              $hook_args = array(
9320                  'file_path' => &$file_path,
9321                  'real_file_path' => &$real_file_path,
9322                  'file_name' => &$file_name,
9323                  'uploaded_path' => &$uploaded_path,
9324                  'success' => &$success,
9325              );
9326  
9327              $plugins->run_hooks('copy_file_to_cdn_end', $hook_args);
9328          }
9329      }
9330  
9331      return $success;
9332  }
9333  
9334  /**
9335   * Validate an url
9336   *
9337   * @param string $url The url to validate.
9338   * @param bool $relative_path Whether or not the url could be a relative path.
9339   * @param bool $allow_local Whether or not the url could be pointing to local networks.
9340   *
9341   * @return bool Whether this is a valid url.
9342   */
9343  function my_validate_url($url, $relative_path=false, $allow_local=false)
9344  {
9345      if($allow_local)
9346      {
9347          $regex = '_^(?:(?:https?|ftp)://)(?:\S+(?::\S*)?@)?(?:(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:localhost|(?:(?:[a-z\x{00a1}-\x{ffff}0-9]-*)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.(?:[a-z\x{00a1}-\x{ffff}0-9]-*)*[a-z\x{00a1}-\x{ffff}0-9]+)*(?:\.(?:[a-z\x{00a1}-\x{ffff}]{2,}))\.?))(?::\d{2,5})?(?:[/?#]\S*)?$_iuS';
9348      }
9349      else
9350      {
9351          $regex = '_^(?:(?:https?|ftp)://)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\x{00a1}-\x{ffff}0-9]-*)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.(?:[a-z\x{00a1}-\x{ffff}0-9]-*)*[a-z\x{00a1}-\x{ffff}0-9]+)*(?:\.(?:[a-z\x{00a1}-\x{ffff}]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?$_iuS';
9352      }
9353  
9354      if($relative_path && my_substr($url, 0, 1) == '/' || preg_match($regex, $url))
9355      {
9356          return true;
9357      }
9358      return false;
9359  }
9360  
9361  /**
9362   * Strip html tags from string, also removes <script> and <style> contents.
9363   *
9364   * @deprecated
9365   * @param  string $string         String to stripe
9366   * @param  string $allowable_tags Allowed html tags
9367   *
9368   * @return string                 Striped string
9369   */
9370  function my_strip_tags($string, $allowable_tags = '')
9371  {
9372      $pattern = array(
9373          '@(&lt;)style[^(&gt;)]*?(&gt;).*?(&lt;)/style(&gt;)@siu',
9374          '@(&lt;)script[^(&gt;)]*?.*?(&lt;)/script(&gt;)@siu',
9375          '@<style[^>]*?>.*?</style>@siu',
9376          '@<script[^>]*?.*?</script>@siu',
9377      );
9378      $string = preg_replace($pattern, '', $string);
9379      return strip_tags($string, $allowable_tags);
9380  }
9381  
9382  /**
9383   * Escapes a RFC 4180-compliant CSV string.
9384   * Based on https://github.com/Automattic/camptix/blob/f80725094440bf09861383b8f11e96c177c45789/camptix.php#L2867
9385   *
9386   * @param string $string The string to be escaped
9387   * @param boolean $escape_active_content Whether or not to escape active content trigger characters
9388   * @return string The escaped string
9389   */
9390  function my_escape_csv($string, $escape_active_content=true)
9391  {
9392      if($escape_active_content)
9393      {
9394          $active_content_triggers = array('=', '+', '-', '@');
9395          $delimiters = array(',', ';', ':', '|', '^', "\n", "\t", " ");
9396  
9397          $first_character = mb_substr($string, 0, 1);
9398  
9399          if(
9400              in_array($first_character, $active_content_triggers, true) ||
9401              in_array($first_character, $delimiters, true)
9402          )
9403          {
9404              $string = "'".$string;
9405          }
9406  
9407          foreach($delimiters as $delimiter)
9408          {
9409              foreach($active_content_triggers as $trigger)
9410              {
9411                  $string = str_replace($delimiter.$trigger, $delimiter."'".$trigger, $string);
9412              }
9413          }
9414      }
9415  
9416      $string = str_replace('"', '""', $string);
9417  
9418      return $string;
9419  }
9420  
9421  // Fallback function for 'array_column', PHP < 5.5.0 compatibility
9422  if(!function_exists('array_column'))
9423  {
9424  	function array_column($input, $column_key)
9425      {
9426          $values = array();
9427           if(!is_array($input))
9428          {
9429              $input = array($input);
9430          }
9431           foreach($input as $val)
9432          {
9433              if(is_array($val) && isset($val[$column_key]))
9434              {
9435                  $values[] = $val[$column_key];
9436              }
9437              elseif(is_object($val) && isset($val->$column_key))
9438              {
9439                  $values[] = $val->$column_key;
9440              }
9441          }
9442           return $values;
9443      }
9444  }
9445  
9446  /**
9447   * Performs a timing attack safe string comparison.
9448   *
9449   * @param string $known_string The first string to be compared.
9450   * @param string $user_string The second, user-supplied string to be compared.
9451   * @return bool Result of the comparison.
9452   */
9453  function my_hash_equals($known_string, $user_string)
9454  {
9455      if(version_compare(PHP_VERSION, '5.6.0', '>='))
9456      {
9457          return hash_equals($known_string, $user_string);
9458      }
9459      else
9460      {
9461          $known_string_length = my_strlen($known_string);
9462          $user_string_length = my_strlen($user_string);
9463  
9464          if($user_string_length != $known_string_length)
9465          {
9466              return false;
9467          }
9468  
9469          $result = 0;
9470  
9471          for($i = 0; $i < $known_string_length; $i++)
9472          {
9473              $result |= ord($known_string[$i]) ^ ord($user_string[$i]);
9474          }
9475  
9476          return $result === 0;
9477      }
9478  }
9479  
9480  /**
9481   * Retrieves all referrals for a specified user
9482   *
9483   * @param int uid
9484   * @param int start position
9485   * @param int total entries
9486   * @param bool false (default) only return display info, true for all info
9487   * @return array
9488   */
9489  function get_user_referrals($uid, $start=0, $limit=0, $full=false)
9490  {
9491      global $db;
9492  
9493      $referrals = $query_options = array();
9494      $uid = (int) $uid;
9495  
9496      if($uid === 0)
9497      {
9498          return $referrals;
9499      }
9500  
9501      if($start && $limit)
9502      {
9503          $query_options['limit_start'] = $start;
9504      }
9505  
9506      if($limit)
9507      {
9508          $query_options['limit'] = $limit;
9509      }
9510  
9511      $fields = 'uid, username, usergroup, displaygroup, regdate';
9512      if($full === true)
9513      {
9514          $fields = '*';
9515      }
9516  
9517      $query = $db->simple_select('users', $fields, "referrer='{$uid}'", $query_options);
9518  
9519      while($referral = $db->fetch_array($query))
9520      {
9521          $referrals[] = $referral;
9522      }
9523  
9524      return $referrals;
9525  }
9526  
9527  /**
9528   * Initialize the parser and store the XML data to be parsed.
9529   *
9530   * @param string $data
9531   * @return MyBBXMLParser The constructed XML parser.
9532   */
9533  function create_xml_parser($data)
9534  {
9535      if(version_compare(PHP_VERSION, '8.0', '>='))
9536      {
9537          require_once  MYBB_ROOT."inc/class_xmlparser.php";
9538  
9539          return new MyBBXMLParser($data);
9540      }
9541      else
9542      {
9543          require_once  MYBB_ROOT."inc/class_xml.php";
9544  
9545          return new XMLParser($data);
9546      }
9547  }
9548  
9549  /**
9550   * Make a filesystem path absolute.
9551   *
9552   * Returns as-is paths which are already absolute.
9553   *
9554   * @param string $path The input path. Can be either absolute or relative.
9555   * @param string $base The absolute base to which to append $path if $path is
9556   *                     relative. Must end in DIRECTORY_SEPARATOR or a forward
9557   *                     slash.
9558   * @return string An absolute filesystem path corresponding to the input path.
9559   */
9560  function mk_path_abs($path, $base = MYBB_ROOT)
9561  {
9562      $iswin = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';
9563      $char1 = my_substr($path, 0, 1);
9564      if($char1 != '/' && !($iswin && ($char1 == '\\' || preg_match('(^[a-zA-Z]:\\\\)', $path))))
9565      {
9566          $path = $base.$path;
9567      }
9568  
9569      return $path;
9570  }
9571  
9572  /**
9573   * Sets the maximum execution time to 0 (zero)
9574   *
9575   * @param int $seconds The maximum execution time, in seconds. If set to zero, no time limit is imposed.
9576   * @return bool Returns true if the maximum execution time was set, false if it was not
9577   */
9578  function my_set_time_limit($seconds = 0)
9579  {
9580      if(!function_exists('set_time_limit')) {
9581          return false;
9582      }
9583  
9584      if(strpos(ini_get('disable_functions'),'set_time_limit') !== false)
9585      {
9586          return false;
9587      }
9588  
9589      set_time_limit($seconds);
9590  
9591      return true;
9592  }


2005 - 2021 © MyBB.de | Alle Rechte vorbehalten! | Sponsor: netcup Cross-referenced by PHPXref