Verzeichnisstruktur phpBB-3.3.15
- Veröffentlicht
- 28.08.2024
So funktioniert es
|
|
Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück |
Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis. |
|
|
(Beispiel Datei-Icons)
|
Auf das Icon klicken um den Quellcode anzuzeigen |
functions.php
0001 <?php
0002 /**
0003 *
0004 * This file is part of the phpBB Forum Software package.
0005 *
0006 * @copyright (c) phpBB Limited <https://www.phpbb.com>
0007 * @license GNU General Public License, version 2 (GPL-2.0)
0008 *
0009 * For full copyright and license information, please see
0010 * the docs/CREDITS.txt file.
0011 *
0012 */
0013
0014 /**
0015 * @ignore
0016 */
0017 if (!defined('IN_PHPBB'))
0018 {
0019 exit;
0020 }
0021
0022 // Common global functions
0023 /**
0024 * Generates an alphanumeric random string of given length
0025 *
0026 * @param int $num_chars Length of random string, defaults to 8.
0027 * This number should be less or equal than 64.
0028 *
0029 * @return string
0030 */
0031 function gen_rand_string($num_chars = 8)
0032 {
0033 $range = array_merge(range('A', 'Z'), range(0, 9));
0034 $size = count($range);
0035
0036 $output = '';
0037 for ($i = 0; $i < $num_chars; $i++)
0038 {
0039 $rand = random_int(0, $size-1);
0040 $output .= $range[$rand];
0041 }
0042
0043 return $output;
0044 }
0045
0046 /**
0047 * Generates a user-friendly alphanumeric random string of given length
0048 * We remove 0 and O so users cannot confuse those in passwords etc.
0049 *
0050 * @param int $num_chars Length of random string, defaults to 8.
0051 * This number should be less or equal than 64.
0052 *
0053 * @return string
0054 */
0055 function gen_rand_string_friendly($num_chars = 8)
0056 {
0057 $range = array_merge(range('A', 'N'), range('P', 'Z'), range(1, 9));
0058 $size = count($range);
0059
0060 $output = '';
0061 for ($i = 0; $i < $num_chars; $i++)
0062 {
0063 $rand = random_int(0, $size-1);
0064 $output .= $range[$rand];
0065 }
0066
0067 return $output;
0068 }
0069
0070 /**
0071 * Return unique id
0072 */
0073 function unique_id()
0074 {
0075 return strtolower(gen_rand_string(16));
0076 }
0077
0078 /**
0079 * Wrapper for mt_rand() which allows swapping $min and $max parameters.
0080 *
0081 * PHP does not allow us to swap the order of the arguments for mt_rand() anymore.
0082 * (since PHP 5.3.4, see http://bugs.php.net/46587)
0083 *
0084 * @param int $min Lowest value to be returned
0085 * @param int $max Highest value to be returned
0086 *
0087 * @return int Random integer between $min and $max (or $max and $min)
0088 */
0089 function phpbb_mt_rand($min, $max)
0090 {
0091 return ($min > $max) ? mt_rand($max, $min) : mt_rand($min, $max);
0092 }
0093
0094 /**
0095 * Wrapper for getdate() which returns the equivalent array for UTC timestamps.
0096 *
0097 * @param int $time Unix timestamp (optional)
0098 *
0099 * @return array Returns an associative array of information related to the timestamp.
0100 * See http://www.php.net/manual/en/function.getdate.php
0101 */
0102 function phpbb_gmgetdate($time = false)
0103 {
0104 if ($time === false)
0105 {
0106 $time = time();
0107 }
0108
0109 // getdate() interprets timestamps in local time.
0110 // So use UTC timezone temporarily to get UTC date info array.
0111 $current_timezone = date_default_timezone_get();
0112
0113 // Set UTC timezone and get respective date info
0114 date_default_timezone_set('UTC');
0115 $date_info = getdate($time);
0116
0117 // Restore timezone back
0118 date_default_timezone_set($current_timezone);
0119
0120 return $date_info;
0121 }
0122
0123 /**
0124 * Return formatted string for filesizes
0125 *
0126 * @param mixed $value filesize in bytes
0127 * (non-negative number; int, float or string)
0128 * @param bool $string_only true if language string should be returned
0129 * @param array $allowed_units only allow these units (data array indexes)
0130 *
0131 * @return mixed data array if $string_only is false
0132 */
0133 function get_formatted_filesize($value, $string_only = true, $allowed_units = false)
0134 {
0135 global $user;
0136
0137 $available_units = array(
0138 'tb' => array(
0139 'min' => 1099511627776, // pow(2, 40)
0140 'index' => 4,
0141 'si_unit' => 'TB',
0142 'iec_unit' => 'TIB',
0143 ),
0144 'gb' => array(
0145 'min' => 1073741824, // pow(2, 30)
0146 'index' => 3,
0147 'si_unit' => 'GB',
0148 'iec_unit' => 'GIB',
0149 ),
0150 'mb' => array(
0151 'min' => 1048576, // pow(2, 20)
0152 'index' => 2,
0153 'si_unit' => 'MB',
0154 'iec_unit' => 'MIB',
0155 ),
0156 'kb' => array(
0157 'min' => 1024, // pow(2, 10)
0158 'index' => 1,
0159 'si_unit' => 'KB',
0160 'iec_unit' => 'KIB',
0161 ),
0162 'b' => array(
0163 'min' => 0,
0164 'index' => 0,
0165 'si_unit' => 'BYTES', // Language index
0166 'iec_unit' => 'BYTES', // Language index
0167 ),
0168 );
0169
0170 foreach ($available_units as $si_identifier => $unit_info)
0171 {
0172 if (!empty($allowed_units) && $si_identifier != 'b' && !in_array($si_identifier, $allowed_units))
0173 {
0174 continue;
0175 }
0176
0177 if ($value >= $unit_info['min'])
0178 {
0179 $unit_info['si_identifier'] = $si_identifier;
0180
0181 break;
0182 }
0183 }
0184 unset($available_units);
0185
0186 for ($i = 0; $i < $unit_info['index']; $i++)
0187 {
0188 $value /= 1024;
0189 }
0190 $value = round($value, 2);
0191
0192 // Lookup units in language dictionary
0193 $unit_info['si_unit'] = (isset($user->lang[$unit_info['si_unit']])) ? $user->lang[$unit_info['si_unit']] : $unit_info['si_unit'];
0194 $unit_info['iec_unit'] = (isset($user->lang[$unit_info['iec_unit']])) ? $user->lang[$unit_info['iec_unit']] : $unit_info['iec_unit'];
0195
0196 // Default to IEC
0197 $unit_info['unit'] = $unit_info['iec_unit'];
0198
0199 if (!$string_only)
0200 {
0201 $unit_info['value'] = $value;
0202
0203 return $unit_info;
0204 }
0205
0206 return $value . ' ' . $unit_info['unit'];
0207 }
0208
0209 /**
0210 * Determine whether we are approaching the maximum execution time. Should be called once
0211 * at the beginning of the script in which it's used.
0212 * @return bool Either true if the maximum execution time is nearly reached, or false
0213 * if some time is still left.
0214 */
0215 function still_on_time($extra_time = 15)
0216 {
0217 static $max_execution_time, $start_time;
0218
0219 $current_time = microtime(true);
0220
0221 if (empty($max_execution_time))
0222 {
0223 $max_execution_time = (function_exists('ini_get')) ? (int) @ini_get('max_execution_time') : (int) @get_cfg_var('max_execution_time');
0224
0225 // If zero, then set to something higher to not let the user catch the ten seconds barrier.
0226 if ($max_execution_time === 0)
0227 {
0228 $max_execution_time = 50 + $extra_time;
0229 }
0230
0231 $max_execution_time = min(max(10, ($max_execution_time - $extra_time)), 50);
0232
0233 // For debugging purposes
0234 // $max_execution_time = 10;
0235
0236 global $starttime;
0237 $start_time = (empty($starttime)) ? $current_time : $starttime;
0238 }
0239
0240 return (ceil($current_time - $start_time) < $max_execution_time) ? true : false;
0241 }
0242
0243 /**
0244 * Wrapper for version_compare() that allows using uppercase A and B
0245 * for alpha and beta releases.
0246 *
0247 * See http://www.php.net/manual/en/function.version-compare.php
0248 *
0249 * @param string $version1 First version number
0250 * @param string $version2 Second version number
0251 * @param string $operator Comparison operator (optional)
0252 *
0253 * @return mixed Boolean (true, false) if comparison operator is specified.
0254 * Integer (-1, 0, 1) otherwise.
0255 */
0256 function phpbb_version_compare($version1, $version2, $operator = null)
0257 {
0258 $version1 = strtolower($version1);
0259 $version2 = strtolower($version2);
0260
0261 if (is_null($operator))
0262 {
0263 return version_compare($version1, $version2);
0264 }
0265 else
0266 {
0267 return version_compare($version1, $version2, $operator);
0268 }
0269 }
0270
0271 // functions used for building option fields
0272
0273 /**
0274 * Pick a language, any language ...
0275 *
0276 * @param string $default Language ISO code to be selected by default in the dropdown list
0277 * @param array $langdata Language data in format of array(array('lang_iso' => string, lang_local_name => string), ...)
0278 *
0279 * @return string HTML options for language selection dropdown list.
0280 */
0281 function language_select($default = '', array $langdata = [])
0282 {
0283 global $db;
0284
0285 if (empty($langdata))
0286 {
0287 $sql = 'SELECT lang_iso, lang_local_name
0288 FROM ' . LANG_TABLE . '
0289 ORDER BY lang_english_name';
0290 $result = $db->sql_query($sql);
0291 $langdata = (array) $db->sql_fetchrowset($result);
0292 $db->sql_freeresult($result);
0293 }
0294
0295 $lang_options = '';
0296 foreach ($langdata as $row)
0297 {
0298 $selected = ($row['lang_iso'] == $default) ? ' selected="selected"' : '';
0299 $lang_options .= '<option value="' . $row['lang_iso'] . '"' . $selected . '>' . $row['lang_local_name'] . '</option>';
0300 }
0301
0302 return $lang_options;
0303 }
0304
0305 /**
0306 * Pick a template/theme combo
0307 *
0308 * @param string $default Style ID to be selected by default in the dropdown list
0309 * @param bool $all Flag indicating if all styles data including inactive ones should be fetched
0310 * @param array $styledata Style data in format of array(array('style_id' => int, style_name => string), ...)
0311 *
0312 * @return string HTML options for style selection dropdown list.
0313 */
0314 function style_select($default = '', $all = false, array $styledata = [])
0315 {
0316 global $db;
0317
0318 if (empty($styledata))
0319 {
0320 $sql_where = (!$all) ? 'WHERE style_active = 1 ' : '';
0321 $sql = 'SELECT style_id, style_name
0322 FROM ' . STYLES_TABLE . "
0323 $sql_where
0324 ORDER BY style_name";
0325 $result = $db->sql_query($sql);
0326 $styledata = (array) $db->sql_fetchrowset($result);
0327 $db->sql_freeresult($result);
0328 }
0329
0330 $style_options = '';
0331 foreach ($styledata as $row)
0332 {
0333 $selected = ($row['style_id'] == $default) ? ' selected="selected"' : '';
0334 $style_options .= '<option value="' . $row['style_id'] . '"' . $selected . '>' . $row['style_name'] . '</option>';
0335 }
0336
0337 return $style_options;
0338 }
0339
0340 /**
0341 * Format the timezone offset with hours and minutes
0342 *
0343 * @param int $tz_offset Timezone offset in seconds
0344 * @param bool $show_null Whether null offsets should be shown
0345 * @return string Normalized offset string: -7200 => -02:00
0346 * 16200 => +04:30
0347 */
0348 function phpbb_format_timezone_offset($tz_offset, $show_null = false)
0349 {
0350 $sign = ($tz_offset < 0) ? '-' : '+';
0351 $time_offset = abs($tz_offset);
0352
0353 if ($time_offset == 0 && $show_null == false)
0354 {
0355 return '';
0356 }
0357
0358 $offset_seconds = $time_offset % 3600;
0359 $offset_minutes = $offset_seconds / 60;
0360 $offset_hours = ($time_offset - $offset_seconds) / 3600;
0361
0362 $offset_string = sprintf("%s%02d:%02d", $sign, $offset_hours, $offset_minutes);
0363 return $offset_string;
0364 }
0365
0366 /**
0367 * Compares two time zone labels.
0368 * Arranges them in increasing order by timezone offset.
0369 * Places UTC before other timezones in the same offset.
0370 */
0371 function phpbb_tz_select_compare($a, $b)
0372 {
0373 $a_sign = $a[3];
0374 $b_sign = $b[3];
0375 if ($a_sign != $b_sign)
0376 {
0377 return $a_sign == '-' ? -1 : 1;
0378 }
0379
0380 $a_offset = substr($a, 4, 5);
0381 $b_offset = substr($b, 4, 5);
0382 if ($a_offset == $b_offset)
0383 {
0384 $a_name = substr($a, 12);
0385 $b_name = substr($b, 12);
0386 if ($a_name == $b_name)
0387 {
0388 return 0;
0389 }
0390 else if ($a_name == 'UTC')
0391 {
0392 return -1;
0393 }
0394 else if ($b_name == 'UTC')
0395 {
0396 return 1;
0397 }
0398 else
0399 {
0400 return $a_name < $b_name ? -1 : 1;
0401 }
0402 }
0403 else
0404 {
0405 if ($a_sign == '-')
0406 {
0407 return $a_offset > $b_offset ? -1 : 1;
0408 }
0409 else
0410 {
0411 return $a_offset < $b_offset ? -1 : 1;
0412 }
0413 }
0414 }
0415
0416 /**
0417 * Return list of timezone identifiers
0418 * We also add the selected timezone if we can create an object with it.
0419 * DateTimeZone::listIdentifiers seems to not add all identifiers to the list,
0420 * because some are only kept for backward compatible reasons. If the user has
0421 * a deprecated value, we add it here, so it can still be kept. Once the user
0422 * changed his value, there is no way back to deprecated values.
0423 *
0424 * @param string $selected_timezone Additional timezone that shall
0425 * be added to the list of identiers
0426 * @return array DateTimeZone::listIdentifiers and additional
0427 * selected_timezone if it is a valid timezone.
0428 */
0429 function phpbb_get_timezone_identifiers($selected_timezone)
0430 {
0431 $timezones = DateTimeZone::listIdentifiers();
0432
0433 if (!in_array($selected_timezone, $timezones))
0434 {
0435 try
0436 {
0437 // Add valid timezones that are currently selected but not returned
0438 // by DateTimeZone::listIdentifiers
0439 $validate_timezone = new DateTimeZone($selected_timezone);
0440 $timezones[] = $selected_timezone;
0441 }
0442 catch (\Exception $e)
0443 {
0444 }
0445 }
0446
0447 return $timezones;
0448 }
0449
0450 /**
0451 * Options to pick a timezone and date/time
0452 *
0453 * @param \phpbb\template\template $template phpBB template object
0454 * @param \phpbb\user $user Object of the current user
0455 * @param string $default A timezone to select
0456 * @param boolean $truncate Shall we truncate the options text
0457 *
0458 * @return array Returns an array containing the options for the time selector.
0459 */
0460 function phpbb_timezone_select($template, $user, $default = '', $truncate = false)
0461 {
0462 static $timezones;
0463
0464 $default_offset = '';
0465 if (!isset($timezones))
0466 {
0467 $unsorted_timezones = phpbb_get_timezone_identifiers($default);
0468
0469 $timezones = array();
0470 foreach ($unsorted_timezones as $timezone)
0471 {
0472 $tz = new DateTimeZone($timezone);
0473 $dt = $user->create_datetime('now', $tz);
0474 $offset = $dt->getOffset();
0475 $current_time = $dt->format($user->lang['DATETIME_FORMAT'], true);
0476 $offset_string = phpbb_format_timezone_offset($offset, true);
0477 $timezones['UTC' . $offset_string . ' - ' . $timezone] = array(
0478 'tz' => $timezone,
0479 'offset' => $offset_string,
0480 'current' => $current_time,
0481 );
0482 if ($timezone === $default)
0483 {
0484 $default_offset = 'UTC' . $offset_string;
0485 }
0486 }
0487 unset($unsorted_timezones);
0488
0489 uksort($timezones, 'phpbb_tz_select_compare');
0490 }
0491
0492 $tz_select = $opt_group = '';
0493
0494 foreach ($timezones as $key => $timezone)
0495 {
0496 if ($opt_group != $timezone['offset'])
0497 {
0498 // Generate tz_select for backwards compatibility
0499 $tz_select .= ($opt_group) ? '</optgroup>' : '';
0500 $tz_select .= '<optgroup label="' . $user->lang(array('timezones', 'UTC_OFFSET_CURRENT'), $timezone['offset'], $timezone['current']) . '">';
0501 $opt_group = $timezone['offset'];
0502 $template->assign_block_vars('timezone_select', array(
0503 'LABEL' => $user->lang(array('timezones', 'UTC_OFFSET_CURRENT'), $timezone['offset'], $timezone['current']),
0504 'VALUE' => $key . ' - ' . $timezone['current'],
0505 ));
0506
0507 $selected = (!empty($default_offset) && strpos($key, $default_offset) !== false) ? ' selected="selected"' : '';
0508 $template->assign_block_vars('timezone_date', array(
0509 'VALUE' => $key . ' - ' . $timezone['current'],
0510 'SELECTED' => !empty($selected),
0511 'TITLE' => $user->lang(array('timezones', 'UTC_OFFSET_CURRENT'), $timezone['offset'], $timezone['current']),
0512 ));
0513 }
0514
0515 $label = $timezone['tz'];
0516 if (isset($user->lang['timezones'][$label]))
0517 {
0518 $label = $user->lang['timezones'][$label];
0519 }
0520 $title = $user->lang(array('timezones', 'UTC_OFFSET_CURRENT'), $timezone['offset'], $label);
0521
0522 if ($truncate)
0523 {
0524 $label = truncate_string($label, 50, 255, false, '...');
0525 }
0526
0527 // Also generate timezone_select for backwards compatibility
0528 $selected = ($timezone['tz'] === $default) ? ' selected="selected"' : '';
0529 $tz_select .= '<option title="' . $title . '" value="' . $timezone['tz'] . '"' . $selected . '>' . $label . '</option>';
0530 $template->assign_block_vars('timezone_select.timezone_options', array(
0531 'TITLE' => $title,
0532 'VALUE' => $timezone['tz'],
0533 'SELECTED' => !empty($selected),
0534 'LABEL' => $label,
0535 ));
0536 }
0537 $tz_select .= '</optgroup>';
0538
0539 return $tz_select;
0540 }
0541
0542 // Functions handling topic/post tracking/marking
0543
0544 /**
0545 * Marks a topic/forum as read
0546 * Marks a topic as posted to
0547 *
0548 * @param string $mode (all, topics, topic, post)
0549 * @param int|bool $forum_id Used in all, topics, and topic mode
0550 * @param int|bool $topic_id Used in topic and post mode
0551 * @param int $post_time 0 means current time(), otherwise to set a specific mark time
0552 * @param int $user_id can only be used with $mode == 'post'
0553 */
0554 function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $user_id = 0)
0555 {
0556 global $db, $user, $config;
0557 global $request, $phpbb_container, $phpbb_dispatcher;
0558
0559 $post_time = ($post_time === 0 || $post_time > time()) ? time() : (int) $post_time;
0560
0561 $should_markread = true;
0562
0563 /**
0564 * This event is used for performing actions directly before marking forums,
0565 * topics or posts as read.
0566 *
0567 * It is also possible to prevent the marking. For that, the $should_markread parameter
0568 * should be set to FALSE.
0569 *
0570 * @event core.markread_before
0571 * @var string mode Variable containing marking mode value
0572 * @var mixed forum_id Variable containing forum id, or false
0573 * @var mixed topic_id Variable containing topic id, or false
0574 * @var int post_time Variable containing post time
0575 * @var int user_id Variable containing the user id
0576 * @var bool should_markread Flag indicating if the markread should be done or not.
0577 * @since 3.1.4-RC1
0578 */
0579 $vars = array(
0580 'mode',
0581 'forum_id',
0582 'topic_id',
0583 'post_time',
0584 'user_id',
0585 'should_markread',
0586 );
0587 extract($phpbb_dispatcher->trigger_event('core.markread_before', compact($vars)));
0588
0589 if (!$should_markread)
0590 {
0591 return;
0592 }
0593
0594 if ($mode == 'all')
0595 {
0596 if (empty($forum_id))
0597 {
0598 // Mark all forums read (index page)
0599 /* @var $phpbb_notifications \phpbb\notification\manager */
0600 $phpbb_notifications = $phpbb_container->get('notification_manager');
0601
0602 // Mark all topic notifications read for this user
0603 $phpbb_notifications->mark_notifications(array(
0604 'notification.type.topic',
0605 'notification.type.quote',
0606 'notification.type.bookmark',
0607 'notification.type.post',
0608 'notification.type.approve_topic',
0609 'notification.type.approve_post',
0610 'notification.type.forum',
0611 ), false, $user->data['user_id'], $post_time);
0612
0613 if ($config['load_db_lastread'] && $user->data['is_registered'])
0614 {
0615 // Mark all forums read (index page)
0616 $tables = array(TOPICS_TRACK_TABLE, FORUMS_TRACK_TABLE);
0617 foreach ($tables as $table)
0618 {
0619 $sql = 'DELETE FROM ' . $table . "
0620 WHERE user_id = {$user->data['user_id']}
0621 AND mark_time < $post_time";
0622 $db->sql_query($sql);
0623 }
0624
0625 $sql = 'UPDATE ' . USERS_TABLE . "
0626 SET user_lastmark = $post_time
0627 WHERE user_id = {$user->data['user_id']}
0628 AND user_lastmark < $post_time";
0629 $db->sql_query($sql);
0630 }
0631 else if ($config['load_anon_lastread'] || $user->data['is_registered'])
0632 {
0633 $tracking_topics = $request->variable($config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE);
0634 $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
0635
0636 unset($tracking_topics['tf']);
0637 unset($tracking_topics['t']);
0638 unset($tracking_topics['f']);
0639 $tracking_topics['l'] = base_convert($post_time - $config['board_startdate'], 10, 36);
0640
0641 $user->set_cookie('track', tracking_serialize($tracking_topics), $post_time + 31536000);
0642 $request->overwrite($config['cookie_name'] . '_track', tracking_serialize($tracking_topics), \phpbb\request\request_interface::COOKIE);
0643
0644 unset($tracking_topics);
0645
0646 if ($user->data['is_registered'])
0647 {
0648 $sql = 'UPDATE ' . USERS_TABLE . "
0649 SET user_lastmark = $post_time
0650 WHERE user_id = {$user->data['user_id']}
0651 AND user_lastmark < $post_time";
0652 $db->sql_query($sql);
0653 }
0654 }
0655 }
0656 }
0657 else if ($mode == 'topics')
0658 {
0659 // Mark all topics in forums read
0660 if (!is_array($forum_id))
0661 {
0662 $forum_id = array($forum_id);
0663 }
0664 else
0665 {
0666 $forum_id = array_unique($forum_id);
0667 }
0668
0669 /* @var $phpbb_notifications \phpbb\notification\manager */
0670 $phpbb_notifications = $phpbb_container->get('notification_manager');
0671
0672 $phpbb_notifications->mark_notifications_by_parent(array(
0673 'notification.type.topic',
0674 'notification.type.approve_topic',
0675 ), $forum_id, $user->data['user_id'], $post_time);
0676
0677 // Mark all post/quote notifications read for this user in this forum
0678 $topic_ids = array();
0679 $sql = 'SELECT topic_id
0680 FROM ' . TOPICS_TABLE . '
0681 WHERE ' . $db->sql_in_set('forum_id', $forum_id);
0682 $result = $db->sql_query($sql);
0683 while ($row = $db->sql_fetchrow($result))
0684 {
0685 $topic_ids[] = $row['topic_id'];
0686 }
0687 $db->sql_freeresult($result);
0688
0689 $phpbb_notifications->mark_notifications_by_parent(array(
0690 'notification.type.quote',
0691 'notification.type.bookmark',
0692 'notification.type.post',
0693 'notification.type.approve_post',
0694 'notification.type.forum',
0695 ), $topic_ids, $user->data['user_id'], $post_time);
0696
0697 // Add 0 to forums array to mark global announcements correctly
0698 // $forum_id[] = 0;
0699
0700 if ($config['load_db_lastread'] && $user->data['is_registered'])
0701 {
0702 $sql = 'DELETE FROM ' . TOPICS_TRACK_TABLE . "
0703 WHERE user_id = {$user->data['user_id']}
0704 AND mark_time < $post_time
0705 AND " . $db->sql_in_set('forum_id', $forum_id);
0706 $db->sql_query($sql);
0707
0708 $sql = 'SELECT forum_id
0709 FROM ' . FORUMS_TRACK_TABLE . "
0710 WHERE user_id = {$user->data['user_id']}
0711 AND " . $db->sql_in_set('forum_id', $forum_id);
0712 $result = $db->sql_query($sql);
0713
0714 $sql_update = array();
0715 while ($row = $db->sql_fetchrow($result))
0716 {
0717 $sql_update[] = (int) $row['forum_id'];
0718 }
0719 $db->sql_freeresult($result);
0720
0721 if (count($sql_update))
0722 {
0723 $sql = 'UPDATE ' . FORUMS_TRACK_TABLE . "
0724 SET mark_time = $post_time
0725 WHERE user_id = {$user->data['user_id']}
0726 AND mark_time < $post_time
0727 AND " . $db->sql_in_set('forum_id', $sql_update);
0728 $db->sql_query($sql);
0729 }
0730
0731 if ($sql_insert = array_diff($forum_id, $sql_update))
0732 {
0733 $sql_ary = array();
0734 foreach ($sql_insert as $f_id)
0735 {
0736 $sql_ary[] = array(
0737 'user_id' => (int) $user->data['user_id'],
0738 'forum_id' => (int) $f_id,
0739 'mark_time' => $post_time,
0740 );
0741 }
0742
0743 $db->sql_multi_insert(FORUMS_TRACK_TABLE, $sql_ary);
0744 }
0745 }
0746 else if ($config['load_anon_lastread'] || $user->data['is_registered'])
0747 {
0748 $tracking = $request->variable($config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE);
0749 $tracking = ($tracking) ? tracking_unserialize($tracking) : array();
0750
0751 foreach ($forum_id as $f_id)
0752 {
0753 $topic_ids36 = (isset($tracking['tf'][$f_id])) ? $tracking['tf'][$f_id] : array();
0754
0755 if (isset($tracking['tf'][$f_id]))
0756 {
0757 unset($tracking['tf'][$f_id]);
0758 }
0759
0760 foreach ($topic_ids36 as $topic_id36)
0761 {
0762 unset($tracking['t'][$topic_id36]);
0763 }
0764
0765 if (isset($tracking['f'][$f_id]))
0766 {
0767 unset($tracking['f'][$f_id]);
0768 }
0769
0770 $tracking['f'][$f_id] = base_convert($post_time - $config['board_startdate'], 10, 36);
0771 }
0772
0773 if (isset($tracking['tf']) && empty($tracking['tf']))
0774 {
0775 unset($tracking['tf']);
0776 }
0777
0778 $user->set_cookie('track', tracking_serialize($tracking), $post_time + 31536000);
0779 $request->overwrite($config['cookie_name'] . '_track', tracking_serialize($tracking), \phpbb\request\request_interface::COOKIE);
0780
0781 unset($tracking);
0782 }
0783 }
0784 else if ($mode == 'topic')
0785 {
0786 if ($topic_id === false || $forum_id === false)
0787 {
0788 return;
0789 }
0790
0791 /* @var $phpbb_notifications \phpbb\notification\manager */
0792 $phpbb_notifications = $phpbb_container->get('notification_manager');
0793
0794 // Mark post notifications read for this user in this topic
0795 $phpbb_notifications->mark_notifications(array(
0796 'notification.type.topic',
0797 'notification.type.approve_topic',
0798 ), $topic_id, $user->data['user_id'], $post_time);
0799
0800 $phpbb_notifications->mark_notifications_by_parent(array(
0801 'notification.type.quote',
0802 'notification.type.bookmark',
0803 'notification.type.post',
0804 'notification.type.approve_post',
0805 'notification.type.forum',
0806 ), $topic_id, $user->data['user_id'], $post_time);
0807
0808 if ($config['load_db_lastread'] && $user->data['is_registered'])
0809 {
0810 $sql = 'UPDATE ' . TOPICS_TRACK_TABLE . "
0811 SET mark_time = $post_time
0812 WHERE user_id = {$user->data['user_id']}
0813 AND mark_time < $post_time
0814 AND topic_id = $topic_id";
0815 $db->sql_query($sql);
0816
0817 // insert row
0818 if (!$db->sql_affectedrows())
0819 {
0820 $db->sql_return_on_error(true);
0821
0822 $sql_ary = array(
0823 'user_id' => (int) $user->data['user_id'],
0824 'topic_id' => (int) $topic_id,
0825 'forum_id' => (int) $forum_id,
0826 'mark_time' => $post_time,
0827 );
0828
0829 $db->sql_query('INSERT INTO ' . TOPICS_TRACK_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
0830
0831 $db->sql_return_on_error(false);
0832 }
0833 }
0834 else if ($config['load_anon_lastread'] || $user->data['is_registered'])
0835 {
0836 $tracking = $request->variable($config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE);
0837 $tracking = ($tracking) ? tracking_unserialize($tracking) : array();
0838
0839 $topic_id36 = base_convert($topic_id, 10, 36);
0840
0841 if (!isset($tracking['t'][$topic_id36]))
0842 {
0843 $tracking['tf'][$forum_id][$topic_id36] = true;
0844 }
0845
0846 $tracking['t'][$topic_id36] = base_convert($post_time - (int) $config['board_startdate'], 10, 36);
0847
0848 // If the cookie grows larger than 10000 characters we will remove the smallest value
0849 // This can result in old topics being unread - but most of the time it should be accurate...
0850 if (strlen($request->variable($config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE)) > 10000)
0851 {
0852 //echo 'Cookie grown too large' . print_r($tracking, true);
0853
0854 // We get the ten most minimum stored time offsets and its associated topic ids
0855 $time_keys = array();
0856 for ($i = 0; $i < 10 && count($tracking['t']); $i++)
0857 {
0858 $min_value = min($tracking['t']);
0859 $m_tkey = array_search($min_value, $tracking['t']);
0860 unset($tracking['t'][$m_tkey]);
0861
0862 $time_keys[$m_tkey] = $min_value;
0863 }
0864
0865 // Now remove the topic ids from the array...
0866 foreach ($tracking['tf'] as $f_id => $topic_id_ary)
0867 {
0868 foreach ($time_keys as $m_tkey => $min_value)
0869 {
0870 if (isset($topic_id_ary[$m_tkey]))
0871 {
0872 $tracking['f'][$f_id] = $min_value;
0873 unset($tracking['tf'][$f_id][$m_tkey]);
0874 }
0875 }
0876 }
0877
0878 if ($user->data['is_registered'])
0879 {
0880 $user->data['user_lastmark'] = intval(base_convert(max($time_keys) + $config['board_startdate'], 36, 10));
0881
0882 $sql = 'UPDATE ' . USERS_TABLE . "
0883 SET user_lastmark = $post_time
0884 WHERE user_id = {$user->data['user_id']}
0885 AND mark_time < $post_time";
0886 $db->sql_query($sql);
0887 }
0888 else
0889 {
0890 $tracking['l'] = max($time_keys);
0891 }
0892 }
0893
0894 $user->set_cookie('track', tracking_serialize($tracking), $post_time + 31536000);
0895 $request->overwrite($config['cookie_name'] . '_track', tracking_serialize($tracking), \phpbb\request\request_interface::COOKIE);
0896 }
0897 }
0898 else if ($mode == 'post')
0899 {
0900 if ($topic_id === false)
0901 {
0902 return;
0903 }
0904
0905 $use_user_id = (!$user_id) ? $user->data['user_id'] : $user_id;
0906
0907 if ($config['load_db_track'] && $use_user_id != ANONYMOUS)
0908 {
0909 $db->sql_return_on_error(true);
0910
0911 $sql_ary = array(
0912 'user_id' => (int) $use_user_id,
0913 'topic_id' => (int) $topic_id,
0914 'topic_posted' => 1,
0915 );
0916
0917 $db->sql_query('INSERT INTO ' . TOPICS_POSTED_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
0918
0919 $db->sql_return_on_error(false);
0920 }
0921 }
0922
0923 /**
0924 * This event is used for performing actions directly after forums,
0925 * topics or posts have been marked as read.
0926 *
0927 * @event core.markread_after
0928 * @var string mode Variable containing marking mode value
0929 * @var mixed forum_id Variable containing forum id, or false
0930 * @var mixed topic_id Variable containing topic id, or false
0931 * @var int post_time Variable containing post time
0932 * @var int user_id Variable containing the user id
0933 * @since 3.2.6-RC1
0934 */
0935 $vars = array(
0936 'mode',
0937 'forum_id',
0938 'topic_id',
0939 'post_time',
0940 'user_id',
0941 );
0942 extract($phpbb_dispatcher->trigger_event('core.markread_after', compact($vars)));
0943 }
0944
0945 /**
0946 * Get topic tracking info by using already fetched info
0947 */
0948 function get_topic_tracking($forum_id, $topic_ids, &$rowset, $forum_mark_time, $global_announce_list = false)
0949 {
0950 global $user;
0951
0952 $last_read = array();
0953
0954 if (!is_array($topic_ids))
0955 {
0956 $topic_ids = array($topic_ids);
0957 }
0958
0959 foreach ($topic_ids as $topic_id)
0960 {
0961 if (!empty($rowset[$topic_id]['mark_time']))
0962 {
0963 $last_read[$topic_id] = $rowset[$topic_id]['mark_time'];
0964 }
0965 }
0966
0967 $topic_ids = array_diff($topic_ids, array_keys($last_read));
0968
0969 if (count($topic_ids))
0970 {
0971 $mark_time = array();
0972
0973 if (!empty($forum_mark_time[$forum_id]) && $forum_mark_time[$forum_id] !== false)
0974 {
0975 $mark_time[$forum_id] = $forum_mark_time[$forum_id];
0976 }
0977
0978 $user_lastmark = (isset($mark_time[$forum_id])) ? $mark_time[$forum_id] : $user->data['user_lastmark'];
0979
0980 foreach ($topic_ids as $topic_id)
0981 {
0982 $last_read[$topic_id] = $user_lastmark;
0983 }
0984 }
0985
0986 return $last_read;
0987 }
0988
0989 /**
0990 * Get topic tracking info from db (for cookie based tracking only this function is used)
0991 */
0992 function get_complete_topic_tracking($forum_id, $topic_ids, $global_announce_list = false)
0993 {
0994 global $config, $user, $request;
0995
0996 $last_read = array();
0997
0998 if (!is_array($topic_ids))
0999 {
1000 $topic_ids = array($topic_ids);
1001 }
1002
1003 if ($config['load_db_lastread'] && $user->data['is_registered'])
1004 {
1005 global $db;
1006
1007 $sql = 'SELECT topic_id, mark_time
1008 FROM ' . TOPICS_TRACK_TABLE . "
1009 WHERE user_id = {$user->data['user_id']}
1010 AND " . $db->sql_in_set('topic_id', $topic_ids);
1011 $result = $db->sql_query($sql);
1012
1013 while ($row = $db->sql_fetchrow($result))
1014 {
1015 $last_read[$row['topic_id']] = $row['mark_time'];
1016 }
1017 $db->sql_freeresult($result);
1018
1019 $topic_ids = array_diff($topic_ids, array_keys($last_read));
1020
1021 if (count($topic_ids))
1022 {
1023 $sql = 'SELECT forum_id, mark_time
1024 FROM ' . FORUMS_TRACK_TABLE . "
1025 WHERE user_id = {$user->data['user_id']}
1026 AND forum_id = $forum_id";
1027 $result = $db->sql_query($sql);
1028
1029 $mark_time = array();
1030 while ($row = $db->sql_fetchrow($result))
1031 {
1032 $mark_time[$row['forum_id']] = $row['mark_time'];
1033 }
1034 $db->sql_freeresult($result);
1035
1036 $user_lastmark = (isset($mark_time[$forum_id])) ? $mark_time[$forum_id] : $user->data['user_lastmark'];
1037
1038 foreach ($topic_ids as $topic_id)
1039 {
1040 $last_read[$topic_id] = $user_lastmark;
1041 }
1042 }
1043 }
1044 else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1045 {
1046 global $tracking_topics;
1047
1048 if (!isset($tracking_topics) || !count($tracking_topics))
1049 {
1050 $tracking_topics = $request->variable($config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE);
1051 $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
1052 }
1053
1054 if (!$user->data['is_registered'])
1055 {
1056 $user_lastmark = (isset($tracking_topics['l'])) ? base_convert($tracking_topics['l'], 36, 10) + $config['board_startdate'] : 0;
1057 }
1058 else
1059 {
1060 $user_lastmark = $user->data['user_lastmark'];
1061 }
1062
1063 foreach ($topic_ids as $topic_id)
1064 {
1065 $topic_id36 = base_convert($topic_id, 10, 36);
1066
1067 if (isset($tracking_topics['t'][$topic_id36]))
1068 {
1069 $last_read[$topic_id] = base_convert($tracking_topics['t'][$topic_id36], 36, 10) + $config['board_startdate'];
1070 }
1071 }
1072
1073 $topic_ids = array_diff($topic_ids, array_keys($last_read));
1074
1075 if (count($topic_ids))
1076 {
1077 $mark_time = array();
1078
1079 if (isset($tracking_topics['f'][$forum_id]))
1080 {
1081 $mark_time[$forum_id] = base_convert($tracking_topics['f'][$forum_id], 36, 10) + $config['board_startdate'];
1082 }
1083
1084 $user_lastmark = (isset($mark_time[$forum_id])) ? $mark_time[$forum_id] : $user_lastmark;
1085
1086 foreach ($topic_ids as $topic_id)
1087 {
1088 $last_read[$topic_id] = $user_lastmark;
1089 }
1090 }
1091 }
1092
1093 return $last_read;
1094 }
1095
1096 /**
1097 * Get list of unread topics
1098 *
1099 * @param int $user_id User ID (or false for current user)
1100 * @param string $sql_extra Extra WHERE SQL statement
1101 * @param string $sql_sort ORDER BY SQL sorting statement
1102 * @param string $sql_limit Limits the size of unread topics list, 0 for unlimited query
1103 * @param string $sql_limit_offset Sets the offset of the first row to search, 0 to search from the start
1104 *
1105 * @return int[] Topic ids as keys, mark_time of topic as value
1106 */
1107 function get_unread_topics($user_id = false, $sql_extra = '', $sql_sort = '', $sql_limit = 1001, $sql_limit_offset = 0)
1108 {
1109 global $config, $db, $user, $request;
1110 global $phpbb_dispatcher;
1111
1112 $user_id = ($user_id === false) ? (int) $user->data['user_id'] : (int) $user_id;
1113
1114 // Data array we're going to return
1115 $unread_topics = array();
1116
1117 if (empty($sql_sort))
1118 {
1119 $sql_sort = 'ORDER BY t.topic_last_post_time DESC, t.topic_last_post_id DESC';
1120 }
1121
1122 if ($config['load_db_lastread'] && $user->data['is_registered'])
1123 {
1124 // Get list of the unread topics
1125 $last_mark = (int) $user->data['user_lastmark'];
1126
1127 $sql_array = array(
1128 'SELECT' => 't.topic_id, t.topic_last_post_time, tt.mark_time as topic_mark_time, ft.mark_time as forum_mark_time',
1129
1130 'FROM' => array(TOPICS_TABLE => 't'),
1131
1132 'LEFT_JOIN' => array(
1133 array(
1134 'FROM' => array(TOPICS_TRACK_TABLE => 'tt'),
1135 'ON' => "tt.user_id = $user_id AND t.topic_id = tt.topic_id",
1136 ),
1137 array(
1138 'FROM' => array(FORUMS_TRACK_TABLE => 'ft'),
1139 'ON' => "ft.user_id = $user_id AND t.forum_id = ft.forum_id",
1140 ),
1141 ),
1142
1143 'WHERE' => "
1144 t.topic_last_post_time > $last_mark AND
1145 (
1146 (tt.mark_time IS NOT NULL AND t.topic_last_post_time > tt.mark_time) OR
1147 (tt.mark_time IS NULL AND ft.mark_time IS NOT NULL AND t.topic_last_post_time > ft.mark_time) OR
1148 (tt.mark_time IS NULL AND ft.mark_time IS NULL)
1149 )
1150 $sql_extra
1151 $sql_sort",
1152 );
1153
1154 /**
1155 * Change SQL query for fetching unread topics data
1156 *
1157 * @event core.get_unread_topics_modify_sql
1158 * @var array sql_array Fully assembled SQL query with keys SELECT, FROM, LEFT_JOIN, WHERE
1159 * @var int last_mark User's last_mark time
1160 * @var string sql_extra Extra WHERE SQL statement
1161 * @var string sql_sort ORDER BY SQL sorting statement
1162 * @since 3.1.4-RC1
1163 */
1164 $vars = array(
1165 'sql_array',
1166 'last_mark',
1167 'sql_extra',
1168 'sql_sort',
1169 );
1170 extract($phpbb_dispatcher->trigger_event('core.get_unread_topics_modify_sql', compact($vars)));
1171
1172 $sql = $db->sql_build_query('SELECT', $sql_array);
1173 $result = $db->sql_query_limit($sql, $sql_limit, $sql_limit_offset);
1174
1175 while ($row = $db->sql_fetchrow($result))
1176 {
1177 $topic_id = (int) $row['topic_id'];
1178 $unread_topics[$topic_id] = ($row['topic_mark_time']) ? (int) $row['topic_mark_time'] : (($row['forum_mark_time']) ? (int) $row['forum_mark_time'] : $last_mark);
1179 }
1180 $db->sql_freeresult($result);
1181 }
1182 else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1183 {
1184 global $tracking_topics;
1185
1186 if (empty($tracking_topics))
1187 {
1188 $tracking_topics = $request->variable($config['cookie_name'] . '_track', '', false, \phpbb\request\request_interface::COOKIE);
1189 $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
1190 }
1191
1192 if (!$user->data['is_registered'])
1193 {
1194 $user_lastmark = (isset($tracking_topics['l'])) ? base_convert($tracking_topics['l'], 36, 10) + $config['board_startdate'] : 0;
1195 }
1196 else
1197 {
1198 $user_lastmark = (int) $user->data['user_lastmark'];
1199 }
1200
1201 $sql = 'SELECT t.topic_id, t.forum_id, t.topic_last_post_time
1202 FROM ' . TOPICS_TABLE . ' t
1203 WHERE t.topic_last_post_time > ' . $user_lastmark . "
1204 $sql_extra
1205 $sql_sort";
1206 $result = $db->sql_query_limit($sql, $sql_limit, $sql_limit_offset);
1207
1208 while ($row = $db->sql_fetchrow($result))
1209 {
1210 $forum_id = (int) $row['forum_id'];
1211 $topic_id = (int) $row['topic_id'];
1212 $topic_id36 = base_convert($topic_id, 10, 36);
1213
1214 if (isset($tracking_topics['t'][$topic_id36]))
1215 {
1216 $last_read = base_convert($tracking_topics['t'][$topic_id36], 36, 10) + $config['board_startdate'];
1217
1218 if ($row['topic_last_post_time'] > $last_read)
1219 {
1220 $unread_topics[$topic_id] = $last_read;
1221 }
1222 }
1223 else if (isset($tracking_topics['f'][$forum_id]))
1224 {
1225 $mark_time = base_convert($tracking_topics['f'][$forum_id], 36, 10) + $config['board_startdate'];
1226
1227 if ($row['topic_last_post_time'] > $mark_time)
1228 {
1229 $unread_topics[$topic_id] = $mark_time;
1230 }
1231 }
1232 else
1233 {
1234 $unread_topics[$topic_id] = $user_lastmark;
1235 }
1236 }
1237 $db->sql_freeresult($result);
1238 }
1239
1240 return $unread_topics;
1241 }
1242
1243 /**
1244 * Check for read forums and update topic tracking info accordingly
1245 *
1246 * @param int $forum_id the forum id to check
1247 * @param int $forum_last_post_time the forums last post time
1248 * @param int $f_mark_time the forums last mark time if user is registered and load_db_lastread enabled
1249 * @param int $mark_time_forum false if the mark time needs to be obtained, else the last users forum mark time
1250 *
1251 * @return true if complete forum got marked read, else false.
1252 */
1253 function update_forum_tracking_info($forum_id, $forum_last_post_time, $f_mark_time = false, $mark_time_forum = false)
1254 {
1255 global $db, $tracking_topics, $user, $config, $request, $phpbb_container;
1256
1257 // Determine the users last forum mark time if not given.
1258 if ($mark_time_forum === false)
1259 {
1260 if ($config['load_db_lastread'] && $user->data['is_registered'])
1261 {
1262 $mark_time_forum = (!empty($f_mark_time)) ? $f_mark_time : $user->data['user_lastmark'];
1263 }
1264 else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1265 {
1266 $tracking_topics = $request->variable($config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE);
1267 $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
1268
1269 if (!$user->data['is_registered'])
1270 {
1271 $user->data['user_lastmark'] = (isset($tracking_topics['l'])) ? (int) (base_convert($tracking_topics['l'], 36, 10) + $config['board_startdate']) : 0;
1272 }
1273
1274 $mark_time_forum = (isset($tracking_topics['f'][$forum_id])) ? (int) (base_convert($tracking_topics['f'][$forum_id], 36, 10) + $config['board_startdate']) : $user->data['user_lastmark'];
1275 }
1276 }
1277
1278 // Handle update of unapproved topics info.
1279 // Only update for moderators having m_approve permission for the forum.
1280 /* @var $phpbb_content_visibility \phpbb\content_visibility */
1281 $phpbb_content_visibility = $phpbb_container->get('content.visibility');
1282
1283 // Check the forum for any left unread topics.
1284 // If there are none, we mark the forum as read.
1285 if ($config['load_db_lastread'] && $user->data['is_registered'])
1286 {
1287 if ($mark_time_forum >= $forum_last_post_time)
1288 {
1289 // We do not need to mark read, this happened before. Therefore setting this to true
1290 $row = true;
1291 }
1292 else
1293 {
1294 $sql = 'SELECT t.forum_id
1295 FROM ' . TOPICS_TABLE . ' t
1296 LEFT JOIN ' . TOPICS_TRACK_TABLE . ' tt
1297 ON (tt.topic_id = t.topic_id
1298 AND tt.user_id = ' . $user->data['user_id'] . ')
1299 WHERE t.forum_id = ' . $forum_id . '
1300 AND t.topic_last_post_time > ' . $mark_time_forum . '
1301 AND t.topic_moved_id = 0
1302 AND ' . $phpbb_content_visibility->get_visibility_sql('topic', $forum_id, 't.') . '
1303 AND (tt.topic_id IS NULL
1304 OR tt.mark_time < t.topic_last_post_time)';
1305 $result = $db->sql_query_limit($sql, 1);
1306 $row = $db->sql_fetchrow($result);
1307 $db->sql_freeresult($result);
1308 }
1309 }
1310 else if ($config['load_anon_lastread'] || $user->data['is_registered'])
1311 {
1312 // Get information from cookie
1313 if (!isset($tracking_topics['tf'][$forum_id]))
1314 {
1315 // We do not need to mark read, this happened before. Therefore setting this to true
1316 $row = true;
1317 }
1318 else
1319 {
1320 $sql = 'SELECT t.topic_id
1321 FROM ' . TOPICS_TABLE . ' t
1322 WHERE t.forum_id = ' . $forum_id . '
1323 AND t.topic_last_post_time > ' . $mark_time_forum . '
1324 AND t.topic_moved_id = 0
1325 AND ' . $phpbb_content_visibility->get_visibility_sql('topic', $forum_id, 't.');
1326 $result = $db->sql_query($sql);
1327
1328 $check_forum = $tracking_topics['tf'][$forum_id];
1329 $unread = false;
1330
1331 while ($row = $db->sql_fetchrow($result))
1332 {
1333 if (!isset($check_forum[base_convert($row['topic_id'], 10, 36)]))
1334 {
1335 $unread = true;
1336 break;
1337 }
1338 }
1339 $db->sql_freeresult($result);
1340
1341 $row = $unread;
1342 }
1343 }
1344 else
1345 {
1346 $row = true;
1347 }
1348
1349 if (!$row)
1350 {
1351 markread('topics', $forum_id);
1352 return true;
1353 }
1354
1355 return false;
1356 }
1357
1358 /**
1359 * Transform an array into a serialized format
1360 */
1361 function tracking_serialize($input)
1362 {
1363 $out = '';
1364 foreach ($input as $key => $value)
1365 {
1366 if (is_array($value))
1367 {
1368 $out .= $key . ':(' . tracking_serialize($value) . ');';
1369 }
1370 else
1371 {
1372 $out .= $key . ':' . $value . ';';
1373 }
1374 }
1375 return $out;
1376 }
1377
1378 /**
1379 * Transform a serialized array into an actual array
1380 */
1381 function tracking_unserialize($string, $max_depth = 3)
1382 {
1383 $n = strlen($string);
1384 if ($n > 10010)
1385 {
1386 die('Invalid data supplied');
1387 }
1388 $data = $stack = array();
1389 $key = '';
1390 $mode = 0;
1391 $level = &$data;
1392 for ($i = 0; $i < $n; ++$i)
1393 {
1394 switch ($mode)
1395 {
1396 case 0:
1397 switch ($string[$i])
1398 {
1399 case ':':
1400 $level[$key] = 0;
1401 $mode = 1;
1402 break;
1403 case ')':
1404 unset($level);
1405 $level = array_pop($stack);
1406 $mode = 3;
1407 break;
1408 default:
1409 $key .= $string[$i];
1410 }
1411 break;
1412
1413 case 1:
1414 switch ($string[$i])
1415 {
1416 case '(':
1417 if (count($stack) >= $max_depth)
1418 {
1419 die('Invalid data supplied');
1420 }
1421 $stack[] = &$level;
1422 $level[$key] = array();
1423 $level = &$level[$key];
1424 $key = '';
1425 $mode = 0;
1426 break;
1427 default:
1428 $level[$key] = $string[$i];
1429 $mode = 2;
1430 break;
1431 }
1432 break;
1433
1434 case 2:
1435 switch ($string[$i])
1436 {
1437 case ')':
1438 unset($level);
1439 $level = array_pop($stack);
1440 $mode = 3;
1441 break;
1442 case ';':
1443 $key = '';
1444 $mode = 0;
1445 break;
1446 default:
1447 $level[$key] .= $string[$i];
1448 break;
1449 }
1450 break;
1451
1452 case 3:
1453 switch ($string[$i])
1454 {
1455 case ')':
1456 unset($level);
1457 $level = array_pop($stack);
1458 break;
1459 case ';':
1460 $key = '';
1461 $mode = 0;
1462 break;
1463 default:
1464 die('Invalid data supplied');
1465 break;
1466 }
1467 break;
1468 }
1469 }
1470
1471 if (count($stack) != 0 || ($mode != 0 && $mode != 3))
1472 {
1473 die('Invalid data supplied');
1474 }
1475
1476 return $level;
1477 }
1478
1479 // Server functions (building urls, redirecting...)
1480
1481 /**
1482 * Append session id to url.
1483 * This function supports hooks.
1484 *
1485 * @param string $url The url the session id needs to be appended to (can have params)
1486 * @param mixed $params String or array of additional url parameters
1487 * @param bool $is_amp Is url using & (true) or & (false)
1488 * @param string $session_id Possibility to use a custom session id instead of the global one
1489 * @param bool $is_route Is url generated by a route.
1490 *
1491 * @return string The corrected url.
1492 *
1493 * Examples:
1494 * <code> append_sid("{$phpbb_root_path}viewtopic.$phpEx?t=1");
1495 * append_sid("{$phpbb_root_path}viewtopic.$phpEx", 't=1');
1496 * append_sid("{$phpbb_root_path}viewtopic.$phpEx", 't=1', false);
1497 * append_sid("{$phpbb_root_path}viewtopic.$phpEx", array('t' => 1, 'f' => 2));
1498 * </code>
1499 *
1500 */
1501 function append_sid($url, $params = false, $is_amp = true, $session_id = false, $is_route = false)
1502 {
1503 global $_SID, $_EXTRA_URL, $phpbb_hook, $phpbb_path_helper;
1504 global $phpbb_dispatcher;
1505
1506 if ($params === '' || (is_array($params) && empty($params)))
1507 {
1508 // Do not append the ? if the param-list is empty anyway.
1509 $params = false;
1510 }
1511
1512 // Update the root path with the correct relative web path
1513 if (!$is_route && $phpbb_path_helper instanceof \phpbb\path_helper)
1514 {
1515 $url = $phpbb_path_helper->update_web_root_path($url);
1516 }
1517
1518 $append_sid_overwrite = false;
1519
1520 /**
1521 * This event can either supplement or override the append_sid() function
1522 *
1523 * To override this function, the event must set $append_sid_overwrite to
1524 * the new URL value, which will be returned following the event
1525 *
1526 * @event core.append_sid
1527 * @var string url The url the session id needs
1528 * to be appended to (can have
1529 * params)
1530 * @var mixed params String or array of additional
1531 * url parameters
1532 * @var bool is_amp Is url using & (true) or
1533 * & (false)
1534 * @var bool|string session_id Possibility to use a custom
1535 * session id (string) instead of
1536 * the global one (false)
1537 * @var bool|string append_sid_overwrite Overwrite function (string
1538 * URL) or not (false)
1539 * @var bool is_route Is url generated by a route.
1540 * @since 3.1.0-a1
1541 */
1542 $vars = array('url', 'params', 'is_amp', 'session_id', 'append_sid_overwrite', 'is_route');
1543 extract($phpbb_dispatcher->trigger_event('core.append_sid', compact($vars)));
1544
1545 if ($append_sid_overwrite)
1546 {
1547 return $append_sid_overwrite;
1548 }
1549
1550 // The following hook remains for backwards compatibility, though use of
1551 // the event above is preferred.
1552 // Developers using the hook function need to globalise the $_SID and $_EXTRA_URL on their own and also handle it appropriately.
1553 // They could mimic most of what is within this function
1554 if (!empty($phpbb_hook) && $phpbb_hook->call_hook(__FUNCTION__, $url, $params, $is_amp, $session_id))
1555 {
1556 if ($phpbb_hook->hook_return(__FUNCTION__))
1557 {
1558 return $phpbb_hook->hook_return_result(__FUNCTION__);
1559 }
1560 }
1561
1562 $params_is_array = is_array($params);
1563
1564 // Get anchor
1565 $anchor = '';
1566 if (strpos($url, '#') !== false)
1567 {
1568 list($url, $anchor) = explode('#', $url, 2);
1569 $anchor = '#' . $anchor;
1570 }
1571 else if (!$params_is_array && strpos($params, '#') !== false)
1572 {
1573 list($params, $anchor) = explode('#', $params, 2);
1574 $anchor = '#' . $anchor;
1575 }
1576
1577 // Handle really simple cases quickly
1578 if ($_SID == '' && $session_id === false && empty($_EXTRA_URL) && !$params_is_array && !$anchor)
1579 {
1580 if ($params === false)
1581 {
1582 return $url;
1583 }
1584
1585 $url_delim = (strpos($url, '?') === false) ? '?' : (($is_amp) ? '&' : '&');
1586 return $url . ($params !== false ? $url_delim. $params : '');
1587 }
1588
1589 // Assign sid if session id is not specified
1590 if ($session_id === false)
1591 {
1592 $session_id = $_SID;
1593 }
1594
1595 $amp_delim = ($is_amp) ? '&' : '&';
1596 $url_delim = (strpos($url, '?') === false) ? '?' : $amp_delim;
1597
1598 // Appending custom url parameter?
1599 $append_url = (!empty($_EXTRA_URL)) ? implode($amp_delim, $_EXTRA_URL) : '';
1600
1601 // Use the short variant if possible ;)
1602 if ($params === false)
1603 {
1604 // Append session id
1605 if (!$session_id)
1606 {
1607 return $url . (($append_url) ? $url_delim . $append_url : '') . $anchor;
1608 }
1609 else
1610 {
1611 return $url . (($append_url) ? $url_delim . $append_url . $amp_delim : $url_delim) . 'sid=' . $session_id . $anchor;
1612 }
1613 }
1614
1615 // Build string if parameters are specified as array
1616 if (is_array($params))
1617 {
1618 $output = array();
1619
1620 foreach ($params as $key => $item)
1621 {
1622 if ($item === NULL)
1623 {
1624 continue;
1625 }
1626
1627 if ($key == '#')
1628 {
1629 $anchor = '#' . $item;
1630 continue;
1631 }
1632
1633 $output[] = $key . '=' . $item;
1634 }
1635
1636 $params = implode($amp_delim, $output);
1637 }
1638
1639 // Append session id and parameters (even if they are empty)
1640 // If parameters are empty, the developer can still append his/her parameters without caring about the delimiter
1641 return $url . (($append_url) ? $url_delim . $append_url . $amp_delim : $url_delim) . $params . ((!$session_id) ? '' : $amp_delim . 'sid=' . $session_id) . $anchor;
1642 }
1643
1644 /**
1645 * Generate board url (example: http://www.example.com/phpBB)
1646 *
1647 * @param bool $without_script_path if set to true the script path gets not appended (example: http://www.example.com)
1648 *
1649 * @return string the generated board url
1650 */
1651 function generate_board_url($without_script_path = false)
1652 {
1653 global $config, $user, $request, $symfony_request;
1654
1655 $server_name = $user->host;
1656
1657 // Forcing server vars is the only way to specify/override the protocol
1658 if ($config['force_server_vars'] || !$server_name)
1659 {
1660 $server_protocol = ($config['server_protocol']) ? $config['server_protocol'] : (($config['cookie_secure']) ? 'https://' : 'http://');
1661 $server_name = $config['server_name'];
1662 $server_port = (int) $config['server_port'];
1663 $script_path = $config['script_path'];
1664
1665 $url = $server_protocol . $server_name;
1666 $cookie_secure = $config['cookie_secure'];
1667 }
1668 else
1669 {
1670 $server_port = (int) $symfony_request->getPort();
1671
1672 $forwarded_proto = $request->server('HTTP_X_FORWARDED_PROTO');
1673
1674 if (!empty($forwarded_proto) && $forwarded_proto === 'https')
1675 {
1676 $server_port = 443;
1677 }
1678 // Do not rely on cookie_secure, users seem to think that it means a secured cookie instead of an encrypted connection
1679 $cookie_secure = $request->is_secure() ? 1 : 0;
1680 $url = (($cookie_secure) ? 'https://' : 'http://') . $server_name;
1681
1682 $script_path = $user->page['root_script_path'];
1683 }
1684
1685 if ($server_port && (($cookie_secure && $server_port <> 443) || (!$cookie_secure && $server_port <> 80)))
1686 {
1687 // HTTP HOST can carry a port number (we fetch $user->host, but for old versions this may be true)
1688 if (strpos($server_name, ':') === false)
1689 {
1690 $url .= ':' . $server_port;
1691 }
1692 }
1693
1694 if (!$without_script_path)
1695 {
1696 $url .= $script_path;
1697 }
1698
1699 // Strip / from the end
1700 if (substr($url, -1, 1) == '/')
1701 {
1702 $url = substr($url, 0, -1);
1703 }
1704
1705 return $url;
1706 }
1707
1708 /**
1709 * Redirects the user to another page then exits the script nicely
1710 * This function is intended for urls within the board. It's not meant to redirect to cross-domains.
1711 *
1712 * @param string $url The url to redirect to
1713 * @param bool $return If true, do not redirect but return the sanitized URL. Default is no return.
1714 * @param bool $disable_cd_check If true, redirect() will redirect to an external domain. If false, the redirect point to the boards url if it does not match the current domain. Default is false.
1715 */
1716 function redirect($url, $return = false, $disable_cd_check = false)
1717 {
1718 global $user, $phpbb_path_helper, $phpbb_dispatcher;
1719
1720 if (!$user->is_setup())
1721 {
1722 $user->add_lang('common');
1723 }
1724
1725 // Make sure no &'s are in, this will break the redirect
1726 $url = str_replace('&', '&', $url);
1727
1728 // Determine which type of redirect we need to handle...
1729 $url_parts = @parse_url($url);
1730
1731 if ($url_parts === false)
1732 {
1733 // Malformed url
1734 trigger_error('INSECURE_REDIRECT', E_USER_WARNING);
1735 }
1736 else if (!empty($url_parts['scheme']) && !empty($url_parts['host']))
1737 {
1738 // Attention: only able to redirect within the same domain if $disable_cd_check is false (yourdomain.com -> www.yourdomain.com will not work)
1739 if (!$disable_cd_check && $url_parts['host'] !== $user->host)
1740 {
1741 trigger_error('INSECURE_REDIRECT', E_USER_WARNING);
1742 }
1743 }
1744 else if ($url[0] == '/')
1745 {
1746 // Absolute uri, prepend direct url...
1747 $url = generate_board_url(true) . $url;
1748 }
1749 else
1750 {
1751 // Relative uri
1752 $pathinfo = pathinfo($url);
1753
1754 // Is the uri pointing to the current directory?
1755 if ($pathinfo['dirname'] == '.')
1756 {
1757 $url = str_replace('./', '', $url);
1758
1759 // Strip / from the beginning
1760 if ($url && substr($url, 0, 1) == '/')
1761 {
1762 $url = substr($url, 1);
1763 }
1764 }
1765
1766 $url = $phpbb_path_helper->remove_web_root_path($url);
1767
1768 if ($user->page['page_dir'])
1769 {
1770 $url = $user->page['page_dir'] . '/' . $url;
1771 }
1772
1773 $url = generate_board_url() . '/' . $url;
1774 }
1775
1776 // Clean URL and check if we go outside the forum directory
1777 $url = $phpbb_path_helper->clean_url($url);
1778
1779 if (!$disable_cd_check && strpos($url, generate_board_url(true) . '/') !== 0)
1780 {
1781 trigger_error('INSECURE_REDIRECT', E_USER_WARNING);
1782 }
1783
1784 // Make sure no linebreaks are there... to prevent http response splitting for PHP < 4.4.2
1785 if (strpos(urldecode($url), "\n") !== false || strpos(urldecode($url), "\r") !== false || strpos($url, ';') !== false)
1786 {
1787 trigger_error('INSECURE_REDIRECT', E_USER_WARNING);
1788 }
1789
1790 // Now, also check the protocol and for a valid url the last time...
1791 $allowed_protocols = array('http', 'https', 'ftp', 'ftps');
1792 $url_parts = parse_url($url);
1793
1794 if ($url_parts === false || empty($url_parts['scheme']) || !in_array($url_parts['scheme'], $allowed_protocols))
1795 {
1796 trigger_error('INSECURE_REDIRECT', E_USER_WARNING);
1797 }
1798
1799 /**
1800 * Execute code and/or overwrite redirect()
1801 *
1802 * @event core.functions.redirect
1803 * @var string url The url
1804 * @var bool return If true, do not redirect but return the sanitized URL.
1805 * @var bool disable_cd_check If true, redirect() will redirect to an external domain. If false, the redirect point to the boards url if it does not match the current domain.
1806 * @since 3.1.0-RC3
1807 */
1808 $vars = array('url', 'return', 'disable_cd_check');
1809 extract($phpbb_dispatcher->trigger_event('core.functions.redirect', compact($vars)));
1810
1811 if ($return)
1812 {
1813 return $url;
1814 }
1815 else
1816 {
1817 garbage_collection();
1818 }
1819
1820 // Behave as per HTTP/1.1 spec for others
1821 header('Location: ' . $url);
1822 exit;
1823 }
1824
1825 /**
1826 * Returns the install redirect path for phpBB.
1827 *
1828 * @param string $phpbb_root_path The root path of the phpBB installation.
1829 * @param string $phpEx The file extension of php files, e.g., "php".
1830 * @return string The install redirect path.
1831 */
1832 function phpbb_get_install_redirect(string $phpbb_root_path, string $phpEx): string
1833 {
1834 $script_name = (!empty($_SERVER['REQUEST_URI'])) ? $_SERVER['REQUEST_URI'] : getenv('REQUEST_URI');
1835 if (!$script_name)
1836 {
1837 $script_name = (!empty($_SERVER['PHP_SELF'])) ? $_SERVER['PHP_SELF'] : getenv('PHP_SELF');
1838 }
1839
1840 // Add trailing dot to prevent dirname() from returning parent directory if $script_name is a directory
1841 $script_name = substr($script_name, -1) === '/' ? $script_name . '.' : $script_name;
1842
1843 // $phpbb_root_path accounts for redirects from e.g. /adm
1844 $script_path = trim(dirname($script_name)) . '/' . $phpbb_root_path . 'install/app.' . $phpEx;
1845 // Replace any number of consecutive backslashes and/or slashes with a single slash
1846 // (could happen on some proxy setups and/or Windows servers)
1847 return preg_replace('#[\\\\/]{2,}#', '/', $script_path);
1848 }
1849
1850 /**
1851 * Re-Apply session id after page reloads
1852 */
1853 function reapply_sid($url, $is_route = false)
1854 {
1855 global $phpEx, $phpbb_root_path;
1856
1857 if ($url === "index.$phpEx")
1858 {
1859 return append_sid("index.$phpEx");
1860 }
1861 else if ($url === "{$phpbb_root_path}index.$phpEx")
1862 {
1863 return append_sid("{$phpbb_root_path}index.$phpEx");
1864 }
1865
1866 // Remove previously added sid
1867 if (strpos($url, 'sid=') !== false)
1868 {
1869 // All kind of links
1870 $url = preg_replace('/(\?)?(&|&)?sid=[a-z0-9]+/', '', $url);
1871 // if the sid was the first param, make the old second as first ones
1872 $url = preg_replace("/$phpEx(&|&)+?/", "$phpEx?", $url);
1873 }
1874
1875 return append_sid($url, false, true, false, $is_route);
1876 }
1877
1878 /**
1879 * Returns url from the session/current page with an re-appended SID with optionally stripping vars from the url
1880 */
1881 function build_url($strip_vars = false)
1882 {
1883 global $config, $user, $phpbb_path_helper;
1884
1885 $page = $phpbb_path_helper->get_valid_page($user->page['page'], $config['enable_mod_rewrite']);
1886
1887 // Append SID
1888 $redirect = append_sid($page, false, false);
1889
1890 if ($strip_vars !== false)
1891 {
1892 $redirect = $phpbb_path_helper->strip_url_params($redirect, $strip_vars, false);
1893 }
1894 else
1895 {
1896 $redirect = str_replace('&', '&', $redirect);
1897 }
1898
1899 return $redirect . ((strpos($redirect, '?') === false) ? '?' : '');
1900 }
1901
1902 /**
1903 * Meta refresh assignment
1904 * Adds META template variable with meta http tag.
1905 *
1906 * @param int $time Time in seconds for meta refresh tag
1907 * @param string $url URL to redirect to. The url will go through redirect() first before the template variable is assigned
1908 * @param bool $disable_cd_check If true, meta_refresh() will redirect to an external domain. If false, the redirect point to the boards url if it does not match the current domain. Default is false.
1909 */
1910 function meta_refresh($time, $url, $disable_cd_check = false)
1911 {
1912 global $template, $refresh_data, $request;
1913
1914 $url = redirect($url, true, $disable_cd_check);
1915 if ($request->is_ajax())
1916 {
1917 $refresh_data = array(
1918 'time' => $time,
1919 'url' => $url,
1920 );
1921 }
1922 else
1923 {
1924 // For XHTML compatibility we change back & to &
1925 $url = str_replace('&', '&', $url);
1926
1927 $template->assign_vars(array(
1928 'META' => '<meta http-equiv="refresh" content="' . $time . '; url=' . $url . '" />')
1929 );
1930 }
1931
1932 return $url;
1933 }
1934
1935 /**
1936 * Outputs correct status line header.
1937 *
1938 * Depending on php sapi one of the two following forms is used:
1939 *
1940 * Status: 404 Not Found
1941 *
1942 * HTTP/1.x 404 Not Found
1943 *
1944 * HTTP version is taken from HTTP_VERSION environment variable,
1945 * and defaults to 1.0.
1946 *
1947 * Sample usage:
1948 *
1949 * send_status_line(404, 'Not Found');
1950 *
1951 * @param int $code HTTP status code
1952 * @param string $message Message for the status code
1953 * @return null
1954 */
1955 function send_status_line($code, $message)
1956 {
1957 if (substr(strtolower(@php_sapi_name()), 0, 3) === 'cgi')
1958 {
1959 // in theory, we shouldn't need that due to php doing it. Reality offers a differing opinion, though
1960 header("Status: $code $message", true, $code);
1961 }
1962 else
1963 {
1964 $version = phpbb_request_http_version();
1965 header("$version $code $message", true, $code);
1966 }
1967 }
1968
1969 /**
1970 * Returns the HTTP version used in the current request.
1971 *
1972 * Handles the case of being called before $request is present,
1973 * in which case it falls back to the $_SERVER superglobal.
1974 *
1975 * @return string HTTP version
1976 */
1977 function phpbb_request_http_version()
1978 {
1979 global $request;
1980
1981 $version = '';
1982 if ($request && $request->server('SERVER_PROTOCOL'))
1983 {
1984 $version = $request->server('SERVER_PROTOCOL');
1985 }
1986 else if (isset($_SERVER['SERVER_PROTOCOL']))
1987 {
1988 $version = $_SERVER['SERVER_PROTOCOL'];
1989 }
1990
1991 if (!empty($version) && is_string($version) && preg_match('#^HTTP/[0-9]\.[0-9]$#', $version))
1992 {
1993 return $version;
1994 }
1995
1996 return 'HTTP/1.0';
1997 }
1998
1999 //Form validation
2000
2001
2002 /**
2003 * Add a secret hash for use in links/GET requests
2004 * @param string $link_name The name of the link; has to match the name used in check_link_hash, otherwise no restrictions apply
2005 * @return string the hash
2006
2007 */
2008 function generate_link_hash($link_name)
2009 {
2010 global $user;
2011
2012 if (!isset($user->data["hash_$link_name"]))
2013 {
2014 $user->data["hash_$link_name"] = substr(sha1($user->data['user_form_salt'] . $link_name), 0, 8);
2015 }
2016
2017 return $user->data["hash_$link_name"];
2018 }
2019
2020
2021 /**
2022 * checks a link hash - for GET requests
2023 * @param string $token the submitted token
2024 * @param string $link_name The name of the link
2025 * @return boolean true if all is fine
2026 */
2027 function check_link_hash($token, $link_name)
2028 {
2029 return $token === generate_link_hash($link_name);
2030 }
2031
2032 /**
2033 * Add a secret token to the form (requires the S_FORM_TOKEN template variable)
2034 * @param string $form_name The name of the form; has to match the name used in check_form_key, otherwise no restrictions apply
2035 * @param string $template_variable_suffix A string that is appended to the name of the template variable to which the form elements are assigned
2036 */
2037 function add_form_key($form_name, $template_variable_suffix = '')
2038 {
2039 global $config, $template, $user, $phpbb_dispatcher;
2040
2041 $now = time();
2042 $token_sid = ($user->data['user_id'] == ANONYMOUS && !empty($config['form_token_sid_guests'])) ? $user->session_id : '';
2043 $token = sha1($now . $user->data['user_form_salt'] . $form_name . $token_sid);
2044
2045 $s_fields = build_hidden_fields(array(
2046 'creation_time' => $now,
2047 'form_token' => $token,
2048 ));
2049
2050 /**
2051 * Perform additional actions on creation of the form token
2052 *
2053 * @event core.add_form_key
2054 * @var string form_name The form name
2055 * @var int now Current time timestamp
2056 * @var string s_fields Generated hidden fields
2057 * @var string token Form token
2058 * @var string token_sid User session ID
2059 * @var string template_variable_suffix The string that is appended to template variable name
2060 *
2061 * @since 3.1.0-RC3
2062 * @changed 3.1.11-RC1 Added template_variable_suffix
2063 */
2064 $vars = array(
2065 'form_name',
2066 'now',
2067 's_fields',
2068 'token',
2069 'token_sid',
2070 'template_variable_suffix',
2071 );
2072 extract($phpbb_dispatcher->trigger_event('core.add_form_key', compact($vars)));
2073
2074 $template->assign_var('S_FORM_TOKEN' . $template_variable_suffix, $s_fields);
2075 }
2076
2077 /**
2078 * Check the form key. Required for all altering actions not secured by confirm_box
2079 *
2080 * @param string $form_name The name of the form; has to match the name used
2081 * in add_form_key, otherwise no restrictions apply
2082 * @param int $timespan The maximum acceptable age for a submitted form
2083 * in seconds. Defaults to the config setting.
2084 * @return bool True, if the form key was valid, false otherwise
2085 */
2086 function check_form_key($form_name, $timespan = false)
2087 {
2088 global $config, $request, $user;
2089
2090 if ($timespan === false)
2091 {
2092 // we enforce a minimum value of half a minute here.
2093 $timespan = ($config['form_token_lifetime'] == -1) ? -1 : max(30, $config['form_token_lifetime']);
2094 }
2095
2096 if ($request->is_set_post('creation_time') && $request->is_set_post('form_token'))
2097 {
2098 $creation_time = abs($request->variable('creation_time', 0));
2099 $token = $request->variable('form_token', '');
2100
2101 $diff = time() - $creation_time;
2102
2103 // If creation_time and the time() now is zero we can assume it was not a human doing this (the check for if ($diff)...
2104 if (defined('DEBUG_TEST') || $diff && ($diff <= $timespan || $timespan === -1))
2105 {
2106 $token_sid = ($user->data['user_id'] == ANONYMOUS && !empty($config['form_token_sid_guests'])) ? $user->session_id : '';
2107 $key = sha1($creation_time . $user->data['user_form_salt'] . $form_name . $token_sid);
2108
2109 if ($key === $token)
2110 {
2111 return true;
2112 }
2113 }
2114 }
2115
2116 return false;
2117 }
2118
2119 // Message/Login boxes
2120
2121 /**
2122 * Build Confirm box
2123 * @param boolean $check True for checking if confirmed (without any additional parameters) and false for displaying the confirm box
2124 * @param string|array $title Title/Message used for confirm box.
2125 * message text is _CONFIRM appended to title.
2126 * If title cannot be found in user->lang a default one is displayed
2127 * If title_CONFIRM cannot be found in user->lang the text given is used.
2128 * If title is an array, the first array value is used as explained per above,
2129 * all other array values are sent as parameters to the language function.
2130 * @param string $hidden Hidden variables
2131 * @param string $html_body Template used for confirm box
2132 * @param string $u_action Custom form action
2133 *
2134 * @return bool True if confirmation was successful, false if not
2135 */
2136 function confirm_box($check, $title = '', $hidden = '', $html_body = 'confirm_body.html', $u_action = '')
2137 {
2138 global $user, $template, $db, $request;
2139 global $config, $language, $phpbb_path_helper, $phpbb_dispatcher;
2140
2141 if (isset($_POST['cancel']))
2142 {
2143 return false;
2144 }
2145
2146 $confirm = ($language->lang('YES') === $request->variable('confirm', '', true, \phpbb\request\request_interface::POST));
2147
2148 if ($check && $confirm)
2149 {
2150 $user_id = $request->variable('confirm_uid', 0);
2151 $session_id = $request->variable('sess', '');
2152 $confirm_key = $request->variable('confirm_key', '');
2153
2154 if ($user_id != $user->data['user_id'] || $session_id != $user->session_id || !$confirm_key || !$user->data['user_last_confirm_key'] || $confirm_key != $user->data['user_last_confirm_key'])
2155 {
2156 return false;
2157 }
2158
2159 // Reset user_last_confirm_key
2160 $sql = 'UPDATE ' . USERS_TABLE . " SET user_last_confirm_key = ''
2161 WHERE user_id = " . $user->data['user_id'];
2162 $db->sql_query($sql);
2163
2164 return true;
2165 }
2166 else if ($check)
2167 {
2168 return false;
2169 }
2170
2171 $s_hidden_fields = build_hidden_fields(array(
2172 'confirm_uid' => $user->data['user_id'],
2173 'sess' => $user->session_id,
2174 'sid' => $user->session_id,
2175 ));
2176
2177 // generate activation key
2178 $confirm_key = gen_rand_string(10);
2179
2180 // generate language strings
2181 if (is_array($title))
2182 {
2183 $key = array_shift($title);
2184 $count = array_shift($title);
2185 $confirm_title = $language->is_set($key) ? $language->lang($key, $count, $title) : $language->lang('CONFIRM');
2186 $confirm_text = $language->is_set($key . '_CONFIRM') ? $language->lang($key . '_CONFIRM', $count, $title) : $key;
2187 }
2188 else
2189 {
2190 $confirm_title = $language->is_set($title) ? $language->lang($title) : $language->lang('CONFIRM');
2191 $confirm_text = $language->is_set($title . '_CONFIRM') ? $language->lang($title . '_CONFIRM') : $title;
2192 }
2193
2194 if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
2195 {
2196 adm_page_header($confirm_title);
2197 }
2198 else
2199 {
2200 page_header($confirm_title);
2201 }
2202
2203 $template->set_filenames(array(
2204 'body' => $html_body)
2205 );
2206
2207 // If activation key already exist, we better do not re-use the key (something very strange is going on...)
2208 if ($request->variable('confirm_key', ''))
2209 {
2210 // This should not occur, therefore we cancel the operation to safe the user
2211 return false;
2212 }
2213
2214 // re-add sid / transform & to & for user->page (user->page is always using &)
2215 $use_page = ($u_action) ? $u_action : str_replace('&', '&', $user->page['page']);
2216 $u_action = reapply_sid($phpbb_path_helper->get_valid_page($use_page, $config['enable_mod_rewrite']));
2217 $u_action .= ((strpos($u_action, '?') === false) ? '?' : '&') . 'confirm_key=' . $confirm_key;
2218
2219 $template->assign_vars(array(
2220 'MESSAGE_TITLE' => $confirm_title,
2221 'MESSAGE_TEXT' => $confirm_text,
2222
2223 'YES_VALUE' => $language->lang('YES'),
2224 'S_CONFIRM_ACTION' => $u_action,
2225 'S_HIDDEN_FIELDS' => $hidden . $s_hidden_fields,
2226 'S_AJAX_REQUEST' => $request->is_ajax(),
2227 ));
2228
2229 $sql = 'UPDATE ' . USERS_TABLE . " SET user_last_confirm_key = '" . $db->sql_escape($confirm_key) . "'
2230 WHERE user_id = " . $user->data['user_id'];
2231 $db->sql_query($sql);
2232
2233 if ($request->is_ajax())
2234 {
2235 $u_action .= '&confirm_uid=' . $user->data['user_id'] . '&sess=' . $user->session_id . '&sid=' . $user->session_id;
2236 $data = array(
2237 'MESSAGE_BODY' => $template->assign_display('body'),
2238 'MESSAGE_TITLE' => $confirm_title,
2239 'MESSAGE_TEXT' => $confirm_text,
2240
2241 'YES_VALUE' => $language->lang('YES'),
2242 'S_CONFIRM_ACTION' => str_replace('&', '&', $u_action), //inefficient, rewrite whole function
2243 'S_HIDDEN_FIELDS' => $hidden . $s_hidden_fields
2244 );
2245
2246 /**
2247 * This event allows an extension to modify the ajax output of confirm box.
2248 *
2249 * @event core.confirm_box_ajax_before
2250 * @var string u_action Action of the form
2251 * @var array data Data to be sent
2252 * @var string hidden Hidden fields generated by caller
2253 * @var string s_hidden_fields Hidden fields generated by this function
2254 * @since 3.2.8-RC1
2255 */
2256 $vars = array(
2257 'u_action',
2258 'data',
2259 'hidden',
2260 's_hidden_fields',
2261 );
2262 extract($phpbb_dispatcher->trigger_event('core.confirm_box_ajax_before', compact($vars)));
2263
2264 $json_response = new \phpbb\json_response;
2265 $json_response->send($data);
2266 }
2267
2268 if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
2269 {
2270 adm_page_footer();
2271 }
2272 else
2273 {
2274 page_footer();
2275 }
2276
2277 exit; // unreachable, page_footer() above will call exit()
2278 }
2279
2280 /**
2281 * Generate login box or verify password
2282 */
2283 function login_box($redirect = '', $l_explain = '', $l_success = '', $admin = false, $s_display = true)
2284 {
2285 global $user, $template, $auth, $phpEx, $phpbb_root_path, $config;
2286 global $request, $phpbb_container, $phpbb_dispatcher, $phpbb_log;
2287
2288 $err = '';
2289 $form_name = 'login';
2290 $username = $autologin = false;
2291
2292 // Make sure user->setup() has been called
2293 if (!$user->is_setup())
2294 {
2295 $user->setup();
2296 }
2297
2298 /**
2299 * This event allows an extension to modify the login process
2300 *
2301 * @event core.login_box_before
2302 * @var string redirect Redirect string
2303 * @var string l_explain Explain language string
2304 * @var string l_success Success language string
2305 * @var bool admin Is admin?
2306 * @var bool s_display Display full login form?
2307 * @var string err Error string
2308 * @since 3.1.9-RC1
2309 */
2310 $vars = array('redirect', 'l_explain', 'l_success', 'admin', 's_display', 'err');
2311 extract($phpbb_dispatcher->trigger_event('core.login_box_before', compact($vars)));
2312
2313 // Print out error if user tries to authenticate as an administrator without having the privileges...
2314 if ($admin && !$auth->acl_get('a_'))
2315 {
2316 // Not authd
2317 // anonymous/inactive users are never able to go to the ACP even if they have the relevant permissions
2318 if ($user->data['is_registered'])
2319 {
2320 $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ADMIN_AUTH_FAIL');
2321 }
2322 send_status_line(403, 'Forbidden');
2323 trigger_error('NO_AUTH_ADMIN');
2324 }
2325
2326 if (empty($err) && ($request->is_set_post('login') || ($request->is_set('login') && $request->variable('login', '') == 'external')))
2327 {
2328 // Get credential
2329 if ($admin)
2330 {
2331 $credential = $request->variable('credential', '');
2332
2333 if (strspn($credential, 'abcdef0123456789') !== strlen($credential) || strlen($credential) != 32)
2334 {
2335 if ($user->data['is_registered'])
2336 {
2337 $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ADMIN_AUTH_FAIL');
2338 }
2339 send_status_line(403, 'Forbidden');
2340 trigger_error('NO_AUTH_ADMIN');
2341 }
2342
2343 $password = $request->untrimmed_variable('password_' . $credential, '', true);
2344 }
2345 else
2346 {
2347 $password = $request->untrimmed_variable('password', '', true);
2348 }
2349
2350 $username = $request->variable('username', '', true);
2351 $autologin = $request->is_set_post('autologin');
2352 $viewonline = (int) !$request->is_set_post('viewonline');
2353 $admin = ($admin) ? 1 : 0;
2354 $viewonline = ($admin) ? $user->data['session_viewonline'] : $viewonline;
2355
2356 // Check if the supplied username is equal to the one stored within the database if re-authenticating
2357 if ($admin && utf8_clean_string($username) != utf8_clean_string($user->data['username']))
2358 {
2359 // We log the attempt to use a different username...
2360 $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ADMIN_AUTH_FAIL');
2361
2362 send_status_line(403, 'Forbidden');
2363 trigger_error('NO_AUTH_ADMIN_USER_DIFFER');
2364 }
2365
2366 // Check form key
2367 if ($password && !defined('IN_CHECK_BAN') && !check_form_key($form_name))
2368 {
2369 $result = array(
2370 'status' => false,
2371 'error_msg' => 'FORM_INVALID',
2372 );
2373 }
2374 else
2375 {
2376 // If authentication is successful we redirect user to previous page
2377 $result = $auth->login($username, $password, $autologin, $viewonline, $admin);
2378 }
2379
2380 // If admin authentication and login, we will log if it was a success or not...
2381 // We also break the operation on the first non-success login - it could be argued that the user already knows
2382 if ($admin)
2383 {
2384 if ($result['status'] == LOGIN_SUCCESS)
2385 {
2386 $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ADMIN_AUTH_SUCCESS');
2387 }
2388 else
2389 {
2390 // Only log the failed attempt if a real user tried to.
2391 // anonymous/inactive users are never able to go to the ACP even if they have the relevant permissions
2392 if ($user->data['is_registered'])
2393 {
2394 $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ADMIN_AUTH_FAIL');
2395 }
2396 }
2397 }
2398
2399 // The result parameter is always an array, holding the relevant information...
2400 if ($result['status'] == LOGIN_SUCCESS)
2401 {
2402 $redirect = $request->variable('redirect', "{$phpbb_root_path}index.$phpEx");
2403
2404 /**
2405 * This event allows an extension to modify the redirection when a user successfully logs in
2406 *
2407 * @event core.login_box_redirect
2408 * @var string redirect Redirect string
2409 * @var bool admin Is admin?
2410 * @var array result Result from auth provider
2411 * @since 3.1.0-RC5
2412 * @changed 3.1.9-RC1 Removed undefined return variable
2413 * @changed 3.2.4-RC1 Added result
2414 */
2415 $vars = array('redirect', 'admin', 'result');
2416 extract($phpbb_dispatcher->trigger_event('core.login_box_redirect', compact($vars)));
2417
2418 // append/replace SID (may change during the session for AOL users)
2419 $redirect = reapply_sid($redirect);
2420
2421 // Special case... the user is effectively banned, but we allow founders to login
2422 if (defined('IN_CHECK_BAN') && $result['user_row']['user_type'] != USER_FOUNDER)
2423 {
2424 return;
2425 }
2426
2427 redirect($redirect);
2428 }
2429
2430 // Something failed, determine what...
2431 if ($result['status'] == LOGIN_BREAK)
2432 {
2433 trigger_error($result['error_msg']);
2434 }
2435
2436 // Special cases... determine
2437 switch ($result['status'])
2438 {
2439 case LOGIN_ERROR_PASSWORD_CONVERT:
2440 $err = sprintf(
2441 $user->lang[$result['error_msg']],
2442 ($config['email_enable']) ? '<a href="' . append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=sendpassword') . '">' : '',
2443 ($config['email_enable']) ? '</a>' : '',
2444 '<a href="' . phpbb_get_board_contact_link($config, $phpbb_root_path, $phpEx) . '">',
2445 '</a>'
2446 );
2447 break;
2448
2449 case LOGIN_ERROR_ATTEMPTS:
2450
2451 $captcha = $phpbb_container->get('captcha.factory')->get_instance($config['captcha_plugin']);
2452 $captcha->init(CONFIRM_LOGIN);
2453 // $captcha->reset();
2454
2455 $template->assign_vars(array(
2456 'CAPTCHA_TEMPLATE' => $captcha->get_template(),
2457 ));
2458 // no break;
2459
2460 // Username, password, etc...
2461 default:
2462 $err = $user->lang[$result['error_msg']];
2463
2464 // Assign admin contact to some error messages
2465 if ($result['error_msg'] == 'LOGIN_ERROR_USERNAME' || $result['error_msg'] == 'LOGIN_ERROR_PASSWORD')
2466 {
2467 $err = sprintf($user->lang[$result['error_msg']], '<a href="' . append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=contactadmin') . '">', '</a>');
2468 }
2469
2470 break;
2471 }
2472
2473 /**
2474 * This event allows an extension to process when a user fails a login attempt
2475 *
2476 * @event core.login_box_failed
2477 * @var array result Login result data
2478 * @var string username User name used to login
2479 * @var string password Password used to login
2480 * @var string err Error message
2481 * @since 3.1.3-RC1
2482 */
2483 $vars = array('result', 'username', 'password', 'err');
2484 extract($phpbb_dispatcher->trigger_event('core.login_box_failed', compact($vars)));
2485 }
2486
2487 // Assign credential for username/password pair
2488 $credential = ($admin) ? md5(unique_id()) : false;
2489
2490 $s_hidden_fields = array(
2491 'sid' => $user->session_id,
2492 );
2493
2494 if ($redirect)
2495 {
2496 $s_hidden_fields['redirect'] = $redirect;
2497 }
2498
2499 if ($admin)
2500 {
2501 $s_hidden_fields['credential'] = $credential;
2502 }
2503
2504 /* @var $provider_collection \phpbb\auth\provider_collection */
2505 $provider_collection = $phpbb_container->get('auth.provider_collection');
2506 $auth_provider = $provider_collection->get_provider();
2507
2508 $auth_provider_data = $auth_provider->get_login_data();
2509 if ($auth_provider_data)
2510 {
2511 if (isset($auth_provider_data['VARS']))
2512 {
2513 $template->assign_vars($auth_provider_data['VARS']);
2514 }
2515
2516 if (isset($auth_provider_data['BLOCK_VAR_NAME']))
2517 {
2518 foreach ($auth_provider_data['BLOCK_VARS'] as $block_vars)
2519 {
2520 $template->assign_block_vars($auth_provider_data['BLOCK_VAR_NAME'], $block_vars);
2521 }
2522 }
2523
2524 $template->assign_vars(array(
2525 'PROVIDER_TEMPLATE_FILE' => $auth_provider_data['TEMPLATE_FILE'],
2526 ));
2527 }
2528
2529 $s_hidden_fields = build_hidden_fields($s_hidden_fields);
2530
2531 /** @var \phpbb\controller\helper $controller_helper */
2532 $controller_helper = $phpbb_container->get('controller.helper');
2533
2534 $login_box_template_data = array(
2535 'LOGIN_ERROR' => $err,
2536 'LOGIN_EXPLAIN' => $l_explain,
2537
2538 'U_SEND_PASSWORD' => ($config['email_enable'] && $config['allow_password_reset']) ? $controller_helper->route('phpbb_ucp_forgot_password_controller') : '',
2539 'U_RESEND_ACTIVATION' => ($config['require_activation'] == USER_ACTIVATION_SELF && $config['email_enable']) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=resend_act') : '',
2540 'U_TERMS_USE' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=terms'),
2541 'U_PRIVACY' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy'),
2542 'UA_PRIVACY' => addslashes(append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy')),
2543
2544 'S_DISPLAY_FULL_LOGIN' => ($s_display) ? true : false,
2545 'S_HIDDEN_FIELDS' => $s_hidden_fields,
2546
2547 'S_ADMIN_AUTH' => $admin,
2548 'USERNAME' => ($admin) ? $user->data['username'] : '',
2549
2550 'USERNAME_CREDENTIAL' => 'username',
2551 'PASSWORD_CREDENTIAL' => ($admin) ? 'password_' . $credential : 'password',
2552 );
2553
2554 /**
2555 * Event to add/modify login box template data
2556 *
2557 * @event core.login_box_modify_template_data
2558 * @var int admin Flag whether user is admin
2559 * @var string username User name
2560 * @var int autologin Flag whether autologin is enabled
2561 * @var string redirect Redirect URL
2562 * @var array login_box_template_data Array with the login box template data
2563 * @since 3.2.3-RC2
2564 */
2565 $vars = array(
2566 'admin',
2567 'username',
2568 'autologin',
2569 'redirect',
2570 'login_box_template_data',
2571 );
2572 extract($phpbb_dispatcher->trigger_event('core.login_box_modify_template_data', compact($vars)));
2573
2574 $template->assign_vars($login_box_template_data);
2575
2576 page_header($user->lang['LOGIN']);
2577
2578 $template->set_filenames(array(
2579 'body' => 'login_body.html')
2580 );
2581 make_jumpbox(append_sid("{$phpbb_root_path}viewforum.$phpEx"));
2582
2583 page_footer();
2584 }
2585
2586 /**
2587 * Generate forum login box
2588 */
2589 function login_forum_box($forum_data)
2590 {
2591 global $db, $phpbb_container, $request, $template, $user, $phpbb_dispatcher, $phpbb_root_path, $phpEx;
2592
2593 $password = $request->variable('password', '', true);
2594
2595 $sql = 'SELECT forum_id
2596 FROM ' . FORUMS_ACCESS_TABLE . '
2597 WHERE forum_id = ' . $forum_data['forum_id'] . '
2598 AND user_id = ' . $user->data['user_id'] . "
2599 AND session_id = '" . $db->sql_escape($user->session_id) . "'";
2600 $result = $db->sql_query($sql);
2601 $row = $db->sql_fetchrow($result);
2602 $db->sql_freeresult($result);
2603
2604 if ($row)
2605 {
2606 return true;
2607 }
2608
2609 if ($password)
2610 {
2611 // Remove expired authorised sessions
2612 $sql = 'SELECT f.session_id
2613 FROM ' . FORUMS_ACCESS_TABLE . ' f
2614 LEFT JOIN ' . SESSIONS_TABLE . ' s ON (f.session_id = s.session_id)
2615 WHERE s.session_id IS NULL';
2616 $result = $db->sql_query($sql);
2617
2618 if ($row = $db->sql_fetchrow($result))
2619 {
2620 $sql_in = array();
2621 do
2622 {
2623 $sql_in[] = (string) $row['session_id'];
2624 }
2625 while ($row = $db->sql_fetchrow($result));
2626
2627 // Remove expired sessions
2628 $sql = 'DELETE FROM ' . FORUMS_ACCESS_TABLE . '
2629 WHERE ' . $db->sql_in_set('session_id', $sql_in);
2630 $db->sql_query($sql);
2631 }
2632 $db->sql_freeresult($result);
2633
2634 /* @var $passwords_manager \phpbb\passwords\manager */
2635 $passwords_manager = $phpbb_container->get('passwords.manager');
2636
2637 if ($passwords_manager->check($password, $forum_data['forum_password']))
2638 {
2639 $sql_ary = array(
2640 'forum_id' => (int) $forum_data['forum_id'],
2641 'user_id' => (int) $user->data['user_id'],
2642 'session_id' => (string) $user->session_id,
2643 );
2644
2645 $db->sql_query('INSERT INTO ' . FORUMS_ACCESS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
2646
2647 return true;
2648 }
2649
2650 $template->assign_var('LOGIN_ERROR', $user->lang['WRONG_PASSWORD']);
2651 }
2652
2653 /**
2654 * Performing additional actions, load additional data on forum login
2655 *
2656 * @event core.login_forum_box
2657 * @var array forum_data Array with forum data
2658 * @var string password Password entered
2659 * @since 3.1.0-RC3
2660 */
2661 $vars = array('forum_data', 'password');
2662 extract($phpbb_dispatcher->trigger_event('core.login_forum_box', compact($vars)));
2663
2664 page_header($user->lang['LOGIN']);
2665
2666 $template->assign_vars(array(
2667 'FORUM_NAME' => isset($forum_data['forum_name']) ? $forum_data['forum_name'] : '',
2668 'S_LOGIN_ACTION' => build_url(array('f')),
2669 'S_HIDDEN_FIELDS' => build_hidden_fields(array('f' => $forum_data['forum_id'])))
2670 );
2671
2672 $template->set_filenames(array(
2673 'body' => 'login_forum.html')
2674 );
2675
2676 make_jumpbox(append_sid("{$phpbb_root_path}viewforum.$phpEx"), $forum_data['forum_id']);
2677
2678 page_footer();
2679 }
2680
2681 // Little helpers
2682
2683 /**
2684 * Little helper for the build_hidden_fields function
2685 */
2686 function _build_hidden_fields($key, $value, $specialchar, $stripslashes)
2687 {
2688 $hidden_fields = '';
2689
2690 if (!is_array($value))
2691 {
2692 $value = ($stripslashes) ? stripslashes($value) : $value;
2693 $value = ($specialchar) ? htmlspecialchars($value, ENT_COMPAT, 'UTF-8') : $value;
2694
2695 $hidden_fields .= '<input type="hidden" name="' . $key . '" value="' . $value . '" />' . "\n";
2696 }
2697 else
2698 {
2699 foreach ($value as $_key => $_value)
2700 {
2701 $_key = ($stripslashes) ? stripslashes($_key) : $_key;
2702 $_key = ($specialchar) ? htmlspecialchars($_key, ENT_COMPAT, 'UTF-8') : $_key;
2703
2704 $hidden_fields .= _build_hidden_fields($key . '[' . $_key . ']', $_value, $specialchar, $stripslashes);
2705 }
2706 }
2707
2708 return $hidden_fields;
2709 }
2710
2711 /**
2712 * Build simple hidden fields from array
2713 *
2714 * @param array $field_ary an array of values to build the hidden field from
2715 * @param bool $specialchar if true, keys and values get specialchared
2716 * @param bool $stripslashes if true, keys and values get stripslashed
2717 *
2718 * @return string the hidden fields
2719 */
2720 function build_hidden_fields($field_ary, $specialchar = false, $stripslashes = false)
2721 {
2722 $s_hidden_fields = '';
2723
2724 foreach ($field_ary as $name => $vars)
2725 {
2726 $name = ($stripslashes) ? stripslashes($name) : $name;
2727 $name = ($specialchar) ? htmlspecialchars($name, ENT_COMPAT, 'UTF-8') : $name;
2728
2729 $s_hidden_fields .= _build_hidden_fields($name, $vars, $specialchar, $stripslashes);
2730 }
2731
2732 return $s_hidden_fields;
2733 }
2734
2735 /**
2736 * Parse cfg file
2737 */
2738 function parse_cfg_file($filename, $lines = false)
2739 {
2740 $parsed_items = array();
2741
2742 if ($lines === false)
2743 {
2744 $lines = file($filename);
2745 }
2746
2747 foreach ($lines as $line)
2748 {
2749 $line = trim($line);
2750
2751 if (!$line || $line[0] == '#' || ($delim_pos = strpos($line, '=')) === false)
2752 {
2753 continue;
2754 }
2755
2756 // Determine first occurrence, since in values the equal sign is allowed
2757 $key = htmlspecialchars(strtolower(trim(substr($line, 0, $delim_pos))), ENT_COMPAT);
2758 $value = trim(substr($line, $delim_pos + 1));
2759
2760 if (in_array($value, array('off', 'false', '0')))
2761 {
2762 $value = false;
2763 }
2764 else if (in_array($value, array('on', 'true', '1')))
2765 {
2766 $value = true;
2767 }
2768 else if (!trim($value))
2769 {
2770 $value = '';
2771 }
2772 else if (($value[0] == "'" && $value[strlen($value) - 1] == "'") || ($value[0] == '"' && $value[strlen($value) - 1] == '"'))
2773 {
2774 $value = htmlspecialchars(substr($value, 1, strlen($value)-2), ENT_COMPAT);
2775 }
2776 else
2777 {
2778 $value = htmlspecialchars($value, ENT_COMPAT);
2779 }
2780
2781 $parsed_items[$key] = $value;
2782 }
2783
2784 if (isset($parsed_items['parent']) && isset($parsed_items['name']) && $parsed_items['parent'] == $parsed_items['name'])
2785 {
2786 unset($parsed_items['parent']);
2787 }
2788
2789 return $parsed_items;
2790 }
2791
2792 /**
2793 * Return a nicely formatted backtrace.
2794 *
2795 * Turns the array returned by debug_backtrace() into HTML markup.
2796 * Also filters out absolute paths to phpBB root.
2797 *
2798 * @return string HTML markup
2799 */
2800 function get_backtrace()
2801 {
2802 $output = '<div style="font-family: monospace;">';
2803 $backtrace = debug_backtrace();
2804
2805 // We skip the first one, because it only shows this file/function
2806 unset($backtrace[0]);
2807
2808 foreach ($backtrace as $trace)
2809 {
2810 // Strip the current directory from path
2811 $trace['file'] = (empty($trace['file'])) ? '(not given by php)' : htmlspecialchars(phpbb_filter_root_path($trace['file']), ENT_COMPAT);
2812 $trace['line'] = (empty($trace['line'])) ? '(not given by php)' : $trace['line'];
2813
2814 // Only show function arguments for include etc.
2815 // Other parameters may contain sensible information
2816 $argument = '';
2817 if (!empty($trace['args'][0]) && in_array($trace['function'], array('include', 'require', 'include_once', 'require_once')))
2818 {
2819 $argument = htmlspecialchars(phpbb_filter_root_path($trace['args'][0]), ENT_COMPAT);
2820 }
2821
2822 $trace['class'] = (!isset($trace['class'])) ? '' : $trace['class'];
2823 $trace['type'] = (!isset($trace['type'])) ? '' : $trace['type'];
2824
2825 $output .= '<br />';
2826 $output .= '<b>FILE:</b> ' . $trace['file'] . '<br />';
2827 $output .= '<b>LINE:</b> ' . ((!empty($trace['line'])) ? $trace['line'] : '') . '<br />';
2828
2829 $output .= '<b>CALL:</b> ' . htmlspecialchars($trace['class'] . $trace['type'] . $trace['function'], ENT_COMPAT);
2830 $output .= '(' . (($argument !== '') ? "'$argument'" : '') . ')<br />';
2831 }
2832 $output .= '</div>';
2833 return $output;
2834 }
2835
2836 /**
2837 * This function returns a regular expression pattern for commonly used expressions
2838 * Use with / as delimiter for email mode and # for url modes
2839 * mode can be: email|bbcode_htm|url|url_inline|www_url|www_url_inline|relative_url|relative_url_inline|ipv4|ipv6
2840 */
2841 function get_preg_expression($mode)
2842 {
2843 switch ($mode)
2844 {
2845 case 'email':
2846 // Regex written by James Watts and Francisco Jose Martin Moreno
2847 // http://fightingforalostcause.net/misc/2006/compare-email-regex.php
2848 return '((?:[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*(?:[\w\!\#$\%\'\*\+\-\/\=\?\^\`{\|\}\~]|&)+)@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,63})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)';
2849 break;
2850
2851 case 'bbcode_htm':
2852 return array(
2853 '#<!\-\- e \-\-><a href="mailto:(.*?)">.*?</a><!\-\- e \-\->#',
2854 '#<!\-\- l \-\-><a (?:class="[\w-]+" )?href="(.*?)(?:(&|\?)sid=[0-9a-f]{32})?">.*?</a><!\-\- l \-\->#',
2855 '#<!\-\- ([mw]) \-\-><a (?:class="[\w-]+" )?href="http://(.*?)">\2</a><!\-\- \1 \-\->#',
2856 '#<!\-\- ([mw]) \-\-><a (?:class="[\w-]+" )?href="(.*?)">.*?</a><!\-\- \1 \-\->#',
2857 '#<!\-\- s(.*?) \-\-><img src="\{SMILIES_PATH\}\/.*? \/><!\-\- s\1 \-\->#',
2858 '#<!\-\- .*? \-\->#s',
2859 '#<.*?>#s',
2860 );
2861 break;
2862
2863 // Whoa these look impressive!
2864 // The code to generate the following two regular expressions which match valid IPv4/IPv6 addresses
2865 // can be found in the develop directory
2866
2867 // @deprecated
2868 case 'ipv4':
2869 return '#^(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$#';
2870 break;
2871
2872 // @deprecated
2873 case 'ipv6':
2874 return '#^(?:(?:(?:[\dA-F]{1,4}:){6}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:::(?:[\dA-F]{1,4}:){0,5}(?:[\dA-F]{1,4}(?::[\dA-F]{1,4})?|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:):(?:[\dA-F]{1,4}:){4}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,2}:(?:[\dA-F]{1,4}:){3}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,3}:(?:[\dA-F]{1,4}:){2}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,4}:(?:[\dA-F]{1,4}:)(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,5}:(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,6}:[\dA-F]{1,4})|(?:(?:[\dA-F]{1,4}:){1,7}:)|(?:::))$#i';
2875 break;
2876
2877 case 'url':
2878 // generated with regex_idn.php file in the develop folder
2879 return "[a-z][a-z\d+\-.]*(?<!javascript):/{2}(?:(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'()*+,;=:@|]+|%[\dA-F]{2})+|[0-9.]+|\[[a-z0-9.]+:[a-z0-9.]+:[a-z0-9.:]+\])(?::\d*)?(?:/(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'()*+,;=:@|]+|%[\dA-F]{2})*)*(?:\?(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'()*+,;=:@/?|]+|%[\dA-F]{2})*)?(?:\#(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'()*+,;=:@/?|]+|%[\dA-F]{2})*)?";
2880 break;
2881
2882 case 'url_http':
2883 // generated with regex_idn.php file in the develop folder
2884 return "http[s]?:/{2}(?:(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'()*+,;=:@|]+|%[\dA-F]{2})+|[0-9.]+|\[[a-z0-9.]+:[a-z0-9.]+:[a-z0-9.:]+\])(?::\d*)?(?:/(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'()*+,;=:@|]+|%[\dA-F]{2})*)*(?:\?(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'()*+,;=:@/?|]+|%[\dA-F]{2})*)?(?:\#(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'()*+,;=:@/?|]+|%[\dA-F]{2})*)?";
2885 break;
2886
2887 case 'url_inline':
2888 // generated with regex_idn.php file in the develop folder
2889 return "[a-z][a-z\d+]*(?<!javascript):/{2}(?:(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'(*+,;=:@|]+|%[\dA-F]{2})+|[0-9.]+|\[[a-z0-9.]+:[a-z0-9.]+:[a-z0-9.:]+\])(?::\d*)?(?:/(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'(*+,;=:@|]+|%[\dA-F]{2})*)*(?:\?(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'(*+,;=:@/?|]+|%[\dA-F]{2})*)?(?:\#(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'(*+,;=:@/?|]+|%[\dA-F]{2})*)?";
2890 break;
2891
2892 case 'www_url':
2893 // generated with regex_idn.php file in the develop folder
2894 return "www\.(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'()*+,;=:@|]+|%[\dA-F]{2})+(?::\d*)?(?:/(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'()*+,;=:@|]+|%[\dA-F]{2})*)*(?:\?(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'()*+,;=:@/?|]+|%[\dA-F]{2})*)?(?:\#(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'()*+,;=:@/?|]+|%[\dA-F]{2})*)?";
2895 break;
2896
2897 case 'www_url_inline':
2898 // generated with regex_idn.php file in the develop folder
2899 return "www\.(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'(*+,;=:@|]+|%[\dA-F]{2})+(?::\d*)?(?:/(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'(*+,;=:@|]+|%[\dA-F]{2})*)*(?:\?(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'(*+,;=:@/?|]+|%[\dA-F]{2})*)?(?:\#(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'(*+,;=:@/?|]+|%[\dA-F]{2})*)?";
2900 break;
2901
2902 case 'relative_url':
2903 // generated with regex_idn.php file in the develop folder
2904 return "(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'()*+,;=:@|]+|%[\dA-F]{2})*(?:/(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'()*+,;=:@|]+|%[\dA-F]{2})*)*(?:\?(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'()*+,;=:@/?|]+|%[\dA-F]{2})*)?(?:\#(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'()*+,;=:@/?|]+|%[\dA-F]{2})*)?";
2905 break;
2906
2907 case 'relative_url_inline':
2908 // generated with regex_idn.php file in the develop folder
2909 return "(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'(*+,;=:@|]+|%[\dA-F]{2})*(?:/(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'(*+,;=:@|]+|%[\dA-F]{2})*)*(?:\?(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'(*+,;=:@/?|]+|%[\dA-F]{2})*)?(?:\#(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'(*+,;=:@/?|]+|%[\dA-F]{2})*)?";
2910 break;
2911
2912 case 'table_prefix':
2913 return '#^[a-zA-Z][a-zA-Z0-9_]*$#';
2914 break;
2915
2916 // Matches the predecing dot
2917 case 'path_remove_dot_trailing_slash':
2918 return '#^(?:(\.)?)+(?:(.+)?)+(?:([\\/\\\])$)#';
2919 break;
2920
2921 case 'semantic_version':
2922 // Regular expression to match semantic versions by http://rgxdb.com/
2923 return '/(?<=^[Vv]|^)(?:(?<major>(?:0|[1-9](?:(?:0|[1-9])+)*))[.](?<minor>(?:0|[1-9](?:(?:0|[1-9])+)*))[.](?<patch>(?:0|[1-9](?:(?:0|[1-9])+)*))(?:-(?<prerelease>(?:(?:(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?|(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?)|(?:0|[1-9](?:(?:0|[1-9])+)*))(?:[.](?:(?:(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?|(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?)|(?:0|[1-9](?:(?:0|[1-9])+)*)))*))?(?:[+](?<build>(?:(?:(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?|(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?)|(?:(?:0|[1-9])+))(?:[.](?:(?:(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?|(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?)|(?:(?:0|[1-9])+)))*))?)$/';
2924 break;
2925 }
2926
2927 return '';
2928 }
2929
2930 /**
2931 * Generate regexp for naughty words censoring
2932 * Depends on whether installed PHP version supports unicode properties
2933 *
2934 * @param string $word word template to be replaced
2935 *
2936 * @return string $preg_expr regex to use with word censor
2937 */
2938 function get_censor_preg_expression($word)
2939 {
2940 // Unescape the asterisk to simplify further conversions
2941 $word = str_replace('\*', '*', preg_quote($word, '#'));
2942
2943 // Replace asterisk(s) inside the pattern, at the start and at the end of it with regexes
2944 $word = preg_replace(array('#(?<=[\p{Nd}\p{L}_])\*+(?=[\p{Nd}\p{L}_])#iu', '#^\*+#', '#\*+$#'), array('([\x20]*?|[\p{Nd}\p{L}_-]*?)', '[\p{Nd}\p{L}_-]*?', '[\p{Nd}\p{L}_-]*?'), $word);
2945
2946 // Generate the final substitution
2947 $preg_expr = '#(?<![\p{Nd}\p{L}_-])(' . $word . ')(?![\p{Nd}\p{L}_-])#iu';
2948
2949 return $preg_expr;
2950 }
2951
2952 /**
2953 * Returns the first block of the specified IPv6 address and as many additional
2954 * ones as specified in the length parameter.
2955 * If length is zero, then an empty string is returned.
2956 * If length is greater than 3 the complete IP will be returned
2957 */
2958 function short_ipv6($ip, $length)
2959 {
2960 if ($length < 1)
2961 {
2962 return '';
2963 }
2964
2965 // Handle IPv4 embedded IPv6 addresses
2966 if (preg_match('/(?:\d{1,3}\.){3}\d{1,3}$/i', $ip))
2967 {
2968 $binary_ip = inet_pton($ip);
2969 $ip_v6 = $binary_ip ? inet_ntop($binary_ip) : $ip;
2970 $ip = $ip_v6 ?: $ip;
2971 }
2972
2973 // extend IPv6 addresses
2974 $blocks = substr_count($ip, ':') + 1;
2975 if ($blocks < 9)
2976 {
2977 $ip = str_replace('::', ':' . str_repeat('0000:', 9 - $blocks), $ip);
2978 }
2979 if ($ip[0] == ':')
2980 {
2981 $ip = '0000' . $ip;
2982 }
2983 if ($length < 4)
2984 {
2985 $ip = implode(':', array_slice(explode(':', $ip), 0, 1 + $length));
2986 }
2987
2988 return $ip;
2989 }
2990
2991 /**
2992 * Normalises an internet protocol address,
2993 * also checks whether the specified address is valid.
2994 *
2995 * IPv4 addresses are returned 'as is'.
2996 *
2997 * IPv6 addresses are normalised according to
2998 * A Recommendation for IPv6 Address Text Representation
2999 * http://tools.ietf.org/html/draft-ietf-6man-text-addr-representation-07
3000 *
3001 * @param string $address IP address
3002 *
3003 * @return mixed false if specified address is not valid,
3004 * string otherwise
3005 */
3006 function phpbb_ip_normalise(string $address)
3007 {
3008 $ip_normalised = false;
3009
3010 if (filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4))
3011 {
3012 $ip_normalised = $address;
3013 }
3014 else if (filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6))
3015 {
3016 $ip_normalised = inet_ntop(inet_pton($address));
3017
3018 // If is ipv4
3019 if (stripos($ip_normalised, '::ffff:') === 0)
3020 {
3021 $ip_normalised = substr($ip_normalised, 7);
3022 }
3023 }
3024
3025 return $ip_normalised;
3026 }
3027
3028 // Handler, header and footer
3029
3030 /**
3031 * Error and message handler, call with trigger_error if read
3032 */
3033 function msg_handler($errno, $msg_text, $errfile, $errline)
3034 {
3035 global $cache, $db, $auth, $template, $config, $user, $request;
3036 global $phpbb_root_path, $msg_title, $msg_long_text, $phpbb_log;
3037 global $phpbb_container;
3038
3039 // https://www.php.net/manual/en/language.operators.errorcontrol.php
3040 // error_reporting() return a different error code inside the error handler after php 8.0
3041 $suppresed = E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR | E_PARSE;
3042 if (PHP_VERSION_ID < 80000)
3043 {
3044 $suppresed = 0;
3045 }
3046
3047 // Do not display notices if we suppress them via @
3048 if (error_reporting() == $suppresed && $errno != E_USER_ERROR && $errno != E_USER_WARNING && $errno != E_USER_NOTICE)
3049 {
3050 return;
3051 }
3052
3053 // Message handler is stripping text. In case we need it, we are possible to define long text...
3054 if (isset($msg_long_text) && $msg_long_text && !$msg_text)
3055 {
3056 $msg_text = $msg_long_text;
3057 }
3058
3059 switch ($errno)
3060 {
3061 case E_NOTICE:
3062 case E_WARNING:
3063
3064 // Check the error reporting level and return if the error level does not match
3065 // If DEBUG is defined the default level is E_ALL
3066 if (($errno & ($phpbb_container != null && $phpbb_container->getParameter('debug.show_errors') ? E_ALL : error_reporting())) == 0)
3067 {
3068 return;
3069 }
3070
3071 if (strpos($errfile, 'cache') === false && strpos($errfile, 'template.') === false)
3072 {
3073 $errfile = phpbb_filter_root_path($errfile);
3074 $msg_text = phpbb_filter_root_path($msg_text);
3075 $error_name = ($errno === E_WARNING) ? 'PHP Warning' : 'PHP Notice';
3076 echo '<b>[phpBB Debug] ' . $error_name . '</b>: in file <b>' . $errfile . '</b> on line <b>' . $errline . '</b>: <b>' . $msg_text . '</b><br />' . "\n";
3077
3078 // we are writing an image - the user won't see the debug, so let's place it in the log
3079 if (defined('IMAGE_OUTPUT') || defined('IN_CRON'))
3080 {
3081 $phpbb_log->add('critical', $user->data['user_id'], $user->ip, 'LOG_IMAGE_GENERATION_ERROR', false, array($errfile, $errline, $msg_text));
3082 }
3083 // echo '<br /><br />BACKTRACE<br />' . get_backtrace() . '<br />' . "\n";
3084 }
3085
3086 return;
3087
3088 break;
3089
3090 case E_USER_ERROR:
3091
3092 if (!empty($user) && $user->is_setup())
3093 {
3094 $msg_text = (!empty($user->lang[$msg_text])) ? $user->lang[$msg_text] : $msg_text;
3095 $msg_title = (!isset($msg_title)) ? $user->lang['GENERAL_ERROR'] : ((!empty($user->lang[$msg_title])) ? $user->lang[$msg_title] : $msg_title);
3096
3097 $l_return_index = sprintf($user->lang['RETURN_INDEX'], '<a href="' . $phpbb_root_path . '">', '</a>');
3098 $l_notify = '';
3099
3100 if (!empty($config['board_contact']))
3101 {
3102 $l_notify = '<p>' . sprintf($user->lang['NOTIFY_ADMIN_EMAIL'], $config['board_contact']) . '</p>';
3103 }
3104 }
3105 else
3106 {
3107 $msg_title = 'General Error';
3108 $l_return_index = '<a href="' . $phpbb_root_path . '">Return to index page</a>';
3109 $l_notify = '';
3110
3111 if (!empty($config['board_contact']))
3112 {
3113 $l_notify = '<p>Please notify the board administrator or webmaster: <a href="mailto:' . $config['board_contact'] . '">' . $config['board_contact'] . '</a></p>';
3114 }
3115 }
3116
3117 $log_text = $msg_text;
3118 $backtrace = get_backtrace();
3119 if ($backtrace)
3120 {
3121 $log_text .= '<br /><br />BACKTRACE<br />' . $backtrace;
3122 }
3123
3124 if (defined('IN_INSTALL') || ($phpbb_container != null && $phpbb_container->getParameter('debug.show_errors')) || isset($auth) && $auth->acl_get('a_'))
3125 {
3126 $msg_text = $log_text;
3127
3128 // If this is defined there already was some output
3129 // So let's not break it
3130 if (defined('IN_DB_UPDATE'))
3131 {
3132 echo '<div class="errorbox">' . $msg_text . '</div>';
3133
3134 $db->sql_return_on_error(true);
3135 phpbb_end_update($cache, $config);
3136 }
3137 }
3138
3139 if ((defined('IN_CRON') || defined('IMAGE_OUTPUT')) && isset($db))
3140 {
3141 // let's avoid loops
3142 $db->sql_return_on_error(true);
3143 $phpbb_log->add('critical', $user->data['user_id'], $user->ip, 'LOG_GENERAL_ERROR', false, array($msg_title, $log_text));
3144 $db->sql_return_on_error(false);
3145 }
3146
3147 // Do not send 200 OK, but service unavailable on errors
3148 send_status_line(503, 'Service Unavailable');
3149
3150 garbage_collection();
3151
3152 // Try to not call the adm page data...
3153
3154 echo '<!DOCTYPE html>';
3155 echo '<html dir="ltr">';
3156 echo '<head>';
3157 echo '<meta charset="utf-8">';
3158 echo '<meta http-equiv="X-UA-Compatible" content="IE=edge">';
3159 echo '<title>' . $msg_title . '</title>';
3160 echo '<style type="text/css">' . "\n" . '/* <![CDATA[ */' . "\n";
3161 echo '* { margin: 0; padding: 0; } html { font-size: 100%; height: 100%; margin-bottom: 1px; background-color: #E4EDF0; } body { font-family: "Lucida Grande", Verdana, Helvetica, Arial, sans-serif; color: #536482; background: #E4EDF0; font-size: 62.5%; margin: 0; } ';
3162 echo 'a:link, a:active, a:visited { color: #006699; text-decoration: none; } a:hover { color: #DD6900; text-decoration: underline; } ';
3163 echo '#wrap { padding: 0 20px 15px 20px; min-width: 615px; } #page-header { text-align: right; height: 40px; } #page-footer { clear: both; font-size: 1em; text-align: center; } ';
3164 echo '.panel { margin: 4px 0; background-color: #FFFFFF; border: solid 1px #A9B8C2; } ';
3165 echo '#errorpage #page-header a { font-weight: bold; line-height: 6em; } #errorpage #content { padding: 10px; } #errorpage #content h1 { line-height: 1.2em; margin-bottom: 0; color: #DF075C; } ';
3166 echo '#errorpage #content div { margin-top: 20px; margin-bottom: 5px; border-bottom: 1px solid #CCCCCC; padding-bottom: 5px; color: #333333; font: bold 1.2em "Lucida Grande", Arial, Helvetica, sans-serif; text-decoration: none; line-height: 120%; text-align: left; } ';
3167 echo "\n" . '/* ]]> */' . "\n";
3168 echo '</style>';
3169 echo '</head>';
3170 echo '<body id="errorpage">';
3171 echo '<div id="wrap">';
3172 echo ' <div id="page-header">';
3173 echo ' ' . $l_return_index;
3174 echo ' </div>';
3175 echo ' <div id="acp">';
3176 echo ' <div class="panel">';
3177 echo ' <div id="content">';
3178 echo ' <h1>' . $msg_title . '</h1>';
3179
3180 echo ' <div>' . $msg_text . '</div>';
3181
3182 echo $l_notify;
3183
3184 echo ' </div>';
3185 echo ' </div>';
3186 echo ' </div>';
3187 echo ' <div id="page-footer">';
3188 echo ' Powered by <a href="https://www.phpbb.com/">phpBB</a>® Forum Software © phpBB Limited';
3189 echo ' </div>';
3190 echo '</div>';
3191 echo '</body>';
3192 echo '</html>';
3193
3194 exit_handler();
3195
3196 // On a fatal error (and E_USER_ERROR *is* fatal) we never want other scripts to continue and force an exit here.
3197 exit;
3198 break;
3199
3200 case E_USER_WARNING:
3201 case E_USER_NOTICE:
3202
3203 define('IN_ERROR_HANDLER', true);
3204
3205 if (empty($user->data))
3206 {
3207 $user->session_begin();
3208 }
3209
3210 // We re-init the auth array to get correct results on login/logout
3211 $auth->acl($user->data);
3212
3213 if (!$user->is_setup())
3214 {
3215 $user->setup();
3216 }
3217
3218 if ($msg_text == 'ERROR_NO_ATTACHMENT' || $msg_text == 'NO_FORUM' || $msg_text == 'NO_TOPIC' || $msg_text == 'NO_USER')
3219 {
3220 send_status_line(404, 'Not Found');
3221 }
3222
3223 $msg_text = (!empty($user->lang[$msg_text])) ? $user->lang[$msg_text] : $msg_text;
3224 $msg_title = (!isset($msg_title)) ? $user->lang['INFORMATION'] : ((!empty($user->lang[$msg_title])) ? $user->lang[$msg_title] : $msg_title);
3225
3226 if (!defined('HEADER_INC'))
3227 {
3228 if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
3229 {
3230 adm_page_header($msg_title);
3231 }
3232 else
3233 {
3234 page_header($msg_title);
3235 }
3236 }
3237
3238 $template->set_filenames(array(
3239 'body' => 'message_body.html')
3240 );
3241
3242 $template->assign_vars(array(
3243 'MESSAGE_TITLE' => $msg_title,
3244 'MESSAGE_TEXT' => $msg_text,
3245 'S_USER_WARNING' => ($errno == E_USER_WARNING) ? true : false,
3246 'S_USER_NOTICE' => ($errno == E_USER_NOTICE) ? true : false)
3247 );
3248
3249 if ($request->is_ajax())
3250 {
3251 global $refresh_data;
3252
3253 $json_response = new \phpbb\json_response;
3254 $json_response->send(array(
3255 'MESSAGE_TITLE' => $msg_title,
3256 'MESSAGE_TEXT' => $msg_text,
3257 'S_USER_WARNING' => ($errno == E_USER_WARNING) ? true : false,
3258 'S_USER_NOTICE' => ($errno == E_USER_NOTICE) ? true : false,
3259 'REFRESH_DATA' => (!empty($refresh_data)) ? $refresh_data : null
3260 ));
3261 }
3262
3263 // We do not want the cron script to be called on error messages
3264 define('IN_CRON', true);
3265
3266 if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
3267 {
3268 adm_page_footer();
3269 }
3270 else
3271 {
3272 page_footer();
3273 }
3274
3275 exit_handler();
3276 break;
3277
3278 // PHP4 compatibility
3279 case E_DEPRECATED:
3280 return true;
3281 break;
3282 }
3283
3284 // If we notice an error not handled here we pass this back to PHP by returning false
3285 // This may not work for all php versions
3286 return false;
3287 }
3288
3289 /**
3290 * Removes absolute path to phpBB root directory from error messages
3291 * and converts backslashes to forward slashes.
3292 *
3293 * @param string $errfile Absolute file path
3294 * (e.g. /var/www/phpbb3/phpBB/includes/functions.php)
3295 * Please note that if $errfile is outside of the phpBB root,
3296 * the root path will not be found and can not be filtered.
3297 * @return string Relative file path
3298 * (e.g. /includes/functions.php)
3299 */
3300 function phpbb_filter_root_path($errfile)
3301 {
3302 global $phpbb_filesystem;
3303
3304 static $root_path;
3305
3306 if (empty($root_path))
3307 {
3308 if ($phpbb_filesystem)
3309 {
3310 $root_path = $phpbb_filesystem->realpath(__DIR__ . '/../');
3311 }
3312 else
3313 {
3314 $filesystem = new \phpbb\filesystem\filesystem();
3315 $root_path = $filesystem->realpath(__DIR__ . '/../');
3316 }
3317 }
3318
3319 return str_replace(array($root_path, '\\'), array('[ROOT]', '/'), $errfile);
3320 }
3321
3322 /**
3323 * Queries the session table to get information about online guests
3324 * @param int $item_id Limits the search to the item with this id
3325 * @param string $item The name of the item which is stored in the session table as session_{$item}_id
3326 * @return int The number of active distinct guest sessions
3327 */
3328 function obtain_guest_count($item_id = 0, $item = 'forum')
3329 {
3330 global $db, $config;
3331
3332 if ($item_id)
3333 {
3334 $reading_sql = ' AND s.session_' . $item . '_id = ' . (int) $item_id;
3335 }
3336 else
3337 {
3338 $reading_sql = '';
3339 }
3340 $time = (time() - (intval($config['load_online_time']) * 60));
3341
3342 // Get number of online guests
3343
3344 if ($db->get_sql_layer() === 'sqlite3')
3345 {
3346 $sql = 'SELECT COUNT(session_ip) as num_guests
3347 FROM (
3348 SELECT DISTINCT s.session_ip
3349 FROM ' . SESSIONS_TABLE . ' s
3350 WHERE s.session_user_id = ' . ANONYMOUS . '
3351 AND s.session_time >= ' . ($time - ((int) ($time % 60))) .
3352 $reading_sql .
3353 ')';
3354 }
3355 else
3356 {
3357 $sql = 'SELECT COUNT(DISTINCT s.session_ip) as num_guests
3358 FROM ' . SESSIONS_TABLE . ' s
3359 WHERE s.session_user_id = ' . ANONYMOUS . '
3360 AND s.session_time >= ' . ($time - ((int) ($time % 60))) .
3361 $reading_sql;
3362 }
3363 $result = $db->sql_query($sql);
3364 $guests_online = (int) $db->sql_fetchfield('num_guests');
3365 $db->sql_freeresult($result);
3366
3367 return $guests_online;
3368 }
3369
3370 /**
3371 * Queries the session table to get information about online users
3372 * @param int $item_id Limits the search to the item with this id
3373 * @param string $item The name of the item which is stored in the session table as session_{$item}_id
3374 * @return array An array containing the ids of online, hidden and visible users, as well as statistical info
3375 */
3376 function obtain_users_online($item_id = 0, $item = 'forum')
3377 {
3378 global $db, $config;
3379
3380 $reading_sql = '';
3381 if ($item_id !== 0)
3382 {
3383 $reading_sql = ' AND s.session_' . $item . '_id = ' . (int) $item_id;
3384 }
3385
3386 $online_users = array(
3387 'online_users' => array(),
3388 'hidden_users' => array(),
3389 'total_online' => 0,
3390 'visible_online' => 0,
3391 'hidden_online' => 0,
3392 'guests_online' => 0,
3393 );
3394
3395 if ($config['load_online_guests'])
3396 {
3397 $online_users['guests_online'] = obtain_guest_count($item_id, $item);
3398 }
3399
3400 // a little discrete magic to cache this for 30 seconds
3401 $time = (time() - (intval($config['load_online_time']) * 60));
3402
3403 $sql = 'SELECT s.session_user_id, s.session_ip, s.session_viewonline
3404 FROM ' . SESSIONS_TABLE . ' s
3405 WHERE s.session_time >= ' . ($time - ((int) ($time % 30))) .
3406 $reading_sql .
3407 ' AND s.session_user_id <> ' . ANONYMOUS;
3408 $result = $db->sql_query($sql);
3409
3410 while ($row = $db->sql_fetchrow($result))
3411 {
3412 // Skip multiple sessions for one user
3413 if (!isset($online_users['online_users'][$row['session_user_id']]))
3414 {
3415 $online_users['online_users'][$row['session_user_id']] = (int) $row['session_user_id'];
3416 if ($row['session_viewonline'])
3417 {
3418 $online_users['visible_online']++;
3419 }
3420 else
3421 {
3422 $online_users['hidden_users'][$row['session_user_id']] = (int) $row['session_user_id'];
3423 $online_users['hidden_online']++;
3424 }
3425 }
3426 }
3427 $online_users['total_online'] = $online_users['guests_online'] + $online_users['visible_online'] + $online_users['hidden_online'];
3428 $db->sql_freeresult($result);
3429
3430 return $online_users;
3431 }
3432
3433 /**
3434 * Uses the result of obtain_users_online to generate a localized, readable representation.
3435 * @param mixed $online_users result of obtain_users_online - array with user_id lists for total, hidden and visible users, and statistics
3436 * @param int $item_id Indicate that the data is limited to one item and not global
3437 * @param string $item The name of the item which is stored in the session table as session_{$item}_id
3438 * @return array An array containing the string for output to the template
3439 */
3440 function obtain_users_online_string($online_users, $item_id = 0, $item = 'forum')
3441 {
3442 global $config, $db, $user, $auth, $phpbb_dispatcher;
3443
3444 $user_online_link = $rowset = array();
3445 // Need caps version of $item for language-strings
3446 $item_caps = strtoupper($item);
3447
3448 if (count($online_users['online_users']))
3449 {
3450 $sql_ary = array(
3451 'SELECT' => 'u.username, u.username_clean, u.user_id, u.user_type, u.user_allow_viewonline, u.user_colour',
3452 'FROM' => array(
3453 USERS_TABLE => 'u',
3454 ),
3455 'WHERE' => $db->sql_in_set('u.user_id', $online_users['online_users']),
3456 'ORDER_BY' => 'u.username_clean ASC',
3457 );
3458
3459 /**
3460 * Modify SQL query to obtain online users data
3461 *
3462 * @event core.obtain_users_online_string_sql
3463 * @var array online_users Array with online users data
3464 * from obtain_users_online()
3465 * @var int item_id Restrict online users to item id
3466 * @var string item Restrict online users to a certain
3467 * session item, e.g. forum for
3468 * session_forum_id
3469 * @var array sql_ary SQL query array to obtain users online data
3470 * @since 3.1.4-RC1
3471 * @changed 3.1.7-RC1 Change sql query into array and adjust var accordingly. Allows extension authors the ability to adjust the sql_ary.
3472 */
3473 $vars = array('online_users', 'item_id', 'item', 'sql_ary');
3474 extract($phpbb_dispatcher->trigger_event('core.obtain_users_online_string_sql', compact($vars)));
3475
3476 $result = $db->sql_query($db->sql_build_query('SELECT', $sql_ary));
3477 $rowset = $db->sql_fetchrowset($result);
3478 $db->sql_freeresult($result);
3479
3480 foreach ($rowset as $row)
3481 {
3482 // User is logged in and therefore not a guest
3483 if ($row['user_id'] != ANONYMOUS)
3484 {
3485 if (isset($online_users['hidden_users'][$row['user_id']]))
3486 {
3487 $row['username'] = '<em>' . $row['username'] . '</em>';
3488 }
3489
3490 if (!isset($online_users['hidden_users'][$row['user_id']]) || $auth->acl_get('u_viewonline') || $row['user_id'] === $user->data['user_id'])
3491 {
3492 $user_online_link[$row['user_id']] = get_username_string(($row['user_type'] <> USER_IGNORE) ? 'full' : 'no_profile', $row['user_id'], $row['username'], $row['user_colour']);
3493 }
3494 }
3495 }
3496 }
3497
3498 /**
3499 * Modify online userlist data
3500 *
3501 * @event core.obtain_users_online_string_before_modify
3502 * @var array online_users Array with online users data
3503 * from obtain_users_online()
3504 * @var int item_id Restrict online users to item id
3505 * @var string item Restrict online users to a certain
3506 * session item, e.g. forum for
3507 * session_forum_id
3508 * @var array rowset Array with online users data
3509 * @var array user_online_link Array with online users items (usernames)
3510 * @since 3.1.10-RC1
3511 */
3512 $vars = array(
3513 'online_users',
3514 'item_id',
3515 'item',
3516 'rowset',
3517 'user_online_link',
3518 );
3519 extract($phpbb_dispatcher->trigger_event('core.obtain_users_online_string_before_modify', compact($vars)));
3520
3521 $online_userlist = implode(', ', $user_online_link);
3522
3523 if (!$online_userlist)
3524 {
3525 $online_userlist = $user->lang['NO_ONLINE_USERS'];
3526 }
3527
3528 if ($item_id === 0)
3529 {
3530 $online_userlist = $user->lang['REGISTERED_USERS'] . ' ' . $online_userlist;
3531 }
3532 else if ($config['load_online_guests'])
3533 {
3534 $online_userlist = $user->lang('BROWSING_' . $item_caps . '_GUESTS', $online_users['guests_online'], $online_userlist);
3535 }
3536 else
3537 {
3538 $online_userlist = sprintf($user->lang['BROWSING_' . $item_caps], $online_userlist);
3539 }
3540 // Build online listing
3541 $visible_online = $user->lang('REG_USERS_TOTAL', (int) $online_users['visible_online']);
3542 $hidden_online = $user->lang('HIDDEN_USERS_TOTAL', (int) $online_users['hidden_online']);
3543
3544 if ($config['load_online_guests'])
3545 {
3546 $guests_online = $user->lang('GUEST_USERS_TOTAL', (int) $online_users['guests_online']);
3547 $l_online_users = $user->lang('ONLINE_USERS_TOTAL_GUESTS', (int) $online_users['total_online'], $visible_online, $hidden_online, $guests_online);
3548 }
3549 else
3550 {
3551 $l_online_users = $user->lang('ONLINE_USERS_TOTAL', (int) $online_users['total_online'], $visible_online, $hidden_online);
3552 }
3553
3554 /**
3555 * Modify online userlist data
3556 *
3557 * @event core.obtain_users_online_string_modify
3558 * @var array online_users Array with online users data
3559 * from obtain_users_online()
3560 * @var int item_id Restrict online users to item id
3561 * @var string item Restrict online users to a certain
3562 * session item, e.g. forum for
3563 * session_forum_id
3564 * @var array rowset Array with online users data
3565 * @var array user_online_link Array with online users items (usernames)
3566 * @var string online_userlist String containing users online list
3567 * @var string l_online_users String with total online users count info
3568 * @since 3.1.4-RC1
3569 */
3570 $vars = array(
3571 'online_users',
3572 'item_id',
3573 'item',
3574 'rowset',
3575 'user_online_link',
3576 'online_userlist',
3577 'l_online_users',
3578 );
3579 extract($phpbb_dispatcher->trigger_event('core.obtain_users_online_string_modify', compact($vars)));
3580
3581 return array(
3582 'online_userlist' => $online_userlist,
3583 'l_online_users' => $l_online_users,
3584 );
3585 }
3586
3587 /**
3588 * Get option bitfield from custom data
3589 *
3590 * @param int $bit The bit/value to get
3591 * @param int $data Current bitfield to check
3592 * @return bool Returns true if value of constant is set in bitfield, else false
3593 */
3594 function phpbb_optionget($bit, $data)
3595 {
3596 return ($data & 1 << (int) $bit) ? true : false;
3597 }
3598
3599 /**
3600 * Set option bitfield
3601 *
3602 * @param int $bit The bit/value to set/unset
3603 * @param bool $set True if option should be set, false if option should be unset.
3604 * @param int $data Current bitfield to change
3605 *
3606 * @return int The new bitfield
3607 */
3608 function phpbb_optionset($bit, $set, $data)
3609 {
3610 if ($set && !($data & 1 << $bit))
3611 {
3612 $data += 1 << $bit;
3613 }
3614 else if (!$set && ($data & 1 << $bit))
3615 {
3616 $data -= 1 << $bit;
3617 }
3618
3619 return $data;
3620 }
3621
3622
3623 /**
3624 * Escapes and quotes a string for use as an HTML/XML attribute value.
3625 *
3626 * This is a port of Python xml.sax.saxutils quoteattr.
3627 *
3628 * The function will attempt to choose a quote character in such a way as to
3629 * avoid escaping quotes in the string. If this is not possible the string will
3630 * be wrapped in double quotes and double quotes will be escaped.
3631 *
3632 * @param string $data The string to be escaped
3633 * @param array $entities Associative array of additional entities to be escaped
3634 * @return string Escaped and quoted string
3635 */
3636 function phpbb_quoteattr($data, $entities = null)
3637 {
3638 $data = str_replace('&', '&', $data);
3639 $data = str_replace('>', '>', $data);
3640 $data = str_replace('<', '<', $data);
3641
3642 $data = str_replace("\n", ' ', $data);
3643 $data = str_replace("\r", ' ', $data);
3644 $data = str_replace("\t", '	', $data);
3645
3646 if (!empty($entities))
3647 {
3648 $data = str_replace(array_keys($entities), array_values($entities), $data);
3649 }
3650
3651 if (strpos($data, '"') !== false)
3652 {
3653 if (strpos($data, "'") !== false)
3654 {
3655 $data = '"' . str_replace('"', '"', $data) . '"';
3656 }
3657 else
3658 {
3659 $data = "'" . $data . "'";
3660 }
3661 }
3662 else
3663 {
3664 $data = '"' . $data . '"';
3665 }
3666
3667 return $data;
3668 }
3669
3670 /**
3671 * Get user avatar
3672 *
3673 * @param array $user_row Row from the users table
3674 * @param string $alt Optional language string for alt tag within image, can be a language key or text
3675 * @param bool $ignore_config Ignores the config-setting, to be still able to view the avatar in the UCP
3676 * @param bool $lazy If true, will be lazy loaded (requires JS)
3677 *
3678 * @return string Avatar html
3679 */
3680 function phpbb_get_user_avatar($user_row, $alt = 'USER_AVATAR', $ignore_config = false, $lazy = false)
3681 {
3682 $row = \phpbb\avatar\manager::clean_row($user_row, 'user');
3683 return phpbb_get_avatar($row, $alt, $ignore_config, $lazy);
3684 }
3685
3686 /**
3687 * Get group avatar
3688 *
3689 * @param array $group_row Row from the groups table
3690 * @param string $alt Optional language string for alt tag within image, can be a language key or text
3691 * @param bool $ignore_config Ignores the config-setting, to be still able to view the avatar in the UCP
3692 * @param bool $lazy If true, will be lazy loaded (requires JS)
3693 *
3694 * @return string Avatar html
3695 */
3696 function phpbb_get_group_avatar($group_row, $alt = 'GROUP_AVATAR', $ignore_config = false, $lazy = false)
3697 {
3698 $row = \phpbb\avatar\manager::clean_row($group_row, 'group');
3699 return phpbb_get_avatar($row, $alt, $ignore_config, $lazy);
3700 }
3701
3702 /**
3703 * Get avatar
3704 *
3705 * @param array $row Row cleaned by \phpbb\avatar\manager::clean_row
3706 * @param string $alt Optional language string for alt tag within image, can be a language key or text
3707 * @param bool $ignore_config Ignores the config-setting, to be still able to view the avatar in the UCP
3708 * @param bool $lazy If true, will be lazy loaded (requires JS)
3709 *
3710 * @return string Avatar html
3711 */
3712 function phpbb_get_avatar($row, $alt, $ignore_config = false, $lazy = false)
3713 {
3714 global $user, $config;
3715 global $phpbb_container, $phpbb_dispatcher;
3716
3717 if (!$config['allow_avatar'] && !$ignore_config)
3718 {
3719 return '';
3720 }
3721
3722 $avatar_data = array(
3723 'src' => $row['avatar'],
3724 'width' => $row['avatar_width'],
3725 'height' => $row['avatar_height'],
3726 );
3727
3728 /* @var $phpbb_avatar_manager \phpbb\avatar\manager */
3729 $phpbb_avatar_manager = $phpbb_container->get('avatar.manager');
3730 $driver = $phpbb_avatar_manager->get_driver($row['avatar_type'], !$ignore_config);
3731 $html = '';
3732
3733 if ($driver)
3734 {
3735 $html = $driver->get_custom_html($user, $row, $alt);
3736 $avatar_data = $driver->get_data($row);
3737 }
3738 else
3739 {
3740 $avatar_data['src'] = '';
3741 }
3742
3743 if (empty($html) && !empty($avatar_data['src']))
3744 {
3745 if ($lazy)
3746 {
3747 // This path is sent with the base template paths in the assign_vars()
3748 // call below. We need to correct it in case we are accessing from a
3749 // controller because the web paths will be incorrect otherwise.
3750 $phpbb_path_helper = $phpbb_container->get('path_helper');
3751 $web_path = $phpbb_path_helper->get_web_root_path();
3752
3753 $theme = "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/theme';
3754
3755 $src = 'src="' . $theme . '/images/no_avatar.gif" data-src="' . $avatar_data['src'] . '"';
3756 }
3757 else
3758 {
3759 $src = 'src="' . $avatar_data['src'] . '"';
3760 }
3761
3762 $html = '<img class="avatar" ' . $src . ' ' .
3763 ($avatar_data['width'] ? ('width="' . $avatar_data['width'] . '" ') : '') .
3764 ($avatar_data['height'] ? ('height="' . $avatar_data['height'] . '" ') : '') .
3765 'alt="' . ((!empty($user->lang[$alt])) ? $user->lang[$alt] : $alt) . '" />';
3766 }
3767
3768 /**
3769 * Event to modify HTML <img> tag of avatar
3770 *
3771 * @event core.get_avatar_after
3772 * @var array row Row cleaned by \phpbb\avatar\manager::clean_row
3773 * @var string alt Optional language string for alt tag within image, can be a language key or text
3774 * @var bool ignore_config Ignores the config-setting, to be still able to view the avatar in the UCP
3775 * @var array avatar_data The HTML attributes for avatar <img> tag
3776 * @var string html The HTML <img> tag of generated avatar
3777 * @since 3.1.6-RC1
3778 */
3779 $vars = array('row', 'alt', 'ignore_config', 'avatar_data', 'html');
3780 extract($phpbb_dispatcher->trigger_event('core.get_avatar_after', compact($vars)));
3781
3782 return $html;
3783 }
3784
3785 /**
3786 * Generate page header
3787 */
3788 function page_header($page_title = '', $display_online_list = false, $item_id = 0, $item = 'forum', $send_headers = true)
3789 {
3790 global $db, $config, $template, $SID, $_SID, $_EXTRA_URL, $user, $auth, $phpEx, $phpbb_root_path;
3791 global $phpbb_dispatcher, $request, $phpbb_container, $phpbb_admin_path;
3792
3793 if (defined('HEADER_INC'))
3794 {
3795 return;
3796 }
3797
3798 define('HEADER_INC', true);
3799
3800 // A listener can set this variable to `true` when it overrides this function
3801 $page_header_override = false;
3802
3803 /**
3804 * Execute code and/or overwrite page_header()
3805 *
3806 * @event core.page_header
3807 * @var string page_title Page title
3808 * @var bool display_online_list Do we display online users list
3809 * @var string item Restrict online users to a certain
3810 * session item, e.g. forum for
3811 * session_forum_id
3812 * @var int item_id Restrict online users to item id
3813 * @var bool page_header_override Shall we return instead of running
3814 * the rest of page_header()
3815 * @since 3.1.0-a1
3816 */
3817 $vars = array('page_title', 'display_online_list', 'item_id', 'item', 'page_header_override');
3818 extract($phpbb_dispatcher->trigger_event('core.page_header', compact($vars)));
3819
3820 if ($page_header_override)
3821 {
3822 return;
3823 }
3824
3825 // gzip_compression
3826 if ($config['gzip_compress'])
3827 {
3828 // to avoid partially compressed output resulting in blank pages in
3829 // the browser or error messages, compression is disabled in a few cases:
3830 //
3831 // 1) if headers have already been sent, this indicates plaintext output
3832 // has been started so further content must not be compressed
3833 // 2) the length of the current output buffer is non-zero. This means
3834 // there is already some uncompressed content in this output buffer
3835 // so further output must not be compressed
3836 // 3) if more than one level of output buffering is used because we
3837 // cannot test all output buffer level content lengths. One level
3838 // could be caused by php.ini output_buffering. Anything
3839 // beyond that is manual, so the code wrapping phpBB in output buffering
3840 // can easily compress the output itself.
3841 //
3842 if (@extension_loaded('zlib') && !headers_sent() && ob_get_level() <= 1 && ob_get_length() == 0)
3843 {
3844 ob_start('ob_gzhandler');
3845 }
3846 }
3847
3848 $user->update_session_infos();
3849
3850 // Generate logged in/logged out status
3851 if ($user->data['user_id'] != ANONYMOUS)
3852 {
3853 $u_login_logout = append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=logout', true, $user->session_id);
3854 $l_login_logout = $user->lang['LOGOUT'];
3855 }
3856 else
3857 {
3858 $redirect = $request->variable('redirect', rawurlencode($user->page['page']));
3859 $u_login_logout = append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=login&redirect=' . $redirect);
3860 $l_login_logout = $user->lang['LOGIN'];
3861 }
3862
3863 // Last visit date/time
3864 $s_last_visit = ($user->data['user_id'] != ANONYMOUS) ? $user->format_date($user->data['session_last_visit']) : '';
3865
3866 // Get users online list ... if required
3867 $l_online_users = $online_userlist = $l_online_record = $l_online_time = '';
3868
3869 if ($config['load_online'] && $config['load_online_time'] && $display_online_list)
3870 {
3871 /**
3872 * Load online data:
3873 * For obtaining another session column use $item and $item_id in the function-parameter, whereby the column is session_{$item}_id.
3874 */
3875 $item_id = max($item_id, 0);
3876
3877 $online_users = obtain_users_online($item_id, $item);
3878 $user_online_strings = obtain_users_online_string($online_users, $item_id, $item);
3879
3880 $l_online_users = $user_online_strings['l_online_users'];
3881 $online_userlist = $user_online_strings['online_userlist'];
3882 $total_online_users = $online_users['total_online'];
3883
3884 if ($total_online_users > $config['record_online_users'])
3885 {
3886 $config->set('record_online_users', $total_online_users, false);
3887 $config->set('record_online_date', time(), false);
3888 }
3889
3890 $l_online_record = $user->lang('RECORD_ONLINE_USERS', (int) $config['record_online_users'], $user->format_date($config['record_online_date'], false, true));
3891
3892 $l_online_time = $user->lang('VIEW_ONLINE_TIMES', (int) $config['load_online_time']);
3893 }
3894
3895 $s_privmsg_new = false;
3896
3897 // Check for new private messages if user is logged in
3898 if (!empty($user->data['is_registered']))
3899 {
3900 if ($user->data['user_new_privmsg'])
3901 {
3902 if (!$user->data['user_last_privmsg'] || $user->data['user_last_privmsg'] > $user->data['session_last_visit'])
3903 {
3904 $sql = 'UPDATE ' . USERS_TABLE . '
3905 SET user_last_privmsg = ' . $user->data['session_last_visit'] . '
3906 WHERE user_id = ' . $user->data['user_id'];
3907 $db->sql_query($sql);
3908
3909 $s_privmsg_new = true;
3910 }
3911 else
3912 {
3913 $s_privmsg_new = false;
3914 }
3915 }
3916 else
3917 {
3918 $s_privmsg_new = false;
3919 }
3920 }
3921
3922 // Negative forum and topic IDs are not allowed
3923 $forum_id = max(0, $request->variable('f', 0));
3924 $topic_id = max(0, $request->variable('t', 0));
3925
3926 $s_feed_news = false;
3927
3928 // Get option for news
3929 if ($config['feed_enable'])
3930 {
3931 $sql = 'SELECT forum_id
3932 FROM ' . FORUMS_TABLE . '
3933 WHERE ' . $db->sql_bit_and('forum_options', FORUM_OPTION_FEED_NEWS, '<> 0');
3934 $result = $db->sql_query_limit($sql, 1, 0, 600);
3935 $s_feed_news = (int) $db->sql_fetchfield('forum_id');
3936 $db->sql_freeresult($result);
3937 }
3938
3939 // This path is sent with the base template paths in the assign_vars()
3940 // call below. We need to correct it in case we are accessing from a
3941 // controller because the web paths will be incorrect otherwise.
3942 /* @var $phpbb_path_helper \phpbb\path_helper */
3943 $phpbb_path_helper = $phpbb_container->get('path_helper');
3944 $web_path = $phpbb_path_helper->get_web_root_path();
3945
3946 // Send a proper content-language to the output
3947 $user_lang = $user->lang['USER_LANG'];
3948 if (strpos($user_lang, '-x-') !== false)
3949 {
3950 $user_lang = substr($user_lang, 0, strpos($user_lang, '-x-'));
3951 }
3952
3953 $s_search_hidden_fields = array();
3954 if ($_SID)
3955 {
3956 $s_search_hidden_fields['sid'] = $_SID;
3957 }
3958
3959 if (!empty($_EXTRA_URL))
3960 {
3961 foreach ($_EXTRA_URL as $url_param)
3962 {
3963 $url_param = explode('=', $url_param, 2);
3964 $s_search_hidden_fields[$url_param[0]] = $url_param[1];
3965 }
3966 }
3967
3968 $dt = $user->create_datetime();
3969 $timezone_offset = $user->lang(array('timezones', 'UTC_OFFSET'), phpbb_format_timezone_offset($dt->getOffset()));
3970 $timezone_name = $user->timezone->getName();
3971 if (isset($user->lang['timezones'][$timezone_name]))
3972 {
3973 $timezone_name = $user->lang['timezones'][$timezone_name];
3974 }
3975
3976 // Output the notifications
3977 $notifications = false;
3978 if ($config['load_notifications'] && $config['allow_board_notifications'] && $user->data['user_id'] != ANONYMOUS && $user->data['user_type'] != USER_IGNORE)
3979 {
3980 /* @var $phpbb_notifications \phpbb\notification\manager */
3981 $phpbb_notifications = $phpbb_container->get('notification_manager');
3982
3983 $notifications = $phpbb_notifications->load_notifications('notification.method.board', array(
3984 'all_unread' => true,
3985 'limit' => 5,
3986 ));
3987
3988 foreach ($notifications['notifications'] as $notification)
3989 {
3990 $template->assign_block_vars('notifications', $notification->prepare_for_display());
3991 }
3992 }
3993
3994 /** @var \phpbb\controller\helper $controller_helper */
3995 $controller_helper = $phpbb_container->get('controller.helper');
3996 $notification_mark_hash = generate_link_hash('mark_all_notifications_read');
3997
3998 $s_login_redirect = build_hidden_fields(array('redirect' => $phpbb_path_helper->remove_web_root_path(build_url())));
3999
4000 // Add form token for login box, in case page is presenting a login form.
4001 add_form_key('login', '_LOGIN');
4002
4003 /**
4004 * Workaround for missing template variable in pre phpBB 3.2.6 styles.
4005 * @deprecated 3.2.7 (To be removed: 4.0.0-a1)
4006 */
4007 $form_token_login = $template->retrieve_var('S_FORM_TOKEN_LOGIN');
4008 if (!empty($form_token_login))
4009 {
4010 $s_login_redirect .= $form_token_login;
4011 // Remove S_FORM_TOKEN_LOGIN as it's already appended to S_LOGIN_REDIRECT
4012 $template->assign_var('S_FORM_TOKEN_LOGIN', '');
4013 }
4014
4015 // The following assigns all _common_ variables that may be used at any point in a template.
4016 $template->assign_vars(array(
4017 'SITENAME' => $config['sitename'],
4018 'SITE_DESCRIPTION' => $config['site_desc'],
4019 'PAGE_TITLE' => $page_title,
4020 'SCRIPT_NAME' => str_replace('.' . $phpEx, '', $user->page['page_name']),
4021 'LAST_VISIT_DATE' => sprintf($user->lang['YOU_LAST_VISIT'], $s_last_visit),
4022 'LAST_VISIT_YOU' => $s_last_visit,
4023 'CURRENT_TIME' => sprintf($user->lang['CURRENT_TIME'], $user->format_date(time(), false, true)),
4024 'TOTAL_USERS_ONLINE' => $l_online_users,
4025 'LOGGED_IN_USER_LIST' => $online_userlist,
4026 'RECORD_USERS' => $l_online_record,
4027
4028 'PRIVATE_MESSAGE_COUNT' => (!empty($user->data['user_unread_privmsg'])) ? $user->data['user_unread_privmsg'] : 0,
4029 'CURRENT_USER_AVATAR' => phpbb_get_user_avatar($user->data),
4030 'CURRENT_USERNAME_SIMPLE' => get_username_string('no_profile', $user->data['user_id'], $user->data['username'], $user->data['user_colour']),
4031 'CURRENT_USERNAME_FULL' => get_username_string('full', $user->data['user_id'], $user->data['username'], $user->data['user_colour']),
4032 'UNREAD_NOTIFICATIONS_COUNT' => ($notifications !== false) ? $notifications['unread_count'] : '',
4033 'NOTIFICATIONS_COUNT' => ($notifications !== false) ? $notifications['unread_count'] : '',
4034 'U_VIEW_ALL_NOTIFICATIONS' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=ucp_notifications'),
4035 'U_MARK_ALL_NOTIFICATIONS' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=ucp_notifications&mode=notification_list&mark=all&token=' . $notification_mark_hash),
4036 'U_NOTIFICATION_SETTINGS' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=ucp_notifications&mode=notification_options'),
4037 'S_NOTIFICATIONS_DISPLAY' => $config['load_notifications'] && $config['allow_board_notifications'],
4038
4039 'S_USER_NEW_PRIVMSG' => $user->data['user_new_privmsg'],
4040 'S_USER_UNREAD_PRIVMSG' => $user->data['user_unread_privmsg'],
4041 'S_USER_NEW' => $user->data['user_new'],
4042
4043 'SID' => $SID,
4044 '_SID' => $_SID,
4045 'SESSION_ID' => $user->session_id,
4046 'ROOT_PATH' => $web_path,
4047 'BOARD_URL' => generate_board_url() . '/',
4048
4049 'L_LOGIN_LOGOUT' => $l_login_logout,
4050 'L_INDEX' => ($config['board_index_text'] !== '') ? $config['board_index_text'] : $user->lang['FORUM_INDEX'],
4051 'L_SITE_HOME' => ($config['site_home_text'] !== '') ? $config['site_home_text'] : $user->lang['HOME'],
4052 'L_ONLINE_EXPLAIN' => $l_online_time,
4053
4054 'U_PRIVATEMSGS' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&folder=inbox'),
4055 'U_RETURN_INBOX' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&folder=inbox'),
4056 'U_MEMBERLIST' => append_sid("{$phpbb_root_path}memberlist.$phpEx"),
4057 'U_VIEWONLINE' => ($auth->acl_gets('u_viewprofile', 'a_user', 'a_useradd', 'a_userdel')) ? append_sid("{$phpbb_root_path}viewonline.$phpEx") : '',
4058 'U_LOGIN_LOGOUT' => $u_login_logout,
4059 'U_INDEX' => append_sid("{$phpbb_root_path}index.$phpEx"),
4060 'U_SEARCH' => append_sid("{$phpbb_root_path}search.$phpEx"),
4061 'U_SITE_HOME' => $config['site_home_url'],
4062 'U_REGISTER' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=register'),
4063 'U_PROFILE' => append_sid("{$phpbb_root_path}ucp.$phpEx"),
4064 'U_USER_PROFILE' => get_username_string('profile', $user->data['user_id'], $user->data['username'], $user->data['user_colour']),
4065 'U_MODCP' => append_sid("{$phpbb_root_path}mcp.$phpEx", false, true, $user->session_id),
4066 'U_FAQ' => $controller_helper->route('phpbb_help_faq_controller'),
4067 'U_SEARCH_SELF' => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=egosearch'),
4068 'U_SEARCH_NEW' => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=newposts'),
4069 'U_SEARCH_UNANSWERED' => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=unanswered'),
4070 'U_SEARCH_UNREAD' => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=unreadposts'),
4071 'U_SEARCH_ACTIVE_TOPICS'=> append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=active_topics'),
4072 'U_DELETE_COOKIES' => $controller_helper->route('phpbb_ucp_delete_cookies_controller'),
4073 'U_CONTACT_US' => ($config['contact_admin_form_enable'] && $config['email_enable']) ? append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=contactadmin') : '',
4074 'U_TEAM' => (!$auth->acl_get('u_viewprofile')) ? '' : append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=team'),
4075 'U_TERMS_USE' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=terms'),
4076 'U_PRIVACY' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy'),
4077 'UA_PRIVACY' => addslashes(append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy')),
4078 'U_RESTORE_PERMISSIONS' => ($user->data['user_perm_from'] && $auth->acl_get('a_switchperm')) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=restore_perm') : '',
4079 'U_FEED' => $controller_helper->route('phpbb_feed_index'),
4080
4081 'S_USER_LOGGED_IN' => ($user->data['user_id'] != ANONYMOUS) ? true : false,
4082 'S_AUTOLOGIN_ENABLED' => ($config['allow_autologin']) ? true : false,
4083 'S_BOARD_DISABLED' => ($config['board_disable']) ? true : false,
4084 'S_REGISTERED_USER' => (!empty($user->data['is_registered'])) ? true : false,
4085 'S_IS_BOT' => (!empty($user->data['is_bot'])) ? true : false,
4086 'S_USER_LANG' => $user_lang,
4087 'S_USER_BROWSER' => (isset($user->data['session_browser'])) ? $user->data['session_browser'] : $user->lang['UNKNOWN_BROWSER'],
4088 'S_USERNAME' => $user->data['username'],
4089 'S_CONTENT_DIRECTION' => $user->lang['DIRECTION'],
4090 'S_CONTENT_FLOW_BEGIN' => ($user->lang['DIRECTION'] == 'ltr') ? 'left' : 'right',
4091 'S_CONTENT_FLOW_END' => ($user->lang['DIRECTION'] == 'ltr') ? 'right' : 'left',
4092 'S_CONTENT_ENCODING' => 'UTF-8',
4093 'S_TIMEZONE' => sprintf($user->lang['ALL_TIMES'], $timezone_offset, $timezone_name),
4094 'S_DISPLAY_ONLINE_LIST' => ($l_online_time) ? 1 : 0,
4095 'S_DISPLAY_SEARCH' => (!$config['load_search']) ? 0 : (isset($auth) ? ($auth->acl_get('u_search') && $auth->acl_getf_global('f_search')) : 1),
4096 'S_DISPLAY_PM' => ($config['allow_privmsg'] && !empty($user->data['is_registered']) && ($auth->acl_get('u_readpm') || $auth->acl_get('u_sendpm'))) ? true : false,
4097 'S_DISPLAY_MEMBERLIST' => (isset($auth)) ? $auth->acl_get('u_viewprofile') : 0,
4098 'S_NEW_PM' => ($s_privmsg_new) ? 1 : 0,
4099 'S_REGISTER_ENABLED' => ($config['require_activation'] != USER_ACTIVATION_DISABLE) ? true : false,
4100 'S_FORUM_ID' => $forum_id,
4101 'S_TOPIC_ID' => $topic_id,
4102
4103 'S_LOGIN_ACTION' => ((!defined('ADMIN_START')) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=login') : append_sid("{$phpbb_admin_path}index.$phpEx", false, true, $user->session_id)),
4104 'S_LOGIN_REDIRECT' => $s_login_redirect,
4105
4106 'S_ENABLE_FEEDS' => ($config['feed_enable']) ? true : false,
4107 'S_ENABLE_FEEDS_OVERALL' => ($config['feed_overall']) ? true : false,
4108 'S_ENABLE_FEEDS_FORUMS' => ($config['feed_overall_forums']) ? true : false,
4109 'S_ENABLE_FEEDS_TOPICS' => ($config['feed_topics_new']) ? true : false,
4110 'S_ENABLE_FEEDS_TOPICS_ACTIVE' => ($config['feed_topics_active']) ? true : false,
4111 'S_ENABLE_FEEDS_NEWS' => ($s_feed_news) ? true : false,
4112
4113 'S_LOAD_UNREADS' => (bool) $config['load_unreads_search'] && ($config['load_anon_lastread'] || !empty($user->data['is_registered'])),
4114
4115 'S_SEARCH_HIDDEN_FIELDS' => build_hidden_fields($s_search_hidden_fields),
4116
4117 'T_ASSETS_VERSION' => $config['assets_version'],
4118 'T_ASSETS_PATH' => "{$web_path}assets",
4119 'T_THEME_PATH' => "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/theme',
4120 'T_TEMPLATE_PATH' => "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/template',
4121 'T_SUPER_TEMPLATE_PATH' => "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/template',
4122 'T_IMAGES_PATH' => "{$web_path}images/",
4123 'T_SMILIES_PATH' => "{$web_path}{$config['smilies_path']}/",
4124 'T_AVATAR_PATH' => "{$web_path}{$config['avatar_path']}/",
4125 'T_AVATAR_GALLERY_PATH' => "{$web_path}{$config['avatar_gallery_path']}/",
4126 'T_ICONS_PATH' => "{$web_path}{$config['icons_path']}/",
4127 'T_RANKS_PATH' => "{$web_path}{$config['ranks_path']}/",
4128 'T_UPLOAD_PATH' => "{$web_path}{$config['upload_path']}/",
4129 'T_STYLESHEET_LINK' => "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/theme/stylesheet.css?assets_version=' . $config['assets_version'],
4130 'T_STYLESHEET_LANG_LINK'=> "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/theme/' . $user->lang_name . '/stylesheet.css?assets_version=' . $config['assets_version'],
4131
4132 'T_FONT_AWESOME_LINK' => !empty($config['allow_cdn']) && !empty($config['load_font_awesome_url']) ? $config['load_font_awesome_url'] : "{$web_path}assets/css/font-awesome.min.css?assets_version=" . $config['assets_version'],
4133
4134 'T_JQUERY_LINK' => !empty($config['allow_cdn']) && !empty($config['load_jquery_url']) ? $config['load_jquery_url'] : "{$web_path}assets/javascript/jquery-3.7.1.min.js?assets_version=" . $config['assets_version'],
4135 'S_ALLOW_CDN' => !empty($config['allow_cdn']),
4136 'S_COOKIE_NOTICE' => !empty($config['cookie_notice']),
4137
4138 'T_THEME_NAME' => rawurlencode($user->style['style_path']),
4139 'T_THEME_LANG_NAME' => $user->lang_name,
4140 'T_TEMPLATE_NAME' => $user->style['style_path'],
4141 'T_SUPER_TEMPLATE_NAME' => rawurlencode((isset($user->style['style_parent_tree']) && $user->style['style_parent_tree']) ? $user->style['style_parent_tree'] : $user->style['style_path']),
4142 'T_IMAGES' => 'images',
4143 'T_SMILIES' => $config['smilies_path'],
4144 'T_AVATAR' => $config['avatar_path'],
4145 'T_AVATAR_GALLERY' => $config['avatar_gallery_path'],
4146 'T_ICONS' => $config['icons_path'],
4147 'T_RANKS' => $config['ranks_path'],
4148 'T_UPLOAD' => $config['upload_path'],
4149
4150 'SITE_LOGO_IMG' => $user->img('site_logo'),
4151 ));
4152
4153 $http_headers = array();
4154
4155 if ($send_headers)
4156 {
4157 // An array of http headers that phpBB will set. The following event may override these.
4158 $http_headers += array(
4159 // application/xhtml+xml not used because of IE
4160 'Content-type' => 'text/html; charset=UTF-8',
4161 'Cache-Control' => 'private, no-cache="set-cookie"',
4162 'Expires' => gmdate('D, d M Y H:i:s', time()) . ' GMT',
4163 'Referrer-Policy' => 'strict-origin-when-cross-origin',
4164 );
4165 if (!empty($user->data['is_bot']))
4166 {
4167 // Let reverse proxies know we detected a bot.
4168 $http_headers['X-PHPBB-IS-BOT'] = 'yes';
4169 }
4170 }
4171
4172 /**
4173 * Execute code and/or overwrite _common_ template variables after they have been assigned.
4174 *
4175 * @event core.page_header_after
4176 * @var string page_title Page title
4177 * @var bool display_online_list Do we display online users list
4178 * @var string item Restrict online users to a certain
4179 * session item, e.g. forum for
4180 * session_forum_id
4181 * @var int item_id Restrict online users to item id
4182 * @var array http_headers HTTP headers that should be set by phpbb
4183 *
4184 * @since 3.1.0-b3
4185 */
4186 $vars = array('page_title', 'display_online_list', 'item_id', 'item', 'http_headers');
4187 extract($phpbb_dispatcher->trigger_event('core.page_header_after', compact($vars)));
4188
4189 foreach ($http_headers as $hname => $hval)
4190 {
4191 header((string) $hname . ': ' . (string) $hval);
4192 }
4193
4194 return;
4195 }
4196
4197 /**
4198 * Check and display the SQL report if requested.
4199 *
4200 * @param \phpbb\request\request_interface $request Request object
4201 * @param \phpbb\auth\auth $auth Auth object
4202 * @param \phpbb\db\driver\driver_interface $db Database connection
4203 *
4204 * @deprecated 3.3.1 (To be removed: 4.0.0-a1); use controller helper's display_sql_report()
4205 */
4206 function phpbb_check_and_display_sql_report(\phpbb\request\request_interface $request, \phpbb\auth\auth $auth, \phpbb\db\driver\driver_interface $db)
4207 {
4208 global $phpbb_container;
4209
4210 /** @var \phpbb\controller\helper $controller_helper */
4211 $controller_helper = $phpbb_container->get('controller.helper');
4212
4213 $controller_helper->display_sql_report();
4214 }
4215
4216 /**
4217 * Generate the debug output string
4218 *
4219 * @param \phpbb\db\driver\driver_interface $db Database connection
4220 * @param \phpbb\config\config $config Config object
4221 * @param \phpbb\auth\auth $auth Auth object
4222 * @param \phpbb\user $user User object
4223 * @param \phpbb\event\dispatcher_interface $phpbb_dispatcher Event dispatcher
4224 * @return string
4225 */
4226 function phpbb_generate_debug_output(\phpbb\db\driver\driver_interface $db, \phpbb\config\config $config, \phpbb\auth\auth $auth, \phpbb\user $user, \phpbb\event\dispatcher_interface $phpbb_dispatcher)
4227 {
4228 global $phpbb_container;
4229
4230 $debug_info = array();
4231
4232 // Output page creation time
4233 if ($phpbb_container->getParameter('debug.load_time'))
4234 {
4235 if (isset($GLOBALS['starttime']))
4236 {
4237 $totaltime = microtime(true) - $GLOBALS['starttime'];
4238 $debug_info[] = sprintf('<span title="SQL time: %.3fs / PHP time: %.3fs">Time: %.3fs</span>', $db->get_sql_time(), ($totaltime - $db->get_sql_time()), $totaltime);
4239 }
4240 }
4241
4242 if ($phpbb_container->getParameter('debug.memory'))
4243 {
4244 $memory_usage = memory_get_peak_usage();
4245 if ($memory_usage)
4246 {
4247 $memory_usage = get_formatted_filesize($memory_usage);
4248
4249 $debug_info[] = 'Peak Memory Usage: ' . $memory_usage;
4250 }
4251
4252 $debug_info[] = 'GZIP: ' . (($config['gzip_compress'] && @extension_loaded('zlib')) ? 'On' : 'Off');
4253
4254 if ($user->load)
4255 {
4256 $debug_info[] = 'Load: ' . $user->load;
4257 }
4258 }
4259
4260 if ($phpbb_container->getParameter('debug.sql_explain'))
4261 {
4262 $debug_info[] = sprintf('<span title="Cached: %d">Queries: %d</span>', $db->sql_num_queries(true), $db->sql_num_queries());
4263
4264 if ($auth->acl_get('a_'))
4265 {
4266 $debug_info[] = '<a href="' . build_url() . '&explain=1">SQL Explain</a>';
4267 }
4268 }
4269
4270 /**
4271 * Modify debug output information
4272 *
4273 * @event core.phpbb_generate_debug_output
4274 * @var array debug_info Array of strings with debug information
4275 *
4276 * @since 3.1.0-RC3
4277 */
4278 $vars = array('debug_info');
4279 extract($phpbb_dispatcher->trigger_event('core.phpbb_generate_debug_output', compact($vars)));
4280
4281 return implode(' | ', $debug_info);
4282 }
4283
4284 /**
4285 * Generate page footer
4286 *
4287 * @param bool $run_cron Whether or not to run the cron
4288 * @param bool $display_template Whether or not to display the template
4289 * @param bool $exit_handler Whether or not to run the exit_handler()
4290 */
4291 function page_footer($run_cron = true, $display_template = true, $exit_handler = true)
4292 {
4293 global $phpbb_dispatcher, $phpbb_container, $template;
4294
4295 // A listener can set this variable to `true` when it overrides this function
4296 $page_footer_override = false;
4297
4298 /**
4299 * Execute code and/or overwrite page_footer()
4300 *
4301 * @event core.page_footer
4302 * @var bool run_cron Shall we run cron tasks
4303 * @var bool page_footer_override Shall we return instead of running
4304 * the rest of page_footer()
4305 * @since 3.1.0-a1
4306 */
4307 $vars = array('run_cron', 'page_footer_override');
4308 extract($phpbb_dispatcher->trigger_event('core.page_footer', compact($vars)));
4309
4310 if ($page_footer_override)
4311 {
4312 return;
4313 }
4314
4315 /** @var \phpbb\controller\helper $controller_helper */
4316 $controller_helper = $phpbb_container->get('controller.helper');
4317
4318 $controller_helper->display_footer($run_cron);
4319
4320 /**
4321 * Execute code and/or modify output before displaying the template.
4322 *
4323 * @event core.page_footer_after
4324 * @var bool display_template Whether or not to display the template
4325 * @var bool exit_handler Whether or not to run the exit_handler()
4326 *
4327 * @since 3.1.0-RC5
4328 */
4329 $vars = array('display_template', 'exit_handler');
4330 extract($phpbb_dispatcher->trigger_event('core.page_footer_after', compact($vars)));
4331
4332 if ($display_template)
4333 {
4334 $template->display('body');
4335 }
4336
4337 garbage_collection();
4338
4339 if ($exit_handler)
4340 {
4341 exit_handler();
4342 }
4343 }
4344
4345 /**
4346 * Closing the cache object and the database
4347 * Cool function name, eh? We might want to add operations to it later
4348 */
4349 function garbage_collection()
4350 {
4351 global $cache, $db;
4352 global $phpbb_dispatcher;
4353
4354 if (!empty($phpbb_dispatcher))
4355 {
4356 /**
4357 * Unload some objects, to free some memory, before we finish our task
4358 *
4359 * @event core.garbage_collection
4360 * @since 3.1.0-a1
4361 */
4362 $phpbb_dispatcher->dispatch('core.garbage_collection');
4363 }
4364
4365 // Unload cache, must be done before the DB connection if closed
4366 if (!empty($cache))
4367 {
4368 $cache->unload();
4369 }
4370
4371 // Close our DB connection.
4372 if (!empty($db))
4373 {
4374 $db->sql_close();
4375 }
4376 }
4377
4378 /**
4379 * Handler for exit calls in phpBB.
4380 * This function supports hooks.
4381 *
4382 * Note: This function is called after the template has been outputted.
4383 */
4384 function exit_handler()
4385 {
4386 global $phpbb_hook;
4387
4388 if (!empty($phpbb_hook) && $phpbb_hook->call_hook(__FUNCTION__))
4389 {
4390 if ($phpbb_hook->hook_return(__FUNCTION__))
4391 {
4392 return $phpbb_hook->hook_return_result(__FUNCTION__);
4393 }
4394 }
4395
4396 // As a pre-caution... some setups display a blank page if the flush() is not there.
4397 (ob_get_level() > 0) ? @ob_flush() : @flush();
4398
4399 exit;
4400 }
4401
4402 /**
4403 * Handler for init calls in phpBB. This function is called in \phpbb\user::setup();
4404 * This function supports hooks.
4405 */
4406 function phpbb_user_session_handler()
4407 {
4408 global $phpbb_hook;
4409
4410 if (!empty($phpbb_hook) && $phpbb_hook->call_hook(__FUNCTION__))
4411 {
4412 if ($phpbb_hook->hook_return(__FUNCTION__))
4413 {
4414 return $phpbb_hook->hook_return_result(__FUNCTION__);
4415 }
4416 }
4417
4418 return;
4419 }
4420
4421 /**
4422 * Casts a numeric string $input to an appropriate numeric type (i.e. integer or float)
4423 *
4424 * @param string $input A numeric string.
4425 *
4426 * @return int|float Integer $input if $input fits integer,
4427 * float $input otherwise.
4428 */
4429 function phpbb_to_numeric($input)
4430 {
4431 return ($input > PHP_INT_MAX) ? (float) $input : (int) $input;
4432 }
4433
4434 /**
4435 * Get the board contact details (e.g. for emails)
4436 *
4437 * @param \phpbb\config\config $config
4438 * @param string $phpEx
4439 * @return string
4440 */
4441 function phpbb_get_board_contact(\phpbb\config\config $config, $phpEx)
4442 {
4443 if ($config['contact_admin_form_enable'])
4444 {
4445 return generate_board_url() . '/memberlist.' . $phpEx . '?mode=contactadmin';
4446 }
4447 else
4448 {
4449 return $config['board_contact'];
4450 }
4451 }
4452
4453 /**
4454 * Get a clickable board contact details link
4455 *
4456 * @param \phpbb\config\config $config
4457 * @param string $phpbb_root_path
4458 * @param string $phpEx
4459 * @return string
4460 */
4461 function phpbb_get_board_contact_link(\phpbb\config\config $config, $phpbb_root_path, $phpEx)
4462 {
4463 if ($config['contact_admin_form_enable'] && $config['email_enable'])
4464 {
4465 return append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=contactadmin');
4466 }
4467 else
4468 {
4469 return 'mailto:' . htmlspecialchars($config['board_contact'], ENT_COMPAT);
4470 }
4471 }
4472