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