Verzeichnisstruktur phpBB-3.3.15


Veröffentlicht
28.08.2024

So funktioniert es


Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück

Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

(Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

functions.php

Zuletzt modifiziert: 02.04.2025, 15:01 - Dateigröße: 147.31 KiB


0001  <?php
0002  /**
0003  *
0004  * This file is part of the phpBB Forum Software package.
0005  *
0006  * @copyright (c) phpBB Limited <https://www.phpbb.com>
0007  * @license GNU General Public License, version 2 (GPL-2.0)
0008  *
0009  * For full copyright and license information, please see
0010  * the docs/CREDITS.txt file.
0011  *
0012  */
0013   
0014  /**
0015  * @ignore
0016  */
0017  if (!defined('IN_PHPBB'))
0018  {
0019      exit;
0020  }
0021   
0022  // Common global functions
0023  /**
0024  * Generates an alphanumeric random string of given length
0025  *
0026  * @param int $num_chars Length of random string, defaults to 8.
0027  * This number should be less or equal than 64.
0028  *
0029  * @return string
0030  */
0031  function gen_rand_string($num_chars = 8)
0032  {
0033      $range = array_merge(range('A', 'Z'), range(0, 9));
0034      $size = count($range);
0035   
0036      $output = '';
0037      for ($i = 0; $i < $num_chars; $i++)
0038      {
0039          $rand = random_int(0, $size-1);
0040          $output .= $range[$rand];
0041      }
0042   
0043      return $output;
0044  }
0045   
0046  /**
0047  * Generates a user-friendly alphanumeric random string of given length
0048  * We remove 0 and O so users cannot confuse those in passwords etc.
0049  *
0050  * @param int $num_chars Length of random string, defaults to 8.
0051  * This number should be less or equal than 64.
0052  *
0053  * @return string
0054  */
0055  function gen_rand_string_friendly($num_chars = 8)
0056  {
0057      $range = array_merge(range('A', 'N'), range('P', 'Z'), range(1, 9));
0058      $size = count($range);
0059   
0060      $output = '';
0061      for ($i = 0; $i < $num_chars; $i++)
0062      {
0063          $rand = random_int(0, $size-1);
0064          $output .= $range[$rand];
0065      }
0066   
0067      return $output;
0068  }
0069   
0070  /**
0071  * Return unique id
0072  */
0073  function unique_id()
0074  {
0075      return strtolower(gen_rand_string(16));
0076  }
0077   
0078  /**
0079  * Wrapper for mt_rand() which allows swapping $min and $max parameters.
0080  *
0081  * PHP does not allow us to swap the order of the arguments for mt_rand() anymore.
0082  * (since PHP 5.3.4, see http://bugs.php.net/46587)
0083  *
0084  * @param int $min        Lowest value to be returned
0085  * @param int $max        Highest value to be returned
0086  *
0087  * @return int            Random integer between $min and $max (or $max and $min)
0088  */
0089  function phpbb_mt_rand($min, $max)
0090  {
0091      return ($min > $max) ? mt_rand($max, $min) : mt_rand($min, $max);
0092  }
0093   
0094  /**
0095  * Wrapper for getdate() which returns the equivalent array for UTC timestamps.
0096  *
0097  * @param int $time        Unix timestamp (optional)
0098  *
0099  * @return array            Returns an associative array of information related to the timestamp.
0100  *                        See http://www.php.net/manual/en/function.getdate.php
0101  */
0102  function phpbb_gmgetdate($time = false)
0103  {
0104      if ($time === false)
0105      {
0106          $time = time();
0107      }
0108   
0109      // getdate() interprets timestamps in local time.
0110      // So use UTC timezone temporarily to get UTC date info array.
0111      $current_timezone = date_default_timezone_get();
0112   
0113      // Set UTC timezone and get respective date info
0114      date_default_timezone_set('UTC');
0115      $date_info = getdate($time);
0116   
0117      // Restore timezone back
0118      date_default_timezone_set($current_timezone);
0119   
0120      return $date_info;
0121  }
0122   
0123  /**
0124  * Return formatted string for filesizes
0125  *
0126  * @param mixed    $value            filesize in bytes
0127  *                                (non-negative number; int, float or string)
0128  * @param bool    $string_only    true if language string should be returned
0129  * @param array    $allowed_units    only allow these units (data array indexes)
0130  *
0131  * @return mixed                    data array if $string_only is false
0132  */
0133  function get_formatted_filesize($value, $string_only = true, $allowed_units = false)
0134  {
0135      global $user;
0136   
0137      $available_units = array(
0138          'tb' => array(
0139              'min'         => 1099511627776, // pow(2, 40)
0140              'index'        => 4,
0141              'si_unit'    => 'TB',
0142              'iec_unit'    => 'TIB',
0143          ),
0144          'gb' => array(
0145              'min'         => 1073741824, // pow(2, 30)
0146              'index'        => 3,
0147              'si_unit'    => 'GB',
0148              'iec_unit'    => 'GIB',
0149          ),
0150          'mb' => array(
0151              'min'        => 1048576, // pow(2, 20)
0152              'index'        => 2,
0153              'si_unit'    => 'MB',
0154              'iec_unit'    => 'MIB',
0155          ),
0156          'kb' => array(
0157              'min'        => 1024, // pow(2, 10)
0158              'index'        => 1,
0159              'si_unit'    => 'KB',
0160              'iec_unit'    => 'KIB',
0161          ),
0162          'b' => array(
0163              'min'        => 0,
0164              'index'        => 0,
0165              'si_unit'    => 'BYTES', // Language index
0166              'iec_unit'    => 'BYTES',  // Language index
0167          ),
0168      );
0169   
0170      foreach ($available_units as $si_identifier => $unit_info)
0171      {
0172          if (!empty($allowed_units) && $si_identifier != 'b' && !in_array($si_identifier, $allowed_units))
0173          {
0174              continue;
0175          }
0176   
0177          if ($value >= $unit_info['min'])
0178          {
0179              $unit_info['si_identifier'] = $si_identifier;
0180   
0181              break;
0182          }
0183      }
0184      unset($available_units);
0185   
0186      for ($i = 0; $i < $unit_info['index']; $i++)
0187      {
0188          $value /= 1024;
0189      }
0190      $value = round($value, 2);
0191   
0192      // Lookup units in language dictionary
0193      $unit_info['si_unit'] = (isset($user->lang[$unit_info['si_unit']])) ? $user->lang[$unit_info['si_unit']] : $unit_info['si_unit'];
0194      $unit_info['iec_unit'] = (isset($user->lang[$unit_info['iec_unit']])) ? $user->lang[$unit_info['iec_unit']] : $unit_info['iec_unit'];
0195   
0196      // Default to IEC
0197      $unit_info['unit'] = $unit_info['iec_unit'];
0198   
0199      if (!$string_only)
0200      {
0201          $unit_info['value'] = $value;
0202   
0203          return $unit_info;
0204      }
0205   
0206      return $value  . ' ' . $unit_info['unit'];
0207  }
0208   
0209  /**
0210  * Determine whether we are approaching the maximum execution time. Should be called once
0211  * at the beginning of the script in which it's used.
0212  * @return    bool    Either true if the maximum execution time is nearly reached, or false
0213  *                    if some time is still left.
0214  */
0215  function still_on_time($extra_time = 15)
0216  {
0217      static $max_execution_time, $start_time;
0218   
0219      $current_time = microtime(true);
0220   
0221      if (empty($max_execution_time))
0222      {
0223          $max_execution_time = (function_exists('ini_get')) ? (int) @ini_get('max_execution_time') : (int) @get_cfg_var('max_execution_time');
0224   
0225          // If zero, then set to something higher to not let the user catch the ten seconds barrier.
0226          if ($max_execution_time === 0)
0227          {
0228              $max_execution_time = 50 + $extra_time;
0229          }
0230   
0231          $max_execution_time = min(max(10, ($max_execution_time - $extra_time)), 50);
0232   
0233          // For debugging purposes
0234          // $max_execution_time = 10;
0235   
0236          global $starttime;
0237          $start_time = (empty($starttime)) ? $current_time : $starttime;
0238      }
0239   
0240      return (ceil($current_time - $start_time) < $max_execution_time) ? true : false;
0241  }
0242   
0243  /**
0244  * Wrapper for version_compare() that allows using uppercase A and B
0245  * for alpha and beta releases.
0246  *
0247  * See http://www.php.net/manual/en/function.version-compare.php
0248  *
0249  * @param string $version1        First version number
0250  * @param string $version2        Second version number
0251  * @param string $operator        Comparison operator (optional)
0252  *
0253  * @return mixed                    Boolean (true, false) if comparison operator is specified.
0254  *                                Integer (-1, 0, 1) otherwise.
0255  */
0256  function phpbb_version_compare($version1, $version2, $operator = null)
0257  {
0258      $version1 = strtolower($version1);
0259      $version2 = strtolower($version2);
0260   
0261      if (is_null($operator))
0262      {
0263          return version_compare($version1, $version2);
0264      }
0265      else
0266      {
0267          return version_compare($version1, $version2, $operator);
0268      }
0269  }
0270   
0271  // functions used for building option fields
0272   
0273  /**
0274   * Pick a language, any language ...
0275   *
0276   * @param string $default    Language ISO code to be selected by default in the dropdown list
0277   * @param array $langdata    Language data in format of array(array('lang_iso' => string, lang_local_name => string), ...)
0278   *
0279   * @return string            HTML options for language selection dropdown list.
0280   */
0281  function language_select($default = '', array $langdata = [])
0282  {
0283      global $db;
0284   
0285      if (empty($langdata))
0286      {
0287          $sql = 'SELECT lang_iso, lang_local_name
0288              FROM ' . LANG_TABLE . '
0289              ORDER BY lang_english_name';
0290          $result = $db->sql_query($sql);
0291          $langdata = (array) $db->sql_fetchrowset($result);
0292          $db->sql_freeresult($result);
0293      }
0294   
0295      $lang_options = '';
0296      foreach ($langdata as $row)
0297      {
0298          $selected = ($row['lang_iso'] == $default) ? ' selected="selected"' : '';
0299          $lang_options .= '<option value="' . $row['lang_iso'] . '"' . $selected . '>' . $row['lang_local_name'] . '</option>';
0300      }
0301   
0302      return $lang_options;
0303  }
0304   
0305  /**
0306   * Pick a template/theme combo
0307   *
0308   * @param string $default    Style ID to be selected by default in the dropdown list
0309   * @param bool $all            Flag indicating if all styles data including inactive ones should be fetched
0310   * @param array $styledata    Style data in format of array(array('style_id' => int, style_name => string), ...)
0311   *
0312   * @return string            HTML options for style selection dropdown list.
0313   */
0314  function style_select($default = '', $all = false, array $styledata = [])
0315  {
0316      global $db;
0317   
0318      if (empty($styledata))
0319      {
0320          $sql_where = (!$all) ? 'WHERE style_active = 1 ' : '';
0321          $sql = 'SELECT style_id, style_name
0322              FROM ' . STYLES_TABLE . "
0323              $sql_where
0324              ORDER BY style_name";
0325          $result = $db->sql_query($sql);
0326          $styledata = (array) $db->sql_fetchrowset($result);
0327          $db->sql_freeresult($result);
0328      }
0329   
0330      $style_options = '';
0331      foreach ($styledata as $row)
0332      {
0333          $selected = ($row['style_id'] == $default) ? ' selected="selected"' : '';
0334          $style_options .= '<option value="' . $row['style_id'] . '"' . $selected . '>' . $row['style_name'] . '</option>';
0335      }
0336   
0337      return $style_options;
0338  }
0339   
0340  /**
0341  * Format the timezone offset with hours and minutes
0342  *
0343  * @param    int        $tz_offset    Timezone offset in seconds
0344  * @param    bool    $show_null    Whether null offsets should be shown
0345  * @return    string        Normalized offset string:    -7200 => -02:00
0346  *                                                    16200 => +04:30
0347  */
0348  function phpbb_format_timezone_offset($tz_offset, $show_null = false)
0349  {
0350      $sign = ($tz_offset < 0) ? '-' : '+';
0351      $time_offset = abs($tz_offset);
0352   
0353      if ($time_offset == 0 && $show_null == false)
0354      {
0355          return '';
0356      }
0357   
0358      $offset_seconds    = $time_offset % 3600;
0359      $offset_minutes    = $offset_seconds / 60;
0360      $offset_hours    = ($time_offset - $offset_seconds) / 3600;
0361   
0362      $offset_string    = sprintf("%s%02d:%02d", $sign, $offset_hours, $offset_minutes);
0363      return $offset_string;
0364  }
0365   
0366  /**
0367  * Compares two time zone labels.
0368  * Arranges them in increasing order by timezone offset.
0369  * Places UTC before other timezones in the same offset.
0370  */
0371  function phpbb_tz_select_compare($a, $b)
0372  {
0373      $a_sign = $a[3];
0374      $b_sign = $b[3];
0375      if ($a_sign != $b_sign)
0376      {
0377          return $a_sign == '-' ? -1 : 1;
0378      }
0379   
0380      $a_offset = substr($a, 4, 5);
0381      $b_offset = substr($b, 4, 5);
0382      if ($a_offset == $b_offset)
0383      {
0384          $a_name = substr($a, 12);
0385          $b_name = substr($b, 12);
0386          if ($a_name == $b_name)
0387          {
0388              return 0;
0389          }
0390          else if ($a_name == 'UTC')
0391          {
0392              return -1;
0393          }
0394          else if ($b_name == 'UTC')
0395          {
0396              return 1;
0397          }
0398          else
0399          {
0400              return $a_name < $b_name ? -1 : 1;
0401          }
0402      }
0403      else
0404      {
0405          if ($a_sign == '-')
0406          {
0407              return $a_offset > $b_offset ? -1 : 1;
0408          }
0409          else
0410          {
0411              return $a_offset < $b_offset ? -1 : 1;
0412          }
0413      }
0414  }
0415   
0416  /**
0417  * Return list of timezone identifiers
0418  * We also add the selected timezone if we can create an object with it.
0419  * DateTimeZone::listIdentifiers seems to not add all identifiers to the list,
0420  * because some are only kept for backward compatible reasons. If the user has
0421  * a deprecated value, we add it here, so it can still be kept. Once the user
0422  * changed his value, there is no way back to deprecated values.
0423  *
0424  * @param    string        $selected_timezone        Additional timezone that shall
0425  *                                                be added to the list of identiers
0426  * @return        array        DateTimeZone::listIdentifiers and additional
0427  *                            selected_timezone if it is a valid timezone.
0428  */
0429  function phpbb_get_timezone_identifiers($selected_timezone)
0430  {
0431      $timezones = DateTimeZone::listIdentifiers();
0432   
0433      if (!in_array($selected_timezone, $timezones))
0434      {
0435          try
0436          {
0437              // Add valid timezones that are currently selected but not returned
0438              // by DateTimeZone::listIdentifiers
0439              $validate_timezone = new DateTimeZone($selected_timezone);
0440              $timezones[] = $selected_timezone;
0441          }
0442          catch (\Exception $e)
0443          {
0444          }
0445      }
0446   
0447      return $timezones;
0448  }
0449   
0450  /**
0451  * Options to pick a timezone and date/time
0452  *
0453  * @param    \phpbb\template\template $template    phpBB template object
0454  * @param    \phpbb\user    $user                Object of the current user
0455  * @param    string        $default            A timezone to select
0456  * @param    boolean        $truncate            Shall we truncate the options text
0457  *
0458  * @return        array        Returns an array containing the options for the time selector.
0459  */
0460  function phpbb_timezone_select($template, $user, $default = '', $truncate = false)
0461  {
0462      static $timezones;
0463   
0464      $default_offset = '';
0465      if (!isset($timezones))
0466      {
0467          $unsorted_timezones = phpbb_get_timezone_identifiers($default);
0468   
0469          $timezones = array();
0470          foreach ($unsorted_timezones as $timezone)
0471          {
0472              $tz = new DateTimeZone($timezone);
0473              $dt = $user->create_datetime('now', $tz);
0474              $offset = $dt->getOffset();
0475              $current_time = $dt->format($user->lang['DATETIME_FORMAT'], true);
0476              $offset_string = phpbb_format_timezone_offset($offset, true);
0477              $timezones['UTC' . $offset_string . ' - ' . $timezone] = array(
0478                  'tz'        => $timezone,
0479                  'offset'    => $offset_string,
0480                  'current'    => $current_time,
0481              );
0482              if ($timezone === $default)
0483              {
0484                  $default_offset = 'UTC' . $offset_string;
0485              }
0486          }
0487          unset($unsorted_timezones);
0488   
0489          uksort($timezones, 'phpbb_tz_select_compare');
0490      }
0491   
0492      $tz_select = $opt_group = '';
0493   
0494      foreach ($timezones as $key => $timezone)
0495      {
0496          if ($opt_group != $timezone['offset'])
0497          {
0498              // Generate tz_select for backwards compatibility
0499              $tz_select .= ($opt_group) ? '</optgroup>' : '';
0500              $tz_select .= '<optgroup label="' . $user->lang(array('timezones', 'UTC_OFFSET_CURRENT'), $timezone['offset'], $timezone['current']) . '">';
0501              $opt_group = $timezone['offset'];
0502              $template->assign_block_vars('timezone_select', array(
0503                  'LABEL'        => $user->lang(array('timezones', 'UTC_OFFSET_CURRENT'), $timezone['offset'], $timezone['current']),
0504                  'VALUE'        => $key . ' - ' . $timezone['current'],
0505              ));
0506   
0507              $selected = (!empty($default_offset) && strpos($key, $default_offset) !== false) ? ' selected="selected"' : '';
0508              $template->assign_block_vars('timezone_date', array(
0509                  'VALUE'        => $key . ' - ' . $timezone['current'],
0510                  'SELECTED'    => !empty($selected),
0511                  'TITLE'        => $user->lang(array('timezones', 'UTC_OFFSET_CURRENT'), $timezone['offset'], $timezone['current']),
0512              ));
0513          }
0514   
0515          $label = $timezone['tz'];
0516          if (isset($user->lang['timezones'][$label]))
0517          {
0518              $label = $user->lang['timezones'][$label];
0519          }
0520          $title = $user->lang(array('timezones', 'UTC_OFFSET_CURRENT'), $timezone['offset'], $label);
0521   
0522          if ($truncate)
0523          {
0524              $label = truncate_string($label, 50, 255, false, '...');
0525          }
0526   
0527          // Also generate timezone_select for backwards compatibility
0528          $selected = ($timezone['tz'] === $default) ? ' selected="selected"' : '';
0529          $tz_select .= '<option title="' . $title . '" value="' . $timezone['tz'] . '"' . $selected . '>' . $label . '</option>';
0530          $template->assign_block_vars('timezone_select.timezone_options', array(
0531              'TITLE'            => $title,
0532              'VALUE'            => $timezone['tz'],
0533              'SELECTED'        => !empty($selected),
0534              'LABEL'            => $label,
0535          ));
0536      }
0537      $tz_select .= '</optgroup>';
0538   
0539      return $tz_select;
0540  }
0541   
0542  // Functions handling topic/post tracking/marking
0543   
0544  /**
0545  * Marks a topic/forum as read
0546  * Marks a topic as posted to
0547  *
0548  * @param string $mode (all, topics, topic, post)
0549  * @param int|bool $forum_id Used in all, topics, and topic mode
0550  * @param int|bool $topic_id Used in topic and post mode
0551  * @param int $post_time 0 means current time(), otherwise to set a specific mark time
0552  * @param int $user_id can only be used with $mode == 'post'
0553  */
0554  function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $user_id = 0)
0555  {
0556      global $db, $user, $config;
0557      global $request, $phpbb_container, $phpbb_dispatcher;
0558   
0559      $post_time = ($post_time === 0 || $post_time > time()) ? time() : (int) $post_time;
0560   
0561      $should_markread = true;
0562   
0563      /**
0564       * This event is used for performing actions directly before marking forums,
0565       * topics or posts as read.
0566       *
0567       * It is also possible to prevent the marking. For that, the $should_markread parameter
0568       * should be set to FALSE.
0569       *
0570       * @event core.markread_before
0571       * @var    string    mode                Variable containing marking mode value
0572       * @var    mixed    forum_id            Variable containing forum id, or false
0573       * @var    mixed    topic_id            Variable containing topic id, or false
0574       * @var    int        post_time            Variable containing post time
0575       * @var    int        user_id                Variable containing the user id
0576       * @var    bool    should_markread        Flag indicating if the markread should be done or not.
0577       * @since 3.1.4-RC1
0578       */
0579      $vars = array(
0580          'mode',
0581          'forum_id',
0582          'topic_id',
0583          'post_time',
0584          'user_id',
0585          'should_markread',
0586      );
0587      extract($phpbb_dispatcher->trigger_event('core.markread_before', compact($vars)));
0588   
0589      if (!$should_markread)
0590      {
0591          return;
0592      }
0593   
0594      if ($mode == 'all')
0595      {
0596          if (empty($forum_id))
0597          {
0598              // Mark all forums read (index page)
0599              /* @var $phpbb_notifications \phpbb\notification\manager */
0600              $phpbb_notifications = $phpbb_container->get('notification_manager');
0601   
0602              // Mark all topic notifications read for this user
0603              $phpbb_notifications->mark_notifications(array(
0604                  'notification.type.topic',
0605                  'notification.type.quote',
0606                  'notification.type.bookmark',
0607                  'notification.type.post',
0608                  'notification.type.approve_topic',
0609                  'notification.type.approve_post',
0610                  'notification.type.forum',
0611              ), false, $user->data['user_id'], $post_time);
0612   
0613              if ($config['load_db_lastread'] && $user->data['is_registered'])
0614              {
0615                  // Mark all forums read (index page)
0616                  $tables = array(TOPICS_TRACK_TABLE, FORUMS_TRACK_TABLE);
0617                  foreach ($tables as $table)
0618                  {
0619                      $sql = 'DELETE FROM ' . $table . "
0620                          WHERE user_id = {$user->data['user_id']}
0621                              AND mark_time < $post_time";
0622                      $db->sql_query($sql);
0623                  }
0624   
0625                  $sql = 'UPDATE ' . USERS_TABLE . "
0626                      SET user_lastmark = $post_time
0627                      WHERE user_id = {$user->data['user_id']}
0628                          AND user_lastmark < $post_time";
0629                  $db->sql_query($sql);
0630              }
0631              else if ($config['load_anon_lastread'] || $user->data['is_registered'])
0632              {
0633                  $tracking_topics = $request->variable($config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE);
0634                  $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
0635   
0636                  unset($tracking_topics['tf']);
0637                  unset($tracking_topics['t']);
0638                  unset($tracking_topics['f']);
0639                  $tracking_topics['l'] = base_convert($post_time - $config['board_startdate'], 10, 36);
0640   
0641                  $user->set_cookie('track', tracking_serialize($tracking_topics), $post_time + 31536000);
0642                  $request->overwrite($config['cookie_name'] . '_track', tracking_serialize($tracking_topics), \phpbb\request\request_interface::COOKIE);
0643   
0644                  unset($tracking_topics);
0645   
0646                  if ($user->data['is_registered'])
0647                  {
0648                      $sql = 'UPDATE ' . USERS_TABLE . "
0649                          SET user_lastmark = $post_time
0650                          WHERE user_id = {$user->data['user_id']}
0651                              AND user_lastmark < $post_time";
0652                      $db->sql_query($sql);
0653                  }
0654              }
0655          }
0656      }
0657      else if ($mode == 'topics')
0658      {
0659          // Mark all topics in forums read
0660          if (!is_array($forum_id))
0661          {
0662              $forum_id = array($forum_id);
0663          }
0664          else
0665          {
0666              $forum_id = array_unique($forum_id);
0667          }
0668   
0669          /* @var $phpbb_notifications \phpbb\notification\manager */
0670          $phpbb_notifications = $phpbb_container->get('notification_manager');
0671   
0672          $phpbb_notifications->mark_notifications_by_parent(array(
0673              'notification.type.topic',
0674              'notification.type.approve_topic',
0675          ), $forum_id, $user->data['user_id'], $post_time);
0676   
0677          // Mark all post/quote notifications read for this user in this forum
0678          $topic_ids = array();
0679          $sql = 'SELECT topic_id
0680              FROM ' . TOPICS_TABLE . '
0681              WHERE ' . $db->sql_in_set('forum_id', $forum_id);
0682          $result = $db->sql_query($sql);
0683          while ($row = $db->sql_fetchrow($result))
0684          {
0685              $topic_ids[] = $row['topic_id'];
0686          }
0687          $db->sql_freeresult($result);
0688   
0689          $phpbb_notifications->mark_notifications_by_parent(array(
0690              'notification.type.quote',
0691              'notification.type.bookmark',
0692              'notification.type.post',
0693              'notification.type.approve_post',
0694              'notification.type.forum',
0695          ), $topic_ids, $user->data['user_id'], $post_time);
0696   
0697          // Add 0 to forums array to mark global announcements correctly
0698          // $forum_id[] = 0;
0699   
0700          if ($config['load_db_lastread'] && $user->data['is_registered'])
0701          {
0702              $sql = 'DELETE FROM ' . TOPICS_TRACK_TABLE . "
0703                  WHERE user_id = {$user->data['user_id']}
0704                      AND mark_time < $post_time
0705                      AND " . $db->sql_in_set('forum_id', $forum_id);
0706              $db->sql_query($sql);
0707   
0708              $sql = 'SELECT forum_id
0709                  FROM ' . FORUMS_TRACK_TABLE . "
0710                  WHERE user_id = {$user->data['user_id']}
0711                      AND " . $db->sql_in_set('forum_id', $forum_id);
0712              $result = $db->sql_query($sql);
0713   
0714              $sql_update = array();
0715              while ($row = $db->sql_fetchrow($result))
0716              {
0717                  $sql_update[] = (int) $row['forum_id'];
0718              }
0719              $db->sql_freeresult($result);
0720   
0721              if (count($sql_update))
0722              {
0723                  $sql = 'UPDATE ' . FORUMS_TRACK_TABLE . "
0724                      SET mark_time = $post_time
0725                      WHERE user_id = {$user->data['user_id']}
0726                          AND mark_time < $post_time
0727                          AND " . $db->sql_in_set('forum_id', $sql_update);
0728                  $db->sql_query($sql);
0729              }
0730   
0731              if ($sql_insert = array_diff($forum_id, $sql_update))
0732              {
0733                  $sql_ary = array();
0734                  foreach ($sql_insert as $f_id)
0735                  {
0736                      $sql_ary[] = array(
0737                          'user_id'    => (int) $user->data['user_id'],
0738                          'forum_id'    => (int) $f_id,
0739                          'mark_time'    => $post_time,
0740                      );
0741                  }
0742   
0743                  $db->sql_multi_insert(FORUMS_TRACK_TABLE, $sql_ary);
0744              }
0745          }
0746          else if ($config['load_anon_lastread'] || $user->data['is_registered'])
0747          {
0748              $tracking = $request->variable($config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE);
0749              $tracking = ($tracking) ? tracking_unserialize($tracking) : array();
0750   
0751              foreach ($forum_id as $f_id)
0752              {
0753                  $topic_ids36 = (isset($tracking['tf'][$f_id])) ? $tracking['tf'][$f_id] : array();
0754   
0755                  if (isset($tracking['tf'][$f_id]))
0756                  {
0757                      unset($tracking['tf'][$f_id]);
0758                  }
0759   
0760                  foreach ($topic_ids36 as $topic_id36)
0761                  {
0762                      unset($tracking['t'][$topic_id36]);
0763                  }
0764   
0765                  if (isset($tracking['f'][$f_id]))
0766                  {
0767                      unset($tracking['f'][$f_id]);
0768                  }
0769   
0770                  $tracking['f'][$f_id] = base_convert($post_time - $config['board_startdate'], 10, 36);
0771              }
0772   
0773              if (isset($tracking['tf']) && empty($tracking['tf']))
0774              {
0775                  unset($tracking['tf']);
0776              }
0777   
0778              $user->set_cookie('track', tracking_serialize($tracking), $post_time + 31536000);
0779              $request->overwrite($config['cookie_name'] . '_track', tracking_serialize($tracking), \phpbb\request\request_interface::COOKIE);
0780   
0781              unset($tracking);
0782          }
0783      }
0784      else if ($mode == 'topic')
0785      {
0786          if ($topic_id === false || $forum_id === false)
0787          {
0788              return;
0789          }
0790   
0791          /* @var $phpbb_notifications \phpbb\notification\manager */
0792          $phpbb_notifications = $phpbb_container->get('notification_manager');
0793   
0794          // Mark post notifications read for this user in this topic
0795          $phpbb_notifications->mark_notifications(array(
0796              'notification.type.topic',
0797              'notification.type.approve_topic',
0798          ), $topic_id, $user->data['user_id'], $post_time);
0799   
0800          $phpbb_notifications->mark_notifications_by_parent(array(
0801              'notification.type.quote',
0802              'notification.type.bookmark',
0803              'notification.type.post',
0804              'notification.type.approve_post',
0805              'notification.type.forum',
0806          ), $topic_id, $user->data['user_id'], $post_time);
0807   
0808          if ($config['load_db_lastread'] && $user->data['is_registered'])
0809          {
0810              $sql = 'UPDATE ' . TOPICS_TRACK_TABLE . "
0811                  SET mark_time = $post_time
0812                  WHERE user_id = {$user->data['user_id']}
0813                      AND mark_time < $post_time
0814                      AND topic_id = $topic_id";
0815              $db->sql_query($sql);
0816   
0817              // insert row
0818              if (!$db->sql_affectedrows())
0819              {
0820                  $db->sql_return_on_error(true);
0821   
0822                  $sql_ary = array(
0823                      'user_id'        => (int) $user->data['user_id'],
0824                      'topic_id'        => (int) $topic_id,
0825                      'forum_id'        => (int) $forum_id,
0826                      'mark_time'        => $post_time,
0827                  );
0828   
0829                  $db->sql_query('INSERT INTO ' . TOPICS_TRACK_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
0830   
0831                  $db->sql_return_on_error(false);
0832              }
0833          }
0834          else if ($config['load_anon_lastread'] || $user->data['is_registered'])
0835          {
0836              $tracking = $request->variable($config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE);
0837              $tracking = ($tracking) ? tracking_unserialize($tracking) : array();
0838   
0839              $topic_id36 = base_convert($topic_id, 10, 36);
0840   
0841              if (!isset($tracking['t'][$topic_id36]))
0842              {
0843                  $tracking['tf'][$forum_id][$topic_id36] = true;
0844              }
0845   
0846              $tracking['t'][$topic_id36] = base_convert($post_time - (int) $config['board_startdate'], 10, 36);
0847   
0848              // If the cookie grows larger than 10000 characters we will remove the smallest value
0849              // This can result in old topics being unread - but most of the time it should be accurate...
0850              if (strlen($request->variable($config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE)) > 10000)
0851              {
0852                  //echo 'Cookie grown too large' . print_r($tracking, true);
0853   
0854                  // We get the ten most minimum stored time offsets and its associated topic ids
0855                  $time_keys = array();
0856                  for ($i = 0; $i < 10 && count($tracking['t']); $i++)
0857                  {
0858                      $min_value = min($tracking['t']);
0859                      $m_tkey = array_search($min_value, $tracking['t']);
0860                      unset($tracking['t'][$m_tkey]);
0861   
0862                      $time_keys[$m_tkey] = $min_value;
0863                  }
0864   
0865                  // Now remove the topic ids from the array...
0866                  foreach ($tracking['tf'] as $f_id => $topic_id_ary)
0867                  {
0868                      foreach ($time_keys as $m_tkey => $min_value)
0869                      {
0870                          if (isset($topic_id_ary[$m_tkey]))
0871                          {
0872                              $tracking['f'][$f_id] = $min_value;
0873                              unset($tracking['tf'][$f_id][$m_tkey]);
0874                          }
0875                      }
0876                  }
0877   
0878                  if ($user->data['is_registered'])
0879                  {
0880                      $user->data['user_lastmark'] = intval(base_convert(max($time_keys) + $config['board_startdate'], 36, 10));
0881   
0882                      $sql = 'UPDATE ' . USERS_TABLE . "
0883                          SET user_lastmark = $post_time
0884                          WHERE user_id = {$user->data['user_id']}
0885                              AND mark_time < $post_time";
0886                      $db->sql_query($sql);
0887                  }
0888                  else
0889                  {
0890                      $tracking['l'] = max($time_keys);
0891                  }
0892              }
0893   
0894              $user->set_cookie('track', tracking_serialize($tracking), $post_time + 31536000);
0895              $request->overwrite($config['cookie_name'] . '_track', tracking_serialize($tracking), \phpbb\request\request_interface::COOKIE);
0896          }
0897      }
0898      else if ($mode == 'post')
0899      {
0900          if ($topic_id === false)
0901          {
0902              return;
0903          }
0904   
0905          $use_user_id = (!$user_id) ? $user->data['user_id'] : $user_id;
0906   
0907          if ($config['load_db_track'] && $use_user_id != ANONYMOUS)
0908          {
0909              $db->sql_return_on_error(true);
0910   
0911              $sql_ary = array(
0912                  'user_id'        => (int) $use_user_id,
0913                  'topic_id'        => (int) $topic_id,
0914                  'topic_posted'    => 1,
0915              );
0916   
0917              $db->sql_query('INSERT INTO ' . TOPICS_POSTED_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
0918   
0919              $db->sql_return_on_error(false);
0920          }
0921      }
0922   
0923      /**
0924       * This event is used for performing actions directly after forums,
0925       * topics or posts have been marked as read.
0926       *
0927       * @event core.markread_after
0928       * @var    string        mode                Variable containing marking mode value
0929       * @var    mixed        forum_id            Variable containing forum id, or false
0930       * @var    mixed        topic_id            Variable containing topic id, or false
0931       * @var    int            post_time            Variable containing post time
0932       * @var    int            user_id                Variable containing the user id
0933       * @since 3.2.6-RC1
0934       */
0935      $vars = array(
0936          'mode',
0937          'forum_id',
0938          'topic_id',
0939          'post_time',
0940          'user_id',
0941      );
0942      extract($phpbb_dispatcher->trigger_event('core.markread_after', compact($vars)));
0943  }
0944   
0945  /**
0946  * Get topic tracking info by using already fetched info
0947  */
0948  function get_topic_tracking($forum_id, $topic_ids, &$rowset, $forum_mark_time, $global_announce_list = false)
0949  {
0950      global $user;
0951   
0952      $last_read = array();
0953   
0954      if (!is_array($topic_ids))
0955      {
0956          $topic_ids = array($topic_ids);
0957      }
0958   
0959      foreach ($topic_ids as $topic_id)
0960      {
0961          if (!empty($rowset[$topic_id]['mark_time']))
0962          {
0963              $last_read[$topic_id] = $rowset[$topic_id]['mark_time'];
0964          }
0965      }
0966   
0967      $topic_ids = array_diff($topic_ids, array_keys($last_read));
0968   
0969      if (count($topic_ids))
0970      {
0971          $mark_time = array();
0972   
0973          if (!empty($forum_mark_time[$forum_id]) && $forum_mark_time[$forum_id] !== false)
0974          {
0975              $mark_time[$forum_id] = $forum_mark_time[$forum_id];
0976          }
0977   
0978          $user_lastmark = (isset($mark_time[$forum_id])) ? $mark_time[$forum_id] : $user->data['user_lastmark'];
0979   
0980          foreach ($topic_ids as $topic_id)
0981          {
0982              $last_read[$topic_id] = $user_lastmark;
0983          }
0984      }
0985   
0986      return $last_read;
0987  }
0988   
0989  /**
0990  * Get topic tracking info from db (for cookie based tracking only this function is used)
0991  */
0992  function get_complete_topic_tracking($forum_id, $topic_ids, $global_announce_list = false)
0993  {
0994      global $config, $user, $request;
0995   
0996      $last_read = array();
0997   
0998      if (!is_array($topic_ids))
0999      {
1000          $topic_ids = array($topic_ids);
1001      }
1002   
1003      if ($config['load_db_lastread'] && $user->data['is_registered'])
1004      {
1005          global $db;
1006   
1007          $sql = 'SELECT topic_id, mark_time
1008              FROM ' . TOPICS_TRACK_TABLE . "
1009              WHERE user_id = {$user->data['user_id']}
1010                  AND " . $db->sql_in_set('topic_id', $topic_ids);
1011          $result = $db->sql_query($sql);
1012   
1013          while ($row = $db->sql_fetchrow($result))
1014          {
1015              $last_read[$row['topic_id']] = $row['mark_time'];
1016          }
1017          $db->sql_freeresult($result);
1018   
1019          $topic_ids = array_diff($topic_ids, array_keys($last_read));
1020   
1021          if (count($topic_ids))
1022          {
1023              $sql = 'SELECT forum_id, mark_time
1024                  FROM ' . FORUMS_TRACK_TABLE . "
1025                  WHERE user_id = {$user->data['user_id']}
1026                      AND forum_id = $forum_id";
1027              $result = $db->sql_query($sql);
1028   
1029              $mark_time = array();
1030              while ($row = $db->sql_fetchrow($result))
1031              {
1032                  $mark_time[$row['forum_id']] = $row['mark_time'];
1033              }
1034              $db->sql_freeresult($result);
1035   
1036              $user_lastmark = (isset($mark_time[$forum_id])) ? $mark_time[$forum_id] : $user->data['user_lastmark'];
1037   
1038              foreach ($topic_ids as $topic_id)
1039              {
1040                  $last_read[$topic_id] = $user_lastmark;
1041              }
1042          }
1043      }
1044      else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1045      {
1046          global $tracking_topics;
1047   
1048          if (!isset($tracking_topics) || !count($tracking_topics))
1049          {
1050              $tracking_topics = $request->variable($config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE);
1051              $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
1052          }
1053   
1054          if (!$user->data['is_registered'])
1055          {
1056              $user_lastmark = (isset($tracking_topics['l'])) ? base_convert($tracking_topics['l'], 36, 10) + $config['board_startdate'] : 0;
1057          }
1058          else
1059          {
1060              $user_lastmark = $user->data['user_lastmark'];
1061          }
1062   
1063          foreach ($topic_ids as $topic_id)
1064          {
1065              $topic_id36 = base_convert($topic_id, 10, 36);
1066   
1067              if (isset($tracking_topics['t'][$topic_id36]))
1068              {
1069                  $last_read[$topic_id] = base_convert($tracking_topics['t'][$topic_id36], 36, 10) + $config['board_startdate'];
1070              }
1071          }
1072   
1073          $topic_ids = array_diff($topic_ids, array_keys($last_read));
1074   
1075          if (count($topic_ids))
1076          {
1077              $mark_time = array();
1078   
1079              if (isset($tracking_topics['f'][$forum_id]))
1080              {
1081                  $mark_time[$forum_id] = base_convert($tracking_topics['f'][$forum_id], 36, 10) + $config['board_startdate'];
1082              }
1083   
1084              $user_lastmark = (isset($mark_time[$forum_id])) ? $mark_time[$forum_id] : $user_lastmark;
1085   
1086              foreach ($topic_ids as $topic_id)
1087              {
1088                  $last_read[$topic_id] = $user_lastmark;
1089              }
1090          }
1091      }
1092   
1093      return $last_read;
1094  }
1095   
1096  /**
1097  * Get list of unread topics
1098  *
1099  * @param int $user_id            User ID (or false for current user)
1100  * @param string $sql_extra        Extra WHERE SQL statement
1101  * @param string $sql_sort        ORDER BY SQL sorting statement
1102  * @param string $sql_limit        Limits the size of unread topics list, 0 for unlimited query
1103  * @param string $sql_limit_offset  Sets the offset of the first row to search, 0 to search from the start
1104  *
1105  * @return int[]        Topic ids as keys, mark_time of topic as value
1106  */
1107  function get_unread_topics($user_id = false, $sql_extra = '', $sql_sort = '', $sql_limit = 1001, $sql_limit_offset = 0)
1108  {
1109      global $config, $db, $user, $request;
1110      global $phpbb_dispatcher;
1111   
1112      $user_id = ($user_id === false) ? (int) $user->data['user_id'] : (int) $user_id;
1113   
1114      // Data array we're going to return
1115      $unread_topics = array();
1116   
1117      if (empty($sql_sort))
1118      {
1119          $sql_sort = 'ORDER BY t.topic_last_post_time DESC, t.topic_last_post_id DESC';
1120      }
1121   
1122      if ($config['load_db_lastread'] && $user->data['is_registered'])
1123      {
1124          // Get list of the unread topics
1125          $last_mark = (int) $user->data['user_lastmark'];
1126   
1127          $sql_array = array(
1128              'SELECT'        => 't.topic_id, t.topic_last_post_time, tt.mark_time as topic_mark_time, ft.mark_time as forum_mark_time',
1129   
1130              'FROM'            => array(TOPICS_TABLE => 't'),
1131   
1132              'LEFT_JOIN'        => array(
1133                  array(
1134                      'FROM'    => array(TOPICS_TRACK_TABLE => 'tt'),
1135                      'ON'    => "tt.user_id = $user_id AND t.topic_id = tt.topic_id",
1136                  ),
1137                  array(
1138                      'FROM'    => array(FORUMS_TRACK_TABLE => 'ft'),
1139                      'ON'    => "ft.user_id = $user_id AND t.forum_id = ft.forum_id",
1140                  ),
1141              ),
1142   
1143              'WHERE'            => "
1144                   t.topic_last_post_time > $last_mark AND
1145                  (
1146                  (tt.mark_time IS NOT NULL AND t.topic_last_post_time > tt.mark_time) OR
1147                  (tt.mark_time IS NULL AND ft.mark_time IS NOT NULL AND t.topic_last_post_time > ft.mark_time) OR
1148                  (tt.mark_time IS NULL AND ft.mark_time IS NULL)
1149                  )
1150                  $sql_extra
1151                  $sql_sort",
1152          );
1153   
1154          /**
1155           * Change SQL query for fetching unread topics data
1156           *
1157           * @event core.get_unread_topics_modify_sql
1158           * @var array     sql_array    Fully assembled SQL query with keys SELECT, FROM, LEFT_JOIN, WHERE
1159           * @var int       last_mark    User's last_mark time
1160           * @var string    sql_extra    Extra WHERE SQL statement
1161           * @var string    sql_sort     ORDER BY SQL sorting statement
1162           * @since 3.1.4-RC1
1163           */
1164          $vars = array(
1165              'sql_array',
1166              'last_mark',
1167              'sql_extra',
1168              'sql_sort',
1169          );
1170          extract($phpbb_dispatcher->trigger_event('core.get_unread_topics_modify_sql', compact($vars)));
1171   
1172          $sql = $db->sql_build_query('SELECT', $sql_array);
1173          $result = $db->sql_query_limit($sql, $sql_limit, $sql_limit_offset);
1174   
1175          while ($row = $db->sql_fetchrow($result))
1176          {
1177              $topic_id = (int) $row['topic_id'];
1178              $unread_topics[$topic_id] = ($row['topic_mark_time']) ? (int) $row['topic_mark_time'] : (($row['forum_mark_time']) ? (int) $row['forum_mark_time'] : $last_mark);
1179          }
1180          $db->sql_freeresult($result);
1181      }
1182      else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1183      {
1184          global $tracking_topics;
1185   
1186          if (empty($tracking_topics))
1187          {
1188              $tracking_topics = $request->variable($config['cookie_name'] . '_track', '', false, \phpbb\request\request_interface::COOKIE);
1189              $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
1190          }
1191   
1192          if (!$user->data['is_registered'])
1193          {
1194              $user_lastmark = (isset($tracking_topics['l'])) ? base_convert($tracking_topics['l'], 36, 10) + $config['board_startdate'] : 0;
1195          }
1196          else
1197          {
1198              $user_lastmark = (int) $user->data['user_lastmark'];
1199          }
1200   
1201          $sql = 'SELECT t.topic_id, t.forum_id, t.topic_last_post_time
1202              FROM ' . TOPICS_TABLE . ' t
1203              WHERE t.topic_last_post_time > ' . $user_lastmark . "
1204              $sql_extra
1205              $sql_sort";
1206          $result = $db->sql_query_limit($sql, $sql_limit, $sql_limit_offset);
1207   
1208          while ($row = $db->sql_fetchrow($result))
1209          {
1210              $forum_id = (int) $row['forum_id'];
1211              $topic_id = (int) $row['topic_id'];
1212              $topic_id36 = base_convert($topic_id, 10, 36);
1213   
1214              if (isset($tracking_topics['t'][$topic_id36]))
1215              {
1216                  $last_read = base_convert($tracking_topics['t'][$topic_id36], 36, 10) + $config['board_startdate'];
1217   
1218                  if ($row['topic_last_post_time'] > $last_read)
1219                  {
1220                      $unread_topics[$topic_id] = $last_read;
1221                  }
1222              }
1223              else if (isset($tracking_topics['f'][$forum_id]))
1224              {
1225                  $mark_time = base_convert($tracking_topics['f'][$forum_id], 36, 10) + $config['board_startdate'];
1226   
1227                  if ($row['topic_last_post_time'] > $mark_time)
1228                  {
1229                      $unread_topics[$topic_id] = $mark_time;
1230                  }
1231              }
1232              else
1233              {
1234                  $unread_topics[$topic_id] = $user_lastmark;
1235              }
1236          }
1237          $db->sql_freeresult($result);
1238      }
1239   
1240      return $unread_topics;
1241  }
1242   
1243  /**
1244  * Check for read forums and update topic tracking info accordingly
1245  *
1246  * @param int $forum_id the forum id to check
1247  * @param int $forum_last_post_time the forums last post time
1248  * @param int $f_mark_time the forums last mark time if user is registered and load_db_lastread enabled
1249  * @param int $mark_time_forum false if the mark time needs to be obtained, else the last users forum mark time
1250  *
1251  * @return true if complete forum got marked read, else false.
1252  */
1253  function update_forum_tracking_info($forum_id, $forum_last_post_time, $f_mark_time = false, $mark_time_forum = false)
1254  {
1255      global $db, $tracking_topics, $user, $config, $request, $phpbb_container;
1256   
1257      // Determine the users last forum mark time if not given.
1258      if ($mark_time_forum === false)
1259      {
1260          if ($config['load_db_lastread'] && $user->data['is_registered'])
1261          {
1262              $mark_time_forum = (!empty($f_mark_time)) ? $f_mark_time : $user->data['user_lastmark'];
1263          }
1264          else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1265          {
1266              $tracking_topics = $request->variable($config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE);
1267              $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
1268   
1269              if (!$user->data['is_registered'])
1270              {
1271                  $user->data['user_lastmark'] = (isset($tracking_topics['l'])) ? (int) (base_convert($tracking_topics['l'], 36, 10) + $config['board_startdate']) : 0;
1272              }
1273   
1274              $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'];
1275          }
1276      }
1277   
1278      // Handle update of unapproved topics info.
1279      // Only update for moderators having m_approve permission for the forum.
1280      /* @var $phpbb_content_visibility \phpbb\content_visibility */
1281      $phpbb_content_visibility = $phpbb_container->get('content.visibility');
1282   
1283      // Check the forum for any left unread topics.
1284      // If there are none, we mark the forum as read.
1285      if ($config['load_db_lastread'] && $user->data['is_registered'])
1286      {
1287          if ($mark_time_forum >= $forum_last_post_time)
1288          {
1289              // We do not need to mark read, this happened before. Therefore setting this to true
1290              $row = true;
1291          }
1292          else
1293          {
1294              $sql = 'SELECT t.forum_id
1295                  FROM ' . TOPICS_TABLE . ' t
1296                  LEFT JOIN ' . TOPICS_TRACK_TABLE . ' tt
1297                      ON (tt.topic_id = t.topic_id
1298                          AND tt.user_id = ' . $user->data['user_id'] . ')
1299                  WHERE t.forum_id = ' . $forum_id . '
1300                      AND t.topic_last_post_time > ' . $mark_time_forum . '
1301                      AND t.topic_moved_id = 0
1302                      AND ' . $phpbb_content_visibility->get_visibility_sql('topic', $forum_id, 't.') . '
1303                      AND (tt.topic_id IS NULL
1304                          OR tt.mark_time < t.topic_last_post_time)';
1305              $result = $db->sql_query_limit($sql, 1);
1306              $row = $db->sql_fetchrow($result);
1307              $db->sql_freeresult($result);
1308          }
1309      }
1310      else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1311      {
1312          // Get information from cookie
1313          if (!isset($tracking_topics['tf'][$forum_id]))
1314          {
1315              // We do not need to mark read, this happened before. Therefore setting this to true
1316              $row = true;
1317          }
1318          else
1319          {
1320              $sql = 'SELECT t.topic_id
1321                  FROM ' . TOPICS_TABLE . ' t
1322                  WHERE t.forum_id = ' . $forum_id . '
1323                      AND t.topic_last_post_time > ' . $mark_time_forum . '
1324                      AND t.topic_moved_id = 0
1325                      AND ' . $phpbb_content_visibility->get_visibility_sql('topic', $forum_id, 't.');
1326              $result = $db->sql_query($sql);
1327   
1328              $check_forum = $tracking_topics['tf'][$forum_id];
1329              $unread = false;
1330   
1331              while ($row = $db->sql_fetchrow($result))
1332              {
1333                  if (!isset($check_forum[base_convert($row['topic_id'], 10, 36)]))
1334                  {
1335                      $unread = true;
1336                      break;
1337                  }
1338              }
1339              $db->sql_freeresult($result);
1340   
1341              $row = $unread;
1342          }
1343      }
1344      else
1345      {
1346          $row = true;
1347      }
1348   
1349      if (!$row)
1350      {
1351          markread('topics', $forum_id);
1352          return true;
1353      }
1354   
1355      return false;
1356  }
1357   
1358  /**
1359  * Transform an array into a serialized format
1360  */
1361  function tracking_serialize($input)
1362  {
1363      $out = '';
1364      foreach ($input as $key => $value)
1365      {
1366          if (is_array($value))
1367          {
1368              $out .= $key . ':(' . tracking_serialize($value) . ');';
1369          }
1370          else
1371          {
1372              $out .= $key . ':' . $value . ';';
1373          }
1374      }
1375      return $out;
1376  }
1377   
1378  /**
1379  * Transform a serialized array into an actual array
1380  */
1381  function tracking_unserialize($string, $max_depth = 3)
1382  {
1383      $n = strlen($string);
1384      if ($n > 10010)
1385      {
1386          die('Invalid data supplied');
1387      }
1388      $data = $stack = array();
1389      $key = '';
1390      $mode = 0;
1391      $level = &$data;
1392      for ($i = 0; $i < $n; ++$i)
1393      {
1394          switch ($mode)
1395          {
1396              case 0:
1397                  switch ($string[$i])
1398                  {
1399                      case ':':
1400                          $level[$key] = 0;
1401                          $mode = 1;
1402                      break;
1403                      case ')':
1404                          unset($level);
1405                          $level = array_pop($stack);
1406                          $mode = 3;
1407                      break;
1408                      default:
1409                          $key .= $string[$i];
1410                  }
1411              break;
1412   
1413              case 1:
1414                  switch ($string[$i])
1415                  {
1416                      case '(':
1417                          if (count($stack) >= $max_depth)
1418                          {
1419                              die('Invalid data supplied');
1420                          }
1421                          $stack[] = &$level;
1422                          $level[$key] = array();
1423                          $level = &$level[$key];
1424                          $key = '';
1425                          $mode = 0;
1426                      break;
1427                      default:
1428                          $level[$key] = $string[$i];
1429                          $mode = 2;
1430                      break;
1431                  }
1432              break;
1433   
1434              case 2:
1435                  switch ($string[$i])
1436                  {
1437                      case ')':
1438                          unset($level);
1439                          $level = array_pop($stack);
1440                          $mode = 3;
1441                      break;
1442                      case ';':
1443                          $key = '';
1444                          $mode = 0;
1445                      break;
1446                      default:
1447                          $level[$key] .= $string[$i];
1448                      break;
1449                  }
1450              break;
1451   
1452              case 3:
1453                  switch ($string[$i])
1454                  {
1455                      case ')':
1456                          unset($level);
1457                          $level = array_pop($stack);
1458                      break;
1459                      case ';':
1460                          $key = '';
1461                          $mode = 0;
1462                      break;
1463                      default:
1464                          die('Invalid data supplied');
1465                      break;
1466                  }
1467              break;
1468          }
1469      }
1470   
1471      if (count($stack) != 0 || ($mode != 0 && $mode != 3))
1472      {
1473          die('Invalid data supplied');
1474      }
1475   
1476      return $level;
1477  }
1478   
1479  // Server functions (building urls, redirecting...)
1480   
1481  /**
1482  * Append session id to url.
1483  * This function supports hooks.
1484  *
1485  * @param string $url The url the session id needs to be appended to (can have params)
1486  * @param mixed $params String or array of additional url parameters
1487  * @param bool $is_amp Is url using &amp; (true) or & (false)
1488  * @param string $session_id Possibility to use a custom session id instead of the global one
1489  * @param bool $is_route Is url generated by a route.
1490  *
1491  * @return string The corrected url.
1492  *
1493  * Examples:
1494  * <code> append_sid("{$phpbb_root_path}viewtopic.$phpEx?t=1");
1495  * append_sid("{$phpbb_root_path}viewtopic.$phpEx", 't=1');
1496  * append_sid("{$phpbb_root_path}viewtopic.$phpEx", 't=1', false);
1497  * append_sid("{$phpbb_root_path}viewtopic.$phpEx", array('t' => 1, 'f' => 2));
1498  * </code>
1499  *
1500  */
1501  function append_sid($url, $params = false, $is_amp = true, $session_id = false, $is_route = false)
1502  {
1503      global $_SID, $_EXTRA_URL, $phpbb_hook, $phpbb_path_helper;
1504      global $phpbb_dispatcher;
1505   
1506      if ($params === '' || (is_array($params) && empty($params)))
1507      {
1508          // Do not append the ? if the param-list is empty anyway.
1509          $params = false;
1510      }
1511   
1512      // Update the root path with the correct relative web path
1513      if (!$is_route && $phpbb_path_helper instanceof \phpbb\path_helper)
1514      {
1515          $url = $phpbb_path_helper->update_web_root_path($url);
1516      }
1517   
1518      $append_sid_overwrite = false;
1519   
1520      /**
1521      * This event can either supplement or override the append_sid() function
1522      *
1523      * To override this function, the event must set $append_sid_overwrite to
1524      * the new URL value, which will be returned following the event
1525      *
1526      * @event core.append_sid
1527      * @var    string        url                        The url the session id needs
1528      *                                            to be appended to (can have
1529      *                                            params)
1530      * @var    mixed        params                    String or array of additional
1531      *                                            url parameters
1532      * @var    bool        is_amp                    Is url using &amp; (true) or
1533      *                                            & (false)
1534      * @var    bool|string    session_id                Possibility to use a custom
1535      *                                            session id (string) instead of
1536      *                                            the global one (false)
1537      * @var    bool|string    append_sid_overwrite    Overwrite function (string
1538      *                                            URL) or not (false)
1539      * @var    bool    is_route                    Is url generated by a route.
1540      * @since 3.1.0-a1
1541      */
1542      $vars = array('url', 'params', 'is_amp', 'session_id', 'append_sid_overwrite', 'is_route');
1543      extract($phpbb_dispatcher->trigger_event('core.append_sid', compact($vars)));
1544   
1545      if ($append_sid_overwrite)
1546      {
1547          return $append_sid_overwrite;
1548      }
1549   
1550      // The following hook remains for backwards compatibility, though use of
1551      // the event above is preferred.
1552      // Developers using the hook function need to globalise the $_SID and $_EXTRA_URL on their own and also handle it appropriately.
1553      // They could mimic most of what is within this function
1554      if (!empty($phpbb_hook) && $phpbb_hook->call_hook(__FUNCTION__, $url, $params, $is_amp, $session_id))
1555      {
1556          if ($phpbb_hook->hook_return(__FUNCTION__))
1557          {
1558              return $phpbb_hook->hook_return_result(__FUNCTION__);
1559          }
1560      }
1561   
1562      $params_is_array = is_array($params);
1563   
1564      // Get anchor
1565      $anchor = '';
1566      if (strpos($url, '#') !== false)
1567      {
1568          list($url, $anchor) = explode('#', $url, 2);
1569          $anchor = '#' . $anchor;
1570      }
1571      else if (!$params_is_array && strpos($params, '#') !== false)
1572      {
1573          list($params, $anchor) = explode('#', $params, 2);
1574          $anchor = '#' . $anchor;
1575      }
1576   
1577      // Handle really simple cases quickly
1578      if ($_SID == '' && $session_id === false && empty($_EXTRA_URL) && !$params_is_array && !$anchor)
1579      {
1580          if ($params === false)
1581          {
1582              return $url;
1583          }
1584   
1585          $url_delim = (strpos($url, '?') === false) ? '?' : (($is_amp) ? '&amp;' : '&');
1586          return $url . ($params !== false ? $url_delim. $params : '');
1587      }
1588   
1589      // Assign sid if session id is not specified
1590      if ($session_id === false)
1591      {
1592          $session_id = $_SID;
1593      }
1594   
1595      $amp_delim = ($is_amp) ? '&amp;' : '&';
1596      $url_delim = (strpos($url, '?') === false) ? '?' : $amp_delim;
1597   
1598      // Appending custom url parameter?
1599      $append_url = (!empty($_EXTRA_URL)) ? implode($amp_delim, $_EXTRA_URL) : '';
1600   
1601      // Use the short variant if possible ;)
1602      if ($params === false)
1603      {
1604          // Append session id
1605          if (!$session_id)
1606          {
1607              return $url . (($append_url) ? $url_delim . $append_url : '') . $anchor;
1608          }
1609          else
1610          {
1611              return $url . (($append_url) ? $url_delim . $append_url . $amp_delim : $url_delim) . 'sid=' . $session_id . $anchor;
1612          }
1613      }
1614   
1615      // Build string if parameters are specified as array
1616      if (is_array($params))
1617      {
1618          $output = array();
1619   
1620          foreach ($params as $key => $item)
1621          {
1622              if ($item === NULL)
1623              {
1624                  continue;
1625              }
1626   
1627              if ($key == '#')
1628              {
1629                  $anchor = '#' . $item;
1630                  continue;
1631              }
1632   
1633              $output[] = $key . '=' . $item;
1634          }
1635   
1636          $params = implode($amp_delim, $output);
1637      }
1638   
1639      // Append session id and parameters (even if they are empty)
1640      // If parameters are empty, the developer can still append his/her parameters without caring about the delimiter
1641      return $url . (($append_url) ? $url_delim . $append_url . $amp_delim : $url_delim) . $params . ((!$session_id) ? '' : $amp_delim . 'sid=' . $session_id) . $anchor;
1642  }
1643   
1644  /**
1645  * Generate board url (example: http://www.example.com/phpBB)
1646  *
1647  * @param bool $without_script_path if set to true the script path gets not appended (example: http://www.example.com)
1648  *
1649  * @return string the generated board url
1650  */
1651  function generate_board_url($without_script_path = false)
1652  {
1653      global $config, $user, $request, $symfony_request;
1654   
1655      $server_name = $user->host;
1656   
1657      // Forcing server vars is the only way to specify/override the protocol
1658      if ($config['force_server_vars'] || !$server_name)
1659      {
1660          $server_protocol = ($config['server_protocol']) ? $config['server_protocol'] : (($config['cookie_secure']) ? 'https://' : 'http://');
1661          $server_name = $config['server_name'];
1662          $server_port = (int) $config['server_port'];
1663          $script_path = $config['script_path'];
1664   
1665          $url = $server_protocol . $server_name;
1666          $cookie_secure = $config['cookie_secure'];
1667      }
1668      else
1669      {
1670          $server_port = (int) $symfony_request->getPort();
1671   
1672          $forwarded_proto = $request->server('HTTP_X_FORWARDED_PROTO');
1673   
1674          if (!empty($forwarded_proto) && $forwarded_proto === 'https')
1675          {
1676              $server_port = 443;
1677          }
1678          // Do not rely on cookie_secure, users seem to think that it means a secured cookie instead of an encrypted connection
1679          $cookie_secure = $request->is_secure() ? 1 : 0;
1680          $url = (($cookie_secure) ? 'https://' : 'http://') . $server_name;
1681   
1682          $script_path = $user->page['root_script_path'];
1683      }
1684   
1685      if ($server_port && (($cookie_secure && $server_port <> 443) || (!$cookie_secure && $server_port <> 80)))
1686      {
1687          // HTTP HOST can carry a port number (we fetch $user->host, but for old versions this may be true)
1688          if (strpos($server_name, ':') === false)
1689          {
1690              $url .= ':' . $server_port;
1691          }
1692      }
1693   
1694      if (!$without_script_path)
1695      {
1696          $url .= $script_path;
1697      }
1698   
1699      // Strip / from the end
1700      if (substr($url, -1, 1) == '/')
1701      {
1702          $url = substr($url, 0, -1);
1703      }
1704   
1705      return $url;
1706  }
1707   
1708  /**
1709  * Redirects the user to another page then exits the script nicely
1710  * This function is intended for urls within the board. It's not meant to redirect to cross-domains.
1711  *
1712  * @param string $url The url to redirect to
1713  * @param bool $return If true, do not redirect but return the sanitized URL. Default is no return.
1714  * @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.
1715  */
1716  function redirect($url, $return = false, $disable_cd_check = false)
1717  {
1718      global $user, $phpbb_path_helper, $phpbb_dispatcher;
1719   
1720      if (!$user->is_setup())
1721      {
1722          $user->add_lang('common');
1723      }
1724   
1725      // Make sure no &amp;'s are in, this will break the redirect
1726      $url = str_replace('&amp;', '&', $url);
1727   
1728      // Determine which type of redirect we need to handle...
1729      $url_parts = @parse_url($url);
1730   
1731      if ($url_parts === false)
1732      {
1733          // Malformed url
1734          trigger_error('INSECURE_REDIRECT', E_USER_WARNING);
1735      }
1736      else if (!empty($url_parts['scheme']) && !empty($url_parts['host']))
1737      {
1738          // Attention: only able to redirect within the same domain if $disable_cd_check is false (yourdomain.com -> www.yourdomain.com will not work)
1739          if (!$disable_cd_check && $url_parts['host'] !== $user->host)
1740          {
1741              trigger_error('INSECURE_REDIRECT', E_USER_WARNING);
1742          }
1743      }
1744      else if ($url[0] == '/')
1745      {
1746          // Absolute uri, prepend direct url...
1747          $url = generate_board_url(true) . $url;
1748      }
1749      else
1750      {
1751          // Relative uri
1752          $pathinfo = pathinfo($url);
1753   
1754          // Is the uri pointing to the current directory?
1755          if ($pathinfo['dirname'] == '.')
1756          {
1757              $url = str_replace('./', '', $url);
1758   
1759              // Strip / from the beginning
1760              if ($url && substr($url, 0, 1) == '/')
1761              {
1762                  $url = substr($url, 1);
1763              }
1764          }
1765   
1766          $url = $phpbb_path_helper->remove_web_root_path($url);
1767   
1768          if ($user->page['page_dir'])
1769          {
1770              $url = $user->page['page_dir'] . '/' . $url;
1771          }
1772   
1773          $url = generate_board_url() . '/' . $url;
1774      }
1775   
1776      // Clean URL and check if we go outside the forum directory
1777      $url = $phpbb_path_helper->clean_url($url);
1778   
1779      if (!$disable_cd_check && strpos($url, generate_board_url(true) . '/') !== 0)
1780      {
1781          trigger_error('INSECURE_REDIRECT', E_USER_WARNING);
1782      }
1783   
1784      // Make sure no linebreaks are there... to prevent http response splitting for PHP < 4.4.2
1785      if (strpos(urldecode($url), "\n") !== false || strpos(urldecode($url), "\r") !== false || strpos($url, ';') !== false)
1786      {
1787          trigger_error('INSECURE_REDIRECT', E_USER_WARNING);
1788      }
1789   
1790      // Now, also check the protocol and for a valid url the last time...
1791      $allowed_protocols = array('http', 'https', 'ftp', 'ftps');
1792      $url_parts = parse_url($url);
1793   
1794      if ($url_parts === false || empty($url_parts['scheme']) || !in_array($url_parts['scheme'], $allowed_protocols))
1795      {
1796          trigger_error('INSECURE_REDIRECT', E_USER_WARNING);
1797      }
1798   
1799      /**
1800      * Execute code and/or overwrite redirect()
1801      *
1802      * @event core.functions.redirect
1803      * @var    string    url                    The url
1804      * @var    bool    return                If true, do not redirect but return the sanitized URL.
1805      * @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.
1806      * @since 3.1.0-RC3
1807      */
1808      $vars = array('url', 'return', 'disable_cd_check');
1809      extract($phpbb_dispatcher->trigger_event('core.functions.redirect', compact($vars)));
1810   
1811      if ($return)
1812      {
1813          return $url;
1814      }
1815      else
1816      {
1817          garbage_collection();
1818      }
1819   
1820      // Behave as per HTTP/1.1 spec for others
1821      header('Location: ' . $url);
1822      exit;
1823  }
1824   
1825  /**
1826   * Returns the install redirect path for phpBB.
1827   *
1828   * @param string $phpbb_root_path The root path of the phpBB installation.
1829   * @param string $phpEx The file extension of php files, e.g., "php".
1830   * @return string The install redirect path.
1831   */
1832  function phpbb_get_install_redirect(string $phpbb_root_path, string $phpEx): string
1833  {
1834      $script_name = (!empty($_SERVER['REQUEST_URI'])) ? $_SERVER['REQUEST_URI'] : getenv('REQUEST_URI');
1835      if (!$script_name)
1836      {
1837          $script_name = (!empty($_SERVER['PHP_SELF'])) ? $_SERVER['PHP_SELF'] : getenv('PHP_SELF');
1838      }
1839   
1840      // Add trailing dot to prevent dirname() from returning parent directory if $script_name is a directory
1841      $script_name = substr($script_name, -1) === '/' ? $script_name . '.' : $script_name;
1842   
1843      // $phpbb_root_path accounts for redirects from e.g. /adm
1844      $script_path = trim(dirname($script_name)) . '/' . $phpbb_root_path . 'install/app.' . $phpEx;
1845      // Replace any number of consecutive backslashes and/or slashes with a single slash
1846      // (could happen on some proxy setups and/or Windows servers)
1847      return preg_replace('#[\\\\/]{2,}#', '/', $script_path);
1848  }
1849   
1850  /**
1851  * Re-Apply session id after page reloads
1852  */
1853  function reapply_sid($url, $is_route = false)
1854  {
1855      global $phpEx, $phpbb_root_path;
1856   
1857      if ($url === "index.$phpEx")
1858      {
1859          return append_sid("index.$phpEx");
1860      }
1861      else if ($url === "{$phpbb_root_path}index.$phpEx")
1862      {
1863          return append_sid("{$phpbb_root_path}index.$phpEx");
1864      }
1865   
1866      // Remove previously added sid
1867      if (strpos($url, 'sid=') !== false)
1868      {
1869          // All kind of links
1870          $url = preg_replace('/(\?)?(&amp;|&)?sid=[a-z0-9]+/', '', $url);
1871          // if the sid was the first param, make the old second as first ones
1872          $url = preg_replace("/$phpEx(&amp;|&)+?/", "$phpEx?", $url);
1873      }
1874   
1875      return append_sid($url, false, true, false, $is_route);
1876  }
1877   
1878  /**
1879  * Returns url from the session/current page with an re-appended SID with optionally stripping vars from the url
1880  */
1881  function build_url($strip_vars = false)
1882  {
1883      global $config, $user, $phpbb_path_helper;
1884   
1885      $page = $phpbb_path_helper->get_valid_page($user->page['page'], $config['enable_mod_rewrite']);
1886   
1887      // Append SID
1888      $redirect = append_sid($page, false, false);
1889   
1890      if ($strip_vars !== false)
1891      {
1892          $redirect = $phpbb_path_helper->strip_url_params($redirect, $strip_vars, false);
1893      }
1894      else
1895      {
1896          $redirect = str_replace('&', '&amp;', $redirect);
1897      }
1898   
1899      return $redirect . ((strpos($redirect, '?') === false) ? '?' : '');
1900  }
1901   
1902  /**
1903  * Meta refresh assignment
1904  * Adds META template variable with meta http tag.
1905  *
1906  * @param int $time Time in seconds for meta refresh tag
1907  * @param string $url URL to redirect to. The url will go through redirect() first before the template variable is assigned
1908  * @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.
1909  */
1910  function meta_refresh($time, $url, $disable_cd_check = false)
1911  {
1912      global $template, $refresh_data, $request;
1913   
1914      $url = redirect($url, true, $disable_cd_check);
1915      if ($request->is_ajax())
1916      {
1917          $refresh_data = array(
1918              'time'    => $time,
1919              'url'    => $url,
1920          );
1921      }
1922      else
1923      {
1924          // For XHTML compatibility we change back & to &amp;
1925          $url = str_replace('&', '&amp;', $url);
1926   
1927          $template->assign_vars(array(
1928              'META' => '<meta http-equiv="refresh" content="' . $time . '; url=' . $url . '" />')
1929          );
1930      }
1931   
1932      return $url;
1933  }
1934   
1935  /**
1936  * Outputs correct status line header.
1937  *
1938  * Depending on php sapi one of the two following forms is used:
1939  *
1940  * Status: 404 Not Found
1941  *
1942  * HTTP/1.x 404 Not Found
1943  *
1944  * HTTP version is taken from HTTP_VERSION environment variable,
1945  * and defaults to 1.0.
1946  *
1947  * Sample usage:
1948  *
1949  * send_status_line(404, 'Not Found');
1950  *
1951  * @param int $code HTTP status code
1952  * @param string $message Message for the status code
1953  * @return null
1954  */
1955  function send_status_line($code, $message)
1956  {
1957      if (substr(strtolower(@php_sapi_name()), 0, 3) === 'cgi')
1958      {
1959          // in theory, we shouldn't need that due to php doing it. Reality offers a differing opinion, though
1960          header("Status: $code $message", true, $code);
1961      }
1962      else
1963      {
1964          $version = phpbb_request_http_version();
1965          header("$version $code $message", true, $code);
1966      }
1967  }
1968   
1969  /**
1970  * Returns the HTTP version used in the current request.
1971  *
1972  * Handles the case of being called before $request is present,
1973  * in which case it falls back to the $_SERVER superglobal.
1974  *
1975  * @return string HTTP version
1976  */
1977  function phpbb_request_http_version()
1978  {
1979      global $request;
1980   
1981      $version = '';
1982      if ($request && $request->server('SERVER_PROTOCOL'))
1983      {
1984          $version = $request->server('SERVER_PROTOCOL');
1985      }
1986      else if (isset($_SERVER['SERVER_PROTOCOL']))
1987      {
1988          $version = $_SERVER['SERVER_PROTOCOL'];
1989      }
1990   
1991      if (!empty($version) && is_string($version) && preg_match('#^HTTP/[0-9]\.[0-9]$#', $version))
1992      {
1993          return $version;
1994      }
1995   
1996      return 'HTTP/1.0';
1997  }
1998   
1999  //Form validation
2000   
2001   
2002  /**
2003  * Add a secret hash   for use in links/GET requests
2004  * @param string  $link_name The name of the link; has to match the name used in check_link_hash, otherwise no restrictions apply
2005  * @return string the hash
2006   
2007  */
2008  function generate_link_hash($link_name)
2009  {
2010      global $user;
2011   
2012      if (!isset($user->data["hash_$link_name"]))
2013      {
2014          $user->data["hash_$link_name"] = substr(sha1($user->data['user_form_salt'] . $link_name), 0, 8);
2015      }
2016   
2017      return $user->data["hash_$link_name"];
2018  }
2019   
2020   
2021  /**
2022  * checks a link hash - for GET requests
2023  * @param string $token the submitted token
2024  * @param string $link_name The name of the link
2025  * @return boolean true if all is fine
2026  */
2027  function check_link_hash($token, $link_name)
2028  {
2029      return $token === generate_link_hash($link_name);
2030  }
2031   
2032  /**
2033  * Add a secret token to the form (requires the S_FORM_TOKEN template variable)
2034  * @param string  $form_name The name of the form; has to match the name used in check_form_key, otherwise no restrictions apply
2035  * @param string  $template_variable_suffix A string that is appended to the name of the template variable to which the form elements are assigned
2036  */
2037  function add_form_key($form_name, $template_variable_suffix = '')
2038  {
2039      global $config, $template, $user, $phpbb_dispatcher;
2040   
2041      $now = time();
2042      $token_sid = ($user->data['user_id'] == ANONYMOUS && !empty($config['form_token_sid_guests'])) ? $user->session_id : '';
2043      $token = sha1($now . $user->data['user_form_salt'] . $form_name . $token_sid);
2044   
2045      $s_fields = build_hidden_fields(array(
2046          'creation_time' => $now,
2047          'form_token'    => $token,
2048      ));
2049   
2050      /**
2051      * Perform additional actions on creation of the form token
2052      *
2053      * @event core.add_form_key
2054      * @var    string    form_name                    The form name
2055      * @var    int        now                            Current time timestamp
2056      * @var    string    s_fields                    Generated hidden fields
2057      * @var    string    token                        Form token
2058      * @var    string    token_sid                    User session ID
2059      * @var    string    template_variable_suffix    The string that is appended to template variable name
2060      *
2061      * @since 3.1.0-RC3
2062      * @changed 3.1.11-RC1 Added template_variable_suffix
2063      */
2064      $vars = array(
2065          'form_name',
2066          'now',
2067          's_fields',
2068          'token',
2069          'token_sid',
2070          'template_variable_suffix',
2071      );
2072      extract($phpbb_dispatcher->trigger_event('core.add_form_key', compact($vars)));
2073   
2074      $template->assign_var('S_FORM_TOKEN' . $template_variable_suffix, $s_fields);
2075  }
2076   
2077  /**
2078   * Check the form key. Required for all altering actions not secured by confirm_box
2079   *
2080   * @param    string    $form_name    The name of the form; has to match the name used
2081   *                                in add_form_key, otherwise no restrictions apply
2082   * @param    int        $timespan    The maximum acceptable age for a submitted form
2083   *                                in seconds. Defaults to the config setting.
2084   * @return    bool    True, if the form key was valid, false otherwise
2085   */
2086  function check_form_key($form_name, $timespan = false)
2087  {
2088      global $config, $request, $user;
2089   
2090      if ($timespan === false)
2091      {
2092          // we enforce a minimum value of half a minute here.
2093          $timespan = ($config['form_token_lifetime'] == -1) ? -1 : max(30, $config['form_token_lifetime']);
2094      }
2095   
2096      if ($request->is_set_post('creation_time') && $request->is_set_post('form_token'))
2097      {
2098          $creation_time    = abs($request->variable('creation_time', 0));
2099          $token = $request->variable('form_token', '');
2100   
2101          $diff = time() - $creation_time;
2102   
2103          // If creation_time and the time() now is zero we can assume it was not a human doing this (the check for if ($diff)...
2104          if (defined('DEBUG_TEST') || $diff && ($diff <= $timespan || $timespan === -1))
2105          {
2106              $token_sid = ($user->data['user_id'] == ANONYMOUS && !empty($config['form_token_sid_guests'])) ? $user->session_id : '';
2107              $key = sha1($creation_time . $user->data['user_form_salt'] . $form_name . $token_sid);
2108   
2109              if ($key === $token)
2110              {
2111                  return true;
2112              }
2113          }
2114      }
2115   
2116      return false;
2117  }
2118   
2119  // Message/Login boxes
2120   
2121  /**
2122  * Build Confirm box
2123  * @param boolean $check True for checking if confirmed (without any additional parameters) and false for displaying the confirm box
2124  * @param string|array $title Title/Message used for confirm box.
2125  *        message text is _CONFIRM appended to title.
2126  *        If title cannot be found in user->lang a default one is displayed
2127  *        If title_CONFIRM cannot be found in user->lang the text given is used.
2128  *       If title is an array, the first array value is used as explained per above,
2129  *       all other array values are sent as parameters to the language function.
2130  * @param string $hidden Hidden variables
2131  * @param string $html_body Template used for confirm box
2132  * @param string $u_action Custom form action
2133  *
2134  * @return bool True if confirmation was successful, false if not
2135  */
2136  function confirm_box($check, $title = '', $hidden = '', $html_body = 'confirm_body.html', $u_action = '')
2137  {
2138      global $user, $template, $db, $request;
2139      global $config, $language, $phpbb_path_helper, $phpbb_dispatcher;
2140   
2141      if (isset($_POST['cancel']))
2142      {
2143          return false;
2144      }
2145   
2146      $confirm = ($language->lang('YES') === $request->variable('confirm', '', true, \phpbb\request\request_interface::POST));
2147   
2148      if ($check && $confirm)
2149      {
2150          $user_id = $request->variable('confirm_uid', 0);
2151          $session_id = $request->variable('sess', '');
2152          $confirm_key = $request->variable('confirm_key', '');
2153   
2154          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'])
2155          {
2156              return false;
2157          }
2158   
2159          // Reset user_last_confirm_key
2160          $sql = 'UPDATE ' . USERS_TABLE . " SET user_last_confirm_key = ''
2161              WHERE user_id = " . $user->data['user_id'];
2162          $db->sql_query($sql);
2163   
2164          return true;
2165      }
2166      else if ($check)
2167      {
2168          return false;
2169      }
2170   
2171      $s_hidden_fields = build_hidden_fields(array(
2172          'confirm_uid'    => $user->data['user_id'],
2173          'sess'            => $user->session_id,
2174          'sid'            => $user->session_id,
2175      ));
2176   
2177      // generate activation key
2178      $confirm_key = gen_rand_string(10);
2179   
2180      // generate language strings
2181      if (is_array($title))
2182      {
2183          $key = array_shift($title);
2184          $count = array_shift($title);
2185          $confirm_title =  $language->is_set($key) ? $language->lang($key, $count, $title) : $language->lang('CONFIRM');
2186          $confirm_text = $language->is_set($key . '_CONFIRM') ? $language->lang($key . '_CONFIRM', $count, $title) : $key;
2187      }
2188      else
2189      {
2190          $confirm_title = $language->is_set($title) ? $language->lang($title) : $language->lang('CONFIRM');
2191          $confirm_text = $language->is_set($title . '_CONFIRM') ? $language->lang($title . '_CONFIRM') : $title;
2192      }
2193   
2194      if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
2195      {
2196          adm_page_header($confirm_title);
2197      }
2198      else
2199      {
2200          page_header($confirm_title);
2201      }
2202   
2203      $template->set_filenames(array(
2204          'body' => $html_body)
2205      );
2206   
2207      // If activation key already exist, we better do not re-use the key (something very strange is going on...)
2208      if ($request->variable('confirm_key', ''))
2209      {
2210          // This should not occur, therefore we cancel the operation to safe the user
2211          return false;
2212      }
2213   
2214      // re-add sid / transform & to &amp; for user->page (user->page is always using &)
2215      $use_page = ($u_action) ? $u_action : str_replace('&', '&amp;', $user->page['page']);
2216      $u_action = reapply_sid($phpbb_path_helper->get_valid_page($use_page, $config['enable_mod_rewrite']));
2217      $u_action .= ((strpos($u_action, '?') === false) ? '?' : '&amp;') . 'confirm_key=' . $confirm_key;
2218   
2219      $template->assign_vars(array(
2220          'MESSAGE_TITLE'        => $confirm_title,
2221          'MESSAGE_TEXT'        => $confirm_text,
2222   
2223          'YES_VALUE'            => $language->lang('YES'),
2224          'S_CONFIRM_ACTION'    => $u_action,
2225          'S_HIDDEN_FIELDS'    => $hidden . $s_hidden_fields,
2226          'S_AJAX_REQUEST'    => $request->is_ajax(),
2227      ));
2228   
2229      $sql = 'UPDATE ' . USERS_TABLE . " SET user_last_confirm_key = '" . $db->sql_escape($confirm_key) . "'
2230          WHERE user_id = " . $user->data['user_id'];
2231      $db->sql_query($sql);
2232   
2233      if ($request->is_ajax())
2234      {
2235          $u_action .= '&confirm_uid=' . $user->data['user_id'] . '&sess=' . $user->session_id . '&sid=' . $user->session_id;
2236          $data = array(
2237              'MESSAGE_BODY'        => $template->assign_display('body'),
2238              'MESSAGE_TITLE'        => $confirm_title,
2239              'MESSAGE_TEXT'        => $confirm_text,
2240   
2241              'YES_VALUE'            => $language->lang('YES'),
2242              'S_CONFIRM_ACTION'    => str_replace('&amp;', '&', $u_action), //inefficient, rewrite whole function
2243              'S_HIDDEN_FIELDS'    => $hidden . $s_hidden_fields
2244          );
2245   
2246          /**
2247           * This event allows an extension to modify the ajax output of confirm box.
2248           *
2249           * @event core.confirm_box_ajax_before
2250           * @var string    u_action        Action of the form
2251           * @var array    data            Data to be sent
2252           * @var string    hidden            Hidden fields generated by caller
2253           * @var string    s_hidden_fields    Hidden fields generated by this function
2254           * @since 3.2.8-RC1
2255           */
2256          $vars = array(
2257              'u_action',
2258              'data',
2259              'hidden',
2260              's_hidden_fields',
2261          );
2262          extract($phpbb_dispatcher->trigger_event('core.confirm_box_ajax_before', compact($vars)));
2263   
2264          $json_response = new \phpbb\json_response;
2265          $json_response->send($data);
2266      }
2267   
2268      if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
2269      {
2270          adm_page_footer();
2271      }
2272      else
2273      {
2274          page_footer();
2275      }
2276   
2277      exit; // unreachable, page_footer() above will call exit()
2278  }
2279   
2280  /**
2281  * Generate login box or verify password
2282  */
2283  function login_box($redirect = '', $l_explain = '', $l_success = '', $admin = false, $s_display = true)
2284  {
2285      global $user, $template, $auth, $phpEx, $phpbb_root_path, $config;
2286      global $request, $phpbb_container, $phpbb_dispatcher, $phpbb_log;
2287   
2288      $err = '';
2289      $form_name = 'login';
2290      $username = $autologin = false;
2291   
2292      // Make sure user->setup() has been called
2293      if (!$user->is_setup())
2294      {
2295          $user->setup();
2296      }
2297   
2298      /**
2299       * This event allows an extension to modify the login process
2300       *
2301       * @event core.login_box_before
2302       * @var string    redirect    Redirect string
2303       * @var string    l_explain    Explain language string
2304       * @var string    l_success    Success language string
2305       * @var    bool    admin        Is admin?
2306       * @var bool    s_display    Display full login form?
2307       * @var string    err            Error string
2308       * @since 3.1.9-RC1
2309       */
2310      $vars = array('redirect', 'l_explain', 'l_success', 'admin', 's_display', 'err');
2311      extract($phpbb_dispatcher->trigger_event('core.login_box_before', compact($vars)));
2312   
2313      // Print out error if user tries to authenticate as an administrator without having the privileges...
2314      if ($admin && !$auth->acl_get('a_'))
2315      {
2316          // Not authd
2317          // anonymous/inactive users are never able to go to the ACP even if they have the relevant permissions
2318          if ($user->data['is_registered'])
2319          {
2320              $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ADMIN_AUTH_FAIL');
2321          }
2322          send_status_line(403, 'Forbidden');
2323          trigger_error('NO_AUTH_ADMIN');
2324      }
2325   
2326      if (empty($err) && ($request->is_set_post('login') || ($request->is_set('login') && $request->variable('login', '') == 'external')))
2327      {
2328          // Get credential
2329          if ($admin)
2330          {
2331              $credential = $request->variable('credential', '');
2332   
2333              if (strspn($credential, 'abcdef0123456789') !== strlen($credential) || strlen($credential) != 32)
2334              {
2335                  if ($user->data['is_registered'])
2336                  {
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');
2341              }
2342   
2343              $password    = $request->untrimmed_variable('password_' . $credential, '', true);
2344          }
2345          else
2346          {
2347              $password    = $request->untrimmed_variable('password', '', true);
2348          }
2349   
2350          $username    = $request->variable('username', '', true);
2351          $autologin    = $request->is_set_post('autologin');
2352          $viewonline = (int) !$request->is_set_post('viewonline');
2353          $admin         = ($admin) ? 1 : 0;
2354          $viewonline = ($admin) ? $user->data['session_viewonline'] : $viewonline;
2355   
2356          // Check if the supplied username is equal to the one stored within the database if re-authenticating
2357          if ($admin && utf8_clean_string($username) != utf8_clean_string($user->data['username']))
2358          {
2359              // We log the attempt to use a different username...
2360              $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ADMIN_AUTH_FAIL');
2361   
2362              send_status_line(403, 'Forbidden');
2363              trigger_error('NO_AUTH_ADMIN_USER_DIFFER');
2364          }
2365   
2366          // Check form key
2367          if ($password && !defined('IN_CHECK_BAN') && !check_form_key($form_name))
2368          {
2369              $result = array(
2370                  'status' => false,
2371                  'error_msg' => 'FORM_INVALID',
2372              );
2373          }
2374          else
2375          {
2376              // If authentication is successful we redirect user to previous page
2377              $result = $auth->login($username, $password, $autologin, $viewonline, $admin);
2378          }
2379   
2380          // If admin authentication and login, we will log if it was a success or not...
2381          // We also break the operation on the first non-success login - it could be argued that the user already knows
2382          if ($admin)
2383          {
2384              if ($result['status'] == LOGIN_SUCCESS)
2385              {
2386                  $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ADMIN_AUTH_SUCCESS');
2387              }
2388              else
2389              {
2390                  // Only log the failed attempt if a real user tried to.
2391                  // anonymous/inactive users are never able to go to the ACP even if they have the relevant permissions
2392                  if ($user->data['is_registered'])
2393                  {
2394                      $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ADMIN_AUTH_FAIL');
2395                  }
2396              }
2397          }
2398   
2399          // The result parameter is always an array, holding the relevant information...
2400          if ($result['status'] == LOGIN_SUCCESS)
2401          {
2402              $redirect = $request->variable('redirect', "{$phpbb_root_path}index.$phpEx");
2403   
2404              /**
2405              * This event allows an extension to modify the redirection when a user successfully logs in
2406              *
2407              * @event core.login_box_redirect
2408              * @var  string    redirect    Redirect string
2409              * @var    bool    admin        Is admin?
2410              * @var    array    result        Result from auth provider
2411              * @since 3.1.0-RC5
2412              * @changed 3.1.9-RC1 Removed undefined return variable
2413              * @changed 3.2.4-RC1 Added result
2414              */
2415              $vars = array('redirect', 'admin', 'result');
2416              extract($phpbb_dispatcher->trigger_event('core.login_box_redirect', compact($vars)));
2417   
2418              // append/replace SID (may change during the session for AOL users)
2419              $redirect = reapply_sid($redirect);
2420   
2421              // Special case... the user is effectively banned, but we allow founders to login
2422              if (defined('IN_CHECK_BAN') && $result['user_row']['user_type'] != USER_FOUNDER)
2423              {
2424                  return;
2425              }
2426   
2427              redirect($redirect);
2428          }
2429   
2430          // Something failed, determine what...
2431          if ($result['status'] == LOGIN_BREAK)
2432          {
2433              trigger_error($result['error_msg']);
2434          }
2435   
2436          // Special cases... determine
2437          switch ($result['status'])
2438          {
2439              case LOGIN_ERROR_PASSWORD_CONVERT:
2440                  $err = sprintf(
2441                      $user->lang[$result['error_msg']],
2442                      ($config['email_enable']) ? '<a href="' . append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=sendpassword') . '">' : '',
2443                      ($config['email_enable']) ? '</a>' : '',
2444                      '<a href="' . phpbb_get_board_contact_link($config, $phpbb_root_path, $phpEx) . '">',
2445                      '</a>'
2446                  );
2447              break;
2448   
2449              case LOGIN_ERROR_ATTEMPTS:
2450   
2451                  $captcha = $phpbb_container->get('captcha.factory')->get_instance($config['captcha_plugin']);
2452                  $captcha->init(CONFIRM_LOGIN);
2453                  // $captcha->reset();
2454   
2455                  $template->assign_vars(array(
2456                      'CAPTCHA_TEMPLATE'            => $captcha->get_template(),
2457                  ));
2458              // no break;
2459   
2460              // Username, password, etc...
2461              default:
2462                  $err = $user->lang[$result['error_msg']];
2463   
2464                  // Assign admin contact to some error messages
2465                  if ($result['error_msg'] == 'LOGIN_ERROR_USERNAME' || $result['error_msg'] == 'LOGIN_ERROR_PASSWORD')
2466                  {
2467                      $err = sprintf($user->lang[$result['error_msg']], '<a href="' . append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=contactadmin') . '">', '</a>');
2468                  }
2469   
2470              break;
2471          }
2472   
2473          /**
2474           * This event allows an extension to process when a user fails a login attempt
2475           *
2476           * @event core.login_box_failed
2477           * @var array   result      Login result data
2478           * @var string  username    User name used to login
2479           * @var string  password    Password used to login
2480           * @var string  err         Error message
2481           * @since 3.1.3-RC1
2482           */
2483          $vars = array('result', 'username', 'password', 'err');
2484          extract($phpbb_dispatcher->trigger_event('core.login_box_failed', compact($vars)));
2485      }
2486   
2487      // Assign credential for username/password pair
2488      $credential = ($admin) ? md5(unique_id()) : false;
2489   
2490      $s_hidden_fields = array(
2491          'sid'        => $user->session_id,
2492      );
2493   
2494      if ($redirect)
2495      {
2496          $s_hidden_fields['redirect'] = $redirect;
2497      }
2498   
2499      if ($admin)
2500      {
2501          $s_hidden_fields['credential'] = $credential;
2502      }
2503   
2504      /* @var $provider_collection \phpbb\auth\provider_collection */
2505      $provider_collection = $phpbb_container->get('auth.provider_collection');
2506      $auth_provider = $provider_collection->get_provider();
2507   
2508      $auth_provider_data = $auth_provider->get_login_data();
2509      if ($auth_provider_data)
2510      {
2511          if (isset($auth_provider_data['VARS']))
2512          {
2513              $template->assign_vars($auth_provider_data['VARS']);
2514          }
2515   
2516          if (isset($auth_provider_data['BLOCK_VAR_NAME']))
2517          {
2518              foreach ($auth_provider_data['BLOCK_VARS'] as $block_vars)
2519              {
2520                  $template->assign_block_vars($auth_provider_data['BLOCK_VAR_NAME'], $block_vars);
2521              }
2522          }
2523   
2524          $template->assign_vars(array(
2525              'PROVIDER_TEMPLATE_FILE' => $auth_provider_data['TEMPLATE_FILE'],
2526          ));
2527      }
2528   
2529      $s_hidden_fields = build_hidden_fields($s_hidden_fields);
2530   
2531      /** @var \phpbb\controller\helper $controller_helper */
2532      $controller_helper = $phpbb_container->get('controller.helper');
2533   
2534      $login_box_template_data = array(
2535          'LOGIN_ERROR'        => $err,
2536          'LOGIN_EXPLAIN'        => $l_explain,
2537   
2538          'U_SEND_PASSWORD'         => ($config['email_enable'] && $config['allow_password_reset']) ? $controller_helper->route('phpbb_ucp_forgot_password_controller') : '',
2539          'U_RESEND_ACTIVATION'    => ($config['require_activation'] == USER_ACTIVATION_SELF && $config['email_enable']) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=resend_act') : '',
2540          'U_TERMS_USE'            => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=terms'),
2541          'U_PRIVACY'                => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy'),
2542          'UA_PRIVACY'            => addslashes(append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy')),
2543   
2544          'S_DISPLAY_FULL_LOGIN'    => ($s_display) ? true : false,
2545          'S_HIDDEN_FIELDS'         => $s_hidden_fields,
2546   
2547          'S_ADMIN_AUTH'            => $admin,
2548          'USERNAME'                => ($admin) ? $user->data['username'] : '',
2549   
2550          'USERNAME_CREDENTIAL'    => 'username',
2551          'PASSWORD_CREDENTIAL'    => ($admin) ? 'password_' . $credential : 'password',
2552      );
2553   
2554      /**
2555       * Event to add/modify login box template data
2556       *
2557       * @event core.login_box_modify_template_data
2558       * @var    int        admin                            Flag whether user is admin
2559       * @var    string    username                        User name
2560       * @var    int        autologin                        Flag whether autologin is enabled
2561       * @var string    redirect                        Redirect URL
2562       * @var    array    login_box_template_data            Array with the login box template data
2563       * @since 3.2.3-RC2
2564       */
2565      $vars = array(
2566          'admin',
2567          'username',
2568          'autologin',
2569          'redirect',
2570          'login_box_template_data',
2571      );
2572      extract($phpbb_dispatcher->trigger_event('core.login_box_modify_template_data', compact($vars)));
2573   
2574      $template->assign_vars($login_box_template_data);
2575   
2576      page_header($user->lang['LOGIN']);
2577   
2578      $template->set_filenames(array(
2579          'body' => 'login_body.html')
2580      );
2581      make_jumpbox(append_sid("{$phpbb_root_path}viewforum.$phpEx"));
2582   
2583      page_footer();
2584  }
2585   
2586  /**
2587  * Generate forum login box
2588  */
2589  function login_forum_box($forum_data)
2590  {
2591      global $db, $phpbb_container, $request, $template, $user, $phpbb_dispatcher, $phpbb_root_path, $phpEx;
2592   
2593      $password = $request->variable('password', '', true);
2594   
2595      $sql = 'SELECT forum_id
2596          FROM ' . FORUMS_ACCESS_TABLE . '
2597          WHERE forum_id = ' . $forum_data['forum_id'] . '
2598              AND user_id = ' . $user->data['user_id'] . "
2599              AND session_id = '" . $db->sql_escape($user->session_id) . "'";
2600      $result = $db->sql_query($sql);
2601      $row = $db->sql_fetchrow($result);
2602      $db->sql_freeresult($result);
2603   
2604      if ($row)
2605      {
2606          return true;
2607      }
2608   
2609      if ($password)
2610      {
2611          // Remove expired authorised sessions
2612          $sql = 'SELECT f.session_id
2613              FROM ' . FORUMS_ACCESS_TABLE . ' f
2614              LEFT JOIN ' . SESSIONS_TABLE . ' s ON (f.session_id = s.session_id)
2615              WHERE s.session_id IS NULL';
2616          $result = $db->sql_query($sql);
2617   
2618          if ($row = $db->sql_fetchrow($result))
2619          {
2620              $sql_in = array();
2621              do
2622              {
2623                  $sql_in[] = (string) $row['session_id'];
2624              }
2625              while ($row = $db->sql_fetchrow($result));
2626   
2627              // Remove expired sessions
2628              $sql = 'DELETE FROM ' . FORUMS_ACCESS_TABLE . '
2629                  WHERE ' . $db->sql_in_set('session_id', $sql_in);
2630              $db->sql_query($sql);
2631          }
2632          $db->sql_freeresult($result);
2633   
2634          /* @var $passwords_manager \phpbb\passwords\manager */
2635          $passwords_manager = $phpbb_container->get('passwords.manager');
2636   
2637          if ($passwords_manager->check($password, $forum_data['forum_password']))
2638          {
2639              $sql_ary = array(
2640                  'forum_id'        => (int) $forum_data['forum_id'],
2641                  'user_id'        => (int) $user->data['user_id'],
2642                  'session_id'    => (string) $user->session_id,
2643              );
2644   
2645              $db->sql_query('INSERT INTO ' . FORUMS_ACCESS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
2646   
2647              return true;
2648          }
2649   
2650          $template->assign_var('LOGIN_ERROR', $user->lang['WRONG_PASSWORD']);
2651      }
2652   
2653      /**
2654      * Performing additional actions, load additional data on forum login
2655      *
2656      * @event core.login_forum_box
2657      * @var    array    forum_data        Array with forum data
2658      * @var    string    password        Password entered
2659      * @since 3.1.0-RC3
2660      */
2661      $vars = array('forum_data', 'password');
2662      extract($phpbb_dispatcher->trigger_event('core.login_forum_box', compact($vars)));
2663   
2664      page_header($user->lang['LOGIN']);
2665   
2666      $template->assign_vars(array(
2667          'FORUM_NAME'            => isset($forum_data['forum_name']) ? $forum_data['forum_name'] : '',
2668          'S_LOGIN_ACTION'        => build_url(array('f')),
2669          'S_HIDDEN_FIELDS'        => build_hidden_fields(array('f' => $forum_data['forum_id'])))
2670      );
2671   
2672      $template->set_filenames(array(
2673          'body' => 'login_forum.html')
2674      );
2675   
2676      make_jumpbox(append_sid("{$phpbb_root_path}viewforum.$phpEx"), $forum_data['forum_id']);
2677   
2678      page_footer();
2679  }
2680   
2681  // Little helpers
2682   
2683  /**
2684  * Little helper for the build_hidden_fields function
2685  */
2686  function _build_hidden_fields($key, $value, $specialchar, $stripslashes)
2687  {
2688      $hidden_fields = '';
2689   
2690      if (!is_array($value))
2691      {
2692          $value = ($stripslashes) ? stripslashes($value) : $value;
2693          $value = ($specialchar) ? htmlspecialchars($value, ENT_COMPAT, 'UTF-8') : $value;
2694   
2695          $hidden_fields .= '<input type="hidden" name="' . $key . '" value="' . $value . '" />' . "\n";
2696      }
2697      else
2698      {
2699          foreach ($value as $_key => $_value)
2700          {
2701              $_key = ($stripslashes) ? stripslashes($_key) : $_key;
2702              $_key = ($specialchar) ? htmlspecialchars($_key, ENT_COMPAT, 'UTF-8') : $_key;
2703   
2704              $hidden_fields .= _build_hidden_fields($key . '[' . $_key . ']', $_value, $specialchar, $stripslashes);
2705          }
2706      }
2707   
2708      return $hidden_fields;
2709  }
2710   
2711  /**
2712  * Build simple hidden fields from array
2713  *
2714  * @param array $field_ary an array of values to build the hidden field from
2715  * @param bool $specialchar if true, keys and values get specialchared
2716  * @param bool $stripslashes if true, keys and values get stripslashed
2717  *
2718  * @return string the hidden fields
2719  */
2720  function build_hidden_fields($field_ary, $specialchar = false, $stripslashes = false)
2721  {
2722      $s_hidden_fields = '';
2723   
2724      foreach ($field_ary as $name => $vars)
2725      {
2726          $name = ($stripslashes) ? stripslashes($name) : $name;
2727          $name = ($specialchar) ? htmlspecialchars($name, ENT_COMPAT, 'UTF-8') : $name;
2728   
2729          $s_hidden_fields .= _build_hidden_fields($name, $vars, $specialchar, $stripslashes);
2730      }
2731   
2732      return $s_hidden_fields;
2733  }
2734   
2735  /**
2736  * Parse cfg file
2737  */
2738  function parse_cfg_file($filename, $lines = false)
2739  {
2740      $parsed_items = array();
2741   
2742      if ($lines === false)
2743      {
2744          $lines = file($filename);
2745      }
2746   
2747      foreach ($lines as $line)
2748      {
2749          $line = trim($line);
2750   
2751          if (!$line || $line[0] == '#' || ($delim_pos = strpos($line, '=')) === false)
2752          {
2753              continue;
2754          }
2755   
2756          // Determine first occurrence, since in values the equal sign is allowed
2757          $key = htmlspecialchars(strtolower(trim(substr($line, 0, $delim_pos))), ENT_COMPAT);
2758          $value = trim(substr($line, $delim_pos + 1));
2759   
2760          if (in_array($value, array('off', 'false', '0')))
2761          {
2762              $value = false;
2763          }
2764          else if (in_array($value, array('on', 'true', '1')))
2765          {
2766              $value = true;
2767          }
2768          else if (!trim($value))
2769          {
2770              $value = '';
2771          }
2772          else if (($value[0] == "'" && $value[strlen($value) - 1] == "'") || ($value[0] == '"' && $value[strlen($value) - 1] == '"'))
2773          {
2774              $value = htmlspecialchars(substr($value, 1, strlen($value)-2), ENT_COMPAT);
2775          }
2776          else
2777          {
2778              $value = htmlspecialchars($value, ENT_COMPAT);
2779          }
2780   
2781          $parsed_items[$key] = $value;
2782      }
2783   
2784      if (isset($parsed_items['parent']) && isset($parsed_items['name']) && $parsed_items['parent'] == $parsed_items['name'])
2785      {
2786          unset($parsed_items['parent']);
2787      }
2788   
2789      return $parsed_items;
2790  }
2791   
2792  /**
2793  * Return a nicely formatted backtrace.
2794  *
2795  * Turns the array returned by debug_backtrace() into HTML markup.
2796  * Also filters out absolute paths to phpBB root.
2797  *
2798  * @return string    HTML markup
2799  */
2800  function get_backtrace()
2801  {
2802      $output = '<div style="font-family: monospace;">';
2803      $backtrace = debug_backtrace();
2804   
2805      // We skip the first one, because it only shows this file/function
2806      unset($backtrace[0]);
2807   
2808      foreach ($backtrace as $trace)
2809      {
2810          // Strip the current directory from path
2811          $trace['file'] = (empty($trace['file'])) ? '(not given by php)' : htmlspecialchars(phpbb_filter_root_path($trace['file']), ENT_COMPAT);
2812          $trace['line'] = (empty($trace['line'])) ? '(not given by php)' : $trace['line'];
2813   
2814          // Only show function arguments for include etc.
2815          // Other parameters may contain sensible information
2816          $argument = '';
2817          if (!empty($trace['args'][0]) && in_array($trace['function'], array('include', 'require', 'include_once', 'require_once')))
2818          {
2819              $argument = htmlspecialchars(phpbb_filter_root_path($trace['args'][0]), ENT_COMPAT);
2820          }
2821   
2822          $trace['class'] = (!isset($trace['class'])) ? '' : $trace['class'];
2823          $trace['type'] = (!isset($trace['type'])) ? '' : $trace['type'];
2824   
2825          $output .= '<br />';
2826          $output .= '<b>FILE:</b> ' . $trace['file'] . '<br />';
2827          $output .= '<b>LINE:</b> ' . ((!empty($trace['line'])) ? $trace['line'] : '') . '<br />';
2828   
2829          $output .= '<b>CALL:</b> ' . htmlspecialchars($trace['class'] . $trace['type'] . $trace['function'], ENT_COMPAT);
2830          $output .= '(' . (($argument !== '') ? "'$argument'" : '') . ')<br />';
2831      }
2832      $output .= '</div>';
2833      return $output;
2834  }
2835   
2836  /**
2837  * This function returns a regular expression pattern for commonly used expressions
2838  * Use with / as delimiter for email mode and # for url modes
2839  * mode can be: email|bbcode_htm|url|url_inline|www_url|www_url_inline|relative_url|relative_url_inline|ipv4|ipv6
2840  */
2841  function get_preg_expression($mode)
2842  {
2843      switch ($mode)
2844      {
2845          case 'email':
2846              // Regex written by James Watts and Francisco Jose Martin Moreno
2847              // http://fightingforalostcause.net/misc/2006/compare-email-regex.php
2848              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})?)';
2849          break;
2850   
2851          case 'bbcode_htm':
2852              return array(
2853                  '#<!\-\- e \-\-><a href="mailto:(.*?)">.*?</a><!\-\- e \-\->#',
2854                  '#<!\-\- l \-\-><a (?:class="[\w-]+" )?href="(.*?)(?:(&amp;|\?)sid=[0-9a-f]{32})?">.*?</a><!\-\- l \-\->#',
2855                  '#<!\-\- ([mw]) \-\-><a (?:class="[\w-]+" )?href="http://(.*?)">\2</a><!\-\- \1 \-\->#',
2856                  '#<!\-\- ([mw]) \-\-><a (?:class="[\w-]+" )?href="(.*?)">.*?</a><!\-\- \1 \-\->#',
2857                  '#<!\-\- s(.*?) \-\-><img src="\{SMILIES_PATH\}\/.*? \/><!\-\- s\1 \-\->#',
2858                  '#<!\-\- .*? \-\->#s',
2859                  '#<.*?>#s',
2860              );
2861          break;
2862   
2863          // Whoa these look impressive!
2864          // The code to generate the following two regular expressions which match valid IPv4/IPv6 addresses
2865          // can be found in the develop directory
2866   
2867          // @deprecated
2868          case 'ipv4':
2869              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])$#';
2870          break;
2871   
2872          // @deprecated
2873          case 'ipv6':
2874              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';
2875          break;
2876   
2877          case 'url':
2878              // generated with regex_idn.php file in the develop folder
2879              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})*)?";
2880          break;
2881   
2882          case 'url_http':
2883              // generated with regex_idn.php file in the develop folder
2884              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})*)?";
2885          break;
2886   
2887          case 'url_inline':
2888              // generated with regex_idn.php file in the develop folder
2889              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})*)?";
2890          break;
2891   
2892          case 'www_url':
2893              // generated with regex_idn.php file in the develop folder
2894              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})*)?";
2895          break;
2896   
2897          case 'www_url_inline':
2898              // generated with regex_idn.php file in the develop folder
2899              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})*)?";
2900          break;
2901   
2902          case 'relative_url':
2903              // generated with regex_idn.php file in the develop folder
2904              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})*)?";
2905          break;
2906   
2907          case 'relative_url_inline':
2908              // generated with regex_idn.php file in the develop folder
2909              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})*)?";
2910          break;
2911   
2912          case 'table_prefix':
2913              return '#^[a-zA-Z][a-zA-Z0-9_]*$#';
2914          break;
2915   
2916          // Matches the predecing dot
2917          case 'path_remove_dot_trailing_slash':
2918              return '#^(?:(\.)?)+(?:(.+)?)+(?:([\\/\\\])$)#';
2919          break;
2920   
2921          case 'semantic_version':
2922              // Regular expression to match semantic versions by http://rgxdb.com/
2923              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])+)))*))?)$/';
2924          break;
2925      }
2926   
2927      return '';
2928  }
2929   
2930  /**
2931  * Generate regexp for naughty words censoring
2932  * Depends on whether installed PHP version supports unicode properties
2933  *
2934  * @param string    $word            word template to be replaced
2935  *
2936  * @return string $preg_expr        regex to use with word censor
2937  */
2938  function get_censor_preg_expression($word)
2939  {
2940      // Unescape the asterisk to simplify further conversions
2941      $word = str_replace('\*', '*', preg_quote($word, '#'));
2942   
2943      // Replace asterisk(s) inside the pattern, at the start and at the end of it with regexes
2944      $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);
2945   
2946      // Generate the final substitution
2947      $preg_expr = '#(?<![\p{Nd}\p{L}_-])(' . $word . ')(?![\p{Nd}\p{L}_-])#iu';
2948   
2949      return $preg_expr;
2950  }
2951   
2952  /**
2953  * Returns the first block of the specified IPv6 address and as many additional
2954  * ones as specified in the length parameter.
2955  * If length is zero, then an empty string is returned.
2956  * If length is greater than 3 the complete IP will be returned
2957  */
2958  function short_ipv6($ip, $length)
2959  {
2960      if ($length < 1)
2961      {
2962          return '';
2963      }
2964   
2965      // Handle IPv4 embedded IPv6 addresses
2966      if (preg_match('/(?:\d{1,3}\.){3}\d{1,3}$/i', $ip))
2967      {
2968          $binary_ip = inet_pton($ip);
2969          $ip_v6 = $binary_ip ? inet_ntop($binary_ip) : $ip;
2970          $ip = $ip_v6 ?: $ip;
2971      }
2972   
2973      // extend IPv6 addresses
2974      $blocks = substr_count($ip, ':') + 1;
2975      if ($blocks < 9)
2976      {
2977          $ip = str_replace('::', ':' . str_repeat('0000:', 9 - $blocks), $ip);
2978      }
2979      if ($ip[0] == ':')
2980      {
2981          $ip = '0000' . $ip;
2982      }
2983      if ($length < 4)
2984      {
2985          $ip = implode(':', array_slice(explode(':', $ip), 0, 1 + $length));
2986      }
2987   
2988      return $ip;
2989  }
2990   
2991  /**
2992  * Normalises an internet protocol address,
2993  * also checks whether the specified address is valid.
2994  *
2995  * IPv4 addresses are returned 'as is'.
2996  *
2997  * IPv6 addresses are normalised according to
2998  *    A Recommendation for IPv6 Address Text Representation
2999  *    http://tools.ietf.org/html/draft-ietf-6man-text-addr-representation-07
3000  *
3001  * @param string $address    IP address
3002  *
3003  * @return mixed        false if specified address is not valid,
3004  *                    string otherwise
3005  */
3006  function phpbb_ip_normalise(string $address)
3007  {
3008      $ip_normalised = false;
3009   
3010      if (filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4))
3011      {
3012          $ip_normalised = $address;
3013      }
3014      else if (filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6))
3015      {
3016          $ip_normalised = inet_ntop(inet_pton($address));
3017   
3018          // If is ipv4
3019          if (stripos($ip_normalised, '::ffff:') === 0)
3020          {
3021              $ip_normalised = substr($ip_normalised, 7);
3022          }
3023      }
3024   
3025      return $ip_normalised;
3026  }
3027   
3028  // Handler, header and footer
3029   
3030  /**
3031  * Error and message handler, call with trigger_error if read
3032  */
3033  function msg_handler($errno, $msg_text, $errfile, $errline)
3034  {
3035      global $cache, $db, $auth, $template, $config, $user, $request;
3036      global $phpbb_root_path, $msg_title, $msg_long_text, $phpbb_log;
3037      global $phpbb_container;
3038   
3039      // https://www.php.net/manual/en/language.operators.errorcontrol.php
3040      // error_reporting() return a different error code inside the error handler after php 8.0
3041      $suppresed = E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR | E_PARSE;
3042      if (PHP_VERSION_ID < 80000)
3043      {
3044          $suppresed = 0;
3045      }
3046   
3047      // Do not display notices if we suppress them via @
3048      if (error_reporting() == $suppresed && $errno != E_USER_ERROR && $errno != E_USER_WARNING && $errno != E_USER_NOTICE)
3049      {
3050          return;
3051      }
3052   
3053      // Message handler is stripping text. In case we need it, we are possible to define long text...
3054      if (isset($msg_long_text) && $msg_long_text && !$msg_text)
3055      {
3056          $msg_text = $msg_long_text;
3057      }
3058   
3059      switch ($errno)
3060      {
3061          case E_NOTICE:
3062          case E_WARNING:
3063   
3064              // Check the error reporting level and return if the error level does not match
3065              // If DEBUG is defined the default level is E_ALL
3066              if (($errno & ($phpbb_container != null && $phpbb_container->getParameter('debug.show_errors') ? E_ALL : error_reporting())) == 0)
3067              {
3068                  return;
3069              }
3070   
3071              if (strpos($errfile, 'cache') === false && strpos($errfile, 'template.') === false)
3072              {
3073                  $errfile = phpbb_filter_root_path($errfile);
3074                  $msg_text = phpbb_filter_root_path($msg_text);
3075                  $error_name = ($errno === E_WARNING) ? 'PHP Warning' : 'PHP Notice';
3076                  echo '<b>[phpBB Debug] ' . $error_name . '</b>: in file <b>' . $errfile . '</b> on line <b>' . $errline . '</b>: <b>' . $msg_text . '</b><br />' . "\n";
3077   
3078                  // we are writing an image - the user won't see the debug, so let's place it in the log
3079                  if (defined('IMAGE_OUTPUT') || defined('IN_CRON'))
3080                  {
3081                      $phpbb_log->add('critical', $user->data['user_id'], $user->ip, 'LOG_IMAGE_GENERATION_ERROR', false, array($errfile, $errline, $msg_text));
3082                  }
3083                  // echo '<br /><br />BACKTRACE<br />' . get_backtrace() . '<br />' . "\n";
3084              }
3085   
3086              return;
3087   
3088          break;
3089   
3090          case E_USER_ERROR:
3091   
3092              if (!empty($user) && $user->is_setup())
3093              {
3094                  $msg_text = (!empty($user->lang[$msg_text])) ? $user->lang[$msg_text] : $msg_text;
3095                  $msg_title = (!isset($msg_title)) ? $user->lang['GENERAL_ERROR'] : ((!empty($user->lang[$msg_title])) ? $user->lang[$msg_title] : $msg_title);
3096   
3097                  $l_return_index = sprintf($user->lang['RETURN_INDEX'], '<a href="' . $phpbb_root_path . '">', '</a>');
3098                  $l_notify = '';
3099   
3100                  if (!empty($config['board_contact']))
3101                  {
3102                      $l_notify = '<p>' . sprintf($user->lang['NOTIFY_ADMIN_EMAIL'], $config['board_contact']) . '</p>';
3103                  }
3104              }
3105              else
3106              {
3107                  $msg_title = 'General Error';
3108                  $l_return_index = '<a href="' . $phpbb_root_path . '">Return to index page</a>';
3109                  $l_notify = '';
3110   
3111                  if (!empty($config['board_contact']))
3112                  {
3113                      $l_notify = '<p>Please notify the board administrator or webmaster: <a href="mailto:' . $config['board_contact'] . '">' . $config['board_contact'] . '</a></p>';
3114                  }
3115              }
3116   
3117              $log_text = $msg_text;
3118              $backtrace = get_backtrace();
3119              if ($backtrace)
3120              {
3121                  $log_text .= '<br /><br />BACKTRACE<br />' . $backtrace;
3122              }
3123   
3124              if (defined('IN_INSTALL') || ($phpbb_container != null && $phpbb_container->getParameter('debug.show_errors')) || isset($auth) && $auth->acl_get('a_'))
3125              {
3126                  $msg_text = $log_text;
3127   
3128                  // If this is defined there already was some output
3129                  // So let's not break it
3130                  if (defined('IN_DB_UPDATE'))
3131                  {
3132                      echo '<div class="errorbox">' . $msg_text . '</div>';
3133   
3134                      $db->sql_return_on_error(true);
3135                      phpbb_end_update($cache, $config);
3136                  }
3137              }
3138   
3139              if ((defined('IN_CRON') || defined('IMAGE_OUTPUT')) && isset($db))
3140              {
3141                  // let's avoid loops
3142                  $db->sql_return_on_error(true);
3143                  $phpbb_log->add('critical', $user->data['user_id'], $user->ip, 'LOG_GENERAL_ERROR', false, array($msg_title, $log_text));
3144                  $db->sql_return_on_error(false);
3145              }
3146   
3147              // Do not send 200 OK, but service unavailable on errors
3148              send_status_line(503, 'Service Unavailable');
3149   
3150              garbage_collection();
3151   
3152              // Try to not call the adm page data...
3153   
3154              echo '<!DOCTYPE html>';
3155              echo '<html dir="ltr">';
3156              echo '<head>';
3157              echo '<meta charset="utf-8">';
3158              echo '<meta http-equiv="X-UA-Compatible" content="IE=edge">';
3159              echo '<title>' . $msg_title . '</title>';
3160              echo '<style type="text/css">' . "\n" . '/* <![CDATA[ */' . "\n";
3161              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; } ';
3162              echo 'a:link, a:active, a:visited { color: #006699; text-decoration: none; } a:hover { color: #DD6900; text-decoration: underline; } ';
3163              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; } ';
3164              echo '.panel { margin: 4px 0; background-color: #FFFFFF; border: solid 1px  #A9B8C2; } ';
3165              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; } ';
3166              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; } ';
3167              echo "\n" . '/* ]]> */' . "\n";
3168              echo '</style>';
3169              echo '</head>';
3170              echo '<body id="errorpage">';
3171              echo '<div id="wrap">';
3172              echo '    <div id="page-header">';
3173              echo '        ' . $l_return_index;
3174              echo '    </div>';
3175              echo '    <div id="acp">';
3176              echo '    <div class="panel">';
3177              echo '        <div id="content">';
3178              echo '            <h1>' . $msg_title . '</h1>';
3179   
3180              echo '            <div>' . $msg_text . '</div>';
3181   
3182              echo $l_notify;
3183   
3184              echo '        </div>';
3185              echo '    </div>';
3186              echo '    </div>';
3187              echo '    <div id="page-footer">';
3188              echo '        Powered by <a href="https://www.phpbb.com/">phpBB</a>&reg; Forum Software &copy; phpBB Limited';
3189              echo '    </div>';
3190              echo '</div>';
3191              echo '</body>';
3192              echo '</html>';
3193   
3194              exit_handler();
3195   
3196              // On a fatal error (and E_USER_ERROR *is* fatal) we never want other scripts to continue and force an exit here.
3197              exit;
3198          break;
3199   
3200          case E_USER_WARNING:
3201          case E_USER_NOTICE:
3202   
3203              define('IN_ERROR_HANDLER', true);
3204   
3205              if (empty($user->data))
3206              {
3207                  $user->session_begin();
3208              }
3209   
3210              // We re-init the auth array to get correct results on login/logout
3211              $auth->acl($user->data);
3212   
3213              if (!$user->is_setup())
3214              {
3215                  $user->setup();
3216              }
3217   
3218              if ($msg_text == 'ERROR_NO_ATTACHMENT' || $msg_text == 'NO_FORUM' || $msg_text == 'NO_TOPIC' || $msg_text == 'NO_USER')
3219              {
3220                  send_status_line(404, 'Not Found');
3221              }
3222   
3223              $msg_text = (!empty($user->lang[$msg_text])) ? $user->lang[$msg_text] : $msg_text;
3224              $msg_title = (!isset($msg_title)) ? $user->lang['INFORMATION'] : ((!empty($user->lang[$msg_title])) ? $user->lang[$msg_title] : $msg_title);
3225   
3226              if (!defined('HEADER_INC'))
3227              {
3228                  if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
3229                  {
3230                      adm_page_header($msg_title);
3231                  }
3232                  else
3233                  {
3234                      page_header($msg_title);
3235                  }
3236              }
3237   
3238              $template->set_filenames(array(
3239                  'body' => 'message_body.html')
3240              );
3241   
3242              $template->assign_vars(array(
3243                  'MESSAGE_TITLE'        => $msg_title,
3244                  'MESSAGE_TEXT'        => $msg_text,
3245                  'S_USER_WARNING'    => ($errno == E_USER_WARNING) ? true : false,
3246                  'S_USER_NOTICE'        => ($errno == E_USER_NOTICE) ? true : false)
3247              );
3248   
3249              if ($request->is_ajax())
3250              {
3251                  global $refresh_data;
3252   
3253                  $json_response = new \phpbb\json_response;
3254                  $json_response->send(array(
3255                      'MESSAGE_TITLE'        => $msg_title,
3256                      'MESSAGE_TEXT'        => $msg_text,
3257                      'S_USER_WARNING'    => ($errno == E_USER_WARNING) ? true : false,
3258                      'S_USER_NOTICE'        => ($errno == E_USER_NOTICE) ? true : false,
3259                      'REFRESH_DATA'        => (!empty($refresh_data)) ? $refresh_data : null
3260                  ));
3261              }
3262   
3263              // We do not want the cron script to be called on error messages
3264              define('IN_CRON', true);
3265   
3266              if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
3267              {
3268                  adm_page_footer();
3269              }
3270              else
3271              {
3272                  page_footer();
3273              }
3274   
3275              exit_handler();
3276          break;
3277   
3278          // PHP4 compatibility
3279          case E_DEPRECATED:
3280              return true;
3281          break;
3282      }
3283   
3284      // If we notice an error not handled here we pass this back to PHP by returning false
3285      // This may not work for all php versions
3286      return false;
3287  }
3288   
3289  /**
3290  * Removes absolute path to phpBB root directory from error messages
3291  * and converts backslashes to forward slashes.
3292  *
3293  * @param string $errfile    Absolute file path
3294  *                            (e.g. /var/www/phpbb3/phpBB/includes/functions.php)
3295  *                            Please note that if $errfile is outside of the phpBB root,
3296  *                            the root path will not be found and can not be filtered.
3297  * @return string            Relative file path
3298  *                            (e.g. /includes/functions.php)
3299  */
3300  function phpbb_filter_root_path($errfile)
3301  {
3302      global $phpbb_filesystem;
3303   
3304      static $root_path;
3305   
3306      if (empty($root_path))
3307      {
3308          if ($phpbb_filesystem)
3309          {
3310              $root_path = $phpbb_filesystem->realpath(__DIR__ . '/../');
3311          }
3312          else
3313          {
3314              $filesystem = new \phpbb\filesystem\filesystem();
3315              $root_path = $filesystem->realpath(__DIR__ . '/../');
3316          }
3317      }
3318   
3319      return str_replace(array($root_path, '\\'), array('[ROOT]', '/'), $errfile);
3320  }
3321   
3322  /**
3323  * Queries the session table to get information about online guests
3324  * @param int $item_id Limits the search to the item with this id
3325  * @param string $item The name of the item which is stored in the session table as session_{$item}_id
3326  * @return int The number of active distinct guest sessions
3327  */
3328  function obtain_guest_count($item_id = 0, $item = 'forum')
3329  {
3330      global $db, $config;
3331   
3332      if ($item_id)
3333      {
3334          $reading_sql = ' AND s.session_' . $item . '_id = ' . (int) $item_id;
3335      }
3336      else
3337      {
3338          $reading_sql = '';
3339      }
3340      $time = (time() - (intval($config['load_online_time']) * 60));
3341   
3342      // Get number of online guests
3343   
3344      if ($db->get_sql_layer() === 'sqlite3')
3345      {
3346          $sql = 'SELECT COUNT(session_ip) as num_guests
3347              FROM (
3348                  SELECT DISTINCT s.session_ip
3349                  FROM ' . SESSIONS_TABLE . ' s
3350                  WHERE s.session_user_id = ' . ANONYMOUS . '
3351                      AND s.session_time >= ' . ($time - ((int) ($time % 60))) .
3352                  $reading_sql .
3353              ')';
3354      }
3355      else
3356      {
3357          $sql = 'SELECT COUNT(DISTINCT s.session_ip) as num_guests
3358              FROM ' . SESSIONS_TABLE . ' s
3359              WHERE s.session_user_id = ' . ANONYMOUS . '
3360                  AND s.session_time >= ' . ($time - ((int) ($time % 60))) .
3361              $reading_sql;
3362      }
3363      $result = $db->sql_query($sql);
3364      $guests_online = (int) $db->sql_fetchfield('num_guests');
3365      $db->sql_freeresult($result);
3366   
3367      return $guests_online;
3368  }
3369   
3370  /**
3371  * Queries the session table to get information about online users
3372  * @param int $item_id Limits the search to the item with this id
3373  * @param string $item The name of the item which is stored in the session table as session_{$item}_id
3374  * @return array An array containing the ids of online, hidden and visible users, as well as statistical info
3375  */
3376  function obtain_users_online($item_id = 0, $item = 'forum')
3377  {
3378      global $db, $config;
3379   
3380      $reading_sql = '';
3381      if ($item_id !== 0)
3382      {
3383          $reading_sql = ' AND s.session_' . $item . '_id = ' . (int) $item_id;
3384      }
3385   
3386      $online_users = array(
3387          'online_users'            => array(),
3388          'hidden_users'            => array(),
3389          'total_online'            => 0,
3390          'visible_online'        => 0,
3391          'hidden_online'            => 0,
3392          'guests_online'            => 0,
3393      );
3394   
3395      if ($config['load_online_guests'])
3396      {
3397          $online_users['guests_online'] = obtain_guest_count($item_id, $item);
3398      }
3399   
3400      // a little discrete magic to cache this for 30 seconds
3401      $time = (time() - (intval($config['load_online_time']) * 60));
3402   
3403      $sql = 'SELECT s.session_user_id, s.session_ip, s.session_viewonline
3404          FROM ' . SESSIONS_TABLE . ' s
3405          WHERE s.session_time >= ' . ($time - ((int) ($time % 30))) .
3406              $reading_sql .
3407          ' AND s.session_user_id <> ' . ANONYMOUS;
3408      $result = $db->sql_query($sql);
3409   
3410      while ($row = $db->sql_fetchrow($result))
3411      {
3412          // Skip multiple sessions for one user
3413          if (!isset($online_users['online_users'][$row['session_user_id']]))
3414          {
3415              $online_users['online_users'][$row['session_user_id']] = (int) $row['session_user_id'];
3416              if ($row['session_viewonline'])
3417              {
3418                  $online_users['visible_online']++;
3419              }
3420              else
3421              {
3422                  $online_users['hidden_users'][$row['session_user_id']] = (int) $row['session_user_id'];
3423                  $online_users['hidden_online']++;
3424              }
3425          }
3426      }
3427      $online_users['total_online'] = $online_users['guests_online'] + $online_users['visible_online'] + $online_users['hidden_online'];
3428      $db->sql_freeresult($result);
3429   
3430      return $online_users;
3431  }
3432   
3433  /**
3434  * Uses the result of obtain_users_online to generate a localized, readable representation.
3435  * @param mixed $online_users result of obtain_users_online - array with user_id lists for total, hidden and visible users, and statistics
3436  * @param int $item_id Indicate that the data is limited to one item and not global
3437  * @param string $item The name of the item which is stored in the session table as session_{$item}_id
3438  * @return array An array containing the string for output to the template
3439  */
3440  function obtain_users_online_string($online_users, $item_id = 0, $item = 'forum')
3441  {
3442      global $config, $db, $user, $auth, $phpbb_dispatcher;
3443   
3444      $user_online_link = $rowset = array();
3445      // Need caps version of $item for language-strings
3446      $item_caps = strtoupper($item);
3447   
3448      if (count($online_users['online_users']))
3449      {
3450          $sql_ary = array(
3451              'SELECT'    => 'u.username, u.username_clean, u.user_id, u.user_type, u.user_allow_viewonline, u.user_colour',
3452              'FROM'        => array(
3453                  USERS_TABLE    => 'u',
3454              ),
3455              'WHERE'        => $db->sql_in_set('u.user_id', $online_users['online_users']),
3456              'ORDER_BY'    => 'u.username_clean ASC',
3457          );
3458   
3459          /**
3460          * Modify SQL query to obtain online users data
3461          *
3462          * @event core.obtain_users_online_string_sql
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    sql_ary            SQL query array to obtain users online data
3470          * @since 3.1.4-RC1
3471          * @changed 3.1.7-RC1            Change sql query into array and adjust var accordingly. Allows extension authors the ability to adjust the sql_ary.
3472          */
3473          $vars = array('online_users', 'item_id', 'item', 'sql_ary');
3474          extract($phpbb_dispatcher->trigger_event('core.obtain_users_online_string_sql', compact($vars)));
3475   
3476          $result = $db->sql_query($db->sql_build_query('SELECT', $sql_ary));
3477          $rowset = $db->sql_fetchrowset($result);
3478          $db->sql_freeresult($result);
3479   
3480          foreach ($rowset as $row)
3481          {
3482              // User is logged in and therefore not a guest
3483              if ($row['user_id'] != ANONYMOUS)
3484              {
3485                  if (isset($online_users['hidden_users'][$row['user_id']]))
3486                  {
3487                      $row['username'] = '<em>' . $row['username'] . '</em>';
3488                  }
3489   
3490                  if (!isset($online_users['hidden_users'][$row['user_id']]) || $auth->acl_get('u_viewonline') || $row['user_id'] === $user->data['user_id'])
3491                  {
3492                      $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']);
3493                  }
3494              }
3495          }
3496      }
3497   
3498      /**
3499      * Modify online userlist data
3500      *
3501      * @event core.obtain_users_online_string_before_modify
3502      * @var    array    online_users        Array with online users data
3503      *                                    from obtain_users_online()
3504      * @var    int        item_id                Restrict online users to item id
3505      * @var    string    item                Restrict online users to a certain
3506      *                                    session item, e.g. forum for
3507      *                                    session_forum_id
3508      * @var    array    rowset                Array with online users data
3509      * @var    array    user_online_link    Array with online users items (usernames)
3510      * @since 3.1.10-RC1
3511      */
3512      $vars = array(
3513          'online_users',
3514          'item_id',
3515          'item',
3516          'rowset',
3517          'user_online_link',
3518      );
3519      extract($phpbb_dispatcher->trigger_event('core.obtain_users_online_string_before_modify', compact($vars)));
3520   
3521      $online_userlist = implode(', ', $user_online_link);
3522   
3523      if (!$online_userlist)
3524      {
3525          $online_userlist = $user->lang['NO_ONLINE_USERS'];
3526      }
3527   
3528      if ($item_id === 0)
3529      {
3530          $online_userlist = $user->lang['REGISTERED_USERS'] . ' ' . $online_userlist;
3531      }
3532      else if ($config['load_online_guests'])
3533      {
3534          $online_userlist = $user->lang('BROWSING_' . $item_caps . '_GUESTS', $online_users['guests_online'], $online_userlist);
3535      }
3536      else
3537      {
3538          $online_userlist = sprintf($user->lang['BROWSING_' . $item_caps], $online_userlist);
3539      }
3540      // Build online listing
3541      $visible_online = $user->lang('REG_USERS_TOTAL', (int) $online_users['visible_online']);
3542      $hidden_online = $user->lang('HIDDEN_USERS_TOTAL', (int) $online_users['hidden_online']);
3543   
3544      if ($config['load_online_guests'])
3545      {
3546          $guests_online = $user->lang('GUEST_USERS_TOTAL', (int) $online_users['guests_online']);
3547          $l_online_users = $user->lang('ONLINE_USERS_TOTAL_GUESTS', (int) $online_users['total_online'], $visible_online, $hidden_online, $guests_online);
3548      }
3549      else
3550      {
3551          $l_online_users = $user->lang('ONLINE_USERS_TOTAL', (int) $online_users['total_online'], $visible_online, $hidden_online);
3552      }
3553   
3554      /**
3555      * Modify online userlist data
3556      *
3557      * @event core.obtain_users_online_string_modify
3558      * @var    array    online_users        Array with online users data
3559      *                                    from obtain_users_online()
3560      * @var    int        item_id                Restrict online users to item id
3561      * @var    string    item                Restrict online users to a certain
3562      *                                    session item, e.g. forum for
3563      *                                    session_forum_id
3564      * @var    array    rowset                Array with online users data
3565      * @var    array    user_online_link    Array with online users items (usernames)
3566      * @var    string    online_userlist        String containing users online list
3567      * @var    string    l_online_users        String with total online users count info
3568      * @since 3.1.4-RC1
3569      */
3570      $vars = array(
3571          'online_users',
3572          'item_id',
3573          'item',
3574          'rowset',
3575          'user_online_link',
3576          'online_userlist',
3577          'l_online_users',
3578      );
3579      extract($phpbb_dispatcher->trigger_event('core.obtain_users_online_string_modify', compact($vars)));
3580   
3581      return array(
3582          'online_userlist'    => $online_userlist,
3583          'l_online_users'    => $l_online_users,
3584      );
3585  }
3586   
3587  /**
3588  * Get option bitfield from custom data
3589  *
3590  * @param int    $bit        The bit/value to get
3591  * @param int    $data        Current bitfield to check
3592  * @return bool    Returns true if value of constant is set in bitfield, else false
3593  */
3594  function phpbb_optionget($bit, $data)
3595  {
3596      return ($data & 1 << (int) $bit) ? true : false;
3597  }
3598   
3599  /**
3600  * Set option bitfield
3601  *
3602  * @param int    $bit        The bit/value to set/unset
3603  * @param bool    $set        True if option should be set, false if option should be unset.
3604  * @param int    $data        Current bitfield to change
3605  *
3606  * @return int    The new bitfield
3607  */
3608  function phpbb_optionset($bit, $set, $data)
3609  {
3610      if ($set && !($data & 1 << $bit))
3611      {
3612          $data += 1 << $bit;
3613      }
3614      else if (!$set && ($data & 1 << $bit))
3615      {
3616          $data -= 1 << $bit;
3617      }
3618   
3619      return $data;
3620  }
3621   
3622   
3623  /**
3624  * Escapes and quotes a string for use as an HTML/XML attribute value.
3625  *
3626  * This is a port of Python xml.sax.saxutils quoteattr.
3627  *
3628  * The function will attempt to choose a quote character in such a way as to
3629  * avoid escaping quotes in the string. If this is not possible the string will
3630  * be wrapped in double quotes and double quotes will be escaped.
3631  *
3632  * @param string $data The string to be escaped
3633  * @param array $entities Associative array of additional entities to be escaped
3634  * @return string Escaped and quoted string
3635  */
3636  function phpbb_quoteattr($data, $entities = null)
3637  {
3638      $data = str_replace('&', '&amp;', $data);
3639      $data = str_replace('>', '&gt;', $data);
3640      $data = str_replace('<', '&lt;', $data);
3641   
3642      $data = str_replace("\n", '&#10;', $data);
3643      $data = str_replace("\r", '&#13;', $data);
3644      $data = str_replace("\t", '&#9;', $data);
3645   
3646      if (!empty($entities))
3647      {
3648          $data = str_replace(array_keys($entities), array_values($entities), $data);
3649      }
3650   
3651      if (strpos($data, '"') !== false)
3652      {
3653          if (strpos($data, "'") !== false)
3654          {
3655              $data = '"' . str_replace('"', '&quot;', $data) . '"';
3656          }
3657          else
3658          {
3659              $data = "'" . $data . "'";
3660          }
3661      }
3662      else
3663      {
3664          $data = '"' . $data . '"';
3665      }
3666   
3667      return $data;
3668  }
3669   
3670  /**
3671  * Get user avatar
3672  *
3673  * @param array $user_row Row from the users table
3674  * @param string $alt Optional language string for alt tag within image, can be a language key or text
3675  * @param bool $ignore_config Ignores the config-setting, to be still able to view the avatar in the UCP
3676  * @param bool $lazy If true, will be lazy loaded (requires JS)
3677  *
3678  * @return string Avatar html
3679  */
3680  function phpbb_get_user_avatar($user_row, $alt = 'USER_AVATAR', $ignore_config = false, $lazy = false)
3681  {
3682      $row = \phpbb\avatar\manager::clean_row($user_row, 'user');
3683      return phpbb_get_avatar($row, $alt, $ignore_config, $lazy);
3684  }
3685   
3686  /**
3687  * Get group avatar
3688  *
3689  * @param array $group_row Row from the groups table
3690  * @param string $alt Optional language string for alt tag within image, can be a language key or text
3691  * @param bool $ignore_config Ignores the config-setting, to be still able to view the avatar in the UCP
3692  * @param bool $lazy If true, will be lazy loaded (requires JS)
3693  *
3694  * @return string Avatar html
3695  */
3696  function phpbb_get_group_avatar($group_row, $alt = 'GROUP_AVATAR', $ignore_config = false, $lazy = false)
3697  {
3698      $row = \phpbb\avatar\manager::clean_row($group_row, 'group');
3699      return phpbb_get_avatar($row, $alt, $ignore_config, $lazy);
3700  }
3701   
3702  /**
3703  * Get avatar
3704  *
3705  * @param array $row Row cleaned by \phpbb\avatar\manager::clean_row
3706  * @param string $alt Optional language string for alt tag within image, can be a language key or text
3707  * @param bool $ignore_config Ignores the config-setting, to be still able to view the avatar in the UCP
3708  * @param bool $lazy If true, will be lazy loaded (requires JS)
3709  *
3710  * @return string Avatar html
3711  */
3712  function phpbb_get_avatar($row, $alt, $ignore_config = false, $lazy = false)
3713  {
3714      global $user, $config;
3715      global $phpbb_container, $phpbb_dispatcher;
3716   
3717      if (!$config['allow_avatar'] && !$ignore_config)
3718      {
3719          return '';
3720      }
3721   
3722      $avatar_data = array(
3723          'src' => $row['avatar'],
3724          'width' => $row['avatar_width'],
3725          'height' => $row['avatar_height'],
3726      );
3727   
3728      /* @var $phpbb_avatar_manager \phpbb\avatar\manager */
3729      $phpbb_avatar_manager = $phpbb_container->get('avatar.manager');
3730      $driver = $phpbb_avatar_manager->get_driver($row['avatar_type'], !$ignore_config);
3731      $html = '';
3732   
3733      if ($driver)
3734      {
3735          $html = $driver->get_custom_html($user, $row, $alt);
3736          $avatar_data = $driver->get_data($row);
3737      }
3738      else
3739      {
3740          $avatar_data['src'] = '';
3741      }
3742   
3743      if (empty($html) && !empty($avatar_data['src']))
3744      {
3745          if ($lazy)
3746          {
3747              // This path is sent with the base template paths in the assign_vars()
3748              // call below. We need to correct it in case we are accessing from a
3749              // controller because the web paths will be incorrect otherwise.
3750              $phpbb_path_helper = $phpbb_container->get('path_helper');
3751              $web_path = $phpbb_path_helper->get_web_root_path();
3752   
3753              $theme = "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/theme';
3754   
3755              $src = 'src="' . $theme . '/images/no_avatar.gif" data-src="' . $avatar_data['src'] . '"';
3756          }
3757          else
3758          {
3759              $src = 'src="' . $avatar_data['src'] . '"';
3760          }
3761   
3762          $html = '<img class="avatar" ' . $src . ' ' .
3763              ($avatar_data['width'] ? ('width="' . $avatar_data['width'] . '" ') : '') .
3764              ($avatar_data['height'] ? ('height="' . $avatar_data['height'] . '" ') : '') .
3765              'alt="' . ((!empty($user->lang[$alt])) ? $user->lang[$alt] : $alt) . '" />';
3766      }
3767   
3768      /**
3769      * Event to modify HTML <img> tag of avatar
3770      *
3771      * @event core.get_avatar_after
3772      * @var    array    row                Row cleaned by \phpbb\avatar\manager::clean_row
3773      * @var    string    alt                Optional language string for alt tag within image, can be a language key or text
3774      * @var    bool    ignore_config    Ignores the config-setting, to be still able to view the avatar in the UCP
3775      * @var    array    avatar_data        The HTML attributes for avatar <img> tag
3776      * @var    string    html            The HTML <img> tag of generated avatar
3777      * @since 3.1.6-RC1
3778      */
3779      $vars = array('row', 'alt', 'ignore_config', 'avatar_data', 'html');
3780      extract($phpbb_dispatcher->trigger_event('core.get_avatar_after', compact($vars)));
3781   
3782      return $html;
3783  }
3784   
3785  /**
3786  * Generate page header
3787  */
3788  function page_header($page_title = '', $display_online_list = false, $item_id = 0, $item = 'forum', $send_headers = true)
3789  {
3790      global $db, $config, $template, $SID, $_SID, $_EXTRA_URL, $user, $auth, $phpEx, $phpbb_root_path;
3791      global $phpbb_dispatcher, $request, $phpbb_container, $phpbb_admin_path;
3792   
3793      if (defined('HEADER_INC'))
3794      {
3795          return;
3796      }
3797   
3798      define('HEADER_INC', true);
3799   
3800      // A listener can set this variable to `true` when it overrides this function
3801      $page_header_override = false;
3802   
3803      /**
3804      * Execute code and/or overwrite page_header()
3805      *
3806      * @event core.page_header
3807      * @var    string    page_title            Page title
3808      * @var    bool    display_online_list        Do we display online users list
3809      * @var    string    item                Restrict online users to a certain
3810      *                                    session item, e.g. forum for
3811      *                                    session_forum_id
3812      * @var    int        item_id                Restrict online users to item id
3813      * @var    bool    page_header_override    Shall we return instead of running
3814      *                                        the rest of page_header()
3815      * @since 3.1.0-a1
3816      */
3817      $vars = array('page_title', 'display_online_list', 'item_id', 'item', 'page_header_override');
3818      extract($phpbb_dispatcher->trigger_event('core.page_header', compact($vars)));
3819   
3820      if ($page_header_override)
3821      {
3822          return;
3823      }
3824   
3825      // gzip_compression
3826      if ($config['gzip_compress'])
3827      {
3828          // to avoid partially compressed output resulting in blank pages in
3829          // the browser or error messages, compression is disabled in a few cases:
3830          //
3831          // 1) if headers have already been sent, this indicates plaintext output
3832          //    has been started so further content must not be compressed
3833          // 2) the length of the current output buffer is non-zero. This means
3834          //    there is already some uncompressed content in this output buffer
3835          //    so further output must not be compressed
3836          // 3) if more than one level of output buffering is used because we
3837          //    cannot test all output buffer level content lengths. One level
3838          //    could be caused by php.ini output_buffering. Anything
3839          //    beyond that is manual, so the code wrapping phpBB in output buffering
3840          //    can easily compress the output itself.
3841          //
3842          if (@extension_loaded('zlib') && !headers_sent() && ob_get_level() <= 1 && ob_get_length() == 0)
3843          {
3844              ob_start('ob_gzhandler');
3845          }
3846      }
3847   
3848      $user->update_session_infos();
3849   
3850      // Generate logged in/logged out status
3851      if ($user->data['user_id'] != ANONYMOUS)
3852      {
3853          $u_login_logout = append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=logout', true, $user->session_id);
3854          $l_login_logout = $user->lang['LOGOUT'];
3855      }
3856      else
3857      {
3858          $redirect = $request->variable('redirect', rawurlencode($user->page['page']));
3859          $u_login_logout = append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=login&amp;redirect=' . $redirect);
3860          $l_login_logout = $user->lang['LOGIN'];
3861      }
3862   
3863      // Last visit date/time
3864      $s_last_visit = ($user->data['user_id'] != ANONYMOUS) ? $user->format_date($user->data['session_last_visit']) : '';
3865   
3866      // Get users online list ... if required
3867      $l_online_users = $online_userlist = $l_online_record = $l_online_time = '';
3868   
3869      if ($config['load_online'] && $config['load_online_time'] && $display_online_list)
3870      {
3871          /**
3872          * Load online data:
3873          * For obtaining another session column use $item and $item_id in the function-parameter, whereby the column is session_{$item}_id.
3874          */
3875          $item_id = max($item_id, 0);
3876   
3877          $online_users = obtain_users_online($item_id, $item);
3878          $user_online_strings = obtain_users_online_string($online_users, $item_id, $item);
3879   
3880          $l_online_users = $user_online_strings['l_online_users'];
3881          $online_userlist = $user_online_strings['online_userlist'];
3882          $total_online_users = $online_users['total_online'];
3883   
3884          if ($total_online_users > $config['record_online_users'])
3885          {
3886              $config->set('record_online_users', $total_online_users, false);
3887              $config->set('record_online_date', time(), false);
3888          }
3889   
3890          $l_online_record = $user->lang('RECORD_ONLINE_USERS', (int) $config['record_online_users'], $user->format_date($config['record_online_date'], false, true));
3891   
3892          $l_online_time = $user->lang('VIEW_ONLINE_TIMES', (int) $config['load_online_time']);
3893      }
3894   
3895      $s_privmsg_new = false;
3896   
3897      // Check for new private messages if user is logged in
3898      if (!empty($user->data['is_registered']))
3899      {
3900          if ($user->data['user_new_privmsg'])
3901          {
3902              if (!$user->data['user_last_privmsg'] || $user->data['user_last_privmsg'] > $user->data['session_last_visit'])
3903              {
3904                  $sql = 'UPDATE ' . USERS_TABLE . '
3905                      SET user_last_privmsg = ' . $user->data['session_last_visit'] . '
3906                      WHERE user_id = ' . $user->data['user_id'];
3907                  $db->sql_query($sql);
3908   
3909                  $s_privmsg_new = true;
3910              }
3911              else
3912              {
3913                  $s_privmsg_new = false;
3914              }
3915          }
3916          else
3917          {
3918              $s_privmsg_new = false;
3919          }
3920      }
3921   
3922      // Negative forum and topic IDs are not allowed
3923      $forum_id = max(0, $request->variable('f', 0));
3924      $topic_id = max(0, $request->variable('t', 0));
3925   
3926      $s_feed_news = false;
3927   
3928      // Get option for news
3929      if ($config['feed_enable'])
3930      {
3931          $sql = 'SELECT forum_id
3932              FROM ' . FORUMS_TABLE . '
3933              WHERE ' . $db->sql_bit_and('forum_options', FORUM_OPTION_FEED_NEWS, '<> 0');
3934          $result = $db->sql_query_limit($sql, 1, 0, 600);
3935          $s_feed_news = (int) $db->sql_fetchfield('forum_id');
3936          $db->sql_freeresult($result);
3937      }
3938   
3939      // This path is sent with the base template paths in the assign_vars()
3940      // call below. We need to correct it in case we are accessing from a
3941      // controller because the web paths will be incorrect otherwise.
3942      /* @var $phpbb_path_helper \phpbb\path_helper */
3943      $phpbb_path_helper = $phpbb_container->get('path_helper');
3944      $web_path = $phpbb_path_helper->get_web_root_path();
3945   
3946      // Send a proper content-language to the output
3947      $user_lang = $user->lang['USER_LANG'];
3948      if (strpos($user_lang, '-x-') !== false)
3949      {
3950          $user_lang = substr($user_lang, 0, strpos($user_lang, '-x-'));
3951      }
3952   
3953      $s_search_hidden_fields = array();
3954      if ($_SID)
3955      {
3956          $s_search_hidden_fields['sid'] = $_SID;
3957      }
3958   
3959      if (!empty($_EXTRA_URL))
3960      {
3961          foreach ($_EXTRA_URL as $url_param)
3962          {
3963              $url_param = explode('=', $url_param, 2);
3964              $s_search_hidden_fields[$url_param[0]] = $url_param[1];
3965          }
3966      }
3967   
3968      $dt = $user->create_datetime();
3969      $timezone_offset = $user->lang(array('timezones', 'UTC_OFFSET'), phpbb_format_timezone_offset($dt->getOffset()));
3970      $timezone_name = $user->timezone->getName();
3971      if (isset($user->lang['timezones'][$timezone_name]))
3972      {
3973          $timezone_name = $user->lang['timezones'][$timezone_name];
3974      }
3975   
3976      // Output the notifications
3977      $notifications = false;
3978      if ($config['load_notifications'] && $config['allow_board_notifications'] && $user->data['user_id'] != ANONYMOUS && $user->data['user_type'] != USER_IGNORE)
3979      {
3980          /* @var $phpbb_notifications \phpbb\notification\manager */
3981          $phpbb_notifications = $phpbb_container->get('notification_manager');
3982   
3983          $notifications = $phpbb_notifications->load_notifications('notification.method.board', array(
3984              'all_unread'    => true,
3985              'limit'            => 5,
3986          ));
3987   
3988          foreach ($notifications['notifications'] as $notification)
3989          {
3990              $template->assign_block_vars('notifications', $notification->prepare_for_display());
3991          }
3992      }
3993   
3994      /** @var \phpbb\controller\helper $controller_helper */
3995      $controller_helper = $phpbb_container->get('controller.helper');
3996      $notification_mark_hash = generate_link_hash('mark_all_notifications_read');
3997   
3998      $s_login_redirect = build_hidden_fields(array('redirect' => $phpbb_path_helper->remove_web_root_path(build_url())));
3999   
4000      // Add form token for login box, in case page is presenting a login form.
4001      add_form_key('login', '_LOGIN');
4002   
4003      /**
4004       * Workaround for missing template variable in pre phpBB 3.2.6 styles.
4005       * @deprecated 3.2.7 (To be removed: 4.0.0-a1)
4006       */
4007      $form_token_login = $template->retrieve_var('S_FORM_TOKEN_LOGIN');
4008      if (!empty($form_token_login))
4009      {
4010          $s_login_redirect .= $form_token_login;
4011          // Remove S_FORM_TOKEN_LOGIN as it's already appended to S_LOGIN_REDIRECT
4012          $template->assign_var('S_FORM_TOKEN_LOGIN', '');
4013      }
4014   
4015      // The following assigns all _common_ variables that may be used at any point in a template.
4016      $template->assign_vars(array(
4017          'SITENAME'                        => $config['sitename'],
4018          'SITE_DESCRIPTION'                => $config['site_desc'],
4019          'PAGE_TITLE'                    => $page_title,
4020          'SCRIPT_NAME'                    => str_replace('.' . $phpEx, '', $user->page['page_name']),
4021          'LAST_VISIT_DATE'                => sprintf($user->lang['YOU_LAST_VISIT'], $s_last_visit),
4022          'LAST_VISIT_YOU'                => $s_last_visit,
4023          'CURRENT_TIME'                    => sprintf($user->lang['CURRENT_TIME'], $user->format_date(time(), false, true)),
4024          'TOTAL_USERS_ONLINE'            => $l_online_users,
4025          'LOGGED_IN_USER_LIST'            => $online_userlist,
4026          'RECORD_USERS'                    => $l_online_record,
4027   
4028          'PRIVATE_MESSAGE_COUNT'            => (!empty($user->data['user_unread_privmsg'])) ? $user->data['user_unread_privmsg'] : 0,
4029          'CURRENT_USER_AVATAR'            => phpbb_get_user_avatar($user->data),
4030          'CURRENT_USERNAME_SIMPLE'        => get_username_string('no_profile', $user->data['user_id'], $user->data['username'], $user->data['user_colour']),
4031          'CURRENT_USERNAME_FULL'            => get_username_string('full', $user->data['user_id'], $user->data['username'], $user->data['user_colour']),
4032          'UNREAD_NOTIFICATIONS_COUNT'    => ($notifications !== false) ? $notifications['unread_count'] : '',
4033          'NOTIFICATIONS_COUNT'            => ($notifications !== false) ? $notifications['unread_count'] : '',
4034          'U_VIEW_ALL_NOTIFICATIONS'        => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=ucp_notifications'),
4035          '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),
4036          'U_NOTIFICATION_SETTINGS'        => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=ucp_notifications&amp;mode=notification_options'),
4037          'S_NOTIFICATIONS_DISPLAY'        => $config['load_notifications'] && $config['allow_board_notifications'],
4038   
4039          'S_USER_NEW_PRIVMSG'            => $user->data['user_new_privmsg'],
4040          'S_USER_UNREAD_PRIVMSG'            => $user->data['user_unread_privmsg'],
4041          'S_USER_NEW'                    => $user->data['user_new'],
4042   
4043          'SID'                => $SID,
4044          '_SID'                => $_SID,
4045          'SESSION_ID'        => $user->session_id,
4046          'ROOT_PATH'            => $web_path,
4047          'BOARD_URL'            => generate_board_url() . '/',
4048   
4049          'L_LOGIN_LOGOUT'    => $l_login_logout,
4050          'L_INDEX'            => ($config['board_index_text'] !== '') ? $config['board_index_text'] : $user->lang['FORUM_INDEX'],
4051          'L_SITE_HOME'        => ($config['site_home_text'] !== '') ? $config['site_home_text'] : $user->lang['HOME'],
4052          'L_ONLINE_EXPLAIN'    => $l_online_time,
4053   
4054          'U_PRIVATEMSGS'            => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&amp;folder=inbox'),
4055          'U_RETURN_INBOX'        => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&amp;folder=inbox'),
4056          'U_MEMBERLIST'            => append_sid("{$phpbb_root_path}memberlist.$phpEx"),
4057          'U_VIEWONLINE'            => ($auth->acl_gets('u_viewprofile', 'a_user', 'a_useradd', 'a_userdel')) ? append_sid("{$phpbb_root_path}viewonline.$phpEx") : '',
4058          'U_LOGIN_LOGOUT'        => $u_login_logout,
4059          'U_INDEX'                => append_sid("{$phpbb_root_path}index.$phpEx"),
4060          'U_SEARCH'                => append_sid("{$phpbb_root_path}search.$phpEx"),
4061          'U_SITE_HOME'            => $config['site_home_url'],
4062          'U_REGISTER'            => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=register'),
4063          'U_PROFILE'                => append_sid("{$phpbb_root_path}ucp.$phpEx"),
4064          'U_USER_PROFILE'        => get_username_string('profile', $user->data['user_id'], $user->data['username'], $user->data['user_colour']),
4065          'U_MODCP'                => append_sid("{$phpbb_root_path}mcp.$phpEx", false, true, $user->session_id),
4066          'U_FAQ'                    => $controller_helper->route('phpbb_help_faq_controller'),
4067          'U_SEARCH_SELF'            => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=egosearch'),
4068          'U_SEARCH_NEW'            => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=newposts'),
4069          'U_SEARCH_UNANSWERED'    => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=unanswered'),
4070          'U_SEARCH_UNREAD'        => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=unreadposts'),
4071          'U_SEARCH_ACTIVE_TOPICS'=> append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=active_topics'),
4072          'U_DELETE_COOKIES'        => $controller_helper->route('phpbb_ucp_delete_cookies_controller'),
4073          'U_CONTACT_US'            => ($config['contact_admin_form_enable'] && $config['email_enable']) ? append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=contactadmin') : '',
4074          'U_TEAM'                => (!$auth->acl_get('u_viewprofile')) ? '' : append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=team'),
4075          'U_TERMS_USE'            => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=terms'),
4076          'U_PRIVACY'                => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy'),
4077          'UA_PRIVACY'            => addslashes(append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy')),
4078          'U_RESTORE_PERMISSIONS'    => ($user->data['user_perm_from'] && $auth->acl_get('a_switchperm')) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=restore_perm') : '',
4079          'U_FEED'                => $controller_helper->route('phpbb_feed_index'),
4080   
4081          'S_USER_LOGGED_IN'        => ($user->data['user_id'] != ANONYMOUS) ? true : false,
4082          'S_AUTOLOGIN_ENABLED'    => ($config['allow_autologin']) ? true : false,
4083          'S_BOARD_DISABLED'        => ($config['board_disable']) ? true : false,
4084          'S_REGISTERED_USER'        => (!empty($user->data['is_registered'])) ? true : false,
4085          'S_IS_BOT'                => (!empty($user->data['is_bot'])) ? true : false,
4086          'S_USER_LANG'            => $user_lang,
4087          'S_USER_BROWSER'        => (isset($user->data['session_browser'])) ? $user->data['session_browser'] : $user->lang['UNKNOWN_BROWSER'],
4088          'S_USERNAME'            => $user->data['username'],
4089          'S_CONTENT_DIRECTION'    => $user->lang['DIRECTION'],
4090          'S_CONTENT_FLOW_BEGIN'    => ($user->lang['DIRECTION'] == 'ltr') ? 'left' : 'right',
4091          'S_CONTENT_FLOW_END'    => ($user->lang['DIRECTION'] == 'ltr') ? 'right' : 'left',
4092          'S_CONTENT_ENCODING'    => 'UTF-8',
4093          'S_TIMEZONE'            => sprintf($user->lang['ALL_TIMES'], $timezone_offset, $timezone_name),
4094          'S_DISPLAY_ONLINE_LIST'    => ($l_online_time) ? 1 : 0,
4095          'S_DISPLAY_SEARCH'        => (!$config['load_search']) ? 0 : (isset($auth) ? ($auth->acl_get('u_search') && $auth->acl_getf_global('f_search')) : 1),
4096          'S_DISPLAY_PM'            => ($config['allow_privmsg'] && !empty($user->data['is_registered']) && ($auth->acl_get('u_readpm') || $auth->acl_get('u_sendpm'))) ? true : false,
4097          'S_DISPLAY_MEMBERLIST'    => (isset($auth)) ? $auth->acl_get('u_viewprofile') : 0,
4098          'S_NEW_PM'                => ($s_privmsg_new) ? 1 : 0,
4099          'S_REGISTER_ENABLED'    => ($config['require_activation'] != USER_ACTIVATION_DISABLE) ? true : false,
4100          'S_FORUM_ID'            => $forum_id,
4101          'S_TOPIC_ID'            => $topic_id,
4102   
4103          '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)),
4104          'S_LOGIN_REDIRECT'        => $s_login_redirect,
4105   
4106          'S_ENABLE_FEEDS'            => ($config['feed_enable']) ? true : false,
4107          'S_ENABLE_FEEDS_OVERALL'    => ($config['feed_overall']) ? true : false,
4108          'S_ENABLE_FEEDS_FORUMS'        => ($config['feed_overall_forums']) ? true : false,
4109          'S_ENABLE_FEEDS_TOPICS'        => ($config['feed_topics_new']) ? true : false,
4110          'S_ENABLE_FEEDS_TOPICS_ACTIVE'    => ($config['feed_topics_active']) ? true : false,
4111          'S_ENABLE_FEEDS_NEWS'        => ($s_feed_news) ? true : false,
4112   
4113          'S_LOAD_UNREADS'            => (bool) $config['load_unreads_search'] && ($config['load_anon_lastread'] || !empty($user->data['is_registered'])),
4114   
4115          'S_SEARCH_HIDDEN_FIELDS'    => build_hidden_fields($s_search_hidden_fields),
4116   
4117          'T_ASSETS_VERSION'        => $config['assets_version'],
4118          'T_ASSETS_PATH'            => "{$web_path}assets",
4119          'T_THEME_PATH'            => "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/theme',
4120          'T_TEMPLATE_PATH'        => "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/template',
4121          'T_SUPER_TEMPLATE_PATH'    => "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/template',
4122          'T_IMAGES_PATH'            => "{$web_path}images/",
4123          'T_SMILIES_PATH'        => "{$web_path}{$config['smilies_path']}/",
4124          'T_AVATAR_PATH'            => "{$web_path}{$config['avatar_path']}/",
4125          'T_AVATAR_GALLERY_PATH'    => "{$web_path}{$config['avatar_gallery_path']}/",
4126          'T_ICONS_PATH'            => "{$web_path}{$config['icons_path']}/",
4127          'T_RANKS_PATH'            => "{$web_path}{$config['ranks_path']}/",
4128          'T_UPLOAD_PATH'            => "{$web_path}{$config['upload_path']}/",
4129          'T_STYLESHEET_LINK'        => "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/theme/stylesheet.css?assets_version=' . $config['assets_version'],
4130          'T_STYLESHEET_LANG_LINK'=> "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/theme/' . $user->lang_name . '/stylesheet.css?assets_version=' . $config['assets_version'],
4131   
4132          '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'],
4133   
4134          'T_JQUERY_LINK'            => !empty($config['allow_cdn']) && !empty($config['load_jquery_url']) ? $config['load_jquery_url'] : "{$web_path}assets/javascript/jquery-3.7.1.min.js?assets_version=" . $config['assets_version'],
4135          'S_ALLOW_CDN'            => !empty($config['allow_cdn']),
4136          'S_COOKIE_NOTICE'        => !empty($config['cookie_notice']),
4137   
4138          'T_THEME_NAME'            => rawurlencode($user->style['style_path']),
4139          'T_THEME_LANG_NAME'        => $user->lang_name,
4140          'T_TEMPLATE_NAME'        => $user->style['style_path'],
4141          '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']),
4142          'T_IMAGES'                => 'images',
4143          'T_SMILIES'                => $config['smilies_path'],
4144          'T_AVATAR'                => $config['avatar_path'],
4145          'T_AVATAR_GALLERY'        => $config['avatar_gallery_path'],
4146          'T_ICONS'                => $config['icons_path'],
4147          'T_RANKS'                => $config['ranks_path'],
4148          'T_UPLOAD'                => $config['upload_path'],
4149   
4150          'SITE_LOGO_IMG'            => $user->img('site_logo'),
4151      ));
4152   
4153      $http_headers = array();
4154   
4155      if ($send_headers)
4156      {
4157          // An array of http headers that phpBB will set. The following event may override these.
4158          $http_headers += array(
4159              // application/xhtml+xml not used because of IE
4160              'Content-type' => 'text/html; charset=UTF-8',
4161              'Cache-Control' => 'private, no-cache="set-cookie"',
4162              'Expires' => gmdate('D, d M Y H:i:s', time()) . ' GMT',
4163              'Referrer-Policy' => 'strict-origin-when-cross-origin',
4164          );
4165          if (!empty($user->data['is_bot']))
4166          {
4167              // Let reverse proxies know we detected a bot.
4168              $http_headers['X-PHPBB-IS-BOT'] = 'yes';
4169          }
4170      }
4171   
4172      /**
4173      * Execute code and/or overwrite _common_ template variables after they have been assigned.
4174      *
4175      * @event core.page_header_after
4176      * @var    string    page_title            Page title
4177      * @var    bool    display_online_list        Do we display online users list
4178      * @var    string    item                Restrict online users to a certain
4179      *                                    session item, e.g. forum for
4180      *                                    session_forum_id
4181      * @var    int        item_id                Restrict online users to item id
4182      * @var    array        http_headers            HTTP headers that should be set by phpbb
4183      *
4184      * @since 3.1.0-b3
4185      */
4186      $vars = array('page_title', 'display_online_list', 'item_id', 'item', 'http_headers');
4187      extract($phpbb_dispatcher->trigger_event('core.page_header_after', compact($vars)));
4188   
4189      foreach ($http_headers as $hname => $hval)
4190      {
4191          header((string) $hname . ': ' . (string) $hval);
4192      }
4193   
4194      return;
4195  }
4196   
4197  /**
4198  * Check and display the SQL report if requested.
4199  *
4200  * @param \phpbb\request\request_interface        $request    Request object
4201  * @param \phpbb\auth\auth                        $auth        Auth object
4202  * @param \phpbb\db\driver\driver_interface        $db            Database connection
4203   *
4204   * @deprecated 3.3.1 (To be removed: 4.0.0-a1); use controller helper's display_sql_report()
4205  */
4206  function phpbb_check_and_display_sql_report(\phpbb\request\request_interface $request, \phpbb\auth\auth $auth, \phpbb\db\driver\driver_interface $db)
4207  {
4208      global $phpbb_container;
4209   
4210      /** @var \phpbb\controller\helper $controller_helper */
4211      $controller_helper = $phpbb_container->get('controller.helper');
4212   
4213      $controller_helper->display_sql_report();
4214  }
4215   
4216  /**
4217  * Generate the debug output string
4218  *
4219  * @param \phpbb\db\driver\driver_interface    $db            Database connection
4220  * @param \phpbb\config\config                $config        Config object
4221  * @param \phpbb\auth\auth                    $auth        Auth object
4222  * @param \phpbb\user                        $user        User object
4223  * @param \phpbb\event\dispatcher_interface    $phpbb_dispatcher    Event dispatcher
4224  * @return string
4225  */
4226  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)
4227  {
4228      global $phpbb_container;
4229   
4230      $debug_info = array();
4231   
4232      // Output page creation time
4233      if ($phpbb_container->getParameter('debug.load_time'))
4234      {
4235          if (isset($GLOBALS['starttime']))
4236          {
4237              $totaltime = microtime(true) - $GLOBALS['starttime'];
4238              $debug_info[] = sprintf('<span title="SQL time: %.3fs / PHP time: %.3fs">Time: %.3fs</span>', $db->get_sql_time(), ($totaltime - $db->get_sql_time()), $totaltime);
4239          }
4240      }
4241   
4242      if ($phpbb_container->getParameter('debug.memory'))
4243      {
4244          $memory_usage = memory_get_peak_usage();
4245          if ($memory_usage)
4246          {
4247              $memory_usage = get_formatted_filesize($memory_usage);
4248   
4249              $debug_info[] = 'Peak Memory Usage: ' . $memory_usage;
4250          }
4251   
4252          $debug_info[] = 'GZIP: ' . (($config['gzip_compress'] && @extension_loaded('zlib')) ? 'On' : 'Off');
4253   
4254          if ($user->load)
4255          {
4256              $debug_info[] = 'Load: ' . $user->load;
4257          }
4258      }
4259   
4260      if ($phpbb_container->getParameter('debug.sql_explain'))
4261      {
4262          $debug_info[] = sprintf('<span title="Cached: %d">Queries: %d</span>', $db->sql_num_queries(true), $db->sql_num_queries());
4263   
4264          if ($auth->acl_get('a_'))
4265          {
4266              $debug_info[] = '<a href="' . build_url() . '&amp;explain=1">SQL Explain</a>';
4267          }
4268      }
4269   
4270      /**
4271      * Modify debug output information
4272      *
4273      * @event core.phpbb_generate_debug_output
4274      * @var    array    debug_info        Array of strings with debug information
4275      *
4276      * @since 3.1.0-RC3
4277      */
4278      $vars = array('debug_info');
4279      extract($phpbb_dispatcher->trigger_event('core.phpbb_generate_debug_output', compact($vars)));
4280   
4281      return implode(' | ', $debug_info);
4282  }
4283   
4284  /**
4285  * Generate page footer
4286  *
4287  * @param bool $run_cron Whether or not to run the cron
4288  * @param bool $display_template Whether or not to display the template
4289  * @param bool $exit_handler Whether or not to run the exit_handler()
4290  */
4291  function page_footer($run_cron = true, $display_template = true, $exit_handler = true)
4292  {
4293      global $phpbb_dispatcher, $phpbb_container, $template;
4294   
4295      // A listener can set this variable to `true` when it overrides this function
4296      $page_footer_override = false;
4297   
4298      /**
4299      * Execute code and/or overwrite page_footer()
4300      *
4301      * @event core.page_footer
4302      * @var    bool    run_cron            Shall we run cron tasks
4303      * @var    bool    page_footer_override    Shall we return instead of running
4304      *                                        the rest of page_footer()
4305      * @since 3.1.0-a1
4306      */
4307      $vars = array('run_cron', 'page_footer_override');
4308      extract($phpbb_dispatcher->trigger_event('core.page_footer', compact($vars)));
4309   
4310      if ($page_footer_override)
4311      {
4312          return;
4313      }
4314   
4315      /** @var \phpbb\controller\helper $controller_helper */
4316      $controller_helper = $phpbb_container->get('controller.helper');
4317   
4318      $controller_helper->display_footer($run_cron);
4319   
4320      /**
4321      * Execute code and/or modify output before displaying the template.
4322      *
4323      * @event core.page_footer_after
4324      * @var    bool display_template    Whether or not to display the template
4325      * @var    bool exit_handler        Whether or not to run the exit_handler()
4326      *
4327      * @since 3.1.0-RC5
4328      */
4329      $vars = array('display_template', 'exit_handler');
4330      extract($phpbb_dispatcher->trigger_event('core.page_footer_after', compact($vars)));
4331   
4332      if ($display_template)
4333      {
4334          $template->display('body');
4335      }
4336   
4337      garbage_collection();
4338   
4339      if ($exit_handler)
4340      {
4341          exit_handler();
4342      }
4343  }
4344   
4345  /**
4346  * Closing the cache object and the database
4347  * Cool function name, eh? We might want to add operations to it later
4348  */
4349  function garbage_collection()
4350  {
4351      global $cache, $db;
4352      global $phpbb_dispatcher;
4353   
4354      if (!empty($phpbb_dispatcher))
4355      {
4356          /**
4357          * Unload some objects, to free some memory, before we finish our task
4358          *
4359          * @event core.garbage_collection
4360          * @since 3.1.0-a1
4361          */
4362          $phpbb_dispatcher->dispatch('core.garbage_collection');
4363      }
4364   
4365      // Unload cache, must be done before the DB connection if closed
4366      if (!empty($cache))
4367      {
4368          $cache->unload();
4369      }
4370   
4371      // Close our DB connection.
4372      if (!empty($db))
4373      {
4374          $db->sql_close();
4375      }
4376  }
4377   
4378  /**
4379  * Handler for exit calls in phpBB.
4380  * This function supports hooks.
4381  *
4382  * Note: This function is called after the template has been outputted.
4383  */
4384  function exit_handler()
4385  {
4386      global $phpbb_hook;
4387   
4388      if (!empty($phpbb_hook) && $phpbb_hook->call_hook(__FUNCTION__))
4389      {
4390          if ($phpbb_hook->hook_return(__FUNCTION__))
4391          {
4392              return $phpbb_hook->hook_return_result(__FUNCTION__);
4393          }
4394      }
4395   
4396      // As a pre-caution... some setups display a blank page if the flush() is not there.
4397      (ob_get_level() > 0) ? @ob_flush() : @flush();
4398   
4399      exit;
4400  }
4401   
4402  /**
4403  * Handler for init calls in phpBB. This function is called in \phpbb\user::setup();
4404  * This function supports hooks.
4405  */
4406  function phpbb_user_session_handler()
4407  {
4408      global $phpbb_hook;
4409   
4410      if (!empty($phpbb_hook) && $phpbb_hook->call_hook(__FUNCTION__))
4411      {
4412          if ($phpbb_hook->hook_return(__FUNCTION__))
4413          {
4414              return $phpbb_hook->hook_return_result(__FUNCTION__);
4415          }
4416      }
4417   
4418      return;
4419  }
4420   
4421  /**
4422  * Casts a numeric string $input to an appropriate numeric type (i.e. integer or float)
4423  *
4424  * @param string $input        A numeric string.
4425  *
4426  * @return int|float            Integer $input if $input fits integer,
4427  *                            float $input otherwise.
4428  */
4429  function phpbb_to_numeric($input)
4430  {
4431      return ($input > PHP_INT_MAX) ? (float) $input : (int) $input;
4432  }
4433   
4434  /**
4435  * Get the board contact details (e.g. for emails)
4436  *
4437  * @param \phpbb\config\config    $config
4438  * @param string                    $phpEx
4439  * @return string
4440  */
4441  function phpbb_get_board_contact(\phpbb\config\config $config, $phpEx)
4442  {
4443      if ($config['contact_admin_form_enable'])
4444      {
4445          return generate_board_url() . '/memberlist.' . $phpEx . '?mode=contactadmin';
4446      }
4447      else
4448      {
4449          return $config['board_contact'];
4450      }
4451  }
4452   
4453  /**
4454  * Get a clickable board contact details link
4455  *
4456  * @param \phpbb\config\config    $config
4457  * @param string                    $phpbb_root_path
4458  * @param string                    $phpEx
4459  * @return string
4460  */
4461  function phpbb_get_board_contact_link(\phpbb\config\config $config, $phpbb_root_path, $phpEx)
4462  {
4463      if ($config['contact_admin_form_enable'] && $config['email_enable'])
4464      {
4465          return append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=contactadmin');
4466      }
4467      else
4468      {
4469          return 'mailto:' . htmlspecialchars($config['board_contact'], ENT_COMPAT);
4470      }
4471  }
4472