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