[ Index ]

PHP Cross Reference of phpBB-3.1.10-deutsch

title

Body

[close]

/includes/ -> functions.php (source)

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