[ Index ]

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