[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/includes/ -> functions.php (source)

   1  <?php
   2  /**
   3  *
   4  * @package phpBB3
   5  * @version $Id$
   6  * @copyright (c) 2005 phpBB Group
   7  * @license http://opensource.org/licenses/gpl-license.php GNU Public License
   8  *
   9  */
  10  
  11  /**
  12  * @ignore
  13  */
  14  if (!defined('IN_PHPBB'))
  15  {
  16      exit;
  17  }
  18  
  19  // Common global functions
  20  
  21  /**
  22  * set_var
  23  *
  24  * Set variable, used by {@link request_var the request_var function}
  25  *
  26  * @access private
  27  */
  28  function set_var(&$result, $var, $type, $multibyte = false)
  29  {
  30      settype($var, $type);
  31      $result = $var;
  32  
  33      if ($type == 'string')
  34      {
  35          $result = trim(htmlspecialchars(str_replace(array("\r\n", "\r", "\0"), array("\n", "\n", ''), $result), ENT_COMPAT, 'UTF-8'));
  36  
  37          if (!empty($result))
  38          {
  39              // Make sure multibyte characters are wellformed
  40              if ($multibyte)
  41              {
  42                  if (!preg_match('/^./u', $result))
  43                  {
  44                      $result = '';
  45                  }
  46              }
  47              else
  48              {
  49                  // no multibyte, allow only ASCII (0-127)
  50                  $result = preg_replace('/[\x80-\xFF]/', '?', $result);
  51              }
  52          }
  53  
  54          $result = (STRIP) ? stripslashes($result) : $result;
  55      }
  56  }
  57  
  58  /**
  59  * request_var
  60  *
  61  * Used to get passed variable
  62  */
  63  function request_var($var_name, $default, $multibyte = false, $cookie = false)
  64  {
  65      if (!$cookie && isset($_COOKIE[$var_name]))
  66      {
  67          if (!isset($_GET[$var_name]) && !isset($_POST[$var_name]))
  68          {
  69              return (is_array($default)) ? array() : $default;
  70          }
  71          $_REQUEST[$var_name] = isset($_POST[$var_name]) ? $_POST[$var_name] : $_GET[$var_name];
  72      }
  73  
  74      $super_global = ($cookie) ? '_COOKIE' : '_REQUEST';
  75      if (!isset($GLOBALS[$super_global][$var_name]) || is_array($GLOBALS[$super_global][$var_name]) != is_array($default))
  76      {
  77          return (is_array($default)) ? array() : $default;
  78      }
  79  
  80      $var = $GLOBALS[$super_global][$var_name];
  81      if (!is_array($default))
  82      {
  83          $type = gettype($default);
  84      }
  85      else
  86      {
  87          list($key_type, $type) = each($default);
  88          $type = gettype($type);
  89          $key_type = gettype($key_type);
  90          if ($type == 'array')
  91          {
  92              reset($default);
  93              $default = current($default);
  94              list($sub_key_type, $sub_type) = each($default);
  95              $sub_type = gettype($sub_type);
  96              $sub_type = ($sub_type == 'array') ? 'NULL' : $sub_type;
  97              $sub_key_type = gettype($sub_key_type);
  98          }
  99      }
 100  
 101      if (is_array($var))
 102      {
 103          $_var = $var;
 104          $var = array();
 105  
 106          foreach ($_var as $k => $v)
 107          {
 108              set_var($k, $k, $key_type);
 109              if ($type == 'array' && is_array($v))
 110              {
 111                  foreach ($v as $_k => $_v)
 112                  {
 113                      if (is_array($_v))
 114                      {
 115                          $_v = null;
 116                      }
 117                      set_var($_k, $_k, $sub_key_type, $multibyte);
 118                      set_var($var[$k][$_k], $_v, $sub_type, $multibyte);
 119                  }
 120              }
 121              else
 122              {
 123                  if ($type == 'array' || is_array($v))
 124                  {
 125                      $v = null;
 126                  }
 127                  set_var($var[$k], $v, $type, $multibyte);
 128              }
 129          }
 130      }
 131      else
 132      {
 133          set_var($var, $var, $type, $multibyte);
 134      }
 135  
 136      return $var;
 137  }
 138  
 139  /**
 140  * Sets a configuration option's value.
 141  *
 142  * Please note that this function does not update the is_dynamic value for
 143  * an already existing config option.
 144  *
 145  * @param string $config_name   The configuration option's name
 146  * @param string $config_value  New configuration value
 147  * @param bool   $is_dynamic    Whether this variable should be cached (false) or
 148  *                              if it changes too frequently (true) to be
 149  *                              efficiently cached.
 150  *
 151  * @return null
 152  */
 153  function set_config($config_name, $config_value, $is_dynamic = false)
 154  {
 155      global $db, $cache, $config;
 156  
 157      $sql = 'UPDATE ' . CONFIG_TABLE . "
 158          SET config_value = '" . $db->sql_escape($config_value) . "'
 159          WHERE config_name = '" . $db->sql_escape($config_name) . "'";
 160      $db->sql_query($sql);
 161  
 162      if (!$db->sql_affectedrows() && !isset($config[$config_name]))
 163      {
 164          $sql = 'INSERT INTO ' . CONFIG_TABLE . ' ' . $db->sql_build_array('INSERT', array(
 165              'config_name'    => $config_name,
 166              'config_value'    => $config_value,
 167              'is_dynamic'    => ($is_dynamic) ? 1 : 0));
 168          $db->sql_query($sql);
 169      }
 170  
 171      $config[$config_name] = $config_value;
 172  
 173      if (!$is_dynamic)
 174      {
 175          $cache->destroy('config');
 176      }
 177  }
 178  
 179  /**
 180  * Increments an integer config value directly in the database.
 181  *
 182  * @param string $config_name   The configuration option's name
 183  * @param int    $increment     Amount to increment by
 184  * @param bool   $is_dynamic    Whether this variable should be cached (false) or
 185  *                              if it changes too frequently (true) to be
 186  *                              efficiently cached.
 187  *
 188  * @return null
 189  */
 190  function set_config_count($config_name, $increment, $is_dynamic = false)
 191  {
 192      global $db, $cache;
 193  
 194      switch ($db->sql_layer)
 195      {
 196          case 'firebird':
 197              // Precision must be from 1 to 18
 198              $sql_update = 'CAST(CAST(config_value as DECIMAL(18, 0)) + ' . (int) $increment . ' as VARCHAR(255))';
 199          break;
 200  
 201          case 'postgres':
 202              // Need to cast to text first for PostgreSQL 7.x
 203              $sql_update = 'CAST(CAST(config_value::text as DECIMAL(255, 0)) + ' . (int) $increment . ' as VARCHAR(255))';
 204          break;
 205  
 206          // MySQL, SQlite, mssql, mssql_odbc, oracle
 207          default:
 208              $sql_update = 'config_value + ' . (int) $increment;
 209          break;
 210      }
 211  
 212      $db->sql_query('UPDATE ' . CONFIG_TABLE . ' SET config_value = ' . $sql_update . " WHERE config_name = '" . $db->sql_escape($config_name) . "'");
 213  
 214      if (!$is_dynamic)
 215      {
 216          $cache->destroy('config');
 217      }
 218  }
 219  
 220  /**
 221  * Generates an alphanumeric random string of given length
 222  *
 223  * @return string
 224  */
 225  function gen_rand_string($num_chars = 8)
 226  {
 227      // [a, z] + [0, 9] = 36
 228      return substr(strtoupper(base_convert(unique_id(), 16, 36)), 0, $num_chars);
 229  }
 230  
 231  /**
 232  * Generates a user-friendly alphanumeric random string of given length
 233  * We remove 0 and O so users cannot confuse those in passwords etc.
 234  *
 235  * @return string
 236  */
 237  function gen_rand_string_friendly($num_chars = 8)
 238  {
 239      $rand_str = unique_id();
 240  
 241      // Remove Z and Y from the base_convert(), replace 0 with Z and O with Y
 242      // [a, z] + [0, 9] - {z, y} = [a, z] + [0, 9] - {0, o} = 34
 243      $rand_str = str_replace(array('0', 'O'), array('Z', 'Y'), strtoupper(base_convert($rand_str, 16, 34)));
 244  
 245      return substr($rand_str, 0, $num_chars);
 246  }
 247  
 248  /**
 249  * Return unique id
 250  * @param string $extra additional entropy
 251  */
 252  function unique_id($extra = 'c')
 253  {
 254      static $dss_seeded = false;
 255      global $config;
 256  
 257      $val = $config['rand_seed'] . microtime();
 258      $val = md5($val);
 259      $config['rand_seed'] = md5($config['rand_seed'] . $val . $extra);
 260  
 261      if ($dss_seeded !== true && ($config['rand_seed_last_update'] < time() - rand(1,10)))
 262      {
 263          set_config('rand_seed_last_update', time(), true);
 264          set_config('rand_seed', $config['rand_seed'], true);
 265          $dss_seeded = true;
 266      }
 267  
 268      return substr($val, 4, 16);
 269  }
 270  
 271  /**
 272  * Wrapper for mt_rand() which allows swapping $min and $max parameters.
 273  *
 274  * PHP does not allow us to swap the order of the arguments for mt_rand() anymore.
 275  * (since PHP 5.3.4, see http://bugs.php.net/46587)
 276  *
 277  * @param int $min        Lowest value to be returned
 278  * @param int $max        Highest value to be returned
 279  *
 280  * @return int            Random integer between $min and $max (or $max and $min)
 281  */
 282  function phpbb_mt_rand($min, $max)
 283  {
 284      return ($min > $max) ? mt_rand($max, $min) : mt_rand($min, $max);
 285  }
 286  
 287  /**
 288  * Wrapper for getdate() which returns the equivalent array for UTC timestamps.
 289  *
 290  * @param int $time        Unix timestamp (optional)
 291  *
 292  * @return array            Returns an associative array of information related to the timestamp.
 293  *                        See http://www.php.net/manual/en/function.getdate.php
 294  */
 295  function phpbb_gmgetdate($time = false)
 296  {
 297      if ($time === false)
 298      {
 299          $time = time();
 300      }
 301  
 302      // getdate() interprets timestamps in local time.
 303      // What follows uses the fact that getdate() and
 304      // date('Z') balance each other out.
 305      return getdate($time - date('Z'));
 306  }
 307  
 308  /**
 309  * Return formatted string for filesizes
 310  *
 311  * @param mixed    $value            filesize in bytes
 312  *                                (non-negative number; int, float or string)
 313  * @param bool    $string_only    true if language string should be returned
 314  * @param array    $allowed_units    only allow these units (data array indexes)
 315  *
 316  * @return mixed                    data array if $string_only is false
 317  * @author bantu
 318  */
 319  function get_formatted_filesize($value, $string_only = true, $allowed_units = false)
 320  {
 321      global $user;
 322  
 323      $available_units = array(
 324          'tb' => array(
 325              'min'         => 1099511627776, // pow(2, 40)
 326              'index'        => 4,
 327              'si_unit'    => 'TB',
 328              'iec_unit'    => 'TIB',
 329          ),
 330          'gb' => array(
 331              'min'         => 1073741824, // pow(2, 30)
 332              'index'        => 3,
 333              'si_unit'    => 'GB',
 334              'iec_unit'    => 'GIB',
 335          ),
 336          'mb' => array(
 337              'min'        => 1048576, // pow(2, 20)
 338              'index'        => 2,
 339              'si_unit'    => 'MB',
 340              'iec_unit'    => 'MIB',
 341          ),
 342          'kb' => array(
 343              'min'        => 1024, // pow(2, 10)
 344              'index'        => 1,
 345              'si_unit'    => 'KB',
 346              'iec_unit'    => 'KIB',
 347          ),
 348          'b' => array(
 349              'min'        => 0,
 350              'index'        => 0,
 351              'si_unit'    => 'BYTES', // Language index
 352              'iec_unit'    => 'BYTES',  // Language index
 353          ),
 354      );
 355  
 356      foreach ($available_units as $si_identifier => $unit_info)
 357      {
 358          if (!empty($allowed_units) && $si_identifier != 'b' && !in_array($si_identifier, $allowed_units))
 359          {
 360              continue;
 361          }
 362  
 363          if ($value >= $unit_info['min'])
 364          {
 365              $unit_info['si_identifier'] = $si_identifier;
 366  
 367              break;
 368          }
 369      }
 370      unset($available_units);
 371  
 372      for ($i = 0; $i < $unit_info['index']; $i++)
 373      {
 374          $value /= 1024;
 375      }
 376      $value = round($value, 2);
 377  
 378      // Lookup units in language dictionary
 379      $unit_info['si_unit'] = (isset($user->lang[$unit_info['si_unit']])) ? $user->lang[$unit_info['si_unit']] : $unit_info['si_unit'];
 380      $unit_info['iec_unit'] = (isset($user->lang[$unit_info['iec_unit']])) ? $user->lang[$unit_info['iec_unit']] : $unit_info['iec_unit'];
 381  
 382      // Default to IEC
 383      $unit_info['unit'] = $unit_info['iec_unit'];
 384  
 385      if (!$string_only)
 386      {
 387          $unit_info['value'] = $value;
 388  
 389          return $unit_info;
 390      }
 391  
 392      return $value  . ' ' . $unit_info['unit'];
 393  }
 394  
 395  /**
 396  * Determine whether we are approaching the maximum execution time. Should be called once
 397  * at the beginning of the script in which it's used.
 398  * @return    bool    Either true if the maximum execution time is nearly reached, or false
 399  *                    if some time is still left.
 400  */
 401  function still_on_time($extra_time = 15)
 402  {
 403      static $max_execution_time, $start_time;
 404  
 405      $time = explode(' ', microtime());
 406      $current_time = $time[0] + $time[1];
 407  
 408      if (empty($max_execution_time))
 409      {
 410          $max_execution_time = (function_exists('ini_get')) ? (int) @ini_get('max_execution_time') : (int) @get_cfg_var('max_execution_time');
 411  
 412          // If zero, then set to something higher to not let the user catch the ten seconds barrier.
 413          if ($max_execution_time === 0)
 414          {
 415              $max_execution_time = 50 + $extra_time;
 416          }
 417  
 418          $max_execution_time = min(max(10, ($max_execution_time - $extra_time)), 50);
 419  
 420          // For debugging purposes
 421          // $max_execution_time = 10;
 422  
 423          global $starttime;
 424          $start_time = (empty($starttime)) ? $current_time : $starttime;
 425      }
 426  
 427      return (ceil($current_time - $start_time) < $max_execution_time) ? true : false;
 428  }
 429  
 430  /**
 431  *
 432  * @version Version 0.1 / slightly modified for phpBB 3.0.x (using $H$ as hash type identifier)
 433  *
 434  * Portable PHP password hashing framework.
 435  *
 436  * Written by Solar Designer <solar at openwall.com> in 2004-2006 and placed in
 437  * the public domain.
 438  *
 439  * There's absolutely no warranty.
 440  *
 441  * The homepage URL for this framework is:
 442  *
 443  *    http://www.openwall.com/phpass/
 444  *
 445  * Please be sure to update the Version line if you edit this file in any way.
 446  * It is suggested that you leave the main version number intact, but indicate
 447  * your project name (after the slash) and add your own revision information.
 448  *
 449  * Please do not change the "private" password hashing method implemented in
 450  * here, thereby making your hashes incompatible.  However, if you must, please
 451  * change the hash type identifier (the "$P$") to something different.
 452  *
 453  * Obviously, since this code is in the public domain, the above are not
 454  * requirements (there can be none), but merely suggestions.
 455  *
 456  *
 457  * Hash the password
 458  */
 459  function phpbb_hash($password)
 460  {
 461      $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
 462  
 463      $random_state = unique_id();
 464      $random = '';
 465      $count = 6;
 466  
 467      if (($fh = @fopen('/dev/urandom', 'rb')))
 468      {
 469          $random = fread($fh, $count);
 470          fclose($fh);
 471      }
 472  
 473      if (strlen($random) < $count)
 474      {
 475          $random = '';
 476  
 477          for ($i = 0; $i < $count; $i += 16)
 478          {
 479              $random_state = md5(unique_id() . $random_state);
 480              $random .= pack('H*', md5($random_state));
 481          }
 482          $random = substr($random, 0, $count);
 483      }
 484  
 485      $hash = _hash_crypt_private($password, _hash_gensalt_private($random, $itoa64), $itoa64);
 486  
 487      if (strlen($hash) == 34)
 488      {
 489          return $hash;
 490      }
 491  
 492      return md5($password);
 493  }
 494  
 495  /**
 496  * Check for correct password
 497  *
 498  * @param string $password The password in plain text
 499  * @param string $hash The stored password hash
 500  *
 501  * @return bool Returns true if the password is correct, false if not.
 502  */
 503  function phpbb_check_hash($password, $hash)
 504  {
 505      if (strlen($password) > 4096)
 506      {
 507          // If the password is too huge, we will simply reject it
 508          // and not let the server try to hash it.
 509          return false;
 510      }
 511  
 512      $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
 513      if (strlen($hash) == 34)
 514      {
 515          return (_hash_crypt_private($password, $hash, $itoa64) === $hash) ? true : false;
 516      }
 517  
 518      return (md5($password) === $hash) ? true : false;
 519  }
 520  
 521  /**
 522  * Generate salt for hash generation
 523  */
 524  function _hash_gensalt_private($input, &$itoa64, $iteration_count_log2 = 6)
 525  {
 526      if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31)
 527      {
 528          $iteration_count_log2 = 8;
 529      }
 530  
 531      $output = '$H$';
 532      $output .= $itoa64[min($iteration_count_log2 + ((PHP_VERSION >= 5) ? 5 : 3), 30)];
 533      $output .= _hash_encode64($input, 6, $itoa64);
 534  
 535      return $output;
 536  }
 537  
 538  /**
 539  * Encode hash
 540  */
 541  function _hash_encode64($input, $count, &$itoa64)
 542  {
 543      $output = '';
 544      $i = 0;
 545  
 546      do
 547      {
 548          $value = ord($input[$i++]);
 549          $output .= $itoa64[$value & 0x3f];
 550  
 551          if ($i < $count)
 552          {
 553              $value |= ord($input[$i]) << 8;
 554          }
 555  
 556          $output .= $itoa64[($value >> 6) & 0x3f];
 557  
 558          if ($i++ >= $count)
 559          {
 560              break;
 561          }
 562  
 563          if ($i < $count)
 564          {
 565              $value |= ord($input[$i]) << 16;
 566          }
 567  
 568          $output .= $itoa64[($value >> 12) & 0x3f];
 569  
 570          if ($i++ >= $count)
 571          {
 572              break;
 573          }
 574  
 575          $output .= $itoa64[($value >> 18) & 0x3f];
 576      }
 577      while ($i < $count);
 578  
 579      return $output;
 580  }
 581  
 582  /**
 583  * The crypt function/replacement
 584  */
 585  function _hash_crypt_private($password, $setting, &$itoa64)
 586  {
 587      $output = '*';
 588  
 589      // Check for correct hash
 590      if (substr($setting, 0, 3) != '$H$' && substr($setting, 0, 3) != '$P$')
 591      {
 592          return $output;
 593      }
 594  
 595      $count_log2 = strpos($itoa64, $setting[3]);
 596  
 597      if ($count_log2 < 7 || $count_log2 > 30)
 598      {
 599          return $output;
 600      }
 601  
 602      $count = 1 << $count_log2;
 603      $salt = substr($setting, 4, 8);
 604  
 605      if (strlen($salt) != 8)
 606      {
 607          return $output;
 608      }
 609  
 610      /**
 611      * We're kind of forced to use MD5 here since it's the only
 612      * cryptographic primitive available in all versions of PHP
 613      * currently in use.  To implement our own low-level crypto
 614      * in PHP would result in much worse performance and
 615      * consequently in lower iteration counts and hashes that are
 616      * quicker to crack (by non-PHP code).
 617      */
 618      if (PHP_VERSION >= 5)
 619      {
 620          $hash = md5($salt . $password, true);
 621          do
 622          {
 623              $hash = md5($hash . $password, true);
 624          }
 625          while (--$count);
 626      }
 627      else
 628      {
 629          $hash = pack('H*', md5($salt . $password));
 630          do
 631          {
 632              $hash = pack('H*', md5($hash . $password));
 633          }
 634          while (--$count);
 635      }
 636  
 637      $output = substr($setting, 0, 12);
 638      $output .= _hash_encode64($hash, 16, $itoa64);
 639  
 640      return $output;
 641  }
 642  
 643  /**
 644  * Hashes an email address to a big integer
 645  *
 646  * @param string $email        Email address
 647  *
 648  * @return string            Unsigned Big Integer
 649  */
 650  function phpbb_email_hash($email)
 651  {
 652      return sprintf('%u', crc32(strtolower($email))) . strlen($email);
 653  }
 654  
 655  /**
 656  * Wrapper for version_compare() that allows using uppercase A and B
 657  * for alpha and beta releases.
 658  *
 659  * See http://www.php.net/manual/en/function.version-compare.php
 660  *
 661  * @param string $version1        First version number
 662  * @param string $version2        Second version number
 663  * @param string $operator        Comparison operator (optional)
 664  *
 665  * @return mixed                    Boolean (true, false) if comparison operator is specified.
 666  *                                Integer (-1, 0, 1) otherwise.
 667  */
 668  function phpbb_version_compare($version1, $version2, $operator = null)
 669  {
 670      $version1 = strtolower($version1);
 671      $version2 = strtolower($version2);
 672  
 673      if (is_null($operator))
 674      {
 675          return version_compare($version1, $version2);
 676      }
 677      else
 678      {
 679          return version_compare($version1, $version2, $operator);
 680      }
 681  }
 682  
 683  /**
 684  * Global function for chmodding directories and files for internal use
 685  *
 686  * This function determines owner and group whom the file belongs to and user and group of PHP and then set safest possible file permissions.
 687  * The function determines owner and group from common.php file and sets the same to the provided file.
 688  * The function uses bit fields to build the permissions.
 689  * The function sets the appropiate execute bit on directories.
 690  *
 691  * Supported constants representing bit fields are:
 692  *
 693  * CHMOD_ALL - all permissions (7)
 694  * CHMOD_READ - read permission (4)
 695  * CHMOD_WRITE - write permission (2)
 696  * CHMOD_EXECUTE - execute permission (1)
 697  *
 698  * NOTE: The function uses POSIX extension and fileowner()/filegroup() functions. If any of them is disabled, this function tries to build proper permissions, by calling is_readable() and is_writable() functions.
 699  *
 700  * @param string    $filename    The file/directory to be chmodded
 701  * @param int    $perms        Permissions to set
 702  *
 703  * @return bool    true on success, otherwise false
 704  * @author faw, phpBB Group
 705  */
 706  function phpbb_chmod($filename, $perms = CHMOD_READ)
 707  {
 708      static $_chmod_info;
 709  
 710      // Return if the file no longer exists.
 711      if (!file_exists($filename))
 712      {
 713          return false;
 714      }
 715  
 716      // Determine some common vars
 717      if (empty($_chmod_info))
 718      {
 719          if (!function_exists('fileowner') || !function_exists('filegroup'))
 720          {
 721              // No need to further determine owner/group - it is unknown
 722              $_chmod_info['process'] = false;
 723          }
 724          else
 725          {
 726              global $phpbb_root_path, $phpEx;
 727  
 728              // Determine owner/group of common.php file and the filename we want to change here
 729              $common_php_owner = @fileowner($phpbb_root_path . 'common.' . $phpEx);
 730              $common_php_group = @filegroup($phpbb_root_path . 'common.' . $phpEx);
 731  
 732              // And the owner and the groups PHP is running under.
 733              $php_uid = (function_exists('posix_getuid')) ? @posix_getuid() : false;
 734              $php_gids = (function_exists('posix_getgroups')) ? @posix_getgroups() : false;
 735  
 736              // If we are unable to get owner/group, then do not try to set them by guessing
 737              if (!$php_uid || empty($php_gids) || !$common_php_owner || !$common_php_group)
 738              {
 739                  $_chmod_info['process'] = false;
 740              }
 741              else
 742              {
 743                  $_chmod_info = array(
 744                      'process'        => true,
 745                      'common_owner'    => $common_php_owner,
 746                      'common_group'    => $common_php_group,
 747                      'php_uid'        => $php_uid,
 748                      'php_gids'        => $php_gids,
 749                  );
 750              }
 751          }
 752      }
 753  
 754      if ($_chmod_info['process'])
 755      {
 756          $file_uid = @fileowner($filename);
 757          $file_gid = @filegroup($filename);
 758  
 759          // Change owner
 760          if (@chown($filename, $_chmod_info['common_owner']))
 761          {
 762              clearstatcache();
 763              $file_uid = @fileowner($filename);
 764          }
 765  
 766          // Change group
 767          if (@chgrp($filename, $_chmod_info['common_group']))
 768          {
 769              clearstatcache();
 770              $file_gid = @filegroup($filename);
 771          }
 772  
 773          // If the file_uid/gid now match the one from common.php we can process further, else we are not able to change something
 774          if ($file_uid != $_chmod_info['common_owner'] || $file_gid != $_chmod_info['common_group'])
 775          {
 776              $_chmod_info['process'] = false;
 777          }
 778      }
 779  
 780      // Still able to process?
 781      if ($_chmod_info['process'])
 782      {
 783          if ($file_uid == $_chmod_info['php_uid'])
 784          {
 785              $php = 'owner';
 786          }
 787          else if (in_array($file_gid, $_chmod_info['php_gids']))
 788          {
 789              $php = 'group';
 790          }
 791          else
 792          {
 793              // Since we are setting the everyone bit anyway, no need to do expensive operations
 794              $_chmod_info['process'] = false;
 795          }
 796      }
 797  
 798      // We are not able to determine or change something
 799      if (!$_chmod_info['process'])
 800      {
 801          $php = 'other';
 802      }
 803  
 804      // Owner always has read/write permission
 805      $owner = CHMOD_READ | CHMOD_WRITE;
 806      if (is_dir($filename))
 807      {
 808          $owner |= CHMOD_EXECUTE;
 809  
 810          // Only add execute bit to the permission if the dir needs to be readable
 811          if ($perms & CHMOD_READ)
 812          {
 813              $perms |= CHMOD_EXECUTE;
 814          }
 815      }
 816  
 817      switch ($php)
 818      {
 819          case 'owner':
 820              $result = @chmod($filename, ($owner << 6) + (0 << 3) + (0 << 0));
 821  
 822              clearstatcache();
 823  
 824              if (is_readable($filename) && phpbb_is_writable($filename))
 825              {
 826                  break;
 827              }
 828  
 829          case 'group':
 830              $result = @chmod($filename, ($owner << 6) + ($perms << 3) + (0 << 0));
 831  
 832              clearstatcache();
 833  
 834              if ((!($perms & CHMOD_READ) || is_readable($filename)) && (!($perms & CHMOD_WRITE) || phpbb_is_writable($filename)))
 835              {
 836                  break;
 837              }
 838  
 839          case 'other':
 840              $result = @chmod($filename, ($owner << 6) + ($perms << 3) + ($perms << 0));
 841  
 842              clearstatcache();
 843  
 844              if ((!($perms & CHMOD_READ) || is_readable($filename)) && (!($perms & CHMOD_WRITE) || phpbb_is_writable($filename)))
 845              {
 846                  break;
 847              }
 848  
 849          default:
 850              return false;
 851          break;
 852      }
 853  
 854      return $result;
 855  }
 856  
 857  /**
 858  * Test if a file/directory is writable
 859  *
 860  * This function calls the native is_writable() when not running under
 861  * Windows and it is not disabled.
 862  *
 863  * @param string $file Path to perform write test on
 864  * @return bool True when the path is writable, otherwise false.
 865  */
 866  function phpbb_is_writable($file)
 867  {
 868      if (strtolower(substr(PHP_OS, 0, 3)) === 'win' || !function_exists('is_writable'))
 869      {
 870          if (file_exists($file))
 871          {
 872              // Canonicalise path to absolute path
 873              $file = phpbb_realpath($file);
 874  
 875              if (is_dir($file))
 876              {
 877                  // Test directory by creating a file inside the directory
 878                  $result = @tempnam($file, 'i_w');
 879  
 880                  if (is_string($result) && file_exists($result))
 881                  {
 882                      unlink($result);
 883  
 884                      // Ensure the file is actually in the directory (returned realpathed)
 885                      return (strpos($result, $file) === 0) ? true : false;
 886                  }
 887              }
 888              else
 889              {
 890                  $handle = @fopen($file, 'r+');
 891  
 892                  if (is_resource($handle))
 893                  {
 894                      fclose($handle);
 895                      return true;
 896                  }
 897              }
 898          }
 899          else
 900          {
 901              // file does not exist test if we can write to the directory
 902              $dir = dirname($file);
 903  
 904              if (file_exists($dir) && is_dir($dir) && phpbb_is_writable($dir))
 905              {
 906                  return true;
 907              }
 908          }
 909  
 910          return false;
 911      }
 912      else
 913      {
 914          return is_writable($file);
 915      }
 916  }
 917  
 918  // Compatibility functions
 919  
 920  if (!function_exists('array_combine'))
 921  {
 922      /**
 923      * A wrapper for the PHP5 function array_combine()
 924      * @param array $keys contains keys for the resulting array
 925      * @param array $values contains values for the resulting array
 926      *
 927      * @return Returns an array by using the values from the keys array as keys and the
 928      *     values from the values array as the corresponding values. Returns false if the
 929      *     number of elements for each array isn't equal or if the arrays are empty.
 930      */
 931  	function array_combine($keys, $values)
 932      {
 933          $keys = array_values($keys);
 934          $values = array_values($values);
 935  
 936          $n = sizeof($keys);
 937          $m = sizeof($values);
 938          if (!$n || !$m || ($n != $m))
 939          {
 940              return false;
 941          }
 942  
 943          $combined = array();
 944          for ($i = 0; $i < $n; $i++)
 945          {
 946              $combined[$keys[$i]] = $values[$i];
 947          }
 948          return $combined;
 949      }
 950  }
 951  
 952  if (!function_exists('str_split'))
 953  {
 954      /**
 955      * A wrapper for the PHP5 function str_split()
 956      * @param array $string contains the string to be converted
 957      * @param array $split_length contains the length of each chunk
 958      *
 959      * @return  Converts a string to an array. If the optional split_length parameter is specified,
 960      *      the returned array will be broken down into chunks with each being split_length in length,
 961      *      otherwise each chunk will be one character in length. FALSE is returned if split_length is
 962      *      less than 1. If the split_length length exceeds the length of string, the entire string is
 963      *      returned as the first (and only) array element.
 964      */
 965  	function str_split($string, $split_length = 1)
 966      {
 967          if ($split_length < 1)
 968          {
 969              return false;
 970          }
 971          else if ($split_length >= strlen($string))
 972          {
 973              return array($string);
 974          }
 975          else
 976          {
 977              preg_match_all('#.{1,' . $split_length . '}#s', $string, $matches);
 978              return $matches[0];
 979          }
 980      }
 981  }
 982  
 983  if (!function_exists('stripos'))
 984  {
 985      /**
 986      * A wrapper for the PHP5 function stripos
 987      * Find position of first occurrence of a case-insensitive string
 988      *
 989      * @param string $haystack is the string to search in
 990      * @param string $needle is the string to search for
 991      *
 992      * @return mixed Returns the numeric position of the first occurrence of needle in the haystack string. Unlike strpos(), stripos() is case-insensitive.
 993      * Note that the needle may be a string of one or more characters.
 994      * If needle is not found, stripos() will return boolean FALSE.
 995      */
 996  	function stripos($haystack, $needle)
 997      {
 998          if (preg_match('#' . preg_quote($needle, '#') . '#i', $haystack, $m))
 999          {
1000              return strpos($haystack, $m[0]);
1001          }
1002  
1003          return false;
1004      }
1005  }
1006  
1007  /**
1008  * Checks if a path ($path) is absolute or relative
1009  *
1010  * @param string $path Path to check absoluteness of
1011  * @return boolean
1012  */
1013  function is_absolute($path)
1014  {
1015      return (isset($path[0]) && $path[0] == '/' || preg_match('#^[a-z]:[/\\\]#i', $path)) ? true : false;
1016  }
1017  
1018  /**
1019  * @author Chris Smith <chris@project-minerva.org>
1020  * @copyright 2006 Project Minerva Team
1021  * @param string $path The path which we should attempt to resolve.
1022  * @return mixed
1023  */
1024  function phpbb_own_realpath($path)
1025  {
1026      // Now to perform funky shizzle
1027  
1028      // Switch to use UNIX slashes
1029      $path = str_replace(DIRECTORY_SEPARATOR, '/', $path);
1030      $path_prefix = '';
1031  
1032      // Determine what sort of path we have
1033      if (is_absolute($path))
1034      {
1035          $absolute = true;
1036  
1037          if ($path[0] == '/')
1038          {
1039              // Absolute path, *NIX style
1040              $path_prefix = '';
1041          }
1042          else
1043          {
1044              // Absolute path, Windows style
1045              // Remove the drive letter and colon
1046              $path_prefix = $path[0] . ':';
1047              $path = substr($path, 2);
1048          }
1049      }
1050      else
1051      {
1052          // Relative Path
1053          // Prepend the current working directory
1054          if (function_exists('getcwd'))
1055          {
1056              // This is the best method, hopefully it is enabled!
1057              $path = str_replace(DIRECTORY_SEPARATOR, '/', getcwd()) . '/' . $path;
1058              $absolute = true;
1059              if (preg_match('#^[a-z]:#i', $path))
1060              {
1061                  $path_prefix = $path[0] . ':';
1062                  $path = substr($path, 2);
1063              }
1064              else
1065              {
1066                  $path_prefix = '';
1067              }
1068          }
1069          else if (isset($_SERVER['SCRIPT_FILENAME']) && !empty($_SERVER['SCRIPT_FILENAME']))
1070          {
1071              // Warning: If chdir() has been used this will lie!
1072              // Warning: This has some problems sometime (CLI can create them easily)
1073              $path = str_replace(DIRECTORY_SEPARATOR, '/', dirname($_SERVER['SCRIPT_FILENAME'])) . '/' . $path;
1074              $absolute = true;
1075              $path_prefix = '';
1076          }
1077          else
1078          {
1079              // We have no way of getting the absolute path, just run on using relative ones.
1080              $absolute = false;
1081              $path_prefix = '.';
1082          }
1083      }
1084  
1085      // Remove any repeated slashes
1086      $path = preg_replace('#/{2,}#', '/', $path);
1087  
1088      // Remove the slashes from the start and end of the path
1089      $path = trim($path, '/');
1090  
1091      // Break the string into little bits for us to nibble on
1092      $bits = explode('/', $path);
1093  
1094      // Remove any . in the path, renumber array for the loop below
1095      $bits = array_values(array_diff($bits, array('.')));
1096  
1097      // Lets get looping, run over and resolve any .. (up directory)
1098      for ($i = 0, $max = sizeof($bits); $i < $max; $i++)
1099      {
1100          // @todo Optimise
1101          if ($bits[$i] == '..' )
1102          {
1103              if (isset($bits[$i - 1]))
1104              {
1105                  if ($bits[$i - 1] != '..')
1106                  {
1107                      // We found a .. and we are able to traverse upwards, lets do it!
1108                      unset($bits[$i]);
1109                      unset($bits[$i - 1]);
1110                      $i -= 2;
1111                      $max -= 2;
1112                      $bits = array_values($bits);
1113                  }
1114              }
1115              else if ($absolute) // ie. !isset($bits[$i - 1]) && $absolute
1116              {
1117                  // We have an absolute path trying to descend above the root of the filesystem
1118                  // ... Error!
1119                  return false;
1120              }
1121          }
1122      }
1123  
1124      // Prepend the path prefix
1125      array_unshift($bits, $path_prefix);
1126  
1127      $resolved = '';
1128  
1129      $max = sizeof($bits) - 1;
1130  
1131      // Check if we are able to resolve symlinks, Windows cannot.
1132      $symlink_resolve = (function_exists('readlink')) ? true : false;
1133  
1134      foreach ($bits as $i => $bit)
1135      {
1136          if (@is_dir("$resolved/$bit") || ($i == $max && @is_file("$resolved/$bit")))
1137          {
1138              // Path Exists
1139              if ($symlink_resolve && is_link("$resolved/$bit") && ($link = readlink("$resolved/$bit")))
1140              {
1141                  // Resolved a symlink.
1142                  $resolved = $link . (($i == $max) ? '' : '/');
1143                  continue;
1144              }
1145          }
1146          else
1147          {
1148              // Something doesn't exist here!
1149              // This is correct realpath() behaviour but sadly open_basedir and safe_mode make this problematic
1150              // return false;
1151          }
1152          $resolved .= $bit . (($i == $max) ? '' : '/');
1153      }
1154  
1155      // @todo If the file exists fine and open_basedir only has one path we should be able to prepend it
1156      // because we must be inside that basedir, the question is where...
1157      // @internal The slash in is_dir() gets around an open_basedir restriction
1158      if (!@file_exists($resolved) || (!@is_dir($resolved . '/') && !is_file($resolved)))
1159      {
1160          return false;
1161      }
1162  
1163      // Put the slashes back to the native operating systems slashes
1164      $resolved = str_replace('/', DIRECTORY_SEPARATOR, $resolved);
1165  
1166      // Check for DIRECTORY_SEPARATOR at the end (and remove it!)
1167      if (substr($resolved, -1) == DIRECTORY_SEPARATOR)
1168      {
1169          return substr($resolved, 0, -1);
1170      }
1171  
1172      return $resolved; // We got here, in the end!
1173  }
1174  
1175  if (!function_exists('realpath'))
1176  {
1177      /**
1178      * A wrapper for realpath
1179      * @ignore
1180      */
1181  	function phpbb_realpath($path)
1182      {
1183          return phpbb_own_realpath($path);
1184      }
1185  }
1186  else
1187  {
1188      /**
1189      * A wrapper for realpath
1190      */
1191  	function phpbb_realpath($path)
1192      {
1193          $realpath = realpath($path);
1194  
1195          // Strangely there are provider not disabling realpath but returning strange values. :o
1196          // We at least try to cope with them.
1197          if ($realpath === $path || $realpath === false)
1198          {
1199              return phpbb_own_realpath($path);
1200          }
1201  
1202          // Check for DIRECTORY_SEPARATOR at the end (and remove it!)
1203          if (substr($realpath, -1) == DIRECTORY_SEPARATOR)
1204          {
1205              $realpath = substr($realpath, 0, -1);
1206          }
1207  
1208          return $realpath;
1209      }
1210  }
1211  
1212  /**
1213  * Eliminates useless . and .. components from specified path.
1214  *
1215  * @param string $path Path to clean
1216  * @return string Cleaned path
1217  */
1218  function phpbb_clean_path($path)
1219  {
1220      $exploded = explode('/', $path);
1221      $filtered = array();
1222      foreach ($exploded as $part)
1223      {
1224          if ($part === '.' && !empty($filtered))
1225          {
1226              continue;
1227          }
1228  
1229          if ($part === '..' && !empty($filtered) && $filtered[sizeof($filtered) - 1] !== '..')
1230          {
1231              array_pop($filtered);
1232          }
1233          else
1234          {
1235              $filtered[] = $part;
1236          }
1237      }
1238      $path = implode('/', $filtered);
1239      return $path;
1240  }
1241  
1242  if (!function_exists('htmlspecialchars_decode'))
1243  {
1244      /**
1245      * A wrapper for htmlspecialchars_decode
1246      * @ignore
1247      */
1248  	function htmlspecialchars_decode($string, $quote_style = ENT_COMPAT)
1249      {
1250          return strtr($string, array_flip(get_html_translation_table(HTML_SPECIALCHARS, $quote_style)));
1251      }
1252  }
1253  
1254  // functions used for building option fields
1255  
1256  /**
1257  * Pick a language, any language ...
1258  */
1259  function language_select($default = '')
1260  {
1261      global $db;
1262  
1263      $sql = 'SELECT lang_iso, lang_local_name
1264          FROM ' . LANG_TABLE . '
1265          ORDER BY lang_english_name';
1266      $result = $db->sql_query($sql);
1267  
1268      $lang_options = '';
1269      while ($row = $db->sql_fetchrow($result))
1270      {
1271          $selected = ($row['lang_iso'] == $default) ? ' selected="selected"' : '';
1272          $lang_options .= '<option value="' . $row['lang_iso'] . '"' . $selected . '>' . $row['lang_local_name'] . '</option>';
1273      }
1274      $db->sql_freeresult($result);
1275  
1276      return $lang_options;
1277  }
1278  
1279  /**
1280  * Pick a template/theme combo,
1281  */
1282  function style_select($default = '', $all = false)
1283  {
1284      global $db;
1285  
1286      $sql_where = (!$all) ? 'WHERE style_active = 1 ' : '';
1287      $sql = 'SELECT style_id, style_name
1288          FROM ' . STYLES_TABLE . "
1289          $sql_where
1290          ORDER BY style_name";
1291      $result = $db->sql_query($sql);
1292  
1293      $style_options = '';
1294      while ($row = $db->sql_fetchrow($result))
1295      {
1296          $selected = ($row['style_id'] == $default) ? ' selected="selected"' : '';
1297          $style_options .= '<option value="' . $row['style_id'] . '"' . $selected . '>' . $row['style_name'] . '</option>';
1298      }
1299      $db->sql_freeresult($result);
1300  
1301      return $style_options;
1302  }
1303  
1304  /**
1305  * Pick a timezone
1306  */
1307  function tz_select($default = '', $truncate = false)
1308  {
1309      global $user;
1310  
1311      $tz_select = '';
1312      foreach ($user->lang['tz_zones'] as $offset => $zone)
1313      {
1314          if ($truncate)
1315          {
1316              $zone_trunc = truncate_string($zone, 50, 255, false, '...');
1317          }
1318          else
1319          {
1320              $zone_trunc = $zone;
1321          }
1322  
1323          if (is_numeric($offset))
1324          {
1325              $selected = ($offset == $default) ? ' selected="selected"' : '';
1326              $tz_select .= '<option title="' . $zone . '" value="' . $offset . '"' . $selected . '>' . $zone_trunc . '</option>';
1327          }
1328      }
1329  
1330      return $tz_select;
1331  }
1332  
1333  // Functions handling topic/post tracking/marking
1334  
1335  /**
1336  * Marks a topic/forum as read
1337  * Marks a topic as posted to
1338  *
1339  * @param int $user_id can only be used with $mode == 'post'
1340  */
1341  function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $user_id = 0)
1342  {
1343      global $db, $user, $config;
1344  
1345      if ($mode == 'all')
1346      {
1347          if ($forum_id === false || !sizeof($forum_id))
1348          {
1349              if ($config['load_db_lastread'] && $user->data['is_registered'])
1350              {
1351                  // Mark all forums read (index page)
1352                  $db->sql_query('DELETE FROM ' . TOPICS_TRACK_TABLE . " WHERE user_id = {$user->data['user_id']}");
1353                  $db->sql_query('DELETE FROM ' . FORUMS_TRACK_TABLE . " WHERE user_id = {$user->data['user_id']}");
1354                  $db->sql_query('UPDATE ' . USERS_TABLE . ' SET user_lastmark = ' . time() . " WHERE user_id = {$user->data['user_id']}");
1355              }
1356              else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1357              {
1358                  $tracking_topics = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
1359                  $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
1360  
1361                  unset($tracking_topics['tf']);
1362                  unset($tracking_topics['t']);
1363                  unset($tracking_topics['f']);
1364                  $tracking_topics['l'] = base_convert(time() - $config['board_startdate'], 10, 36);
1365  
1366                  $user->set_cookie('track', tracking_serialize($tracking_topics), time() + 31536000);
1367                  $_COOKIE[$config['cookie_name'] . '_track'] = (STRIP) ? addslashes(tracking_serialize($tracking_topics)) : tracking_serialize($tracking_topics);
1368  
1369                  unset($tracking_topics);
1370  
1371                  if ($user->data['is_registered'])
1372                  {
1373                      $db->sql_query('UPDATE ' . USERS_TABLE . ' SET user_lastmark = ' . time() . " WHERE user_id = {$user->data['user_id']}");
1374                  }
1375              }
1376          }
1377  
1378          return;
1379      }
1380      else if ($mode == 'topics')
1381      {
1382          // Mark all topics in forums read
1383          if (!is_array($forum_id))
1384          {
1385              $forum_id = array($forum_id);
1386          }
1387  
1388          // Add 0 to forums array to mark global announcements correctly
1389          // $forum_id[] = 0;
1390  
1391          if ($config['load_db_lastread'] && $user->data['is_registered'])
1392          {
1393              $sql = 'DELETE FROM ' . TOPICS_TRACK_TABLE . "
1394                  WHERE user_id = {$user->data['user_id']}
1395                      AND " . $db->sql_in_set('forum_id', $forum_id);
1396              $db->sql_query($sql);
1397  
1398              $sql = 'SELECT forum_id
1399                  FROM ' . FORUMS_TRACK_TABLE . "
1400                  WHERE user_id = {$user->data['user_id']}
1401                      AND " . $db->sql_in_set('forum_id', $forum_id);
1402              $result = $db->sql_query($sql);
1403  
1404              $sql_update = array();
1405              while ($row = $db->sql_fetchrow($result))
1406              {
1407                  $sql_update[] = (int) $row['forum_id'];
1408              }
1409              $db->sql_freeresult($result);
1410  
1411              if (sizeof($sql_update))
1412              {
1413                  $sql = 'UPDATE ' . FORUMS_TRACK_TABLE . '
1414                      SET mark_time = ' . time() . "
1415                      WHERE user_id = {$user->data['user_id']}
1416                          AND " . $db->sql_in_set('forum_id', $sql_update);
1417                  $db->sql_query($sql);
1418              }
1419  
1420              if ($sql_insert = array_diff($forum_id, $sql_update))
1421              {
1422                  $sql_ary = array();
1423                  foreach ($sql_insert as $f_id)
1424                  {
1425                      $sql_ary[] = array(
1426                          'user_id'    => (int) $user->data['user_id'],
1427                          'forum_id'    => (int) $f_id,
1428                          'mark_time'    => time()
1429                      );
1430                  }
1431  
1432                  $db->sql_multi_insert(FORUMS_TRACK_TABLE, $sql_ary);
1433              }
1434          }
1435          else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1436          {
1437              $tracking = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
1438              $tracking = ($tracking) ? tracking_unserialize($tracking) : array();
1439  
1440              foreach ($forum_id as $f_id)
1441              {
1442                  $topic_ids36 = (isset($tracking['tf'][$f_id])) ? $tracking['tf'][$f_id] : array();
1443  
1444                  if (isset($tracking['tf'][$f_id]))
1445                  {
1446                      unset($tracking['tf'][$f_id]);
1447                  }
1448  
1449                  foreach ($topic_ids36 as $topic_id36)
1450                  {
1451                      unset($tracking['t'][$topic_id36]);
1452                  }
1453  
1454                  if (isset($tracking['f'][$f_id]))
1455                  {
1456                      unset($tracking['f'][$f_id]);
1457                  }
1458  
1459                  $tracking['f'][$f_id] = base_convert(time() - $config['board_startdate'], 10, 36);
1460              }
1461  
1462              if (isset($tracking['tf']) && empty($tracking['tf']))
1463              {
1464                  unset($tracking['tf']);
1465              }
1466  
1467              $user->set_cookie('track', tracking_serialize($tracking), time() + 31536000);
1468              $_COOKIE[$config['cookie_name'] . '_track'] = (STRIP) ? addslashes(tracking_serialize($tracking)) : tracking_serialize($tracking);
1469  
1470              unset($tracking);
1471          }
1472  
1473          return;
1474      }
1475      else if ($mode == 'topic')
1476      {
1477          if ($topic_id === false || $forum_id === false)
1478          {
1479              return;
1480          }
1481  
1482          if ($config['load_db_lastread'] && $user->data['is_registered'])
1483          {
1484              $sql = 'UPDATE ' . TOPICS_TRACK_TABLE . '
1485                  SET mark_time = ' . (($post_time) ? $post_time : time()) . "
1486                  WHERE user_id = {$user->data['user_id']}
1487                      AND topic_id = $topic_id";
1488              $db->sql_query($sql);
1489  
1490              // insert row
1491              if (!$db->sql_affectedrows())
1492              {
1493                  $db->sql_return_on_error(true);
1494  
1495                  $sql_ary = array(
1496                      'user_id'        => (int) $user->data['user_id'],
1497                      'topic_id'        => (int) $topic_id,
1498                      'forum_id'        => (int) $forum_id,
1499                      'mark_time'        => ($post_time) ? (int) $post_time : time(),
1500                  );
1501  
1502                  $db->sql_query('INSERT INTO ' . TOPICS_TRACK_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
1503  
1504                  $db->sql_return_on_error(false);
1505              }
1506          }
1507          else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1508          {
1509              $tracking = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
1510              $tracking = ($tracking) ? tracking_unserialize($tracking) : array();
1511  
1512              $topic_id36 = base_convert($topic_id, 10, 36);
1513  
1514              if (!isset($tracking['t'][$topic_id36]))
1515              {
1516                  $tracking['tf'][$forum_id][$topic_id36] = true;
1517              }
1518  
1519              $post_time = ($post_time) ? $post_time : time();
1520              $tracking['t'][$topic_id36] = base_convert($post_time - $config['board_startdate'], 10, 36);
1521  
1522              // If the cookie grows larger than 10000 characters we will remove the smallest value
1523              // This can result in old topics being unread - but most of the time it should be accurate...
1524              if (isset($_COOKIE[$config['cookie_name'] . '_track']) && strlen($_COOKIE[$config['cookie_name'] . '_track']) > 10000)
1525              {
1526                  //echo 'Cookie grown too large' . print_r($tracking, true);
1527  
1528                  // We get the ten most minimum stored time offsets and its associated topic ids
1529                  $time_keys = array();
1530                  for ($i = 0; $i < 10 && sizeof($tracking['t']); $i++)
1531                  {
1532                      $min_value = min($tracking['t']);
1533                      $m_tkey = array_search($min_value, $tracking['t']);
1534                      unset($tracking['t'][$m_tkey]);
1535  
1536                      $time_keys[$m_tkey] = $min_value;
1537                  }
1538  
1539                  // Now remove the topic ids from the array...
1540                  foreach ($tracking['tf'] as $f_id => $topic_id_ary)
1541                  {
1542                      foreach ($time_keys as $m_tkey => $min_value)
1543                      {
1544                          if (isset($topic_id_ary[$m_tkey]))
1545                          {
1546                              $tracking['f'][$f_id] = $min_value;
1547                              unset($tracking['tf'][$f_id][$m_tkey]);
1548                          }
1549                      }
1550                  }
1551  
1552                  if ($user->data['is_registered'])
1553                  {
1554                      $user->data['user_lastmark'] = intval(base_convert(max($time_keys) + $config['board_startdate'], 36, 10));
1555                      $db->sql_query('UPDATE ' . USERS_TABLE . ' SET user_lastmark = ' . $user->data['user_lastmark'] . " WHERE user_id = {$user->data['user_id']}");
1556                  }
1557                  else
1558                  {
1559                      $tracking['l'] = max($time_keys);
1560                  }
1561              }
1562  
1563              $user->set_cookie('track', tracking_serialize($tracking), time() + 31536000);
1564              $_COOKIE[$config['cookie_name'] . '_track'] = (STRIP) ? addslashes(tracking_serialize($tracking)) : tracking_serialize($tracking);
1565          }
1566  
1567          return;
1568      }
1569      else if ($mode == 'post')
1570      {
1571          if ($topic_id === false)
1572          {
1573              return;
1574          }
1575  
1576          $use_user_id = (!$user_id) ? $user->data['user_id'] : $user_id;
1577  
1578          if ($config['load_db_track'] && $use_user_id != ANONYMOUS)
1579          {
1580              $db->sql_return_on_error(true);
1581  
1582              $sql_ary = array(
1583                  'user_id'        => (int) $use_user_id,
1584                  'topic_id'        => (int) $topic_id,
1585                  'topic_posted'    => 1
1586              );
1587  
1588              $db->sql_query('INSERT INTO ' . TOPICS_POSTED_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
1589  
1590              $db->sql_return_on_error(false);
1591          }
1592  
1593          return;
1594      }
1595  }
1596  
1597  /**
1598  * Get topic tracking info by using already fetched info
1599  */
1600  function get_topic_tracking($forum_id, $topic_ids, &$rowset, $forum_mark_time, $global_announce_list = false)
1601  {
1602      global $config, $user;
1603  
1604      $last_read = array();
1605  
1606      if (!is_array($topic_ids))
1607      {
1608          $topic_ids = array($topic_ids);
1609      }
1610  
1611      foreach ($topic_ids as $topic_id)
1612      {
1613          if (!empty($rowset[$topic_id]['mark_time']))
1614          {
1615              $last_read[$topic_id] = $rowset[$topic_id]['mark_time'];
1616          }
1617      }
1618  
1619      $topic_ids = array_diff($topic_ids, array_keys($last_read));
1620  
1621      if (sizeof($topic_ids))
1622      {
1623          $mark_time = array();
1624  
1625          // Get global announcement info
1626          if ($global_announce_list && sizeof($global_announce_list))
1627          {
1628              if (!isset($forum_mark_time[0]))
1629              {
1630                  global $db;
1631  
1632                  $sql = 'SELECT mark_time
1633                      FROM ' . FORUMS_TRACK_TABLE . "
1634                      WHERE user_id = {$user->data['user_id']}
1635                          AND forum_id = 0";
1636                  $result = $db->sql_query($sql);
1637                  $row = $db->sql_fetchrow($result);
1638                  $db->sql_freeresult($result);
1639  
1640                  if ($row)
1641                  {
1642                      $mark_time[0] = $row['mark_time'];
1643                  }
1644              }
1645              else
1646              {
1647                  if ($forum_mark_time[0] !== false)
1648                  {
1649                      $mark_time[0] = $forum_mark_time[0];
1650                  }
1651              }
1652          }
1653  
1654          if (!empty($forum_mark_time[$forum_id]) && $forum_mark_time[$forum_id] !== false)
1655          {
1656              $mark_time[$forum_id] = $forum_mark_time[$forum_id];
1657          }
1658  
1659          $user_lastmark = (isset($mark_time[$forum_id])) ? $mark_time[$forum_id] : $user->data['user_lastmark'];
1660  
1661          foreach ($topic_ids as $topic_id)
1662          {
1663              if ($global_announce_list && isset($global_announce_list[$topic_id]))
1664              {
1665                  $last_read[$topic_id] = (isset($mark_time[0])) ? $mark_time[0] : $user_lastmark;
1666              }
1667              else
1668              {
1669                  $last_read[$topic_id] = $user_lastmark;
1670              }
1671          }
1672      }
1673  
1674      return $last_read;
1675  }
1676  
1677  /**
1678  * Get topic tracking info from db (for cookie based tracking only this function is used)
1679  */
1680  function get_complete_topic_tracking($forum_id, $topic_ids, $global_announce_list = false)
1681  {
1682      global $config, $user;
1683  
1684      $last_read = array();
1685  
1686      if (!is_array($topic_ids))
1687      {
1688          $topic_ids = array($topic_ids);
1689      }
1690  
1691      if ($config['load_db_lastread'] && $user->data['is_registered'])
1692      {
1693          global $db;
1694  
1695          $sql = 'SELECT topic_id, mark_time
1696              FROM ' . TOPICS_TRACK_TABLE . "
1697              WHERE user_id = {$user->data['user_id']}
1698                  AND " . $db->sql_in_set('topic_id', $topic_ids);
1699          $result = $db->sql_query($sql);
1700  
1701          while ($row = $db->sql_fetchrow($result))
1702          {
1703              $last_read[$row['topic_id']] = $row['mark_time'];
1704          }
1705          $db->sql_freeresult($result);
1706  
1707          $topic_ids = array_diff($topic_ids, array_keys($last_read));
1708  
1709          if (sizeof($topic_ids))
1710          {
1711              $sql = 'SELECT forum_id, mark_time
1712                  FROM ' . FORUMS_TRACK_TABLE . "
1713                  WHERE user_id = {$user->data['user_id']}
1714                      AND forum_id " .
1715                      (($global_announce_list && sizeof($global_announce_list)) ? "IN (0, $forum_id)" : "= $forum_id");
1716              $result = $db->sql_query($sql);
1717  
1718              $mark_time = array();
1719              while ($row = $db->sql_fetchrow($result))
1720              {
1721                  $mark_time[$row['forum_id']] = $row['mark_time'];
1722              }
1723              $db->sql_freeresult($result);
1724  
1725              $user_lastmark = (isset($mark_time[$forum_id])) ? $mark_time[$forum_id] : $user->data['user_lastmark'];
1726  
1727              foreach ($topic_ids as $topic_id)
1728              {
1729                  if ($global_announce_list && isset($global_announce_list[$topic_id]))
1730                  {
1731                      $last_read[$topic_id] = (isset($mark_time[0])) ? $mark_time[0] : $user_lastmark;
1732                  }
1733                  else
1734                  {
1735                      $last_read[$topic_id] = $user_lastmark;
1736                  }
1737              }
1738          }
1739      }
1740      else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1741      {
1742          global $tracking_topics;
1743  
1744          if (!isset($tracking_topics) || !sizeof($tracking_topics))
1745          {
1746              $tracking_topics = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
1747              $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
1748          }
1749  
1750          if (!$user->data['is_registered'])
1751          {
1752              $user_lastmark = (isset($tracking_topics['l'])) ? base_convert($tracking_topics['l'], 36, 10) + $config['board_startdate'] : 0;
1753          }
1754          else
1755          {
1756              $user_lastmark = $user->data['user_lastmark'];
1757          }
1758  
1759          foreach ($topic_ids as $topic_id)
1760          {
1761              $topic_id36 = base_convert($topic_id, 10, 36);
1762  
1763              if (isset($tracking_topics['t'][$topic_id36]))
1764              {
1765                  $last_read[$topic_id] = base_convert($tracking_topics['t'][$topic_id36], 36, 10) + $config['board_startdate'];
1766              }
1767          }
1768  
1769          $topic_ids = array_diff($topic_ids, array_keys($last_read));
1770  
1771          if (sizeof($topic_ids))
1772          {
1773              $mark_time = array();
1774              if ($global_announce_list && sizeof($global_announce_list))
1775              {
1776                  if (isset($tracking_topics['f'][0]))
1777                  {
1778                      $mark_time[0] = base_convert($tracking_topics['f'][0], 36, 10) + $config['board_startdate'];
1779                  }
1780              }
1781  
1782              if (isset($tracking_topics['f'][$forum_id]))
1783              {
1784                  $mark_time[$forum_id] = base_convert($tracking_topics['f'][$forum_id], 36, 10) + $config['board_startdate'];
1785              }
1786  
1787              $user_lastmark = (isset($mark_time[$forum_id])) ? $mark_time[$forum_id] : $user_lastmark;
1788  
1789              foreach ($topic_ids as $topic_id)
1790              {
1791                  if ($global_announce_list && isset($global_announce_list[$topic_id]))
1792                  {
1793                      $last_read[$topic_id] = (isset($mark_time[0])) ? $mark_time[0] : $user_lastmark;
1794                  }
1795                  else
1796                  {
1797                      $last_read[$topic_id] = $user_lastmark;
1798                  }
1799              }
1800          }
1801      }
1802  
1803      return $last_read;
1804  }
1805  
1806  /**
1807  * Get list of unread topics
1808  *
1809  * @param int $user_id            User ID (or false for current user)
1810  * @param string $sql_extra        Extra WHERE SQL statement
1811  * @param string $sql_sort        ORDER BY SQL sorting statement
1812  * @param string $sql_limit        Limits the size of unread topics list, 0 for unlimited query
1813  * @param string $sql_limit_offset  Sets the offset of the first row to search, 0 to search from the start
1814  *
1815  * @return array[int][int]        Topic ids as keys, mark_time of topic as value
1816  */
1817  function get_unread_topics($user_id = false, $sql_extra = '', $sql_sort = '', $sql_limit = 1001, $sql_limit_offset = 0)
1818  {
1819      global $config, $db, $user;
1820  
1821      $user_id = ($user_id === false) ? (int) $user->data['user_id'] : (int) $user_id;
1822  
1823      // Data array we're going to return
1824      $unread_topics = array();
1825  
1826      if (empty($sql_sort))
1827      {
1828          $sql_sort = 'ORDER BY t.topic_last_post_time DESC';
1829      }
1830  
1831      if ($config['load_db_lastread'] && $user->data['is_registered'])
1832      {
1833          // Get list of the unread topics
1834          $last_mark = (int) $user->data['user_lastmark'];
1835  
1836          $sql_array = array(
1837              'SELECT'        => 't.topic_id, t.topic_last_post_time, tt.mark_time as topic_mark_time, ft.mark_time as forum_mark_time',
1838  
1839              'FROM'            => array(TOPICS_TABLE => 't'),
1840  
1841              'LEFT_JOIN'        => array(
1842                  array(
1843                      'FROM'    => array(TOPICS_TRACK_TABLE => 'tt'),
1844                      'ON'    => "tt.user_id = $user_id AND t.topic_id = tt.topic_id",
1845                  ),
1846                  array(
1847                      'FROM'    => array(FORUMS_TRACK_TABLE => 'ft'),
1848                      'ON'    => "ft.user_id = $user_id AND t.forum_id = ft.forum_id",
1849                  ),
1850              ),
1851  
1852              'WHERE'            => "
1853                   t.topic_last_post_time > $last_mark AND
1854                  (
1855                  (tt.mark_time IS NOT NULL AND t.topic_last_post_time > tt.mark_time) OR
1856                  (tt.mark_time IS NULL AND ft.mark_time IS NOT NULL AND t.topic_last_post_time > ft.mark_time) OR
1857                  (tt.mark_time IS NULL AND ft.mark_time IS NULL)
1858                  )
1859                  $sql_extra
1860                  $sql_sort",
1861          );
1862  
1863          $sql = $db->sql_build_query('SELECT', $sql_array);
1864          $result = $db->sql_query_limit($sql, $sql_limit, $sql_limit_offset);
1865  
1866          while ($row = $db->sql_fetchrow($result))
1867          {
1868              $topic_id = (int) $row['topic_id'];
1869              $unread_topics[$topic_id] = ($row['topic_mark_time']) ? (int) $row['topic_mark_time'] : (($row['forum_mark_time']) ? (int) $row['forum_mark_time'] : $last_mark);
1870          }
1871          $db->sql_freeresult($result);
1872      }
1873      else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1874      {
1875          global $tracking_topics;
1876  
1877          if (empty($tracking_topics))
1878          {
1879              $tracking_topics = request_var($config['cookie_name'] . '_track', '', false, true);
1880              $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
1881          }
1882  
1883          if (!$user->data['is_registered'])
1884          {
1885              $user_lastmark = (isset($tracking_topics['l'])) ? base_convert($tracking_topics['l'], 36, 10) + $config['board_startdate'] : 0;
1886          }
1887          else
1888          {
1889              $user_lastmark = (int) $user->data['user_lastmark'];
1890          }
1891  
1892          $sql = 'SELECT t.topic_id, t.forum_id, t.topic_last_post_time
1893              FROM ' . TOPICS_TABLE . ' t
1894              WHERE t.topic_last_post_time > ' . $user_lastmark . "
1895              $sql_extra
1896              $sql_sort";
1897          $result = $db->sql_query_limit($sql, $sql_limit, $sql_limit_offset);
1898  
1899          while ($row = $db->sql_fetchrow($result))
1900          {
1901              $forum_id = (int) $row['forum_id'];
1902              $topic_id = (int) $row['topic_id'];
1903              $topic_id36 = base_convert($topic_id, 10, 36);
1904  
1905              if (isset($tracking_topics['t'][$topic_id36]))
1906              {
1907                  $last_read = base_convert($tracking_topics['t'][$topic_id36], 36, 10) + $config['board_startdate'];
1908  
1909                  if ($row['topic_last_post_time'] > $last_read)
1910                  {
1911                      $unread_topics[$topic_id] = $last_read;
1912                  }
1913              }
1914              else if (isset($tracking_topics['f'][$forum_id]))
1915              {
1916                  $mark_time = base_convert($tracking_topics['f'][$forum_id], 36, 10) + $config['board_startdate'];
1917  
1918                  if ($row['topic_last_post_time'] > $mark_time)
1919                  {
1920                      $unread_topics[$topic_id] = $mark_time;
1921                  }
1922              }
1923              else
1924              {
1925                  $unread_topics[$topic_id] = $user_lastmark;
1926              }
1927          }
1928          $db->sql_freeresult($result);
1929      }
1930  
1931      return $unread_topics;
1932  }
1933  
1934  /**
1935  * Check for read forums and update topic tracking info accordingly
1936  *
1937  * @param int $forum_id the forum id to check
1938  * @param int $forum_last_post_time the forums last post time
1939  * @param int $f_mark_time the forums last mark time if user is registered and load_db_lastread enabled
1940  * @param int $mark_time_forum false if the mark time needs to be obtained, else the last users forum mark time
1941  *
1942  * @return true if complete forum got marked read, else false.
1943  */
1944  function update_forum_tracking_info($forum_id, $forum_last_post_time, $f_mark_time = false, $mark_time_forum = false)
1945  {
1946      global $db, $tracking_topics, $user, $config, $auth;
1947  
1948      // Determine the users last forum mark time if not given.
1949      if ($mark_time_forum === false)
1950      {
1951          if ($config['load_db_lastread'] && $user->data['is_registered'])
1952          {
1953              $mark_time_forum = (!empty($f_mark_time)) ? $f_mark_time : $user->data['user_lastmark'];
1954          }
1955          else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1956          {
1957              $tracking_topics = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? ((STRIP) ? stripslashes($_COOKIE[$config['cookie_name'] . '_track']) : $_COOKIE[$config['cookie_name'] . '_track']) : '';
1958              $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
1959  
1960              if (!$user->data['is_registered'])
1961              {
1962                  $user->data['user_lastmark'] = (isset($tracking_topics['l'])) ? (int) (base_convert($tracking_topics['l'], 36, 10) + $config['board_startdate']) : 0;
1963              }
1964  
1965              $mark_time_forum = (isset($tracking_topics['f'][$forum_id])) ? (int) (base_convert($tracking_topics['f'][$forum_id], 36, 10) + $config['board_startdate']) : $user->data['user_lastmark'];
1966          }
1967      }
1968  
1969      // Handle update of unapproved topics info.
1970      // Only update for moderators having m_approve permission for the forum.
1971      $sql_update_unapproved = ($auth->acl_get('m_approve', $forum_id)) ? '': 'AND t.topic_approved = 1';
1972  
1973      // Check the forum for any left unread topics.
1974      // If there are none, we mark the forum as read.
1975      if ($config['load_db_lastread'] && $user->data['is_registered'])
1976      {
1977          if ($mark_time_forum >= $forum_last_post_time)
1978          {
1979              // We do not need to mark read, this happened before. Therefore setting this to true
1980              $row = true;
1981          }
1982          else
1983          {
1984              $sql = 'SELECT t.forum_id
1985                  FROM ' . TOPICS_TABLE . ' t
1986                  LEFT JOIN ' . TOPICS_TRACK_TABLE . ' tt
1987                      ON (tt.topic_id = t.topic_id
1988                          AND tt.user_id = ' . $user->data['user_id'] . ')
1989                  WHERE t.forum_id = ' . $forum_id . '
1990                      AND t.topic_last_post_time > ' . $mark_time_forum . '
1991                      AND t.topic_moved_id = 0 ' .
1992                      $sql_update_unapproved . '
1993                      AND (tt.topic_id IS NULL
1994                          OR tt.mark_time < t.topic_last_post_time)';
1995              $result = $db->sql_query_limit($sql, 1);
1996              $row = $db->sql_fetchrow($result);
1997              $db->sql_freeresult($result);
1998          }
1999      }
2000      else if ($config['load_anon_lastread'] || $user->data['is_registered'])
2001      {
2002          // Get information from cookie
2003          $row = false;
2004  
2005          if (!isset($tracking_topics['tf'][$forum_id]))
2006          {
2007              // We do not need to mark read, this happened before. Therefore setting this to true
2008              $row = true;
2009          }
2010          else
2011          {
2012              $sql = 'SELECT t.topic_id
2013                  FROM ' . TOPICS_TABLE . ' t
2014                  WHERE t.forum_id = ' . $forum_id . '
2015                      AND t.topic_last_post_time > ' . $mark_time_forum . '
2016                      AND t.topic_moved_id = 0 ' .
2017                      $sql_update_unapproved;
2018              $result = $db->sql_query($sql);
2019  
2020              $check_forum = $tracking_topics['tf'][$forum_id];
2021              $unread = false;
2022  
2023              while ($row = $db->sql_fetchrow($result))
2024              {
2025                  if (!isset($check_forum[base_convert($row['topic_id'], 10, 36)]))
2026                  {
2027                      $unread = true;
2028                      break;
2029                  }
2030              }
2031              $db->sql_freeresult($result);
2032  
2033              $row = $unread;
2034          }
2035      }
2036      else
2037      {
2038          $row = true;
2039      }
2040  
2041      if (!$row)
2042      {
2043          markread('topics', $forum_id);
2044          return true;
2045      }
2046  
2047      return false;
2048  }
2049  
2050  /**
2051  * Transform an array into a serialized format
2052  */
2053  function tracking_serialize($input)
2054  {
2055      $out = '';
2056      foreach ($input as $key => $value)
2057      {
2058          if (is_array($value))
2059          {
2060              $out .= $key . ':(' . tracking_serialize($value) . ');';
2061          }
2062          else
2063          {
2064              $out .= $key . ':' . $value . ';';
2065          }
2066      }
2067      return $out;
2068  }
2069  
2070  /**
2071  * Transform a serialized array into an actual array
2072  */
2073  function tracking_unserialize($string, $max_depth = 3)
2074  {
2075      $n = strlen($string);
2076      if ($n > 10010)
2077      {
2078          die('Invalid data supplied');
2079      }
2080      $data = $stack = array();
2081      $key = '';
2082      $mode = 0;
2083      $level = &$data;
2084      for ($i = 0; $i < $n; ++$i)
2085      {
2086          switch ($mode)
2087          {
2088              case 0:
2089                  switch ($string[$i])
2090                  {
2091                      case ':':
2092                          $level[$key] = 0;
2093                          $mode = 1;
2094                      break;
2095                      case ')':
2096                          unset($level);
2097                          $level = array_pop($stack);
2098                          $mode = 3;
2099                      break;
2100                      default:
2101                          $key .= $string[$i];
2102                  }
2103              break;
2104  
2105              case 1:
2106                  switch ($string[$i])
2107                  {
2108                      case '(':
2109                          if (sizeof($stack) >= $max_depth)
2110                          {
2111                              die('Invalid data supplied');
2112                          }
2113                          $stack[] = &$level;
2114                          $level[$key] = array();
2115                          $level = &$level[$key];
2116                          $key = '';
2117                          $mode = 0;
2118                      break;
2119                      default:
2120                          $level[$key] = $string[$i];
2121                          $mode = 2;
2122                      break;
2123                  }
2124              break;
2125  
2126              case 2:
2127                  switch ($string[$i])
2128                  {
2129                      case ')':
2130                          unset($level);
2131                          $level = array_pop($stack);
2132                          $mode = 3;
2133                      break;
2134                      case ';':
2135                          $key = '';
2136                          $mode = 0;
2137                      break;
2138                      default:
2139                          $level[$key] .= $string[$i];
2140                      break;
2141                  }
2142              break;
2143  
2144              case 3:
2145                  switch ($string[$i])
2146                  {
2147                      case ')':
2148                          unset($level);
2149                          $level = array_pop($stack);
2150                      break;
2151                      case ';':
2152                          $key = '';
2153                          $mode = 0;
2154                      break;
2155                      default:
2156                          die('Invalid data supplied');
2157                      break;
2158                  }
2159              break;
2160          }
2161      }
2162  
2163      if (sizeof($stack) != 0 || ($mode != 0 && $mode != 3))
2164      {
2165          die('Invalid data supplied');
2166      }
2167  
2168      return $level;
2169  }
2170  
2171  // Pagination functions
2172  
2173  /**
2174  * Pagination routine, generates page number sequence
2175  * tpl_prefix is for using different pagination blocks at one page
2176  */
2177  function generate_pagination($base_url, $num_items, $per_page, $start_item, $add_prevnext_text = false, $tpl_prefix = '')
2178  {
2179      global $template, $user;
2180  
2181      // Make sure $per_page is a valid value
2182      $per_page = ($per_page <= 0) ? 1 : $per_page;
2183  
2184      $seperator = '<span class="page-sep">' . $user->lang['COMMA_SEPARATOR'] . '</span>';
2185      $total_pages = ceil($num_items / $per_page);
2186  
2187      if ($total_pages == 1 || !$num_items)
2188      {
2189          return false;
2190      }
2191  
2192      $on_page = floor($start_item / $per_page) + 1;
2193      $url_delim = (strpos($base_url, '?') === false) ? '?' : ((strpos($base_url, '?') === strlen($base_url) - 1) ? '' : '&amp;');
2194  
2195      $page_string = ($on_page == 1) ? '<strong>1</strong>' : '<a href="' . $base_url . '">1</a>';
2196  
2197      if ($total_pages > 5)
2198      {
2199          $start_cnt = min(max(1, $on_page - 4), $total_pages - 5);
2200          $end_cnt = max(min($total_pages, $on_page + 4), 6);
2201  
2202          $page_string .= ($start_cnt > 1) ? '<span class="page-dots"> ... </span>' : $seperator;
2203  
2204          for ($i = $start_cnt + 1; $i < $end_cnt; $i++)
2205          {
2206              $page_string .= ($i == $on_page) ? '<strong>' . $i . '</strong>' : '<a href="' . $base_url . "{$url_delim}start=" . (($i - 1) * $per_page) . '">' . $i . '</a>';
2207              if ($i < $end_cnt - 1)
2208              {
2209                  $page_string .= $seperator;
2210              }
2211          }
2212  
2213          $page_string .= ($end_cnt < $total_pages) ? '<span class="page-dots"> ... </span>' : $seperator;
2214      }
2215      else
2216      {
2217          $page_string .= $seperator;
2218  
2219          for ($i = 2; $i < $total_pages; $i++)
2220          {
2221              $page_string .= ($i == $on_page) ? '<strong>' . $i . '</strong>' : '<a href="' . $base_url . "{$url_delim}start=" . (($i - 1) * $per_page) . '">' . $i . '</a>';
2222              if ($i < $total_pages)
2223              {
2224                  $page_string .= $seperator;
2225              }
2226          }
2227      }
2228  
2229      $page_string .= ($on_page == $total_pages) ? '<strong>' . $total_pages . '</strong>' : '<a href="' . $base_url . "{$url_delim}start=" . (($total_pages - 1) * $per_page) . '">' . $total_pages . '</a>';
2230  
2231      if ($add_prevnext_text)
2232      {
2233          if ($on_page != 1)
2234          {
2235              $page_string = '<a href="' . $base_url . "{$url_delim}start=" . (($on_page - 2) * $per_page) . '">' . $user->lang['PREVIOUS'] . '</a>&nbsp;&nbsp;' . $page_string;
2236          }
2237  
2238          if ($on_page != $total_pages)
2239          {
2240              $page_string .= '&nbsp;&nbsp;<a href="' . $base_url . "{$url_delim}start=" . ($on_page * $per_page) . '">' . $user->lang['NEXT'] . '</a>';
2241          }
2242      }
2243  
2244      $template->assign_vars(array(
2245          $tpl_prefix . 'BASE_URL'        => $base_url,
2246          'A_' . $tpl_prefix . 'BASE_URL'    => addslashes($base_url),
2247          $tpl_prefix . 'PER_PAGE'        => $per_page,
2248  
2249          $tpl_prefix . 'PREVIOUS_PAGE'    => ($on_page == 1) ? '' : $base_url . "{$url_delim}start=" . (($on_page - 2) * $per_page),
2250          $tpl_prefix . 'NEXT_PAGE'        => ($on_page == $total_pages) ? '' : $base_url . "{$url_delim}start=" . ($on_page * $per_page),
2251          $tpl_prefix . 'TOTAL_PAGES'        => $total_pages,
2252      ));
2253  
2254      return $page_string;
2255  }
2256  
2257  /**
2258  * Return current page (pagination)
2259  */
2260  function on_page($num_items, $per_page, $start)
2261  {
2262      global $template, $user;
2263  
2264      // Make sure $per_page is a valid value
2265      $per_page = ($per_page <= 0) ? 1 : $per_page;
2266  
2267      $on_page = floor($start / $per_page) + 1;
2268  
2269      $template->assign_vars(array(
2270          'ON_PAGE'        => $on_page)
2271      );
2272  
2273      return sprintf($user->lang['PAGE_OF'], $on_page, max(ceil($num_items / $per_page), 1));
2274  }
2275  
2276  // Server functions (building urls, redirecting...)
2277  
2278  /**
2279  * Append session id to url.
2280  * This function supports hooks.
2281  *
2282  * @param string $url The url the session id needs to be appended to (can have params)
2283  * @param mixed $params String or array of additional url parameters
2284  * @param bool $is_amp Is url using &amp; (true) or & (false)
2285  * @param string $session_id Possibility to use a custom session id instead of the global one
2286  *
2287  * Examples:
2288  * <code>
2289  * append_sid("{$phpbb_root_path}viewtopic.$phpEx?t=1&amp;f=2");
2290  * append_sid("{$phpbb_root_path}viewtopic.$phpEx", 't=1&amp;f=2');
2291  * append_sid("{$phpbb_root_path}viewtopic.$phpEx", 't=1&f=2', false);
2292  * append_sid("{$phpbb_root_path}viewtopic.$phpEx", array('t' => 1, 'f' => 2));
2293  * </code>
2294  *
2295  */
2296  function append_sid($url, $params = false, $is_amp = true, $session_id = false)
2297  {
2298      global $_SID, $_EXTRA_URL, $phpbb_hook;
2299  
2300      if ($params === '' || (is_array($params) && empty($params)))
2301      {
2302          // Do not append the ? if the param-list is empty anyway.
2303          $params = false;
2304      }
2305  
2306      // Developers using the hook function need to globalise the $_SID and $_EXTRA_URL on their own and also handle it appropriately.
2307      // They could mimic most of what is within this function
2308      if (!empty($phpbb_hook) && $phpbb_hook->call_hook(__FUNCTION__, $url, $params, $is_amp, $session_id))
2309      {
2310          if ($phpbb_hook->hook_return(__FUNCTION__))
2311          {
2312              return $phpbb_hook->hook_return_result(__FUNCTION__);
2313          }
2314      }
2315  
2316      $params_is_array = is_array($params);
2317  
2318      // Get anchor
2319      $anchor = '';
2320      if (strpos($url, '#') !== false)
2321      {
2322          list($url, $anchor) = explode('#', $url, 2);
2323          $anchor = '#' . $anchor;
2324      }
2325      else if (!$params_is_array && strpos($params, '#') !== false)
2326      {
2327          list($params, $anchor) = explode('#', $params, 2);
2328          $anchor = '#' . $anchor;
2329      }
2330  
2331      // Handle really simple cases quickly
2332      if ($_SID == '' && $session_id === false && empty($_EXTRA_URL) && !$params_is_array && !$anchor)
2333      {
2334          if ($params === false)
2335          {
2336              return $url;
2337          }
2338  
2339          $url_delim = (strpos($url, '?') === false) ? '?' : (($is_amp) ? '&amp;' : '&');
2340          return $url . ($params !== false ? $url_delim. $params : '');
2341      }
2342  
2343      // Assign sid if session id is not specified
2344      if ($session_id === false)
2345      {
2346          $session_id = $_SID;
2347      }
2348  
2349      $amp_delim = ($is_amp) ? '&amp;' : '&';
2350      $url_delim = (strpos($url, '?') === false) ? '?' : $amp_delim;
2351  
2352      // Appending custom url parameter?
2353      $append_url = (!empty($_EXTRA_URL)) ? implode($amp_delim, $_EXTRA_URL) : '';
2354  
2355      // Use the short variant if possible ;)
2356      if ($params === false)
2357      {
2358          // Append session id
2359          if (!$session_id)
2360          {
2361              return $url . (($append_url) ? $url_delim . $append_url : '') . $anchor;
2362          }
2363          else
2364          {
2365              return $url . (($append_url) ? $url_delim . $append_url . $amp_delim : $url_delim) . 'sid=' . $session_id . $anchor;
2366          }
2367      }
2368  
2369      // Build string if parameters are specified as array
2370      if (is_array($params))
2371      {
2372          $output = array();
2373  
2374          foreach ($params as $key => $item)
2375          {
2376              if ($item === NULL)
2377              {
2378                  continue;
2379              }
2380  
2381              if ($key == '#')
2382              {
2383                  $anchor = '#' . $item;
2384                  continue;
2385              }
2386  
2387              $output[] = $key . '=' . $item;
2388          }
2389  
2390          $params = implode($amp_delim, $output);
2391      }
2392  
2393      // Append session id and parameters (even if they are empty)
2394      // If parameters are empty, the developer can still append his/her parameters without caring about the delimiter
2395      return $url . (($append_url) ? $url_delim . $append_url . $amp_delim : $url_delim) . $params . ((!$session_id) ? '' : $amp_delim . 'sid=' . $session_id) . $anchor;
2396  }
2397  
2398  /**
2399  * Generate board url (example: http://www.example.com/phpBB)
2400  *
2401  * @param bool $without_script_path if set to true the script path gets not appended (example: http://www.example.com)
2402  *
2403  * @return string the generated board url
2404  */
2405  function generate_board_url($without_script_path = false)
2406  {
2407      global $config, $user;
2408  
2409      $server_name = $user->host;
2410      $server_port = (!empty($_SERVER['SERVER_PORT'])) ? (int) $_SERVER['SERVER_PORT'] : (int) getenv('SERVER_PORT');
2411  
2412      // Forcing server vars is the only way to specify/override the protocol
2413      if ($config['force_server_vars'] || !$server_name)
2414      {
2415          $server_protocol = ($config['server_protocol']) ? $config['server_protocol'] : (($config['cookie_secure']) ? 'https://' : 'http://');
2416          $server_name = $config['server_name'];
2417          $server_port = (int) $config['server_port'];
2418          $script_path = $config['script_path'];
2419  
2420          $url = $server_protocol . $server_name;
2421          $cookie_secure = $config['cookie_secure'];
2422      }
2423      else
2424      {
2425          // Do not rely on cookie_secure, users seem to think that it means a secured cookie instead of an encrypted connection
2426          $cookie_secure = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 1 : 0;
2427          $url = (($cookie_secure) ? 'https://' : 'http://') . $server_name;
2428  
2429          $script_path = $user->page['root_script_path'];
2430      }
2431  
2432      if ($server_port && (($cookie_secure && $server_port <> 443) || (!$cookie_secure && $server_port <> 80)))
2433      {
2434          // HTTP HOST can carry a port number (we fetch $user->host, but for old versions this may be true)
2435          if (strpos($server_name, ':') === false)
2436          {
2437              $url .= ':' . $server_port;
2438          }
2439      }
2440  
2441      if (!$without_script_path)
2442      {
2443          $url .= $script_path;
2444      }
2445  
2446      // Strip / from the end
2447      if (substr($url, -1, 1) == '/')
2448      {
2449          $url = substr($url, 0, -1);
2450      }
2451  
2452      return $url;
2453  }
2454  
2455  /**
2456  * Redirects the user to another page then exits the script nicely
2457  * This function is intended for urls within the board. It's not meant to redirect to cross-domains.
2458  *
2459  * @param string $url The url to redirect to
2460  * @param bool $return If true, do not redirect but return the sanitized URL. Default is no return.
2461  * @param bool $disable_cd_check If true, redirect() will redirect to an external domain. If false, the redirect point to the boards url if it does not match the current domain. Default is false.
2462  */
2463  function redirect($url, $return = false, $disable_cd_check = false)
2464  {
2465      global $db, $cache, $config, $user, $phpbb_root_path;
2466  
2467      $failover_flag = false;
2468  
2469      if (empty($user->lang))
2470      {
2471          $user->add_lang('common');
2472      }
2473  
2474      if (!$return)
2475      {
2476          garbage_collection();
2477      }
2478  
2479      // Make sure no &amp;'s are in, this will break the redirect
2480      $url = str_replace('&amp;', '&', $url);
2481  
2482      // Determine which type of redirect we need to handle...
2483      $url_parts = @parse_url($url);
2484  
2485      if ($url_parts === false)
2486      {
2487          // Malformed url, redirect to current page...
2488          $url = generate_board_url() . '/' . $user->page['page'];
2489      }
2490      else if (!empty($url_parts['scheme']) && !empty($url_parts['host']))
2491      {
2492          // Attention: only able to redirect within the same domain if $disable_cd_check is false (yourdomain.com -> www.yourdomain.com will not work)
2493          if (!$disable_cd_check && $url_parts['host'] !== $user->host)
2494          {
2495              $url = generate_board_url();
2496          }
2497      }
2498      else if ($url[0] == '/')
2499      {
2500          // Absolute uri, prepend direct url...
2501          $url = generate_board_url(true) . $url;
2502      }
2503      else
2504      {
2505          // Relative uri
2506          $pathinfo = pathinfo($url);
2507  
2508          if (!$disable_cd_check && !file_exists($pathinfo['dirname'] . '/'))
2509          {
2510              $url = str_replace('../', '', $url);
2511              $pathinfo = pathinfo($url);
2512  
2513              if (!file_exists($pathinfo['dirname'] . '/'))
2514              {
2515                  // fallback to "last known user page"
2516                  // at least this way we know the user does not leave the phpBB root
2517                  $url = generate_board_url() . '/' . $user->page['page'];
2518                  $failover_flag = true;
2519              }
2520          }
2521  
2522          if (!$failover_flag)
2523          {
2524              // Is the uri pointing to the current directory?
2525              if ($pathinfo['dirname'] == '.')
2526              {
2527                  $url = str_replace('./', '', $url);
2528  
2529                  // Strip / from the beginning
2530                  if ($url && substr($url, 0, 1) == '/')
2531                  {
2532                      $url = substr($url, 1);
2533                  }
2534  
2535                  if ($user->page['page_dir'])
2536                  {
2537                      $url = generate_board_url() . '/' . $user->page['page_dir'] . '/' . $url;
2538                  }
2539                  else
2540                  {
2541                      $url = generate_board_url() . '/' . $url;
2542                  }
2543              }
2544              else
2545              {
2546                  // Used ./ before, but $phpbb_root_path is working better with urls within another root path
2547                  $root_dirs = explode('/', str_replace('\\', '/', phpbb_realpath($phpbb_root_path)));
2548                  $page_dirs = explode('/', str_replace('\\', '/', phpbb_realpath($pathinfo['dirname'])));
2549                  $intersection = array_intersect_assoc($root_dirs, $page_dirs);
2550  
2551                  $root_dirs = array_diff_assoc($root_dirs, $intersection);
2552                  $page_dirs = array_diff_assoc($page_dirs, $intersection);
2553  
2554                  $dir = str_repeat('../', sizeof($root_dirs)) . implode('/', $page_dirs);
2555  
2556                  // Strip / from the end
2557                  if ($dir && substr($dir, -1, 1) == '/')
2558                  {
2559                      $dir = substr($dir, 0, -1);
2560                  }
2561  
2562                  // Strip / from the beginning
2563                  if ($dir && substr($dir, 0, 1) == '/')
2564                  {
2565                      $dir = substr($dir, 1);
2566                  }
2567  
2568                  $url = str_replace($pathinfo['dirname'] . '/', '', $url);
2569  
2570                  // Strip / from the beginning
2571                  if (substr($url, 0, 1) == '/')
2572                  {
2573                      $url = substr($url, 1);
2574                  }
2575  
2576                  $url = (!empty($dir) ? $dir . '/' : '') . $url;
2577                  $url = generate_board_url() . '/' . $url;
2578              }
2579          }
2580      }
2581  
2582      // Make sure no linebreaks are there... to prevent http response splitting for PHP < 4.4.2
2583      if (strpos(urldecode($url), "\n") !== false || strpos(urldecode($url), "\r") !== false || strpos($url, ';') !== false)
2584      {
2585          trigger_error('Tried to redirect to potentially insecure url.', E_USER_ERROR);
2586      }
2587  
2588      // Now, also check the protocol and for a valid url the last time...
2589      $allowed_protocols = array('http', 'https', 'ftp', 'ftps');
2590      $url_parts = parse_url($url);
2591  
2592      if ($url_parts === false || empty($url_parts['scheme']) || !in_array($url_parts['scheme'], $allowed_protocols))
2593      {
2594          trigger_error('Tried to redirect to potentially insecure url.', E_USER_ERROR);
2595      }
2596  
2597      if ($return)
2598      {
2599          return $url;
2600      }
2601  
2602      // Redirect via an HTML form for PITA webservers
2603      if (@preg_match('#Microsoft|WebSTAR|Xitami#', getenv('SERVER_SOFTWARE')))
2604      {
2605          header('Refresh: 0; URL=' . $url);
2606  
2607          echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
2608          echo '<html xmlns="http://www.w3.org/1999/xhtml" dir="' . $user->lang['DIRECTION'] . '" lang="' . $user->lang['USER_LANG'] . '" xml:lang="' . $user->lang['USER_LANG'] . '">';
2609          echo '<head>';
2610          echo '<meta http-equiv="content-type" content="text/html; charset=utf-8" />';
2611          echo '<meta http-equiv="refresh" content="0; url=' . str_replace('&', '&amp;', $url) . '" />';
2612          echo '<title>' . $user->lang['REDIRECT'] . '</title>';
2613          echo '</head>';
2614          echo '<body>';
2615          echo '<div style="text-align: center;">' . sprintf($user->lang['URL_REDIRECT'], '<a href="' . str_replace('&', '&amp;', $url) . '">', '</a>') . '</div>';
2616          echo '</body>';
2617          echo '</html>';
2618  
2619          exit;
2620      }
2621  
2622      // Behave as per HTTP/1.1 spec for others
2623      header('Location: ' . $url);
2624      exit;
2625  }
2626  
2627  /**
2628  * Re-Apply session id after page reloads
2629  */
2630  function reapply_sid($url)
2631  {
2632      global $phpEx, $phpbb_root_path;
2633  
2634      if ($url === "index.$phpEx")
2635      {
2636          return append_sid("index.$phpEx");
2637      }
2638      else if ($url === "{$phpbb_root_path}index.$phpEx")
2639      {
2640          return append_sid("{$phpbb_root_path}index.$phpEx");
2641      }
2642  
2643      // Remove previously added sid
2644      if (strpos($url, 'sid=') !== false)
2645      {
2646          // All kind of links
2647          $url = preg_replace('/(\?)?(&amp;|&)?sid=[a-z0-9]+/', '', $url);
2648          // if the sid was the first param, make the old second as first ones
2649          $url = preg_replace("/$phpEx(&amp;|&)+?/", "$phpEx?", $url);
2650      }
2651  
2652      return append_sid($url);
2653  }
2654  
2655  /**
2656  * Returns url from the session/current page with an re-appended SID with optionally stripping vars from the url
2657  */
2658  function build_url($strip_vars = false)
2659  {
2660      global $user, $phpbb_root_path;
2661  
2662      // Append SID
2663      $redirect = append_sid($user->page['page'], false, false);
2664  
2665      // Add delimiter if not there...
2666      if (strpos($redirect, '?') === false)
2667      {
2668          $redirect .= '?';
2669      }
2670  
2671      // Strip vars...
2672      if ($strip_vars !== false && strpos($redirect, '?') !== false)
2673      {
2674          if (!is_array($strip_vars))
2675          {
2676              $strip_vars = array($strip_vars);
2677          }
2678  
2679          $query = $_query = array();
2680  
2681          $args = substr($redirect, strpos($redirect, '?') + 1);
2682          $args = ($args) ? explode('&', $args) : array();
2683          $redirect = substr($redirect, 0, strpos($redirect, '?'));
2684  
2685          foreach ($args as $argument)
2686          {
2687              $arguments = explode('=', $argument);
2688              $key = $arguments[0];
2689              unset($arguments[0]);
2690  
2691              if ($key === '')
2692              {
2693                  continue;
2694              }
2695  
2696              $query[$key] = implode('=', $arguments);
2697          }
2698  
2699          // Strip the vars off
2700          foreach ($strip_vars as $strip)
2701          {
2702              if (isset($query[$strip]))
2703              {
2704                  unset($query[$strip]);
2705              }
2706          }
2707  
2708          // Glue the remaining parts together... already urlencoded
2709          foreach ($query as $key => $value)
2710          {
2711              $_query[] = $key . '=' . $value;
2712          }
2713          $query = implode('&', $_query);
2714  
2715          $redirect .= ($query) ? '?' . $query : '';
2716      }
2717  
2718      // We need to be cautious here.
2719      // On some situations, the redirect path is an absolute URL, sometimes a relative path
2720      // For a relative path, let's prefix it with $phpbb_root_path to point to the correct location,
2721      // else we use the URL directly.
2722      $url_parts = @parse_url($redirect);
2723  
2724      // URL
2725      if ($url_parts !== false && !empty($url_parts['scheme']) && !empty($url_parts['host']))
2726      {
2727          return str_replace('&', '&amp;', $redirect);
2728      }
2729  
2730      return $phpbb_root_path . str_replace('&', '&amp;', $redirect);
2731  }
2732  
2733  /**
2734  * Meta refresh assignment
2735  * Adds META template variable with meta http tag.
2736  *
2737  * @param int $time Time in seconds for meta refresh tag
2738  * @param string $url URL to redirect to. The url will go through redirect() first before the template variable is assigned
2739  * @param bool $disable_cd_check If true, meta_refresh() will redirect to an external domain. If false, the redirect point to the boards url if it does not match the current domain. Default is false.
2740  */
2741  function meta_refresh($time, $url, $disable_cd_check = false)
2742  {
2743      global $template;
2744  
2745      $url = redirect($url, true, $disable_cd_check);
2746      $url = str_replace('&', '&amp;', $url);
2747  
2748      // For XHTML compatibility we change back & to &amp;
2749      $template->assign_vars(array(
2750          'META' => '<meta http-equiv="refresh" content="' . $time . '; url=' . $url . '" />')
2751      );
2752  
2753      return $url;
2754  }
2755  
2756  /**
2757  * Outputs correct status line header.
2758  *
2759  * Depending on php sapi one of the two following forms is used:
2760  *
2761  * Status: 404 Not Found
2762  *
2763  * HTTP/1.x 404 Not Found
2764  *
2765  * HTTP version is taken from HTTP_VERSION environment variable,
2766  * and defaults to 1.0.
2767  *
2768  * Sample usage:
2769  *
2770  * send_status_line(404, 'Not Found');
2771  *
2772  * @param int $code HTTP status code
2773  * @param string $message Message for the status code
2774  * @return null
2775  */
2776  function send_status_line($code, $message)
2777  {
2778      if (substr(strtolower(@php_sapi_name()), 0, 3) === 'cgi')
2779      {
2780          // in theory, we shouldn't need that due to php doing it. Reality offers a differing opinion, though
2781          header("Status: $code $message", true, $code);
2782      }
2783      else
2784      {
2785          if (!empty($_SERVER['SERVER_PROTOCOL']))
2786          {
2787              $version = $_SERVER['SERVER_PROTOCOL'];
2788          }
2789          else
2790          {
2791              $version = 'HTTP/1.0';
2792          }
2793          header("$version $code $message", true, $code);
2794      }
2795  }
2796  
2797  //Form validation
2798  
2799  
2800  /**
2801  * Add a secret hash   for use in links/GET requests
2802  * @param string  $link_name The name of the link; has to match the name used in check_link_hash, otherwise no restrictions apply
2803  * @return string the hash
2804  
2805  */
2806  function generate_link_hash($link_name)
2807  {
2808      global $user;
2809  
2810      if (!isset($user->data["hash_$link_name"]))
2811      {
2812          $user->data["hash_$link_name"] = substr(sha1($user->data['user_form_salt'] . $link_name), 0, 8);
2813      }
2814  
2815      return $user->data["hash_$link_name"];
2816  }
2817  
2818  
2819  /**
2820  * checks a link hash - for GET requests
2821  * @param string $token the submitted token
2822  * @param string $link_name The name of the link
2823  * @return boolean true if all is fine
2824  */
2825  function check_link_hash($token, $link_name)
2826  {
2827      return $token === generate_link_hash($link_name);
2828  }
2829  
2830  /**
2831  * Add a secret token to the form (requires the S_FORM_TOKEN template variable)
2832  * @param string  $form_name The name of the form; has to match the name used in check_form_key, otherwise no restrictions apply
2833  */
2834  function add_form_key($form_name)
2835  {
2836      global $config, $template, $user;
2837  
2838      $now = time();
2839      $token_sid = ($user->data['user_id'] == ANONYMOUS && !empty($config['form_token_sid_guests'])) ? $user->session_id : '';
2840      $token = sha1($now . $user->data['user_form_salt'] . $form_name . $token_sid);
2841  
2842      $s_fields = build_hidden_fields(array(
2843          'creation_time' => $now,
2844          'form_token'    => $token,
2845      ));
2846  
2847      $template->assign_vars(array(
2848          'S_FORM_TOKEN'    => $s_fields,
2849      ));
2850  }
2851  
2852  /**
2853  * Check the form key. Required for all altering actions not secured by confirm_box
2854  * @param string  $form_name The name of the form; has to match the name used in add_form_key, otherwise no restrictions apply
2855  * @param int $timespan The maximum acceptable age for a submitted form in seconds. Defaults to the config setting.
2856  * @param string $return_page The address for the return link
2857  * @param bool $trigger If true, the function will triger an error when encountering an invalid form
2858  */
2859  function check_form_key($form_name, $timespan = false, $return_page = '', $trigger = false)
2860  {
2861      global $config, $user;
2862  
2863      if ($timespan === false)
2864      {
2865          // we enforce a minimum value of half a minute here.
2866          $timespan = ($config['form_token_lifetime'] == -1) ? -1 : max(30, $config['form_token_lifetime']);
2867      }
2868  
2869      if (isset($_POST['creation_time']) && isset($_POST['form_token']))
2870      {
2871          $creation_time    = abs(request_var('creation_time', 0));
2872          $token = request_var('form_token', '');
2873  
2874          $diff = time() - $creation_time;
2875  
2876          // If creation_time and the time() now is zero we can assume it was not a human doing this (the check for if ($diff)...
2877          if (defined('DEBUG_TEST') || $diff && ($diff <= $timespan || $timespan === -1))
2878          {
2879              $token_sid = ($user->data['user_id'] == ANONYMOUS && !empty($config['form_token_sid_guests'])) ? $user->session_id : '';
2880              $key = sha1($creation_time . $user->data['user_form_salt'] . $form_name . $token_sid);
2881  
2882              if ($key === $token)
2883              {
2884                  return true;
2885              }
2886          }
2887      }
2888  
2889      if ($trigger)
2890      {
2891          trigger_error($user->lang['FORM_INVALID'] . $return_page);
2892      }
2893  
2894      return false;
2895  }
2896  
2897  // Message/Login boxes
2898  
2899  /**
2900  * Build Confirm box
2901  * @param boolean $check True for checking if confirmed (without any additional parameters) and false for displaying the confirm box
2902  * @param string $title Title/Message used for confirm box.
2903  *        message text is _CONFIRM appended to title.
2904  *        If title cannot be found in user->lang a default one is displayed
2905  *        If title_CONFIRM cannot be found in user->lang the text given is used.
2906  * @param string $hidden Hidden variables
2907  * @param string $html_body Template used for confirm box
2908  * @param string $u_action Custom form action
2909  */
2910  function confirm_box($check, $title = '', $hidden = '', $html_body = 'confirm_body.html', $u_action = '')
2911  {
2912      global $user, $template, $db;
2913      global $phpEx, $phpbb_root_path;
2914  
2915      if (isset($_POST['cancel']))
2916      {
2917          return false;
2918      }
2919  
2920      $confirm = false;
2921      if (isset($_POST['confirm']))
2922      {
2923          // language frontier
2924          if ($_POST['confirm'] === $user->lang['YES'])
2925          {
2926              $confirm = true;
2927          }
2928      }
2929  
2930      if ($check && $confirm)
2931      {
2932          $user_id = request_var('confirm_uid', 0);
2933          $session_id = request_var('sess', '');
2934          $confirm_key = request_var('confirm_key', '');
2935  
2936          if ($user_id != $user->data['user_id'] || $session_id != $user->session_id || !$confirm_key || !$user->data['user_last_confirm_key'] || $confirm_key != $user->data['user_last_confirm_key'])
2937          {
2938              return false;
2939          }
2940  
2941          // Reset user_last_confirm_key
2942          $sql = 'UPDATE ' . USERS_TABLE . " SET user_last_confirm_key = ''
2943              WHERE user_id = " . $user->data['user_id'];
2944          $db->sql_query($sql);
2945  
2946          return true;
2947      }
2948      else if ($check)
2949      {
2950          return false;
2951      }
2952  
2953      $s_hidden_fields = build_hidden_fields(array(
2954          'confirm_uid'    => $user->data['user_id'],
2955          'sess'            => $user->session_id,
2956          'sid'            => $user->session_id,
2957      ));
2958  
2959      // generate activation key
2960      $confirm_key = gen_rand_string(10);
2961  
2962      if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
2963      {
2964          adm_page_header((!isset($user->lang[$title])) ? $user->lang['CONFIRM'] : $user->lang[$title]);
2965      }
2966      else
2967      {
2968          page_header(((!isset($user->lang[$title])) ? $user->lang['CONFIRM'] : $user->lang[$title]), false);
2969      }
2970  
2971      $template->set_filenames(array(
2972          'body' => $html_body)
2973      );
2974  
2975      // If activation key already exist, we better do not re-use the key (something very strange is going on...)
2976      if (request_var('confirm_key', ''))
2977      {
2978          // This should not occur, therefore we cancel the operation to safe the user
2979          return false;
2980      }
2981  
2982      // re-add sid / transform & to &amp; for user->page (user->page is always using &)
2983      $use_page = ($u_action) ? $phpbb_root_path . $u_action : $phpbb_root_path . str_replace('&', '&amp;', $user->page['page']);
2984      $u_action = reapply_sid($use_page);
2985      $u_action .= ((strpos($u_action, '?') === false) ? '?' : '&amp;') . 'confirm_key=' . $confirm_key;
2986  
2987      $template->assign_vars(array(
2988          'MESSAGE_TITLE'        => (!isset($user->lang[$title])) ? $user->lang['CONFIRM'] : $user->lang[$title],
2989          'MESSAGE_TEXT'        => (!isset($user->lang[$title . '_CONFIRM'])) ? $title : $user->lang[$title . '_CONFIRM'],
2990  
2991          'YES_VALUE'            => $user->lang['YES'],
2992          'S_CONFIRM_ACTION'    => $u_action,
2993          'S_HIDDEN_FIELDS'    => $hidden . $s_hidden_fields)
2994      );
2995  
2996      $sql = 'UPDATE ' . USERS_TABLE . " SET user_last_confirm_key = '" . $db->sql_escape($confirm_key) . "'
2997          WHERE user_id = " . $user->data['user_id'];
2998      $db->sql_query($sql);
2999  
3000      if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
3001      {
3002          adm_page_footer();
3003      }
3004      else
3005      {
3006          page_footer();
3007      }
3008  }
3009  
3010  /**
3011  * Generate login box or verify password
3012  */
3013  function login_box($redirect = '', $l_explain = '', $l_success = '', $admin = false, $s_display = true)
3014  {
3015      global $db, $user, $template, $auth, $phpEx, $phpbb_root_path, $config;
3016  
3017      if (!class_exists('phpbb_captcha_factory'))
3018      {
3019          include($phpbb_root_path . 'includes/captcha/captcha_factory.' . $phpEx);
3020      }
3021  
3022      $err = '';
3023  
3024      // Make sure user->setup() has been called
3025      if (empty($user->lang))
3026      {
3027          $user->setup();
3028      }
3029  
3030      // Print out error if user tries to authenticate as an administrator without having the privileges...
3031      if ($admin && !$auth->acl_get('a_'))
3032      {
3033          // Not authd
3034          // anonymous/inactive users are never able to go to the ACP even if they have the relevant permissions
3035          if ($user->data['is_registered'])
3036          {
3037              add_log('admin', 'LOG_ADMIN_AUTH_FAIL');
3038          }
3039          trigger_error('NO_AUTH_ADMIN');
3040      }
3041  
3042      if (isset($_POST['login']))
3043      {
3044          // Get credential
3045          if ($admin)
3046          {
3047              $credential = request_var('credential', '');
3048  
3049              if (strspn($credential, 'abcdef0123456789') !== strlen($credential) || strlen($credential) != 32)
3050              {
3051                  if ($user->data['is_registered'])
3052                  {
3053                      add_log('admin', 'LOG_ADMIN_AUTH_FAIL');
3054                  }
3055                  trigger_error('NO_AUTH_ADMIN');
3056              }
3057  
3058              $password    = request_var('password_' . $credential, '', true);
3059          }
3060          else
3061          {
3062              $password    = request_var('password', '', true);
3063          }
3064  
3065          $username    = request_var('username', '', true);
3066          $autologin    = (!empty($_POST['autologin'])) ? true : false;
3067          $viewonline = (!empty($_POST['viewonline'])) ? 0 : 1;
3068          $admin         = ($admin) ? 1 : 0;
3069          $viewonline = ($admin) ? $user->data['session_viewonline'] : $viewonline;
3070  
3071          // Check if the supplied username is equal to the one stored within the database if re-authenticating
3072          if ($admin && utf8_clean_string($username) != utf8_clean_string($user->data['username']))
3073          {
3074              // We log the attempt to use a different username...
3075              add_log('admin', 'LOG_ADMIN_AUTH_FAIL');
3076              trigger_error('NO_AUTH_ADMIN_USER_DIFFER');
3077          }
3078  
3079          // If authentication is successful we redirect user to previous page
3080          $result = $auth->login($username, $password, $autologin, $viewonline, $admin);
3081  
3082          // If admin authentication and login, we will log if it was a success or not...
3083          // We also break the operation on the first non-success login - it could be argued that the user already knows
3084          if ($admin)
3085          {
3086              if ($result['status'] == LOGIN_SUCCESS)
3087              {
3088                  add_log('admin', 'LOG_ADMIN_AUTH_SUCCESS');
3089              }
3090              else
3091              {
3092                  // Only log the failed attempt if a real user tried to.
3093                  // anonymous/inactive users are never able to go to the ACP even if they have the relevant permissions
3094                  if ($user->data['is_registered'])
3095                  {
3096                      add_log('admin', 'LOG_ADMIN_AUTH_FAIL');
3097                  }
3098              }
3099          }
3100  
3101          // The result parameter is always an array, holding the relevant information...
3102          if ($result['status'] == LOGIN_SUCCESS)
3103          {
3104              $redirect = request_var('redirect', "{$phpbb_root_path}index.$phpEx");
3105              $message = ($l_success) ? $l_success : $user->lang['LOGIN_REDIRECT'];
3106              $l_redirect = ($admin) ? $user->lang['PROCEED_TO_ACP'] : (($redirect === "{$phpbb_root_path}index.$phpEx" || $redirect === "index.$phpEx") ? $user->lang['RETURN_INDEX'] : $user->lang['RETURN_PAGE']);
3107  
3108              // append/replace SID (may change during the session for AOL users)
3109              $redirect = reapply_sid($redirect);
3110  
3111              // Special case... the user is effectively banned, but we allow founders to login
3112              if (defined('IN_CHECK_BAN') && $result['user_row']['user_type'] != USER_FOUNDER)
3113              {
3114                  return;
3115              }
3116  
3117              $redirect = meta_refresh(3, $redirect);
3118              trigger_error($message . '<br /><br />' . sprintf($l_redirect, '<a href="' . $redirect . '">', '</a>'));
3119          }
3120  
3121          // Something failed, determine what...
3122          if ($result['status'] == LOGIN_BREAK)
3123          {
3124              trigger_error($result['error_msg']);
3125          }
3126  
3127          // Special cases... determine
3128          switch ($result['status'])
3129          {
3130              case LOGIN_ERROR_ATTEMPTS:
3131  
3132                  $captcha = phpbb_captcha_factory::get_instance($config['captcha_plugin']);
3133                  $captcha->init(CONFIRM_LOGIN);
3134                  // $captcha->reset();
3135  
3136                  $template->assign_vars(array(
3137                      'CAPTCHA_TEMPLATE'            => $captcha->get_template(),
3138                  ));
3139  
3140                  $err = $user->lang[$result['error_msg']];
3141              break;
3142  
3143              case LOGIN_ERROR_PASSWORD_CONVERT:
3144                  $err = sprintf(
3145                      $user->lang[$result['error_msg']],
3146                      ($config['email_enable']) ? '<a href="' . append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=sendpassword') . '">' : '',
3147                      ($config['email_enable']) ? '</a>' : '',
3148                      ($config['board_contact']) ? '<a href="mailto:' . htmlspecialchars($config['board_contact']) . '">' : '',
3149                      ($config['board_contact']) ? '</a>' : ''
3150                  );
3151              break;
3152  
3153              // Username, password, etc...
3154              default:
3155                  $err = $user->lang[$result['error_msg']];
3156  
3157                  // Assign admin contact to some error messages
3158                  if ($result['error_msg'] == 'LOGIN_ERROR_USERNAME' || $result['error_msg'] == 'LOGIN_ERROR_PASSWORD')
3159                  {
3160                      $err = (!$config['board_contact']) ? sprintf($user->lang[$result['error_msg']], '', '') : sprintf($user->lang[$result['error_msg']], '<a href="mailto:' . htmlspecialchars($config['board_contact']) . '">', '</a>');
3161                  }
3162  
3163              break;
3164          }
3165      }
3166  
3167      // Assign credential for username/password pair
3168      $credential = ($admin) ? md5(unique_id()) : false;
3169  
3170      $s_hidden_fields = array(
3171          'sid'        => $user->session_id,
3172      );
3173  
3174      if ($redirect)
3175      {
3176          $s_hidden_fields['redirect'] = $redirect;
3177      }
3178  
3179      if ($admin)
3180      {
3181          $s_hidden_fields['credential'] = $credential;
3182      }
3183  
3184      $s_hidden_fields = build_hidden_fields($s_hidden_fields);
3185  
3186      $template->assign_vars(array(
3187          'LOGIN_ERROR'        => $err,
3188          'LOGIN_EXPLAIN'        => $l_explain,
3189  
3190          'U_SEND_PASSWORD'         => ($config['email_enable']) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=sendpassword') : '',
3191          'U_RESEND_ACTIVATION'    => ($config['require_activation'] == USER_ACTIVATION_SELF && $config['email_enable']) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=resend_act') : '',
3192          'U_TERMS_USE'            => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=terms'),
3193          'U_PRIVACY'                => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy'),
3194  
3195          'S_DISPLAY_FULL_LOGIN'    => ($s_display) ? true : false,
3196          'S_HIDDEN_FIELDS'         => $s_hidden_fields,
3197  
3198          'S_ADMIN_AUTH'            => $admin,
3199          'USERNAME'                => ($admin) ? $user->data['username'] : '',
3200  
3201          'USERNAME_CREDENTIAL'    => 'username',
3202          'PASSWORD_CREDENTIAL'    => ($admin) ? 'password_' . $credential : 'password',
3203      ));
3204  
3205      page_header($user->lang['LOGIN'], false);
3206  
3207      $template->set_filenames(array(
3208          'body' => 'login_body.html')
3209      );
3210      make_jumpbox(append_sid("{$phpbb_root_path}viewforum.$phpEx"));
3211  
3212      page_footer();
3213  }
3214  
3215  /**
3216  * Generate forum login box
3217  */
3218  function login_forum_box($forum_data)
3219  {
3220      global $db, $config, $user, $template, $phpEx;
3221  
3222      $password = request_var('password', '', true);
3223  
3224      $sql = 'SELECT forum_id
3225          FROM ' . FORUMS_ACCESS_TABLE . '
3226          WHERE forum_id = ' . $forum_data['forum_id'] . '
3227              AND user_id = ' . $user->data['user_id'] . "
3228              AND session_id = '" . $db->sql_escape($user->session_id) . "'";
3229      $result = $db->sql_query($sql);
3230      $row = $db->sql_fetchrow($result);
3231      $db->sql_freeresult($result);
3232  
3233      if ($row)
3234      {
3235          return true;
3236      }
3237  
3238      if ($password)
3239      {
3240          // Remove expired authorised sessions
3241          $sql = 'SELECT f.session_id
3242              FROM ' . FORUMS_ACCESS_TABLE . ' f
3243              LEFT JOIN ' . SESSIONS_TABLE . ' s ON (f.session_id = s.session_id)
3244              WHERE s.session_id IS NULL';
3245          $result = $db->sql_query($sql);
3246  
3247          if ($row = $db->sql_fetchrow($result))
3248          {
3249              $sql_in = array();
3250              do
3251              {
3252                  $sql_in[] = (string) $row['session_id'];
3253              }
3254              while ($row = $db->sql_fetchrow($result));
3255  
3256              // Remove expired sessions
3257              $sql = 'DELETE FROM ' . FORUMS_ACCESS_TABLE . '
3258                  WHERE ' . $db->sql_in_set('session_id', $sql_in);
3259              $db->sql_query($sql);
3260          }
3261          $db->sql_freeresult($result);
3262  
3263          if (phpbb_check_hash($password, $forum_data['forum_password']))
3264          {
3265              $sql_ary = array(
3266                  'forum_id'        => (int) $forum_data['forum_id'],
3267                  'user_id'        => (int) $user->data['user_id'],
3268                  'session_id'    => (string) $user->session_id,
3269              );
3270  
3271              $db->sql_query('INSERT INTO ' . FORUMS_ACCESS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
3272  
3273              return true;
3274          }
3275  
3276          $template->assign_var('LOGIN_ERROR', $user->lang['WRONG_PASSWORD']);
3277      }
3278  
3279      page_header($user->lang['LOGIN'], false);
3280  
3281      $template->assign_vars(array(
3282          'FORUM_NAME'            => isset($forum_data['forum_name']) ? $forum_data['forum_name'] : '',
3283          'S_LOGIN_ACTION'        => build_url(array('f')),
3284          'S_HIDDEN_FIELDS'        => build_hidden_fields(array('f' => $forum_data['forum_id'])))
3285      );
3286  
3287      $template->set_filenames(array(
3288          'body' => 'login_forum.html')
3289      );
3290  
3291      page_footer();
3292  }
3293  
3294  // Little helpers
3295  
3296  /**
3297  * Little helper for the build_hidden_fields function
3298  */
3299  function _build_hidden_fields($key, $value, $specialchar, $stripslashes)
3300  {
3301      $hidden_fields = '';
3302  
3303      if (!is_array($value))
3304      {
3305          $value = ($stripslashes) ? stripslashes($value) : $value;
3306          $value = ($specialchar) ? htmlspecialchars($value, ENT_COMPAT, 'UTF-8') : $value;
3307  
3308          $hidden_fields .= '<input type="hidden" name="' . $key . '" value="' . $value . '" />' . "\n";
3309      }
3310      else
3311      {
3312          foreach ($value as $_key => $_value)
3313          {
3314              $_key = ($stripslashes) ? stripslashes($_key) : $_key;
3315              $_key = ($specialchar) ? htmlspecialchars($_key, ENT_COMPAT, 'UTF-8') : $_key;
3316  
3317              $hidden_fields .= _build_hidden_fields($key . '[' . $_key . ']', $_value, $specialchar, $stripslashes);
3318          }
3319      }
3320  
3321      return $hidden_fields;
3322  }
3323  
3324  /**
3325  * Build simple hidden fields from array
3326  *
3327  * @param array $field_ary an array of values to build the hidden field from
3328  * @param bool $specialchar if true, keys and values get specialchared
3329  * @param bool $stripslashes if true, keys and values get stripslashed
3330  *
3331  * @return string the hidden fields
3332  */
3333  function build_hidden_fields($field_ary, $specialchar = false, $stripslashes = false)
3334  {
3335      $s_hidden_fields = '';
3336  
3337      foreach ($field_ary as $name => $vars)
3338      {
3339          $name = ($stripslashes) ? stripslashes($name) : $name;
3340          $name = ($specialchar) ? htmlspecialchars($name, ENT_COMPAT, 'UTF-8') : $name;
3341  
3342          $s_hidden_fields .= _build_hidden_fields($name, $vars, $specialchar, $stripslashes);
3343      }
3344  
3345      return $s_hidden_fields;
3346  }
3347  
3348  /**
3349  * Parse cfg file
3350  */
3351  function parse_cfg_file($filename, $lines = false)
3352  {
3353      $parsed_items = array();
3354  
3355      if ($lines === false)
3356      {
3357          $lines = file($filename);
3358      }
3359  
3360      foreach ($lines as $line)
3361      {
3362          $line = trim($line);
3363  
3364          if (!$line || $line[0] == '#' || ($delim_pos = strpos($line, '=')) === false)
3365          {
3366              continue;
3367          }
3368  
3369          // Determine first occurrence, since in values the equal sign is allowed
3370          $key = strtolower(trim(substr($line, 0, $delim_pos)));
3371          $value = trim(substr($line, $delim_pos + 1));
3372  
3373          if (in_array($value, array('off', 'false', '0')))
3374          {
3375              $value = false;
3376          }
3377          else if (in_array($value, array('on', 'true', '1')))
3378          {
3379              $value = true;
3380          }
3381          else if (!trim($value))
3382          {
3383              $value = '';
3384          }
3385          else if (($value[0] == "'" && $value[sizeof($value) - 1] == "'") || ($value[0] == '"' && $value[sizeof($value) - 1] == '"'))
3386          {
3387              $value = substr($value, 1, sizeof($value)-2);
3388          }
3389  
3390          $parsed_items[$key] = $value;
3391      }
3392      
3393      if (isset($parsed_items['inherit_from']) && isset($parsed_items['name']) && $parsed_items['inherit_from'] == $parsed_items['name'])
3394      {
3395          unset($parsed_items['inherit_from']);
3396      }
3397  
3398      return $parsed_items;
3399  }
3400  
3401  /**
3402  * Add log event
3403  */
3404  function add_log()
3405  {
3406      global $db, $user;
3407  
3408      // In phpBB 3.1.x i want to have logging in a class to be able to control it
3409      // For now, we need a quite hakish approach to circumvent logging for some actions
3410      // @todo implement cleanly
3411      if (!empty($GLOBALS['skip_add_log']))
3412      {
3413          return false;
3414      }
3415  
3416      $args = func_get_args();
3417  
3418      $mode            = array_shift($args);
3419      $reportee_id    = ($mode == 'user') ? intval(array_shift($args)) : '';
3420      $forum_id        = ($mode == 'mod') ? intval(array_shift($args)) : '';
3421      $topic_id        = ($mode == 'mod') ? intval(array_shift($args)) : '';
3422      $action            = array_shift($args);
3423      $data            = (!sizeof($args)) ? '' : serialize($args);
3424  
3425      $sql_ary = array(
3426          'user_id'        => (empty($user->data)) ? ANONYMOUS : $user->data['user_id'],
3427          'log_ip'        => $user->ip,
3428          'log_time'        => time(),
3429          'log_operation'    => $action,
3430          'log_data'        => $data,
3431      );
3432  
3433      switch ($mode)
3434      {
3435          case 'admin':
3436              $sql_ary['log_type'] = LOG_ADMIN;
3437          break;
3438  
3439          case 'mod':
3440              $sql_ary += array(
3441                  'log_type'    => LOG_MOD,
3442                  'forum_id'    => $forum_id,
3443                  'topic_id'    => $topic_id
3444              );
3445          break;
3446  
3447          case 'user':
3448              $sql_ary += array(
3449                  'log_type'        => LOG_USERS,
3450                  'reportee_id'    => $reportee_id
3451              );
3452          break;
3453  
3454          case 'critical':
3455              $sql_ary['log_type'] = LOG_CRITICAL;
3456          break;
3457  
3458          default:
3459              return false;
3460      }
3461  
3462      $db->sql_query('INSERT INTO ' . LOG_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
3463  
3464      return $db->sql_nextid();
3465  }
3466  
3467  /**
3468  * Return a nicely formatted backtrace.
3469  *
3470  * Turns the array returned by debug_backtrace() into HTML markup.
3471  * Also filters out absolute paths to phpBB root.
3472  *
3473  * @return string    HTML markup
3474  */
3475  function get_backtrace()
3476  {
3477      $output = '<div style="font-family: monospace;">';
3478      $backtrace = debug_backtrace();
3479  
3480      // We skip the first one, because it only shows this file/function
3481      unset($backtrace[0]);
3482  
3483      foreach ($backtrace as $trace)
3484      {
3485          // Strip the current directory from path
3486          $trace['file'] = (empty($trace['file'])) ? '(not given by php)' : htmlspecialchars(phpbb_filter_root_path($trace['file']));
3487          $trace['line'] = (empty($trace['line'])) ? '(not given by php)' : $trace['line'];
3488  
3489          // Only show function arguments for include etc.
3490          // Other parameters may contain sensible information
3491          $argument = '';
3492          if (!empty($trace['args'][0]) && in_array($trace['function'], array('include', 'require', 'include_once', 'require_once')))
3493          {
3494              $argument = htmlspecialchars(phpbb_filter_root_path($trace['args'][0]));
3495          }
3496  
3497          $trace['class'] = (!isset($trace['class'])) ? '' : $trace['class'];
3498          $trace['type'] = (!isset($trace['type'])) ? '' : $trace['type'];
3499  
3500          $output .= '<br />';
3501          $output .= '<b>FILE:</b> ' . $trace['file'] . '<br />';
3502          $output .= '<b>LINE:</b> ' . ((!empty($trace['line'])) ? $trace['line'] : '') . '<br />';
3503  
3504          $output .= '<b>CALL:</b> ' . htmlspecialchars($trace['class'] . $trace['type'] . $trace['function']);
3505          $output .= '(' . (($argument !== '') ? "'$argument'" : '') . ')<br />';
3506      }
3507      $output .= '</div>';
3508      return $output;
3509  }
3510  
3511  /**
3512  * This function returns a regular expression pattern for commonly used expressions
3513  * Use with / as delimiter for email mode and # for url modes
3514  * mode can be: email|bbcode_htm|url|url_inline|www_url|www_url_inline|relative_url|relative_url_inline|ipv4|ipv6
3515  */
3516  function get_preg_expression($mode)
3517  {
3518      switch ($mode)
3519      {
3520          case 'email':
3521              // Regex written by James Watts and Francisco Jose Martin Moreno
3522              // http://fightingforalostcause.net/misc/2006/compare-email-regex.php
3523              return '([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*(?:[\w\!\#$\%\'\*\+\-\/\=\?\^\`{\|\}\~]|&amp;)+@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,63})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)';
3524          break;
3525  
3526          case 'bbcode_htm':
3527              return array(
3528                  '#<!\-\- e \-\-><a href="mailto:(.*?)">.*?</a><!\-\- e \-\->#',
3529                  '#<!\-\- l \-\-><a (?:class="[\w-]+" )?href="(.*?)(?:(&amp;|\?)sid=[0-9a-f]{32})?">.*?</a><!\-\- l \-\->#',
3530                  '#<!\-\- ([mw]) \-\-><a (?:class="[\w-]+" )?href="(.*?)">.*?</a><!\-\- \1 \-\->#',
3531                  '#<!\-\- s(.*?) \-\-><img src="\{SMILIES_PATH\}\/.*? \/><!\-\- s\1 \-\->#',
3532                  '#<!\-\- .*? \-\->#s',
3533                  '#<.*?>#s',
3534              );
3535          break;
3536  
3537          // Whoa these look impressive!
3538          // The code to generate the following two regular expressions which match valid IPv4/IPv6 addresses
3539          // can be found in the develop directory
3540          case 'ipv4':
3541              return '#^(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$#';
3542          break;
3543  
3544          case 'ipv6':
3545              return '#^(?:(?:(?:[\dA-F]{1,4}:){6}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:::(?:[\dA-F]{1,4}:){0,5}(?:[\dA-F]{1,4}(?::[\dA-F]{1,4})?|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:):(?:[\dA-F]{1,4}:){4}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,2}:(?:[\dA-F]{1,4}:){3}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,3}:(?:[\dA-F]{1,4}:){2}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,4}:(?:[\dA-F]{1,4}:)(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,5}:(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,6}:[\dA-F]{1,4})|(?:(?:[\dA-F]{1,4}:){1,7}:)|(?:::))$#i';
3546          break;
3547  
3548          case 'url':
3549          case 'url_inline':
3550              $inline = ($mode == 'url') ? ')' : '';
3551              $scheme = ($mode == 'url') ? '[a-z\d+\-.]' : '[a-z\d+]'; // avoid automatic parsing of "word" in "last word.http://..."
3552              // generated with regex generation file in the develop folder
3553              return "[a-z]$scheme*:/{2}(?:(?:[a-z0-9\-._~!$&'($inline*+,;=:@|]+|%[\dA-F]{2})+|[0-9.]+|\[[a-z0-9.]+:[a-z0-9.]+:[a-z0-9.:]+\])(?::\d*)?(?:/(?:[a-z0-9\-._~!$&'($inline*+,;=:@|]+|%[\dA-F]{2})*)*(?:\?(?:[a-z0-9\-._~!$&'($inline*+,;=:@/?|]+|%[\dA-F]{2})*)?(?:\#(?:[a-z0-9\-._~!$&'($inline*+,;=:@/?|]+|%[\dA-F]{2})*)?";
3554          break;
3555  
3556          case 'www_url':
3557          case 'www_url_inline':
3558              $inline = ($mode == 'www_url') ? ')' : '';
3559              return "www\.(?:[a-z0-9\-._~!$&'($inline*+,;=:@|]+|%[\dA-F]{2})+(?::\d*)?(?:/(?:[a-z0-9\-._~!$&'($inline*+,;=:@|]+|%[\dA-F]{2})*)*(?:\?(?:[a-z0-9\-._~!$&'($inline*+,;=:@/?|]+|%[\dA-F]{2})*)?(?:\#(?:[a-z0-9\-._~!$&'($inline*+,;=:@/?|]+|%[\dA-F]{2})*)?";
3560          break;
3561  
3562          case 'relative_url':
3563          case 'relative_url_inline':
3564              $inline = ($mode == 'relative_url') ? ')' : '';
3565              return "(?:[a-z0-9\-._~!$&'($inline*+,;=:@|]+|%[\dA-F]{2})*(?:/(?:[a-z0-9\-._~!$&'($inline*+,;=:@|]+|%[\dA-F]{2})*)*(?:\?(?:[a-z0-9\-._~!$&'($inline*+,;=:@/?|]+|%[\dA-F]{2})*)?(?:\#(?:[a-z0-9\-._~!$&'($inline*+,;=:@/?|]+|%[\dA-F]{2})*)?";
3566          break;
3567  
3568          case 'table_prefix':
3569              return '#^[a-zA-Z][a-zA-Z0-9_]*$#';
3570          break;
3571      }
3572  
3573      return '';
3574  }
3575  
3576  /**
3577  * Generate regexp for naughty words censoring
3578  * Depends on whether installed PHP version supports unicode properties
3579  *
3580  * @param string    $word            word template to be replaced
3581  * @param bool    $use_unicode    whether or not to take advantage of PCRE supporting unicode
3582  *
3583  * @return string $preg_expr        regex to use with word censor
3584  */
3585  function get_censor_preg_expression($word, $use_unicode = true)
3586  {
3587      static $unicode_support = null;
3588  
3589      // Check whether PHP version supports unicode properties
3590      if (is_null($unicode_support))
3591      {
3592          $unicode_support = ((version_compare(PHP_VERSION, '5.1.0', '>=') || (version_compare(PHP_VERSION, '5.0.0-dev', '<=') && version_compare(PHP_VERSION, '4.4.0', '>='))) && @preg_match('/\p{L}/u', 'a') !== false) ? true : false;
3593      }
3594  
3595      // Unescape the asterisk to simplify further conversions
3596      $word = str_replace('\*', '*', preg_quote($word, '#'));
3597  
3598      if ($use_unicode && $unicode_support)
3599      {
3600          // Replace asterisk(s) inside the pattern, at the start and at the end of it with regexes
3601          $word = preg_replace(array('#(?<=[\p{Nd}\p{L}_])\*+(?=[\p{Nd}\p{L}_])#iu', '#^\*+#', '#\*+$#'), array('([\x20]*?|[\p{Nd}\p{L}_-]*?)', '[\p{Nd}\p{L}_-]*?', '[\p{Nd}\p{L}_-]*?'), $word);
3602  
3603          // Generate the final substitution
3604          $preg_expr = '#(?<![\p{Nd}\p{L}_-])(' . $word . ')(?![\p{Nd}\p{L}_-])#iu';
3605      }
3606      else
3607      {
3608          // Replace the asterisk inside the pattern, at the start and at the end of it with regexes
3609          $word = preg_replace(array('#(?<=\S)\*+(?=\S)#iu', '#^\*+#', '#\*+$#'), array('(\x20*?\S*?)', '\S*?', '\S*?'), $word);
3610  
3611          // Generate the final substitution
3612          $preg_expr = '#(?<!\S)(' . $word . ')(?!\S)#iu';
3613      }
3614  
3615      return $preg_expr;
3616  }
3617  
3618  /**
3619  * Returns the first block of the specified IPv6 address and as many additional
3620  * ones as specified in the length paramater.
3621  * If length is zero, then an empty string is returned.
3622  * If length is greater than 3 the complete IP will be returned
3623  */
3624  function short_ipv6($ip, $length)
3625  {
3626      if ($length < 1)
3627      {
3628          return '';
3629      }
3630  
3631      // extend IPv6 addresses
3632      $blocks = substr_count($ip, ':') + 1;
3633      if ($blocks < 9)
3634      {
3635          $ip = str_replace('::', ':' . str_repeat('0000:', 9 - $blocks), $ip);
3636      }
3637      if ($ip[0] == ':')
3638      {
3639          $ip = '0000' . $ip;
3640      }
3641      if ($length < 4)
3642      {
3643          $ip = implode(':', array_slice(explode(':', $ip), 0, 1 + $length));
3644      }
3645  
3646      return $ip;
3647  }
3648  
3649  /**
3650  * Wrapper for php's checkdnsrr function.
3651  *
3652  * @param string $host    Fully-Qualified Domain Name
3653  * @param string $type    Resource record type to lookup
3654  *                        Supported types are: MX (default), A, AAAA, NS, TXT, CNAME
3655  *                        Other types may work or may not work
3656  *
3657  * @return mixed        true if entry found,
3658  *                    false if entry not found,
3659  *                    null if this function is not supported by this environment
3660  *
3661  * Since null can also be returned, you probably want to compare the result
3662  * with === true or === false,
3663  *
3664  * @author bantu
3665  */
3666  function phpbb_checkdnsrr($host, $type = 'MX')
3667  {
3668      // The dot indicates to search the DNS root (helps those having DNS prefixes on the same domain)
3669      if (substr($host, -1) == '.')
3670      {
3671          $host_fqdn = $host;
3672          $host = substr($host, 0, -1);
3673      }
3674      else
3675      {
3676          $host_fqdn = $host . '.';
3677      }
3678      // $host        has format    some.host.example.com
3679      // $host_fqdn    has format    some.host.example.com.
3680  
3681      // If we're looking for an A record we can use gethostbyname()
3682      if ($type == 'A' && function_exists('gethostbyname'))
3683      {
3684          return (@gethostbyname($host_fqdn) == $host_fqdn) ? false : true;
3685      }
3686  
3687      // checkdnsrr() is available on Windows since PHP 5.3,
3688      // but until 5.3.3 it only works for MX records
3689      // See: http://bugs.php.net/bug.php?id=51844
3690  
3691      // Call checkdnsrr() if
3692      // we're looking for an MX record or
3693      // we're not on Windows or
3694      // we're running a PHP version where #51844 has been fixed
3695  
3696      // checkdnsrr() supports AAAA since 5.0.0
3697      // checkdnsrr() supports TXT since 5.2.4
3698      if (
3699          ($type == 'MX' || DIRECTORY_SEPARATOR != '\\' || version_compare(PHP_VERSION, '5.3.3', '>=')) &&
3700          ($type != 'AAAA' || version_compare(PHP_VERSION, '5.0.0', '>=')) &&
3701          ($type != 'TXT' || version_compare(PHP_VERSION, '5.2.4', '>=')) &&
3702          function_exists('checkdnsrr')
3703      )
3704      {
3705          return checkdnsrr($host_fqdn, $type);
3706      }
3707  
3708      // dns_get_record() is available since PHP 5; since PHP 5.3 also on Windows,
3709      // but on Windows it does not work reliable for AAAA records before PHP 5.3.1
3710  
3711      // Call dns_get_record() if
3712      // we're not looking for an AAAA record or
3713      // we're not on Windows or
3714      // we're running a PHP version where AAAA lookups work reliable
3715      if (
3716          ($type != 'AAAA' || DIRECTORY_SEPARATOR != '\\' || version_compare(PHP_VERSION, '5.3.1', '>=')) &&
3717          function_exists('dns_get_record')
3718      )
3719      {
3720          // dns_get_record() expects an integer as second parameter
3721          // We have to convert the string $type to the corresponding integer constant.
3722          $type_constant = 'DNS_' . $type;
3723          $type_param = (defined($type_constant)) ? constant($type_constant) : DNS_ANY;
3724  
3725          // dns_get_record() might throw E_WARNING and return false for records that do not exist
3726          $resultset = @dns_get_record($host_fqdn, $type_param);
3727  
3728          if (empty($resultset) || !is_array($resultset))
3729          {
3730              return false;
3731          }
3732          else if ($type_param == DNS_ANY)
3733          {
3734              // $resultset is a non-empty array
3735              return true;
3736          }
3737  
3738          foreach ($resultset as $result)
3739          {
3740              if (
3741                  isset($result['host']) && $result['host'] == $host &&
3742                  isset($result['type']) && $result['type'] == $type
3743              )
3744              {
3745                  return true;
3746              }
3747          }
3748  
3749          return false;
3750      }
3751  
3752      // If we're on Windows we can still try to call nslookup via exec() as a last resort
3753      if (DIRECTORY_SEPARATOR == '\\' && function_exists('exec'))
3754      {
3755          @exec('nslookup -type=' . escapeshellarg($type) . ' ' . escapeshellarg($host_fqdn), $output);
3756  
3757          // If output is empty, the nslookup failed
3758          if (empty($output))
3759          {
3760              return NULL;
3761          }
3762  
3763          foreach ($output as $line)
3764          {
3765              $line = trim($line);
3766  
3767              if (empty($line))
3768              {
3769                  continue;
3770              }
3771  
3772              // Squash tabs and multiple whitespaces to a single whitespace.
3773              $line = preg_replace('/\s+/', ' ', $line);
3774  
3775              switch ($type)
3776              {
3777                  case 'MX':
3778                      if (stripos($line, "$host MX") === 0)
3779                      {
3780                          return true;
3781                      }
3782                  break;
3783  
3784                  case 'NS':
3785                      if (stripos($line, "$host nameserver") === 0)
3786                      {
3787                          return true;
3788                      }
3789                  break;
3790  
3791                  case 'TXT':
3792                      if (stripos($line, "$host text") === 0)
3793                      {
3794                          return true;
3795                      }
3796                  break;
3797  
3798                  case 'CNAME':
3799                      if (stripos($line, "$host canonical name") === 0)
3800                      {
3801                          return true;
3802                      }
3803                  break;
3804  
3805                  default:
3806                  case 'AAAA':
3807                      // AAAA records returned by nslookup on Windows XP/2003 have this format.
3808                      // Later Windows versions use the A record format below for AAAA records.
3809                      if (stripos($line, "$host AAAA IPv6 address") === 0)
3810                      {
3811                          return true;
3812                      }
3813                  // No break
3814  
3815                  case 'A':
3816                      if (!empty($host_matches))
3817                      {
3818                          // Second line
3819                          if (stripos($line, "Address: ") === 0)
3820                          {
3821                              return true;
3822                          }
3823                          else
3824                          {
3825                              $host_matches = false;
3826                          }
3827                      }
3828                      else if (stripos($line, "Name: $host") === 0)
3829                      {
3830                          // First line
3831                          $host_matches = true;
3832                      }
3833                  break;
3834              }
3835          }
3836  
3837          return false;
3838      }
3839  
3840      return NULL;
3841  }
3842  
3843  // Handler, header and footer
3844  
3845  /**
3846  * Error and message handler, call with trigger_error if reqd
3847  */
3848  function msg_handler($errno, $msg_text, $errfile, $errline)
3849  {
3850      global $cache, $db, $auth, $template, $config, $user;
3851      global $phpEx, $phpbb_root_path, $msg_title, $msg_long_text;
3852  
3853      // Do not display notices if we suppress them via @
3854      if (error_reporting() == 0 && $errno != E_USER_ERROR && $errno != E_USER_WARNING && $errno != E_USER_NOTICE)
3855      {
3856          return;
3857      }
3858  
3859      // Message handler is stripping text. In case we need it, we are possible to define long text...
3860      if (isset($msg_long_text) && $msg_long_text && !$msg_text)
3861      {
3862          $msg_text = $msg_long_text;
3863      }
3864  
3865      if (!defined('E_DEPRECATED'))
3866      {
3867          define('E_DEPRECATED', 8192);
3868      }
3869  
3870      switch ($errno)
3871      {
3872          case E_NOTICE:
3873          case E_WARNING:
3874  
3875              // Check the error reporting level and return if the error level does not match
3876              // If DEBUG is defined the default level is E_ALL
3877              if (($errno & ((defined('DEBUG')) ? E_ALL : error_reporting())) == 0)
3878              {
3879                  return;
3880              }
3881  
3882              if (strpos($errfile, 'cache') === false && strpos($errfile, 'template.') === false)
3883              {
3884                  $errfile = phpbb_filter_root_path($errfile);
3885                  $msg_text = phpbb_filter_root_path($msg_text);
3886                  $error_name = ($errno === E_WARNING) ? 'PHP Warning' : 'PHP Notice';
3887                  echo '<b>[phpBB Debug] ' . $error_name . '</b>: in file <b>' . $errfile . '</b> on line <b>' . $errline . '</b>: <b>' . $msg_text . '</b><br />' . "\n";
3888  
3889                  // we are writing an image - the user won't see the debug, so let's place it in the log
3890                  if (defined('IMAGE_OUTPUT') || defined('IN_CRON'))
3891                  {
3892                      add_log('critical', 'LOG_IMAGE_GENERATION_ERROR', $errfile, $errline, $msg_text);
3893                  }
3894                  // echo '<br /><br />BACKTRACE<br />' . get_backtrace() . '<br />' . "\n";
3895              }
3896  
3897              return;
3898  
3899          break;
3900  
3901          case E_USER_ERROR:
3902  
3903              if (!empty($user) && !empty($user->lang))
3904              {
3905                  $msg_text = (!empty($user->lang[$msg_text])) ? $user->lang[$msg_text] : $msg_text;
3906                  $msg_title = (!isset($msg_title)) ? $user->lang['GENERAL_ERROR'] : ((!empty($user->lang[$msg_title])) ? $user->lang[$msg_title] : $msg_title);
3907  
3908                  $l_return_index = sprintf($user->lang['RETURN_INDEX'], '<a href="' . $phpbb_root_path . '">', '</a>');
3909                  $l_notify = '';
3910  
3911                  if (!empty($config['board_contact']))
3912                  {
3913                      $l_notify = '<p>' . sprintf($user->lang['NOTIFY_ADMIN_EMAIL'], $config['board_contact']) . '</p>';
3914                  }
3915              }
3916              else
3917              {
3918                  $msg_title = 'General Error';
3919                  $l_return_index = '<a href="' . $phpbb_root_path . '">Return to index page</a>';
3920                  $l_notify = '';
3921  
3922                  if (!empty($config['board_contact']))
3923                  {
3924                      $l_notify = '<p>Please notify the board administrator or webmaster: <a href="mailto:' . $config['board_contact'] . '">' . $config['board_contact'] . '</a></p>';
3925                  }
3926              }
3927  
3928              $log_text = $msg_text;
3929              $backtrace = get_backtrace();
3930              if ($backtrace)
3931              {
3932                  $log_text .= '<br /><br />BACKTRACE<br />' . $backtrace;
3933              }
3934  
3935              if (defined('IN_INSTALL') || defined('DEBUG_EXTRA') || isset($auth) && $auth->acl_get('a_'))
3936              {
3937                  $msg_text = $log_text;
3938              }
3939  
3940              if ((defined('DEBUG') || defined('IN_CRON') || defined('IMAGE_OUTPUT')) && isset($db))
3941              {
3942                  // let's avoid loops
3943                  $db->sql_return_on_error(true);
3944                  add_log('critical', 'LOG_GENERAL_ERROR', $msg_title, $log_text);
3945                  $db->sql_return_on_error(false);
3946              }
3947  
3948              // Do not send 200 OK, but service unavailable on errors
3949              send_status_line(503, 'Service Unavailable');
3950  
3951              garbage_collection();
3952  
3953              // Try to not call the adm page data...
3954  
3955              echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
3956              echo '<html xmlns="http://www.w3.org/1999/xhtml" dir="ltr">';
3957              echo '<head>';
3958              echo '<meta http-equiv="content-type" content="text/html; charset=utf-8" />';
3959              echo '<title>' . $msg_title . '</title>';
3960              echo '<style type="text/css">' . "\n" . '/* <![CDATA[ */' . "\n";
3961              echo '* { margin: 0; padding: 0; } html { font-size: 100%; height: 100%; margin-bottom: 1px; background-color: #E4EDF0; } body { font-family: "Lucida Grande", Verdana, Helvetica, Arial, sans-serif; color: #536482; background: #E4EDF0; font-size: 62.5%; margin: 0; } ';
3962              echo 'a:link, a:active, a:visited { color: #006699; text-decoration: none; } a:hover { color: #DD6900; text-decoration: underline; } ';
3963              echo '#wrap { padding: 0 20px 15px 20px; min-width: 615px; } #page-header { text-align: right; height: 40px; } #page-footer { clear: both; font-size: 1em; text-align: center; } ';
3964              echo '.panel { margin: 4px 0; background-color: #FFFFFF; border: solid 1px  #A9B8C2; } ';
3965              echo '#errorpage #page-header a { font-weight: bold; line-height: 6em; } #errorpage #content { padding: 10px; } #errorpage #content h1 { line-height: 1.2em; margin-bottom: 0; color: #DF075C; } ';
3966              echo '#errorpage #content div { margin-top: 20px; margin-bottom: 5px; border-bottom: 1px solid #CCCCCC; padding-bottom: 5px; color: #333333; font: bold 1.2em "Lucida Grande", Arial, Helvetica, sans-serif; text-decoration: none; line-height: 120%; text-align: left; } ';
3967              echo "\n" . '/* ]]> */' . "\n";
3968              echo '</style>';
3969              echo '</head>';
3970              echo '<body id="errorpage">';
3971              echo '<div id="wrap">';
3972              echo '    <div id="page-header">';
3973              echo '        ' . $l_return_index;
3974              echo '    </div>';
3975              echo '    <div id="acp">';
3976              echo '    <div class="panel">';
3977              echo '        <div id="content">';
3978              echo '            <h1>' . $msg_title . '</h1>';
3979  
3980              echo '            <div>' . $msg_text . '</div>';
3981  
3982              echo $l_notify;
3983  
3984              echo '        </div>';
3985              echo '    </div>';
3986              echo '    </div>';
3987              echo '    <div id="page-footer">';
3988              echo '        Powered by <a href="https://www.phpbb.com/">phpBB</a>&reg; Forum Software &copy; phpBB Group';
3989              echo '    </div>';
3990              echo '</div>';
3991              echo '</body>';
3992              echo '</html>';
3993  
3994              exit_handler();
3995  
3996              // On a fatal error (and E_USER_ERROR *is* fatal) we never want other scripts to continue and force an exit here.
3997              exit;
3998          break;
3999  
4000          case E_USER_WARNING:
4001          case E_USER_NOTICE:
4002  
4003              define('IN_ERROR_HANDLER', true);
4004  
4005              if (empty($user->data))
4006              {
4007                  $user->session_begin();
4008              }
4009  
4010              // We re-init the auth array to get correct results on login/logout
4011              $auth->acl($user->data);
4012  
4013              if (empty($user->lang))
4014              {
4015                  $user->setup();
4016              }
4017  
4018              if ($msg_text == 'ERROR_NO_ATTACHMENT' || $msg_text == 'NO_FORUM' || $msg_text == 'NO_TOPIC' || $msg_text == 'NO_USER')
4019              {
4020                  send_status_line(404, 'Not Found');
4021              }
4022  
4023              $msg_text = (!empty($user->lang[$msg_text])) ? $user->lang[$msg_text] : $msg_text;
4024              $msg_title = (!isset($msg_title)) ? $user->lang['INFORMATION'] : ((!empty($user->lang[$msg_title])) ? $user->lang[$msg_title] : $msg_title);
4025  
4026              if (!defined('HEADER_INC'))
4027              {
4028                  if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
4029                  {
4030                      adm_page_header($msg_title);
4031                  }
4032                  else
4033                  {
4034                      page_header($msg_title, false);
4035                  }
4036              }
4037  
4038              $template->set_filenames(array(
4039                  'body' => 'message_body.html')
4040              );
4041  
4042              $template->assign_vars(array(
4043                  'MESSAGE_TITLE'        => $msg_title,
4044                  'MESSAGE_TEXT'        => $msg_text,
4045                  'S_USER_WARNING'    => ($errno == E_USER_WARNING) ? true : false,
4046                  'S_USER_NOTICE'        => ($errno == E_USER_NOTICE) ? true : false)
4047              );
4048  
4049              // We do not want the cron script to be called on error messages
4050              define('IN_CRON', true);
4051  
4052              if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
4053              {
4054                  adm_page_footer();
4055              }
4056              else
4057              {
4058                  page_footer();
4059              }
4060  
4061              exit_handler();
4062          break;
4063  
4064          // PHP4 compatibility
4065          case E_DEPRECATED:
4066              return true;
4067          break;
4068      }
4069  
4070      // If we notice an error not handled here we pass this back to PHP by returning false
4071      // This may not work for all php versions
4072      return false;
4073  }
4074  
4075  /**
4076  * Removes absolute path to phpBB root directory from error messages
4077  * and converts backslashes to forward slashes.
4078  *
4079  * @param string $errfile    Absolute file path
4080  *                            (e.g. /var/www/phpbb3/phpBB/includes/functions.php)
4081  *                            Please note that if $errfile is outside of the phpBB root,
4082  *                            the root path will not be found and can not be filtered.
4083  * @return string            Relative file path
4084  *                            (e.g. /includes/functions.php)
4085  */
4086  function phpbb_filter_root_path($errfile)
4087  {
4088      static $root_path;
4089  
4090      if (empty($root_path))
4091      {
4092          $root_path = phpbb_realpath(dirname(__FILE__) . '/../');
4093      }
4094  
4095      return str_replace(array($root_path, '\\'), array('[ROOT]', '/'), $errfile);
4096  }
4097  
4098  /**
4099  * Queries the session table to get information about online guests
4100  * @param int $item_id Limits the search to the item with this id
4101  * @param string $item The name of the item which is stored in the session table as session_{$item}_id
4102  * @return int The number of active distinct guest sessions
4103  */
4104  function obtain_guest_count($item_id = 0, $item = 'forum')
4105  {
4106      global $db, $config;
4107  
4108      if ($item_id)
4109      {
4110          $reading_sql = ' AND s.session_' . $item . '_id = ' . (int) $item_id;
4111      }
4112      else
4113      {
4114          $reading_sql = '';
4115      }
4116      $time = (time() - (intval($config['load_online_time']) * 60));
4117  
4118      // Get number of online guests
4119  
4120      if ($db->sql_layer === 'sqlite')
4121      {
4122          $sql = 'SELECT COUNT(session_ip) as num_guests
4123              FROM (
4124                  SELECT DISTINCT s.session_ip
4125                  FROM ' . SESSIONS_TABLE . ' s
4126                  WHERE s.session_user_id = ' . ANONYMOUS . '
4127                      AND s.session_time >= ' . ($time - ((int) ($time % 60))) .
4128                  $reading_sql .
4129              ')';
4130      }
4131      else
4132      {
4133          $sql = 'SELECT COUNT(DISTINCT s.session_ip) as num_guests
4134              FROM ' . SESSIONS_TABLE . ' s
4135              WHERE s.session_user_id = ' . ANONYMOUS . '
4136                  AND s.session_time >= ' . ($time - ((int) ($time % 60))) .
4137              $reading_sql;
4138      }
4139      $result = $db->sql_query($sql);
4140      $guests_online = (int) $db->sql_fetchfield('num_guests');
4141      $db->sql_freeresult($result);
4142  
4143      return $guests_online;
4144  }
4145  
4146  /**
4147  * Queries the session table to get information about online users
4148  * @param int $item_id Limits the search to the item with this id
4149  * @param string $item The name of the item which is stored in the session table as session_{$item}_id
4150  * @return array An array containing the ids of online, hidden and visible users, as well as statistical info
4151  */
4152  function obtain_users_online($item_id = 0, $item = 'forum')
4153  {
4154      global $db, $config, $user;
4155  
4156      $reading_sql = '';
4157      if ($item_id !== 0)
4158      {
4159          $reading_sql = ' AND s.session_' . $item . '_id = ' . (int) $item_id;
4160      }
4161  
4162      $online_users = array(
4163          'online_users'            => array(),
4164          'hidden_users'            => array(),
4165          'total_online'            => 0,
4166          'visible_online'        => 0,
4167          'hidden_online'            => 0,
4168          'guests_online'            => 0,
4169      );
4170  
4171      if ($config['load_online_guests'])
4172      {
4173          $online_users['guests_online'] = obtain_guest_count($item_id, $item);
4174      }
4175  
4176      // a little discrete magic to cache this for 30 seconds
4177      $time = (time() - (intval($config['load_online_time']) * 60));
4178  
4179      $sql = 'SELECT s.session_user_id, s.session_ip, s.session_viewonline
4180          FROM ' . SESSIONS_TABLE . ' s
4181          WHERE s.session_time >= ' . ($time - ((int) ($time % 30))) .
4182              $reading_sql .
4183          ' AND s.session_user_id <> ' . ANONYMOUS;
4184      $result = $db->sql_query($sql);
4185  
4186      while ($row = $db->sql_fetchrow($result))
4187      {
4188          // Skip multiple sessions for one user
4189          if (!isset($online_users['online_users'][$row['session_user_id']]))
4190          {
4191              $online_users['online_users'][$row['session_user_id']] = (int) $row['session_user_id'];
4192              if ($row['session_viewonline'])
4193              {
4194                  $online_users['visible_online']++;
4195              }
4196              else
4197              {
4198                  $online_users['hidden_users'][$row['session_user_id']] = (int) $row['session_user_id'];
4199                  $online_users['hidden_online']++;
4200              }
4201          }
4202      }
4203      $online_users['total_online'] = $online_users['guests_online'] + $online_users['visible_online'] + $online_users['hidden_online'];
4204      $db->sql_freeresult($result);
4205  
4206      return $online_users;
4207  }
4208  
4209  /**
4210  * Uses the result of obtain_users_online to generate a localized, readable representation.
4211  * @param mixed $online_users result of obtain_users_online - array with user_id lists for total, hidden and visible users, and statistics
4212  * @param int $item_id Indicate that the data is limited to one item and not global
4213  * @param string $item The name of the item which is stored in the session table as session_{$item}_id
4214  * @return array An array containing the string for output to the template
4215  */
4216  function obtain_users_online_string($online_users, $item_id = 0, $item = 'forum')
4217  {
4218      global $config, $db, $user, $auth;
4219  
4220      $user_online_link = $online_userlist = '';
4221      // Need caps version of $item for language-strings
4222      $item_caps = strtoupper($item);
4223  
4224      if (sizeof($online_users['online_users']))
4225      {
4226          $sql = 'SELECT username, username_clean, user_id, user_type, user_allow_viewonline, user_colour
4227                  FROM ' . USERS_TABLE . '
4228                  WHERE ' . $db->sql_in_set('user_id', $online_users['online_users']) . '
4229                  ORDER BY username_clean ASC';
4230          $result = $db->sql_query($sql);
4231  
4232          while ($row = $db->sql_fetchrow($result))
4233          {
4234              // User is logged in and therefore not a guest
4235              if ($row['user_id'] != ANONYMOUS)
4236              {
4237                  if (isset($online_users['hidden_users'][$row['user_id']]))
4238                  {
4239                      $row['username'] = '<em>' . $row['username'] . '</em>';
4240                  }
4241  
4242                  if (!isset($online_users['hidden_users'][$row['user_id']]) || $auth->acl_get('u_viewonline'))
4243                  {
4244                      $user_online_link = get_username_string(($row['user_type'] <> USER_IGNORE) ? 'full' : 'no_profile', $row['user_id'], $row['username'], $row['user_colour']);
4245                      $online_userlist .= ($online_userlist != '') ? ', ' . $user_online_link : $user_online_link;
4246                  }
4247              }
4248          }
4249          $db->sql_freeresult($result);
4250      }
4251  
4252      if (!$online_userlist)
4253      {
4254          $online_userlist = $user->lang['NO_ONLINE_USERS'];
4255      }
4256  
4257      if ($item_id === 0)
4258      {
4259          $online_userlist = $user->lang['REGISTERED_USERS'] . ' ' . $online_userlist;
4260      }
4261      else if ($config['load_online_guests'])
4262      {
4263          $l_online = ($online_users['guests_online'] === 1) ? $user->lang['BROWSING_' . $item_caps . '_GUEST'] : $user->lang['BROWSING_' . $item_caps . '_GUESTS'];
4264          $online_userlist = sprintf($l_online, $online_userlist, $online_users['guests_online']);
4265      }
4266      else
4267      {
4268          $online_userlist = sprintf($user->lang['BROWSING_' . $item_caps], $online_userlist);
4269      }
4270      // Build online listing
4271      $vars_online = array(
4272          'ONLINE'    => array('total_online', 'l_t_user_s', 0),
4273          'REG'        => array('visible_online', 'l_r_user_s', !$config['load_online_guests']),
4274          'HIDDEN'    => array('hidden_online', 'l_h_user_s', $config['load_online_guests']),
4275          'GUEST'        => array('guests_online', 'l_g_user_s', 0)
4276      );
4277  
4278      foreach ($vars_online as $l_prefix => $var_ary)
4279      {
4280          if ($var_ary[2])
4281          {
4282              $l_suffix = '_AND';
4283          }
4284          else
4285          {
4286              $l_suffix = '';
4287          }
4288          switch ($online_users[$var_ary[0]])
4289          {
4290              case 0:
4291                  ${$var_ary[1]} = $user->lang[$l_prefix . '_USERS_ZERO_TOTAL' . $l_suffix];
4292              break;
4293  
4294              case 1:
4295                  ${$var_ary[1]} = $user->lang[$l_prefix . '_USER_TOTAL' . $l_suffix];
4296              break;
4297  
4298              default:
4299                  ${$var_ary[1]} = $user->lang[$l_prefix . '_USERS_TOTAL' . $l_suffix];
4300              break;
4301          }
4302      }
4303      unset($vars_online);
4304  
4305      $l_online_users = sprintf($l_t_user_s, $online_users['total_online']);
4306      $l_online_users .= sprintf($l_r_user_s, $online_users['visible_online']);
4307      $l_online_users .= sprintf($l_h_user_s, $online_users['hidden_online']);
4308  
4309      if ($config['load_online_guests'])
4310      {
4311          $l_online_users .= sprintf($l_g_user_s, $online_users['guests_online']);
4312      }
4313  
4314  
4315  
4316      return array(
4317          'online_userlist'    => $online_userlist,
4318          'l_online_users'    => $l_online_users,
4319      );
4320  }
4321  
4322  /**
4323  * Get option bitfield from custom data
4324  *
4325  * @param int    $bit        The bit/value to get
4326  * @param int    $data        Current bitfield to check
4327  * @return bool    Returns true if value of constant is set in bitfield, else false
4328  */
4329  function phpbb_optionget($bit, $data)
4330  {
4331      return ($data & 1 << (int) $bit) ? true : false;
4332  }
4333  
4334  /**
4335  * Set option bitfield
4336  *
4337  * @param int    $bit        The bit/value to set/unset
4338  * @param bool    $set        True if option should be set, false if option should be unset.
4339  * @param int    $data        Current bitfield to change
4340  *
4341  * @return int    The new bitfield
4342  */
4343  function phpbb_optionset($bit, $set, $data)
4344  {
4345      if ($set && !($data & 1 << $bit))
4346      {
4347          $data += 1 << $bit;
4348      }
4349      else if (!$set && ($data & 1 << $bit))
4350      {
4351          $data -= 1 << $bit;
4352      }
4353  
4354      return $data;
4355  }
4356  
4357  /**
4358  * Login using http authenticate.
4359  *
4360  * @param array    $param        Parameter array, see $param_defaults array.
4361  *
4362  * @return null
4363  */
4364  function phpbb_http_login($param)
4365  {
4366      global $auth, $user;
4367      global $config;
4368  
4369      $param_defaults = array(
4370          'auth_message'    => '',
4371  
4372          'autologin'        => false,
4373          'viewonline'    => true,
4374          'admin'            => false,
4375      );
4376  
4377      // Overwrite default values with passed values
4378      $param = array_merge($param_defaults, $param);
4379  
4380      // User is already logged in
4381      // We will not overwrite his session
4382      if (!empty($user->data['is_registered']))
4383      {
4384          return;
4385      }
4386  
4387      // $_SERVER keys to check
4388      $username_keys = array(
4389          'PHP_AUTH_USER',
4390          'Authorization',
4391          'REMOTE_USER', 'REDIRECT_REMOTE_USER',
4392          'HTTP_AUTHORIZATION', 'REDIRECT_HTTP_AUTHORIZATION',
4393          'REMOTE_AUTHORIZATION', 'REDIRECT_REMOTE_AUTHORIZATION',
4394          'AUTH_USER',
4395      );
4396  
4397      $password_keys = array(
4398          'PHP_AUTH_PW',
4399          'REMOTE_PASSWORD',
4400          'AUTH_PASSWORD',
4401      );
4402  
4403      $username = null;
4404      foreach ($username_keys as $k)
4405      {
4406          if (isset($_SERVER[$k]))
4407          {
4408              $username = $_SERVER[$k];
4409              break;
4410          }
4411      }
4412  
4413      $password = null;
4414      foreach ($password_keys as $k)
4415      {
4416          if (isset($_SERVER[$k]))
4417          {
4418              $password = $_SERVER[$k];
4419              break;
4420          }
4421      }
4422  
4423      // Decode encoded information (IIS, CGI, FastCGI etc.)
4424      if (!is_null($username) && is_null($password) && strpos($username, 'Basic ') === 0)
4425      {
4426          list($username, $password) = explode(':', base64_decode(substr($username, 6)), 2);
4427      }
4428  
4429      if (!is_null($username) && !is_null($password))
4430      {
4431          set_var($username, $username, 'string', true);
4432          set_var($password, $password, 'string', true);
4433  
4434          $auth_result = $auth->login($username, $password, $param['autologin'], $param['viewonline'], $param['admin']);
4435  
4436          if ($auth_result['status'] == LOGIN_SUCCESS)
4437          {
4438              return;
4439          }
4440          else if ($auth_result['status'] == LOGIN_ERROR_ATTEMPTS)
4441          {
4442              send_status_line(401, 'Unauthorized');
4443  
4444              trigger_error('NOT_AUTHORISED');
4445          }
4446      }
4447  
4448      // Prepend sitename to auth_message
4449      $param['auth_message'] = ($param['auth_message'] === '') ? $config['sitename'] : $config['sitename'] . ' - ' . $param['auth_message'];
4450  
4451      // We should probably filter out non-ASCII characters - RFC2616
4452      $param['auth_message'] = preg_replace('/[\x80-\xFF]/', '?', $param['auth_message']);
4453  
4454      header('WWW-Authenticate: Basic realm="' . $param['auth_message'] . '"');
4455      send_status_line(401, 'Unauthorized');
4456  
4457      trigger_error('NOT_AUTHORISED');
4458  }
4459  
4460  /**
4461  * Generate page header
4462  */
4463  function page_header($page_title = '', $display_online_list = true, $item_id = 0, $item = 'forum')
4464  {
4465      global $db, $config, $template, $SID, $_SID, $_EXTRA_URL, $user, $auth, $phpEx, $phpbb_root_path;
4466  
4467      if (defined('HEADER_INC'))
4468      {
4469          return;
4470      }
4471  
4472      define('HEADER_INC', true);
4473  
4474      // gzip_compression
4475      if ($config['gzip_compress'])
4476      {
4477          // to avoid partially compressed output resulting in blank pages in
4478          // the browser or error messages, compression is disabled in a few cases:
4479          //
4480          // 1) if headers have already been sent, this indicates plaintext output
4481          //    has been started so further content must not be compressed
4482          // 2) the length of the current output buffer is non-zero. This means
4483          //    there is already some uncompressed content in this output buffer
4484          //    so further output must not be compressed
4485          // 3) if more than one level of output buffering is used because we
4486          //    cannot test all output buffer level content lengths. One level
4487          //    could be caused by php.ini output_buffering. Anything
4488          //    beyond that is manual, so the code wrapping phpBB in output buffering
4489          //    can easily compress the output itself.
4490          //
4491          if (@extension_loaded('zlib') && !headers_sent() && ob_get_level() <= 1 && ob_get_length() == 0)
4492          {
4493              ob_start('ob_gzhandler');
4494          }
4495      }
4496  
4497      // Generate logged in/logged out status
4498      if ($user->data['user_id'] != ANONYMOUS)
4499      {
4500          $u_login_logout = append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=logout', true, $user->session_id);
4501          $l_login_logout = sprintf($user->lang['LOGOUT_USER'], $user->data['username']);
4502      }
4503      else
4504      {
4505          $u_login_logout = append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=login');
4506          $l_login_logout = $user->lang['LOGIN'];
4507      }
4508  
4509      // Last visit date/time
4510      $s_last_visit = ($user->data['user_id'] != ANONYMOUS) ? $user->format_date($user->data['session_last_visit']) : '';
4511  
4512      // Get users online list ... if required
4513      $l_online_users = $online_userlist = $l_online_record = $l_online_time = '';
4514  
4515      if ($config['load_online'] && $config['load_online_time'] && $display_online_list)
4516      {
4517          /**
4518          * Load online data:
4519          * For obtaining another session column use $item and $item_id in the function-parameter, whereby the column is session_{$item}_id.
4520          */
4521          $item_id = max($item_id, 0);
4522  
4523          $online_users = obtain_users_online($item_id, $item);
4524          $user_online_strings = obtain_users_online_string($online_users, $item_id, $item);
4525  
4526          $l_online_users = $user_online_strings['l_online_users'];
4527          $online_userlist = $user_online_strings['online_userlist'];
4528          $total_online_users = $online_users['total_online'];
4529  
4530          if ($total_online_users > $config['record_online_users'])
4531          {
4532              set_config('record_online_users', $total_online_users, true);
4533              set_config('record_online_date', time(), true);
4534          }
4535  
4536          $l_online_record = sprintf($user->lang['RECORD_ONLINE_USERS'], $config['record_online_users'], $user->format_date($config['record_online_date'], false, true));
4537  
4538          $l_online_time = ($config['load_online_time'] == 1) ? 'VIEW_ONLINE_TIME' : 'VIEW_ONLINE_TIMES';
4539          $l_online_time = sprintf($user->lang[$l_online_time], $config['load_online_time']);
4540      }
4541  
4542      $l_privmsgs_text = $l_privmsgs_text_unread = '';
4543      $s_privmsg_new = false;
4544  
4545      // Obtain number of new private messages if user is logged in
4546      if (!empty($user->data['is_registered']))
4547      {
4548          if ($user->data['user_new_privmsg'])
4549          {
4550              $l_message_new = ($user->data['user_new_privmsg'] == 1) ? $user->lang['NEW_PM'] : $user->lang['NEW_PMS'];
4551              $l_privmsgs_text = sprintf($l_message_new, $user->data['user_new_privmsg']);
4552  
4553              if (!$user->data['user_last_privmsg'] || $user->data['user_last_privmsg'] > $user->data['session_last_visit'])
4554              {
4555                  $sql = 'UPDATE ' . USERS_TABLE . '
4556                      SET user_last_privmsg = ' . $user->data['session_last_visit'] . '
4557                      WHERE user_id = ' . $user->data['user_id'];
4558                  $db->sql_query($sql);
4559  
4560                  $s_privmsg_new = true;
4561              }
4562              else
4563              {
4564                  $s_privmsg_new = false;
4565              }
4566          }
4567          else
4568          {
4569              $l_privmsgs_text = $user->lang['NO_NEW_PM'];
4570              $s_privmsg_new = false;
4571          }
4572  
4573          $l_privmsgs_text_unread = '';
4574  
4575          if ($user->data['user_unread_privmsg'] && $user->data['user_unread_privmsg'] != $user->data['user_new_privmsg'])
4576          {
4577              $l_message_unread = ($user->data['user_unread_privmsg'] == 1) ? $user->lang['UNREAD_PM'] : $user->lang['UNREAD_PMS'];
4578              $l_privmsgs_text_unread = sprintf($l_message_unread, $user->data['user_unread_privmsg']);
4579          }
4580      }
4581  
4582      $forum_id = request_var('f', 0);
4583      $topic_id = request_var('t', 0);
4584  
4585      $s_feed_news = false;
4586  
4587      // Get option for news
4588      if ($config['feed_enable'])
4589      {
4590          $sql = 'SELECT forum_id
4591              FROM ' . FORUMS_TABLE . '
4592              WHERE ' . $db->sql_bit_and('forum_options', FORUM_OPTION_FEED_NEWS, '<> 0');
4593          $result = $db->sql_query_limit($sql, 1, 0, 600);
4594          $s_feed_news = (int) $db->sql_fetchfield('forum_id');
4595          $db->sql_freeresult($result);
4596      }
4597  
4598      // Determine board url - we may need it later
4599      $board_url = generate_board_url() . '/';
4600      $web_path = (defined('PHPBB_USE_BOARD_URL_PATH') && PHPBB_USE_BOARD_URL_PATH) ? $board_url : $phpbb_root_path;
4601  
4602      // Which timezone?
4603      $tz = ($user->data['user_id'] != ANONYMOUS) ? strval(doubleval($user->data['user_timezone'])) : strval(doubleval($config['board_timezone']));
4604  
4605      // Send a proper content-language to the output
4606      $user_lang = $user->lang['USER_LANG'];
4607      if (strpos($user_lang, '-x-') !== false)
4608      {
4609          $user_lang = substr($user_lang, 0, strpos($user_lang, '-x-'));
4610      }
4611  
4612      $s_search_hidden_fields = array();
4613      if ($_SID)
4614      {
4615          $s_search_hidden_fields['sid'] = $_SID;
4616      }
4617  
4618      if (!empty($_EXTRA_URL))
4619      {
4620          foreach ($_EXTRA_URL as $url_param)
4621          {
4622              $url_param = explode('=', $url_param, 2);
4623              $s_search_hidden_fields[$url_param[0]] = $url_param[1];
4624          }
4625      }
4626  
4627      // The following assigns all _common_ variables that may be used at any point in a template.
4628      $template->assign_vars(array(
4629          'SITENAME'                        => $config['sitename'],
4630          'SITE_DESCRIPTION'                => $config['site_desc'],
4631          'PAGE_TITLE'                    => $page_title,
4632          'SCRIPT_NAME'                    => str_replace('.' . $phpEx, '', $user->page['page_name']),
4633          'LAST_VISIT_DATE'                => sprintf($user->lang['YOU_LAST_VISIT'], $s_last_visit),
4634          'LAST_VISIT_YOU'                => $s_last_visit,
4635          'CURRENT_TIME'                    => sprintf($user->lang['CURRENT_TIME'], $user->format_date(time(), false, true)),
4636          'TOTAL_USERS_ONLINE'            => $l_online_users,
4637          'LOGGED_IN_USER_LIST'            => $online_userlist,
4638          'RECORD_USERS'                    => $l_online_record,
4639          'PRIVATE_MESSAGE_INFO'            => $l_privmsgs_text,
4640          'PRIVATE_MESSAGE_INFO_UNREAD'    => $l_privmsgs_text_unread,
4641  
4642          'S_USER_NEW_PRIVMSG'            => $user->data['user_new_privmsg'],
4643          'S_USER_UNREAD_PRIVMSG'            => $user->data['user_unread_privmsg'],
4644          'S_USER_NEW'                    => $user->data['user_new'],
4645  
4646          'SID'                => $SID,
4647          '_SID'                => $_SID,
4648          'SESSION_ID'        => $user->session_id,
4649          'ROOT_PATH'            => $phpbb_root_path,
4650          'BOARD_URL'            => $board_url,
4651  
4652          'L_LOGIN_LOGOUT'    => $l_login_logout,
4653          'L_INDEX'            => $user->lang['FORUM_INDEX'],
4654          'L_ONLINE_EXPLAIN'    => $l_online_time,
4655  
4656          'U_PRIVATEMSGS'            => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&amp;folder=inbox'),
4657          'U_RETURN_INBOX'        => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&amp;folder=inbox'),
4658          'U_POPUP_PM'            => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&amp;mode=popup'),
4659          'UA_POPUP_PM'            => addslashes(append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&amp;mode=popup')),
4660          'U_MEMBERLIST'            => append_sid("{$phpbb_root_path}memberlist.$phpEx"),
4661          'U_VIEWONLINE'            => ($auth->acl_gets('u_viewprofile', 'a_user', 'a_useradd', 'a_userdel')) ? append_sid("{$phpbb_root_path}viewonline.$phpEx") : '',
4662          'U_LOGIN_LOGOUT'        => $u_login_logout,
4663          'U_INDEX'                => append_sid("{$phpbb_root_path}index.$phpEx"),
4664          'U_SEARCH'                => append_sid("{$phpbb_root_path}search.$phpEx"),
4665          'U_REGISTER'            => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=register'),
4666          'U_PROFILE'                => append_sid("{$phpbb_root_path}ucp.$phpEx"),
4667          'U_MODCP'                => append_sid("{$phpbb_root_path}mcp.$phpEx", false, true, $user->session_id),
4668          'U_FAQ'                    => append_sid("{$phpbb_root_path}faq.$phpEx"),
4669          'U_SEARCH_SELF'            => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=egosearch'),
4670          'U_SEARCH_NEW'            => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=newposts'),
4671          'U_SEARCH_UNANSWERED'    => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=unanswered'),
4672          'U_SEARCH_UNREAD'        => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=unreadposts'),
4673          'U_SEARCH_ACTIVE_TOPICS'=> append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=active_topics'),
4674          'U_DELETE_COOKIES'        => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=delete_cookies'),
4675          'U_TEAM'                => ($user->data['user_id'] != ANONYMOUS && !$auth->acl_get('u_viewprofile')) ? '' : append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=leaders'),
4676          'U_TERMS_USE'            => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=terms'),
4677          'U_PRIVACY'                => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy'),
4678          'U_RESTORE_PERMISSIONS'    => ($user->data['user_perm_from'] && $auth->acl_get('a_switchperm')) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=restore_perm') : '',
4679          'U_FEED'                => generate_board_url() . "/feed.$phpEx",
4680  
4681          'S_USER_LOGGED_IN'        => ($user->data['user_id'] != ANONYMOUS) ? true : false,
4682          'S_AUTOLOGIN_ENABLED'    => ($config['allow_autologin']) ? true : false,
4683          'S_BOARD_DISABLED'        => ($config['board_disable']) ? true : false,
4684          'S_REGISTERED_USER'        => (!empty($user->data['is_registered'])) ? true : false,
4685          'S_IS_BOT'                => (!empty($user->data['is_bot'])) ? true : false,
4686          'S_USER_PM_POPUP'        => $user->optionget('popuppm'),
4687          'S_USER_LANG'            => $user_lang,
4688          'S_USER_BROWSER'        => (isset($user->data['session_browser'])) ? $user->data['session_browser'] : $user->lang['UNKNOWN_BROWSER'],
4689          'S_USERNAME'            => $user->data['username'],
4690          'S_CONTENT_DIRECTION'    => $user->lang['DIRECTION'],
4691          'S_CONTENT_FLOW_BEGIN'    => ($user->lang['DIRECTION'] == 'ltr') ? 'left' : 'right',
4692          'S_CONTENT_FLOW_END'    => ($user->lang['DIRECTION'] == 'ltr') ? 'right' : 'left',
4693          'S_CONTENT_ENCODING'    => 'UTF-8',
4694          'S_TIMEZONE'            => ($user->data['user_dst'] || ($user->data['user_id'] == ANONYMOUS && $config['board_dst'])) ? sprintf($user->lang['ALL_TIMES'], $user->lang['tz'][$tz], $user->lang['tz']['dst']) : sprintf($user->lang['ALL_TIMES'], $user->lang['tz'][$tz], ''),
4695          'S_DISPLAY_ONLINE_LIST'    => ($l_online_time) ? 1 : 0,
4696          'S_DISPLAY_SEARCH'        => (!$config['load_search']) ? 0 : (isset($auth) ? ($auth->acl_get('u_search') && $auth->acl_getf_global('f_search')) : 1),
4697          'S_DISPLAY_PM'            => ($config['allow_privmsg'] && !empty($user->data['is_registered']) && ($auth->acl_get('u_readpm') || $auth->acl_get('u_sendpm'))) ? true : false,
4698          'S_DISPLAY_MEMBERLIST'    => (isset($auth)) ? $auth->acl_get('u_viewprofile') : 0,
4699          'S_NEW_PM'                => ($s_privmsg_new) ? 1 : 0,
4700          'S_REGISTER_ENABLED'    => ($config['require_activation'] != USER_ACTIVATION_DISABLE) ? true : false,
4701          'S_FORUM_ID'            => $forum_id,
4702          'S_TOPIC_ID'            => $topic_id,
4703  
4704          'S_LOGIN_ACTION'        => ((!defined('ADMIN_START')) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=login') : append_sid("index.$phpEx", false, true, $user->session_id)),
4705          'S_LOGIN_REDIRECT'        => build_hidden_fields(array('redirect' => build_url())),
4706  
4707          'S_ENABLE_FEEDS'            => ($config['feed_enable']) ? true : false,
4708          'S_ENABLE_FEEDS_OVERALL'    => ($config['feed_overall']) ? true : false,
4709          'S_ENABLE_FEEDS_FORUMS'        => ($config['feed_overall_forums']) ? true : false,
4710          'S_ENABLE_FEEDS_TOPICS'        => ($config['feed_topics_new']) ? true : false,
4711          'S_ENABLE_FEEDS_TOPICS_ACTIVE'    => ($config['feed_topics_active']) ? true : false,
4712          'S_ENABLE_FEEDS_NEWS'        => ($s_feed_news) ? true : false,
4713  
4714          'S_LOAD_UNREADS'            => ($config['load_unreads_search'] && ($config['load_anon_lastread'] || $user->data['is_registered'])) ? true : false,
4715  
4716          'S_SEARCH_HIDDEN_FIELDS'    => build_hidden_fields($s_search_hidden_fields),
4717  
4718          'T_THEME_PATH'            => "{$web_path}styles/" . rawurlencode($user->theme['theme_path']) . '/theme',
4719          'T_TEMPLATE_PATH'        => "{$web_path}styles/" . rawurlencode($user->theme['template_path']) . '/template',
4720          'T_SUPER_TEMPLATE_PATH'    => (isset($user->theme['template_inherit_path']) && $user->theme['template_inherit_path']) ? "{$web_path}styles/" . rawurlencode($user->theme['template_inherit_path']) . '/template' : "{$web_path}styles/" . rawurlencode($user->theme['template_path']) . '/template',
4721          'T_IMAGESET_PATH'        => "{$web_path}styles/" . rawurlencode($user->theme['imageset_path']) . '/imageset',
4722          'T_IMAGESET_LANG_PATH'    => "{$web_path}styles/" . rawurlencode($user->theme['imageset_path']) . '/imageset/' . $user->lang_name,
4723          'T_IMAGES_PATH'            => "{$web_path}images/",
4724          'T_SMILIES_PATH'        => "{$web_path}{$config['smilies_path']}/",
4725          'T_AVATAR_PATH'            => "{$web_path}{$config['avatar_path']}/",
4726          'T_AVATAR_GALLERY_PATH'    => "{$web_path}{$config['avatar_gallery_path']}/",
4727          'T_ICONS_PATH'            => "{$web_path}{$config['icons_path']}/",
4728          'T_RANKS_PATH'            => "{$web_path}{$config['ranks_path']}/",
4729          'T_UPLOAD_PATH'            => "{$web_path}{$config['upload_path']}/",
4730          'T_STYLESHEET_LINK'        => (!$user->theme['theme_storedb']) ? "{$web_path}styles/" . rawurlencode($user->theme['theme_path']) . '/theme/stylesheet.css' : append_sid("{$phpbb_root_path}style.$phpEx", 'id=' . $user->theme['style_id'] . '&amp;lang=' . $user->lang_name),
4731          'T_STYLESHEET_NAME'        => $user->theme['theme_name'],
4732  
4733          'T_THEME_NAME'            => rawurlencode($user->theme['theme_path']),
4734          'T_TEMPLATE_NAME'        => rawurlencode($user->theme['template_path']),
4735          'T_SUPER_TEMPLATE_NAME'    => rawurlencode((isset($user->theme['template_inherit_path']) && $user->theme['template_inherit_path']) ? $user->theme['template_inherit_path'] : $user->theme['template_path']),
4736          'T_IMAGESET_NAME'        => rawurlencode($user->theme['imageset_path']),
4737          'T_IMAGESET_LANG_NAME'    => $user->data['user_lang'],
4738          'T_IMAGES'                => 'images',
4739          'T_SMILIES'                => $config['smilies_path'],
4740          'T_AVATAR'                => $config['avatar_path'],
4741          'T_AVATAR_GALLERY'        => $config['avatar_gallery_path'],
4742          'T_ICONS'                => $config['icons_path'],
4743          'T_RANKS'                => $config['ranks_path'],
4744          'T_UPLOAD'                => $config['upload_path'],
4745  
4746          'SITE_LOGO_IMG'            => $user->img('site_logo'),
4747  
4748          'A_COOKIE_SETTINGS'        => addslashes('; path=' . $config['cookie_path'] . ((!$config['cookie_domain'] || $config['cookie_domain'] == 'localhost' || $config['cookie_domain'] == '127.0.0.1') ? '' : '; domain=' . $config['cookie_domain']) . ((!$config['cookie_secure']) ? '' : '; secure')),
4749      ));
4750  
4751      // application/xhtml+xml not used because of IE
4752      header('Content-type: text/html; charset=UTF-8');
4753  
4754      header('Cache-Control: private, no-cache="set-cookie"');
4755      header('Expires: 0');
4756      header('Pragma: no-cache');
4757  
4758      if (!empty($user->data['is_bot']))
4759      {
4760          // Let reverse proxies know we detected a bot.
4761          header('X-PHPBB-IS-BOT: yes');
4762      }
4763  
4764      return;
4765  }
4766  
4767  /**
4768  * Generate page footer
4769  */
4770  function page_footer($run_cron = true)
4771  {
4772      global $db, $config, $template, $user, $auth, $cache, $starttime, $phpbb_root_path, $phpEx;
4773  
4774      // Output page creation time
4775      if (defined('DEBUG'))
4776      {
4777          $mtime = explode(' ', microtime());
4778          $totaltime = $mtime[0] + $mtime[1] - $starttime;
4779  
4780          if (!empty($_REQUEST['explain']) && $auth->acl_get('a_') && defined('DEBUG_EXTRA') && method_exists($db, 'sql_report'))
4781          {
4782              $db->sql_report('display');
4783          }
4784  
4785          $debug_output = sprintf('Time : %.3fs | ' . $db->sql_num_queries() . ' Queries | GZIP : ' . (($config['gzip_compress'] && @extension_loaded('zlib')) ? 'On' : 'Off') . (($user->load) ? ' | Load : ' . $user->load : ''), $totaltime);
4786  
4787          if ($auth->acl_get('a_') && defined('DEBUG_EXTRA'))
4788          {
4789              if (function_exists('memory_get_usage'))
4790              {
4791                  if ($memory_usage = memory_get_usage())
4792                  {
4793                      global $base_memory_usage;
4794                      $memory_usage -= $base_memory_usage;
4795                      $memory_usage = get_formatted_filesize($memory_usage);
4796  
4797                      $debug_output .= ' | Memory Usage: ' . $memory_usage;
4798                  }
4799              }
4800  
4801              $debug_output .= ' | <a href="' . build_url() . '&amp;explain=1">Explain</a>';
4802          }
4803      }
4804  
4805      $template->assign_vars(array(
4806          'DEBUG_OUTPUT'            => (defined('DEBUG')) ? $debug_output : '',
4807          'TRANSLATION_INFO'        => (!empty($user->lang['TRANSLATION_INFO'])) ? $user->lang['TRANSLATION_INFO'] : '',
4808          'CREDIT_LINE'            => $user->lang('POWERED_BY', '<a href="https://www.phpbb.com/">phpBB</a>&reg; Forum Software &copy; phpBB Group'),
4809  
4810          'U_ACP' => ($auth->acl_get('a_') && !empty($user->data['is_registered'])) ? append_sid("{$phpbb_root_path}adm/index.$phpEx", false, true, $user->session_id) : '')
4811      );
4812  
4813      // Call cron-type script
4814      $call_cron = false;
4815      if (!defined('IN_CRON') && $run_cron && !$config['board_disable'] && !$user->data['is_bot'])
4816      {
4817          $call_cron = true;
4818          $time_now = (!empty($user->time_now) && is_int($user->time_now)) ? $user->time_now : time();
4819  
4820          // Any old lock present?
4821          if (!empty($config['cron_lock']))
4822          {
4823              $cron_time = explode(' ', $config['cron_lock']);
4824  
4825              // If 1 hour lock is present we do not call cron.php
4826              if ($cron_time[0] + 3600 >= $time_now)
4827              {
4828                  $call_cron = false;
4829              }
4830          }
4831      }
4832  
4833      // Call cron job?
4834      if ($call_cron)
4835      {
4836          $cron_type = '';
4837  
4838          if ($time_now - $config['queue_interval'] > $config['last_queue_run'] && !defined('IN_ADMIN') && file_exists($phpbb_root_path . 'cache/queue.' . $phpEx))
4839          {
4840              // Process email queue
4841              $cron_type = 'queue';
4842          }
4843          else if (method_exists($cache, 'tidy') && $time_now - $config['cache_gc'] > $config['cache_last_gc'])
4844          {
4845              // Tidy the cache
4846              $cron_type = 'tidy_cache';
4847          }
4848          else if ($config['warnings_expire_days'] && ($time_now - $config['warnings_gc'] > $config['warnings_last_gc']))
4849          {
4850              $cron_type = 'tidy_warnings';
4851          }
4852          else if ($time_now - $config['database_gc'] > $config['database_last_gc'])
4853          {
4854              // Tidy the database
4855              $cron_type = 'tidy_database';
4856          }
4857          else if ($time_now - $config['search_gc'] > $config['search_last_gc'])
4858          {
4859              // Tidy the search
4860              $cron_type = 'tidy_search';
4861          }
4862          else if ($time_now - $config['session_gc'] > $config['session_last_gc'])
4863          {
4864              $cron_type = 'tidy_sessions';
4865          }
4866  
4867          if ($cron_type)
4868          {
4869              $template->assign_var('RUN_CRON_TASK', '<img src="' . append_sid($phpbb_root_path . 'cron.' . $phpEx, 'cron_type=' . $cron_type) . '" width="1" height="1" alt="cron" />');
4870          }
4871      }
4872  
4873      $template->display('body');
4874  
4875      garbage_collection();
4876      exit_handler();
4877  }
4878  
4879  /**
4880  * Closing the cache object and the database
4881  * Cool function name, eh? We might want to add operations to it later
4882  */
4883  function garbage_collection()
4884  {
4885      global $cache, $db;
4886  
4887      // Unload cache, must be done before the DB connection if closed
4888      if (!empty($cache))
4889      {
4890          $cache->unload();
4891      }
4892  
4893      // Close our DB connection.
4894      if (!empty($db))
4895      {
4896          $db->sql_close();
4897      }
4898  }
4899  
4900  /**
4901  * Handler for exit calls in phpBB.
4902  * This function supports hooks.
4903  *
4904  * Note: This function is called after the template has been outputted.
4905  */
4906  function exit_handler()
4907  {
4908      global $phpbb_hook, $config;
4909  
4910      if (!empty($phpbb_hook) && $phpbb_hook->call_hook(__FUNCTION__))
4911      {
4912          if ($phpbb_hook->hook_return(__FUNCTION__))
4913          {
4914              return $phpbb_hook->hook_return_result(__FUNCTION__);
4915          }
4916      }
4917  
4918      // As a pre-caution... some setups display a blank page if the flush() is not there.
4919      (ob_get_level() > 0) ? @ob_flush() : @flush();
4920  
4921      exit;
4922  }
4923  
4924  /**
4925  * Handler for init calls in phpBB. This function is called in user::setup();
4926  * This function supports hooks.
4927  */
4928  function phpbb_user_session_handler()
4929  {
4930      global $phpbb_hook;
4931  
4932      if (!empty($phpbb_hook) && $phpbb_hook->call_hook(__FUNCTION__))
4933      {
4934          if ($phpbb_hook->hook_return(__FUNCTION__))
4935          {
4936              return $phpbb_hook->hook_return_result(__FUNCTION__);
4937          }
4938      }
4939  
4940      return;
4941  }
4942  
4943  ?>


Generated: Wed Oct 2 15:03:47 2013 Cross-referenced by PHPXref 0.7.1