[ Index ]

PHP Cross Reference of phpBB-3.2.11-deutsch

title

Body

[close]

/includes/ -> functions.php (source)

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


Generated: Wed Nov 11 20:33:01 2020 Cross-referenced by PHPXref 0.7.1