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