[ Index ]

PHP Cross Reference of phpBB-3.1.12-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  
2237      // Forcing server vars is the only way to specify/override the protocol
2238      if ($config['force_server_vars'] || !$server_name)
2239      {
2240          $server_protocol = ($config['server_protocol']) ? $config['server_protocol'] : (($config['cookie_secure']) ? 'https://' : 'http://');
2241          $server_name = $config['server_name'];
2242          $server_port = (int) $config['server_port'];
2243          $script_path = $config['script_path'];
2244  
2245          $url = $server_protocol . $server_name;
2246          $cookie_secure = $config['cookie_secure'];
2247      }
2248      else
2249      {
2250          $server_port = $request->server('SERVER_PORT', 0);
2251          $forwarded_proto = $request->server('HTTP_X_FORWARDED_PROTO');
2252  
2253          if (!empty($forwarded_proto) && $forwarded_proto === 'https')
2254          {
2255              $server_port = 443;
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  * @param string  $template_variable_suffix A string that is appended to the name of the template variable to which the form elements are assigned
2613  */
2614  function add_form_key($form_name, $template_variable_suffix = '')
2615  {
2616      global $config, $template, $user, $phpbb_dispatcher;
2617  
2618      $now = time();
2619      $token_sid = ($user->data['user_id'] == ANONYMOUS && !empty($config['form_token_sid_guests'])) ? $user->session_id : '';
2620      $token = sha1($now . $user->data['user_form_salt'] . $form_name . $token_sid);
2621  
2622      $s_fields = build_hidden_fields(array(
2623          'creation_time' => $now,
2624          'form_token'    => $token,
2625      ));
2626  
2627      /**
2628      * Perform additional actions on creation of the form token
2629      *
2630      * @event core.add_form_key
2631      * @var    string    form_name                    The form name
2632      * @var    int        now                            Current time timestamp
2633      * @var    string    s_fields                    Generated hidden fields
2634      * @var    string    token                        Form token
2635      * @var    string    token_sid                    User session ID
2636      * @var    string    template_variable_suffix    The string that is appended to template variable name
2637      *
2638      * @since 3.1.0-RC3
2639      * @changed 3.1.11-RC1 Added template_variable_suffix
2640      */
2641      $vars = array(
2642          'form_name',
2643          'now',
2644          's_fields',
2645          'token',
2646          'token_sid',
2647          'template_variable_suffix',
2648      );
2649      extract($phpbb_dispatcher->trigger_event('core.add_form_key', compact($vars)));
2650  
2651      $template->assign_var('S_FORM_TOKEN' . $template_variable_suffix, $s_fields);
2652  }
2653  
2654  /**
2655   * Check the form key. Required for all altering actions not secured by confirm_box
2656   *
2657   * @param    string    $form_name    The name of the form; has to match the name used
2658   *                                in add_form_key, otherwise no restrictions apply
2659   * @param    int        $timespan    The maximum acceptable age for a submitted form
2660   *                                in seconds. Defaults to the config setting.
2661   * @return    bool    True, if the form key was valid, false otherwise
2662   */
2663  function check_form_key($form_name, $timespan = false)
2664  {
2665      global $config, $request, $user;
2666  
2667      if ($timespan === false)
2668      {
2669          // we enforce a minimum value of half a minute here.
2670          $timespan = ($config['form_token_lifetime'] == -1) ? -1 : max(30, $config['form_token_lifetime']);
2671      }
2672  
2673      if ($request->is_set_post('creation_time') && $request->is_set_post('form_token'))
2674      {
2675          $creation_time    = abs($request->variable('creation_time', 0));
2676          $token = $request->variable('form_token', '');
2677  
2678          $diff = time() - $creation_time;
2679  
2680          // If creation_time and the time() now is zero we can assume it was not a human doing this (the check for if ($diff)...
2681          if (defined('DEBUG_TEST') || $diff && ($diff <= $timespan || $timespan === -1))
2682          {
2683              $token_sid = ($user->data['user_id'] == ANONYMOUS && !empty($config['form_token_sid_guests'])) ? $user->session_id : '';
2684              $key = sha1($creation_time . $user->data['user_form_salt'] . $form_name . $token_sid);
2685  
2686              if ($key === $token)
2687              {
2688                  return true;
2689              }
2690          }
2691      }
2692  
2693      return false;
2694  }
2695  
2696  // Message/Login boxes
2697  
2698  /**
2699  * Build Confirm box
2700  * @param boolean $check True for checking if confirmed (without any additional parameters) and false for displaying the confirm box
2701  * @param string $title Title/Message used for confirm box.
2702  *        message text is _CONFIRM appended to title.
2703  *        If title cannot be found in user->lang a default one is displayed
2704  *        If title_CONFIRM cannot be found in user->lang the text given is used.
2705  * @param string $hidden Hidden variables
2706  * @param string $html_body Template used for confirm box
2707  * @param string $u_action Custom form action
2708  */
2709  function confirm_box($check, $title = '', $hidden = '', $html_body = 'confirm_body.html', $u_action = '')
2710  {
2711      global $user, $template, $db, $request;
2712      global $config, $phpbb_path_helper;
2713  
2714      if (isset($_POST['cancel']))
2715      {
2716          return false;
2717      }
2718  
2719      $confirm = ($user->lang['YES'] === $request->variable('confirm', '', true, \phpbb\request\request_interface::POST));
2720  
2721      if ($check && $confirm)
2722      {
2723          $user_id = request_var('confirm_uid', 0);
2724          $session_id = request_var('sess', '');
2725          $confirm_key = request_var('confirm_key', '');
2726  
2727          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'])
2728          {
2729              return false;
2730          }
2731  
2732          // Reset user_last_confirm_key
2733          $sql = 'UPDATE ' . USERS_TABLE . " SET user_last_confirm_key = ''
2734              WHERE user_id = " . $user->data['user_id'];
2735          $db->sql_query($sql);
2736  
2737          return true;
2738      }
2739      else if ($check)
2740      {
2741          return false;
2742      }
2743  
2744      $s_hidden_fields = build_hidden_fields(array(
2745          'confirm_uid'    => $user->data['user_id'],
2746          'sess'            => $user->session_id,
2747          'sid'            => $user->session_id,
2748      ));
2749  
2750      // generate activation key
2751      $confirm_key = gen_rand_string(10);
2752  
2753      if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
2754      {
2755          adm_page_header((!isset($user->lang[$title])) ? $user->lang['CONFIRM'] : $user->lang[$title]);
2756      }
2757      else
2758      {
2759          page_header((!isset($user->lang[$title])) ? $user->lang['CONFIRM'] : $user->lang[$title]);
2760      }
2761  
2762      $template->set_filenames(array(
2763          'body' => $html_body)
2764      );
2765  
2766      // If activation key already exist, we better do not re-use the key (something very strange is going on...)
2767      if (request_var('confirm_key', ''))
2768      {
2769          // This should not occur, therefore we cancel the operation to safe the user
2770          return false;
2771      }
2772  
2773      // re-add sid / transform & to &amp; for user->page (user->page is always using &)
2774      $use_page = ($u_action) ? $u_action : str_replace('&', '&amp;', $user->page['page']);
2775      $u_action = reapply_sid($phpbb_path_helper->get_valid_page($use_page, $config['enable_mod_rewrite']));
2776      $u_action .= ((strpos($u_action, '?') === false) ? '?' : '&amp;') . 'confirm_key=' . $confirm_key;
2777  
2778      $template->assign_vars(array(
2779          'MESSAGE_TITLE'        => (!isset($user->lang[$title])) ? $user->lang['CONFIRM'] : $user->lang($title, 1),
2780          'MESSAGE_TEXT'        => (!isset($user->lang[$title . '_CONFIRM'])) ? $title : $user->lang[$title . '_CONFIRM'],
2781  
2782          'YES_VALUE'            => $user->lang['YES'],
2783          'S_CONFIRM_ACTION'    => $u_action,
2784          'S_HIDDEN_FIELDS'    => $hidden . $s_hidden_fields,
2785          'S_AJAX_REQUEST'    => $request->is_ajax(),
2786      ));
2787  
2788      $sql = 'UPDATE ' . USERS_TABLE . " SET user_last_confirm_key = '" . $db->sql_escape($confirm_key) . "'
2789          WHERE user_id = " . $user->data['user_id'];
2790      $db->sql_query($sql);
2791  
2792      if ($request->is_ajax())
2793      {
2794          $u_action .= '&confirm_uid=' . $user->data['user_id'] . '&sess=' . $user->session_id . '&sid=' . $user->session_id;
2795          $json_response = new \phpbb\json_response;
2796          $json_response->send(array(
2797              'MESSAGE_BODY'        => $template->assign_display('body'),
2798              'MESSAGE_TITLE'        => (!isset($user->lang[$title])) ? $user->lang['CONFIRM'] : $user->lang[$title],
2799              'MESSAGE_TEXT'        => (!isset($user->lang[$title . '_CONFIRM'])) ? $title : $user->lang[$title . '_CONFIRM'],
2800  
2801              'YES_VALUE'            => $user->lang['YES'],
2802              'S_CONFIRM_ACTION'    => str_replace('&amp;', '&', $u_action), //inefficient, rewrite whole function
2803              'S_HIDDEN_FIELDS'    => $hidden . $s_hidden_fields
2804          ));
2805      }
2806  
2807      if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
2808      {
2809          adm_page_footer();
2810      }
2811      else
2812      {
2813          page_footer();
2814      }
2815  }
2816  
2817  /**
2818  * Generate login box or verify password
2819  */
2820  function login_box($redirect = '', $l_explain = '', $l_success = '', $admin = false, $s_display = true)
2821  {
2822      global $db, $user, $template, $auth, $phpEx, $phpbb_root_path, $config;
2823      global $request, $phpbb_container, $phpbb_dispatcher;
2824  
2825      $err = '';
2826  
2827      // Make sure user->setup() has been called
2828      if (empty($user->lang))
2829      {
2830          $user->setup();
2831      }
2832  
2833      /**
2834       * This event allows an extension to modify the login process
2835       *
2836       * @event core.login_box_before
2837       * @var string    redirect    Redirect string
2838       * @var string    l_explain    Explain language string
2839       * @var string    l_success    Success language string
2840       * @var    bool    admin        Is admin?
2841       * @var bool    s_display    Display full login form?
2842       * @var string    err            Error string
2843       * @since 3.1.9-RC1
2844       */
2845      $vars = array('redirect', 'l_explain', 'l_success', 'admin', 's_display', 'err');
2846      extract($phpbb_dispatcher->trigger_event('core.login_box_before', compact($vars)));
2847  
2848      // Print out error if user tries to authenticate as an administrator without having the privileges...
2849      if ($admin && !$auth->acl_get('a_'))
2850      {
2851          // Not authd
2852          // anonymous/inactive users are never able to go to the ACP even if they have the relevant permissions
2853          if ($user->data['is_registered'])
2854          {
2855              add_log('admin', 'LOG_ADMIN_AUTH_FAIL');
2856          }
2857          trigger_error('NO_AUTH_ADMIN');
2858      }
2859  
2860      if (empty($err) && ($request->is_set_post('login') || ($request->is_set('login') && $request->variable('login', '') == 'external')))
2861      {
2862          // Get credential
2863          if ($admin)
2864          {
2865              $credential = request_var('credential', '');
2866  
2867              if (strspn($credential, 'abcdef0123456789') !== strlen($credential) || strlen($credential) != 32)
2868              {
2869                  if ($user->data['is_registered'])
2870                  {
2871                      add_log('admin', 'LOG_ADMIN_AUTH_FAIL');
2872                  }
2873                  trigger_error('NO_AUTH_ADMIN');
2874              }
2875  
2876              $password    = $request->untrimmed_variable('password_' . $credential, '', true);
2877          }
2878          else
2879          {
2880              $password    = $request->untrimmed_variable('password', '', true);
2881          }
2882  
2883          $username    = request_var('username', '', true);
2884          $autologin    = $request->is_set_post('autologin');
2885          $viewonline = (int) !$request->is_set_post('viewonline');
2886          $admin         = ($admin) ? 1 : 0;
2887          $viewonline = ($admin) ? $user->data['session_viewonline'] : $viewonline;
2888  
2889          // Check if the supplied username is equal to the one stored within the database if re-authenticating
2890          if ($admin && utf8_clean_string($username) != utf8_clean_string($user->data['username']))
2891          {
2892              // We log the attempt to use a different username...
2893              add_log('admin', 'LOG_ADMIN_AUTH_FAIL');
2894              trigger_error('NO_AUTH_ADMIN_USER_DIFFER');
2895          }
2896  
2897          // If authentication is successful we redirect user to previous page
2898          $result = $auth->login($username, $password, $autologin, $viewonline, $admin);
2899  
2900          // If admin authentication and login, we will log if it was a success or not...
2901          // We also break the operation on the first non-success login - it could be argued that the user already knows
2902          if ($admin)
2903          {
2904              if ($result['status'] == LOGIN_SUCCESS)
2905              {
2906                  add_log('admin', 'LOG_ADMIN_AUTH_SUCCESS');
2907              }
2908              else
2909              {
2910                  // Only log the failed attempt if a real user tried to.
2911                  // anonymous/inactive users are never able to go to the ACP even if they have the relevant permissions
2912                  if ($user->data['is_registered'])
2913                  {
2914                      add_log('admin', 'LOG_ADMIN_AUTH_FAIL');
2915                  }
2916              }
2917          }
2918  
2919          // The result parameter is always an array, holding the relevant information...
2920          if ($result['status'] == LOGIN_SUCCESS)
2921          {
2922              $redirect = request_var('redirect', "{$phpbb_root_path}index.$phpEx");
2923  
2924              /**
2925              * This event allows an extension to modify the redirection when a user successfully logs in
2926              *
2927              * @event core.login_box_redirect
2928              * @var  string    redirect    Redirect string
2929              * @var    bool    admin        Is admin?
2930              * @since 3.1.0-RC5
2931              * @changed 3.1.9-RC1 Removed undefined return variable
2932              */
2933              $vars = array('redirect', 'admin');
2934              extract($phpbb_dispatcher->trigger_event('core.login_box_redirect', compact($vars)));
2935  
2936              // append/replace SID (may change during the session for AOL users)
2937              $redirect = reapply_sid($redirect);
2938  
2939              // Special case... the user is effectively banned, but we allow founders to login
2940              if (defined('IN_CHECK_BAN') && $result['user_row']['user_type'] != USER_FOUNDER)
2941              {
2942                  return;
2943              }
2944  
2945              redirect($redirect);
2946          }
2947  
2948          // Something failed, determine what...
2949          if ($result['status'] == LOGIN_BREAK)
2950          {
2951              trigger_error($result['error_msg']);
2952          }
2953  
2954          // Special cases... determine
2955          switch ($result['status'])
2956          {
2957              case LOGIN_ERROR_PASSWORD_CONVERT:
2958                  $err = sprintf(
2959                      $user->lang[$result['error_msg']],
2960                      ($config['email_enable']) ? '<a href="' . append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=sendpassword') . '">' : '',
2961                      ($config['email_enable']) ? '</a>' : '',
2962                      '<a href="' . phpbb_get_board_contact_link($config, $phpbb_root_path, $phpEx) . '">',
2963                      '</a>'
2964                  );
2965              break;
2966  
2967              case LOGIN_ERROR_ATTEMPTS:
2968  
2969                  $captcha = $phpbb_container->get('captcha.factory')->get_instance($config['captcha_plugin']);
2970                  $captcha->init(CONFIRM_LOGIN);
2971                  // $captcha->reset();
2972  
2973                  $template->assign_vars(array(
2974                      'CAPTCHA_TEMPLATE'            => $captcha->get_template(),
2975                  ));
2976              // no break;
2977  
2978              // Username, password, etc...
2979              default:
2980                  $err = $user->lang[$result['error_msg']];
2981  
2982                  // Assign admin contact to some error messages
2983                  if ($result['error_msg'] == 'LOGIN_ERROR_USERNAME' || $result['error_msg'] == 'LOGIN_ERROR_PASSWORD')
2984                  {
2985                      $err = sprintf($user->lang[$result['error_msg']], '<a href="' . append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=contactadmin') . '">', '</a>');
2986                  }
2987  
2988              break;
2989          }
2990  
2991          /**
2992           * This event allows an extension to process when a user fails a login attempt
2993           *
2994           * @event core.login_box_failed
2995           * @var array   result      Login result data
2996           * @var string  username    User name used to login
2997           * @var string  password    Password used to login
2998           * @var string  err         Error message
2999           * @since 3.1.3-RC1
3000           */
3001          $vars = array('result', 'username', 'password', 'err');
3002          extract($phpbb_dispatcher->trigger_event('core.login_box_failed', compact($vars)));
3003      }
3004  
3005      // Assign credential for username/password pair
3006      $credential = ($admin) ? md5(unique_id()) : false;
3007  
3008      $s_hidden_fields = array(
3009          'sid'        => $user->session_id,
3010      );
3011  
3012      if ($redirect)
3013      {
3014          $s_hidden_fields['redirect'] = $redirect;
3015      }
3016  
3017      if ($admin)
3018      {
3019          $s_hidden_fields['credential'] = $credential;
3020      }
3021  
3022      $provider_collection = $phpbb_container->get('auth.provider_collection');
3023      $auth_provider = $provider_collection->get_provider();
3024  
3025      $auth_provider_data = $auth_provider->get_login_data();
3026      if ($auth_provider_data)
3027      {
3028          if (isset($auth_provider_data['VARS']))
3029          {
3030              $template->assign_vars($auth_provider_data['VARS']);
3031          }
3032  
3033          if (isset($auth_provider_data['BLOCK_VAR_NAME']))
3034          {
3035              foreach ($auth_provider_data['BLOCK_VARS'] as $block_vars)
3036              {
3037                  $template->assign_block_vars($auth_provider_data['BLOCK_VAR_NAME'], $block_vars);
3038              }
3039          }
3040  
3041          $template->assign_vars(array(
3042              'PROVIDER_TEMPLATE_FILE' => $auth_provider_data['TEMPLATE_FILE'],
3043          ));
3044      }
3045  
3046      $s_hidden_fields = build_hidden_fields($s_hidden_fields);
3047  
3048      $template->assign_vars(array(
3049          'LOGIN_ERROR'        => $err,
3050          'LOGIN_EXPLAIN'        => $l_explain,
3051  
3052          'U_SEND_PASSWORD'         => ($config['email_enable']) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=sendpassword') : '',
3053          'U_RESEND_ACTIVATION'    => ($config['require_activation'] == USER_ACTIVATION_SELF && $config['email_enable']) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=resend_act') : '',
3054          'U_TERMS_USE'            => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=terms'),
3055          'U_PRIVACY'                => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy'),
3056  
3057          'S_DISPLAY_FULL_LOGIN'    => ($s_display) ? true : false,
3058          'S_HIDDEN_FIELDS'         => $s_hidden_fields,
3059  
3060          'S_ADMIN_AUTH'            => $admin,
3061          'USERNAME'                => ($admin) ? $user->data['username'] : '',
3062  
3063          'USERNAME_CREDENTIAL'    => 'username',
3064          'PASSWORD_CREDENTIAL'    => ($admin) ? 'password_' . $credential : 'password',
3065      ));
3066  
3067      page_header($user->lang['LOGIN']);
3068  
3069      $template->set_filenames(array(
3070          'body' => 'login_body.html')
3071      );
3072      make_jumpbox(append_sid("{$phpbb_root_path}viewforum.$phpEx"));
3073  
3074      page_footer();
3075  }
3076  
3077  /**
3078  * Generate forum login box
3079  */
3080  function login_forum_box($forum_data)
3081  {
3082      global $db, $phpbb_container, $request, $template, $user, $phpbb_dispatcher;
3083  
3084      $password = $request->variable('password', '', true);
3085  
3086      $sql = 'SELECT forum_id
3087          FROM ' . FORUMS_ACCESS_TABLE . '
3088          WHERE forum_id = ' . $forum_data['forum_id'] . '
3089              AND user_id = ' . $user->data['user_id'] . "
3090              AND session_id = '" . $db->sql_escape($user->session_id) . "'";
3091      $result = $db->sql_query($sql);
3092      $row = $db->sql_fetchrow($result);
3093      $db->sql_freeresult($result);
3094  
3095      if ($row)
3096      {
3097          return true;
3098      }
3099  
3100      if ($password)
3101      {
3102          // Remove expired authorised sessions
3103          $sql = 'SELECT f.session_id
3104              FROM ' . FORUMS_ACCESS_TABLE . ' f
3105              LEFT JOIN ' . SESSIONS_TABLE . ' s ON (f.session_id = s.session_id)
3106              WHERE s.session_id IS NULL';
3107          $result = $db->sql_query($sql);
3108  
3109          if ($row = $db->sql_fetchrow($result))
3110          {
3111              $sql_in = array();
3112              do
3113              {
3114                  $sql_in[] = (string) $row['session_id'];
3115              }
3116              while ($row = $db->sql_fetchrow($result));
3117  
3118              // Remove expired sessions
3119              $sql = 'DELETE FROM ' . FORUMS_ACCESS_TABLE . '
3120                  WHERE ' . $db->sql_in_set('session_id', $sql_in);
3121              $db->sql_query($sql);
3122          }
3123          $db->sql_freeresult($result);
3124  
3125          $passwords_manager = $phpbb_container->get('passwords.manager');
3126  
3127          if ($passwords_manager->check($password, $forum_data['forum_password']))
3128          {
3129              $sql_ary = array(
3130                  'forum_id'        => (int) $forum_data['forum_id'],
3131                  'user_id'        => (int) $user->data['user_id'],
3132                  'session_id'    => (string) $user->session_id,
3133              );
3134  
3135              $db->sql_query('INSERT INTO ' . FORUMS_ACCESS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
3136  
3137              return true;
3138          }
3139  
3140          $template->assign_var('LOGIN_ERROR', $user->lang['WRONG_PASSWORD']);
3141      }
3142  
3143      /**
3144      * Performing additional actions, load additional data on forum login
3145      *
3146      * @event core.login_forum_box
3147      * @var    array    forum_data        Array with forum data
3148      * @var    string    password        Password entered
3149      * @since 3.1.0-RC3
3150      */
3151      $vars = array('forum_data', 'password');
3152      extract($phpbb_dispatcher->trigger_event('core.login_forum_box', compact($vars)));
3153  
3154      page_header($user->lang['LOGIN']);
3155  
3156      $template->assign_vars(array(
3157          'FORUM_NAME'            => isset($forum_data['forum_name']) ? $forum_data['forum_name'] : '',
3158          'S_LOGIN_ACTION'        => build_url(array('f')),
3159          'S_HIDDEN_FIELDS'        => build_hidden_fields(array('f' => $forum_data['forum_id'])))
3160      );
3161  
3162      $template->set_filenames(array(
3163          'body' => 'login_forum.html')
3164      );
3165  
3166      page_footer();
3167  }
3168  
3169  // Little helpers
3170  
3171  /**
3172  * Little helper for the build_hidden_fields function
3173  */
3174  function _build_hidden_fields($key, $value, $specialchar, $stripslashes)
3175  {
3176      $hidden_fields = '';
3177  
3178      if (!is_array($value))
3179      {
3180          $value = ($stripslashes) ? stripslashes($value) : $value;
3181          $value = ($specialchar) ? htmlspecialchars($value, ENT_COMPAT, 'UTF-8') : $value;
3182  
3183          $hidden_fields .= '<input type="hidden" name="' . $key . '" value="' . $value . '" />' . "\n";
3184      }
3185      else
3186      {
3187          foreach ($value as $_key => $_value)
3188          {
3189              $_key = ($stripslashes) ? stripslashes($_key) : $_key;
3190              $_key = ($specialchar) ? htmlspecialchars($_key, ENT_COMPAT, 'UTF-8') : $_key;
3191  
3192              $hidden_fields .= _build_hidden_fields($key . '[' . $_key . ']', $_value, $specialchar, $stripslashes);
3193          }
3194      }
3195  
3196      return $hidden_fields;
3197  }
3198  
3199  /**
3200  * Build simple hidden fields from array
3201  *
3202  * @param array $field_ary an array of values to build the hidden field from
3203  * @param bool $specialchar if true, keys and values get specialchared
3204  * @param bool $stripslashes if true, keys and values get stripslashed
3205  *
3206  * @return string the hidden fields
3207  */
3208  function build_hidden_fields($field_ary, $specialchar = false, $stripslashes = false)
3209  {
3210      $s_hidden_fields = '';
3211  
3212      foreach ($field_ary as $name => $vars)
3213      {
3214          $name = ($stripslashes) ? stripslashes($name) : $name;
3215          $name = ($specialchar) ? htmlspecialchars($name, ENT_COMPAT, 'UTF-8') : $name;
3216  
3217          $s_hidden_fields .= _build_hidden_fields($name, $vars, $specialchar, $stripslashes);
3218      }
3219  
3220      return $s_hidden_fields;
3221  }
3222  
3223  /**
3224  * Parse cfg file
3225  */
3226  function parse_cfg_file($filename, $lines = false)
3227  {
3228      $parsed_items = array();
3229  
3230      if ($lines === false)
3231      {
3232          $lines = file($filename);
3233      }
3234  
3235      foreach ($lines as $line)
3236      {
3237          $line = trim($line);
3238  
3239          if (!$line || $line[0] == '#' || ($delim_pos = strpos($line, '=')) === false)
3240          {
3241              continue;
3242          }
3243  
3244          // Determine first occurrence, since in values the equal sign is allowed
3245          $key = htmlspecialchars(strtolower(trim(substr($line, 0, $delim_pos))));
3246          $value = trim(substr($line, $delim_pos + 1));
3247  
3248          if (in_array($value, array('off', 'false', '0')))
3249          {
3250              $value = false;
3251          }
3252          else if (in_array($value, array('on', 'true', '1')))
3253          {
3254              $value = true;
3255          }
3256          else if (!trim($value))
3257          {
3258              $value = '';
3259          }
3260          else if (($value[0] == "'" && $value[sizeof($value) - 1] == "'") || ($value[0] == '"' && $value[sizeof($value) - 1] == '"'))
3261          {
3262              $value = htmlspecialchars(substr($value, 1, sizeof($value)-2));
3263          }
3264          else
3265          {
3266              $value = htmlspecialchars($value);
3267          }
3268  
3269          $parsed_items[$key] = $value;
3270      }
3271  
3272      if (isset($parsed_items['parent']) && isset($parsed_items['name']) && $parsed_items['parent'] == $parsed_items['name'])
3273      {
3274          unset($parsed_items['parent']);
3275      }
3276  
3277      return $parsed_items;
3278  }
3279  
3280  /**
3281  * Add log entry
3282  *
3283  * @param    string    $mode                The mode defines which log_type is used and from which log the entry is retrieved
3284  * @param    int        $forum_id            Mode 'mod' ONLY: forum id of the related item, NOT INCLUDED otherwise
3285  * @param    int        $topic_id            Mode 'mod' ONLY: topic id of the related item, NOT INCLUDED otherwise
3286  * @param    int        $reportee_id        Mode 'user' ONLY: user id of the reportee, NOT INCLUDED otherwise
3287  * @param    string    $log_operation        Name of the operation
3288  * @param    array    $additional_data    More arguments can be added, depending on the log_type
3289  *
3290  * @return    int|bool        Returns the log_id, if the entry was added to the database, false otherwise.
3291  *
3292  * @deprecated    Use $phpbb_log->add() instead
3293  */
3294  function add_log()
3295  {
3296      global $phpbb_log, $user;
3297  
3298      $args = func_get_args();
3299      $mode = array_shift($args);
3300  
3301      // This looks kind of dirty, but add_log has some additional data before the log_operation
3302      $additional_data = array();
3303      switch ($mode)
3304      {
3305          case 'admin':
3306          case 'critical':
3307          break;
3308          case 'mod':
3309              $additional_data['forum_id'] = array_shift($args);
3310              $additional_data['topic_id'] = array_shift($args);
3311          break;
3312          case 'user':
3313              $additional_data['reportee_id'] = array_shift($args);
3314          break;
3315      }
3316  
3317      $log_operation = array_shift($args);
3318      $additional_data = array_merge($additional_data, $args);
3319  
3320      $user_id = (empty($user->data)) ? ANONYMOUS : $user->data['user_id'];
3321      $user_ip = (empty($user->ip)) ? '' : $user->ip;
3322  
3323      return $phpbb_log->add($mode, $user_id, $user_ip, $log_operation, time(), $additional_data);
3324  }
3325  
3326  /**
3327  * Return a nicely formatted backtrace.
3328  *
3329  * Turns the array returned by debug_backtrace() into HTML markup.
3330  * Also filters out absolute paths to phpBB root.
3331  *
3332  * @return string    HTML markup
3333  */
3334  function get_backtrace()
3335  {
3336      $output = '<div style="font-family: monospace;">';
3337      $backtrace = debug_backtrace();
3338  
3339      // We skip the first one, because it only shows this file/function
3340      unset($backtrace[0]);
3341  
3342      foreach ($backtrace as $trace)
3343      {
3344          // Strip the current directory from path
3345          $trace['file'] = (empty($trace['file'])) ? '(not given by php)' : htmlspecialchars(phpbb_filter_root_path($trace['file']));
3346          $trace['line'] = (empty($trace['line'])) ? '(not given by php)' : $trace['line'];
3347  
3348          // Only show function arguments for include etc.
3349          // Other parameters may contain sensible information
3350          $argument = '';
3351          if (!empty($trace['args'][0]) && in_array($trace['function'], array('include', 'require', 'include_once', 'require_once')))
3352          {
3353              $argument = htmlspecialchars(phpbb_filter_root_path($trace['args'][0]));
3354          }
3355  
3356          $trace['class'] = (!isset($trace['class'])) ? '' : $trace['class'];
3357          $trace['type'] = (!isset($trace['type'])) ? '' : $trace['type'];
3358  
3359          $output .= '<br />';
3360          $output .= '<b>FILE:</b> ' . $trace['file'] . '<br />';
3361          $output .= '<b>LINE:</b> ' . ((!empty($trace['line'])) ? $trace['line'] : '') . '<br />';
3362  
3363          $output .= '<b>CALL:</b> ' . htmlspecialchars($trace['class'] . $trace['type'] . $trace['function']);
3364          $output .= '(' . (($argument !== '') ? "'$argument'" : '') . ')<br />';
3365      }
3366      $output .= '</div>';
3367      return $output;
3368  }
3369  
3370  /**
3371  * This function returns a regular expression pattern for commonly used expressions
3372  * Use with / as delimiter for email mode and # for url modes
3373  * mode can be: email|bbcode_htm|url|url_inline|www_url|www_url_inline|relative_url|relative_url_inline|ipv4|ipv6
3374  */
3375  function get_preg_expression($mode)
3376  {
3377      switch ($mode)
3378      {
3379          case 'email':
3380              // Regex written by James Watts and Francisco Jose Martin Moreno
3381              // http://fightingforalostcause.net/misc/2006/compare-email-regex.php
3382              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})?)';
3383          break;
3384  
3385          case 'bbcode_htm':
3386              return array(
3387                  '#<!\-\- e \-\-><a href="mailto:(.*?)">.*?</a><!\-\- e \-\->#',
3388                  '#<!\-\- l \-\-><a (?:class="[\w-]+" )?href="(.*?)(?:(&amp;|\?)sid=[0-9a-f]{32})?">.*?</a><!\-\- l \-\->#',
3389                  '#<!\-\- ([mw]) \-\-><a (?:class="[\w-]+" )?href="(.*?)">.*?</a><!\-\- \1 \-\->#',
3390                  '#<!\-\- s(.*?) \-\-><img src="\{SMILIES_PATH\}\/.*? \/><!\-\- s\1 \-\->#',
3391                  '#<!\-\- .*? \-\->#s',
3392                  '#<.*?>#s',
3393              );
3394          break;
3395  
3396          // Whoa these look impressive!
3397          // The code to generate the following two regular expressions which match valid IPv4/IPv6 addresses
3398          // can be found in the develop directory
3399          case 'ipv4':
3400              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])$#';
3401          break;
3402  
3403          case 'ipv6':
3404              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';
3405          break;
3406  
3407          case 'url':
3408              // generated with regex_idn.php file in the develop folder
3409              return "[a-z][a-z\d+\-.]*(?<!javascript):/{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})*)?";
3410          break;
3411  
3412          case 'url_http':
3413              // generated with regex_idn.php file in the develop folder
3414              return "http[s]?:/{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})*)?";
3415          break;
3416  
3417          case 'url_inline':
3418              // generated with regex_idn.php file in the develop folder
3419              return "[a-z][a-z\d+]*(?<!javascript):/{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})*)?";
3420          break;
3421  
3422          case 'www_url':
3423              // generated with regex_idn.php file in the develop folder
3424              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})*)?";
3425          break;
3426  
3427          case 'www_url_inline':
3428              // generated with regex_idn.php file in the develop folder
3429              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})*)?";
3430          break;
3431  
3432          case 'relative_url':
3433              // generated with regex_idn.php file in the develop folder
3434              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})*)?";
3435          break;
3436  
3437          case 'relative_url_inline':
3438              // generated with regex_idn.php file in the develop folder
3439              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})*)?";
3440          break;
3441  
3442          case 'table_prefix':
3443              return '#^[a-zA-Z][a-zA-Z0-9_]*$#';
3444          break;
3445  
3446          // Matches the predecing dot
3447          case 'path_remove_dot_trailing_slash':
3448              return '#^(?:(\.)?)+(?:(.+)?)+(?:([\\/\\\])$)#';
3449          break;
3450  
3451          case 'semantic_version':
3452              // Regular expression to match semantic versions by http://rgxdb.com/
3453              return '/(?<=^[Vv]|^)(?:(?<major>(?:0|[1-9](?:(?:0|[1-9])+)*))[.](?<minor>(?:0|[1-9](?:(?:0|[1-9])+)*))[.](?<patch>(?:0|[1-9](?:(?:0|[1-9])+)*))(?:-(?<prerelease>(?:(?:(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?|(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?)|(?:0|[1-9](?:(?:0|[1-9])+)*))(?:[.](?:(?:(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?|(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?)|(?:0|[1-9](?:(?:0|[1-9])+)*)))*))?(?:[+](?<build>(?:(?:(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?|(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?)|(?:(?:0|[1-9])+))(?:[.](?:(?:(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?|(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?)|(?:(?:0|[1-9])+)))*))?)$/';
3454          break;
3455      }
3456  
3457      return '';
3458  }
3459  
3460  /**
3461  * Generate regexp for naughty words censoring
3462  * Depends on whether installed PHP version supports unicode properties
3463  *
3464  * @param string    $word            word template to be replaced
3465  * @param bool    $use_unicode    whether or not to take advantage of PCRE supporting unicode
3466  *
3467  * @return string $preg_expr        regex to use with word censor
3468  */
3469  function get_censor_preg_expression($word, $use_unicode = true)
3470  {
3471      // Unescape the asterisk to simplify further conversions
3472      $word = str_replace('\*', '*', preg_quote($word, '#'));
3473  
3474      if ($use_unicode && phpbb_pcre_utf8_support())
3475      {
3476          // Replace asterisk(s) inside the pattern, at the start and at the end of it with regexes
3477          $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);
3478  
3479          // Generate the final substitution
3480          $preg_expr = '#(?<![\p{Nd}\p{L}_-])(' . $word . ')(?![\p{Nd}\p{L}_-])#iu';
3481      }
3482      else
3483      {
3484          // Replace the asterisk inside the pattern, at the start and at the end of it with regexes
3485          $word = preg_replace(array('#(?<=\S)\*+(?=\S)#iu', '#^\*+#', '#\*+$#'), array('(\x20*?\S*?)', '\S*?', '\S*?'), $word);
3486  
3487          // Generate the final substitution
3488          $preg_expr = '#(?<!\S)(' . $word . ')(?!\S)#iu';
3489      }
3490  
3491      return $preg_expr;
3492  }
3493  
3494  /**
3495  * Returns the first block of the specified IPv6 address and as many additional
3496  * ones as specified in the length paramater.
3497  * If length is zero, then an empty string is returned.
3498  * If length is greater than 3 the complete IP will be returned
3499  */
3500  function short_ipv6($ip, $length)
3501  {
3502      if ($length < 1)
3503      {
3504          return '';
3505      }
3506  
3507      // extend IPv6 addresses
3508      $blocks = substr_count($ip, ':') + 1;
3509      if ($blocks < 9)
3510      {
3511          $ip = str_replace('::', ':' . str_repeat('0000:', 9 - $blocks), $ip);
3512      }
3513      if ($ip[0] == ':')
3514      {
3515          $ip = '0000' . $ip;
3516      }
3517      if ($length < 4)
3518      {
3519          $ip = implode(':', array_slice(explode(':', $ip), 0, 1 + $length));
3520      }
3521  
3522      return $ip;
3523  }
3524  
3525  /**
3526  * Normalises an internet protocol address,
3527  * also checks whether the specified address is valid.
3528  *
3529  * IPv4 addresses are returned 'as is'.
3530  *
3531  * IPv6 addresses are normalised according to
3532  *    A Recommendation for IPv6 Address Text Representation
3533  *    http://tools.ietf.org/html/draft-ietf-6man-text-addr-representation-07
3534  *
3535  * @param string $address    IP address
3536  *
3537  * @return mixed        false if specified address is not valid,
3538  *                    string otherwise
3539  */
3540  function phpbb_ip_normalise($address)
3541  {
3542      $address = trim($address);
3543  
3544      if (empty($address) || !is_string($address))
3545      {
3546          return false;
3547      }
3548  
3549      if (preg_match(get_preg_expression('ipv4'), $address))
3550      {
3551          return $address;
3552      }
3553  
3554      return phpbb_inet_ntop(phpbb_inet_pton($address));
3555  }
3556  
3557  /**
3558  * Wrapper for inet_ntop()
3559  *
3560  * Converts a packed internet address to a human readable representation
3561  * inet_ntop() is supported by PHP since 5.1.0, since 5.3.0 also on Windows.
3562  *
3563  * @param string $in_addr    A 32bit IPv4, or 128bit IPv6 address.
3564  *
3565  * @return mixed        false on failure,
3566  *                    string otherwise
3567  */
3568  function phpbb_inet_ntop($in_addr)
3569  {
3570      $in_addr = bin2hex($in_addr);
3571  
3572      switch (strlen($in_addr))
3573      {
3574          case 8:
3575              return implode('.', array_map('hexdec', str_split($in_addr, 2)));
3576  
3577          case 32:
3578              if (substr($in_addr, 0, 24) === '00000000000000000000ffff')
3579              {
3580                  return phpbb_inet_ntop(pack('H*', substr($in_addr, 24)));
3581              }
3582  
3583              $parts = str_split($in_addr, 4);
3584              $parts = preg_replace('/^0+(?!$)/', '', $parts);
3585              $ret = implode(':', $parts);
3586  
3587              $matches = array();
3588              preg_match_all('/(?<=:|^)(?::?0){2,}/', $ret, $matches, PREG_OFFSET_CAPTURE);
3589              $matches = $matches[0];
3590  
3591              if (empty($matches))
3592              {
3593                  return $ret;
3594              }
3595  
3596              $longest_match = '';
3597              $longest_match_offset = 0;
3598              foreach ($matches as $match)
3599              {
3600                  if (strlen($match[0]) > strlen($longest_match))
3601                  {
3602                      $longest_match = $match[0];
3603                      $longest_match_offset = $match[1];
3604                  }
3605              }
3606  
3607              $ret = substr_replace($ret, '', $longest_match_offset, strlen($longest_match));
3608  
3609              if ($longest_match_offset == strlen($ret))
3610              {
3611                  $ret .= ':';
3612              }
3613  
3614              if ($longest_match_offset == 0)
3615              {
3616                  $ret = ':' . $ret;
3617              }
3618  
3619              return $ret;
3620  
3621          default:
3622              return false;
3623      }
3624  }
3625  
3626  /**
3627  * Wrapper for inet_pton()
3628  *
3629  * Converts a human readable IP address to its packed in_addr representation
3630  * inet_pton() is supported by PHP since 5.1.0, since 5.3.0 also on Windows.
3631  *
3632  * @param string $address    A human readable IPv4 or IPv6 address.
3633  *
3634  * @return mixed        false if address is invalid,
3635  *                    in_addr representation of the given address otherwise (string)
3636  */
3637  function phpbb_inet_pton($address)
3638  {
3639      $ret = '';
3640      if (preg_match(get_preg_expression('ipv4'), $address))
3641      {
3642          foreach (explode('.', $address) as $part)
3643          {
3644              $ret .= ($part <= 0xF ? '0' : '') . dechex($part);
3645          }
3646  
3647          return pack('H*', $ret);
3648      }
3649  
3650      if (preg_match(get_preg_expression('ipv6'), $address))
3651      {
3652          $parts = explode(':', $address);
3653          $missing_parts = 8 - sizeof($parts) + 1;
3654  
3655          if (substr($address, 0, 2) === '::')
3656          {
3657              ++$missing_parts;
3658          }
3659  
3660          if (substr($address, -2) === '::')
3661          {
3662              ++$missing_parts;
3663          }
3664  
3665          $embedded_ipv4 = false;
3666          $last_part = end($parts);
3667  
3668          if (preg_match(get_preg_expression('ipv4'), $last_part))
3669          {
3670              $parts[sizeof($parts) - 1] = '';
3671              $last_part = phpbb_inet_pton($last_part);
3672              $embedded_ipv4 = true;
3673              --$missing_parts;
3674          }
3675  
3676          foreach ($parts as $i => $part)
3677          {
3678              if (strlen($part))
3679              {
3680                  $ret .= str_pad($part, 4, '0', STR_PAD_LEFT);
3681              }
3682              else if ($i && $i < sizeof($parts) - 1)
3683              {
3684                  $ret .= str_repeat('0000', $missing_parts);
3685              }
3686          }
3687  
3688          $ret = pack('H*', $ret);
3689  
3690          if ($embedded_ipv4)
3691          {
3692              $ret .= $last_part;
3693          }
3694  
3695          return $ret;
3696      }
3697  
3698      return false;
3699  }
3700  
3701  /**
3702  * Wrapper for php's checkdnsrr function.
3703  *
3704  * @param string $host    Fully-Qualified Domain Name
3705  * @param string $type    Resource record type to lookup
3706  *                        Supported types are: MX (default), A, AAAA, NS, TXT, CNAME
3707  *                        Other types may work or may not work
3708  *
3709  * @return mixed        true if entry found,
3710  *                    false if entry not found,
3711  *                    null if this function is not supported by this environment
3712  *
3713  * Since null can also be returned, you probably want to compare the result
3714  * with === true or === false,
3715  */
3716  function phpbb_checkdnsrr($host, $type = 'MX')
3717  {
3718      // The dot indicates to search the DNS root (helps those having DNS prefixes on the same domain)
3719      if (substr($host, -1) == '.')
3720      {
3721          $host_fqdn = $host;
3722          $host = substr($host, 0, -1);
3723      }
3724      else
3725      {
3726          $host_fqdn = $host . '.';
3727      }
3728      // $host        has format    some.host.example.com
3729      // $host_fqdn    has format    some.host.example.com.
3730  
3731      // If we're looking for an A record we can use gethostbyname()
3732      if ($type == 'A' && function_exists('gethostbyname'))
3733      {
3734          return (@gethostbyname($host_fqdn) == $host_fqdn) ? false : true;
3735      }
3736  
3737      // checkdnsrr() is available on Windows since PHP 5.3,
3738      // but until 5.3.3 it only works for MX records
3739      // See: http://bugs.php.net/bug.php?id=51844
3740  
3741      // Call checkdnsrr() if
3742      // we're looking for an MX record or
3743      // we're not on Windows or
3744      // we're running a PHP version where #51844 has been fixed
3745  
3746      // checkdnsrr() supports AAAA since 5.0.0
3747      // checkdnsrr() supports TXT since 5.2.4
3748      if (
3749          ($type == 'MX' || DIRECTORY_SEPARATOR != '\\' || version_compare(PHP_VERSION, '5.3.3', '>=')) &&
3750          ($type != 'AAAA' || version_compare(PHP_VERSION, '5.0.0', '>=')) &&
3751          ($type != 'TXT' || version_compare(PHP_VERSION, '5.2.4', '>=')) &&
3752          function_exists('checkdnsrr')
3753      )
3754      {
3755          return checkdnsrr($host_fqdn, $type);
3756      }
3757  
3758      // dns_get_record() is available since PHP 5; since PHP 5.3 also on Windows,
3759      // but on Windows it does not work reliable for AAAA records before PHP 5.3.1
3760  
3761      // Call dns_get_record() if
3762      // we're not looking for an AAAA record or
3763      // we're not on Windows or
3764      // we're running a PHP version where AAAA lookups work reliable
3765      if (
3766          ($type != 'AAAA' || DIRECTORY_SEPARATOR != '\\' || version_compare(PHP_VERSION, '5.3.1', '>=')) &&
3767          function_exists('dns_get_record')
3768      )
3769      {
3770          // dns_get_record() expects an integer as second parameter
3771          // We have to convert the string $type to the corresponding integer constant.
3772          $type_constant = 'DNS_' . $type;
3773          $type_param = (defined($type_constant)) ? constant($type_constant) : DNS_ANY;
3774  
3775          // dns_get_record() might throw E_WARNING and return false for records that do not exist
3776          $resultset = @dns_get_record($host_fqdn, $type_param);
3777  
3778          if (empty($resultset) || !is_array($resultset))
3779          {
3780              return false;
3781          }
3782          else if ($type_param == DNS_ANY)
3783          {
3784              // $resultset is a non-empty array
3785              return true;
3786          }
3787  
3788          foreach ($resultset as $result)
3789          {
3790              if (
3791                  isset($result['host']) && $result['host'] == $host &&
3792                  isset($result['type']) && $result['type'] == $type
3793              )
3794              {
3795                  return true;
3796              }
3797          }
3798  
3799          return false;
3800      }
3801  
3802      // If we're on Windows we can still try to call nslookup via exec() as a last resort
3803      if (DIRECTORY_SEPARATOR == '\\' && function_exists('exec'))
3804      {
3805          @exec('nslookup -type=' . escapeshellarg($type) . ' ' . escapeshellarg($host_fqdn), $output);
3806  
3807          // If output is empty, the nslookup failed
3808          if (empty($output))
3809          {
3810              return NULL;
3811          }
3812  
3813          foreach ($output as $line)
3814          {
3815              $line = trim($line);
3816  
3817              if (empty($line))
3818              {
3819                  continue;
3820              }
3821  
3822              // Squash tabs and multiple whitespaces to a single whitespace.
3823              $line = preg_replace('/\s+/', ' ', $line);
3824  
3825              switch ($type)
3826              {
3827                  case 'MX':
3828                      if (stripos($line, "$host MX") === 0)
3829                      {
3830                          return true;
3831                      }
3832                  break;
3833  
3834                  case 'NS':
3835                      if (stripos($line, "$host nameserver") === 0)
3836                      {
3837                          return true;
3838                      }
3839                  break;
3840  
3841                  case 'TXT':
3842                      if (stripos($line, "$host text") === 0)
3843                      {
3844                          return true;
3845                      }
3846                  break;
3847  
3848                  case 'CNAME':
3849                      if (stripos($line, "$host canonical name") === 0)
3850                      {
3851                          return true;
3852                      }
3853                  break;
3854  
3855                  default:
3856                  case 'AAAA':
3857                      // AAAA records returned by nslookup on Windows XP/2003 have this format.
3858                      // Later Windows versions use the A record format below for AAAA records.
3859                      if (stripos($line, "$host AAAA IPv6 address") === 0)
3860                      {
3861                          return true;
3862                      }
3863                  // No break
3864  
3865                  case 'A':
3866                      if (!empty($host_matches))
3867                      {
3868                          // Second line
3869                          if (stripos($line, "Address: ") === 0)
3870                          {
3871                              return true;
3872                          }
3873                          else
3874                          {
3875                              $host_matches = false;
3876                          }
3877                      }
3878                      else if (stripos($line, "Name: $host") === 0)
3879                      {
3880                          // First line
3881                          $host_matches = true;
3882                      }
3883                  break;
3884              }
3885          }
3886  
3887          return false;
3888      }
3889  
3890      return NULL;
3891  }
3892  
3893  // Handler, header and footer
3894  
3895  /**
3896  * Error and message handler, call with trigger_error if read
3897  */
3898  function msg_handler($errno, $msg_text, $errfile, $errline)
3899  {
3900      global $cache, $db, $auth, $template, $config, $user, $request;
3901      global $phpEx, $phpbb_root_path, $msg_title, $msg_long_text;
3902  
3903      // Do not display notices if we suppress them via @
3904      if (error_reporting() == 0 && $errno != E_USER_ERROR && $errno != E_USER_WARNING && $errno != E_USER_NOTICE)
3905      {
3906          return;
3907      }
3908  
3909      // Message handler is stripping text. In case we need it, we are possible to define long text...
3910      if (isset($msg_long_text) && $msg_long_text && !$msg_text)
3911      {
3912          $msg_text = $msg_long_text;
3913      }
3914  
3915      if (!defined('E_DEPRECATED'))
3916      {
3917          define('E_DEPRECATED', 8192);
3918      }
3919  
3920      switch ($errno)
3921      {
3922          case E_NOTICE:
3923          case E_WARNING:
3924  
3925              // Check the error reporting level and return if the error level does not match
3926              // If DEBUG is defined the default level is E_ALL
3927              if (($errno & ((defined('DEBUG')) ? E_ALL : error_reporting())) == 0)
3928              {
3929                  return;
3930              }
3931  
3932              if (strpos($errfile, 'cache') === false && strpos($errfile, 'template.') === false)
3933              {
3934                  $errfile = phpbb_filter_root_path($errfile);
3935                  $msg_text = phpbb_filter_root_path($msg_text);
3936                  $error_name = ($errno === E_WARNING) ? 'PHP Warning' : 'PHP Notice';
3937                  echo '<b>[phpBB Debug] ' . $error_name . '</b>: in file <b>' . $errfile . '</b> on line <b>' . $errline . '</b>: <b>' . $msg_text . '</b><br />' . "\n";
3938  
3939                  // we are writing an image - the user won't see the debug, so let's place it in the log
3940                  if (defined('IMAGE_OUTPUT') || defined('IN_CRON'))
3941                  {
3942                      add_log('critical', 'LOG_IMAGE_GENERATION_ERROR', $errfile, $errline, $msg_text);
3943                  }
3944                  // echo '<br /><br />BACKTRACE<br />' . get_backtrace() . '<br />' . "\n";
3945              }
3946  
3947              return;
3948  
3949          break;
3950  
3951          case E_USER_ERROR:
3952  
3953              if (!empty($user) && !empty($user->lang))
3954              {
3955                  $msg_text = (!empty($user->lang[$msg_text])) ? $user->lang[$msg_text] : $msg_text;
3956                  $msg_title = (!isset($msg_title)) ? $user->lang['GENERAL_ERROR'] : ((!empty($user->lang[$msg_title])) ? $user->lang[$msg_title] : $msg_title);
3957  
3958                  $l_return_index = sprintf($user->lang['RETURN_INDEX'], '<a href="' . $phpbb_root_path . '">', '</a>');
3959                  $l_notify = '';
3960  
3961                  if (!empty($config['board_contact']))
3962                  {
3963                      $l_notify = '<p>' . sprintf($user->lang['NOTIFY_ADMIN_EMAIL'], $config['board_contact']) . '</p>';
3964                  }
3965              }
3966              else
3967              {
3968                  $msg_title = 'General Error';
3969                  $l_return_index = '<a href="' . $phpbb_root_path . '">Return to index page</a>';
3970                  $l_notify = '';
3971  
3972                  if (!empty($config['board_contact']))
3973                  {
3974                      $l_notify = '<p>Please notify the board administrator or webmaster: <a href="mailto:' . $config['board_contact'] . '">' . $config['board_contact'] . '</a></p>';
3975                  }
3976              }
3977  
3978              $log_text = $msg_text;
3979              $backtrace = get_backtrace();
3980              if ($backtrace)
3981              {
3982                  $log_text .= '<br /><br />BACKTRACE<br />' . $backtrace;
3983              }
3984  
3985              if (defined('IN_INSTALL') || defined('DEBUG') || isset($auth) && $auth->acl_get('a_'))
3986              {
3987                  $msg_text = $log_text;
3988  
3989                  // If this is defined there already was some output
3990                  // So let's not break it
3991                  if (defined('IN_DB_UPDATE'))
3992                  {
3993                      echo '<div class="errorbox">' . $msg_text . '</div>';
3994  
3995                      $db->sql_return_on_error(true);
3996                      phpbb_end_update($cache, $config);
3997                  }
3998              }
3999  
4000              if ((defined('IN_CRON') || defined('IMAGE_OUTPUT')) && isset($db))
4001              {
4002                  // let's avoid loops
4003                  $db->sql_return_on_error(true);
4004                  add_log('critical', 'LOG_GENERAL_ERROR', $msg_title, $log_text);
4005                  $db->sql_return_on_error(false);
4006              }
4007  
4008              // Do not send 200 OK, but service unavailable on errors
4009              send_status_line(503, 'Service Unavailable');
4010  
4011              garbage_collection();
4012  
4013              // Try to not call the adm page data...
4014  
4015              echo '<!DOCTYPE html>';
4016              echo '<html dir="ltr">';
4017              echo '<head>';
4018              echo '<meta charset="utf-8">';
4019              echo '<meta http-equiv="X-UA-Compatible" content="IE=edge">';
4020              echo '<title>' . $msg_title . '</title>';
4021              echo '<style type="text/css">' . "\n" . '/* <![CDATA[ */' . "\n";
4022              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; } ';
4023              echo 'a:link, a:active, a:visited { color: #006699; text-decoration: none; } a:hover { color: #DD6900; text-decoration: underline; } ';
4024              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; } ';
4025              echo '.panel { margin: 4px 0; background-color: #FFFFFF; border: solid 1px  #A9B8C2; } ';
4026              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; } ';
4027              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; } ';
4028              echo "\n" . '/* ]]> */' . "\n";
4029              echo '</style>';
4030              echo '</head>';
4031              echo '<body id="errorpage">';
4032              echo '<div id="wrap">';
4033              echo '    <div id="page-header">';
4034              echo '        ' . $l_return_index;
4035              echo '    </div>';
4036              echo '    <div id="acp">';
4037              echo '    <div class="panel">';
4038              echo '        <div id="content">';
4039              echo '            <h1>' . $msg_title . '</h1>';
4040  
4041              echo '            <div>' . $msg_text . '</div>';
4042  
4043              echo $l_notify;
4044  
4045              echo '        </div>';
4046              echo '    </div>';
4047              echo '    </div>';
4048              echo '    <div id="page-footer">';
4049              echo '        Powered by <a href="https://www.phpbb.com/">phpBB</a>&reg; Forum Software &copy; phpBB Limited';
4050              echo '    </div>';
4051              echo '</div>';
4052              echo '</body>';
4053              echo '</html>';
4054  
4055              exit_handler();
4056  
4057              // On a fatal error (and E_USER_ERROR *is* fatal) we never want other scripts to continue and force an exit here.
4058              exit;
4059          break;
4060  
4061          case E_USER_WARNING:
4062          case E_USER_NOTICE:
4063  
4064              define('IN_ERROR_HANDLER', true);
4065  
4066              if (empty($user->data))
4067              {
4068                  $user->session_begin();
4069              }
4070  
4071              // We re-init the auth array to get correct results on login/logout
4072              $auth->acl($user->data);
4073  
4074              if (empty($user->lang))
4075              {
4076                  $user->setup();
4077              }
4078  
4079              if ($msg_text == 'ERROR_NO_ATTACHMENT' || $msg_text == 'NO_FORUM' || $msg_text == 'NO_TOPIC' || $msg_text == 'NO_USER')
4080              {
4081                  send_status_line(404, 'Not Found');
4082              }
4083  
4084              $msg_text = (!empty($user->lang[$msg_text])) ? $user->lang[$msg_text] : $msg_text;
4085              $msg_title = (!isset($msg_title)) ? $user->lang['INFORMATION'] : ((!empty($user->lang[$msg_title])) ? $user->lang[$msg_title] : $msg_title);
4086  
4087              if (!defined('HEADER_INC'))
4088              {
4089                  if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
4090                  {
4091                      adm_page_header($msg_title);
4092                  }
4093                  else
4094                  {
4095                      page_header($msg_title);
4096                  }
4097              }
4098  
4099              $template->set_filenames(array(
4100                  'body' => 'message_body.html')
4101              );
4102  
4103              $template->assign_vars(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              );
4109  
4110              if ($request->is_ajax())
4111              {
4112                  global $refresh_data;
4113  
4114                  $json_response = new \phpbb\json_response;
4115                  $json_response->send(array(
4116                      'MESSAGE_TITLE'        => $msg_title,
4117                      'MESSAGE_TEXT'        => $msg_text,
4118                      'S_USER_WARNING'    => ($errno == E_USER_WARNING) ? true : false,
4119                      'S_USER_NOTICE'        => ($errno == E_USER_NOTICE) ? true : false,
4120                      'REFRESH_DATA'        => (!empty($refresh_data)) ? $refresh_data : null
4121                  ));
4122              }
4123  
4124              // We do not want the cron script to be called on error messages
4125              define('IN_CRON', true);
4126  
4127              if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
4128              {
4129                  adm_page_footer();
4130              }
4131              else
4132              {
4133                  page_footer();
4134              }
4135  
4136              exit_handler();
4137          break;
4138  
4139          // PHP4 compatibility
4140          case E_DEPRECATED:
4141              return true;
4142          break;
4143      }
4144  
4145      // If we notice an error not handled here we pass this back to PHP by returning false
4146      // This may not work for all php versions
4147      return false;
4148  }
4149  
4150  /**
4151  * Removes absolute path to phpBB root directory from error messages
4152  * and converts backslashes to forward slashes.
4153  *
4154  * @param string $errfile    Absolute file path
4155  *                            (e.g. /var/www/phpbb3/phpBB/includes/functions.php)
4156  *                            Please note that if $errfile is outside of the phpBB root,
4157  *                            the root path will not be found and can not be filtered.
4158  * @return string            Relative file path
4159  *                            (e.g. /includes/functions.php)
4160  */
4161  function phpbb_filter_root_path($errfile)
4162  {
4163      static $root_path;
4164  
4165      if (empty($root_path))
4166      {
4167          $root_path = phpbb_realpath(dirname(__FILE__) . '/../');
4168      }
4169  
4170      return str_replace(array($root_path, '\\'), array('[ROOT]', '/'), $errfile);
4171  }
4172  
4173  /**
4174  * Queries the session table to get information about online guests
4175  * @param int $item_id Limits the search to the item with this id
4176  * @param string $item The name of the item which is stored in the session table as session_{$item}_id
4177  * @return int The number of active distinct guest sessions
4178  */
4179  function obtain_guest_count($item_id = 0, $item = 'forum')
4180  {
4181      global $db, $config;
4182  
4183      if ($item_id)
4184      {
4185          $reading_sql = ' AND s.session_' . $item . '_id = ' . (int) $item_id;
4186      }
4187      else
4188      {
4189          $reading_sql = '';
4190      }
4191      $time = (time() - (intval($config['load_online_time']) * 60));
4192  
4193      // Get number of online guests
4194  
4195      if ($db->get_sql_layer() === 'sqlite' || $db->get_sql_layer() === 'sqlite3')
4196      {
4197          $sql = 'SELECT COUNT(session_ip) as num_guests
4198              FROM (
4199                  SELECT DISTINCT s.session_ip
4200                  FROM ' . SESSIONS_TABLE . ' s
4201                  WHERE s.session_user_id = ' . ANONYMOUS . '
4202                      AND s.session_time >= ' . ($time - ((int) ($time % 60))) .
4203                  $reading_sql .
4204              ')';
4205      }
4206      else
4207      {
4208          $sql = 'SELECT COUNT(DISTINCT s.session_ip) as num_guests
4209              FROM ' . SESSIONS_TABLE . ' s
4210              WHERE s.session_user_id = ' . ANONYMOUS . '
4211                  AND s.session_time >= ' . ($time - ((int) ($time % 60))) .
4212              $reading_sql;
4213      }
4214      $result = $db->sql_query($sql);
4215      $guests_online = (int) $db->sql_fetchfield('num_guests');
4216      $db->sql_freeresult($result);
4217  
4218      return $guests_online;
4219  }
4220  
4221  /**
4222  * Queries the session table to get information about online users
4223  * @param int $item_id Limits the search to the item with this id
4224  * @param string $item The name of the item which is stored in the session table as session_{$item}_id
4225  * @return array An array containing the ids of online, hidden and visible users, as well as statistical info
4226  */
4227  function obtain_users_online($item_id = 0, $item = 'forum')
4228  {
4229      global $db, $config, $user;
4230  
4231      $reading_sql = '';
4232      if ($item_id !== 0)
4233      {
4234          $reading_sql = ' AND s.session_' . $item . '_id = ' . (int) $item_id;
4235      }
4236  
4237      $online_users = array(
4238          'online_users'            => array(),
4239          'hidden_users'            => array(),
4240          'total_online'            => 0,
4241          'visible_online'        => 0,
4242          'hidden_online'            => 0,
4243          'guests_online'            => 0,
4244      );
4245  
4246      if ($config['load_online_guests'])
4247      {
4248          $online_users['guests_online'] = obtain_guest_count($item_id, $item);
4249      }
4250  
4251      // a little discrete magic to cache this for 30 seconds
4252      $time = (time() - (intval($config['load_online_time']) * 60));
4253  
4254      $sql = 'SELECT s.session_user_id, s.session_ip, s.session_viewonline
4255          FROM ' . SESSIONS_TABLE . ' s
4256          WHERE s.session_time >= ' . ($time - ((int) ($time % 30))) .
4257              $reading_sql .
4258          ' AND s.session_user_id <> ' . ANONYMOUS;
4259      $result = $db->sql_query($sql);
4260  
4261      while ($row = $db->sql_fetchrow($result))
4262      {
4263          // Skip multiple sessions for one user
4264          if (!isset($online_users['online_users'][$row['session_user_id']]))
4265          {
4266              $online_users['online_users'][$row['session_user_id']] = (int) $row['session_user_id'];
4267              if ($row['session_viewonline'])
4268              {
4269                  $online_users['visible_online']++;
4270              }
4271              else
4272              {
4273                  $online_users['hidden_users'][$row['session_user_id']] = (int) $row['session_user_id'];
4274                  $online_users['hidden_online']++;
4275              }
4276          }
4277      }
4278      $online_users['total_online'] = $online_users['guests_online'] + $online_users['visible_online'] + $online_users['hidden_online'];
4279      $db->sql_freeresult($result);
4280  
4281      return $online_users;
4282  }
4283  
4284  /**
4285  * Uses the result of obtain_users_online to generate a localized, readable representation.
4286  * @param mixed $online_users result of obtain_users_online - array with user_id lists for total, hidden and visible users, and statistics
4287  * @param int $item_id Indicate that the data is limited to one item and not global
4288  * @param string $item The name of the item which is stored in the session table as session_{$item}_id
4289  * @return array An array containing the string for output to the template
4290  */
4291  function obtain_users_online_string($online_users, $item_id = 0, $item = 'forum')
4292  {
4293      global $config, $db, $user, $auth, $phpbb_dispatcher;
4294  
4295      $guests_online = $hidden_online = $l_online_users = $online_userlist = $visible_online = '';
4296      $user_online_link = $rowset = array();
4297      // Need caps version of $item for language-strings
4298      $item_caps = strtoupper($item);
4299  
4300      if (sizeof($online_users['online_users']))
4301      {
4302          $sql_ary = array(
4303              'SELECT'    => 'u.username, u.username_clean, u.user_id, u.user_type, u.user_allow_viewonline, u.user_colour',
4304              'FROM'        => array(
4305                  USERS_TABLE    => 'u',
4306              ),
4307              'WHERE'        => $db->sql_in_set('u.user_id', $online_users['online_users']),
4308              'ORDER_BY'    => 'u.username_clean ASC',
4309          );
4310  
4311          /**
4312          * Modify SQL query to obtain online users data
4313          *
4314          * @event core.obtain_users_online_string_sql
4315          * @var    array    online_users    Array with online users data
4316          *                                from obtain_users_online()
4317          * @var    int        item_id            Restrict online users to item id
4318          * @var    string    item            Restrict online users to a certain
4319          *                                session item, e.g. forum for
4320          *                                session_forum_id
4321          * @var    array    sql_ary            SQL query array to obtain users online data
4322          * @since 3.1.4-RC1
4323          * @changed 3.1.7-RC1            Change sql query into array and adjust var accordingly. Allows extension authors the ability to adjust the sql_ary.
4324          */
4325          $vars = array('online_users', 'item_id', 'item', 'sql_ary');
4326          extract($phpbb_dispatcher->trigger_event('core.obtain_users_online_string_sql', compact($vars)));
4327  
4328          $result = $db->sql_query($db->sql_build_query('SELECT', $sql_ary));
4329          $rowset = $db->sql_fetchrowset($result);
4330          $db->sql_freeresult($result);
4331  
4332          foreach ($rowset as $row)
4333          {
4334              // User is logged in and therefore not a guest
4335              if ($row['user_id'] != ANONYMOUS)
4336              {
4337                  if (isset($online_users['hidden_users'][$row['user_id']]))
4338                  {
4339                      $row['username'] = '<em>' . $row['username'] . '</em>';
4340                  }
4341  
4342                  if (!isset($online_users['hidden_users'][$row['user_id']]) || $auth->acl_get('u_viewonline') || $row['user_id'] === $user->data['user_id'])
4343                  {
4344                      $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']);
4345                  }
4346              }
4347          }
4348      }
4349  
4350      /**
4351      * Modify online userlist data
4352      *
4353      * @event core.obtain_users_online_string_before_modify
4354      * @var    array    online_users        Array with online users data
4355      *                                    from obtain_users_online()
4356      * @var    int        item_id                Restrict online users to item id
4357      * @var    string    item                Restrict online users to a certain
4358      *                                    session item, e.g. forum for
4359      *                                    session_forum_id
4360      * @var    array    rowset                Array with online users data
4361      * @var    array    user_online_link    Array with online users items (usernames)
4362      * @since 3.1.10-RC1
4363      */
4364      $vars = array(
4365          'online_users',
4366          'item_id',
4367          'item',
4368          'rowset',
4369          'user_online_link',
4370      );
4371      extract($phpbb_dispatcher->trigger_event('core.obtain_users_online_string_before_modify', compact($vars)));
4372  
4373      $online_userlist = implode(', ', $user_online_link);
4374  
4375      if (!$online_userlist)
4376      {
4377          $online_userlist = $user->lang['NO_ONLINE_USERS'];
4378      }
4379  
4380      if ($item_id === 0)
4381      {
4382          $online_userlist = $user->lang['REGISTERED_USERS'] . ' ' . $online_userlist;
4383      }
4384      else if ($config['load_online_guests'])
4385      {
4386          $online_userlist = $user->lang('BROWSING_' . $item_caps . '_GUESTS', $online_users['guests_online'], $online_userlist);
4387      }
4388      else
4389      {
4390          $online_userlist = sprintf($user->lang['BROWSING_' . $item_caps], $online_userlist);
4391      }
4392      // Build online listing
4393      $visible_online = $user->lang('REG_USERS_TOTAL', (int) $online_users['visible_online']);
4394      $hidden_online = $user->lang('HIDDEN_USERS_TOTAL', (int) $online_users['hidden_online']);
4395  
4396      if ($config['load_online_guests'])
4397      {
4398          $guests_online = $user->lang('GUEST_USERS_TOTAL', (int) $online_users['guests_online']);
4399          $l_online_users = $user->lang('ONLINE_USERS_TOTAL_GUESTS', (int) $online_users['total_online'], $visible_online, $hidden_online, $guests_online);
4400      }
4401      else
4402      {
4403          $l_online_users = $user->lang('ONLINE_USERS_TOTAL', (int) $online_users['total_online'], $visible_online, $hidden_online);
4404      }
4405  
4406      /**
4407      * Modify online userlist data
4408      *
4409      * @event core.obtain_users_online_string_modify
4410      * @var    array    online_users        Array with online users data
4411      *                                    from obtain_users_online()
4412      * @var    int        item_id                Restrict online users to item id
4413      * @var    string    item                Restrict online users to a certain
4414      *                                    session item, e.g. forum for
4415      *                                    session_forum_id
4416      * @var    array    rowset                Array with online users data
4417      * @var    array    user_online_link    Array with online users items (usernames)
4418      * @var    string    online_userlist        String containing users online list
4419      * @var    string    l_online_users        String with total online users count info
4420      * @since 3.1.4-RC1
4421      */
4422      $vars = array(
4423          'online_users',
4424          'item_id',
4425          'item',
4426          'rowset',
4427          'user_online_link',
4428          'online_userlist',
4429          'l_online_users',
4430      );
4431      extract($phpbb_dispatcher->trigger_event('core.obtain_users_online_string_modify', compact($vars)));
4432  
4433      return array(
4434          'online_userlist'    => $online_userlist,
4435          'l_online_users'    => $l_online_users,
4436      );
4437  }
4438  
4439  /**
4440  * Get option bitfield from custom data
4441  *
4442  * @param int    $bit        The bit/value to get
4443  * @param int    $data        Current bitfield to check
4444  * @return bool    Returns true if value of constant is set in bitfield, else false
4445  */
4446  function phpbb_optionget($bit, $data)
4447  {
4448      return ($data & 1 << (int) $bit) ? true : false;
4449  }
4450  
4451  /**
4452  * Set option bitfield
4453  *
4454  * @param int    $bit        The bit/value to set/unset
4455  * @param bool    $set        True if option should be set, false if option should be unset.
4456  * @param int    $data        Current bitfield to change
4457  *
4458  * @return int    The new bitfield
4459  */
4460  function phpbb_optionset($bit, $set, $data)
4461  {
4462      if ($set && !($data & 1 << $bit))
4463      {
4464          $data += 1 << $bit;
4465      }
4466      else if (!$set && ($data & 1 << $bit))
4467      {
4468          $data -= 1 << $bit;
4469      }
4470  
4471      return $data;
4472  }
4473  
4474  /**
4475  * Determine which plural form we should use.
4476  * For some languages this is not as simple as for English.
4477  *
4478  * @param $rule        int            ID of the plural rule we want to use, see http://wiki.phpbb.com/Plural_Rules#Plural_Rules
4479  * @param $number    int|float    The number we want to get the plural case for. Float numbers are floored.
4480  * @return    int        The plural-case we need to use for the number plural-rule combination
4481  */
4482  function phpbb_get_plural_form($rule, $number)
4483  {
4484      $number = (int) $number;
4485  
4486      if ($rule > 15 || $rule < 0)
4487      {
4488          trigger_error('INVALID_PLURAL_RULE');
4489      }
4490  
4491      /**
4492      * The following plural rules are based on a list published by the Mozilla Developer Network
4493      * https://developer.mozilla.org/en/Localization_and_Plurals
4494      */
4495      switch ($rule)
4496      {
4497          case 0:
4498              /**
4499              * Families: Asian (Chinese, Japanese, Korean, Vietnamese), Persian, Turkic/Altaic (Turkish), Thai, Lao
4500              * 1 - everything: 0, 1, 2, ...
4501              */
4502              return 1;
4503  
4504          case 1:
4505              /**
4506              * 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)
4507              * 1 - 1
4508              * 2 - everything else: 0, 2, 3, ...
4509              */
4510              return ($number == 1) ? 1 : 2;
4511  
4512          case 2:
4513              /**
4514              * Families: Romanic (French, Brazilian Portuguese)
4515              * 1 - 0, 1
4516              * 2 - everything else: 2, 3, ...
4517              */
4518              return (($number == 0) || ($number == 1)) ? 1 : 2;
4519  
4520          case 3:
4521              /**
4522              * Families: Baltic (Latvian)
4523              * 1 - 0
4524              * 2 - ends in 1, not 11: 1, 21, ... 101, 121, ...
4525              * 3 - everything else: 2, 3, ... 10, 11, 12, ... 20, 22, ...
4526              */
4527              return ($number == 0) ? 1 : ((($number % 10 == 1) && ($number % 100 != 11)) ? 2 : 3);
4528  
4529          case 4:
4530              /**
4531              * Families: Celtic (Scottish Gaelic)
4532              * 1 - is 1 or 11: 1, 11
4533              * 2 - is 2 or 12: 2, 12
4534              * 3 - others between 3 and 19: 3, 4, ... 10, 13, ... 18, 19
4535              * 4 - everything else: 0, 20, 21, ...
4536              */
4537              return ($number == 1 || $number == 11) ? 1 : (($number == 2 || $number == 12) ? 2 : (($number >= 3 && $number <= 19) ? 3 : 4));
4538  
4539          case 5:
4540              /**
4541              * Families: Romanic (Romanian)
4542              * 1 - 1
4543              * 2 - is 0 or ends in 01-19: 0, 2, 3, ... 19, 101, 102, ... 119, 201, ...
4544              * 3 - everything else: 20, 21, ...
4545              */
4546              return ($number == 1) ? 1 : ((($number == 0) || (($number % 100 > 0) && ($number % 100 < 20))) ? 2 : 3);
4547  
4548          case 6:
4549              /**
4550              * Families: Baltic (Lithuanian)
4551              * 1 - ends in 1, not 11: 1, 21, 31, ... 101, 121, ...
4552              * 2 - ends in 0 or ends in 10-20: 0, 10, 11, 12, ... 19, 20, 30, 40, ...
4553              * 3 - everything else: 2, 3, ... 8, 9, 22, 23, ... 29, 32, 33, ...
4554              */
4555              return (($number % 10 == 1) && ($number % 100 != 11)) ? 1 : ((($number % 10 < 2) || (($number % 100 >= 10) && ($number % 100 < 20))) ? 2 : 3);
4556  
4557          case 7:
4558              /**
4559              * Families: Slavic (Croatian, Serbian, Russian, Ukrainian)
4560              * 1 - ends in 1, not 11: 1, 21, 31, ... 101, 121, ...
4561              * 2 - ends in 2-4, not 12-14: 2, 3, 4, 22, 23, 24, 32, ...
4562              * 3 - everything else: 0, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 25, 26, ...
4563              */
4564              return (($number % 10 == 1) && ($number % 100 != 11)) ? 1 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 2 : 3);
4565  
4566          case 8:
4567              /**
4568              * Families: Slavic (Slovak, Czech)
4569              * 1 - 1
4570              * 2 - 2, 3, 4
4571              * 3 - everything else: 0, 5, 6, 7, ...
4572              */
4573              return ($number == 1) ? 1 : ((($number >= 2) && ($number <= 4)) ? 2 : 3);
4574  
4575          case 9:
4576              /**
4577              * Families: Slavic (Polish)
4578              * 1 - 1
4579              * 2 - ends in 2-4, not 12-14: 2, 3, 4, 22, 23, 24, 32, ... 104, 122, ...
4580              * 3 - everything else: 0, 5, 6, ... 11, 12, 13, 14, 15, ... 20, 21, 25, ...
4581              */
4582              return ($number == 1) ? 1 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 12) || ($number % 100 > 14))) ? 2 : 3);
4583  
4584          case 10:
4585              /**
4586              * Families: Slavic (Slovenian, Sorbian)
4587              * 1 - ends in 01: 1, 101, 201, ...
4588              * 2 - ends in 02: 2, 102, 202, ...
4589              * 3 - ends in 03-04: 3, 4, 103, 104, 203, 204, ...
4590              * 4 - everything else: 0, 5, 6, 7, 8, 9, 10, 11, ...
4591              */
4592              return ($number % 100 == 1) ? 1 : (($number % 100 == 2) ? 2 : ((($number % 100 == 3) || ($number % 100 == 4)) ? 3 : 4));
4593  
4594          case 11:
4595              /**
4596              * Families: Celtic (Irish Gaeilge)
4597              * 1 - 1
4598              * 2 - 2
4599              * 3 - is 3-6: 3, 4, 5, 6
4600              * 4 - is 7-10: 7, 8, 9, 10
4601              * 5 - everything else: 0, 11, 12, ...
4602              */
4603              return ($number == 1) ? 1 : (($number == 2) ? 2 : (($number >= 3 && $number <= 6) ? 3 : (($number >= 7 && $number <= 10) ? 4 : 5)));
4604  
4605          case 12:
4606              /**
4607              * Families: Semitic (Arabic)
4608              * 1 - 1
4609              * 2 - 2
4610              * 3 - ends in 03-10: 3, 4, ... 10, 103, 104, ... 110, 203, 204, ...
4611              * 4 - ends in 11-99: 11, ... 99, 111, 112, ...
4612              * 5 - everything else: 100, 101, 102, 200, 201, 202, ...
4613              * 6 - 0
4614              */
4615              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))));
4616  
4617          case 13:
4618              /**
4619              * Families: Semitic (Maltese)
4620              * 1 - 1
4621              * 2 - is 0 or ends in 01-10: 0, 2, 3, ... 9, 10, 101, 102, ...
4622              * 3 - ends in 11-19: 11, 12, ... 18, 19, 111, 112, ...
4623              * 4 - everything else: 20, 21, ...
4624              */
4625              return ($number == 1) ? 1 : ((($number == 0) || (($number % 100 > 1) && ($number % 100 < 11))) ? 2 : ((($number % 100 > 10) && ($number % 100 < 20)) ? 3 : 4));
4626  
4627          case 14:
4628              /**
4629              * Families: Slavic (Macedonian)
4630              * 1 - ends in 1: 1, 11, 21, ...
4631              * 2 - ends in 2: 2, 12, 22, ...
4632              * 3 - everything else: 0, 3, 4, ... 10, 13, 14, ... 20, 23, ...
4633              */
4634              return ($number % 10 == 1) ? 1 : (($number % 10 == 2) ? 2 : 3);
4635  
4636          case 15:
4637              /**
4638              * Families: Icelandic
4639              * 1 - ends in 1, not 11: 1, 21, 31, ... 101, 121, 131, ...
4640              * 2 - everything else: 0, 2, 3, ... 10, 11, 12, ... 20, 22, ...
4641              */
4642              return (($number % 10 == 1) && ($number % 100 != 11)) ? 1 : 2;
4643      }
4644  }
4645  
4646  /**
4647  * Login using http authenticate.
4648  *
4649  * @param array    $param        Parameter array, see $param_defaults array.
4650  *
4651  * @return null
4652  */
4653  function phpbb_http_login($param)
4654  {
4655      global $auth, $user, $request;
4656      global $config;
4657  
4658      $param_defaults = array(
4659          'auth_message'    => '',
4660  
4661          'autologin'        => false,
4662          'viewonline'    => true,
4663          'admin'            => false,
4664      );
4665  
4666      // Overwrite default values with passed values
4667      $param = array_merge($param_defaults, $param);
4668  
4669      // User is already logged in
4670      // We will not overwrite his session
4671      if (!empty($user->data['is_registered']))
4672      {
4673          return;
4674      }
4675  
4676      // $_SERVER keys to check
4677      $username_keys = array(
4678          'PHP_AUTH_USER',
4679          'Authorization',
4680          'REMOTE_USER', 'REDIRECT_REMOTE_USER',
4681          'HTTP_AUTHORIZATION', 'REDIRECT_HTTP_AUTHORIZATION',
4682          'REMOTE_AUTHORIZATION', 'REDIRECT_REMOTE_AUTHORIZATION',
4683          'AUTH_USER',
4684      );
4685  
4686      $password_keys = array(
4687          'PHP_AUTH_PW',
4688          'REMOTE_PASSWORD',
4689          'AUTH_PASSWORD',
4690      );
4691  
4692      $username = null;
4693      foreach ($username_keys as $k)
4694      {
4695          if ($request->is_set($k, \phpbb\request\request_interface::SERVER))
4696          {
4697              $username = htmlspecialchars_decode($request->server($k));
4698              break;
4699          }
4700      }
4701  
4702      $password = null;
4703      foreach ($password_keys as $k)
4704      {
4705          if ($request->is_set($k, \phpbb\request\request_interface::SERVER))
4706          {
4707              $password = htmlspecialchars_decode($request->server($k));
4708              break;
4709          }
4710      }
4711  
4712      // Decode encoded information (IIS, CGI, FastCGI etc.)
4713      if (!is_null($username) && is_null($password) && strpos($username, 'Basic ') === 0)
4714      {
4715          list($username, $password) = explode(':', base64_decode(substr($username, 6)), 2);
4716      }
4717  
4718      if (!is_null($username) && !is_null($password))
4719      {
4720          set_var($username, $username, 'string', true);
4721          set_var($password, $password, 'string', true);
4722  
4723          $auth_result = $auth->login($username, $password, $param['autologin'], $param['viewonline'], $param['admin']);
4724  
4725          if ($auth_result['status'] == LOGIN_SUCCESS)
4726          {
4727              return;
4728          }
4729          else if ($auth_result['status'] == LOGIN_ERROR_ATTEMPTS)
4730          {
4731              send_status_line(401, 'Unauthorized');
4732  
4733              trigger_error('NOT_AUTHORISED');
4734          }
4735      }
4736  
4737      // Prepend sitename to auth_message
4738      $param['auth_message'] = ($param['auth_message'] === '') ? $config['sitename'] : $config['sitename'] . ' - ' . $param['auth_message'];
4739  
4740      // We should probably filter out non-ASCII characters - RFC2616
4741      $param['auth_message'] = preg_replace('/[\x80-\xFF]/', '?', $param['auth_message']);
4742  
4743      header('WWW-Authenticate: Basic realm="' . $param['auth_message'] . '"');
4744      send_status_line(401, 'Unauthorized');
4745  
4746      trigger_error('NOT_AUTHORISED');
4747  }
4748  
4749  /**
4750  * Escapes and quotes a string for use as an HTML/XML attribute value.
4751  *
4752  * This is a port of Python xml.sax.saxutils quoteattr.
4753  *
4754  * The function will attempt to choose a quote character in such a way as to
4755  * avoid escaping quotes in the string. If this is not possible the string will
4756  * be wrapped in double quotes and double quotes will be escaped.
4757  *
4758  * @param string $data The string to be escaped
4759  * @param array $entities Associative array of additional entities to be escaped
4760  * @return string Escaped and quoted string
4761  */
4762  function phpbb_quoteattr($data, $entities = null)
4763  {
4764      $data = str_replace('&', '&amp;', $data);
4765      $data = str_replace('>', '&gt;', $data);
4766      $data = str_replace('<', '&lt;', $data);
4767  
4768      $data = str_replace("\n", '&#10;', $data);
4769      $data = str_replace("\r", '&#13;', $data);
4770      $data = str_replace("\t", '&#9;', $data);
4771  
4772      if (!empty($entities))
4773      {
4774          $data = str_replace(array_keys($entities), array_values($entities), $data);
4775      }
4776  
4777      if (strpos($data, '"') !== false)
4778      {
4779          if (strpos($data, "'") !== false)
4780          {
4781              $data = '"' . str_replace('"', '&quot;', $data) . '"';
4782          }
4783          else
4784          {
4785              $data = "'" . $data . "'";
4786          }
4787      }
4788      else
4789      {
4790          $data = '"' . $data . '"';
4791      }
4792  
4793      return $data;
4794  }
4795  
4796  /**
4797  * Converts query string (GET) parameters in request into hidden fields.
4798  *
4799  * Useful for forwarding GET parameters when submitting forms with GET method.
4800  *
4801  * It is possible to omit some of the GET parameters, which is useful if
4802  * they are specified in the form being submitted.
4803  *
4804  * sid is always omitted.
4805  *
4806  * @param \phpbb\request\request $request Request object
4807  * @param array $exclude A list of variable names that should not be forwarded
4808  * @return string HTML with hidden fields
4809  */
4810  function phpbb_build_hidden_fields_for_query_params($request, $exclude = null)
4811  {
4812      $names = $request->variable_names(\phpbb\request\request_interface::GET);
4813      $hidden = '';
4814      foreach ($names as $name)
4815      {
4816          // Sessions are dealt with elsewhere, omit sid always
4817          if ($name == 'sid')
4818          {
4819              continue;
4820          }
4821  
4822          // Omit any additional parameters requested
4823          if (!empty($exclude) && in_array($name, $exclude))
4824          {
4825              continue;
4826          }
4827  
4828          $escaped_name = phpbb_quoteattr($name);
4829  
4830          // Note: we might retrieve the variable from POST or cookies
4831          // here. To avoid exposing cookies, skip variables that are
4832          // overwritten somewhere other than GET entirely.
4833          $value = $request->variable($name, '', true);
4834          $get_value = $request->variable($name, '', true, \phpbb\request\request_interface::GET);
4835          if ($value === $get_value)
4836          {
4837              $escaped_value = phpbb_quoteattr($value);
4838              $hidden .= "<input type='hidden' name=$escaped_name value=$escaped_value />";
4839          }
4840      }
4841      return $hidden;
4842  }
4843  
4844  /**
4845  * Get user avatar
4846  *
4847  * @param array $user_row Row from the users table
4848  * @param string $alt Optional language string for alt tag within image, can be a language key or text
4849  * @param bool $ignore_config Ignores the config-setting, to be still able to view the avatar in the UCP
4850  * @param bool $lazy If true, will be lazy loaded (requires JS)
4851  *
4852  * @return string Avatar html
4853  */
4854  function phpbb_get_user_avatar($user_row, $alt = 'USER_AVATAR', $ignore_config = false, $lazy = false)
4855  {
4856      $row = \phpbb\avatar\manager::clean_row($user_row, 'user');
4857      return phpbb_get_avatar($row, $alt, $ignore_config, $lazy);
4858  }
4859  
4860  /**
4861  * Get group avatar
4862  *
4863  * @param array $group_row Row from the groups table
4864  * @param string $alt Optional language string for alt tag within image, can be a language key or text
4865  * @param bool $ignore_config Ignores the config-setting, to be still able to view the avatar in the UCP
4866  * @param bool $lazy If true, will be lazy loaded (requires JS)
4867  *
4868  * @return string Avatar html
4869  */
4870  function phpbb_get_group_avatar($user_row, $alt = 'GROUP_AVATAR', $ignore_config = false, $lazy = false)
4871  {
4872      $row = \phpbb\avatar\manager::clean_row($user_row, 'group');
4873      return phpbb_get_avatar($row, $alt, $ignore_config, $lazy);
4874  }
4875  
4876  /**
4877  * Get avatar
4878  *
4879  * @param array $row Row cleaned by \phpbb\avatar\manager::clean_row
4880  * @param string $alt Optional language string for alt tag within image, can be a language key or text
4881  * @param bool $ignore_config Ignores the config-setting, to be still able to view the avatar in the UCP
4882  * @param bool $lazy If true, will be lazy loaded (requires JS)
4883  *
4884  * @return string Avatar html
4885  */
4886  function phpbb_get_avatar($row, $alt, $ignore_config = false, $lazy = false)
4887  {
4888      global $user, $config, $cache, $phpbb_root_path, $phpEx;
4889      global $request;
4890      global $phpbb_container, $phpbb_dispatcher;
4891  
4892      if (!$config['allow_avatar'] && !$ignore_config)
4893      {
4894          return '';
4895      }
4896  
4897      $avatar_data = array(
4898          'src' => $row['avatar'],
4899          'width' => $row['avatar_width'],
4900          'height' => $row['avatar_height'],
4901      );
4902  
4903      $phpbb_avatar_manager = $phpbb_container->get('avatar.manager');
4904      $driver = $phpbb_avatar_manager->get_driver($row['avatar_type'], !$ignore_config);
4905      $html = '';
4906  
4907      if ($driver)
4908      {
4909          $html = $driver->get_custom_html($user, $row, $alt);
4910          if (!empty($html))
4911          {
4912              return $html;
4913          }
4914  
4915          $avatar_data = $driver->get_data($row);
4916      }
4917      else
4918      {
4919          $avatar_data['src'] = '';
4920      }
4921  
4922      if (!empty($avatar_data['src']))
4923      {
4924          if ($lazy)
4925          {
4926              // Determine board url - we may need it later
4927              $board_url = generate_board_url() . '/';
4928              // This path is sent with the base template paths in the assign_vars()
4929              // call below. We need to correct it in case we are accessing from a
4930              // controller because the web paths will be incorrect otherwise.
4931              $phpbb_path_helper = $phpbb_container->get('path_helper');
4932              $corrected_path = $phpbb_path_helper->get_web_root_path();
4933  
4934              $web_path = (defined('PHPBB_USE_BOARD_URL_PATH') && PHPBB_USE_BOARD_URL_PATH) ? $board_url : $corrected_path;
4935  
4936              $theme = "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/theme';
4937  
4938              $src = 'src="' . $theme . '/images/no_avatar.gif" data-src="' . $avatar_data['src'] . '"';
4939          }
4940          else
4941          {
4942              $src = 'src="' . $avatar_data['src'] . '"';
4943          }
4944  
4945          $html = '<img class="avatar" ' . $src . ' ' .
4946              ($avatar_data['width'] ? ('width="' . $avatar_data['width'] . '" ') : '') .
4947              ($avatar_data['height'] ? ('height="' . $avatar_data['height'] . '" ') : '') .
4948              'alt="' . ((!empty($user->lang[$alt])) ? $user->lang[$alt] : $alt) . '" />';
4949      }
4950  
4951      /**
4952      * Event to modify HTML <img> tag of avatar
4953      *
4954      * @event core.get_avatar_after
4955      * @var    array    row                Row cleaned by \phpbb\avatar\manager::clean_row
4956      * @var    string    alt                Optional language string for alt tag within image, can be a language key or text
4957      * @var    bool    ignore_config    Ignores the config-setting, to be still able to view the avatar in the UCP
4958      * @var    array    avatar_data        The HTML attributes for avatar <img> tag
4959      * @var    string    html            The HTML <img> tag of generated avatar
4960      * @since 3.1.6-RC1
4961      */
4962      $vars = array('row', 'alt', 'ignore_config', 'avatar_data', 'html');
4963      extract($phpbb_dispatcher->trigger_event('core.get_avatar_after', compact($vars)));
4964  
4965      return $html;
4966  }
4967  
4968  /**
4969  * Generate page header
4970  */
4971  function page_header($page_title = '', $display_online_list = false, $item_id = 0, $item = 'forum', $send_headers = true)
4972  {
4973      global $db, $config, $template, $SID, $_SID, $_EXTRA_URL, $user, $auth, $phpEx, $phpbb_root_path;
4974      global $phpbb_dispatcher, $request, $phpbb_container, $phpbb_admin_path;
4975  
4976      if (defined('HEADER_INC'))
4977      {
4978          return;
4979      }
4980  
4981      define('HEADER_INC', true);
4982  
4983      // A listener can set this variable to `true` when it overrides this function
4984      $page_header_override = false;
4985  
4986      /**
4987      * Execute code and/or overwrite page_header()
4988      *
4989      * @event core.page_header
4990      * @var    string    page_title            Page title
4991      * @var    bool    display_online_list        Do we display online users list
4992      * @var    string    item                Restrict online users to a certain
4993      *                                    session item, e.g. forum for
4994      *                                    session_forum_id
4995      * @var    int        item_id                Restrict online users to item id
4996      * @var    bool    page_header_override    Shall we return instead of running
4997      *                                        the rest of page_header()
4998      * @since 3.1.0-a1
4999      */
5000      $vars = array('page_title', 'display_online_list', 'item_id', 'item', 'page_header_override');
5001      extract($phpbb_dispatcher->trigger_event('core.page_header', compact($vars)));
5002  
5003      if ($page_header_override)
5004      {
5005          return;
5006      }
5007  
5008      // gzip_compression
5009      if ($config['gzip_compress'])
5010      {
5011          // to avoid partially compressed output resulting in blank pages in
5012          // the browser or error messages, compression is disabled in a few cases:
5013          //
5014          // 1) if headers have already been sent, this indicates plaintext output
5015          //    has been started so further content must not be compressed
5016          // 2) the length of the current output buffer is non-zero. This means
5017          //    there is already some uncompressed content in this output buffer
5018          //    so further output must not be compressed
5019          // 3) if more than one level of output buffering is used because we
5020          //    cannot test all output buffer level content lengths. One level
5021          //    could be caused by php.ini output_buffering. Anything
5022          //    beyond that is manual, so the code wrapping phpBB in output buffering
5023          //    can easily compress the output itself.
5024          //
5025          if (@extension_loaded('zlib') && !headers_sent() && ob_get_level() <= 1 && ob_get_length() == 0)
5026          {
5027              ob_start('ob_gzhandler');
5028          }
5029      }
5030  
5031