[ Index ]

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