[ Index ]

PHP Cross Reference of phpBB-3.2.8-deutsch

title

Body

[close]

/includes/ -> functions.php (source)

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