[ Index ]

PHP Cross Reference of phpBB-3.2.0-deutsch

title

Body

[close]

/includes/ -> functions.php (source)

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