[ Index ]

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