[ Index ]

PHP Cross Reference of MyBB 1.8.38

title

Body

[close]

/inc/ -> functions.php (source)

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


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