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