Verzeichnisstruktur phpBB-3.1.0


Veröffentlicht
27.10.2014

So funktioniert es


Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück

Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

(Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

functions.php

Zuletzt modifiziert: 09.10.2024, 12:51 - Dateigröße: 159.31 KiB


0001  <?php
0002  /**
0003  *
0004  * This file is part of the phpBB Forum Software package.
0005  *
0006  * @copyright (c) phpBB Limited <https://www.phpbb.com>
0007  * @license GNU General Public License, version 2 (GPL-2.0)
0008  *
0009  * For full copyright and license information, please see
0010  * the docs/CREDITS.txt file.
0011  *
0012  */
0013   
0014  /**
0015  * @ignore
0016  */
0017  if (!defined('IN_PHPBB'))
0018  {
0019      exit;
0020  }
0021   
0022  // Common global functions
0023  /**
0024  * Load the autoloaders added by the extensions.
0025  *
0026  * @param string $phpbb_root_path Path to the phpbb root directory.
0027  */
0028  function phpbb_load_extensions_autoloaders($phpbb_root_path)
0029  {
0030      $iterator = new \RecursiveIteratorIterator(
0031          new \phpbb\recursive_dot_prefix_filter_iterator(
0032              new \RecursiveDirectoryIterator(
0033                  $phpbb_root_path . 'ext/',
0034                  \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS
0035              )
0036          ),
0037          \RecursiveIteratorIterator::SELF_FIRST
0038      );
0039      $iterator->setMaxDepth(2);
0040   
0041      foreach ($iterator as $file_info)
0042      {
0043          if ($file_info->getFilename() === 'vendor' && $iterator->getDepth() === 2)
0044          {
0045              $filename = $file_info->getRealPath() . '/autoload.php';
0046              if (file_exists($filename))
0047              {
0048                  require $filename;
0049              }
0050          }
0051      }
0052  }
0053   
0054  /**
0055  * Casts a variable to the given type.
0056  *
0057  * @deprecated
0058  */
0059  function set_var(&$result, $var, $type, $multibyte = false)
0060  {
0061      // no need for dependency injection here, if you have the object, call the method yourself!
0062      $type_cast_helper = new \phpbb\request\type_cast_helper();
0063      $type_cast_helper->set_var($result, $var, $type, $multibyte);
0064  }
0065   
0066  /**
0067  * Wrapper function of \phpbb\request\request::variable which exists for backwards compatability.
0068  * See {@link \phpbb\request\request_interface::variable \phpbb\request\request_interface::variable} for
0069  * documentation of this function's use.
0070  *
0071  * @deprecated
0072  * @param    mixed            $var_name    The form variable's name from which data shall be retrieved.
0073  *                                         If the value is an array this may be an array of indizes which will give
0074  *                                         direct access to a value at any depth. E.g. if the value of "var" is array(1 => "a")
0075  *                                         then specifying array("var", 1) as the name will return "a".
0076  *                                         If you pass an instance of {@link \phpbb\request\request_interface phpbb_request_interface}
0077  *                                         as this parameter it will overwrite the current request class instance. If you do
0078  *                                         not do so, it will create its own instance (but leave superglobals enabled).
0079  * @param    mixed            $default    A default value that is returned if the variable was not set.
0080  *                                         This function will always return a value of the same type as the default.
0081  * @param    bool            $multibyte    If $default is a string this paramater has to be true if the variable may contain any UTF-8 characters
0082  *                                        Default is false, causing all bytes outside the ASCII range (0-127) to be replaced with question marks
0083  * @param    bool            $cookie        This param is mapped to \phpbb\request\request_interface::COOKIE as the last param for
0084  *                                         \phpbb\request\request_interface::variable for backwards compatability reasons.
0085  * @param    \phpbb\request\request_interface|null|false    If an instance of \phpbb\request\request_interface is given the instance is stored in
0086  *                                        a static variable and used for all further calls where this parameters is null. Until
0087  *                                        the function is called with an instance it automatically creates a new \phpbb\request\request
0088  *                                        instance on every call. By passing false this per-call instantiation can be restored
0089  *                                        after having passed in a \phpbb\request\request_interface instance.
0090  *
0091  * @return    mixed    The value of $_REQUEST[$var_name] run through {@link set_var set_var} to ensure that the type is the
0092  *                     the same as that of $default. If the variable is not set $default is returned.
0093  */
0094  function request_var($var_name, $default, $multibyte = false, $cookie = false, $request = null)
0095  {
0096      // This is all just an ugly hack to add "Dependency Injection" to a function
0097      // the only real code is the function call which maps this function to a method.
0098      static $static_request = null;
0099   
0100      if ($request instanceof \phpbb\request\request_interface)
0101      {
0102          $static_request = $request;
0103   
0104          if (empty($var_name))
0105          {
0106              return;
0107          }
0108      }
0109      else if ($request === false)
0110      {
0111          $static_request = null;
0112   
0113          if (empty($var_name))
0114          {
0115              return;
0116          }
0117      }
0118   
0119      $tmp_request = $static_request;
0120   
0121      // no request class set, create a temporary one ourselves to keep backwards compatability
0122      if ($tmp_request === null)
0123      {
0124          // false param: enable super globals, so the created request class does not
0125          // make super globals inaccessible everywhere outside this function.
0126          $tmp_request = new \phpbb\request\request(new \phpbb\request\type_cast_helper(), false);
0127      }
0128   
0129      return $tmp_request->variable($var_name, $default, $multibyte, ($cookie) ? \phpbb\request\request_interface::COOKIE : \phpbb\request\request_interface::REQUEST);
0130  }
0131   
0132  /**
0133  * Sets a configuration option's value.
0134  *
0135  * Please note that this function does not update the is_dynamic value for
0136  * an already existing config option.
0137  *
0138  * @param string $config_name   The configuration option's name
0139  * @param string $config_value  New configuration value
0140  * @param bool   $is_dynamic    Whether this variable should be cached (false) or
0141  *                              if it changes too frequently (true) to be
0142  *                              efficiently cached.
0143  *
0144  * @return null
0145  *
0146  * @deprecated
0147  */
0148  function set_config($config_name, $config_value, $is_dynamic = false, \phpbb\config\config $set_config = null)
0149  {
0150      static $config = null;
0151   
0152      if ($set_config !== null)
0153      {
0154          $config = $set_config;
0155   
0156          if (empty($config_name))
0157          {
0158              return;
0159          }
0160      }
0161   
0162      $config->set($config_name, $config_value, !$is_dynamic);
0163  }
0164   
0165  /**
0166  * Increments an integer config value directly in the database.
0167  *
0168  * @param string $config_name   The configuration option's name
0169  * @param int    $increment     Amount to increment by
0170  * @param bool   $is_dynamic    Whether this variable should be cached (false) or
0171  *                              if it changes too frequently (true) to be
0172  *                              efficiently cached.
0173  *
0174  * @return null
0175  *
0176  * @deprecated
0177  */
0178  function set_config_count($config_name, $increment, $is_dynamic = false, \phpbb\config\config $set_config = null)
0179  {
0180      static $config = null;
0181   
0182      if ($set_config !== null)
0183      {
0184          $config = $set_config;
0185   
0186          if (empty($config_name))
0187          {
0188              return;
0189          }
0190      }
0191   
0192      $config->increment($config_name, $increment, !$is_dynamic);
0193  }
0194   
0195  /**
0196  * Generates an alphanumeric random string of given length
0197  *
0198  * @return string
0199  */
0200  function gen_rand_string($num_chars = 8)
0201  {
0202      // [a, z] + [0, 9] = 36
0203      return substr(strtoupper(base_convert(unique_id(), 16, 36)), 0, $num_chars);
0204  }
0205   
0206  /**
0207  * Generates a user-friendly alphanumeric random string of given length
0208  * We remove 0 and O so users cannot confuse those in passwords etc.
0209  *
0210  * @return string
0211  */
0212  function gen_rand_string_friendly($num_chars = 8)
0213  {
0214      $rand_str = unique_id();
0215   
0216      // Remove Z and Y from the base_convert(), replace 0 with Z and O with Y
0217      // [a, z] + [0, 9] - {z, y} = [a, z] + [0, 9] - {0, o} = 34
0218      $rand_str = str_replace(array('0', 'O'), array('Z', 'Y'), strtoupper(base_convert($rand_str, 16, 34)));
0219   
0220      return substr($rand_str, 0, $num_chars);
0221  }
0222   
0223  /**
0224  * Return unique id
0225  * @param string $extra additional entropy
0226  */
0227  function unique_id($extra = 'c')
0228  {
0229      static $dss_seeded = false;
0230      global $config;
0231   
0232      $val = $config['rand_seed'] . microtime();
0233      $val = md5($val);
0234      $config['rand_seed'] = md5($config['rand_seed'] . $val . $extra);
0235   
0236      if ($dss_seeded !== true && ($config['rand_seed_last_update'] < time() - rand(1,10)))
0237      {
0238          set_config('rand_seed_last_update', time(), true);
0239          set_config('rand_seed', $config['rand_seed'], true);
0240          $dss_seeded = true;
0241      }
0242   
0243      return substr($val, 4, 16);
0244  }
0245   
0246  /**
0247  * Wrapper for mt_rand() which allows swapping $min and $max parameters.
0248  *
0249  * PHP does not allow us to swap the order of the arguments for mt_rand() anymore.
0250  * (since PHP 5.3.4, see http://bugs.php.net/46587)
0251  *
0252  * @param int $min        Lowest value to be returned
0253  * @param int $max        Highest value to be returned
0254  *
0255  * @return int            Random integer between $min and $max (or $max and $min)
0256  */
0257  function phpbb_mt_rand($min, $max)
0258  {
0259      return ($min > $max) ? mt_rand($max, $min) : mt_rand($min, $max);
0260  }
0261   
0262  /**
0263  * Wrapper for getdate() which returns the equivalent array for UTC timestamps.
0264  *
0265  * @param int $time        Unix timestamp (optional)
0266  *
0267  * @return array            Returns an associative array of information related to the timestamp.
0268  *                        See http://www.php.net/manual/en/function.getdate.php
0269  */
0270  function phpbb_gmgetdate($time = false)
0271  {
0272      if ($time === false)
0273      {
0274          $time = time();
0275      }
0276   
0277      // getdate() interprets timestamps in local time.
0278      // What follows uses the fact that getdate() and
0279      // date('Z') balance each other out.
0280      return getdate($time - date('Z'));
0281  }
0282   
0283  /**
0284  * Return formatted string for filesizes
0285  *
0286  * @param mixed    $value            filesize in bytes
0287  *                                (non-negative number; int, float or string)
0288  * @param bool    $string_only    true if language string should be returned
0289  * @param array    $allowed_units    only allow these units (data array indexes)
0290  *
0291  * @return mixed                    data array if $string_only is false
0292  */
0293  function get_formatted_filesize($value, $string_only = true, $allowed_units = false)
0294  {
0295      global $user;
0296   
0297      $available_units = array(
0298          'tb' => array(
0299              'min'         => 1099511627776, // pow(2, 40)
0300              'index'        => 4,
0301              'si_unit'    => 'TB',
0302              'iec_unit'    => 'TIB',
0303          ),
0304          'gb' => array(
0305              'min'         => 1073741824, // pow(2, 30)
0306              'index'        => 3,
0307              'si_unit'    => 'GB',
0308              'iec_unit'    => 'GIB',
0309          ),
0310          'mb' => array(
0311              'min'        => 1048576, // pow(2, 20)
0312              'index'        => 2,
0313              'si_unit'    => 'MB',
0314              'iec_unit'    => 'MIB',
0315          ),
0316          'kb' => array(
0317              'min'        => 1024, // pow(2, 10)
0318              'index'        => 1,
0319              'si_unit'    => 'KB',
0320              'iec_unit'    => 'KIB',
0321          ),
0322          'b' => array(
0323              'min'        => 0,
0324              'index'        => 0,
0325              'si_unit'    => 'BYTES', // Language index
0326              'iec_unit'    => 'BYTES',  // Language index
0327          ),
0328      );
0329   
0330      foreach ($available_units as $si_identifier => $unit_info)
0331      {
0332          if (!empty($allowed_units) && $si_identifier != 'b' && !in_array($si_identifier, $allowed_units))
0333          {
0334              continue;
0335          }
0336   
0337          if ($value >= $unit_info['min'])
0338          {
0339              $unit_info['si_identifier'] = $si_identifier;
0340   
0341              break;
0342          }
0343      }
0344      unset($available_units);
0345   
0346      for ($i = 0; $i < $unit_info['index']; $i++)
0347      {
0348          $value /= 1024;
0349      }
0350      $value = round($value, 2);
0351   
0352      // Lookup units in language dictionary
0353      $unit_info['si_unit'] = (isset($user->lang[$unit_info['si_unit']])) ? $user->lang[$unit_info['si_unit']] : $unit_info['si_unit'];
0354      $unit_info['iec_unit'] = (isset($user->lang[$unit_info['iec_unit']])) ? $user->lang[$unit_info['iec_unit']] : $unit_info['iec_unit'];
0355   
0356      // Default to IEC
0357      $unit_info['unit'] = $unit_info['iec_unit'];
0358   
0359      if (!$string_only)
0360      {
0361          $unit_info['value'] = $value;
0362   
0363          return $unit_info;
0364      }
0365   
0366      return $value  . ' ' . $unit_info['unit'];
0367  }
0368   
0369  /**
0370  * Determine whether we are approaching the maximum execution time. Should be called once
0371  * at the beginning of the script in which it's used.
0372  * @return    bool    Either true if the maximum execution time is nearly reached, or false
0373  *                    if some time is still left.
0374  */
0375  function still_on_time($extra_time = 15)
0376  {
0377      static $max_execution_time, $start_time;
0378   
0379      $time = explode(' ', microtime());
0380      $current_time = $time[0] + $time[1];
0381   
0382      if (empty($max_execution_time))
0383      {
0384          $max_execution_time = (function_exists('ini_get')) ? (int) @ini_get('max_execution_time') : (int) @get_cfg_var('max_execution_time');
0385   
0386          // If zero, then set to something higher to not let the user catch the ten seconds barrier.
0387          if ($max_execution_time === 0)
0388          {
0389              $max_execution_time = 50 + $extra_time;
0390          }
0391   
0392          $max_execution_time = min(max(10, ($max_execution_time - $extra_time)), 50);
0393   
0394          // For debugging purposes
0395          // $max_execution_time = 10;
0396   
0397          global $starttime;
0398          $start_time = (empty($starttime)) ? $current_time : $starttime;
0399      }
0400   
0401      return (ceil($current_time - $start_time) < $max_execution_time) ? true : false;
0402  }
0403   
0404  /**
0405  * Hashes an email address to a big integer
0406  *
0407  * @param string $email        Email address
0408  *
0409  * @return string            Unsigned Big Integer
0410  */
0411  function phpbb_email_hash($email)
0412  {
0413      return sprintf('%u', crc32(strtolower($email))) . strlen($email);
0414  }
0415   
0416  /**
0417  * Wrapper for version_compare() that allows using uppercase A and B
0418  * for alpha and beta releases.
0419  *
0420  * See http://www.php.net/manual/en/function.version-compare.php
0421  *
0422  * @param string $version1        First version number
0423  * @param string $version2        Second version number
0424  * @param string $operator        Comparison operator (optional)
0425  *
0426  * @return mixed                    Boolean (true, false) if comparison operator is specified.
0427  *                                Integer (-1, 0, 1) otherwise.
0428  */
0429  function phpbb_version_compare($version1, $version2, $operator = null)
0430  {
0431      $version1 = strtolower($version1);
0432      $version2 = strtolower($version2);
0433   
0434      if (is_null($operator))
0435      {
0436          return version_compare($version1, $version2);
0437      }
0438      else
0439      {
0440          return version_compare($version1, $version2, $operator);
0441      }
0442  }
0443   
0444  /**
0445  * Global function for chmodding directories and files for internal use
0446  *
0447  * This function determines owner and group whom the file belongs to and user and group of PHP and then set safest possible file permissions.
0448  * The function determines owner and group from common.php file and sets the same to the provided file.
0449  * The function uses bit fields to build the permissions.
0450  * The function sets the appropiate execute bit on directories.
0451  *
0452  * Supported constants representing bit fields are:
0453  *
0454  * CHMOD_ALL - all permissions (7)
0455  * CHMOD_READ - read permission (4)
0456  * CHMOD_WRITE - write permission (2)
0457  * CHMOD_EXECUTE - execute permission (1)
0458  *
0459  * 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.
0460  *
0461  * @param string    $filename    The file/directory to be chmodded
0462  * @param int    $perms        Permissions to set
0463  *
0464  * @return bool    true on success, otherwise false
0465  */
0466  function phpbb_chmod($filename, $perms = CHMOD_READ)
0467  {
0468      static $_chmod_info;
0469   
0470      // Return if the file no longer exists.
0471      if (!file_exists($filename))
0472      {
0473          return false;
0474      }
0475   
0476      // Determine some common vars
0477      if (empty($_chmod_info))
0478      {
0479          if (!function_exists('fileowner') || !function_exists('filegroup'))
0480          {
0481              // No need to further determine owner/group - it is unknown
0482              $_chmod_info['process'] = false;
0483          }
0484          else
0485          {
0486              global $phpbb_root_path, $phpEx;
0487   
0488              // Determine owner/group of common.php file and the filename we want to change here
0489              $common_php_owner = @fileowner($phpbb_root_path . 'common.' . $phpEx);
0490              $common_php_group = @filegroup($phpbb_root_path . 'common.' . $phpEx);
0491   
0492              // And the owner and the groups PHP is running under.
0493              $php_uid = (function_exists('posix_getuid')) ? @posix_getuid() : false;
0494              $php_gids = (function_exists('posix_getgroups')) ? @posix_getgroups() : false;
0495   
0496              // If we are unable to get owner/group, then do not try to set them by guessing
0497              if (!$php_uid || empty($php_gids) || !$common_php_owner || !$common_php_group)
0498              {
0499                  $_chmod_info['process'] = false;
0500              }
0501              else
0502              {
0503                  $_chmod_info = array(
0504                      'process'        => true,
0505                      'common_owner'    => $common_php_owner,
0506                      'common_group'    => $common_php_group,
0507                      'php_uid'        => $php_uid,
0508                      'php_gids'        => $php_gids,
0509                  );
0510              }
0511          }
0512      }
0513   
0514      if ($_chmod_info['process'])
0515      {
0516          $file_uid = @fileowner($filename);
0517          $file_gid = @filegroup($filename);
0518   
0519          // Change owner
0520          if (@chown($filename, $_chmod_info['common_owner']))
0521          {
0522              clearstatcache();
0523              $file_uid = @fileowner($filename);
0524          }
0525   
0526          // Change group
0527          if (@chgrp($filename, $_chmod_info['common_group']))
0528          {
0529              clearstatcache();
0530              $file_gid = @filegroup($filename);
0531          }
0532   
0533          // If the file_uid/gid now match the one from common.php we can process further, else we are not able to change something
0534          if ($file_uid != $_chmod_info['common_owner'] || $file_gid != $_chmod_info['common_group'])
0535          {
0536              $_chmod_info['process'] = false;
0537          }
0538      }
0539   
0540      // Still able to process?
0541      if ($_chmod_info['process'])
0542      {
0543          if ($file_uid == $_chmod_info['php_uid'])
0544          {
0545              $php = 'owner';
0546          }
0547          else if (in_array($file_gid, $_chmod_info['php_gids']))
0548          {
0549              $php = 'group';
0550          }
0551          else
0552          {
0553              // Since we are setting the everyone bit anyway, no need to do expensive operations
0554              $_chmod_info['process'] = false;
0555          }
0556      }
0557   
0558      // We are not able to determine or change something
0559      if (!$_chmod_info['process'])
0560      {
0561          $php = 'other';
0562      }
0563   
0564      // Owner always has read/write permission
0565      $owner = CHMOD_READ | CHMOD_WRITE;
0566      if (is_dir($filename))
0567      {
0568          $owner |= CHMOD_EXECUTE;
0569   
0570          // Only add execute bit to the permission if the dir needs to be readable
0571          if ($perms & CHMOD_READ)
0572          {
0573              $perms |= CHMOD_EXECUTE;
0574          }
0575      }
0576   
0577      switch ($php)
0578      {
0579          case 'owner':
0580              $result = @chmod($filename, ($owner << 6) + (0 << 3) + (0 << 0));
0581   
0582              clearstatcache();
0583   
0584              if (is_readable($filename) && phpbb_is_writable($filename))
0585              {
0586                  break;
0587              }
0588   
0589          case 'group':
0590              $result = @chmod($filename, ($owner << 6) + ($perms << 3) + (0 << 0));
0591   
0592              clearstatcache();
0593   
0594              if ((!($perms & CHMOD_READ) || is_readable($filename)) && (!($perms & CHMOD_WRITE) || phpbb_is_writable($filename)))
0595              {
0596                  break;
0597              }
0598   
0599          case 'other':
0600              $result = @chmod($filename, ($owner << 6) + ($perms << 3) + ($perms << 0));
0601   
0602              clearstatcache();
0603   
0604              if ((!($perms & CHMOD_READ) || is_readable($filename)) && (!($perms & CHMOD_WRITE) || phpbb_is_writable($filename)))
0605              {
0606                  break;
0607              }
0608   
0609          default:
0610              return false;
0611          break;
0612      }
0613   
0614      return $result;
0615  }
0616   
0617  /**
0618  * Test if a file/directory is writable
0619  *
0620  * This function calls the native is_writable() when not running under
0621  * Windows and it is not disabled.
0622  *
0623  * @param string $file Path to perform write test on
0624  * @return bool True when the path is writable, otherwise false.
0625  */
0626  function phpbb_is_writable($file)
0627  {
0628      if (strtolower(substr(PHP_OS, 0, 3)) === 'win' || !function_exists('is_writable'))
0629      {
0630          if (file_exists($file))
0631          {
0632              // Canonicalise path to absolute path
0633              $file = phpbb_realpath($file);
0634   
0635              if (is_dir($file))
0636              {
0637                  // Test directory by creating a file inside the directory
0638                  $result = @tempnam($file, 'i_w');
0639   
0640                  if (is_string($result) && file_exists($result))
0641                  {
0642                      unlink($result);
0643   
0644                      // Ensure the file is actually in the directory (returned realpathed)
0645                      return (strpos($result, $file) === 0) ? true : false;
0646                  }
0647              }
0648              else
0649              {
0650                  $handle = @fopen($file, 'r+');
0651   
0652                  if (is_resource($handle))
0653                  {
0654                      fclose($handle);
0655                      return true;
0656                  }
0657              }
0658          }
0659          else
0660          {
0661              // file does not exist test if we can write to the directory
0662              $dir = dirname($file);
0663   
0664              if (file_exists($dir) && is_dir($dir) && phpbb_is_writable($dir))
0665              {
0666                  return true;
0667              }
0668          }
0669   
0670          return false;
0671      }
0672      else
0673      {
0674          return is_writable($file);
0675      }
0676  }
0677   
0678  /**
0679  * Checks if a path ($path) is absolute or relative
0680  *
0681  * @param string $path Path to check absoluteness of
0682  * @return boolean
0683  */
0684  function phpbb_is_absolute($path)
0685  {
0686      return (isset($path[0]) && $path[0] == '/' || preg_match('#^[a-z]:[/\\\]#i', $path)) ? true : false;
0687  }
0688   
0689  /**
0690  * @author Chris Smith <chris@project-minerva.org>
0691  * @copyright 2006 Project Minerva Team
0692  * @param string $path The path which we should attempt to resolve.
0693  * @return mixed
0694  */
0695  function phpbb_own_realpath($path)
0696  {
0697      global $request;
0698   
0699      // Now to perform funky shizzle
0700   
0701      // Switch to use UNIX slashes
0702      $path = str_replace(DIRECTORY_SEPARATOR, '/', $path);
0703      $path_prefix = '';
0704   
0705      // Determine what sort of path we have
0706      if (phpbb_is_absolute($path))
0707      {
0708          $absolute = true;
0709   
0710          if ($path[0] == '/')
0711          {
0712              // Absolute path, *NIX style
0713              $path_prefix = '';
0714          }
0715          else
0716          {
0717              // Absolute path, Windows style
0718              // Remove the drive letter and colon
0719              $path_prefix = $path[0] . ':';
0720              $path = substr($path, 2);
0721          }
0722      }
0723      else
0724      {
0725          // Relative Path
0726          // Prepend the current working directory
0727          if (function_exists('getcwd'))
0728          {
0729              // This is the best method, hopefully it is enabled!
0730              $path = str_replace(DIRECTORY_SEPARATOR, '/', getcwd()) . '/' . $path;
0731              $absolute = true;
0732              if (preg_match('#^[a-z]:#i', $path))
0733              {
0734                  $path_prefix = $path[0] . ':';
0735                  $path = substr($path, 2);
0736              }
0737              else
0738              {
0739                  $path_prefix = '';
0740              }
0741          }
0742          else if ($request->server('SCRIPT_FILENAME'))
0743          {
0744              // Warning: If chdir() has been used this will lie!
0745              // Warning: This has some problems sometime (CLI can create them easily)
0746              $filename = htmlspecialchars_decode($request->server('SCRIPT_FILENAME'));
0747              $path = str_replace(DIRECTORY_SEPARATOR, '/', dirname($filename)) . '/' . $path;
0748              $absolute = true;
0749              $path_prefix = '';
0750          }
0751          else
0752          {
0753              // We have no way of getting the absolute path, just run on using relative ones.
0754              $absolute = false;
0755              $path_prefix = '.';
0756          }
0757      }
0758   
0759      // Remove any repeated slashes
0760      $path = preg_replace('#/{2,}#', '/', $path);
0761   
0762      // Remove the slashes from the start and end of the path
0763      $path = trim($path, '/');
0764   
0765      // Break the string into little bits for us to nibble on
0766      $bits = explode('/', $path);
0767   
0768      // Remove any . in the path, renumber array for the loop below
0769      $bits = array_values(array_diff($bits, array('.')));
0770   
0771      // Lets get looping, run over and resolve any .. (up directory)
0772      for ($i = 0, $max = sizeof($bits); $i < $max; $i++)
0773      {
0774          // @todo Optimise
0775          if ($bits[$i] == '..' )
0776          {
0777              if (isset($bits[$i - 1]))
0778              {
0779                  if ($bits[$i - 1] != '..')
0780                  {
0781                      // We found a .. and we are able to traverse upwards, lets do it!
0782                      unset($bits[$i]);
0783                      unset($bits[$i - 1]);
0784                      $i -= 2;
0785                      $max -= 2;
0786                      $bits = array_values($bits);
0787                  }
0788              }
0789              else if ($absolute) // ie. !isset($bits[$i - 1]) && $absolute
0790              {
0791                  // We have an absolute path trying to descend above the root of the filesystem
0792                  // ... Error!
0793                  return false;
0794              }
0795          }
0796      }
0797   
0798      // Prepend the path prefix
0799      array_unshift($bits, $path_prefix);
0800   
0801      $resolved = '';
0802   
0803      $max = sizeof($bits) - 1;
0804   
0805      // Check if we are able to resolve symlinks, Windows cannot.
0806      $symlink_resolve = (function_exists('readlink')) ? true : false;
0807   
0808      foreach ($bits as $i => $bit)
0809      {
0810          if (@is_dir("$resolved/$bit") || ($i == $max && @is_file("$resolved/$bit")))
0811          {
0812              // Path Exists
0813              if ($symlink_resolve && is_link("$resolved/$bit") && ($link = readlink("$resolved/$bit")))
0814              {
0815                  // Resolved a symlink.
0816                  $resolved = $link . (($i == $max) ? '' : '/');
0817                  continue;
0818              }
0819          }
0820          else
0821          {
0822              // Something doesn't exist here!
0823              // This is correct realpath() behaviour but sadly open_basedir and safe_mode make this problematic
0824              // return false;
0825          }
0826          $resolved .= $bit . (($i == $max) ? '' : '/');
0827      }
0828   
0829      // @todo If the file exists fine and open_basedir only has one path we should be able to prepend it
0830      // because we must be inside that basedir, the question is where...
0831      // @internal The slash in is_dir() gets around an open_basedir restriction
0832      if (!@file_exists($resolved) || (!@is_dir($resolved . '/') && !is_file($resolved)))
0833      {
0834          return false;
0835      }
0836   
0837      // Put the slashes back to the native operating systems slashes
0838      $resolved = str_replace('/', DIRECTORY_SEPARATOR, $resolved);
0839   
0840      // Check for DIRECTORY_SEPARATOR at the end (and remove it!)
0841      if (substr($resolved, -1) == DIRECTORY_SEPARATOR)
0842      {
0843          return substr($resolved, 0, -1);
0844      }
0845   
0846      return $resolved; // We got here, in the end!
0847  }
0848   
0849  if (!function_exists('realpath'))
0850  {
0851      /**
0852      * A wrapper for realpath
0853      * @ignore
0854      */
0855      function phpbb_realpath($path)
0856      {
0857          return phpbb_own_realpath($path);
0858      }
0859  }
0860  else
0861  {
0862      /**
0863      * A wrapper for realpath
0864      */
0865      function phpbb_realpath($path)
0866      {
0867          $realpath = realpath($path);
0868   
0869          // Strangely there are provider not disabling realpath but returning strange values. :o
0870          // We at least try to cope with them.
0871          if ($realpath === $path || $realpath === false)
0872          {
0873              return phpbb_own_realpath($path);
0874          }
0875   
0876          // Check for DIRECTORY_SEPARATOR at the end (and remove it!)
0877          if (substr($realpath, -1) == DIRECTORY_SEPARATOR)
0878          {
0879              $realpath = substr($realpath, 0, -1);
0880          }
0881   
0882          return $realpath;
0883      }
0884  }
0885   
0886  // functions used for building option fields
0887   
0888  /**
0889  * Pick a language, any language ...
0890  */
0891  function language_select($default = '')
0892  {
0893      global $db;
0894   
0895      $sql = 'SELECT lang_iso, lang_local_name
0896          FROM ' . LANG_TABLE . '
0897          ORDER BY lang_english_name';
0898      $result = $db->sql_query($sql);
0899   
0900      $lang_options = '';
0901      while ($row = $db->sql_fetchrow($result))
0902      {
0903          $selected = ($row['lang_iso'] == $default) ? ' selected="selected"' : '';
0904          $lang_options .= '<option value="' . $row['lang_iso'] . '"' . $selected . '>' . $row['lang_local_name'] . '</option>';
0905      }
0906      $db->sql_freeresult($result);
0907   
0908      return $lang_options;
0909  }
0910   
0911  /**
0912  * Pick a template/theme combo,
0913  */
0914  function style_select($default = '', $all = false)
0915  {
0916      global $db;
0917   
0918      $sql_where = (!$all) ? 'WHERE style_active = 1 ' : '';
0919      $sql = 'SELECT style_id, style_name
0920          FROM ' . STYLES_TABLE . "
0921          $sql_where
0922          ORDER BY style_name";
0923      $result = $db->sql_query($sql);
0924   
0925      $style_options = '';
0926      while ($row = $db->sql_fetchrow($result))
0927      {
0928          $selected = ($row['style_id'] == $default) ? ' selected="selected"' : '';
0929          $style_options .= '<option value="' . $row['style_id'] . '"' . $selected . '>' . $row['style_name'] . '</option>';
0930      }
0931      $db->sql_freeresult($result);
0932   
0933      return $style_options;
0934  }
0935   
0936  /**
0937  * Format the timezone offset with hours and minutes
0938  *
0939  * @param    int        $tz_offset    Timezone offset in seconds
0940  * @param    bool    $show_null    Whether null offsets should be shown
0941  * @return    string        Normalized offset string:    -7200 => -02:00
0942  *                                                    16200 => +04:30
0943  */
0944  function phpbb_format_timezone_offset($tz_offset, $show_null = false)
0945  {
0946      $sign = ($tz_offset < 0) ? '-' : '+';
0947      $time_offset = abs($tz_offset);
0948   
0949      if ($time_offset == 0 && $show_null == false)
0950      {
0951          return '';
0952      }
0953   
0954      $offset_seconds    = $time_offset % 3600;
0955      $offset_minutes    = $offset_seconds / 60;
0956      $offset_hours    = ($time_offset - $offset_seconds) / 3600;
0957   
0958      $offset_string    = sprintf("%s%02d:%02d", $sign, $offset_hours, $offset_minutes);
0959      return $offset_string;
0960  }
0961   
0962  /**
0963  * Compares two time zone labels.
0964  * Arranges them in increasing order by timezone offset.
0965  * Places UTC before other timezones in the same offset.
0966  */
0967  function phpbb_tz_select_compare($a, $b)
0968  {
0969      $a_sign = $a[3];
0970      $b_sign = $b[3];
0971      if ($a_sign != $b_sign)
0972      {
0973          return $a_sign == '-' ? -1 : 1;
0974      }
0975   
0976      $a_offset = substr($a, 4, 5);
0977      $b_offset = substr($b, 4, 5);
0978      if ($a_offset == $b_offset)
0979      {
0980          $a_name = substr($a, 12);
0981          $b_name = substr($b, 12);
0982          if ($a_name == $b_name)
0983          {
0984              return 0;
0985          }
0986          else if ($a_name == 'UTC')
0987          {
0988              return -1;
0989          }
0990          else if ($b_name == 'UTC')
0991          {
0992              return 1;
0993          }
0994          else
0995          {
0996              return $a_name < $b_name ? -1 : 1;
0997          }
0998      }
0999      else
1000      {
1001          if ($a_sign == '-')
1002          {
1003              return $a_offset > $b_offset ? -1 : 1;
1004          }
1005          else
1006          {
1007              return $a_offset < $b_offset ? -1 : 1;
1008          }
1009      }
1010  }
1011   
1012  /**
1013  * Return list of timezone identifiers
1014  * We also add the selected timezone if we can create an object with it.
1015  * DateTimeZone::listIdentifiers seems to not add all identifiers to the list,
1016  * because some are only kept for backward compatible reasons. If the user has
1017  * a deprecated value, we add it here, so it can still be kept. Once the user
1018  * changed his value, there is no way back to deprecated values.
1019  *
1020  * @param    string        $selected_timezone        Additional timezone that shall
1021  *                                                be added to the list of identiers
1022  * @return        array        DateTimeZone::listIdentifiers and additional
1023  *                            selected_timezone if it is a valid timezone.
1024  */
1025  function phpbb_get_timezone_identifiers($selected_timezone)
1026  {
1027      $timezones = DateTimeZone::listIdentifiers();
1028   
1029      if (!in_array($selected_timezone, $timezones))
1030      {
1031          try
1032          {
1033              // Add valid timezones that are currently selected but not returned
1034              // by DateTimeZone::listIdentifiers
1035              $validate_timezone = new DateTimeZone($selected_timezone);
1036              $timezones[] = $selected_timezone;
1037          }
1038          catch (\Exception $e)
1039          {
1040          }
1041      }
1042   
1043      return $timezones;
1044  }
1045   
1046  /**
1047  * Options to pick a timezone and date/time
1048  *
1049  * @param    \phpbb\template\template $template    phpBB template object
1050  * @param    \phpbb\user    $user                Object of the current user
1051  * @param    string        $default            A timezone to select
1052  * @param    boolean        $truncate            Shall we truncate the options text
1053  *
1054  * @return        array        Returns an array containing the options for the time selector.
1055  */
1056  function phpbb_timezone_select($template, $user, $default = '', $truncate = false)
1057  {
1058      static $timezones;
1059   
1060      $default_offset = '';
1061      if (!isset($timezones))
1062      {
1063          $unsorted_timezones = phpbb_get_timezone_identifiers($default);
1064   
1065          $timezones = array();
1066          foreach ($unsorted_timezones as $timezone)
1067          {
1068              $tz = new DateTimeZone($timezone);
1069              $dt = $user->create_datetime('now', $tz);
1070              $offset = $dt->getOffset();
1071              $current_time = $dt->format($user->lang['DATETIME_FORMAT'], true);
1072              $offset_string = phpbb_format_timezone_offset($offset, true);
1073              $timezones['UTC' . $offset_string . ' - ' . $timezone] = array(
1074                  'tz'        => $timezone,
1075                  'offset'    => $offset_string,
1076                  'current'    => $current_time,
1077              );
1078              if ($timezone === $default)
1079              {
1080                  $default_offset = 'UTC' . $offset_string;
1081              }
1082          }
1083          unset($unsorted_timezones);
1084   
1085          uksort($timezones, 'phpbb_tz_select_compare');
1086      }
1087   
1088      $tz_select = $opt_group = '';
1089   
1090      foreach ($timezones as $key => $timezone)
1091      {
1092          if ($opt_group != $timezone['offset'])
1093          {
1094              // Generate tz_select for backwards compatibility
1095              $tz_select .= ($opt_group) ? '</optgroup>' : '';
1096              $tz_select .= '<optgroup label="' . $user->lang(array('timezones', 'UTC_OFFSET_CURRENT'), $timezone['offset'], $timezone['current']) . '">';
1097              $opt_group = $timezone['offset'];
1098              $template->assign_block_vars('timezone_select', array(
1099                  'LABEL'        => $user->lang(array('timezones', 'UTC_OFFSET_CURRENT'), $timezone['offset'], $timezone['current']),
1100                  'VALUE'        => $key . ' - ' . $timezone['current'],
1101              ));
1102   
1103              $selected = (!empty($default_offset) && strpos($key, $default_offset) !== false) ? ' selected="selected"' : '';
1104              $template->assign_block_vars('timezone_date', array(
1105                  'VALUE'        => $key . ' - ' . $timezone['current'],
1106                  'SELECTED'    => !empty($selected),
1107                  'TITLE'        => $user->lang(array('timezones', 'UTC_OFFSET_CURRENT'), $timezone['offset'], $timezone['current']),
1108              ));
1109          }
1110   
1111          $label = $timezone['tz'];
1112          if (isset($user->lang['timezones'][$label]))
1113          {
1114              $label = $user->lang['timezones'][$label];
1115          }
1116          $title = $user->lang(array('timezones', 'UTC_OFFSET_CURRENT'), $timezone['offset'], $label);
1117   
1118          if ($truncate)
1119          {
1120              $label = truncate_string($label, 50, 255, false, '...');
1121          }
1122   
1123          // Also generate timezone_select for backwards compatibility
1124          $selected = ($timezone['tz'] === $default) ? ' selected="selected"' : '';
1125          $tz_select .= '<option title="' . $title . '" value="' . $timezone['tz'] . '"' . $selected . '>' . $label . '</option>';
1126          $template->assign_block_vars('timezone_select.timezone_options', array(
1127              'TITLE'            => $title,
1128              'VALUE'            => $timezone['tz'],
1129              'SELECTED'        => !empty($selected),
1130              'LABEL'            => $label,
1131          ));
1132      }
1133      $tz_select .= '</optgroup>';
1134   
1135      return $tz_select;
1136  }
1137   
1138  // Functions handling topic/post tracking/marking
1139   
1140  /**
1141  * Marks a topic/forum as read
1142  * Marks a topic as posted to
1143  *
1144  * @param string $mode (all, topics, topic, post)
1145  * @param int|bool $forum_id Used in all, topics, and topic mode
1146  * @param int|bool $topic_id Used in topic and post mode
1147  * @param int $post_time 0 means current time(), otherwise to set a specific mark time
1148  * @param int $user_id can only be used with $mode == 'post'
1149  */
1150  function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $user_id = 0)
1151  {
1152      global $db, $user, $config;
1153      global $request, $phpbb_container;
1154   
1155      $post_time = ($post_time === 0 || $post_time > time()) ? time() : (int) $post_time;
1156   
1157      if ($mode == 'all')
1158      {
1159          if ($forum_id === false || !sizeof($forum_id))
1160          {
1161              // Mark all forums read (index page)
1162   
1163              $phpbb_notifications = $phpbb_container->get('notification_manager');
1164   
1165              // Mark all topic notifications read for this user
1166              $phpbb_notifications->mark_notifications_read(array(
1167                  'notification.type.topic',
1168                  'notification.type.quote',
1169                  'notification.type.bookmark',
1170                  'notification.type.post',
1171                  'notification.type.approve_topic',
1172                  'notification.type.approve_post',
1173              ), false, $user->data['user_id'], $post_time);
1174   
1175              if ($config['load_db_lastread'] && $user->data['is_registered'])
1176              {
1177                  // Mark all forums read (index page)
1178                  $tables = array(TOPICS_TRACK_TABLE, FORUMS_TRACK_TABLE);
1179                  foreach ($tables as $table)
1180                  {
1181                      $sql = 'DELETE FROM ' . $table . "
1182                          WHERE user_id = {$user->data['user_id']}
1183                              AND mark_time < $post_time";
1184                      $db->sql_query($sql);
1185                  }
1186   
1187                  $sql = 'UPDATE ' . USERS_TABLE . "
1188                      SET user_lastmark = $post_time
1189                      WHERE user_id = {$user->data['user_id']}
1190                          AND user_lastmark < $post_time";
1191                  $db->sql_query($sql);
1192              }
1193              else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1194              {
1195                  $tracking_topics = $request->variable($config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE);
1196                  $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
1197   
1198                  unset($tracking_topics['tf']);
1199                  unset($tracking_topics['t']);
1200                  unset($tracking_topics['f']);
1201                  $tracking_topics['l'] = base_convert($post_time - $config['board_startdate'], 10, 36);
1202   
1203                  $user->set_cookie('track', tracking_serialize($tracking_topics), $post_time + 31536000);
1204                  $request->overwrite($config['cookie_name'] . '_track', tracking_serialize($tracking_topics), \phpbb\request\request_interface::COOKIE);
1205   
1206                  unset($tracking_topics);
1207   
1208                  if ($user->data['is_registered'])
1209                  {
1210                      $sql = 'UPDATE ' . USERS_TABLE . "
1211                          SET user_lastmark = $post_time
1212                          WHERE user_id = {$user->data['user_id']}
1213                              AND user_lastmark < $post_time";
1214                      $db->sql_query($sql);
1215                  }
1216              }
1217          }
1218   
1219          return;
1220      }
1221      else if ($mode == 'topics')
1222      {
1223          // Mark all topics in forums read
1224          if (!is_array($forum_id))
1225          {
1226              $forum_id = array($forum_id);
1227          }
1228   
1229          $phpbb_notifications = $phpbb_container->get('notification_manager');
1230   
1231          $phpbb_notifications->mark_notifications_read_by_parent(array(
1232              'notification.type.topic',
1233              'notification.type.approve_topic',
1234          ), $forum_id, $user->data['user_id'], $post_time);
1235   
1236          // Mark all post/quote notifications read for this user in this forum
1237          $topic_ids = array();
1238          $sql = 'SELECT topic_id
1239              FROM ' . TOPICS_TABLE . '
1240              WHERE ' . $db->sql_in_set('forum_id', $forum_id);
1241          $result = $db->sql_query($sql);
1242          while ($row = $db->sql_fetchrow($result))
1243          {
1244              $topic_ids[] = $row['topic_id'];
1245          }
1246          $db->sql_freeresult($result);
1247   
1248          $phpbb_notifications->mark_notifications_read_by_parent(array(
1249              'notification.type.quote',
1250              'notification.type.bookmark',
1251              'notification.type.post',
1252              'notification.type.approve_post',
1253          ), $topic_ids, $user->data['user_id'], $post_time);
1254   
1255          // Add 0 to forums array to mark global announcements correctly
1256          // $forum_id[] = 0;
1257   
1258          if ($config['load_db_lastread'] && $user->data['is_registered'])
1259          {
1260              $sql = 'DELETE FROM ' . TOPICS_TRACK_TABLE . "
1261                  WHERE user_id = {$user->data['user_id']}
1262                      AND mark_time < $post_time
1263                      AND " . $db->sql_in_set('forum_id', $forum_id);
1264              $db->sql_query($sql);
1265   
1266              $sql = 'SELECT forum_id
1267                  FROM ' . FORUMS_TRACK_TABLE . "
1268                  WHERE user_id = {$user->data['user_id']}
1269                      AND " . $db->sql_in_set('forum_id', $forum_id);
1270              $result = $db->sql_query($sql);
1271   
1272              $sql_update = array();
1273              while ($row = $db->sql_fetchrow($result))
1274              {
1275                  $sql_update[] = (int) $row['forum_id'];
1276              }
1277              $db->sql_freeresult($result);
1278   
1279              if (sizeof($sql_update))
1280              {
1281                  $sql = 'UPDATE ' . FORUMS_TRACK_TABLE . "
1282                      SET mark_time = $post_time
1283                      WHERE user_id = {$user->data['user_id']}
1284                          AND mark_time < $post_time
1285                          AND " . $db->sql_in_set('forum_id', $sql_update);
1286                  $db->sql_query($sql);
1287              }
1288   
1289              if ($sql_insert = array_diff($forum_id, $sql_update))
1290              {
1291                  $sql_ary = array();
1292                  foreach ($sql_insert as $f_id)
1293                  {
1294                      $sql_ary[] = array(
1295                          'user_id'    => (int) $user->data['user_id'],
1296                          'forum_id'    => (int) $f_id,
1297                          'mark_time'    => $post_time,
1298                      );
1299                  }
1300   
1301                  $db->sql_multi_insert(FORUMS_TRACK_TABLE, $sql_ary);
1302              }
1303          }
1304          else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1305          {
1306              $tracking = $request->variable($config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE);
1307              $tracking = ($tracking) ? tracking_unserialize($tracking) : array();
1308   
1309              foreach ($forum_id as $f_id)
1310              {
1311                  $topic_ids36 = (isset($tracking['tf'][$f_id])) ? $tracking['tf'][$f_id] : array();
1312   
1313                  if (isset($tracking['tf'][$f_id]))
1314                  {
1315                      unset($tracking['tf'][$f_id]);
1316                  }
1317   
1318                  foreach ($topic_ids36 as $topic_id36)
1319                  {
1320                      unset($tracking['t'][$topic_id36]);
1321                  }
1322   
1323                  if (isset($tracking['f'][$f_id]))
1324                  {
1325                      unset($tracking['f'][$f_id]);
1326                  }
1327   
1328                  $tracking['f'][$f_id] = base_convert($post_time - $config['board_startdate'], 10, 36);
1329              }
1330   
1331              if (isset($tracking['tf']) && empty($tracking['tf']))
1332              {
1333                  unset($tracking['tf']);
1334              }
1335   
1336              $user->set_cookie('track', tracking_serialize($tracking), $post_time + 31536000);
1337              $request->overwrite($config['cookie_name'] . '_track', tracking_serialize($tracking), \phpbb\request\request_interface::COOKIE);
1338   
1339              unset($tracking);
1340          }
1341   
1342          return;
1343      }
1344      else if ($mode == 'topic')
1345      {
1346          if ($topic_id === false || $forum_id === false)
1347          {
1348              return;
1349          }
1350   
1351          $phpbb_notifications = $phpbb_container->get('notification_manager');
1352   
1353          // Mark post notifications read for this user in this topic
1354          $phpbb_notifications->mark_notifications_read(array(
1355              'notification.type.topic',
1356              'notification.type.approve_topic',
1357          ), $topic_id, $user->data['user_id'], $post_time);
1358   
1359          $phpbb_notifications->mark_notifications_read_by_parent(array(
1360              'notification.type.quote',
1361              'notification.type.bookmark',
1362              'notification.type.post',
1363              'notification.type.approve_post',
1364          ), $topic_id, $user->data['user_id'], $post_time);
1365   
1366          if ($config['load_db_lastread'] && $user->data['is_registered'])
1367          {
1368              $sql = 'UPDATE ' . TOPICS_TRACK_TABLE . "
1369                  SET mark_time = $post_time
1370                  WHERE user_id = {$user->data['user_id']}
1371                      AND mark_time < $post_time
1372                      AND topic_id = $topic_id";
1373              $db->sql_query($sql);
1374   
1375              // insert row
1376              if (!$db->sql_affectedrows())
1377              {
1378                  $db->sql_return_on_error(true);
1379   
1380                  $sql_ary = array(
1381                      'user_id'        => (int) $user->data['user_id'],
1382                      'topic_id'        => (int) $topic_id,
1383                      'forum_id'        => (int) $forum_id,
1384                      'mark_time'        => $post_time,
1385                  );
1386   
1387                  $db->sql_query('INSERT INTO ' . TOPICS_TRACK_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
1388   
1389                  $db->sql_return_on_error(false);
1390              }
1391          }
1392          else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1393          {
1394              $tracking = $request->variable($config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE);
1395              $tracking = ($tracking) ? tracking_unserialize($tracking) : array();
1396   
1397              $topic_id36 = base_convert($topic_id, 10, 36);
1398   
1399              if (!isset($tracking['t'][$topic_id36]))
1400              {
1401                  $tracking['tf'][$forum_id][$topic_id36] = true;
1402              }
1403   
1404              $tracking['t'][$topic_id36] = base_convert($post_time - $config['board_startdate'], 10, 36);
1405   
1406              // If the cookie grows larger than 10000 characters we will remove the smallest value
1407              // This can result in old topics being unread - but most of the time it should be accurate...
1408              if (strlen($request->variable($config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE)) > 10000)
1409              {
1410                  //echo 'Cookie grown too large' . print_r($tracking, true);
1411   
1412                  // We get the ten most minimum stored time offsets and its associated topic ids
1413                  $time_keys = array();
1414                  for ($i = 0; $i < 10 && sizeof($tracking['t']); $i++)
1415                  {
1416                      $min_value = min($tracking['t']);
1417                      $m_tkey = array_search($min_value, $tracking['t']);
1418                      unset($tracking['t'][$m_tkey]);
1419   
1420                      $time_keys[$m_tkey] = $min_value;
1421                  }
1422   
1423                  // Now remove the topic ids from the array...
1424                  foreach ($tracking['tf'] as $f_id => $topic_id_ary)
1425                  {
1426                      foreach ($time_keys as $m_tkey => $min_value)
1427                      {
1428                          if (isset($topic_id_ary[$m_tkey]))
1429                          {
1430                              $tracking['f'][$f_id] = $min_value;
1431                              unset($tracking['tf'][$f_id][$m_tkey]);
1432                          }
1433                      }
1434                  }
1435   
1436                  if ($user->data['is_registered'])
1437                  {
1438                      $user->data['user_lastmark'] = intval(base_convert(max($time_keys) + $config['board_startdate'], 36, 10));
1439   
1440                      $sql = 'UPDATE ' . USERS_TABLE . "
1441                          SET user_lastmark = $post_time
1442                          WHERE user_id = {$user->data['user_id']}
1443                              AND mark_time < $post_time";
1444                      $db->sql_query($sql);
1445                  }
1446                  else
1447                  {
1448                      $tracking['l'] = max($time_keys);
1449                  }
1450              }
1451   
1452              $user->set_cookie('track', tracking_serialize($tracking), $post_time + 31536000);
1453              $request->overwrite($config['cookie_name'] . '_track', tracking_serialize($tracking), \phpbb\request\request_interface::COOKIE);
1454          }
1455   
1456          return;
1457      }
1458      else if ($mode == 'post')
1459      {
1460          if ($topic_id === false)
1461          {
1462              return;
1463          }
1464   
1465          $use_user_id = (!$user_id) ? $user->data['user_id'] : $user_id;
1466   
1467          if ($config['load_db_track'] && $use_user_id != ANONYMOUS)
1468          {
1469              $db->sql_return_on_error(true);
1470   
1471              $sql_ary = array(
1472                  'user_id'        => (int) $use_user_id,
1473                  'topic_id'        => (int) $topic_id,
1474                  'topic_posted'    => 1,
1475              );
1476   
1477              $db->sql_query('INSERT INTO ' . TOPICS_POSTED_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
1478   
1479              $db->sql_return_on_error(false);
1480          }
1481   
1482          return;
1483      }
1484  }
1485   
1486  /**
1487  * Get topic tracking info by using already fetched info
1488  */
1489  function get_topic_tracking($forum_id, $topic_ids, &$rowset, $forum_mark_time, $global_announce_list = false)
1490  {
1491      global $config, $user;
1492   
1493      $last_read = array();
1494   
1495      if (!is_array($topic_ids))
1496      {
1497          $topic_ids = array($topic_ids);
1498      }
1499   
1500      foreach ($topic_ids as $topic_id)
1501      {
1502          if (!empty($rowset[$topic_id]['mark_time']))
1503          {
1504              $last_read[$topic_id] = $rowset[$topic_id]['mark_time'];
1505          }
1506      }
1507   
1508      $topic_ids = array_diff($topic_ids, array_keys($last_read));
1509   
1510      if (sizeof($topic_ids))
1511      {
1512          $mark_time = array();
1513   
1514          if (!empty($forum_mark_time[$forum_id]) && $forum_mark_time[$forum_id] !== false)
1515          {
1516              $mark_time[$forum_id] = $forum_mark_time[$forum_id];
1517          }
1518   
1519          $user_lastmark = (isset($mark_time[$forum_id])) ? $mark_time[$forum_id] : $user->data['user_lastmark'];
1520   
1521          foreach ($topic_ids as $topic_id)
1522          {
1523              $last_read[$topic_id] = $user_lastmark;
1524          }
1525      }
1526   
1527      return $last_read;
1528  }
1529   
1530  /**
1531  * Get topic tracking info from db (for cookie based tracking only this function is used)
1532  */
1533  function get_complete_topic_tracking($forum_id, $topic_ids, $global_announce_list = false)
1534  {
1535      global $config, $user, $request;
1536   
1537      $last_read = array();
1538   
1539      if (!is_array($topic_ids))
1540      {
1541          $topic_ids = array($topic_ids);
1542      }
1543   
1544      if ($config['load_db_lastread'] && $user->data['is_registered'])
1545      {
1546          global $db;
1547   
1548          $sql = 'SELECT topic_id, mark_time
1549              FROM ' . TOPICS_TRACK_TABLE . "
1550              WHERE user_id = {$user->data['user_id']}
1551                  AND " . $db->sql_in_set('topic_id', $topic_ids);
1552          $result = $db->sql_query($sql);
1553   
1554          while ($row = $db->sql_fetchrow($result))
1555          {
1556              $last_read[$row['topic_id']] = $row['mark_time'];
1557          }
1558          $db->sql_freeresult($result);
1559   
1560          $topic_ids = array_diff($topic_ids, array_keys($last_read));
1561   
1562          if (sizeof($topic_ids))
1563          {
1564              $sql = 'SELECT forum_id, mark_time
1565                  FROM ' . FORUMS_TRACK_TABLE . "
1566                  WHERE user_id = {$user->data['user_id']}
1567                      AND forum_id = $forum_id";
1568              $result = $db->sql_query($sql);
1569   
1570              $mark_time = array();
1571              while ($row = $db->sql_fetchrow($result))
1572              {
1573                  $mark_time[$row['forum_id']] = $row['mark_time'];
1574              }
1575              $db->sql_freeresult($result);
1576   
1577              $user_lastmark = (isset($mark_time[$forum_id])) ? $mark_time[$forum_id] : $user->data['user_lastmark'];
1578   
1579              foreach ($topic_ids as $topic_id)
1580              {
1581                  $last_read[$topic_id] = $user_lastmark;
1582              }
1583          }
1584      }
1585      else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1586      {
1587          global $tracking_topics;
1588   
1589          if (!isset($tracking_topics) || !sizeof($tracking_topics))
1590          {
1591              $tracking_topics = $request->variable($config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE);
1592              $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
1593          }
1594   
1595          if (!$user->data['is_registered'])
1596          {
1597              $user_lastmark = (isset($tracking_topics['l'])) ? base_convert($tracking_topics['l'], 36, 10) + $config['board_startdate'] : 0;
1598          }
1599          else
1600          {
1601              $user_lastmark = $user->data['user_lastmark'];
1602          }
1603   
1604          foreach ($topic_ids as $topic_id)
1605          {
1606              $topic_id36 = base_convert($topic_id, 10, 36);
1607   
1608              if (isset($tracking_topics['t'][$topic_id36]))
1609              {
1610                  $last_read[$topic_id] = base_convert($tracking_topics['t'][$topic_id36], 36, 10) + $config['board_startdate'];
1611              }
1612          }
1613   
1614          $topic_ids = array_diff($topic_ids, array_keys($last_read));
1615   
1616          if (sizeof($topic_ids))
1617          {
1618              $mark_time = array();
1619   
1620              if (isset($tracking_topics['f'][$forum_id]))
1621              {
1622                  $mark_time[$forum_id] = base_convert($tracking_topics['f'][$forum_id], 36, 10) + $config['board_startdate'];
1623              }
1624   
1625              $user_lastmark = (isset($mark_time[$forum_id])) ? $mark_time[$forum_id] : $user_lastmark;
1626   
1627              foreach ($topic_ids as $topic_id)
1628              {
1629                  $last_read[$topic_id] = $user_lastmark;
1630              }
1631          }
1632      }
1633   
1634      return $last_read;
1635  }
1636   
1637  /**
1638  * Get list of unread topics
1639  *
1640  * @param int $user_id            User ID (or false for current user)
1641  * @param string $sql_extra        Extra WHERE SQL statement
1642  * @param string $sql_sort        ORDER BY SQL sorting statement
1643  * @param string $sql_limit        Limits the size of unread topics list, 0 for unlimited query
1644  * @param string $sql_limit_offset  Sets the offset of the first row to search, 0 to search from the start
1645  *
1646  * @return array[int][int]        Topic ids as keys, mark_time of topic as value
1647  */
1648  function get_unread_topics($user_id = false, $sql_extra = '', $sql_sort = '', $sql_limit = 1001, $sql_limit_offset = 0)
1649  {
1650      global $config, $db, $user;
1651   
1652      $user_id = ($user_id === false) ? (int) $user->data['user_id'] : (int) $user_id;
1653   
1654      // Data array we're going to return
1655      $unread_topics = array();
1656   
1657      if (empty($sql_sort))
1658      {
1659          $sql_sort = 'ORDER BY t.topic_last_post_time DESC, t.topic_last_post_id DESC';
1660      }
1661   
1662      if ($config['load_db_lastread'] && $user->data['is_registered'])
1663      {
1664          // Get list of the unread topics
1665          $last_mark = (int) $user->data['user_lastmark'];
1666   
1667          $sql_array = array(
1668              'SELECT'        => 't.topic_id, t.topic_last_post_time, tt.mark_time as topic_mark_time, ft.mark_time as forum_mark_time',
1669   
1670              'FROM'            => array(TOPICS_TABLE => 't'),
1671   
1672              'LEFT_JOIN'        => array(
1673                  array(
1674                      'FROM'    => array(TOPICS_TRACK_TABLE => 'tt'),
1675                      'ON'    => "tt.user_id = $user_id AND t.topic_id = tt.topic_id",
1676                  ),
1677                  array(
1678                      'FROM'    => array(FORUMS_TRACK_TABLE => 'ft'),
1679                      'ON'    => "ft.user_id = $user_id AND t.forum_id = ft.forum_id",
1680                  ),
1681              ),
1682   
1683              'WHERE'            => "
1684                   t.topic_last_post_time > $last_mark AND
1685                  (
1686                  (tt.mark_time IS NOT NULL AND t.topic_last_post_time > tt.mark_time) OR
1687                  (tt.mark_time IS NULL AND ft.mark_time IS NOT NULL AND t.topic_last_post_time > ft.mark_time) OR
1688                  (tt.mark_time IS NULL AND ft.mark_time IS NULL)
1689                  )
1690                  $sql_extra
1691                  $sql_sort",
1692          );
1693   
1694          $sql = $db->sql_build_query('SELECT', $sql_array);
1695          $result = $db->sql_query_limit($sql, $sql_limit, $sql_limit_offset);
1696   
1697          while ($row = $db->sql_fetchrow($result))
1698          {
1699              $topic_id = (int) $row['topic_id'];
1700              $unread_topics[$topic_id] = ($row['topic_mark_time']) ? (int) $row['topic_mark_time'] : (($row['forum_mark_time']) ? (int) $row['forum_mark_time'] : $last_mark);
1701          }
1702          $db->sql_freeresult($result);
1703      }
1704      else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1705      {
1706          global $tracking_topics;
1707   
1708          if (empty($tracking_topics))
1709          {
1710              $tracking_topics = request_var($config['cookie_name'] . '_track', '', false, true);
1711              $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
1712          }
1713   
1714          if (!$user->data['is_registered'])
1715          {
1716              $user_lastmark = (isset($tracking_topics['l'])) ? base_convert($tracking_topics['l'], 36, 10) + $config['board_startdate'] : 0;
1717          }
1718          else
1719          {
1720              $user_lastmark = (int) $user->data['user_lastmark'];
1721          }
1722   
1723          $sql = 'SELECT t.topic_id, t.forum_id, t.topic_last_post_time
1724              FROM ' . TOPICS_TABLE . ' t
1725              WHERE t.topic_last_post_time > ' . $user_lastmark . "
1726              $sql_extra
1727              $sql_sort";
1728          $result = $db->sql_query_limit($sql, $sql_limit, $sql_limit_offset);
1729   
1730          while ($row = $db->sql_fetchrow($result))
1731          {
1732              $forum_id = (int) $row['forum_id'];
1733              $topic_id = (int) $row['topic_id'];
1734              $topic_id36 = base_convert($topic_id, 10, 36);
1735   
1736              if (isset($tracking_topics['t'][$topic_id36]))
1737              {
1738                  $last_read = base_convert($tracking_topics['t'][$topic_id36], 36, 10) + $config['board_startdate'];
1739   
1740                  if ($row['topic_last_post_time'] > $last_read)
1741                  {
1742                      $unread_topics[$topic_id] = $last_read;
1743                  }
1744              }
1745              else if (isset($tracking_topics['f'][$forum_id]))
1746              {
1747                  $mark_time = base_convert($tracking_topics['f'][$forum_id], 36, 10) + $config['board_startdate'];
1748   
1749                  if ($row['topic_last_post_time'] > $mark_time)
1750                  {
1751                      $unread_topics[$topic_id] = $mark_time;
1752                  }
1753              }
1754              else
1755              {
1756                  $unread_topics[$topic_id] = $user_lastmark;
1757              }
1758          }
1759          $db->sql_freeresult($result);
1760      }
1761   
1762      return $unread_topics;
1763  }
1764   
1765  /**
1766  * Check for read forums and update topic tracking info accordingly
1767  *
1768  * @param int $forum_id the forum id to check
1769  * @param int $forum_last_post_time the forums last post time
1770  * @param int $f_mark_time the forums last mark time if user is registered and load_db_lastread enabled
1771  * @param int $mark_time_forum false if the mark time needs to be obtained, else the last users forum mark time
1772  *
1773  * @return true if complete forum got marked read, else false.
1774  */
1775  function update_forum_tracking_info($forum_id, $forum_last_post_time, $f_mark_time = false, $mark_time_forum = false)
1776  {
1777      global $db, $tracking_topics, $user, $config, $auth, $request, $phpbb_container;
1778   
1779      // Determine the users last forum mark time if not given.
1780      if ($mark_time_forum === false)
1781      {
1782          if ($config['load_db_lastread'] && $user->data['is_registered'])
1783          {
1784              $mark_time_forum = (!empty($f_mark_time)) ? $f_mark_time : $user->data['user_lastmark'];
1785          }
1786          else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1787          {
1788              $tracking_topics = $request->variable($config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE);
1789              $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
1790   
1791              if (!$user->data['is_registered'])
1792              {
1793                  $user->data['user_lastmark'] = (isset($tracking_topics['l'])) ? (int) (base_convert($tracking_topics['l'], 36, 10) + $config['board_startdate']) : 0;
1794              }
1795   
1796              $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'];
1797          }
1798      }
1799   
1800      // Handle update of unapproved topics info.
1801      // Only update for moderators having m_approve permission for the forum.
1802      $phpbb_content_visibility = $phpbb_container->get('content.visibility');
1803   
1804      // Check the forum for any left unread topics.
1805      // If there are none, we mark the forum as read.
1806      if ($config['load_db_lastread'] && $user->data['is_registered'])
1807      {
1808          if ($mark_time_forum >= $forum_last_post_time)
1809          {
1810              // We do not need to mark read, this happened before. Therefore setting this to true
1811              $row = true;
1812          }
1813          else
1814          {
1815              $sql = 'SELECT t.forum_id
1816                  FROM ' . TOPICS_TABLE . ' t
1817                  LEFT JOIN ' . TOPICS_TRACK_TABLE . ' tt
1818                      ON (tt.topic_id = t.topic_id
1819                          AND tt.user_id = ' . $user->data['user_id'] . ')
1820                  WHERE t.forum_id = ' . $forum_id . '
1821                      AND t.topic_last_post_time > ' . $mark_time_forum . '
1822                      AND t.topic_moved_id = 0
1823                      AND ' . $phpbb_content_visibility->get_visibility_sql('topic', $forum_id, 't.') . '
1824                      AND (tt.topic_id IS NULL
1825                          OR tt.mark_time < t.topic_last_post_time)';
1826              $result = $db->sql_query_limit($sql, 1);
1827              $row = $db->sql_fetchrow($result);
1828              $db->sql_freeresult($result);
1829          }
1830      }
1831      else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1832      {
1833          // Get information from cookie
1834          $row = false;
1835   
1836          if (!isset($tracking_topics['tf'][$forum_id]))
1837          {
1838              // We do not need to mark read, this happened before. Therefore setting this to true
1839              $row = true;
1840          }
1841          else
1842          {
1843              $sql = 'SELECT t.topic_id
1844                  FROM ' . TOPICS_TABLE . ' t
1845                  WHERE t.forum_id = ' . $forum_id . '
1846                      AND t.topic_last_post_time > ' . $mark_time_forum . '
1847                      AND t.topic_moved_id = 0
1848                      AND ' . $phpbb_content_visibility->get_visibility_sql('topic', $forum_id, 't.');
1849              $result = $db->sql_query($sql);
1850   
1851              $check_forum = $tracking_topics['tf'][$forum_id];
1852              $unread = false;
1853   
1854              while ($row = $db->sql_fetchrow($result))
1855              {
1856                  if (!isset($check_forum[base_convert($row['topic_id'], 10, 36)]))
1857                  {
1858                      $unread = true;
1859                      break;
1860                  }
1861              }
1862              $db->sql_freeresult($result);
1863   
1864              $row = $unread;
1865          }
1866      }
1867      else
1868      {
1869          $row = true;
1870      }
1871   
1872      if (!$row)
1873      {
1874          markread('topics', $forum_id);
1875          return true;
1876      }
1877   
1878      return false;
1879  }
1880   
1881  /**
1882  * Transform an array into a serialized format
1883  */
1884  function tracking_serialize($input)
1885  {
1886      $out = '';
1887      foreach ($input as $key => $value)
1888      {
1889          if (is_array($value))
1890          {
1891              $out .= $key . ':(' . tracking_serialize($value) . ');';
1892          }
1893          else
1894          {
1895              $out .= $key . ':' . $value . ';';
1896          }
1897      }
1898      return $out;
1899  }
1900   
1901  /**
1902  * Transform a serialized array into an actual array
1903  */
1904  function tracking_unserialize($string, $max_depth = 3)
1905  {
1906      $n = strlen($string);
1907      if ($n > 10010)
1908      {
1909          die('Invalid data supplied');
1910      }
1911      $data = $stack = array();
1912      $key = '';
1913      $mode = 0;
1914      $level = &$data;
1915      for ($i = 0; $i < $n; ++$i)
1916      {
1917          switch ($mode)
1918          {
1919              case 0:
1920                  switch ($string[$i])
1921                  {
1922                      case ':':
1923                          $level[$key] = 0;
1924                          $mode = 1;
1925                      break;
1926                      case ')':
1927                          unset($level);
1928                          $level = array_pop($stack);
1929                          $mode = 3;
1930                      break;
1931                      default:
1932                          $key .= $string[$i];
1933                  }
1934              break;
1935   
1936              case 1:
1937                  switch ($string[$i])
1938                  {
1939                      case '(':
1940                          if (sizeof($stack) >= $max_depth)
1941                          {
1942                              die('Invalid data supplied');
1943                          }
1944                          $stack[] = &$level;
1945                          $level[$key] = array();
1946                          $level = &$level[$key];
1947                          $key = '';
1948                          $mode = 0;
1949                      break;
1950                      default:
1951                          $level[$key] = $string[$i];
1952                          $mode = 2;
1953                      break;
1954                  }
1955              break;
1956   
1957              case 2:
1958                  switch ($string[$i])
1959                  {
1960                      case ')':
1961                          unset($level);
1962                          $level = array_pop($stack);
1963                          $mode = 3;
1964                      break;
1965                      case ';':
1966                          $key = '';
1967                          $mode = 0;
1968                      break;
1969                      default:
1970                          $level[$key] .= $string[$i];
1971                      break;
1972                  }
1973              break;
1974   
1975              case 3:
1976                  switch ($string[$i])
1977                  {
1978                      case ')':
1979                          unset($level);
1980                          $level = array_pop($stack);
1981                      break;
1982                      case ';':
1983                          $key = '';
1984                          $mode = 0;
1985                      break;
1986                      default:
1987                          die('Invalid data supplied');
1988                      break;
1989                  }
1990              break;
1991          }
1992      }
1993   
1994      if (sizeof($stack) != 0 || ($mode != 0 && $mode != 3))
1995      {
1996          die('Invalid data supplied');
1997      }
1998   
1999      return $level;
2000  }
2001   
2002  // Server functions (building urls, redirecting...)
2003   
2004  /**
2005  * Append session id to url.
2006  * This function supports hooks.
2007  *
2008  * @param string $url The url the session id needs to be appended to (can have params)
2009  * @param mixed $params String or array of additional url parameters
2010  * @param bool $is_amp Is url using &amp; (true) or & (false)
2011  * @param string $session_id Possibility to use a custom session id instead of the global one
2012  * @param bool $is_route Is url generated by a route.
2013  *
2014  * @return string The corrected url.
2015  *
2016  * Examples:
2017  * <code>
2018  * append_sid("{$phpbb_root_path}viewtopic.$phpEx?t=1&amp;f=2");
2019  * append_sid("{$phpbb_root_path}viewtopic.$phpEx", 't=1&amp;f=2');
2020  * append_sid("{$phpbb_root_path}viewtopic.$phpEx", 't=1&f=2', false);
2021  * append_sid("{$phpbb_root_path}viewtopic.$phpEx", array('t' => 1, 'f' => 2));
2022  * </code>
2023  *
2024  */
2025  function append_sid($url, $params = false, $is_amp = true, $session_id = false, $is_route = false)
2026  {
2027      global $_SID, $_EXTRA_URL, $phpbb_hook, $phpbb_path_helper;
2028      global $phpbb_dispatcher;
2029   
2030      if ($params === '' || (is_array($params) && empty($params)))
2031      {
2032          // Do not append the ? if the param-list is empty anyway.
2033          $params = false;
2034      }
2035   
2036      // Update the root path with the correct relative web path
2037      if (!$is_route && $phpbb_path_helper instanceof \phpbb\path_helper)
2038      {
2039          $url = $phpbb_path_helper->update_web_root_path($url);
2040      }
2041   
2042      $append_sid_overwrite = false;
2043   
2044      /**
2045      * This event can either supplement or override the append_sid() function
2046      *
2047      * To override this function, the event must set $append_sid_overwrite to
2048      * the new URL value, which will be returned following the event
2049      *
2050      * @event core.append_sid
2051      * @var    string        url                        The url the session id needs
2052      *                                            to be appended to (can have
2053      *                                            params)
2054      * @var    mixed        params                    String or array of additional
2055      *                                            url parameters
2056      * @var    bool        is_amp                    Is url using &amp; (true) or
2057      *                                            & (false)
2058      * @var    bool|string    session_id                Possibility to use a custom
2059      *                                            session id (string) instead of
2060      *                                            the global one (false)
2061      * @var    bool|string    append_sid_overwrite    Overwrite function (string
2062      *                                            URL) or not (false)
2063      * @var    bool    is_route                    Is url generated by a route.
2064      * @since 3.1.0-a1
2065      */
2066      $vars = array('url', 'params', 'is_amp', 'session_id', 'append_sid_overwrite', 'is_route');
2067      extract($phpbb_dispatcher->trigger_event('core.append_sid', compact($vars)));
2068   
2069      if ($append_sid_overwrite)
2070      {
2071          return $append_sid_overwrite;
2072      }
2073   
2074      // The following hook remains for backwards compatibility, though use of
2075      // the event above is preferred.
2076      // Developers using the hook function need to globalise the $_SID and $_EXTRA_URL on their own and also handle it appropriately.
2077      // They could mimic most of what is within this function
2078      if (!empty($phpbb_hook) && $phpbb_hook->call_hook(__FUNCTION__, $url, $params, $is_amp, $session_id))
2079      {
2080          if ($phpbb_hook->hook_return(__FUNCTION__))
2081          {
2082              return $phpbb_hook->hook_return_result(__FUNCTION__);
2083          }
2084      }
2085   
2086      $params_is_array = is_array($params);
2087   
2088      // Get anchor
2089      $anchor = '';
2090      if (strpos($url, '#') !== false)
2091      {
2092          list($url, $anchor) = explode('#', $url, 2);
2093          $anchor = '#' . $anchor;
2094      }
2095      else if (!$params_is_array && strpos($params, '#') !== false)
2096      {
2097          list($params, $anchor) = explode('#', $params, 2);
2098          $anchor = '#' . $anchor;
2099      }
2100   
2101      // Handle really simple cases quickly
2102      if ($_SID == '' && $session_id === false && empty($_EXTRA_URL) && !$params_is_array && !$anchor)
2103      {
2104          if ($params === false)
2105          {
2106              return $url;
2107          }
2108   
2109          $url_delim = (strpos($url, '?') === false) ? '?' : (($is_amp) ? '&amp;' : '&');
2110          return $url . ($params !== false ? $url_delim. $params : '');
2111      }
2112   
2113      // Assign sid if session id is not specified
2114      if ($session_id === false)
2115      {
2116          $session_id = $_SID;
2117      }
2118   
2119      $amp_delim = ($is_amp) ? '&amp;' : '&';
2120      $url_delim = (strpos($url, '?') === false) ? '?' : $amp_delim;
2121   
2122      // Appending custom url parameter?
2123      $append_url = (!empty($_EXTRA_URL)) ? implode($amp_delim, $_EXTRA_URL) : '';
2124   
2125      // Use the short variant if possible ;)
2126      if ($params === false)
2127      {
2128          // Append session id
2129          if (!$session_id)
2130          {
2131              return $url . (($append_url) ? $url_delim . $append_url : '') . $anchor;
2132          }
2133          else
2134          {
2135              return $url . (($append_url) ? $url_delim . $append_url . $amp_delim : $url_delim) . 'sid=' . $session_id . $anchor;
2136          }
2137      }
2138   
2139      // Build string if parameters are specified as array
2140      if (is_array($params))
2141      {
2142          $output = array();
2143   
2144          foreach ($params as $key => $item)
2145          {
2146              if ($item === NULL)
2147              {
2148                  continue;
2149              }
2150   
2151              if ($key == '#')
2152              {
2153                  $anchor = '#' . $item;
2154                  continue;
2155              }
2156   
2157              $output[] = $key . '=' . $item;
2158          }
2159   
2160          $params = implode($amp_delim, $output);
2161      }
2162   
2163      // Append session id and parameters (even if they are empty)
2164      // If parameters are empty, the developer can still append his/her parameters without caring about the delimiter
2165      return $url . (($append_url) ? $url_delim . $append_url . $amp_delim : $url_delim) . $params . ((!$session_id) ? '' : $amp_delim . 'sid=' . $session_id) . $anchor;
2166  }
2167   
2168  /**
2169  * Generate board url (example: http://www.example.com/phpBB)
2170  *
2171  * @param bool $without_script_path if set to true the script path gets not appended (example: http://www.example.com)
2172  *
2173  * @return string the generated board url
2174  */
2175  function generate_board_url($without_script_path = false)
2176  {
2177      global $config, $user, $request;
2178   
2179      $server_name = $user->host;
2180      $server_port = $request->server('SERVER_PORT', 0);
2181   
2182      // Forcing server vars is the only way to specify/override the protocol
2183      if ($config['force_server_vars'] || !$server_name)
2184      {
2185          $server_protocol = ($config['server_protocol']) ? $config['server_protocol'] : (($config['cookie_secure']) ? 'https://' : 'http://');
2186          $server_name = $config['server_name'];
2187          $server_port = (int) $config['server_port'];
2188          $script_path = $config['script_path'];
2189   
2190          $url = $server_protocol . $server_name;
2191          $cookie_secure = $config['cookie_secure'];
2192      }
2193      else
2194      {
2195          // Do not rely on cookie_secure, users seem to think that it means a secured cookie instead of an encrypted connection
2196          $cookie_secure = $request->is_secure() ? 1 : 0;
2197          $url = (($cookie_secure) ? 'https://' : 'http://') . $server_name;
2198   
2199          $script_path = $user->page['root_script_path'];
2200      }
2201   
2202      if ($server_port && (($cookie_secure && $server_port <> 443) || (!$cookie_secure && $server_port <> 80)))
2203      {
2204          // HTTP HOST can carry a port number (we fetch $user->host, but for old versions this may be true)
2205          if (strpos($server_name, ':') === false)
2206          {
2207              $url .= ':' . $server_port;
2208          }
2209      }
2210   
2211      if (!$without_script_path)
2212      {
2213          $url .= $script_path;
2214      }
2215   
2216      // Strip / from the end
2217      if (substr($url, -1, 1) == '/')
2218      {
2219          $url = substr($url, 0, -1);
2220      }
2221   
2222      return $url;
2223  }
2224   
2225  /**
2226  * Redirects the user to another page then exits the script nicely
2227  * This function is intended for urls within the board. It's not meant to redirect to cross-domains.
2228  *
2229  * @param string $url The url to redirect to
2230  * @param bool $return If true, do not redirect but return the sanitized URL. Default is no return.
2231  * @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.
2232  */
2233  function redirect($url, $return = false, $disable_cd_check = false)
2234  {
2235      global $db, $cache, $config, $user, $phpbb_root_path, $phpbb_filesystem, $phpbb_path_helper, $phpEx, $phpbb_dispatcher;
2236   
2237      $failover_flag = false;
2238   
2239      if (empty($user->lang))
2240      {
2241          $user->add_lang('common');
2242      }
2243   
2244      // Make sure no &amp;'s are in, this will break the redirect
2245      $url = str_replace('&amp;', '&', $url);
2246   
2247      // Determine which type of redirect we need to handle...
2248      $url_parts = @parse_url($url);
2249   
2250      if ($url_parts === false)
2251      {
2252          // Malformed url, redirect to current page...
2253          $url = generate_board_url() . '/' . $user->page['page'];
2254      }
2255      else if (!empty($url_parts['scheme']) && !empty($url_parts['host']))
2256      {
2257          // Attention: only able to redirect within the same domain if $disable_cd_check is false (yourdomain.com -> www.yourdomain.com will not work)
2258          if (!$disable_cd_check && $url_parts['host'] !== $user->host)
2259          {
2260              $url = generate_board_url();
2261          }
2262      }
2263      else if ($url[0] == '/')
2264      {
2265          // Absolute uri, prepend direct url...
2266          $url = generate_board_url(true) . $url;
2267      }
2268      else
2269      {
2270          // Relative uri
2271          $pathinfo = pathinfo($url);
2272   
2273          // Is the uri pointing to the current directory?
2274          if ($pathinfo['dirname'] == '.')
2275          {
2276              $url = str_replace('./', '', $url);
2277   
2278              // Strip / from the beginning
2279              if ($url && substr($url, 0, 1) == '/')
2280              {
2281                  $url = substr($url, 1);
2282              }
2283          }
2284   
2285          $url = $phpbb_path_helper->remove_web_root_path($url);
2286   
2287          if ($user->page['page_dir'])
2288          {
2289              $url = $user->page['page_dir'] . '/' . $url;
2290          }
2291   
2292          $url = generate_board_url() . '/' . $url;
2293      }
2294   
2295      // Clean URL and check if we go outside the forum directory
2296      $url = $phpbb_path_helper->clean_url($url);
2297   
2298      if (!$disable_cd_check && strpos($url, generate_board_url(true)) === false)
2299      {
2300          trigger_error('INSECURE_REDIRECT', E_USER_ERROR);
2301      }
2302   
2303      // Make sure no linebreaks are there... to prevent http response splitting for PHP < 4.4.2
2304      if (strpos(urldecode($url), "\n") !== false || strpos(urldecode($url), "\r") !== false || strpos($url, ';') !== false)
2305      {
2306          trigger_error('INSECURE_REDIRECT', E_USER_ERROR);
2307      }
2308   
2309      // Now, also check the protocol and for a valid url the last time...
2310      $allowed_protocols = array('http', 'https', 'ftp', 'ftps');
2311      $url_parts = parse_url($url);
2312   
2313      if ($url_parts === false || empty($url_parts['scheme']) || !in_array($url_parts['scheme'], $allowed_protocols))
2314      {
2315          trigger_error('INSECURE_REDIRECT', E_USER_ERROR);
2316      }
2317   
2318      /**
2319      * Execute code and/or overwrite redirect()
2320      *
2321      * @event core.functions.redirect
2322      * @var    string    url                    The url
2323      * @var    bool    return                If true, do not redirect but return the sanitized URL.
2324      * @var    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.
2325      * @since 3.1.0-RC3
2326      */
2327      $vars = array('url', 'return', 'disable_cd_check');
2328      extract($phpbb_dispatcher->trigger_event('core.functions.redirect', compact($vars)));
2329   
2330      if ($return)
2331      {
2332          return $url;
2333      }
2334      else
2335      {
2336          garbage_collection();
2337      }
2338   
2339      // Redirect via an HTML form for PITA webservers
2340      if (@preg_match('#Microsoft|WebSTAR|Xitami#', getenv('SERVER_SOFTWARE')))
2341      {
2342          header('Refresh: 0; URL=' . $url);
2343   
2344          echo '<!DOCTYPE html>';
2345          echo '<html dir="' . $user->lang['DIRECTION'] . '" lang="' . $user->lang['USER_LANG'] . '">';
2346          echo '<head>';
2347          echo '<meta charset="utf-8">';
2348          echo '<meta http-equiv="refresh" content="0; url=' . str_replace('&', '&amp;', $url) . '" />';
2349          echo '<title>' . $user->lang['REDIRECT'] . '</title>';
2350          echo '</head>';
2351          echo '<body>';
2352          echo '<div style="text-align: center;">' . sprintf($user->lang['URL_REDIRECT'], '<a href="' . str_replace('&', '&amp;', $url) . '">', '</a>') . '</div>';
2353          echo '</body>';
2354          echo '</html>';
2355   
2356          exit;
2357      }
2358   
2359      // Behave as per HTTP/1.1 spec for others
2360      header('Location: ' . $url);
2361      exit;
2362  }
2363   
2364  /**
2365  * Re-Apply session id after page reloads
2366  */
2367  function reapply_sid($url)
2368  {
2369      global $phpEx, $phpbb_root_path;
2370   
2371      if ($url === "index.$phpEx")
2372      {
2373          return append_sid("index.$phpEx");
2374      }
2375      else if ($url === "{$phpbb_root_path}index.$phpEx")
2376      {
2377          return append_sid("{$phpbb_root_path}index.$phpEx");
2378      }
2379   
2380      // Remove previously added sid
2381      if (strpos($url, 'sid=') !== false)
2382      {
2383          // All kind of links
2384          $url = preg_replace('/(\?)?(&amp;|&)?sid=[a-z0-9]+/', '', $url);
2385          // if the sid was the first param, make the old second as first ones
2386          $url = preg_replace("/$phpEx(&amp;|&)+?/", "$phpEx?", $url);
2387      }
2388   
2389      return append_sid($url);
2390  }
2391   
2392  /**
2393  * Returns url from the session/current page with an re-appended SID with optionally stripping vars from the url
2394  */
2395  function build_url($strip_vars = false)
2396  {
2397      global $config, $user, $phpbb_path_helper;
2398   
2399      $php_ext = $phpbb_path_helper->get_php_ext();
2400      $page = $user->page['page'];
2401   
2402      // We need to be cautious here.
2403      // On some situations, the redirect path is an absolute URL, sometimes a relative path
2404      // For a relative path, let's prefix it with $phpbb_root_path to point to the correct location,
2405      // else we use the URL directly.
2406      $url_parts = parse_url($page);
2407   
2408      // URL
2409      if ($url_parts === false || empty($url_parts['scheme']) || empty($url_parts['host']))
2410      {
2411          // Remove 'app.php/' from the page, when rewrite is enabled
2412          if ($config['enable_mod_rewrite'] && strpos($page, 'app.' . $php_ext . '/') === 0)
2413          {
2414              $page = substr($page, strlen('app.' . $php_ext . '/'));
2415          }
2416   
2417          $page = $phpbb_path_helper->get_phpbb_root_path() . $page;
2418      }
2419   
2420      // Append SID
2421      $redirect = append_sid($page, false, false);
2422   
2423      if ($strip_vars !== false)
2424      {
2425          $redirect = $phpbb_path_helper->strip_url_params($redirect, $strip_vars, false);
2426      }
2427      else
2428      {
2429          $redirect = str_replace('&', '&amp;', $redirect);
2430      }
2431   
2432      return $redirect . ((strpos($redirect, '?') === false) ? '?' : '');
2433  }
2434   
2435  /**
2436  * Meta refresh assignment
2437  * Adds META template variable with meta http tag.
2438  *
2439  * @param int $time Time in seconds for meta refresh tag
2440  * @param string $url URL to redirect to. The url will go through redirect() first before the template variable is assigned
2441  * @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.
2442  */
2443  function meta_refresh($time, $url, $disable_cd_check = false)
2444  {
2445      global $template, $refresh_data, $request;
2446   
2447      $url = redirect($url, true, $disable_cd_check);
2448      if ($request->is_ajax())
2449      {
2450          $refresh_data = array(
2451              'time'    => $time,
2452              'url'    => $url,
2453          );
2454      }
2455      else
2456      {
2457          // For XHTML compatibility we change back & to &amp;
2458          $url = str_replace('&', '&amp;', $url);
2459   
2460          $template->assign_vars(array(
2461              'META' => '<meta http-equiv="refresh" content="' . $time . '; url=' . $url . '" />')
2462          );
2463      }
2464   
2465      return $url;
2466  }
2467   
2468  /**
2469  * Outputs correct status line header.
2470  *
2471  * Depending on php sapi one of the two following forms is used:
2472  *
2473  * Status: 404 Not Found
2474  *
2475  * HTTP/1.x 404 Not Found
2476  *
2477  * HTTP version is taken from HTTP_VERSION environment variable,
2478  * and defaults to 1.0.
2479  *
2480  * Sample usage:
2481  *
2482  * send_status_line(404, 'Not Found');
2483  *
2484  * @param int $code HTTP status code
2485  * @param string $message Message for the status code
2486  * @return null
2487  */
2488  function send_status_line($code, $message)
2489  {
2490      if (substr(strtolower(@php_sapi_name()), 0, 3) === 'cgi')
2491      {
2492          // in theory, we shouldn't need that due to php doing it. Reality offers a differing opinion, though
2493          header("Status: $code $message", true, $code);
2494      }
2495      else
2496      {
2497          $version = phpbb_request_http_version();
2498          header("$version $code $message", true, $code);
2499      }
2500  }
2501   
2502  /**
2503  * Returns the HTTP version used in the current request.
2504  *
2505  * Handles the case of being called before $request is present,
2506  * in which case it falls back to the $_SERVER superglobal.
2507  *
2508  * @return string HTTP version
2509  */
2510  function phpbb_request_http_version()
2511  {
2512      global $request;
2513   
2514      if ($request && $request->server('SERVER_PROTOCOL'))
2515      {
2516          return $request->server('SERVER_PROTOCOL');
2517      }
2518      else if (isset($_SERVER['SERVER_PROTOCOL']))
2519      {
2520          return $_SERVER['SERVER_PROTOCOL'];
2521      }
2522   
2523      return 'HTTP/1.0';
2524  }
2525   
2526  //Form validation
2527   
2528   
2529  /**
2530  * Add a secret hash   for use in links/GET requests
2531  * @param string  $link_name The name of the link; has to match the name used in check_link_hash, otherwise no restrictions apply
2532  * @return string the hash
2533   
2534  */
2535  function generate_link_hash($link_name)
2536  {
2537      global $user;
2538   
2539      if (!isset($user->data["hash_$link_name"]))
2540      {
2541          $user->data["hash_$link_name"] = substr(sha1($user->data['user_form_salt'] . $link_name), 0, 8);
2542      }
2543   
2544      return $user->data["hash_$link_name"];
2545  }
2546   
2547   
2548  /**
2549  * checks a link hash - for GET requests
2550  * @param string $token the submitted token
2551  * @param string $link_name The name of the link
2552  * @return boolean true if all is fine
2553  */
2554  function check_link_hash($token, $link_name)
2555  {
2556      return $token === generate_link_hash($link_name);
2557  }
2558   
2559  /**
2560  * Add a secret token to the form (requires the S_FORM_TOKEN template variable)
2561  * @param string  $form_name The name of the form; has to match the name used in check_form_key, otherwise no restrictions apply
2562  */
2563  function add_form_key($form_name)
2564  {
2565      global $config, $template, $user, $phpbb_dispatcher;
2566   
2567      $now = time();
2568      $token_sid = ($user->data['user_id'] == ANONYMOUS && !empty($config['form_token_sid_guests'])) ? $user->session_id : '';
2569      $token = sha1($now . $user->data['user_form_salt'] . $form_name . $token_sid);
2570   
2571      $s_fields = build_hidden_fields(array(
2572          'creation_time' => $now,
2573          'form_token'    => $token,
2574      ));
2575   
2576      /**
2577      * Perform additional actions on creation of the form token
2578      *
2579      * @event core.add_form_key
2580      * @var    string    form_name            The form name
2581      * @var    int        now                    Current time timestamp
2582      * @var    string    s_fields            Generated hidden fields
2583      * @var    string    token                Form token
2584      * @var    string    token_sid            User session ID
2585      *
2586      * @since 3.1.0-RC3
2587      */
2588      $vars = array(
2589          'form_name',
2590          'now',
2591          's_fields',
2592          'token',
2593          'token_sid',
2594      );
2595      extract($phpbb_dispatcher->trigger_event('core.add_form_key', compact($vars)));
2596   
2597      $template->assign_vars(array(
2598          'S_FORM_TOKEN'    => $s_fields,
2599      ));
2600  }
2601   
2602  /**
2603   * Check the form key. Required for all altering actions not secured by confirm_box
2604   *
2605   * @param    string    $form_name    The name of the form; has to match the name used
2606   *                                in add_form_key, otherwise no restrictions apply
2607   * @param    int        $timespan    The maximum acceptable age for a submitted form
2608   *                                in seconds. Defaults to the config setting.
2609   * @return    bool    True, if the form key was valid, false otherwise
2610   */
2611  function check_form_key($form_name, $timespan = false)
2612  {
2613      global $config, $request, $user;
2614   
2615      if ($timespan === false)
2616      {
2617          // we enforce a minimum value of half a minute here.
2618          $timespan = ($config['form_token_lifetime'] == -1) ? -1 : max(30, $config['form_token_lifetime']);
2619      }
2620   
2621      if ($request->is_set_post('creation_time') && $request->is_set_post('form_token'))
2622      {
2623          $creation_time    = abs($request->variable('creation_time', 0));
2624          $token = $request->variable('form_token', '');
2625   
2626          $diff = time() - $creation_time;
2627   
2628          // If creation_time and the time() now is zero we can assume it was not a human doing this (the check for if ($diff)...
2629          if (defined('DEBUG_TEST') || $diff && ($diff <= $timespan || $timespan === -1))
2630          {
2631              $token_sid = ($user->data['user_id'] == ANONYMOUS && !empty($config['form_token_sid_guests'])) ? $user->session_id : '';
2632              $key = sha1($creation_time . $user->data['user_form_salt'] . $form_name . $token_sid);
2633   
2634              if ($key === $token)
2635              {
2636                  return true;
2637              }
2638          }
2639      }
2640   
2641      return false;
2642  }
2643   
2644  // Message/Login boxes
2645   
2646  /**
2647  * Build Confirm box
2648  * @param boolean $check True for checking if confirmed (without any additional parameters) and false for displaying the confirm box
2649  * @param string $title Title/Message used for confirm box.
2650  *        message text is _CONFIRM appended to title.
2651  *        If title cannot be found in user->lang a default one is displayed
2652  *        If title_CONFIRM cannot be found in user->lang the text given is used.
2653  * @param string $hidden Hidden variables
2654  * @param string $html_body Template used for confirm box
2655  * @param string $u_action Custom form action
2656  */
2657  function confirm_box($check, $title = '', $hidden = '', $html_body = 'confirm_body.html', $u_action = '')
2658  {
2659      global $user, $template, $db, $request;
2660      global $phpEx, $phpbb_root_path, $request;
2661   
2662      if (isset($_POST['cancel']))
2663      {
2664          return false;
2665      }
2666   
2667      $confirm = ($user->lang['YES'] === $request->variable('confirm', '', true, \phpbb\request\request_interface::POST));
2668   
2669      if ($check && $confirm)
2670      {
2671          $user_id = request_var('confirm_uid', 0);
2672          $session_id = request_var('sess', '');
2673          $confirm_key = request_var('confirm_key', '');
2674   
2675          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'])
2676          {
2677              return false;
2678          }
2679   
2680          // Reset user_last_confirm_key
2681          $sql = 'UPDATE ' . USERS_TABLE . " SET user_last_confirm_key = ''
2682              WHERE user_id = " . $user->data['user_id'];
2683          $db->sql_query($sql);
2684   
2685          return true;
2686      }
2687      else if ($check)
2688      {
2689          return false;
2690      }
2691   
2692      $s_hidden_fields = build_hidden_fields(array(
2693          'confirm_uid'    => $user->data['user_id'],
2694          'sess'            => $user->session_id,
2695          'sid'            => $user->session_id,
2696      ));
2697   
2698      // generate activation key
2699      $confirm_key = gen_rand_string(10);
2700   
2701      if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
2702      {
2703          adm_page_header((!isset($user->lang[$title])) ? $user->lang['CONFIRM'] : $user->lang[$title]);
2704      }
2705      else
2706      {
2707          page_header((!isset($user->lang[$title])) ? $user->lang['CONFIRM'] : $user->lang[$title]);
2708      }
2709   
2710      $template->set_filenames(array(
2711          'body' => $html_body)
2712      );
2713   
2714      // If activation key already exist, we better do not re-use the key (something very strange is going on...)
2715      if (request_var('confirm_key', ''))
2716      {
2717          // This should not occur, therefore we cancel the operation to safe the user
2718          return false;
2719      }
2720   
2721      // re-add sid / transform & to &amp; for user->page (user->page is always using &)
2722      $use_page = ($u_action) ? $phpbb_root_path . $u_action : $phpbb_root_path . str_replace('&', '&amp;', $user->page['page']);
2723      $u_action = reapply_sid($use_page);
2724      $u_action .= ((strpos($u_action, '?') === false) ? '?' : '&amp;') . 'confirm_key=' . $confirm_key;
2725   
2726      $template->assign_vars(array(
2727          'MESSAGE_TITLE'        => (!isset($user->lang[$title])) ? $user->lang['CONFIRM'] : $user->lang[$title],
2728          'MESSAGE_TEXT'        => (!isset($user->lang[$title . '_CONFIRM'])) ? $title : $user->lang[$title . '_CONFIRM'],
2729   
2730          'YES_VALUE'            => $user->lang['YES'],
2731          'S_CONFIRM_ACTION'    => $u_action,
2732          'S_HIDDEN_FIELDS'    => $hidden . $s_hidden_fields,
2733          'S_AJAX_REQUEST'    => $request->is_ajax(),
2734      ));
2735   
2736      $sql = 'UPDATE ' . USERS_TABLE . " SET user_last_confirm_key = '" . $db->sql_escape($confirm_key) . "'
2737          WHERE user_id = " . $user->data['user_id'];
2738      $db->sql_query($sql);
2739   
2740      if ($request->is_ajax())
2741      {
2742          $u_action .= '&confirm_uid=' . $user->data['user_id'] . '&sess=' . $user->session_id . '&sid=' . $user->session_id;
2743          $json_response = new \phpbb\json_response;
2744          $json_response->send(array(
2745              'MESSAGE_BODY'        => $template->assign_display('body'),
2746              'MESSAGE_TITLE'        => (!isset($user->lang[$title])) ? $user->lang['CONFIRM'] : $user->lang[$title],
2747              'MESSAGE_TEXT'        => (!isset($user->lang[$title . '_CONFIRM'])) ? $title : $user->lang[$title . '_CONFIRM'],
2748   
2749              'YES_VALUE'            => $user->lang['YES'],
2750              'S_CONFIRM_ACTION'    => str_replace('&amp;', '&', $u_action), //inefficient, rewrite whole function
2751              'S_HIDDEN_FIELDS'    => $hidden . $s_hidden_fields
2752          ));
2753      }
2754   
2755      if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
2756      {
2757          adm_page_footer();
2758      }
2759      else
2760      {
2761          page_footer();
2762      }
2763  }
2764   
2765  /**
2766  * Generate login box or verify password
2767  */
2768  function login_box($redirect = '', $l_explain = '', $l_success = '', $admin = false, $s_display = true)
2769  {
2770      global $db, $user, $template, $auth, $phpEx, $phpbb_root_path, $config;
2771      global $request, $phpbb_container, $phpbb_dispatcher;
2772   
2773      $err = '';
2774   
2775      // Make sure user->setup() has been called
2776      if (empty($user->lang))
2777      {
2778          $user->setup();
2779      }
2780   
2781      // Print out error if user tries to authenticate as an administrator without having the privileges...
2782      if ($admin && !$auth->acl_get('a_'))
2783      {
2784          // Not authd
2785          // anonymous/inactive users are never able to go to the ACP even if they have the relevant permissions
2786          if ($user->data['is_registered'])
2787          {
2788              add_log('admin', 'LOG_ADMIN_AUTH_FAIL');
2789          }
2790          trigger_error('NO_AUTH_ADMIN');
2791      }
2792   
2793      if ($request->is_set_post('login') || ($request->is_set('login') && $request->variable('login', '') == 'external'))
2794      {
2795          // Get credential
2796          if ($admin)
2797          {
2798              $credential = request_var('credential', '');
2799   
2800              if (strspn($credential, 'abcdef0123456789') !== strlen($credential) || strlen($credential) != 32)
2801              {
2802                  if ($user->data['is_registered'])
2803                  {
2804                      add_log('admin', 'LOG_ADMIN_AUTH_FAIL');
2805                  }
2806                  trigger_error('NO_AUTH_ADMIN');
2807              }
2808   
2809              $password    = $request->untrimmed_variable('password_' . $credential, '', true);
2810          }
2811          else
2812          {
2813              $password    = $request->untrimmed_variable('password', '', true);
2814          }
2815   
2816          $username    = request_var('username', '', true);
2817          $autologin    = $request->is_set_post('autologin');
2818          $viewonline = (int) !$request->is_set_post('viewonline');
2819          $admin         = ($admin) ? 1 : 0;
2820          $viewonline = ($admin) ? $user->data['session_viewonline'] : $viewonline;
2821   
2822          // Check if the supplied username is equal to the one stored within the database if re-authenticating
2823          if ($admin && utf8_clean_string($username) != utf8_clean_string($user->data['username']))
2824          {
2825              // We log the attempt to use a different username...
2826              add_log('admin', 'LOG_ADMIN_AUTH_FAIL');
2827              trigger_error('NO_AUTH_ADMIN_USER_DIFFER');
2828          }
2829   
2830          // If authentication is successful we redirect user to previous page
2831          $result = $auth->login($username, $password, $autologin, $viewonline, $admin);
2832   
2833          // If admin authentication and login, we will log if it was a success or not...
2834          // We also break the operation on the first non-success login - it could be argued that the user already knows
2835          if ($admin)
2836          {
2837              if ($result['status'] == LOGIN_SUCCESS)
2838              {
2839                  add_log('admin', 'LOG_ADMIN_AUTH_SUCCESS');
2840              }
2841              else
2842              {
2843                  // Only log the failed attempt if a real user tried to.
2844                  // anonymous/inactive users are never able to go to the ACP even if they have the relevant permissions
2845                  if ($user->data['is_registered'])
2846                  {
2847                      add_log('admin', 'LOG_ADMIN_AUTH_FAIL');
2848                  }
2849              }
2850          }
2851   
2852          // The result parameter is always an array, holding the relevant information...
2853          if ($result['status'] == LOGIN_SUCCESS)
2854          {
2855              $redirect = request_var('redirect', "{$phpbb_root_path}index.$phpEx");
2856   
2857              /**
2858              * This event allows an extension to modify the redirection when a user successfully logs in
2859              *
2860              * @event core.login_box_redirect
2861              * @var  string    redirect    Redirect string
2862              * @var    boolean    admin        Is admin?
2863              * @var    bool    return        If true, do not redirect but return the sanitized URL.
2864              * @since 3.1.0-RC5
2865              */
2866              $vars = array('redirect', 'admin', 'return');
2867              extract($phpbb_dispatcher->trigger_event('core.login_box_redirect', compact($vars)));
2868   
2869              // append/replace SID (may change during the session for AOL users)
2870              $redirect = reapply_sid($redirect);
2871   
2872              // Special case... the user is effectively banned, but we allow founders to login
2873              if (defined('IN_CHECK_BAN') && $result['user_row']['user_type'] != USER_FOUNDER)
2874              {
2875                  return;
2876              }
2877   
2878              redirect($redirect);
2879          }
2880   
2881          // Something failed, determine what...
2882          if ($result['status'] == LOGIN_BREAK)
2883          {
2884              trigger_error($result['error_msg']);
2885          }
2886   
2887          // Special cases... determine
2888          switch ($result['status'])
2889          {
2890              case LOGIN_ERROR_ATTEMPTS:
2891   
2892                  $captcha = $phpbb_container->get('captcha.factory')->get_instance($config['captcha_plugin']);
2893                  $captcha->init(CONFIRM_LOGIN);
2894                  // $captcha->reset();
2895   
2896                  $template->assign_vars(array(
2897                      'CAPTCHA_TEMPLATE'            => $captcha->get_template(),
2898                  ));
2899   
2900                  $err = $user->lang[$result['error_msg']];
2901              break;
2902   
2903              case LOGIN_ERROR_PASSWORD_CONVERT:
2904                  $err = sprintf(
2905                      $user->lang[$result['error_msg']],
2906                      ($config['email_enable']) ? '<a href="' . append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=sendpassword') . '">' : '',
2907                      ($config['email_enable']) ? '</a>' : '',
2908                      '<a href="' . phpbb_get_board_contact_link($config, $phpbb_root_path, $phpEx) . '">',
2909                      '</a>'
2910                  );
2911              break;
2912   
2913              // Username, password, etc...
2914              default:
2915                  $err = $user->lang[$result['error_msg']];
2916   
2917                  // Assign admin contact to some error messages
2918                  if ($result['error_msg'] == 'LOGIN_ERROR_USERNAME' || $result['error_msg'] == 'LOGIN_ERROR_PASSWORD')
2919                  {
2920                      $err = sprintf($user->lang[$result['error_msg']], '<a href="' . append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=contactadmin') . '">', '</a>');
2921                  }
2922   
2923              break;
2924          }
2925      }
2926   
2927      // Assign credential for username/password pair
2928      $credential = ($admin) ? md5(unique_id()) : false;
2929   
2930      $s_hidden_fields = array(
2931          'sid'        => $user->session_id,
2932      );
2933   
2934      if ($redirect)
2935      {
2936          $s_hidden_fields['redirect'] = $redirect;
2937      }
2938   
2939      if ($admin)
2940      {
2941          $s_hidden_fields['credential'] = $credential;
2942      }
2943   
2944      $provider_collection = $phpbb_container->get('auth.provider_collection');
2945      $auth_provider = $provider_collection->get_provider();
2946   
2947      $auth_provider_data = $auth_provider->get_login_data();
2948      if ($auth_provider_data)
2949      {
2950          if (isset($auth_provider_data['VARS']))
2951          {
2952              $template->assign_vars($auth_provider_data['VARS']);
2953          }
2954   
2955          if (isset($auth_provider_data['BLOCK_VAR_NAME']))
2956          {
2957              foreach ($auth_provider_data['BLOCK_VARS'] as $block_vars)
2958              {
2959                  $template->assign_block_vars($auth_provider_data['BLOCK_VAR_NAME'], $block_vars);
2960              }
2961          }
2962   
2963          $template->assign_vars(array(
2964              'PROVIDER_TEMPLATE_FILE' => $auth_provider_data['TEMPLATE_FILE'],
2965          ));
2966      }
2967   
2968      $s_hidden_fields = build_hidden_fields($s_hidden_fields);
2969   
2970      $template->assign_vars(array(
2971          'LOGIN_ERROR'        => $err,
2972          'LOGIN_EXPLAIN'        => $l_explain,
2973   
2974          'U_SEND_PASSWORD'         => ($config['email_enable']) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=sendpassword') : '',
2975          'U_RESEND_ACTIVATION'    => ($config['require_activation'] == USER_ACTIVATION_SELF && $config['email_enable']) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=resend_act') : '',
2976          'U_TERMS_USE'            => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=terms'),
2977          'U_PRIVACY'                => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy'),
2978   
2979          'S_DISPLAY_FULL_LOGIN'    => ($s_display) ? true : false,
2980          'S_HIDDEN_FIELDS'         => $s_hidden_fields,
2981   
2982          'S_ADMIN_AUTH'            => $admin,
2983          'USERNAME'                => ($admin) ? $user->data['username'] : '',
2984   
2985          'USERNAME_CREDENTIAL'    => 'username',
2986          'PASSWORD_CREDENTIAL'    => ($admin) ? 'password_' . $credential : 'password',
2987      ));
2988   
2989      page_header($user->lang['LOGIN']);
2990   
2991      $template->set_filenames(array(
2992          'body' => 'login_body.html')
2993      );
2994      make_jumpbox(append_sid("{$phpbb_root_path}viewforum.$phpEx"));
2995   
2996      page_footer();
2997  }
2998   
2999  /**
3000  * Generate forum login box
3001  */
3002  function login_forum_box($forum_data)
3003  {
3004      global $db, $phpbb_container, $request, $template, $user, $phpbb_dispatcher;
3005   
3006      $password = $request->variable('password', '', true);
3007   
3008      $sql = 'SELECT forum_id
3009          FROM ' . FORUMS_ACCESS_TABLE . '
3010          WHERE forum_id = ' . $forum_data['forum_id'] . '
3011              AND user_id = ' . $user->data['user_id'] . "
3012              AND session_id = '" . $db->sql_escape($user->session_id) . "'";
3013      $result = $db->sql_query($sql);
3014      $row = $db->sql_fetchrow($result);
3015      $db->sql_freeresult($result);
3016   
3017      if ($row)
3018      {
3019          return true;
3020      }
3021   
3022      if ($password)
3023      {
3024          // Remove expired authorised sessions
3025          $sql = 'SELECT f.session_id
3026              FROM ' . FORUMS_ACCESS_TABLE . ' f
3027              LEFT JOIN ' . SESSIONS_TABLE . ' s ON (f.session_id = s.session_id)
3028              WHERE s.session_id IS NULL';
3029          $result = $db->sql_query($sql);
3030   
3031          if ($row = $db->sql_fetchrow($result))
3032          {
3033              $sql_in = array();
3034              do
3035              {
3036                  $sql_in[] = (string) $row['session_id'];
3037              }
3038              while ($row = $db->sql_fetchrow($result));
3039   
3040              // Remove expired sessions
3041              $sql = 'DELETE FROM ' . FORUMS_ACCESS_TABLE . '
3042                  WHERE ' . $db->sql_in_set('session_id', $sql_in);
3043              $db->sql_query($sql);
3044          }
3045          $db->sql_freeresult($result);
3046   
3047          $passwords_manager = $phpbb_container->get('passwords.manager');
3048   
3049          if ($passwords_manager->check($password, $forum_data['forum_password']))
3050          {
3051              $sql_ary = array(
3052                  'forum_id'        => (int) $forum_data['forum_id'],
3053                  'user_id'        => (int) $user->data['user_id'],
3054                  'session_id'    => (string) $user->session_id,
3055              );
3056   
3057              $db->sql_query('INSERT INTO ' . FORUMS_ACCESS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
3058   
3059              return true;
3060          }
3061   
3062          $template->assign_var('LOGIN_ERROR', $user->lang['WRONG_PASSWORD']);
3063      }
3064   
3065      /**
3066      * Performing additional actions, load additional data on forum login
3067      *
3068      * @event core.login_forum_box
3069      * @var    array    forum_data        Array with forum data
3070      * @var    string    password        Password entered
3071      * @since 3.1.0-RC3
3072      */
3073      $vars = array('forum_data', 'password');
3074      extract($phpbb_dispatcher->trigger_event('core.login_forum_box', compact($vars)));
3075   
3076      page_header($user->lang['LOGIN']);
3077   
3078      $template->assign_vars(array(
3079          'FORUM_NAME'            => isset($forum_data['forum_name']) ? $forum_data['forum_name'] : '',
3080          'S_LOGIN_ACTION'        => build_url(array('f')),
3081          'S_HIDDEN_FIELDS'        => build_hidden_fields(array('f' => $forum_data['forum_id'])))
3082      );
3083   
3084      $template->set_filenames(array(
3085          'body' => 'login_forum.html')
3086      );
3087   
3088      page_footer();
3089  }
3090   
3091  // Little helpers
3092   
3093  /**
3094  * Little helper for the build_hidden_fields function
3095  */
3096  function _build_hidden_fields($key, $value, $specialchar, $stripslashes)
3097  {
3098      $hidden_fields = '';
3099   
3100      if (!is_array($value))
3101      {
3102          $value = ($stripslashes) ? stripslashes($value) : $value;
3103          $value = ($specialchar) ? htmlspecialchars($value, ENT_COMPAT, 'UTF-8') : $value;
3104   
3105          $hidden_fields .= '<input type="hidden" name="' . $key . '" value="' . $value . '" />' . "\n";
3106      }
3107      else
3108      {
3109          foreach ($value as $_key => $_value)
3110          {
3111              $_key = ($stripslashes) ? stripslashes($_key) : $_key;
3112              $_key = ($specialchar) ? htmlspecialchars($_key, ENT_COMPAT, 'UTF-8') : $_key;
3113   
3114              $hidden_fields .= _build_hidden_fields($key . '[' . $_key . ']', $_value, $specialchar, $stripslashes);
3115          }
3116      }
3117   
3118      return $hidden_fields;
3119  }
3120   
3121  /**
3122  * Build simple hidden fields from array
3123  *
3124  * @param array $field_ary an array of values to build the hidden field from
3125  * @param bool $specialchar if true, keys and values get specialchared
3126  * @param bool $stripslashes if true, keys and values get stripslashed
3127  *
3128  * @return string the hidden fields
3129  */
3130  function build_hidden_fields($field_ary, $specialchar = false, $stripslashes = false)
3131  {
3132      $s_hidden_fields = '';
3133   
3134      foreach ($field_ary as $name => $vars)
3135      {
3136          $name = ($stripslashes) ? stripslashes($name) : $name;
3137          $name = ($specialchar) ? htmlspecialchars($name, ENT_COMPAT, 'UTF-8') : $name;
3138   
3139          $s_hidden_fields .= _build_hidden_fields($name, $vars, $specialchar, $stripslashes);
3140      }
3141   
3142      return $s_hidden_fields;
3143  }
3144   
3145  /**
3146  * Parse cfg file
3147  */
3148  function parse_cfg_file($filename, $lines = false)
3149  {
3150      $parsed_items = array();
3151   
3152      if ($lines === false)
3153      {
3154          $lines = file($filename);
3155      }
3156   
3157      foreach ($lines as $line)
3158      {
3159          $line = trim($line);
3160   
3161          if (!$line || $line[0] == '#' || ($delim_pos = strpos($line, '=')) === false)
3162          {
3163              continue;
3164          }
3165   
3166          // Determine first occurrence, since in values the equal sign is allowed
3167          $key = htmlspecialchars(strtolower(trim(substr($line, 0, $delim_pos))));
3168          $value = trim(substr($line, $delim_pos + 1));
3169   
3170          if (in_array($value, array('off', 'false', '0')))
3171          {
3172              $value = false;
3173          }
3174          else if (in_array($value, array('on', 'true', '1')))
3175          {
3176              $value = true;
3177          }
3178          else if (!trim($value))
3179          {
3180              $value = '';
3181          }
3182          else if (($value[0] == "'" && $value[sizeof($value) - 1] == "'") || ($value[0] == '"' && $value[sizeof($value) - 1] == '"'))
3183          {
3184              $value = htmlspecialchars(substr($value, 1, sizeof($value)-2));
3185          }
3186          else
3187          {
3188              $value = htmlspecialchars($value);
3189          }
3190   
3191          $parsed_items[$key] = $value;
3192      }
3193   
3194      if (isset($parsed_items['parent']) && isset($parsed_items['name']) && $parsed_items['parent'] == $parsed_items['name'])
3195      {
3196          unset($parsed_items['parent']);
3197      }
3198   
3199      return $parsed_items;
3200  }
3201   
3202  /**
3203  * Add log entry
3204  *
3205  * @param    string    $mode                The mode defines which log_type is used and from which log the entry is retrieved
3206  * @param    int        $forum_id            Mode 'mod' ONLY: forum id of the related item, NOT INCLUDED otherwise
3207  * @param    int        $topic_id            Mode 'mod' ONLY: topic id of the related item, NOT INCLUDED otherwise
3208  * @param    int        $reportee_id        Mode 'user' ONLY: user id of the reportee, NOT INCLUDED otherwise
3209  * @param    string    $log_operation        Name of the operation
3210  * @param    array    $additional_data    More arguments can be added, depending on the log_type
3211  *
3212  * @return    int|bool        Returns the log_id, if the entry was added to the database, false otherwise.
3213  *
3214  * @deprecated    Use $phpbb_log->add() instead
3215  */
3216  function add_log()
3217  {
3218      global $phpbb_log, $user;
3219   
3220      $args = func_get_args();
3221      $mode = array_shift($args);
3222   
3223      // This looks kind of dirty, but add_log has some additional data before the log_operation
3224      $additional_data = array();
3225      switch ($mode)
3226      {
3227          case 'admin':
3228          case 'critical':
3229          break;
3230          case 'mod':
3231              $additional_data['forum_id'] = array_shift($args);
3232              $additional_data['topic_id'] = array_shift($args);
3233          break;
3234          case 'user':
3235              $additional_data['reportee_id'] = array_shift($args);
3236          break;
3237      }
3238   
3239      $log_operation = array_shift($args);
3240      $additional_data = array_merge($additional_data, $args);
3241   
3242      $user_id = (empty($user->data)) ? ANONYMOUS : $user->data['user_id'];
3243      $user_ip = (empty($user->ip)) ? '' : $user->ip;
3244   
3245      return $phpbb_log->add($mode, $user_id, $user_ip, $log_operation, time(), $additional_data);
3246  }
3247   
3248  /**
3249  * Return a nicely formatted backtrace.
3250  *
3251  * Turns the array returned by debug_backtrace() into HTML markup.
3252  * Also filters out absolute paths to phpBB root.
3253  *
3254  * @return string    HTML markup
3255  */
3256  function get_backtrace()
3257  {
3258      $output = '<div style="font-family: monospace;">';
3259      $backtrace = debug_backtrace();
3260   
3261      // We skip the first one, because it only shows this file/function
3262      unset($backtrace[0]);
3263   
3264      foreach ($backtrace as $trace)
3265      {
3266          // Strip the current directory from path
3267          $trace['file'] = (empty($trace['file'])) ? '(not given by php)' : htmlspecialchars(phpbb_filter_root_path($trace['file']));
3268          $trace['line'] = (empty($trace['line'])) ? '(not given by php)' : $trace['line'];
3269   
3270          // Only show function arguments for include etc.
3271          // Other parameters may contain sensible information
3272          $argument = '';
3273          if (!empty($trace['args'][0]) && in_array($trace['function'], array('include', 'require', 'include_once', 'require_once')))
3274          {
3275              $argument = htmlspecialchars(phpbb_filter_root_path($trace['args'][0]));
3276          }
3277   
3278          $trace['class'] = (!isset($trace['class'])) ? '' : $trace['class'];
3279          $trace['type'] = (!isset($trace['type'])) ? '' : $trace['type'];
3280   
3281          $output .= '<br />';
3282          $output .= '<b>FILE:</b> ' . $trace['file'] . '<br />';
3283          $output .= '<b>LINE:</b> ' . ((!empty($trace['line'])) ? $trace['line'] : '') . '<br />';
3284   
3285          $output .= '<b>CALL:</b> ' . htmlspecialchars($trace['class'] . $trace['type'] . $trace['function']);
3286          $output .= '(' . (($argument !== '') ? "'$argument'" : '') . ')<br />';
3287      }
3288      $output .= '</div>';
3289      return $output;
3290  }
3291   
3292  /**
3293  * This function returns a regular expression pattern for commonly used expressions
3294  * Use with / as delimiter for email mode and # for url modes
3295  * mode can be: email|bbcode_htm|url|url_inline|www_url|www_url_inline|relative_url|relative_url_inline|ipv4|ipv6
3296  */
3297  function get_preg_expression($mode)
3298  {
3299      switch ($mode)
3300      {
3301          case 'email':
3302              // Regex written by James Watts and Francisco Jose Martin Moreno
3303              // http://fightingforalostcause.net/misc/2006/compare-email-regex.php
3304              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})?)';
3305          break;
3306   
3307          case 'bbcode_htm':
3308              return array(
3309                  '#<!\-\- e \-\-><a href="mailto:(.*?)">.*?</a><!\-\- e \-\->#',
3310                  '#<!\-\- l \-\-><a (?:class="[\w-]+" )?href="(.*?)(?:(&amp;|\?)sid=[0-9a-f]{32})?">.*?</a><!\-\- l \-\->#',
3311                  '#<!\-\- ([mw]) \-\-><a (?:class="[\w-]+" )?href="(.*?)">.*?</a><!\-\- \1 \-\->#',
3312                  '#<!\-\- s(.*?) \-\-><img src="\{SMILIES_PATH\}\/.*? \/><!\-\- s\1 \-\->#',
3313                  '#<!\-\- .*? \-\->#s',
3314                  '#<.*?>#s',
3315              );
3316          break;
3317   
3318          // Whoa these look impressive!
3319          // The code to generate the following two regular expressions which match valid IPv4/IPv6 addresses
3320          // can be found in the develop directory
3321          case 'ipv4':
3322              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])$#';
3323          break;
3324   
3325          case 'ipv6':
3326              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';
3327          break;
3328   
3329          case 'url':
3330          case 'url_inline':
3331              $inline = ($mode == 'url') ? ')' : '';
3332              $scheme = ($mode == 'url') ? '[a-z\d+\-.]' : '[a-z\d+]'; // avoid automatic parsing of "word" in "last word.http://..."
3333              // generated with regex generation file in the develop folder
3334              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})*)?";
3335          break;
3336   
3337          case 'www_url':
3338          case 'www_url_inline':
3339              $inline = ($mode == 'www_url') ? ')' : '';
3340              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})*)?";
3341          break;
3342   
3343          case 'relative_url':
3344          case 'relative_url_inline':
3345              $inline = ($mode == 'relative_url') ? ')' : '';
3346              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})*)?";
3347          break;
3348   
3349          case 'table_prefix':
3350              return '#^[a-zA-Z][a-zA-Z0-9_]*$#';
3351          break;
3352   
3353          // Matches the predecing dot
3354          case 'path_remove_dot_trailing_slash':
3355              return '#^(?:(\.)?)+(?:(.+)?)+(?:([\\/\\\])$)#';
3356          break;
3357      }
3358   
3359      return '';
3360  }
3361   
3362  /**
3363  * Generate regexp for naughty words censoring
3364  * Depends on whether installed PHP version supports unicode properties
3365  *
3366  * @param string    $word            word template to be replaced
3367  * @param bool    $use_unicode    whether or not to take advantage of PCRE supporting unicode
3368  *
3369  * @return string $preg_expr        regex to use with word censor
3370  */
3371  function get_censor_preg_expression($word, $use_unicode = true)
3372  {
3373      // Unescape the asterisk to simplify further conversions
3374      $word = str_replace('\*', '*', preg_quote($word, '#'));
3375   
3376      if ($use_unicode && phpbb_pcre_utf8_support())
3377      {
3378          // Replace asterisk(s) inside the pattern, at the start and at the end of it with regexes
3379          $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);
3380   
3381          // Generate the final substitution
3382          $preg_expr = '#(?<![\p{Nd}\p{L}_-])(' . $word . ')(?![\p{Nd}\p{L}_-])#iu';
3383      }
3384      else
3385      {
3386          // Replace the asterisk inside the pattern, at the start and at the end of it with regexes
3387          $word = preg_replace(array('#(?<=\S)\*+(?=\S)#iu', '#^\*+#', '#\*+$#'), array('(\x20*?\S*?)', '\S*?', '\S*?'), $word);
3388   
3389          // Generate the final substitution
3390          $preg_expr = '#(?<!\S)(' . $word . ')(?!\S)#iu';
3391      }
3392   
3393      return $preg_expr;
3394  }
3395   
3396  /**
3397  * Returns the first block of the specified IPv6 address and as many additional
3398  * ones as specified in the length paramater.
3399  * If length is zero, then an empty string is returned.
3400  * If length is greater than 3 the complete IP will be returned
3401  */
3402  function short_ipv6($ip, $length)
3403  {
3404      if ($length < 1)
3405      {
3406          return '';
3407      }
3408   
3409      // extend IPv6 addresses
3410      $blocks = substr_count($ip, ':') + 1;
3411      if ($blocks < 9)
3412      {
3413          $ip = str_replace('::', ':' . str_repeat('0000:', 9 - $blocks), $ip);
3414      }
3415      if ($ip[0] == ':')
3416      {
3417          $ip = '0000' . $ip;
3418      }
3419      if ($length < 4)
3420      {
3421          $ip = implode(':', array_slice(explode(':', $ip), 0, 1 + $length));
3422      }
3423   
3424      return $ip;
3425  }
3426   
3427  /**
3428  * Normalises an internet protocol address,
3429  * also checks whether the specified address is valid.
3430  *
3431  * IPv4 addresses are returned 'as is'.
3432  *
3433  * IPv6 addresses are normalised according to
3434  *    A Recommendation for IPv6 Address Text Representation
3435  *    http://tools.ietf.org/html/draft-ietf-6man-text-addr-representation-07
3436  *
3437  * @param string $address    IP address
3438  *
3439  * @return mixed        false if specified address is not valid,
3440  *                    string otherwise
3441  */
3442  function phpbb_ip_normalise($address)
3443  {
3444      $address = trim($address);
3445   
3446      if (empty($address) || !is_string($address))
3447      {
3448          return false;
3449      }
3450   
3451      if (preg_match(get_preg_expression('ipv4'), $address))
3452      {
3453          return $address;
3454      }
3455   
3456      return phpbb_inet_ntop(phpbb_inet_pton($address));
3457  }
3458   
3459  /**
3460  * Wrapper for inet_ntop()
3461  *
3462  * Converts a packed internet address to a human readable representation
3463  * inet_ntop() is supported by PHP since 5.1.0, since 5.3.0 also on Windows.
3464  *
3465  * @param string $in_addr    A 32bit IPv4, or 128bit IPv6 address.
3466  *
3467  * @return mixed        false on failure,
3468  *                    string otherwise
3469  */
3470  function phpbb_inet_ntop($in_addr)
3471  {
3472      $in_addr = bin2hex($in_addr);
3473   
3474      switch (strlen($in_addr))
3475      {
3476          case 8:
3477              return implode('.', array_map('hexdec', str_split($in_addr, 2)));
3478   
3479          case 32:
3480              if (substr($in_addr, 0, 24) === '00000000000000000000ffff')
3481              {
3482                  return phpbb_inet_ntop(pack('H*', substr($in_addr, 24)));
3483              }
3484   
3485              $parts = str_split($in_addr, 4);
3486              $parts = preg_replace('/^0+(?!$)/', '', $parts);
3487              $ret = implode(':', $parts);
3488   
3489              $matches = array();
3490              preg_match_all('/(?<=:|^)(?::?0){2,}/', $ret, $matches, PREG_OFFSET_CAPTURE);
3491              $matches = $matches[0];
3492   
3493              if (empty($matches))
3494              {
3495                  return $ret;
3496              }
3497   
3498              $longest_match = '';
3499              $longest_match_offset = 0;
3500              foreach ($matches as $match)
3501              {
3502                  if (strlen($match[0]) > strlen($longest_match))
3503                  {
3504                      $longest_match = $match[0];
3505                      $longest_match_offset = $match[1];
3506                  }
3507              }
3508   
3509              $ret = substr_replace($ret, '', $longest_match_offset, strlen($longest_match));
3510   
3511              if ($longest_match_offset == strlen($ret))
3512              {
3513                  $ret .= ':';
3514              }
3515   
3516              if ($longest_match_offset == 0)
3517              {
3518                  $ret = ':' . $ret;
3519              }
3520   
3521              return $ret;
3522   
3523          default:
3524              return false;
3525      }
3526  }
3527   
3528  /**
3529  * Wrapper for inet_pton()
3530  *
3531  * Converts a human readable IP address to its packed in_addr representation
3532  * inet_pton() is supported by PHP since 5.1.0, since 5.3.0 also on Windows.
3533  *
3534  * @param string $address    A human readable IPv4 or IPv6 address.
3535  *
3536  * @return mixed        false if address is invalid,
3537  *                    in_addr representation of the given address otherwise (string)
3538  */
3539  function phpbb_inet_pton($address)
3540  {
3541      $ret = '';
3542      if (preg_match(get_preg_expression('ipv4'), $address))
3543      {
3544          foreach (explode('.', $address) as $part)
3545          {
3546              $ret .= ($part <= 0xF ? '0' : '') . dechex($part);
3547          }
3548   
3549          return pack('H*', $ret);
3550      }
3551   
3552      if (preg_match(get_preg_expression('ipv6'), $address))
3553      {
3554          $parts = explode(':', $address);
3555          $missing_parts = 8 - sizeof($parts) + 1;
3556   
3557          if (substr($address, 0, 2) === '::')
3558          {
3559              ++$missing_parts;
3560          }
3561   
3562          if (substr($address, -2) === '::')
3563          {
3564              ++$missing_parts;
3565          }
3566   
3567          $embedded_ipv4 = false;
3568          $last_part = end($parts);
3569   
3570          if (preg_match(get_preg_expression('ipv4'), $last_part))
3571          {
3572              $parts[sizeof($parts) - 1] = '';
3573              $last_part = phpbb_inet_pton($last_part);
3574              $embedded_ipv4 = true;
3575              --$missing_parts;
3576          }
3577   
3578          foreach ($parts as $i => $part)
3579          {
3580              if (strlen($part))
3581              {
3582                  $ret .= str_pad($part, 4, '0', STR_PAD_LEFT);
3583              }
3584              else if ($i && $i < sizeof($parts) - 1)
3585              {
3586                  $ret .= str_repeat('0000', $missing_parts);
3587              }
3588          }
3589   
3590          $ret = pack('H*', $ret);
3591   
3592          if ($embedded_ipv4)
3593          {
3594              $ret .= $last_part;
3595          }
3596   
3597          return $ret;
3598      }
3599   
3600      return false;
3601  }
3602   
3603  /**
3604  * Wrapper for php's checkdnsrr function.
3605  *
3606  * @param string $host    Fully-Qualified Domain Name
3607  * @param string $type    Resource record type to lookup
3608  *                        Supported types are: MX (default), A, AAAA, NS, TXT, CNAME
3609  *                        Other types may work or may not work
3610  *
3611  * @return mixed        true if entry found,
3612  *                    false if entry not found,
3613  *                    null if this function is not supported by this environment
3614  *
3615  * Since null can also be returned, you probably want to compare the result
3616  * with === true or === false,
3617  */
3618  function phpbb_checkdnsrr($host, $type = 'MX')
3619  {
3620      // The dot indicates to search the DNS root (helps those having DNS prefixes on the same domain)
3621      if (substr($host, -1) == '.')
3622      {
3623          $host_fqdn = $host;
3624          $host = substr($host, 0, -1);
3625      }
3626      else
3627      {
3628          $host_fqdn = $host . '.';
3629      }
3630      // $host        has format    some.host.example.com
3631      // $host_fqdn    has format    some.host.example.com.
3632   
3633      // If we're looking for an A record we can use gethostbyname()
3634      if ($type == 'A' && function_exists('gethostbyname'))
3635      {
3636          return (@gethostbyname($host_fqdn) == $host_fqdn) ? false : true;
3637      }
3638   
3639      // checkdnsrr() is available on Windows since PHP 5.3,
3640      // but until 5.3.3 it only works for MX records
3641      // See: http://bugs.php.net/bug.php?id=51844
3642   
3643      // Call checkdnsrr() if
3644      // we're looking for an MX record or
3645      // we're not on Windows or
3646      // we're running a PHP version where #51844 has been fixed
3647   
3648      // checkdnsrr() supports AAAA since 5.0.0
3649      // checkdnsrr() supports TXT since 5.2.4
3650      if (
3651          ($type == 'MX' || DIRECTORY_SEPARATOR != '\\' || version_compare(PHP_VERSION, '5.3.3', '>=')) &&
3652          ($type != 'AAAA' || version_compare(PHP_VERSION, '5.0.0', '>=')) &&
3653          ($type != 'TXT' || version_compare(PHP_VERSION, '5.2.4', '>=')) &&
3654          function_exists('checkdnsrr')
3655      )
3656      {
3657          return checkdnsrr($host_fqdn, $type);
3658      }
3659   
3660      // dns_get_record() is available since PHP 5; since PHP 5.3 also on Windows,
3661      // but on Windows it does not work reliable for AAAA records before PHP 5.3.1
3662   
3663      // Call dns_get_record() if
3664      // we're not looking for an AAAA record or
3665      // we're not on Windows or
3666      // we're running a PHP version where AAAA lookups work reliable
3667      if (
3668          ($type != 'AAAA' || DIRECTORY_SEPARATOR != '\\' || version_compare(PHP_VERSION, '5.3.1', '>=')) &&
3669          function_exists('dns_get_record')
3670      )
3671      {
3672          // dns_get_record() expects an integer as second parameter
3673          // We have to convert the string $type to the corresponding integer constant.
3674          $type_constant = 'DNS_' . $type;
3675          $type_param = (defined($type_constant)) ? constant($type_constant) : DNS_ANY;
3676   
3677          // dns_get_record() might throw E_WARNING and return false for records that do not exist
3678          $resultset = @dns_get_record($host_fqdn, $type_param);
3679   
3680          if (empty($resultset) || !is_array($resultset))
3681          {
3682              return false;
3683          }
3684          else if ($type_param == DNS_ANY)
3685          {
3686              // $resultset is a non-empty array
3687              return true;
3688          }
3689   
3690          foreach ($resultset as $result)
3691          {
3692              if (
3693                  isset($result['host']) && $result['host'] == $host &&
3694                  isset($result['type']) && $result['type'] == $type
3695              )
3696              {
3697                  return true;
3698              }
3699          }
3700   
3701          return false;
3702      }
3703   
3704      // If we're on Windows we can still try to call nslookup via exec() as a last resort
3705      if (DIRECTORY_SEPARATOR == '\\' && function_exists('exec'))
3706      {
3707          @exec('nslookup -type=' . escapeshellarg($type) . ' ' . escapeshellarg($host_fqdn), $output);
3708   
3709          // If output is empty, the nslookup failed
3710          if (empty($output))
3711          {
3712              return NULL;
3713          }
3714   
3715          foreach ($output as $line)
3716          {
3717              $line = trim($line);
3718   
3719              if (empty($line))
3720              {
3721                  continue;
3722              }
3723   
3724              // Squash tabs and multiple whitespaces to a single whitespace.
3725              $line = preg_replace('/\s+/', ' ', $line);
3726   
3727              switch ($type)
3728              {
3729                  case 'MX':
3730                      if (stripos($line, "$host MX") === 0)
3731                      {
3732                          return true;
3733                      }
3734                  break;
3735   
3736                  case 'NS':
3737                      if (stripos($line, "$host nameserver") === 0)
3738                      {
3739                          return true;
3740                      }
3741                  break;
3742   
3743                  case 'TXT':
3744                      if (stripos($line, "$host text") === 0)
3745                      {
3746                          return true;
3747                      }
3748                  break;
3749   
3750                  case 'CNAME':
3751                      if (stripos($line, "$host canonical name") === 0)
3752                      {
3753                          return true;
3754                      }
3755                  break;
3756   
3757                  default:
3758                  case 'AAAA':
3759                      // AAAA records returned by nslookup on Windows XP/2003 have this format.
3760                      // Later Windows versions use the A record format below for AAAA records.
3761                      if (stripos($line, "$host AAAA IPv6 address") === 0)
3762                      {
3763                          return true;
3764                      }
3765                  // No break
3766   
3767                  case 'A':
3768                      if (!empty($host_matches))
3769                      {
3770                          // Second line
3771                          if (stripos($line, "Address: ") === 0)
3772                          {
3773                              return true;
3774                          }
3775                          else
3776                          {
3777                              $host_matches = false;
3778                          }
3779                      }
3780                      else if (stripos($line, "Name: $host") === 0)
3781                      {
3782                          // First line
3783                          $host_matches = true;
3784                      }
3785                  break;
3786              }
3787          }
3788   
3789          return false;
3790      }
3791   
3792      return NULL;
3793  }
3794   
3795  // Handler, header and footer
3796   
3797  /**
3798  * Error and message handler, call with trigger_error if read
3799  */
3800  function msg_handler($errno, $msg_text, $errfile, $errline)
3801  {
3802      global $cache, $db, $auth, $template, $config, $user, $request;
3803      global $phpEx, $phpbb_root_path, $msg_title, $msg_long_text;
3804   
3805      // Do not display notices if we suppress them via @
3806      if (error_reporting() == 0 && $errno != E_USER_ERROR && $errno != E_USER_WARNING && $errno != E_USER_NOTICE)
3807      {
3808          return;
3809      }
3810   
3811      // Message handler is stripping text. In case we need it, we are possible to define long text...
3812      if (isset($msg_long_text) && $msg_long_text && !$msg_text)
3813      {
3814          $msg_text = $msg_long_text;
3815      }
3816   
3817      if (!defined('E_DEPRECATED'))
3818      {
3819          define('E_DEPRECATED', 8192);
3820      }
3821   
3822      switch ($errno)
3823      {
3824          case E_NOTICE:
3825          case E_WARNING:
3826   
3827              // Check the error reporting level and return if the error level does not match
3828              // If DEBUG is defined the default level is E_ALL
3829              if (($errno & ((defined('DEBUG')) ? E_ALL : error_reporting())) == 0)
3830              {
3831                  return;
3832              }
3833   
3834              if (strpos($errfile, 'cache') === false && strpos($errfile, 'template.') === false)
3835              {
3836                  $errfile = phpbb_filter_root_path($errfile);
3837                  $msg_text = phpbb_filter_root_path($msg_text);
3838                  $error_name = ($errno === E_WARNING) ? 'PHP Warning' : 'PHP Notice';
3839                  echo '<b>[phpBB Debug] ' . $error_name . '</b>: in file <b>' . $errfile . '</b> on line <b>' . $errline . '</b>: <b>' . $msg_text . '</b><br />' . "\n";
3840   
3841                  // we are writing an image - the user won't see the debug, so let's place it in the log
3842                  if (defined('IMAGE_OUTPUT') || defined('IN_CRON'))
3843                  {
3844                      add_log('critical', 'LOG_IMAGE_GENERATION_ERROR', $errfile, $errline, $msg_text);
3845                  }
3846                  // echo '<br /><br />BACKTRACE<br />' . get_backtrace() . '<br />' . "\n";
3847              }
3848   
3849              return;
3850   
3851          break;
3852   
3853          case E_USER_ERROR:
3854   
3855              if (!empty($user) && !empty($user->lang))
3856              {
3857                  $msg_text = (!empty($user->lang[$msg_text])) ? $user->lang[$msg_text] : $msg_text;
3858                  $msg_title = (!isset($msg_title)) ? $user->lang['GENERAL_ERROR'] : ((!empty($user->lang[$msg_title])) ? $user->lang[$msg_title] : $msg_title);
3859   
3860                  $l_return_index = sprintf($user->lang['RETURN_INDEX'], '<a href="' . $phpbb_root_path . '">', '</a>');
3861                  $l_notify = '';
3862   
3863                  if (!empty($config['board_contact']))
3864                  {
3865                      $l_notify = '<p>' . sprintf($user->lang['NOTIFY_ADMIN_EMAIL'], $config['board_contact']) . '</p>';
3866                  }
3867              }
3868              else
3869              {
3870                  $msg_title = 'General Error';
3871                  $l_return_index = '<a href="' . $phpbb_root_path . '">Return to index page</a>';
3872                  $l_notify = '';
3873   
3874                  if (!empty($config['board_contact']))
3875                  {
3876                      $l_notify = '<p>Please notify the board administrator or webmaster: <a href="mailto:' . $config['board_contact'] . '">' . $config['board_contact'] . '</a></p>';
3877                  }
3878              }
3879   
3880              $log_text = $msg_text;
3881              $backtrace = get_backtrace();
3882              if ($backtrace)
3883              {
3884                  $log_text .= '<br /><br />BACKTRACE<br />' . $backtrace;
3885              }
3886   
3887              if (defined('IN_INSTALL') || defined('DEBUG') || isset($auth) && $auth->acl_get('a_'))
3888              {
3889                  $msg_text = $log_text;
3890   
3891                  // If this is defined there already was some output
3892                  // So let's not break it
3893                  if (defined('IN_DB_UPDATE'))
3894                  {
3895                      echo '<div class="errorbox">' . $msg_text . '</div>';
3896   
3897                      $db->sql_return_on_error(true);
3898                      phpbb_end_update($cache, $config);
3899                  }
3900              }
3901   
3902              if ((defined('IN_CRON') || defined('IMAGE_OUTPUT')) && isset($db))
3903              {
3904                  // let's avoid loops
3905                  $db->sql_return_on_error(true);
3906                  add_log('critical', 'LOG_GENERAL_ERROR', $msg_title, $log_text);
3907                  $db->sql_return_on_error(false);
3908              }
3909   
3910              // Do not send 200 OK, but service unavailable on errors
3911              send_status_line(503, 'Service Unavailable');
3912   
3913              garbage_collection();
3914   
3915              // Try to not call the adm page data...
3916   
3917              echo '<!DOCTYPE html>';
3918              echo '<html dir="ltr">';
3919              echo '<head>';
3920              echo '<meta charset="utf-8">';
3921              echo '<title>' . $msg_title . '</title>';
3922              echo '<style type="text/css">' . "\n" . '/* <![CDATA[ */' . "\n";
3923              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; } ';
3924              echo 'a:link, a:active, a:visited { color: #006699; text-decoration: none; } a:hover { color: #DD6900; text-decoration: underline; } ';
3925              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; } ';
3926              echo '.panel { margin: 4px 0; background-color: #FFFFFF; border: solid 1px  #A9B8C2; } ';
3927              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; } ';
3928              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; } ';
3929              echo "\n" . '/* ]]> */' . "\n";
3930              echo '</style>';
3931              echo '</head>';
3932              echo '<body id="errorpage">';
3933              echo '<div id="wrap">';
3934              echo '    <div id="page-header">';
3935              echo '        ' . $l_return_index;
3936              echo '    </div>';
3937              echo '    <div id="acp">';
3938              echo '    <div class="panel">';
3939              echo '        <div id="content">';
3940              echo '            <h1>' . $msg_title . '</h1>';
3941   
3942              echo '            <div>' . $msg_text . '</div>';
3943   
3944              echo $l_notify;
3945   
3946              echo '        </div>';
3947              echo '    </div>';
3948              echo '    </div>';
3949              echo '    <div id="page-footer">';
3950              echo '        Powered by <a href="https://www.phpbb.com/">phpBB</a>&reg; Forum Software &copy; phpBB Limited';
3951              echo '    </div>';
3952              echo '</div>';
3953              echo '</body>';
3954              echo '</html>';
3955   
3956              exit_handler();
3957   
3958              // On a fatal error (and E_USER_ERROR *is* fatal) we never want other scripts to continue and force an exit here.
3959              exit;
3960          break;
3961   
3962          case E_USER_WARNING:
3963          case E_USER_NOTICE:
3964   
3965              define('IN_ERROR_HANDLER', true);
3966   
3967              if (empty($user->data))
3968              {
3969                  $user->session_begin();
3970              }
3971   
3972              // We re-init the auth array to get correct results on login/logout
3973              $auth->acl($user->data);
3974   
3975              if (empty($user->lang))
3976              {
3977                  $user->setup();
3978              }
3979   
3980              if ($msg_text == 'ERROR_NO_ATTACHMENT' || $msg_text == 'NO_FORUM' || $msg_text == 'NO_TOPIC' || $msg_text == 'NO_USER')
3981              {
3982                  send_status_line(404, 'Not Found');
3983              }
3984   
3985              $msg_text = (!empty($user->lang[$msg_text])) ? $user->lang[$msg_text] : $msg_text;
3986              $msg_title = (!isset($msg_title)) ? $user->lang['INFORMATION'] : ((!empty($user->lang[$msg_title])) ? $user->lang[$msg_title] : $msg_title);
3987   
3988              if (!defined('HEADER_INC'))
3989              {
3990                  if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
3991                  {
3992                      adm_page_header($msg_title);
3993                  }
3994                  else
3995                  {
3996                      page_header($msg_title);
3997                  }
3998              }
3999   
4000              $template->set_filenames(array(
4001                  'body' => 'message_body.html')
4002              );
4003   
4004              $template->assign_vars(array(
4005                  'MESSAGE_TITLE'        => $msg_title,
4006                  'MESSAGE_TEXT'        => $msg_text,
4007                  'S_USER_WARNING'    => ($errno == E_USER_WARNING) ? true : false,
4008                  'S_USER_NOTICE'        => ($errno == E_USER_NOTICE) ? true : false)
4009              );
4010   
4011              if ($request->is_ajax())
4012              {
4013                  global $refresh_data;
4014   
4015                  $json_response = new \phpbb\json_response;
4016                  $json_response->send(array(
4017                      'MESSAGE_TITLE'        => $msg_title,
4018                      'MESSAGE_TEXT'        => $msg_text,
4019                      'S_USER_WARNING'    => ($errno == E_USER_WARNING) ? true : false,
4020                      'S_USER_NOTICE'        => ($errno == E_USER_NOTICE) ? true : false,
4021                      'REFRESH_DATA'        => (!empty($refresh_data)) ? $refresh_data : null
4022                  ));
4023              }
4024   
4025              // We do not want the cron script to be called on error messages
4026              define('IN_CRON', true);
4027   
4028              if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
4029              {
4030                  adm_page_footer();
4031              }
4032              else
4033              {
4034                  page_footer();
4035              }
4036   
4037              exit_handler();
4038          break;
4039   
4040          // PHP4 compatibility
4041          case E_DEPRECATED:
4042              return true;
4043          break;
4044      }
4045   
4046      // If we notice an error not handled here we pass this back to PHP by returning false
4047      // This may not work for all php versions
4048      return false;
4049  }
4050   
4051  /**
4052  * Removes absolute path to phpBB root directory from error messages
4053  * and converts backslashes to forward slashes.
4054  *
4055  * @param string $errfile    Absolute file path
4056  *                            (e.g. /var/www/phpbb3/phpBB/includes/functions.php)
4057  *                            Please note that if $errfile is outside of the phpBB root,
4058  *                            the root path will not be found and can not be filtered.
4059  * @return string            Relative file path
4060  *                            (e.g. /includes/functions.php)
4061  */
4062  function phpbb_filter_root_path($errfile)
4063  {
4064      static $root_path;
4065   
4066      if (empty($root_path))
4067      {
4068          $root_path = phpbb_realpath(dirname(__FILE__) . '/../');
4069      }
4070   
4071      return str_replace(array($root_path, '\\'), array('[ROOT]', '/'), $errfile);
4072  }
4073   
4074  /**
4075  * Queries the session table to get information about online guests
4076  * @param int $item_id Limits the search to the item with this id
4077  * @param string $item The name of the item which is stored in the session table as session_{$item}_id
4078  * @return int The number of active distinct guest sessions
4079  */
4080  function obtain_guest_count($item_id = 0, $item = 'forum')
4081  {
4082      global $db, $config;
4083   
4084      if ($item_id)
4085      {
4086          $reading_sql = ' AND s.session_' . $item . '_id = ' . (int) $item_id;
4087      }
4088      else
4089      {
4090          $reading_sql = '';
4091      }
4092      $time = (time() - (intval($config['load_online_time']) * 60));
4093   
4094      // Get number of online guests
4095   
4096      if ($db->get_sql_layer() === 'sqlite' || $db->get_sql_layer() === 'sqlite3')
4097      {
4098          $sql = 'SELECT COUNT(session_ip) as num_guests
4099              FROM (
4100                  SELECT DISTINCT s.session_ip
4101                  FROM ' . SESSIONS_TABLE . ' s
4102                  WHERE s.session_user_id = ' . ANONYMOUS . '
4103                      AND s.session_time >= ' . ($time - ((int) ($time % 60))) .
4104                  $reading_sql .
4105              ')';
4106      }
4107      else
4108      {
4109          $sql = 'SELECT COUNT(DISTINCT s.session_ip) as num_guests
4110              FROM ' . SESSIONS_TABLE . ' s
4111              WHERE s.session_user_id = ' . ANONYMOUS . '
4112                  AND s.session_time >= ' . ($time - ((int) ($time % 60))) .
4113              $reading_sql;
4114      }
4115      $result = $db->sql_query($sql);
4116      $guests_online = (int) $db->sql_fetchfield('num_guests');
4117      $db->sql_freeresult($result);
4118   
4119      return $guests_online;
4120  }
4121   
4122  /**
4123  * Queries the session table to get information about online users
4124  * @param int $item_id Limits the search to the item with this id
4125  * @param string $item The name of the item which is stored in the session table as session_{$item}_id
4126  * @return array An array containing the ids of online, hidden and visible users, as well as statistical info
4127  */
4128  function obtain_users_online($item_id = 0, $item = 'forum')
4129  {
4130      global $db, $config, $user;
4131   
4132      $reading_sql = '';
4133      if ($item_id !== 0)
4134      {
4135          $reading_sql = ' AND s.session_' . $item . '_id = ' . (int) $item_id;
4136      }
4137   
4138      $online_users = array(
4139          'online_users'            => array(),
4140          'hidden_users'            => array(),
4141          'total_online'            => 0,
4142          'visible_online'        => 0,
4143          'hidden_online'            => 0,
4144          'guests_online'            => 0,
4145      );
4146   
4147      if ($config['load_online_guests'])
4148      {
4149          $online_users['guests_online'] = obtain_guest_count($item_id, $item);
4150      }
4151   
4152      // a little discrete magic to cache this for 30 seconds
4153      $time = (time() - (intval($config['load_online_time']) * 60));
4154   
4155      $sql = 'SELECT s.session_user_id, s.session_ip, s.session_viewonline
4156          FROM ' . SESSIONS_TABLE . ' s
4157          WHERE s.session_time >= ' . ($time - ((int) ($time % 30))) .
4158              $reading_sql .
4159          ' AND s.session_user_id <> ' . ANONYMOUS;
4160      $result = $db->sql_query($sql);
4161   
4162      while ($row = $db->sql_fetchrow($result))
4163      {
4164          // Skip multiple sessions for one user
4165          if (!isset($online_users['online_users'][$row['session_user_id']]))
4166          {
4167              $online_users['online_users'][$row['session_user_id']] = (int) $row['session_user_id'];
4168              if ($row['session_viewonline'])
4169              {
4170                  $online_users['visible_online']++;
4171              }
4172              else
4173              {
4174                  $online_users['hidden_users'][$row['session_user_id']] = (int) $row['session_user_id'];
4175                  $online_users['hidden_online']++;
4176              }
4177          }
4178      }
4179      $online_users['total_online'] = $online_users['guests_online'] + $online_users['visible_online'] + $online_users['hidden_online'];
4180      $db->sql_freeresult($result);
4181   
4182      return $online_users;
4183  }
4184   
4185  /**
4186  * Uses the result of obtain_users_online to generate a localized, readable representation.
4187  * @param mixed $online_users result of obtain_users_online - array with user_id lists for total, hidden and visible users, and statistics
4188  * @param int $item_id Indicate that the data is limited to one item and not global
4189  * @param string $item The name of the item which is stored in the session table as session_{$item}_id
4190  * @return array An array containing the string for output to the template
4191  */
4192  function obtain_users_online_string($online_users, $item_id = 0, $item = 'forum')
4193  {
4194      global $config, $db, $user, $auth;
4195   
4196      $user_online_link = $online_userlist = '';
4197      // Need caps version of $item for language-strings
4198      $item_caps = strtoupper($item);
4199   
4200      if (sizeof($online_users['online_users']))
4201      {
4202          $sql = 'SELECT username, username_clean, user_id, user_type, user_allow_viewonline, user_colour
4203                  FROM ' . USERS_TABLE . '
4204                  WHERE ' . $db->sql_in_set('user_id', $online_users['online_users']) . '
4205                  ORDER BY username_clean ASC';
4206          $result = $db->sql_query($sql);
4207   
4208          while ($row = $db->sql_fetchrow($result))
4209          {
4210              // User is logged in and therefore not a guest
4211              if ($row['user_id'] != ANONYMOUS)
4212              {
4213                  if (isset($online_users['hidden_users'][$row['user_id']]))
4214                  {
4215                      $row['username'] = '<em>' . $row['username'] . '</em>';
4216                  }
4217   
4218                  if (!isset($online_users['hidden_users'][$row['user_id']]) || $auth->acl_get('u_viewonline'))
4219                  {
4220                      $user_online_link = get_username_string(($row['user_type'] <> USER_IGNORE) ? 'full' : 'no_profile', $row['user_id'], $row['username'], $row['user_colour']);
4221                      $online_userlist .= ($online_userlist != '') ? ', ' . $user_online_link : $user_online_link;
4222                  }
4223              }
4224          }
4225          $db->sql_freeresult($result);
4226      }
4227   
4228      if (!$online_userlist)
4229      {
4230          $online_userlist = $user->lang['NO_ONLINE_USERS'];
4231      }
4232   
4233      if ($item_id === 0)
4234      {
4235          $online_userlist = $user->lang['REGISTERED_USERS'] . ' ' . $online_userlist;
4236      }
4237      else if ($config['load_online_guests'])
4238      {
4239          $online_userlist = $user->lang('BROWSING_' . $item_caps . '_GUESTS', $online_users['guests_online'], $online_userlist);
4240      }
4241      else
4242      {
4243          $online_userlist = sprintf($user->lang['BROWSING_' . $item_caps], $online_userlist);
4244      }
4245      // Build online listing
4246      $visible_online = $user->lang('REG_USERS_TOTAL', (int) $online_users['visible_online']);
4247      $hidden_online = $user->lang('HIDDEN_USERS_TOTAL', (int) $online_users['hidden_online']);
4248   
4249      if ($config['load_online_guests'])
4250      {
4251          $guests_online = $user->lang('GUEST_USERS_TOTAL', (int) $online_users['guests_online']);
4252          $l_online_users = $user->lang('ONLINE_USERS_TOTAL_GUESTS', (int) $online_users['total_online'], $visible_online, $hidden_online, $guests_online);
4253      }
4254      else
4255      {
4256          $l_online_users = $user->lang('ONLINE_USERS_TOTAL', (int) $online_users['total_online'], $visible_online, $hidden_online);
4257      }
4258   
4259      return array(
4260          'online_userlist'    => $online_userlist,
4261          'l_online_users'    => $l_online_users,
4262      );
4263  }
4264   
4265  /**
4266  * Get option bitfield from custom data
4267  *
4268  * @param int    $bit        The bit/value to get
4269  * @param int    $data        Current bitfield to check
4270  * @return bool    Returns true if value of constant is set in bitfield, else false
4271  */
4272  function phpbb_optionget($bit, $data)
4273  {
4274      return ($data & 1 << (int) $bit) ? true : false;
4275  }
4276   
4277  /**
4278  * Set option bitfield
4279  *
4280  * @param int    $bit        The bit/value to set/unset
4281  * @param bool    $set        True if option should be set, false if option should be unset.
4282  * @param int    $data        Current bitfield to change
4283  *
4284  * @return int    The new bitfield
4285  */
4286  function phpbb_optionset($bit, $set, $data)
4287  {
4288      if ($set && !($data & 1 << $bit))
4289      {
4290          $data += 1 << $bit;
4291      }
4292      else if (!$set && ($data & 1 << $bit))
4293      {
4294          $data -= 1 << $bit;
4295      }
4296   
4297      return $data;
4298  }
4299   
4300  /**
4301  * Determine which plural form we should use.
4302  * For some languages this is not as simple as for English.
4303  *
4304  * @param $rule        int            ID of the plural rule we want to use, see http://wiki.phpbb.com/Plural_Rules#Plural_Rules
4305  * @param $number    int|float    The number we want to get the plural case for. Float numbers are floored.
4306  * @return    int        The plural-case we need to use for the number plural-rule combination
4307  */
4308  function phpbb_get_plural_form($rule, $number)
4309  {
4310      $number = (int) $number;
4311   
4312      if ($rule > 15 || $rule < 0)
4313      {
4314          trigger_error('INVALID_PLURAL_RULE');
4315      }
4316   
4317      /**
4318      * The following plural rules are based on a list published by the Mozilla Developer Network
4319      * https://developer.mozilla.org/en/Localization_and_Plurals
4320      */
4321      switch ($rule)
4322      {
4323          case 0:
4324              /**
4325              * Families: Asian (Chinese, Japanese, Korean, Vietnamese), Persian, Turkic/Altaic (Turkish), Thai, Lao
4326              * 1 - everything: 0, 1, 2, ...
4327              */
4328              return 1;
4329   
4330          case 1:
4331              /**
4332              * Families: Germanic (Danish, Dutch, English, Faroese, Frisian, German, Norwegian, Swedish), Finno-Ugric (Estonian, Finnish, Hungarian), Language isolate (Basque), Latin/Greek (Greek), Semitic (Hebrew), Romanic (Italian, Portuguese, Spanish, Catalan)
4333              * 1 - 1
4334              * 2 - everything else: 0, 2, 3, ...
4335              */
4336              return ($number == 1) ? 1 : 2;
4337   
4338          case 2:
4339              /**
4340              * Families: Romanic (French, Brazilian Portuguese)
4341              * 1 - 0, 1
4342              * 2 - everything else: 2, 3, ...
4343              */
4344              return (($number == 0) || ($number == 1)) ? 1 : 2;
4345   
4346          case 3:
4347              /**
4348              * Families: Baltic (Latvian)
4349              * 1 - 0
4350              * 2 - ends in 1, not 11: 1, 21, ... 101, 121, ...
4351              * 3 - everything else: 2, 3, ... 10, 11, 12, ... 20, 22, ...
4352              */
4353              return ($number == 0) ? 1 : ((($number % 10 == 1) && ($number % 100 != 11)) ? 2 : 3);
4354   
4355          case 4:
4356              /**
4357              * Families: Celtic (Scottish Gaelic)
4358              * 1 - is 1 or 11: 1, 11
4359              * 2 - is 2 or 12: 2, 12
4360              * 3 - others between 3 and 19: 3, 4, ... 10, 13, ... 18, 19
4361              * 4 - everything else: 0, 20, 21, ...
4362              */
4363              return ($number == 1 || $number == 11) ? 1 : (($number == 2 || $number == 12) ? 2 : (($number >= 3 && $number <= 19) ? 3 : 4));
4364   
4365          case 5:
4366              /**
4367              * Families: Romanic (Romanian)
4368              * 1 - 1
4369              * 2 - is 0 or ends in 01-19: 0, 2, 3, ... 19, 101, 102, ... 119, 201, ...
4370              * 3 - everything else: 20, 21, ...
4371              */
4372              return ($number == 1) ? 1 : ((($number == 0) || (($number % 100 > 0) && ($number % 100 < 20))) ? 2 : 3);
4373   
4374          case 6:
4375              /**
4376              * Families: Baltic (Lithuanian)
4377              * 1 - ends in 1, not 11: 1, 21, 31, ... 101, 121, ...
4378              * 2 - ends in 0 or ends in 10-20: 0, 10, 11, 12, ... 19, 20, 30, 40, ...
4379              * 3 - everything else: 2, 3, ... 8, 9, 22, 23, ... 29, 32, 33, ...
4380              */
4381              return (($number % 10 == 1) && ($number % 100 != 11)) ? 1 : ((($number % 10 < 2) || (($number % 100 >= 10) && ($number % 100 < 20))) ? 2 : 3);
4382   
4383          case 7:
4384              /**
4385              * Families: Slavic (Croatian, Serbian, Russian, Ukrainian)
4386              * 1 - ends in 1, not 11: 1, 21, 31, ... 101, 121, ...
4387              * 2 - ends in 2-4, not 12-14: 2, 3, 4, 22, 23, 24, 32, ...
4388              * 3 - everything else: 0, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 25, 26, ...
4389              */
4390              return (($number % 10 == 1) && ($number % 100 != 11)) ? 1 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 2 : 3);
4391   
4392          case 8:
4393              /**
4394              * Families: Slavic (Slovak, Czech)
4395              * 1 - 1
4396              * 2 - 2, 3, 4
4397              * 3 - everything else: 0, 5, 6, 7, ...
4398              */
4399              return ($number == 1) ? 1 : ((($number >= 2) && ($number <= 4)) ? 2 : 3);
4400   
4401          case 9:
4402              /**
4403              * Families: Slavic (Polish)
4404              * 1 - 1
4405              * 2 - ends in 2-4, not 12-14: 2, 3, 4, 22, 23, 24, 32, ... 104, 122, ...
4406              * 3 - everything else: 0, 5, 6, ... 11, 12, 13, 14, 15, ... 20, 21, 25, ...
4407              */
4408              return ($number == 1) ? 1 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 12) || ($number % 100 > 14))) ? 2 : 3);
4409   
4410          case 10:
4411              /**
4412              * Families: Slavic (Slovenian, Sorbian)
4413              * 1 - ends in 01: 1, 101, 201, ...
4414              * 2 - ends in 02: 2, 102, 202, ...
4415              * 3 - ends in 03-04: 3, 4, 103, 104, 203, 204, ...
4416              * 4 - everything else: 0, 5, 6, 7, 8, 9, 10, 11, ...
4417              */
4418              return ($number % 100 == 1) ? 1 : (($number % 100 == 2) ? 2 : ((($number % 100 == 3) || ($number % 100 == 4)) ? 3 : 4));
4419   
4420          case 11:
4421              /**
4422              * Families: Celtic (Irish Gaeilge)
4423              * 1 - 1
4424              * 2 - 2
4425              * 3 - is 3-6: 3, 4, 5, 6
4426              * 4 - is 7-10: 7, 8, 9, 10
4427              * 5 - everything else: 0, 11, 12, ...
4428              */
4429              return ($number == 1) ? 1 : (($number == 2) ? 2 : (($number >= 3 && $number <= 6) ? 3 : (($number >= 7 && $number <= 10) ? 4 : 5)));
4430   
4431          case 12:
4432              /**
4433              * Families: Semitic (Arabic)
4434              * 1 - 1
4435              * 2 - 2
4436              * 3 - ends in 03-10: 3, 4, ... 10, 103, 104, ... 110, 203, 204, ...
4437              * 4 - ends in 11-99: 11, ... 99, 111, 112, ...
4438              * 5 - everything else: 100, 101, 102, 200, 201, 202, ...
4439              * 6 - 0
4440              */
4441              return ($number == 1) ? 1 : (($number == 2) ? 2 : ((($number % 100 >= 3) && ($number % 100 <= 10)) ? 3 : ((($number % 100 >= 11) && ($number % 100 <= 99)) ? 4 : (($number != 0) ? 5 : 6))));
4442   
4443          case 13:
4444              /**
4445              * Families: Semitic (Maltese)
4446              * 1 - 1
4447              * 2 - is 0 or ends in 01-10: 0, 2, 3, ... 9, 10, 101, 102, ...
4448              * 3 - ends in 11-19: 11, 12, ... 18, 19, 111, 112, ...
4449              * 4 - everything else: 20, 21, ...
4450              */
4451              return ($number == 1) ? 1 : ((($number == 0) || (($number % 100 > 1) && ($number % 100 < 11))) ? 2 : ((($number % 100 > 10) && ($number % 100 < 20)) ? 3 : 4));
4452   
4453          case 14:
4454              /**
4455              * Families: Slavic (Macedonian)
4456              * 1 - ends in 1: 1, 11, 21, ...
4457              * 2 - ends in 2: 2, 12, 22, ...
4458              * 3 - everything else: 0, 3, 4, ... 10, 13, 14, ... 20, 23, ...
4459              */
4460              return ($number % 10 == 1) ? 1 : (($number % 10 == 2) ? 2 : 3);
4461   
4462          case 15:
4463              /**
4464              * Families: Icelandic
4465              * 1 - ends in 1, not 11: 1, 21, 31, ... 101, 121, 131, ...
4466              * 2 - everything else: 0, 2, 3, ... 10, 11, 12, ... 20, 22, ...
4467              */
4468              return (($number % 10 == 1) && ($number % 100 != 11)) ? 1 : 2;
4469      }
4470  }
4471   
4472  /**
4473  * Login using http authenticate.
4474  *
4475  * @param array    $param        Parameter array, see $param_defaults array.
4476  *
4477  * @return null
4478  */
4479  function phpbb_http_login($param)
4480  {
4481      global $auth, $user, $request;
4482      global $config;
4483   
4484      $param_defaults = array(
4485          'auth_message'    => '',
4486   
4487          'autologin'        => false,
4488          'viewonline'    => true,
4489          'admin'            => false,
4490      );
4491   
4492      // Overwrite default values with passed values
4493      $param = array_merge($param_defaults, $param);
4494   
4495      // User is already logged in
4496      // We will not overwrite his session
4497      if (!empty($user->data['is_registered']))
4498      {
4499          return;
4500      }
4501   
4502      // $_SERVER keys to check
4503      $username_keys = array(
4504          'PHP_AUTH_USER',
4505          'Authorization',
4506          'REMOTE_USER', 'REDIRECT_REMOTE_USER',
4507          'HTTP_AUTHORIZATION', 'REDIRECT_HTTP_AUTHORIZATION',
4508          'REMOTE_AUTHORIZATION', 'REDIRECT_REMOTE_AUTHORIZATION',
4509          'AUTH_USER',
4510      );
4511   
4512      $password_keys = array(
4513          'PHP_AUTH_PW',
4514          'REMOTE_PASSWORD',
4515          'AUTH_PASSWORD',
4516      );
4517   
4518      $username = null;
4519      foreach ($username_keys as $k)
4520      {
4521          if ($request->is_set($k, \phpbb\request\request_interface::SERVER))
4522          {
4523              $username = htmlspecialchars_decode($request->server($k));
4524              break;
4525          }
4526      }
4527   
4528      $password = null;
4529      foreach ($password_keys as $k)
4530      {
4531          if ($request->is_set($k, \phpbb\request\request_interface::SERVER))
4532          {
4533              $password = htmlspecialchars_decode($request->server($k));
4534              break;
4535          }
4536      }
4537   
4538      // Decode encoded information (IIS, CGI, FastCGI etc.)
4539      if (!is_null($username) && is_null($password) && strpos($username, 'Basic ') === 0)
4540      {
4541          list($username, $password) = explode(':', base64_decode(substr($username, 6)), 2);
4542      }
4543   
4544      if (!is_null($username) && !is_null($password))
4545      {
4546          set_var($username, $username, 'string', true);
4547          set_var($password, $password, 'string', true);
4548   
4549          $auth_result = $auth->login($username, $password, $param['autologin'], $param['viewonline'], $param['admin']);
4550   
4551          if ($auth_result['status'] == LOGIN_SUCCESS)
4552          {
4553              return;
4554          }
4555          else if ($auth_result['status'] == LOGIN_ERROR_ATTEMPTS)
4556          {
4557              send_status_line(401, 'Unauthorized');
4558   
4559              trigger_error('NOT_AUTHORISED');
4560          }
4561      }
4562   
4563      // Prepend sitename to auth_message
4564      $param['auth_message'] = ($param['auth_message'] === '') ? $config['sitename'] : $config['sitename'] . ' - ' . $param['auth_message'];
4565   
4566      // We should probably filter out non-ASCII characters - RFC2616
4567      $param['auth_message'] = preg_replace('/[\x80-\xFF]/', '?', $param['auth_message']);
4568   
4569      header('WWW-Authenticate: Basic realm="' . $param['auth_message'] . '"');
4570      send_status_line(401, 'Unauthorized');
4571   
4572      trigger_error('NOT_AUTHORISED');
4573  }
4574   
4575  /**
4576  * Escapes and quotes a string for use as an HTML/XML attribute value.
4577  *
4578  * This is a port of Python xml.sax.saxutils quoteattr.
4579  *
4580  * The function will attempt to choose a quote character in such a way as to
4581  * avoid escaping quotes in the string. If this is not possible the string will
4582  * be wrapped in double quotes and double quotes will be escaped.
4583  *
4584  * @param string $data The string to be escaped
4585  * @param array $entities Associative array of additional entities to be escaped
4586  * @return string Escaped and quoted string
4587  */
4588  function phpbb_quoteattr($data, $entities = null)
4589  {
4590      $data = str_replace('&', '&amp;', $data);
4591      $data = str_replace('>', '&gt;', $data);
4592      $data = str_replace('<', '&lt;', $data);
4593   
4594      $data = str_replace("\n", '&#10;', $data);
4595      $data = str_replace("\r", '&#13;', $data);
4596      $data = str_replace("\t", '&#9;', $data);
4597   
4598      if (!empty($entities))
4599      {
4600          $data = str_replace(array_keys($entities), array_values($entities), $data);
4601      }
4602   
4603      if (strpos($data, '"') !== false)
4604      {
4605          if (strpos($data, "'") !== false)
4606          {
4607              $data = '"' . str_replace('"', '&quot;', $data) . '"';
4608          }
4609          else
4610          {
4611              $data = "'" . $data . "'";
4612          }
4613      }
4614      else
4615      {
4616          $data = '"' . $data . '"';
4617      }
4618   
4619      return $data;
4620  }
4621   
4622  /**
4623  * Converts query string (GET) parameters in request into hidden fields.
4624  *
4625  * Useful for forwarding GET parameters when submitting forms with GET method.
4626  *
4627  * It is possible to omit some of the GET parameters, which is useful if
4628  * they are specified in the form being submitted.
4629  *
4630  * sid is always omitted.
4631  *
4632  * @param \phpbb\request\request $request Request object
4633  * @param array $exclude A list of variable names that should not be forwarded
4634  * @return string HTML with hidden fields
4635  */
4636  function phpbb_build_hidden_fields_for_query_params($request, $exclude = null)
4637  {
4638      $names = $request->variable_names(\phpbb\request\request_interface::GET);
4639      $hidden = '';
4640      foreach ($names as $name)
4641      {
4642          // Sessions are dealt with elsewhere, omit sid always
4643          if ($name == 'sid')
4644          {
4645              continue;
4646          }
4647   
4648          // Omit any additional parameters requested
4649          if (!empty($exclude) && in_array($name, $exclude))
4650          {
4651              continue;
4652          }
4653   
4654          $escaped_name = phpbb_quoteattr($name);
4655   
4656          // Note: we might retrieve the variable from POST or cookies
4657          // here. To avoid exposing cookies, skip variables that are
4658          // overwritten somewhere other than GET entirely.
4659          $value = $request->variable($name, '', true);
4660          $get_value = $request->variable($name, '', true, \phpbb\request\request_interface::GET);
4661          if ($value === $get_value)
4662          {
4663              $escaped_value = phpbb_quoteattr($value);
4664              $hidden .= "<input type='hidden' name=$escaped_name value=$escaped_value />";
4665          }
4666      }
4667      return $hidden;
4668  }
4669   
4670  /**
4671  * Get user avatar
4672  *
4673  * @param array $user_row Row from the users table
4674  * @param string $alt Optional language string for alt tag within image, can be a language key or text
4675  * @param bool $ignore_config Ignores the config-setting, to be still able to view the avatar in the UCP
4676  *
4677  * @return string Avatar html
4678  */
4679  function phpbb_get_user_avatar($user_row, $alt = 'USER_AVATAR', $ignore_config = false)
4680  {
4681      $row = \phpbb\avatar\manager::clean_row($user_row, 'user');
4682      return phpbb_get_avatar($row, $alt, $ignore_config);
4683  }
4684   
4685  /**
4686  * Get group avatar
4687  *
4688  * @param array $group_row Row from the groups table
4689  * @param string $alt Optional language string for alt tag within image, can be a language key or text
4690  * @param bool $ignore_config Ignores the config-setting, to be still able to view the avatar in the UCP
4691  *
4692  * @return string Avatar html
4693  */
4694  function phpbb_get_group_avatar($user_row, $alt = 'GROUP_AVATAR', $ignore_config = false)
4695  {
4696      $row = \phpbb\avatar\manager::clean_row($user_row, 'group');
4697      return phpbb_get_avatar($row, $alt, $ignore_config);
4698  }
4699   
4700  /**
4701  * Get avatar
4702  *
4703  * @param array $row Row cleaned by \phpbb\avatar\manager::clean_row
4704  * @param string $alt Optional language string for alt tag within image, can be a language key or text
4705  * @param bool $ignore_config Ignores the config-setting, to be still able to view the avatar in the UCP
4706  *
4707  * @return string Avatar html
4708  */
4709  function phpbb_get_avatar($row, $alt, $ignore_config = false)
4710  {
4711      global $user, $config, $cache, $phpbb_root_path, $phpEx;
4712      global $request;
4713      global $phpbb_container;
4714   
4715      if (!$config['allow_avatar'] && !$ignore_config)
4716      {
4717          return '';
4718      }
4719   
4720      $avatar_data = array(
4721          'src' => $row['avatar'],
4722          'width' => $row['avatar_width'],
4723          'height' => $row['avatar_height'],
4724      );
4725   
4726      $phpbb_avatar_manager = $phpbb_container->get('avatar.manager');
4727      $driver = $phpbb_avatar_manager->get_driver($row['avatar_type'], $ignore_config);
4728      $html = '';
4729   
4730      if ($driver)
4731      {
4732          $html = $driver->get_custom_html($user, $row, $alt);
4733          if (!empty($html))
4734          {
4735              return $html;
4736          }
4737   
4738          $avatar_data = $driver->get_data($row, $ignore_config);
4739      }
4740      else
4741      {
4742          $avatar_data['src'] = '';
4743      }
4744   
4745      if (!empty($avatar_data['src']))
4746      {
4747          $html = '<img src="' . $avatar_data['src'] . '" ' .
4748              ($avatar_data['width'] ? ('width="' . $avatar_data['width'] . '" ') : '') .
4749              ($avatar_data['height'] ? ('height="' . $avatar_data['height'] . '" ') : '') .
4750              'alt="' . ((!empty($user->lang[$alt])) ? $user->lang[$alt] : $alt) . '" />';
4751      }
4752   
4753      return $html;
4754  }
4755   
4756  /**
4757  * Generate page header
4758  */
4759  function page_header($page_title = '', $display_online_list = false, $item_id = 0, $item = 'forum')
4760  {
4761      global $db, $config, $template, $SID, $_SID, $_EXTRA_URL, $user, $auth, $phpEx, $phpbb_root_path;
4762      global $phpbb_dispatcher, $request, $phpbb_container, $phpbb_admin_path;
4763   
4764      if (defined('HEADER_INC'))
4765      {
4766          return;
4767      }
4768   
4769      define('HEADER_INC', true);
4770   
4771      // A listener can set this variable to `true` when it overrides this function
4772      $page_header_override = false;
4773   
4774      /**
4775      * Execute code and/or overwrite page_header()
4776      *
4777      * @event core.page_header
4778      * @var    string    page_title            Page title
4779      * @var    bool    display_online_list        Do we display online users list
4780      * @var    string    item                Restrict online users to a certain
4781      *                                    session item, e.g. forum for
4782      *                                    session_forum_id
4783      * @var    int        item_id                Restrict online users to item id
4784      * @var    bool    page_header_override    Shall we return instead of running
4785      *                                        the rest of page_header()
4786      * @since 3.1.0-a1
4787      */
4788      $vars = array('page_title', 'display_online_list', 'item_id', 'item', 'page_header_override');
4789      extract($phpbb_dispatcher->trigger_event('core.page_header', compact($vars)));
4790   
4791      if ($page_header_override)
4792      {
4793          return;
4794      }
4795   
4796      // gzip_compression
4797      if ($config['gzip_compress'])
4798      {
4799          // to avoid partially compressed output resulting in blank pages in
4800          // the browser or error messages, compression is disabled in a few cases:
4801          //
4802          // 1) if headers have already been sent, this indicates plaintext output
4803          //    has been started so further content must not be compressed
4804          // 2) the length of the current output buffer is non-zero. This means
4805          //    there is already some uncompressed content in this output buffer
4806          //    so further output must not be compressed
4807          // 3) if more than one level of output buffering is used because we
4808          //    cannot test all output buffer level content lengths. One level
4809          //    could be caused by php.ini output_buffering. Anything
4810          //    beyond that is manual, so the code wrapping phpBB in output buffering
4811          //    can easily compress the output itself.
4812          //
4813          if (@extension_loaded('zlib') && !headers_sent() && ob_get_level() <= 1 && ob_get_length() == 0)
4814          {
4815              ob_start('ob_gzhandler');
4816          }
4817      }
4818   
4819      // Generate logged in/logged out status
4820      if ($user->data['user_id'] != ANONYMOUS)
4821      {
4822          $u_login_logout = append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=logout', true, $user->session_id);
4823          $l_login_logout = $user->lang['LOGOUT'];
4824      }
4825      else
4826      {
4827          $u_login_logout = append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=login');
4828          $l_login_logout = $user->lang['LOGIN'];
4829      }
4830   
4831      // Last visit date/time
4832      $s_last_visit = ($user->data['user_id'] != ANONYMOUS) ? $user->format_date($user->data['session_last_visit']) : '';
4833   
4834      // Get users online list ... if required
4835      $l_online_users = $online_userlist = $l_online_record = $l_online_time = '';
4836   
4837      if ($config['load_online'] && $config['load_online_time'] && $display_online_list)
4838      {
4839          /**
4840          * Load online data:
4841          * For obtaining another session column use $item and $item_id in the function-parameter, whereby the column is session_{$item}_id.
4842          */
4843          $item_id = max($item_id, 0);
4844   
4845          $online_users = obtain_users_online($item_id, $item);
4846          $user_online_strings = obtain_users_online_string($online_users, $item_id, $item);
4847   
4848          $l_online_users = $user_online_strings['l_online_users'];
4849          $online_userlist = $user_online_strings['online_userlist'];
4850          $total_online_users = $online_users['total_online'];
4851   
4852          if ($total_online_users > $config['record_online_users'])
4853          {
4854              set_config('record_online_users', $total_online_users, true);
4855              set_config('record_online_date', time(), true);
4856          }
4857   
4858          $l_online_record = $user->lang('RECORD_ONLINE_USERS', (int) $config['record_online_users'], $user->format_date($config['record_online_date'], false, true));
4859   
4860          $l_online_time = $user->lang('VIEW_ONLINE_TIMES', (int) $config['load_online_time']);
4861      }
4862   
4863      $s_privmsg_new = false;
4864   
4865      // Check for new private messages if user is logged in
4866      if (!empty($user->data['is_registered']))
4867      {
4868          if ($user->data['user_new_privmsg'])
4869          {
4870              if (!$user->data['user_last_privmsg'] || $user->data['user_last_privmsg'] > $user->data['session_last_visit'])
4871              {
4872                  $sql = 'UPDATE ' . USERS_TABLE . '
4873                      SET user_last_privmsg = ' . $user->data['session_last_visit'] . '
4874                      WHERE user_id = ' . $user->data['user_id'];
4875                  $db->sql_query($sql);
4876   
4877                  $s_privmsg_new = true;
4878              }
4879              else
4880              {
4881                  $s_privmsg_new = false;
4882              }
4883          }
4884          else
4885          {
4886              $s_privmsg_new = false;
4887          }
4888      }
4889   
4890      $forum_id = request_var('f', 0);
4891      $topic_id = request_var('t', 0);
4892   
4893      $s_feed_news = false;
4894   
4895      // Get option for news
4896      if ($config['feed_enable'])
4897      {
4898          $sql = 'SELECT forum_id
4899              FROM ' . FORUMS_TABLE . '
4900              WHERE ' . $db->sql_bit_and('forum_options', FORUM_OPTION_FEED_NEWS, '<> 0');
4901          $result = $db->sql_query_limit($sql, 1, 0, 600);
4902          $s_feed_news = (int) $db->sql_fetchfield('forum_id');
4903          $db->sql_freeresult($result);
4904      }
4905   
4906      // Determine board url - we may need it later
4907      $board_url = generate_board_url() . '/';
4908      // This path is sent with the base template paths in the assign_vars()
4909      // call below. We need to correct it in case we are accessing from a
4910      // controller because the web paths will be incorrect otherwise.
4911      $phpbb_path_helper = $phpbb_container->get('path_helper');
4912      $corrected_path = $phpbb_path_helper->get_web_root_path();
4913      $web_path = (defined('PHPBB_USE_BOARD_URL_PATH') && PHPBB_USE_BOARD_URL_PATH) ? $board_url : $corrected_path;
4914   
4915      // Send a proper content-language to the output
4916      $user_lang = $user->lang['USER_LANG'];
4917      if (strpos($user_lang, '-x-') !== false)
4918      {
4919          $user_lang = substr($user_lang, 0, strpos($user_lang, '-x-'));
4920      }
4921   
4922      $s_search_hidden_fields = array();
4923      if ($_SID)
4924      {
4925          $s_search_hidden_fields['sid'] = $_SID;
4926      }
4927   
4928      if (!empty($_EXTRA_URL))
4929      {
4930          foreach ($_EXTRA_URL as $url_param)
4931          {
4932              $url_param = explode('=', $url_param, 2);
4933              $s_search_hidden_fields[$url_param[0]] = $url_param[1];
4934          }
4935      }
4936   
4937      $dt = $user->create_datetime();
4938      $timezone_offset = $user->lang(array('timezones', 'UTC_OFFSET'), phpbb_format_timezone_offset($dt->getOffset()));
4939      $timezone_name = $user->timezone->getName();
4940      if (isset($user->lang['timezones'][$timezone_name]))
4941      {
4942          $timezone_name = $user->lang['timezones'][$timezone_name];
4943      }
4944   
4945      // Output the notifications
4946      $notifications = false;
4947      if ($config['load_notifications'] && $user->data['user_id'] != ANONYMOUS && $user->data['user_type'] != USER_IGNORE)
4948      {
4949          $phpbb_notifications = $phpbb_container->get('notification_manager');
4950   
4951          $notifications = $phpbb_notifications->load_notifications(array(
4952              'all_unread'    => true,
4953              'limit'            => 5,
4954          ));
4955   
4956          foreach ($notifications['notifications'] as $notification)
4957          {
4958              $template->assign_block_vars('notifications', $notification->prepare_for_display());
4959          }
4960      }
4961   
4962      $notification_mark_hash = generate_link_hash('mark_all_notifications_read');
4963   
4964      // The following assigns all _common_ variables that may be used at any point in a template.
4965      $template->assign_vars(array(
4966          'SITENAME'                        => $config['sitename'],
4967          'SITE_DESCRIPTION'                => $config['site_desc'],
4968          'PAGE_TITLE'                    => $page_title,
4969          'SCRIPT_NAME'                    => str_replace('.' . $phpEx, '', $user->page['page_name']),
4970          'LAST_VISIT_DATE'                => sprintf($user->lang['YOU_LAST_VISIT'], $s_last_visit),
4971          'LAST_VISIT_YOU'                => $s_last_visit,
4972          'CURRENT_TIME'                    => sprintf($user->lang['CURRENT_TIME'], $user->format_date(time(), false, true)),
4973          'TOTAL_USERS_ONLINE'            => $l_online_users,
4974          'LOGGED_IN_USER_LIST'            => $online_userlist,
4975          'RECORD_USERS'                    => $l_online_record,
4976   
4977          'PRIVATE_MESSAGE_COUNT'            => (!empty($user->data['user_unread_privmsg'])) ? $user->data['user_unread_privmsg'] : 0,
4978          'CURRENT_USER_AVATAR'            => phpbb_get_user_avatar($user->data),
4979          'CURRENT_USERNAME_SIMPLE'        => get_username_string('no_profile', $user->data['user_id'], $user->data['username'], $user->data['user_colour']),
4980          'CURRENT_USERNAME_FULL'            => get_username_string('full', $user->data['user_id'], $user->data['username'], $user->data['user_colour']),
4981          'UNREAD_NOTIFICATIONS_COUNT'    => ($notifications !== false) ? $notifications['unread_count'] : '',
4982          'NOTIFICATIONS_COUNT'            => ($notifications !== false) ? $notifications['unread_count'] : '',
4983          'U_VIEW_ALL_NOTIFICATIONS'        => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=ucp_notifications'),
4984          'U_MARK_ALL_NOTIFICATIONS'        => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=ucp_notifications&amp;mode=notification_list&amp;mark=all&amp;token=' . $notification_mark_hash),
4985          'U_NOTIFICATION_SETTINGS'        => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=ucp_notifications&amp;mode=notification_options'),
4986          'S_NOTIFICATIONS_DISPLAY'        => $config['load_notifications'],
4987   
4988          'S_USER_NEW_PRIVMSG'            => $user->data['user_new_privmsg'],
4989          'S_USER_UNREAD_PRIVMSG'            => $user->data['user_unread_privmsg'],
4990          'S_USER_NEW'                    => $user->data['user_new'],
4991   
4992          'SID'                => $SID,
4993          '_SID'                => $_SID,
4994          'SESSION_ID'        => $user->session_id,
4995          'ROOT_PATH'            => $web_path,
4996          'BOARD_URL'            => $board_url,
4997   
4998          'L_LOGIN_LOGOUT'    => $l_login_logout,
4999          'L_INDEX'            => ($config['board_index_text'] !== '') ? $config['board_index_text'] : $user->lang['FORUM_INDEX'],
5000          'L_SITE_HOME'        => ($config['site_home_text'] !== '') ? $config['site_home_text'] : $user->lang['HOME'],
5001          'L_ONLINE_EXPLAIN'    => $l_online_time,
5002   
5003          'U_PRIVATEMSGS'            => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&amp;folder=inbox'),
5004          'U_RETURN_INBOX'        => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&amp;folder=inbox'),
5005          'U_MEMBERLIST'            => append_sid("{$phpbb_root_path}memberlist.$phpEx"),
5006          'U_VIEWONLINE'            => ($auth->acl_gets('u_viewprofile', 'a_user', 'a_useradd', 'a_userdel')) ? append_sid("{$phpbb_root_path}viewonline.$phpEx") : '',
5007          'U_LOGIN_LOGOUT'        => $u_login_logout,
5008          'U_INDEX'                => append_sid("{$phpbb_root_path}index.$phpEx"),
5009          'U_SEARCH'                => append_sid("{$phpbb_root_path}search.$phpEx"),
5010          'U_SITE_HOME'            => $config['site_home_url'],
5011          'U_REGISTER'            => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=register'),
5012          'U_PROFILE'                => append_sid("{$phpbb_root_path}ucp.$phpEx"),
5013          'U_USER_PROFILE'        => get_username_string('profile', $user->data['user_id'], $user->data['username'], $user->data['user_colour']),
5014          'U_MODCP'                => append_sid("{$phpbb_root_path}mcp.$phpEx", false, true, $user->session_id),
5015          'U_FAQ'                    => append_sid("{$phpbb_root_path}faq.$phpEx"),
5016          'U_SEARCH_SELF'            => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=egosearch'),
5017          'U_SEARCH_NEW'            => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=newposts'),
5018          'U_SEARCH_UNANSWERED'    => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=unanswered'),
5019          'U_SEARCH_UNREAD'        => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=unreadposts'),
5020          'U_SEARCH_ACTIVE_TOPICS'=> append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=active_topics'),
5021          'U_DELETE_COOKIES'        => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=delete_cookies'),
5022          'U_CONTACT_US'            => ($config['contact_admin_form_enable'] && $config['email_enable']) ? append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=contactadmin') : '',
5023          'U_TEAM'                => ($user->data['user_id'] != ANONYMOUS && !$auth->acl_get('u_viewprofile')) ? '' : append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=team'),
5024          'U_TERMS_USE'            => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=terms'),
5025          'U_PRIVACY'                => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy'),
5026          'U_RESTORE_PERMISSIONS'    => ($user->data['user_perm_from'] && $auth->acl_get('a_switchperm')) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=restore_perm') : '',
5027          'U_FEED'                => generate_board_url() . "/feed.$phpEx",
5028   
5029          'S_USER_LOGGED_IN'        => ($user->data['user_id'] != ANONYMOUS) ? true : false,
5030          'S_AUTOLOGIN_ENABLED'    => ($config['allow_autologin']) ? true : false,
5031          'S_BOARD_DISABLED'        => ($config['board_disable']) ? true : false,
5032          'S_REGISTERED_USER'        => (!empty($user->data['is_registered'])) ? true : false,
5033          'S_IS_BOT'                => (!empty($user->data['is_bot'])) ? true : false,
5034          'S_USER_LANG'            => $user_lang,
5035          'S_USER_BROWSER'        => (isset($user->data['session_browser'])) ? $user->data['session_browser'] : $user->lang['UNKNOWN_BROWSER'],
5036          'S_USERNAME'            => $user->data['username'],
5037          'S_CONTENT_DIRECTION'    => $user->lang['DIRECTION'],
5038          'S_CONTENT_FLOW_BEGIN'    => ($user->lang['DIRECTION'] == 'ltr') ? 'left' : 'right',
5039          'S_CONTENT_FLOW_END'    => ($user->lang['DIRECTION'] == 'ltr') ? 'right' : 'left',
5040          'S_CONTENT_ENCODING'    => 'UTF-8',
5041          'S_TIMEZONE'            => sprintf($user->lang['ALL_TIMES'], $timezone_offset, $timezone_name),
5042          'S_DISPLAY_ONLINE_LIST'    => ($l_online_time) ? 1 : 0,
5043          'S_DISPLAY_SEARCH'        => (!$config['load_search']) ? 0 : (isset($auth) ? ($auth->acl_get('u_search') && $auth->acl_getf_global('f_search')) : 1),
5044          'S_DISPLAY_PM'            => ($config['allow_privmsg'] && !empty($user->data['is_registered']) && ($auth->acl_get('u_readpm') || $auth->acl_get('u_sendpm'))) ? true : false,
5045          'S_DISPLAY_MEMBERLIST'    => (isset($auth)) ? $auth->acl_get('u_viewprofile') : 0,
5046          'S_NEW_PM'                => ($s_privmsg_new) ? 1 : 0,
5047          'S_REGISTER_ENABLED'    => ($config['require_activation'] != USER_ACTIVATION_DISABLE) ? true : false,
5048          'S_FORUM_ID'            => $forum_id,
5049          'S_TOPIC_ID'            => $topic_id,
5050   
5051          'S_LOGIN_ACTION'        => ((!defined('ADMIN_START')) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=login') : append_sid("{$phpbb_admin_path}index.$phpEx", false, true, $user->session_id)),
5052          'S_LOGIN_REDIRECT'        => build_hidden_fields(array('redirect' => $phpbb_path_helper->remove_web_root_path(build_url()))),
5053   
5054          'S_ENABLE_FEEDS'            => ($config['feed_enable']) ? true : false,
5055          'S_ENABLE_FEEDS_OVERALL'    => ($config['feed_overall']) ? true : false,
5056          'S_ENABLE_FEEDS_FORUMS'        => ($config['feed_overall_forums']) ? true : false,
5057          'S_ENABLE_FEEDS_TOPICS'        => ($config['feed_topics_new']) ? true : false,
5058          'S_ENABLE_FEEDS_TOPICS_ACTIVE'    => ($config['feed_topics_active']) ? true : false,
5059          'S_ENABLE_FEEDS_NEWS'        => ($s_feed_news) ? true : false,
5060   
5061          'S_LOAD_UNREADS'            => ($config['load_unreads_search'] && ($config['load_anon_lastread'] || $user->data['is_registered'])) ? true : false,
5062   
5063          'S_SEARCH_HIDDEN_FIELDS'    => build_hidden_fields($s_search_hidden_fields),
5064   
5065          'T_ASSETS_VERSION'        => $config['assets_version'],
5066          'T_ASSETS_PATH'            => "{$web_path}assets",
5067          'T_THEME_PATH'            => "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/theme',
5068          'T_TEMPLATE_PATH'        => "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/template',
5069          'T_SUPER_TEMPLATE_PATH'    => "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/template',
5070          'T_IMAGES_PATH'            => "{$web_path}images/",
5071          'T_SMILIES_PATH'        => "{$web_path}{$config['smilies_path']}/",
5072          'T_AVATAR_PATH'            => "{$web_path}{$config['avatar_path']}/",
5073          'T_AVATAR_GALLERY_PATH'    => "{$web_path}{$config['avatar_gallery_path']}/",
5074          'T_ICONS_PATH'            => "{$web_path}{$config['icons_path']}/",
5075          'T_RANKS_PATH'            => "{$web_path}{$config['ranks_path']}/",
5076          'T_UPLOAD_PATH'            => "{$web_path}{$config['upload_path']}/",
5077          'T_STYLESHEET_LINK'        => "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/theme/stylesheet.css?assets_version=' . $config['assets_version'],
5078          'T_STYLESHEET_LANG_LINK'    => "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/theme/' . $user->lang_name . '/stylesheet.css?assets_version=' . $config['assets_version'],
5079          'T_JQUERY_LINK'            => !empty($config['allow_cdn']) && !empty($config['load_jquery_url']) ? $config['load_jquery_url'] : "{$web_path}assets/javascript/jquery.min.js?assets_version=" . $config['assets_version'],
5080          'S_ALLOW_CDN'            => !empty($config['allow_cdn']),
5081   
5082          'T_THEME_NAME'            => rawurlencode($user->style['style_path']),
5083          'T_THEME_LANG_NAME'        => $user->data['user_lang'],
5084          'T_TEMPLATE_NAME'        => $user->style['style_path'],
5085          'T_SUPER_TEMPLATE_NAME'    => rawurlencode((isset($user->style['style_parent_tree']) && $user->style['style_parent_tree']) ? $user->style['style_parent_tree'] : $user->style['style_path']),
5086          'T_IMAGES'                => 'images',
5087          'T_SMILIES'                => $config['smilies_path'],
5088          'T_AVATAR'                => $config['avatar_path'],
5089          'T_AVATAR_GALLERY'        => $config['avatar_gallery_path'],
5090          'T_ICONS'                => $config['icons_path'],
5091          'T_RANKS'                => $config['ranks_path'],
5092          'T_UPLOAD'                => $config['upload_path'],
5093   
5094          'SITE_LOGO_IMG'            => $user->img('site_logo'),
5095      ));
5096   
5097      // An array of http headers that phpbb will set. The following event may override these.
5098      $http_headers = array(
5099          // application/xhtml+xml not used because of IE
5100          'Content-type' => 'text/html; charset=UTF-8',
5101          'Cache-Control' => 'private, no-cache="set-cookie"',
5102          'Expires' => gmdate('D, d M Y H:i:s', time()) . ' GMT',
5103      );
5104      if (!empty($user->data['is_bot']))
5105      {
5106          // Let reverse proxies know we detected a bot.
5107          $http_headers['X-PHPBB-IS-BOT'] = 'yes';
5108      }
5109   
5110      /**
5111      * Execute code and/or overwrite _common_ template variables after they have been assigned.
5112      *
5113      * @event core.page_header_after
5114      * @var    string    page_title            Page title
5115      * @var    bool    display_online_list        Do we display online users list
5116      * @var    string    item                Restrict online users to a certain
5117      *                                    session item, e.g. forum for
5118      *                                    session_forum_id
5119      * @var    int        item_id                Restrict online users to item id
5120      * @var    array        http_headers            HTTP headers that should be set by phpbb
5121      *
5122      * @since 3.1.0-b3
5123      */
5124      $vars = array('page_title', 'display_online_list', 'item_id', 'item', 'http_headers');
5125      extract($phpbb_dispatcher->trigger_event('core.page_header_after', compact($vars)));
5126   
5127      foreach ($http_headers as $hname => $hval)
5128      {
5129          header((string) $hname . ': ' . (string) $hval);
5130      }
5131   
5132      return;
5133  }
5134   
5135  /**
5136  * Check and display the SQL report if requested.
5137  *
5138  * @param \phpbb\request\request_interface        $request    Request object
5139  * @param \phpbb\auth\auth                        $auth        Auth object
5140  * @param \phpbb\db\driver\driver_interface        $db            Database connection
5141  */
5142  function phpbb_check_and_display_sql_report(\phpbb\request\request_interface $request, \phpbb\auth\auth $auth, \phpbb\db\driver\driver_interface $db)
5143  {
5144      if ($request->variable('explain', false) && $auth->acl_get('a_') && defined('DEBUG'))
5145      {
5146          $db->sql_report('display');
5147      }
5148  }
5149   
5150  /**
5151  * Generate the debug output string
5152  *
5153  * @param \phpbb\db\driver\driver_interface    $db            Database connection
5154  * @param \phpbb\config\config                $config        Config object
5155  * @param \phpbb\auth\auth                    $auth        Auth object
5156  * @param \phpbb\user                        $user        User object
5157  * @param \phpbb\event\dispatcher_interface    $phpbb_dispatcher    Event dispatcher
5158  * @return string
5159  */
5160  function phpbb_generate_debug_output(\phpbb\db\driver\driver_interface $db, \phpbb\config\config $config, \phpbb\auth\auth $auth, \phpbb\user $user, \phpbb\event\dispatcher_interface $phpbb_dispatcher)
5161  {
5162      $debug_info = array();
5163   
5164      // Output page creation time
5165      if (defined('PHPBB_DISPLAY_LOAD_TIME'))
5166      {
5167          if (isset($GLOBALS['starttime']))
5168          {
5169              $totaltime = microtime(true) - $GLOBALS['starttime'];
5170              $debug_info[] = sprintf('<abbr title="SQL time: %.3fs / PHP time: %.3fs">Time: %.3fs</abbr>', $db->get_sql_time(), ($totaltime - $db->get_sql_time()), $totaltime);
5171          }
5172   
5173          $debug_info[] = sprintf('<abbr title="Cached: %d">Queries: %d</abbr>', $db->sql_num_queries(true), $db->sql_num_queries());
5174   
5175          $memory_usage = memory_get_peak_usage();
5176          if ($memory_usage)
5177          {
5178              $memory_usage = get_formatted_filesize($memory_usage);
5179   
5180              $debug_info[] = 'Peak Memory Usage: ' . $memory_usage;
5181          }
5182      }
5183   
5184      if (defined('DEBUG'))
5185      {
5186          $debug_info[] = 'GZIP: ' . (($config['gzip_compress'] && @extension_loaded('zlib')) ? 'On' : 'Off');
5187   
5188          if ($user->load)
5189          {
5190              $debug_info[] = 'Load: ' . $user->load;
5191          }
5192   
5193          if ($auth->acl_get('a_'))
5194          {
5195              $debug_info[] = '<a href="' . build_url() . '&amp;explain=1">SQL Explain</a>';
5196          }
5197      }
5198   
5199      /**
5200      * Modify debug output information
5201      *
5202      * @event core.phpbb_generate_debug_output
5203      * @var    array    debug_info        Array of strings with debug information
5204      *
5205      * @since 3.1.0-RC3
5206      */
5207      $vars = array('debug_info');
5208      extract($phpbb_dispatcher->trigger_event('core.phpbb_generate_debug_output', compact($vars)));
5209   
5210      return implode(' | ', $debug_info);
5211  }
5212   
5213  /**
5214  * Generate page footer
5215  *
5216  * @param bool $run_cron Whether or not to run the cron
5217  * @param bool $display_template Whether or not to display the template
5218  * @param bool $exit_handler Whether or not to run the exit_handler()
5219  */
5220  function page_footer($run_cron = true, $display_template = true, $exit_handler = true)
5221  {
5222      global $db, $config, $template, $user, $auth, $cache, $starttime, $phpbb_root_path, $phpEx;
5223      global $request, $phpbb_dispatcher, $phpbb_admin_path;
5224   
5225      // A listener can set this variable to `true` when it overrides this function
5226      $page_footer_override = false;
5227   
5228      /**
5229      * Execute code and/or overwrite page_footer()
5230      *
5231      * @event core.page_footer
5232      * @var    bool    run_cron            Shall we run cron tasks
5233      * @var    bool    page_footer_override    Shall we return instead of running
5234      *                                        the rest of page_footer()
5235      * @since 3.1.0-a1
5236      */
5237      $vars = array('run_cron', 'page_footer_override');
5238      extract($phpbb_dispatcher->trigger_event('core.page_footer', compact($vars)));
5239   
5240      if ($page_footer_override)
5241      {
5242          return;
5243      }
5244   
5245      phpbb_check_and_display_sql_report($request, $auth, $db);
5246   
5247      $template->assign_vars(array(
5248          'DEBUG_OUTPUT'            => phpbb_generate_debug_output($db, $config, $auth, $user, $phpbb_dispatcher),
5249          'TRANSLATION_INFO'        => (!empty($user->lang['TRANSLATION_INFO'])) ? $user->lang['TRANSLATION_INFO'] : '',
5250          'CREDIT_LINE'            => $user->lang('POWERED_BY', '<a href="https://www.phpbb.com/">phpBB</a>&reg; Forum Software &copy; phpBB Limited'),
5251   
5252          'U_ACP' => ($auth->acl_get('a_') && !empty($user->data['is_registered'])) ? append_sid("{$phpbb_admin_path}index.$phpEx", false, true, $user->session_id) : '')
5253      );
5254   
5255      // Call cron-type script
5256      $call_cron = false;
5257      if (!defined('IN_CRON') && !$config['use_system_cron'] && $run_cron && !$config['board_disable'] && !$user->data['is_bot'] && !$cache->get('_cron.lock_check'))
5258      {
5259          $call_cron = true;
5260          $time_now = (!empty($user->time_now) && is_int($user->time_now)) ? $user->time_now : time();
5261   
5262          // Any old lock present?
5263          if (!empty($config['cron_lock']))
5264          {
5265              $cron_time = explode(' ', $config['cron_lock']);
5266   
5267              // If 1 hour lock is present we do not call cron.php
5268              if ($cron_time[0] + 3600 >= $time_now)
5269              {
5270                  $call_cron = false;
5271              }
5272          }
5273      }
5274   
5275      // Call cron job?
5276      if ($call_cron)
5277      {
5278          global $phpbb_container;
5279          $cron = $phpbb_container->get('cron.manager');
5280          $task = $cron->find_one_ready_task();
5281   
5282          if ($task)
5283          {
5284              $url = $task->get_url();
5285              $template->assign_var('RUN_CRON_TASK', '<img src="' . $url . '" width="1" height="1" alt="cron" />');
5286          }
5287          else
5288          {
5289              $cache->put('_cron.lock_check', true, 60);
5290          }
5291      }
5292   
5293      /**
5294      * Execute code and/or modify output before displaying the template.
5295      *
5296      * @event core.page_footer_after
5297      * @var    bool display_template    Whether or not to display the template
5298      * @var    bool exit_handler        Whether or not to run the exit_handler()
5299      *
5300      * @since 3.1.0-RC5
5301      */
5302      $vars = array('display_template', 'exit_handler');
5303      extract($phpbb_dispatcher->trigger_event('core.page_footer_after', compact($vars)));
5304   
5305      if ($display_template)
5306      {
5307          $template->display('body');
5308      }
5309   
5310      garbage_collection();
5311   
5312      if ($exit_handler)
5313      {
5314          exit_handler();
5315      }
5316  }
5317   
5318  /**
5319  * Closing the cache object and the database
5320  * Cool function name, eh? We might want to add operations to it later
5321  */
5322  function garbage_collection()
5323  {
5324      global $cache, $db;
5325      global $phpbb_dispatcher;
5326   
5327      if (!empty($phpbb_dispatcher))
5328      {
5329          /**
5330          * Unload some objects, to free some memory, before we finish our task
5331          *
5332          * @event core.garbage_collection
5333          * @since 3.1.0-a1
5334          */
5335          $phpbb_dispatcher->dispatch('core.garbage_collection');
5336      }
5337   
5338      // Unload cache, must be done before the DB connection if closed
5339      if (!empty($cache))
5340      {
5341          $cache->unload();
5342      }
5343   
5344      // Close our DB connection.
5345      if (!empty($db))
5346      {
5347          $db->sql_close();
5348      }
5349  }
5350   
5351  /**
5352  * Handler for exit calls in phpBB.
5353  * This function supports hooks.
5354  *
5355  * Note: This function is called after the template has been outputted.
5356  */
5357  function exit_handler()
5358  {
5359      global $phpbb_hook, $config;
5360   
5361      if (!empty($phpbb_hook) && $phpbb_hook->call_hook(__FUNCTION__))
5362      {
5363          if ($phpbb_hook->hook_return(__FUNCTION__))
5364          {
5365              return $phpbb_hook->hook_return_result(__FUNCTION__);
5366          }
5367      }
5368   
5369      // As a pre-caution... some setups display a blank page if the flush() is not there.
5370      (ob_get_level() > 0) ? @ob_flush() : @flush();
5371   
5372      exit;
5373  }
5374   
5375  /**
5376  * Handler for init calls in phpBB. This function is called in \phpbb\user::setup();
5377  * This function supports hooks.
5378  */
5379  function phpbb_user_session_handler()
5380  {
5381      global $phpbb_hook;
5382   
5383      if (!empty($phpbb_hook) && $phpbb_hook->call_hook(__FUNCTION__))
5384      {
5385          if ($phpbb_hook->hook_return(__FUNCTION__))
5386          {
5387              return $phpbb_hook->hook_return_result(__FUNCTION__);
5388          }
5389      }
5390   
5391      return;
5392  }
5393   
5394  /**
5395  * Check if PCRE has UTF-8 support
5396  * PHP may not be linked with the bundled PCRE lib and instead with an older version
5397  *
5398  * @return bool    Returns true if PCRE (the regular expressions library) supports UTF-8 encoding
5399  */
5400  function phpbb_pcre_utf8_support()
5401  {
5402      static $utf8_pcre_properties = null;
5403      if (is_null($utf8_pcre_properties))
5404      {
5405          $utf8_pcre_properties = (@preg_match('/\p{L}/u', 'a') !== false);
5406      }
5407      return $utf8_pcre_properties;
5408  }
5409   
5410  /**
5411  * Casts a numeric string $input to an appropriate numeric type (i.e. integer or float)
5412  *
5413  * @param string $input        A numeric string.
5414  *
5415  * @return int|float            Integer $input if $input fits integer,
5416  *                            float $input otherwise.
5417  */
5418  function phpbb_to_numeric($input)
5419  {
5420      return ($input > PHP_INT_MAX) ? (float) $input : (int) $input;
5421  }
5422   
5423  /**
5424  * Get the board contact details (e.g. for emails)
5425  *
5426  * @param \phpbb\config\config    $config
5427  * @param string                    $phpEx
5428  * @return string
5429  */
5430  function phpbb_get_board_contact(\phpbb\config\config $config, $phpEx)
5431  {
5432      if ($config['contact_admin_form_enable'])
5433      {
5434          return generate_board_url() . '/memberlist.' . $phpEx . '?mode=contactadmin';
5435      }
5436      else
5437      {
5438          return $config['board_contact'];
5439      }
5440  }
5441   
5442  /**
5443  * Get a clickable board contact details link
5444  *
5445  * @param \phpbb\config\config    $config
5446  * @param string                    $phpbb_root_path
5447  * @param string                    $phpEx
5448  * @return string
5449  */
5450  function phpbb_get_board_contact_link(\phpbb\config\config $config, $phpbb_root_path, $phpEx)
5451  {
5452      if ($config['contact_admin_form_enable'] && $config['email_enable'])
5453      {
5454          return append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=contactadmin');
5455      }
5456      else
5457      {
5458          return 'mailto:' . htmlspecialchars($config['board_contact']);
5459      }
5460  }
5461