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