[ Index ]

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


Generated: Mon Nov 25 19:05:08 2024 Cross-referenced by PHPXref 0.7.1