Verzeichnisstruktur phpBB-3.2.0


Veröffentlicht
06.01.2017

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