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