[ Index ] |
PHP Cross Reference of phpBB-3.3.10-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 * Re-Apply session id after page reloads 1819 */ 1820 function reapply_sid($url, $is_route = false) 1821 { 1822 global $phpEx, $phpbb_root_path; 1823 1824 if ($url === "index.$phpEx") 1825 { 1826 return append_sid("index.$phpEx"); 1827 } 1828 else if ($url === "{$phpbb_root_path}index.$phpEx") 1829 { 1830 return append_sid("{$phpbb_root_path}index.$phpEx"); 1831 } 1832 1833 // Remove previously added sid 1834 if (strpos($url, 'sid=') !== false) 1835 { 1836 // All kind of links 1837 $url = preg_replace('/(\?)?(&|&)?sid=[a-z0-9]+/', '', $url); 1838 // if the sid was the first param, make the old second as first ones 1839 $url = preg_replace("/$phpEx(&|&)+?/", "$phpEx?", $url); 1840 } 1841 1842 return append_sid($url, false, true, false, $is_route); 1843 } 1844 1845 /** 1846 * Returns url from the session/current page with an re-appended SID with optionally stripping vars from the url 1847 */ 1848 function build_url($strip_vars = false) 1849 { 1850 global $config, $user, $phpbb_path_helper; 1851 1852 $page = $phpbb_path_helper->get_valid_page($user->page['page'], $config['enable_mod_rewrite']); 1853 1854 // Append SID 1855 $redirect = append_sid($page, false, false); 1856 1857 if ($strip_vars !== false) 1858 { 1859 $redirect = $phpbb_path_helper->strip_url_params($redirect, $strip_vars, false); 1860 } 1861 else 1862 { 1863 $redirect = str_replace('&', '&', $redirect); 1864 } 1865 1866 return $redirect . ((strpos($redirect, '?') === false) ? '?' : ''); 1867 } 1868 1869 /** 1870 * Meta refresh assignment 1871 * Adds META template variable with meta http tag. 1872 * 1873 * @param int $time Time in seconds for meta refresh tag 1874 * @param string $url URL to redirect to. The url will go through redirect() first before the template variable is assigned 1875 * @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. 1876 */ 1877 function meta_refresh($time, $url, $disable_cd_check = false) 1878 { 1879 global $template, $refresh_data, $request; 1880 1881 $url = redirect($url, true, $disable_cd_check); 1882 if ($request->is_ajax()) 1883 { 1884 $refresh_data = array( 1885 'time' => $time, 1886 'url' => $url, 1887 ); 1888 } 1889 else 1890 { 1891 // For XHTML compatibility we change back & to & 1892 $url = str_replace('&', '&', $url); 1893 1894 $template->assign_vars(array( 1895 'META' => '<meta http-equiv="refresh" content="' . $time . '; url=' . $url . '" />') 1896 ); 1897 } 1898 1899 return $url; 1900 } 1901 1902 /** 1903 * Outputs correct status line header. 1904 * 1905 * Depending on php sapi one of the two following forms is used: 1906 * 1907 * Status: 404 Not Found 1908 * 1909 * HTTP/1.x 404 Not Found 1910 * 1911 * HTTP version is taken from HTTP_VERSION environment variable, 1912 * and defaults to 1.0. 1913 * 1914 * Sample usage: 1915 * 1916 * send_status_line(404, 'Not Found'); 1917 * 1918 * @param int $code HTTP status code 1919 * @param string $message Message for the status code 1920 * @return null 1921 */ 1922 function send_status_line($code, $message) 1923 { 1924 if (substr(strtolower(@php_sapi_name()), 0, 3) === 'cgi') 1925 { 1926 // in theory, we shouldn't need that due to php doing it. Reality offers a differing opinion, though 1927 header("Status: $code $message", true, $code); 1928 } 1929 else 1930 { 1931 $version = phpbb_request_http_version(); 1932 header("$version $code $message", true, $code); 1933 } 1934 } 1935 1936 /** 1937 * Returns the HTTP version used in the current request. 1938 * 1939 * Handles the case of being called before $request is present, 1940 * in which case it falls back to the $_SERVER superglobal. 1941 * 1942 * @return string HTTP version 1943 */ 1944 function phpbb_request_http_version() 1945 { 1946 global $request; 1947 1948 $version = ''; 1949 if ($request && $request->server('SERVER_PROTOCOL')) 1950 { 1951 $version = $request->server('SERVER_PROTOCOL'); 1952 } 1953 else if (isset($_SERVER['SERVER_PROTOCOL'])) 1954 { 1955 $version = $_SERVER['SERVER_PROTOCOL']; 1956 } 1957 1958 if (!empty($version) && is_string($version) && preg_match('#^HTTP/[0-9]\.[0-9]$#', $version)) 1959 { 1960 return $version; 1961 } 1962 1963 return 'HTTP/1.0'; 1964 } 1965 1966 //Form validation 1967 1968 1969 /** 1970 * Add a secret hash for use in links/GET requests 1971 * @param string $link_name The name of the link; has to match the name used in check_link_hash, otherwise no restrictions apply 1972 * @return string the hash 1973 1974 */ 1975 function generate_link_hash($link_name) 1976 { 1977 global $user; 1978 1979 if (!isset($user->data["hash_$link_name"])) 1980 { 1981 $user->data["hash_$link_name"] = substr(sha1($user->data['user_form_salt'] . $link_name), 0, 8); 1982 } 1983 1984 return $user->data["hash_$link_name"]; 1985 } 1986 1987 1988 /** 1989 * checks a link hash - for GET requests 1990 * @param string $token the submitted token 1991 * @param string $link_name The name of the link 1992 * @return boolean true if all is fine 1993 */ 1994 function check_link_hash($token, $link_name) 1995 { 1996 return $token === generate_link_hash($link_name); 1997 } 1998 1999 /** 2000 * Add a secret token to the form (requires the S_FORM_TOKEN template variable) 2001 * @param string $form_name The name of the form; has to match the name used in check_form_key, otherwise no restrictions apply 2002 * @param string $template_variable_suffix A string that is appended to the name of the template variable to which the form elements are assigned 2003 */ 2004 function add_form_key($form_name, $template_variable_suffix = '') 2005 { 2006 global $config, $template, $user, $phpbb_dispatcher; 2007 2008 $now = time(); 2009 $token_sid = ($user->data['user_id'] == ANONYMOUS && !empty($config['form_token_sid_guests'])) ? $user->session_id : ''; 2010 $token = sha1($now . $user->data['user_form_salt'] . $form_name . $token_sid); 2011 2012 $s_fields = build_hidden_fields(array( 2013 'creation_time' => $now, 2014 'form_token' => $token, 2015 )); 2016 2017 /** 2018 * Perform additional actions on creation of the form token 2019 * 2020 * @event core.add_form_key 2021 * @var string form_name The form name 2022 * @var int now Current time timestamp 2023 * @var string s_fields Generated hidden fields 2024 * @var string token Form token 2025 * @var string token_sid User session ID 2026 * @var string template_variable_suffix The string that is appended to template variable name 2027 * 2028 * @since 3.1.0-RC3 2029 * @changed 3.1.11-RC1 Added template_variable_suffix 2030 */ 2031 $vars = array( 2032 'form_name', 2033 'now', 2034 's_fields', 2035 'token', 2036 'token_sid', 2037 'template_variable_suffix', 2038 ); 2039 extract($phpbb_dispatcher->trigger_event('core.add_form_key', compact($vars))); 2040 2041 $template->assign_var('S_FORM_TOKEN' . $template_variable_suffix, $s_fields); 2042 } 2043 2044 /** 2045 * Check the form key. Required for all altering actions not secured by confirm_box 2046 * 2047 * @param string $form_name The name of the form; has to match the name used 2048 * in add_form_key, otherwise no restrictions apply 2049 * @param int $timespan The maximum acceptable age for a submitted form 2050 * in seconds. Defaults to the config setting. 2051 * @return bool True, if the form key was valid, false otherwise 2052 */ 2053 function check_form_key($form_name, $timespan = false) 2054 { 2055 global $config, $request, $user; 2056 2057 if ($timespan === false) 2058 { 2059 // we enforce a minimum value of half a minute here. 2060 $timespan = ($config['form_token_lifetime'] == -1) ? -1 : max(30, $config['form_token_lifetime']); 2061 } 2062 2063 if ($request->is_set_post('creation_time') && $request->is_set_post('form_token')) 2064 { 2065 $creation_time = abs($request->variable('creation_time', 0)); 2066 $token = $request->variable('form_token', ''); 2067 2068 $diff = time() - $creation_time; 2069 2070 // If creation_time and the time() now is zero we can assume it was not a human doing this (the check for if ($diff)... 2071 if (defined('DEBUG_TEST') || $diff && ($diff <= $timespan || $timespan === -1)) 2072 { 2073 $token_sid = ($user->data['user_id'] == ANONYMOUS && !empty($config['form_token_sid_guests'])) ? $user->session_id : ''; 2074 $key = sha1($creation_time . $user->data['user_form_salt'] . $form_name . $token_sid); 2075 2076 if ($key === $token) 2077 { 2078 return true; 2079 } 2080 } 2081 } 2082 2083 return false; 2084 } 2085 2086 // Message/Login boxes 2087 2088 /** 2089 * Build Confirm box 2090 * @param boolean $check True for checking if confirmed (without any additional parameters) and false for displaying the confirm box 2091 * @param string|array $title Title/Message used for confirm box. 2092 * message text is _CONFIRM appended to title. 2093 * If title cannot be found in user->lang a default one is displayed 2094 * If title_CONFIRM cannot be found in user->lang the text given is used. 2095 * If title is an array, the first array value is used as explained per above, 2096 * all other array values are sent as parameters to the language function. 2097 * @param string $hidden Hidden variables 2098 * @param string $html_body Template used for confirm box 2099 * @param string $u_action Custom form action 2100 * 2101 * @return bool True if confirmation was successful, false if not 2102 */ 2103 function confirm_box($check, $title = '', $hidden = '', $html_body = 'confirm_body.html', $u_action = '') 2104 { 2105 global $user, $template, $db, $request; 2106 global $config, $language, $phpbb_path_helper, $phpbb_dispatcher; 2107 2108 if (isset($_POST['cancel'])) 2109 { 2110 return false; 2111 } 2112 2113 $confirm = ($language->lang('YES') === $request->variable('confirm', '', true, \phpbb\request\request_interface::POST)); 2114 2115 if ($check && $confirm) 2116 { 2117 $user_id = $request->variable('confirm_uid', 0); 2118 $session_id = $request->variable('sess', ''); 2119 $confirm_key = $request->variable('confirm_key', ''); 2120 2121 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']) 2122 { 2123 return false; 2124 } 2125 2126 // Reset user_last_confirm_key 2127 $sql = 'UPDATE ' . USERS_TABLE . " SET user_last_confirm_key = '' 2128 WHERE user_id = " . $user->data['user_id']; 2129 $db->sql_query($sql); 2130 2131 return true; 2132 } 2133 else if ($check) 2134 { 2135 return false; 2136 } 2137 2138 $s_hidden_fields = build_hidden_fields(array( 2139 'confirm_uid' => $user->data['user_id'], 2140 'sess' => $user->session_id, 2141 'sid' => $user->session_id, 2142 )); 2143 2144 // generate activation key 2145 $confirm_key = gen_rand_string(10); 2146 2147 // generate language strings 2148 if (is_array($title)) 2149 { 2150 $key = array_shift($title); 2151 $count = array_shift($title); 2152 $confirm_title = $language->is_set($key) ? $language->lang($key, $count, $title) : $language->lang('CONFIRM'); 2153 $confirm_text = $language->is_set($key . '_CONFIRM') ? $language->lang($key . '_CONFIRM', $count, $title) : $key; 2154 } 2155 else 2156 { 2157 $confirm_title = $language->is_set($title) ? $language->lang($title) : $language->lang('CONFIRM'); 2158 $confirm_text = $language->is_set($title . '_CONFIRM') ? $language->lang($title . '_CONFIRM') : $title; 2159 } 2160 2161 if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin']) 2162 { 2163 adm_page_header($confirm_title); 2164 } 2165 else 2166 { 2167 page_header($confirm_title); 2168 } 2169 2170 $template->set_filenames(array( 2171 'body' => $html_body) 2172 ); 2173 2174 // If activation key already exist, we better do not re-use the key (something very strange is going on...) 2175 if ($request->variable('confirm_key', '')) 2176 { 2177 // This should not occur, therefore we cancel the operation to safe the user 2178 return false; 2179 } 2180 2181 // re-add sid / transform & to & for user->page (user->page is always using &) 2182 $use_page = ($u_action) ? $u_action : str_replace('&', '&', $user->page['page']); 2183 $u_action = reapply_sid($phpbb_path_helper->get_valid_page($use_page, $config['enable_mod_rewrite'])); 2184 $u_action .= ((strpos($u_action, '?') === false) ? '?' : '&') . 'confirm_key=' . $confirm_key; 2185 2186 $template->assign_vars(array( 2187 'MESSAGE_TITLE' => $confirm_title, 2188 'MESSAGE_TEXT' => $confirm_text, 2189 2190 'YES_VALUE' => $language->lang('YES'), 2191 'S_CONFIRM_ACTION' => $u_action, 2192 'S_HIDDEN_FIELDS' => $hidden . $s_hidden_fields, 2193 'S_AJAX_REQUEST' => $request->is_ajax(), 2194 )); 2195 2196 $sql = 'UPDATE ' . USERS_TABLE . " SET user_last_confirm_key = '" . $db->sql_escape($confirm_key) . "' 2197 WHERE user_id = " . $user->data['user_id']; 2198 $db->sql_query($sql); 2199 2200 if ($request->is_ajax()) 2201 { 2202 $u_action .= '&confirm_uid=' . $user->data['user_id'] . '&sess=' . $user->session_id . '&sid=' . $user->session_id; 2203 $data = array( 2204 'MESSAGE_BODY' => $template->assign_display('body'), 2205 'MESSAGE_TITLE' => $confirm_title, 2206 'MESSAGE_TEXT' => $confirm_text, 2207 2208 'YES_VALUE' => $language->lang('YES'), 2209 'S_CONFIRM_ACTION' => str_replace('&', '&', $u_action), //inefficient, rewrite whole function 2210 'S_HIDDEN_FIELDS' => $hidden . $s_hidden_fields 2211 ); 2212 2213 /** 2214 * This event allows an extension to modify the ajax output of confirm box. 2215 * 2216 * @event core.confirm_box_ajax_before 2217 * @var string u_action Action of the form 2218 * @var array data Data to be sent 2219 * @var string hidden Hidden fields generated by caller 2220 * @var string s_hidden_fields Hidden fields generated by this function 2221 * @since 3.2.8-RC1 2222 */ 2223 $vars = array( 2224 'u_action', 2225 'data', 2226 'hidden', 2227 's_hidden_fields', 2228 ); 2229 extract($phpbb_dispatcher->trigger_event('core.confirm_box_ajax_before', compact($vars))); 2230 2231 $json_response = new \phpbb\json_response; 2232 $json_response->send($data); 2233 } 2234 2235 if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin']) 2236 { 2237 adm_page_footer(); 2238 } 2239 else 2240 { 2241 page_footer(); 2242 } 2243 2244 exit; // unreachable, page_footer() above will call exit() 2245 } 2246 2247 /** 2248 * Generate login box or verify password 2249 */ 2250 function login_box($redirect = '', $l_explain = '', $l_success = '', $admin = false, $s_display = true) 2251 { 2252 global $user, $template, $auth, $phpEx, $phpbb_root_path, $config; 2253 global $request, $phpbb_container, $phpbb_dispatcher, $phpbb_log; 2254 2255 $err = ''; 2256 $form_name = 'login'; 2257 $username = $autologin = false; 2258 2259 // Make sure user->setup() has been called 2260 if (!$user->is_setup()) 2261 { 2262 $user->setup(); 2263 } 2264 2265 /** 2266 * This event allows an extension to modify the login process 2267 * 2268 * @event core.login_box_before 2269 * @var string redirect Redirect string 2270 * @var string l_explain Explain language string 2271 * @var string l_success Success language string 2272 * @var bool admin Is admin? 2273 * @var bool s_display Display full login form? 2274 * @var string err Error string 2275 * @since 3.1.9-RC1 2276 */ 2277 $vars = array('redirect', 'l_explain', 'l_success', 'admin', 's_display', 'err'); 2278 extract($phpbb_dispatcher->trigger_event('core.login_box_before', compact($vars))); 2279 2280 // Print out error if user tries to authenticate as an administrator without having the privileges... 2281 if ($admin && !$auth->acl_get('a_')) 2282 { 2283 // Not authd 2284 // anonymous/inactive users are never able to go to the ACP even if they have the relevant permissions 2285 if ($user->data['is_registered']) 2286 { 2287 $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ADMIN_AUTH_FAIL'); 2288 } 2289 send_status_line(403, 'Forbidden'); 2290 trigger_error('NO_AUTH_ADMIN'); 2291 } 2292 2293 if (empty($err) && ($request->is_set_post('login') || ($request->is_set('login') && $request->variable('login', '') == 'external'))) 2294 { 2295 // Get credential 2296 if ($admin) 2297 { 2298 $credential = $request->variable('credential', ''); 2299 2300 if (strspn($credential, 'abcdef0123456789') !== strlen($credential) || strlen($credential) != 32) 2301 { 2302 if ($user->data['is_registered']) 2303 { 2304 $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ADMIN_AUTH_FAIL'); 2305 } 2306 send_status_line(403, 'Forbidden'); 2307 trigger_error('NO_AUTH_ADMIN'); 2308 } 2309 2310 $password = $request->untrimmed_variable('password_' . $credential, '', true); 2311 } 2312 else 2313 { 2314 $password = $request->untrimmed_variable('password', '', true); 2315 } 2316 2317 $username = $request->variable('username', '', true); 2318 $autologin = $request->is_set_post('autologin'); 2319 $viewonline = (int) !$request->is_set_post('viewonline'); 2320 $admin = ($admin) ? 1 : 0; 2321 $viewonline = ($admin) ? $user->data['session_viewonline'] : $viewonline; 2322 2323 // Check if the supplied username is equal to the one stored within the database if re-authenticating 2324 if ($admin && utf8_clean_string($username) != utf8_clean_string($user->data['username'])) 2325 { 2326 // We log the attempt to use a different username... 2327 $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ADMIN_AUTH_FAIL'); 2328 2329 send_status_line(403, 'Forbidden'); 2330 trigger_error('NO_AUTH_ADMIN_USER_DIFFER'); 2331 } 2332 2333 // Check form key 2334 if ($password && !defined('IN_CHECK_BAN') && !check_form_key($form_name)) 2335 { 2336 $result = array( 2337 'status' => false, 2338 'error_msg' => 'FORM_INVALID', 2339 ); 2340 } 2341 else 2342 { 2343 // If authentication is successful we redirect user to previous page 2344 $result = $auth->login($username, $password, $autologin, $viewonline, $admin); 2345 } 2346 2347 // If admin authentication and login, we will log if it was a success or not... 2348 // We also break the operation on the first non-success login - it could be argued that the user already knows 2349 if ($admin) 2350 { 2351 if ($result['status'] == LOGIN_SUCCESS) 2352 { 2353 $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ADMIN_AUTH_SUCCESS'); 2354 } 2355 else 2356 { 2357 // Only log the failed attempt if a real user tried to. 2358 // anonymous/inactive users are never able to go to the ACP even if they have the relevant permissions 2359 if ($user->data['is_registered']) 2360 { 2361 $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ADMIN_AUTH_FAIL'); 2362 } 2363 } 2364 } 2365 2366 // The result parameter is always an array, holding the relevant information... 2367 if ($result['status'] == LOGIN_SUCCESS) 2368 { 2369 $redirect = $request->variable('redirect', "{$phpbb_root_path}index.$phpEx"); 2370 2371 /** 2372 * This event allows an extension to modify the redirection when a user successfully logs in 2373 * 2374 * @event core.login_box_redirect 2375 * @var string redirect Redirect string 2376 * @var bool admin Is admin? 2377 * @var array result Result from auth provider 2378 * @since 3.1.0-RC5 2379 * @changed 3.1.9-RC1 Removed undefined return variable 2380 * @changed 3.2.4-RC1 Added result 2381 */ 2382 $vars = array('redirect', 'admin', 'result'); 2383 extract($phpbb_dispatcher->trigger_event('core.login_box_redirect', compact($vars))); 2384 2385 // append/replace SID (may change during the session for AOL users) 2386 $redirect = reapply_sid($redirect); 2387 2388 // Special case... the user is effectively banned, but we allow founders to login 2389 if (defined('IN_CHECK_BAN') && $result['user_row']['user_type'] != USER_FOUNDER) 2390 { 2391 return; 2392 } 2393 2394 redirect($redirect); 2395 } 2396 2397 // Something failed, determine what... 2398 if ($result['status'] == LOGIN_BREAK) 2399 { 2400 trigger_error($result['error_msg']); 2401 } 2402 2403 // Special cases... determine 2404 switch ($result['status']) 2405 { 2406 case LOGIN_ERROR_PASSWORD_CONVERT: 2407 $err = sprintf( 2408 $user->lang[$result['error_msg']], 2409 ($config['email_enable']) ? '<a href="' . append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=sendpassword') . '">' : '', 2410 ($config['email_enable']) ? '</a>' : '', 2411 '<a href="' . phpbb_get_board_contact_link($config, $phpbb_root_path, $phpEx) . '">', 2412 '</a>' 2413 ); 2414 break; 2415 2416 case LOGIN_ERROR_ATTEMPTS: 2417 2418 $captcha = $phpbb_container->get('captcha.factory')->get_instance($config['captcha_plugin']); 2419 $captcha->init(CONFIRM_LOGIN); 2420 // $captcha->reset(); 2421 2422 $template->assign_vars(array( 2423 'CAPTCHA_TEMPLATE' => $captcha->get_template(), 2424 )); 2425 // no break; 2426 2427 // Username, password, etc... 2428 default: 2429 $err = $user->lang[$result['error_msg']]; 2430 2431 // Assign admin contact to some error messages 2432 if ($result['error_msg'] == 'LOGIN_ERROR_USERNAME' || $result['error_msg'] == 'LOGIN_ERROR_PASSWORD') 2433 { 2434 $err = sprintf($user->lang[$result['error_msg']], '<a href="' . append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=contactadmin') . '">', '</a>'); 2435 } 2436 2437 break; 2438 } 2439 2440 /** 2441 * This event allows an extension to process when a user fails a login attempt 2442 * 2443 * @event core.login_box_failed 2444 * @var array result Login result data 2445 * @var string username User name used to login 2446 * @var string password Password used to login 2447 * @var string err Error message 2448 * @since 3.1.3-RC1 2449 */ 2450 $vars = array('result', 'username', 'password', 'err'); 2451 extract($phpbb_dispatcher->trigger_event('core.login_box_failed', compact($vars))); 2452 } 2453 2454 // Assign credential for username/password pair 2455 $credential = ($admin) ? md5(unique_id()) : false; 2456 2457 $s_hidden_fields = array( 2458 'sid' => $user->session_id, 2459 ); 2460 2461 if ($redirect) 2462 { 2463 $s_hidden_fields['redirect'] = $redirect; 2464 } 2465 2466 if ($admin) 2467 { 2468 $s_hidden_fields['credential'] = $credential; 2469 } 2470 2471 /* @var $provider_collection \phpbb\auth\provider_collection */ 2472 $provider_collection = $phpbb_container->get('auth.provider_collection'); 2473 $auth_provider = $provider_collection->get_provider(); 2474 2475 $auth_provider_data = $auth_provider->get_login_data(); 2476 if ($auth_provider_data) 2477 { 2478 if (isset($auth_provider_data['VARS'])) 2479 { 2480 $template->assign_vars($auth_provider_data['VARS']); 2481 } 2482 2483 if (isset($auth_provider_data['BLOCK_VAR_NAME'])) 2484 { 2485 foreach ($auth_provider_data['BLOCK_VARS'] as $block_vars) 2486 { 2487 $template->assign_block_vars($auth_provider_data['BLOCK_VAR_NAME'], $block_vars); 2488 } 2489 } 2490 2491 $template->assign_vars(array( 2492 'PROVIDER_TEMPLATE_FILE' => $auth_provider_data['TEMPLATE_FILE'], 2493 )); 2494 } 2495 2496 $s_hidden_fields = build_hidden_fields($s_hidden_fields); 2497 2498 /** @var \phpbb\controller\helper $controller_helper */ 2499 $controller_helper = $phpbb_container->get('controller.helper'); 2500 2501 $login_box_template_data = array( 2502 'LOGIN_ERROR' => $err, 2503 'LOGIN_EXPLAIN' => $l_explain, 2504 2505 'U_SEND_PASSWORD' => ($config['email_enable'] && $config['allow_password_reset']) ? $controller_helper->route('phpbb_ucp_forgot_password_controller') : '', 2506 'U_RESEND_ACTIVATION' => ($config['require_activation'] == USER_ACTIVATION_SELF && $config['email_enable']) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=resend_act') : '', 2507 'U_TERMS_USE' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=terms'), 2508 'U_PRIVACY' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy'), 2509 'UA_PRIVACY' => addslashes(append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy')), 2510 2511 'S_DISPLAY_FULL_LOGIN' => ($s_display) ? true : false, 2512 'S_HIDDEN_FIELDS' => $s_hidden_fields, 2513 2514 'S_ADMIN_AUTH' => $admin, 2515 'USERNAME' => ($admin) ? $user->data['username'] : '', 2516 2517 'USERNAME_CREDENTIAL' => 'username', 2518 'PASSWORD_CREDENTIAL' => ($admin) ? 'password_' . $credential : 'password', 2519 ); 2520 2521 /** 2522 * Event to add/modify login box template data 2523 * 2524 * @event core.login_box_modify_template_data 2525 * @var int admin Flag whether user is admin 2526 * @var string username User name 2527 * @var int autologin Flag whether autologin is enabled 2528 * @var string redirect Redirect URL 2529 * @var array login_box_template_data Array with the login box template data 2530 * @since 3.2.3-RC2 2531 */ 2532 $vars = array( 2533 'admin', 2534 'username', 2535 'autologin', 2536 'redirect', 2537 'login_box_template_data', 2538 ); 2539 extract($phpbb_dispatcher->trigger_event('core.login_box_modify_template_data', compact($vars))); 2540 2541 $template->assign_vars($login_box_template_data); 2542 2543 page_header($user->lang['LOGIN']); 2544 2545 $template->set_filenames(array( 2546 'body' => 'login_body.html') 2547 ); 2548 make_jumpbox(append_sid("{$phpbb_root_path}viewforum.$phpEx")); 2549 2550 page_footer(); 2551 } 2552 2553 /** 2554 * Generate forum login box 2555 */ 2556 function login_forum_box($forum_data) 2557 { 2558 global $db, $phpbb_container, $request, $template, $user, $phpbb_dispatcher, $phpbb_root_path, $phpEx; 2559 2560 $password = $request->variable('password', '', true); 2561 2562 $sql = 'SELECT forum_id 2563 FROM ' . FORUMS_ACCESS_TABLE . ' 2564 WHERE forum_id = ' . $forum_data['forum_id'] . ' 2565 AND user_id = ' . $user->data['user_id'] . " 2566 AND session_id = '" . $db->sql_escape($user->session_id) . "'"; 2567 $result = $db->sql_query($sql); 2568 $row = $db->sql_fetchrow($result); 2569 $db->sql_freeresult($result); 2570 2571 if ($row) 2572 { 2573 return true; 2574 } 2575 2576 if ($password) 2577 { 2578 // Remove expired authorised sessions 2579 $sql = 'SELECT f.session_id 2580 FROM ' . FORUMS_ACCESS_TABLE . ' f 2581 LEFT JOIN ' . SESSIONS_TABLE . ' s ON (f.session_id = s.session_id) 2582 WHERE s.session_id IS NULL'; 2583 $result = $db->sql_query($sql); 2584 2585 if ($row = $db->sql_fetchrow($result)) 2586 { 2587 $sql_in = array(); 2588 do 2589 { 2590 $sql_in[] = (string) $row['session_id']; 2591 } 2592 while ($row = $db->sql_fetchrow($result)); 2593 2594 // Remove expired sessions 2595 $sql = 'DELETE FROM ' . FORUMS_ACCESS_TABLE . ' 2596 WHERE ' . $db->sql_in_set('session_id', $sql_in); 2597 $db->sql_query($sql); 2598 } 2599 $db->sql_freeresult($result); 2600 2601 /* @var $passwords_manager \phpbb\passwords\manager */ 2602 $passwords_manager = $phpbb_container->get('passwords.manager'); 2603 2604 if ($passwords_manager->check($password, $forum_data['forum_password'])) 2605 { 2606 $sql_ary = array( 2607 'forum_id' => (int) $forum_data['forum_id'], 2608 'user_id' => (int) $user->data['user_id'], 2609 'session_id' => (string) $user->session_id, 2610 ); 2611 2612 $db->sql_query('INSERT INTO ' . FORUMS_ACCESS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary)); 2613 2614 return true; 2615 } 2616 2617 $template->assign_var('LOGIN_ERROR', $user->lang['WRONG_PASSWORD']); 2618 } 2619 2620 /** 2621 * Performing additional actions, load additional data on forum login 2622 * 2623 * @event core.login_forum_box 2624 * @var array forum_data Array with forum data 2625 * @var string password Password entered 2626 * @since 3.1.0-RC3 2627 */ 2628 $vars = array('forum_data', 'password'); 2629 extract($phpbb_dispatcher->trigger_event('core.login_forum_box', compact($vars))); 2630 2631 page_header($user->lang['LOGIN']); 2632 2633 $template->assign_vars(array( 2634 'FORUM_NAME' => isset($forum_data['forum_name']) ? $forum_data['forum_name'] : '', 2635 'S_LOGIN_ACTION' => build_url(array('f')), 2636 'S_HIDDEN_FIELDS' => build_hidden_fields(array('f' => $forum_data['forum_id']))) 2637 ); 2638 2639 $template->set_filenames(array( 2640 'body' => 'login_forum.html') 2641 ); 2642 2643 make_jumpbox(append_sid("{$phpbb_root_path}viewforum.$phpEx"), $forum_data['forum_id']); 2644 2645 page_footer(); 2646 } 2647 2648 // Little helpers 2649 2650 /** 2651 * Little helper for the build_hidden_fields function 2652 */ 2653 function _build_hidden_fields($key, $value, $specialchar, $stripslashes) 2654 { 2655 $hidden_fields = ''; 2656 2657 if (!is_array($value)) 2658 { 2659 $value = ($stripslashes) ? stripslashes($value) : $value; 2660 $value = ($specialchar) ? htmlspecialchars($value, ENT_COMPAT, 'UTF-8') : $value; 2661 2662 $hidden_fields .= '<input type="hidden" name="' . $key . '" value="' . $value . '" />' . "\n"; 2663 } 2664 else 2665 { 2666 foreach ($value as $_key => $_value) 2667 { 2668 $_key = ($stripslashes) ? stripslashes($_key) : $_key; 2669 $_key = ($specialchar) ? htmlspecialchars($_key, ENT_COMPAT, 'UTF-8') : $_key; 2670 2671 $hidden_fields .= _build_hidden_fields($key . '[' . $_key . ']', $_value, $specialchar, $stripslashes); 2672 } 2673 } 2674 2675 return $hidden_fields; 2676 } 2677 2678 /** 2679 * Build simple hidden fields from array 2680 * 2681 * @param array $field_ary an array of values to build the hidden field from 2682 * @param bool $specialchar if true, keys and values get specialchared 2683 * @param bool $stripslashes if true, keys and values get stripslashed 2684 * 2685 * @return string the hidden fields 2686 */ 2687 function build_hidden_fields($field_ary, $specialchar = false, $stripslashes = false) 2688 { 2689 $s_hidden_fields = ''; 2690 2691 foreach ($field_ary as $name => $vars) 2692 { 2693 $name = ($stripslashes) ? stripslashes($name) : $name; 2694 $name = ($specialchar) ? htmlspecialchars($name, ENT_COMPAT, 'UTF-8') : $name; 2695 2696 $s_hidden_fields .= _build_hidden_fields($name, $vars, $specialchar, $stripslashes); 2697 } 2698 2699 return $s_hidden_fields; 2700 } 2701 2702 /** 2703 * Parse cfg file 2704 */ 2705 function parse_cfg_file($filename, $lines = false) 2706 { 2707 $parsed_items = array(); 2708 2709 if ($lines === false) 2710 { 2711 $lines = file($filename); 2712 } 2713 2714 foreach ($lines as $line) 2715 { 2716 $line = trim($line); 2717 2718 if (!$line || $line[0] == '#' || ($delim_pos = strpos($line, '=')) === false) 2719 { 2720 continue; 2721 } 2722 2723 // Determine first occurrence, since in values the equal sign is allowed 2724 $key = htmlspecialchars(strtolower(trim(substr($line, 0, $delim_pos))), ENT_COMPAT); 2725 $value = trim(substr($line, $delim_pos + 1)); 2726 2727 if (in_array($value, array('off', 'false', '0'))) 2728 { 2729 $value = false; 2730 } 2731 else if (in_array($value, array('on', 'true', '1'))) 2732 { 2733 $value = true; 2734 } 2735 else if (!trim($value)) 2736 { 2737 $value = ''; 2738 } 2739 else if (($value[0] == "'" && $value[strlen($value) - 1] == "'") || ($value[0] == '"' && $value[strlen($value) - 1] == '"')) 2740 { 2741 $value = htmlspecialchars(substr($value, 1, strlen($value)-2), ENT_COMPAT); 2742 } 2743 else 2744 { 2745 $value = htmlspecialchars($value, ENT_COMPAT); 2746 } 2747 2748 $parsed_items[$key] = $value; 2749 } 2750 2751 if (isset($parsed_items['parent']) && isset($parsed_items['name']) && $parsed_items['parent'] == $parsed_items['name']) 2752 { 2753 unset($parsed_items['parent']); 2754 } 2755 2756 return $parsed_items; 2757 } 2758 2759 /** 2760 * Return a nicely formatted backtrace. 2761 * 2762 * Turns the array returned by debug_backtrace() into HTML markup. 2763 * Also filters out absolute paths to phpBB root. 2764 * 2765 * @return string HTML markup 2766 */ 2767 function get_backtrace() 2768 { 2769 $output = '<div style="font-family: monospace;">'; 2770 $backtrace = debug_backtrace(); 2771 2772 // We skip the first one, because it only shows this file/function 2773 unset($backtrace[0]); 2774 2775 foreach ($backtrace as $trace) 2776 { 2777 // Strip the current directory from path 2778 $trace['file'] = (empty($trace['file'])) ? '(not given by php)' : htmlspecialchars(phpbb_filter_root_path($trace['file']), ENT_COMPAT); 2779 $trace['line'] = (empty($trace['line'])) ? '(not given by php)' : $trace['line']; 2780 2781 // Only show function arguments for include etc. 2782 // Other parameters may contain sensible information 2783 $argument = ''; 2784 if (!empty($trace['args'][0]) && in_array($trace['function'], array('include', 'require', 'include_once', 'require_once'))) 2785 { 2786 $argument = htmlspecialchars(phpbb_filter_root_path($trace['args'][0]), ENT_COMPAT); 2787 } 2788 2789 $trace['class'] = (!isset($trace['class'])) ? '' : $trace['class']; 2790 $trace['type'] = (!isset($trace['type'])) ? '' : $trace['type']; 2791 2792 $output .= '<br />'; 2793 $output .= '<b>FILE:</b> ' . $trace['file'] . '<br />'; 2794 $output .= '<b>LINE:</b> ' . ((!empty($trace['line'])) ? $trace['line'] : '') . '<br />'; 2795 2796 $output .= '<b>CALL:</b> ' . htmlspecialchars($trace['class'] . $trace['type'] . $trace['function'], ENT_COMPAT); 2797 $output .= '(' . (($argument !== '') ? "'$argument'" : '') . ')<br />'; 2798 } 2799 $output .= '</div>'; 2800 return $output; 2801 } 2802 2803 /** 2804 * This function returns a regular expression pattern for commonly used expressions 2805 * Use with / as delimiter for email mode and # for url modes 2806 * mode can be: email|bbcode_htm|url|url_inline|www_url|www_url_inline|relative_url|relative_url_inline|ipv4|ipv6 2807 */ 2808 function get_preg_expression($mode) 2809 { 2810 switch ($mode) 2811 { 2812 case 'email': 2813 // Regex written by James Watts and Francisco Jose Martin Moreno 2814 // http://fightingforalostcause.net/misc/2006/compare-email-regex.php 2815 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})?)'; 2816 break; 2817 2818 case 'bbcode_htm': 2819 return array( 2820 '#<!\-\- e \-\-><a href="mailto:(.*?)">.*?</a><!\-\- e \-\->#', 2821 '#<!\-\- l \-\-><a (?:class="[\w-]+" )?href="(.*?)(?:(&|\?)sid=[0-9a-f]{32})?">.*?</a><!\-\- l \-\->#', 2822 '#<!\-\- ([mw]) \-\-><a (?:class="[\w-]+" )?href="http://(.*?)">\2</a><!\-\- \1 \-\->#', 2823 '#<!\-\- ([mw]) \-\-><a (?:class="[\w-]+" )?href="(.*?)">.*?</a><!\-\- \1 \-\->#', 2824 '#<!\-\- s(.*?) \-\-><img src="\{SMILIES_PATH\}\/.*? \/><!\-\- s\1 \-\->#', 2825 '#<!\-\- .*? \-\->#s', 2826 '#<.*?>#s', 2827 ); 2828 break; 2829 2830 // Whoa these look impressive! 2831 // The code to generate the following two regular expressions which match valid IPv4/IPv6 addresses 2832 // can be found in the develop directory 2833 2834 // @deprecated 2835 case 'ipv4': 2836 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])$#'; 2837 break; 2838 2839 // @deprecated 2840 case 'ipv6': 2841 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'; 2842 break; 2843 2844 case 'url': 2845 // generated with regex_idn.php file in the develop folder 2846 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})*)?"; 2847 break; 2848 2849 case 'url_http': 2850 // generated with regex_idn.php file in the develop folder 2851 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})*)?"; 2852 break; 2853 2854 case 'url_inline': 2855 // generated with regex_idn.php file in the develop folder 2856 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})*)?"; 2857 break; 2858 2859 case 'www_url': 2860 // generated with regex_idn.php file in the develop folder 2861 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})*)?"; 2862 break; 2863 2864 case 'www_url_inline': 2865 // generated with regex_idn.php file in the develop folder 2866 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})*)?"; 2867 break; 2868 2869 case 'relative_url': 2870 // generated with regex_idn.php file in the develop folder 2871 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})*)?"; 2872 break; 2873 2874 case 'relative_url_inline': 2875 // generated with regex_idn.php file in the develop folder 2876 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})*)?"; 2877 break; 2878 2879 case 'table_prefix': 2880 return '#^[a-zA-Z][a-zA-Z0-9_]*$#'; 2881 break; 2882 2883 // Matches the predecing dot 2884 case 'path_remove_dot_trailing_slash': 2885 return '#^(?:(\.)?)+(?:(.+)?)+(?:([\\/\\\])$)#'; 2886 break; 2887 2888 case 'semantic_version': 2889 // Regular expression to match semantic versions by http://rgxdb.com/ 2890 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])+)))*))?)$/'; 2891 break; 2892 } 2893 2894 return ''; 2895 } 2896 2897 /** 2898 * Generate regexp for naughty words censoring 2899 * Depends on whether installed PHP version supports unicode properties 2900 * 2901 * @param string $word word template to be replaced 2902 * 2903 * @return string $preg_expr regex to use with word censor 2904 */ 2905 function get_censor_preg_expression($word) 2906 { 2907 // Unescape the asterisk to simplify further conversions 2908 $word = str_replace('\*', '*', preg_quote($word, '#')); 2909 2910 // Replace asterisk(s) inside the pattern, at the start and at the end of it with regexes 2911 $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); 2912 2913 // Generate the final substitution 2914 $preg_expr = '#(?<![\p{Nd}\p{L}_-])(' . $word . ')(?![\p{Nd}\p{L}_-])#iu'; 2915 2916 return $preg_expr; 2917 } 2918 2919 /** 2920 * Returns the first block of the specified IPv6 address and as many additional 2921 * ones as specified in the length paramater. 2922 * If length is zero, then an empty string is returned. 2923 * If length is greater than 3 the complete IP will be returned 2924 */ 2925 function short_ipv6($ip, $length) 2926 { 2927 if ($length < 1) 2928 { 2929 return ''; 2930 } 2931 2932 // extend IPv6 addresses 2933 $blocks = substr_count($ip, ':') + 1; 2934 if ($blocks < 9) 2935 { 2936 $ip = str_replace('::', ':' . str_repeat('0000:', 9 - $blocks), $ip); 2937 } 2938 if ($ip[0] == ':') 2939 { 2940 $ip = '0000' . $ip; 2941 } 2942 if ($length < 4) 2943 { 2944 $ip = implode(':', array_slice(explode(':', $ip), 0, 1 + $length)); 2945 } 2946 2947 return $ip; 2948 } 2949 2950 /** 2951 * Normalises an internet protocol address, 2952 * also checks whether the specified address is valid. 2953 * 2954 * IPv4 addresses are returned 'as is'. 2955 * 2956 * IPv6 addresses are normalised according to 2957 * A Recommendation for IPv6 Address Text Representation 2958 * http://tools.ietf.org/html/draft-ietf-6man-text-addr-representation-07 2959 * 2960 * @param string $address IP address 2961 * 2962 * @return mixed false if specified address is not valid, 2963 * string otherwise 2964 */ 2965 function phpbb_ip_normalise(string $address) 2966 { 2967 $ip_normalised = false; 2968 2969 if (filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) 2970 { 2971 $ip_normalised = $address; 2972 } 2973 else if (filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) 2974 { 2975 $ip_normalised = inet_ntop(inet_pton($address)); 2976 2977 // If is ipv4 2978 if (stripos($ip_normalised, '::ffff:') === 0) 2979 { 2980 $ip_normalised = substr($ip_normalised, 7); 2981 } 2982 } 2983 2984 return $ip_normalised; 2985 } 2986 2987 // Handler, header and footer 2988 2989 /** 2990 * Error and message handler, call with trigger_error if read 2991 */ 2992 function msg_handler($errno, $msg_text, $errfile, $errline) 2993 { 2994 global $cache, $db, $auth, $template, $config, $user, $request; 2995 global $phpbb_root_path, $msg_title, $msg_long_text, $phpbb_log; 2996 global $phpbb_container; 2997 2998 // Do not display notices if we suppress them via @ 2999 if (error_reporting() == 0 && $errno != E_USER_ERROR && $errno != E_USER_WARNING && $errno != E_USER_NOTICE) 3000 { 3001 return; 3002 } 3003 3004 // Message handler is stripping text. In case we need it, we are possible to define long text... 3005 if (isset($msg_long_text) && $msg_long_text && !$msg_text) 3006 { 3007 $msg_text = $msg_long_text; 3008 } 3009 3010 switch ($errno) 3011 { 3012 case E_NOTICE: 3013 case E_WARNING: 3014 3015 // Check the error reporting level and return if the error level does not match 3016 // If DEBUG is defined the default level is E_ALL 3017 if (($errno & ($phpbb_container != null && $phpbb_container->getParameter('debug.show_errors') ? E_ALL : error_reporting())) == 0) 3018 { 3019 return; 3020 } 3021 3022 if (strpos($errfile, 'cache') === false && strpos($errfile, 'template.') === false) 3023 { 3024 $errfile = phpbb_filter_root_path($errfile); 3025 $msg_text = phpbb_filter_root_path($msg_text); 3026 $error_name = ($errno === E_WARNING) ? 'PHP Warning' : 'PHP Notice'; 3027 echo '<b>[phpBB Debug] ' . $error_name . '</b>: in file <b>' . $errfile . '</b> on line <b>' . $errline . '</b>: <b>' . $msg_text . '</b><br />' . "\n"; 3028 3029 // we are writing an image - the user won't see the debug, so let's place it in the log 3030 if (defined('IMAGE_OUTPUT') || defined('IN_CRON')) 3031 { 3032 $phpbb_log->add('critical', $user->data['user_id'], $user->ip, 'LOG_IMAGE_GENERATION_ERROR', false, array($errfile, $errline, $msg_text)); 3033 } 3034 // echo '<br /><br />BACKTRACE<br />' . get_backtrace() . '<br />' . "\n"; 3035 } 3036 3037 return; 3038 3039 break; 3040 3041 case E_USER_ERROR: 3042 3043 if (!empty($user) && $user->is_setup()) 3044 { 3045 $msg_text = (!empty($user->lang[$msg_text])) ? $user->lang[$msg_text] : $msg_text; 3046 $msg_title = (!isset($msg_title)) ? $user->lang['GENERAL_ERROR'] : ((!empty($user->lang[$msg_title])) ? $user->lang[$msg_title] : $msg_title); 3047 3048 $l_return_index = sprintf($user->lang['RETURN_INDEX'], '<a href="' . $phpbb_root_path . '">', '</a>'); 3049 $l_notify = ''; 3050 3051 if (!empty($config['board_contact'])) 3052 { 3053 $l_notify = '<p>' . sprintf($user->lang['NOTIFY_ADMIN_EMAIL'], $config['board_contact']) . '</p>'; 3054 } 3055 } 3056 else 3057 { 3058 $msg_title = 'General Error'; 3059 $l_return_index = '<a href="' . $phpbb_root_path . '">Return to index page</a>'; 3060 $l_notify = ''; 3061 3062 if (!empty($config['board_contact'])) 3063 { 3064 $l_notify = '<p>Please notify the board administrator or webmaster: <a href="mailto:' . $config['board_contact'] . '">' . $config['board_contact'] . '</a></p>'; 3065 } 3066 } 3067 3068 $log_text = $msg_text; 3069 $backtrace = get_backtrace(); 3070 if ($backtrace) 3071 { 3072 $log_text .= '<br /><br />BACKTRACE<br />' . $backtrace; 3073 } 3074 3075 if (defined('IN_INSTALL') || ($phpbb_container != null && $phpbb_container->getParameter('debug.show_errors')) || isset($auth) && $auth->acl_get('a_')) 3076 { 3077 $msg_text = $log_text; 3078 3079 // If this is defined there already was some output 3080 // So let's not break it 3081 if (defined('IN_DB_UPDATE')) 3082 { 3083 echo '<div class="errorbox">' . $msg_text . '</div>'; 3084 3085 $db->sql_return_on_error(true); 3086 phpbb_end_update($cache, $config); 3087 } 3088 } 3089 3090 if ((defined('IN_CRON') || defined('IMAGE_OUTPUT')) && isset($db)) 3091 { 3092 // let's avoid loops 3093 $db->sql_return_on_error(true); 3094 $phpbb_log->add('critical', $user->data['user_id'], $user->ip, 'LOG_GENERAL_ERROR', false, array($msg_title, $log_text)); 3095 $db->sql_return_on_error(false); 3096 } 3097 3098 // Do not send 200 OK, but service unavailable on errors 3099 send_status_line(503, 'Service Unavailable'); 3100 3101 garbage_collection(); 3102 3103 // Try to not call the adm page data... 3104 3105 echo '<!DOCTYPE html>'; 3106 echo '<html dir="ltr">'; 3107 echo '<head>'; 3108 echo '<meta charset="utf-8">'; 3109 echo '<meta http-equiv="X-UA-Compatible" content="IE=edge">'; 3110 echo '<title>' . $msg_title . '</title>'; 3111 echo '<style type="text/css">' . "\n" . '/* <![CDATA[ */' . "\n"; 3112 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; } '; 3113 echo 'a:link, a:active, a:visited { color: #006699; text-decoration: none; } a:hover { color: #DD6900; text-decoration: underline; } '; 3114 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; } '; 3115 echo '.panel { margin: 4px 0; background-color: #FFFFFF; border: solid 1px #A9B8C2; } '; 3116 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; } '; 3117 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; } '; 3118 echo "\n" . '/* ]]> */' . "\n"; 3119 echo '</style>'; 3120 echo '</head>'; 3121 echo '<body id="errorpage">'; 3122 echo '<div id="wrap">'; 3123 echo ' <div id="page-header">'; 3124 echo ' ' . $l_return_index; 3125 echo ' </div>'; 3126 echo ' <div id="acp">'; 3127 echo ' <div class="panel">'; 3128 echo ' <div id="content">'; 3129 echo ' <h1>' . $msg_title . '</h1>'; 3130 3131 echo ' <div>' . $msg_text . '</div>'; 3132 3133 echo $l_notify; 3134 3135 echo ' </div>'; 3136 echo ' </div>'; 3137 echo ' </div>'; 3138 echo ' <div id="page-footer">'; 3139 echo ' Powered by <a href="https://www.phpbb.com/">phpBB</a>® Forum Software © phpBB Limited'; 3140 echo ' </div>'; 3141 echo '</div>'; 3142 echo '</body>'; 3143 echo '</html>'; 3144 3145 exit_handler(); 3146 3147 // On a fatal error (and E_USER_ERROR *is* fatal) we never want other scripts to continue and force an exit here. 3148 exit; 3149 break; 3150 3151 case E_USER_WARNING: 3152 case E_USER_NOTICE: 3153 3154 define('IN_ERROR_HANDLER', true); 3155 3156 if (empty($user->data)) 3157 { 3158 $user->session_begin(); 3159 } 3160 3161 // We re-init the auth array to get correct results on login/logout 3162 $auth->acl($user->data); 3163 3164 if (!$user->is_setup()) 3165 { 3166 $user->setup(); 3167 } 3168 3169 if ($msg_text == 'ERROR_NO_ATTACHMENT' || $msg_text == 'NO_FORUM' || $msg_text == 'NO_TOPIC' || $msg_text == 'NO_USER') 3170 { 3171 send_status_line(404, 'Not Found'); 3172 } 3173 3174 $msg_text = (!empty($user->lang[$msg_text])) ? $user->lang[$msg_text] : $msg_text; 3175 $msg_title = (!isset($msg_title)) ? $user->lang['INFORMATION'] : ((!empty($user->lang[$msg_title])) ? $user->lang[$msg_title] : $msg_title); 3176 3177 if (!defined('HEADER_INC')) 3178 { 3179 if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin']) 3180 { 3181 adm_page_header($msg_title); 3182 } 3183 else 3184 { 3185 page_header($msg_title); 3186 } 3187 } 3188 3189 $template->set_filenames(array( 3190 'body' => 'message_body.html') 3191 ); 3192 3193 $template->assign_vars(array( 3194 'MESSAGE_TITLE' => $msg_title, 3195 'MESSAGE_TEXT' => $msg_text, 3196 'S_USER_WARNING' => ($errno == E_USER_WARNING) ? true : false, 3197 'S_USER_NOTICE' => ($errno == E_USER_NOTICE) ? true : false) 3198 ); 3199 3200 if ($request->is_ajax()) 3201 { 3202 global $refresh_data; 3203 3204 $json_response = new \phpbb\json_response; 3205 $json_response->send(array( 3206 'MESSAGE_TITLE' => $msg_title, 3207 'MESSAGE_TEXT' => $msg_text, 3208 'S_USER_WARNING' => ($errno == E_USER_WARNING) ? true : false, 3209 'S_USER_NOTICE' => ($errno == E_USER_NOTICE) ? true : false, 3210 'REFRESH_DATA' => (!empty($refresh_data)) ? $refresh_data : null 3211 )); 3212 } 3213 3214 // We do not want the cron script to be called on error messages 3215 define('IN_CRON', true); 3216 3217 if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin']) 3218 { 3219 adm_page_footer(); 3220 } 3221 else 3222 { 3223 page_footer(); 3224 } 3225 3226 exit_handler(); 3227 break; 3228 3229 // PHP4 compatibility 3230 case E_DEPRECATED: 3231 return true; 3232 break; 3233 } 3234 3235 // If we notice an error not handled here we pass this back to PHP by returning false 3236 // This may not work for all php versions 3237 return false; 3238 } 3239 3240 /** 3241 * Removes absolute path to phpBB root directory from error messages 3242 * and converts backslashes to forward slashes. 3243 * 3244 * @param string $errfile Absolute file path 3245 * (e.g. /var/www/phpbb3/phpBB/includes/functions.php) 3246 * Please note that if $errfile is outside of the phpBB root, 3247 * the root path will not be found and can not be filtered. 3248 * @return string Relative file path 3249 * (e.g. /includes/functions.php) 3250 */ 3251 function phpbb_filter_root_path($errfile) 3252 { 3253 global $phpbb_filesystem; 3254 3255 static $root_path; 3256 3257 if (empty($root_path)) 3258 { 3259 if ($phpbb_filesystem) 3260 { 3261 $root_path = $phpbb_filesystem->realpath(__DIR__ . '/../'); 3262 } 3263 else 3264 { 3265 $filesystem = new \phpbb\filesystem\filesystem(); 3266 $root_path = $filesystem->realpath(__DIR__ . '/../'); 3267 } 3268 } 3269 3270 return str_replace(array($root_path, '\\'), array('[ROOT]', '/'), $errfile); 3271 } 3272 3273 /** 3274 * Queries the session table to get information about online guests 3275 * @param int $item_id Limits the search to the item with this id 3276 * @param string $item The name of the item which is stored in the session table as session_{$item}_id 3277 * @return int The number of active distinct guest sessions 3278 */ 3279 function obtain_guest_count($item_id = 0, $item = 'forum') 3280 { 3281 global $db, $config; 3282 3283 if ($item_id) 3284 { 3285 $reading_sql = ' AND s.session_' . $item . '_id = ' . (int) $item_id; 3286 } 3287 else 3288 { 3289 $reading_sql = ''; 3290 } 3291 $time = (time() - (intval($config['load_online_time']) * 60)); 3292 3293 // Get number of online guests 3294 3295 if ($db->get_sql_layer() === 'sqlite3') 3296 { 3297 $sql = 'SELECT COUNT(session_ip) as num_guests 3298 FROM ( 3299 SELECT DISTINCT s.session_ip 3300 FROM ' . SESSIONS_TABLE . ' s 3301 WHERE s.session_user_id = ' . ANONYMOUS . ' 3302 AND s.session_time >= ' . ($time - ((int) ($time % 60))) . 3303 $reading_sql . 3304 ')'; 3305 } 3306 else 3307 { 3308 $sql = 'SELECT COUNT(DISTINCT s.session_ip) as num_guests 3309 FROM ' . SESSIONS_TABLE . ' s 3310 WHERE s.session_user_id = ' . ANONYMOUS . ' 3311 AND s.session_time >= ' . ($time - ((int) ($time % 60))) . 3312 $reading_sql; 3313 } 3314 $result = $db->sql_query($sql); 3315 $guests_online = (int) $db->sql_fetchfield('num_guests'); 3316 $db->sql_freeresult($result); 3317 3318 return $guests_online; 3319 } 3320 3321 /** 3322 * Queries the session table to get information about online users 3323 * @param int $item_id Limits the search to the item with this id 3324 * @param string $item The name of the item which is stored in the session table as session_{$item}_id 3325 * @return array An array containing the ids of online, hidden and visible users, as well as statistical info 3326 */ 3327 function obtain_users_online($item_id = 0, $item = 'forum') 3328 { 3329 global $db, $config; 3330 3331 $reading_sql = ''; 3332 if ($item_id !== 0) 3333 { 3334 $reading_sql = ' AND s.session_' . $item . '_id = ' . (int) $item_id; 3335 } 3336 3337 $online_users = array( 3338 'online_users' => array(), 3339 'hidden_users' => array(), 3340 'total_online' => 0, 3341 'visible_online' => 0, 3342 'hidden_online' => 0, 3343 'guests_online' => 0, 3344 ); 3345 3346 if ($config['load_online_guests']) 3347 { 3348 $online_users['guests_online'] = obtain_guest_count($item_id, $item); 3349 } 3350 3351 // a little discrete magic to cache this for 30 seconds 3352 $time = (time() - (intval($config['load_online_time']) * 60)); 3353 3354 $sql = 'SELECT s.session_user_id, s.session_ip, s.session_viewonline 3355 FROM ' . SESSIONS_TABLE . ' s 3356 WHERE s.session_time >= ' . ($time - ((int) ($time % 30))) . 3357 $reading_sql . 3358 ' AND s.session_user_id <> ' . ANONYMOUS; 3359 $result = $db->sql_query($sql); 3360 3361 while ($row = $db->sql_fetchrow($result)) 3362 { 3363 // Skip multiple sessions for one user 3364 if (!isset($online_users['online_users'][$row['session_user_id']])) 3365 { 3366 $online_users['online_users'][$row['session_user_id']] = (int) $row['session_user_id']; 3367 if ($row['session_viewonline']) 3368 { 3369 $online_users['visible_online']++; 3370 } 3371 else 3372 { 3373 $online_users['hidden_users'][$row['session_user_id']] = (int) $row['session_user_id']; 3374 $online_users['hidden_online']++; 3375 } 3376 } 3377 } 3378 $online_users['total_online'] = $online_users['guests_online'] + $online_users['visible_online'] + $online_users['hidden_online']; 3379 $db->sql_freeresult($result); 3380 3381 return $online_users; 3382 } 3383 3384 /** 3385 * Uses the result of obtain_users_online to generate a localized, readable representation. 3386 * @param mixed $online_users result of obtain_users_online - array with user_id lists for total, hidden and visible users, and statistics 3387 * @param int $item_id Indicate that the data is limited to one item and not global 3388 * @param string $item The name of the item which is stored in the session table as session_{$item}_id 3389 * @return array An array containing the string for output to the template 3390 */ 3391 function obtain_users_online_string($online_users, $item_id = 0, $item = 'forum') 3392 { 3393 global $config, $db, $user, $auth, $phpbb_dispatcher; 3394 3395 $user_online_link = $rowset = array(); 3396 // Need caps version of $item for language-strings 3397 $item_caps = strtoupper($item); 3398 3399 if (count($online_users['online_users'])) 3400 { 3401 $sql_ary = array( 3402 'SELECT' => 'u.username, u.username_clean, u.user_id, u.user_type, u.user_allow_viewonline, u.user_colour', 3403 'FROM' => array( 3404 USERS_TABLE => 'u', 3405 ), 3406 'WHERE' => $db->sql_in_set('u.user_id', $online_users['online_users']), 3407 'ORDER_BY' => 'u.username_clean ASC', 3408 ); 3409 3410 /** 3411 * Modify SQL query to obtain online users data 3412 * 3413 * @event core.obtain_users_online_string_sql 3414 * @var array online_users Array with online users data 3415 * from obtain_users_online() 3416 * @var int item_id Restrict online users to item id 3417 * @var string item Restrict online users to a certain 3418 * session item, e.g. forum for 3419 * session_forum_id 3420 * @var array sql_ary SQL query array to obtain users online data 3421 * @since 3.1.4-RC1 3422 * @changed 3.1.7-RC1 Change sql query into array and adjust var accordingly. Allows extension authors the ability to adjust the sql_ary. 3423 */ 3424 $vars = array('online_users', 'item_id', 'item', 'sql_ary'); 3425 extract($phpbb_dispatcher->trigger_event('core.obtain_users_online_string_sql', compact($vars))); 3426 3427 $result = $db->sql_query($db->sql_build_query('SELECT', $sql_ary)); 3428 $rowset = $db->sql_fetchrowset($result); 3429 $db->sql_freeresult($result); 3430 3431 foreach ($rowset as $row) 3432 { 3433 // User is logged in and therefore not a guest 3434 if ($row['user_id'] != ANONYMOUS) 3435 { 3436 if (isset($online_users['hidden_users'][$row['user_id']])) 3437 { 3438 $row['username'] = '<em>' . $row['username'] . '</em>'; 3439 } 3440 3441 if (!isset($online_users['hidden_users'][$row['user_id']]) || $auth->acl_get('u_viewonline') || $row['user_id'] === $user->data['user_id']) 3442 { 3443 $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']); 3444 } 3445 } 3446 } 3447 } 3448 3449 /** 3450 * Modify online userlist data 3451 * 3452 * @event core.obtain_users_online_string_before_modify 3453 * @var array online_users Array with online users data 3454 * from obtain_users_online() 3455 * @var int item_id Restrict online users to item id 3456 * @var string item Restrict online users to a certain 3457 * session item, e.g. forum for 3458 * session_forum_id 3459 * @var array rowset Array with online users data 3460 * @var array user_online_link Array with online users items (usernames) 3461 * @since 3.1.10-RC1 3462 */ 3463 $vars = array( 3464 'online_users', 3465 'item_id', 3466 'item', 3467 'rowset', 3468 'user_online_link', 3469 ); 3470 extract($phpbb_dispatcher->trigger_event('core.obtain_users_online_string_before_modify', compact($vars))); 3471 3472 $online_userlist = implode(', ', $user_online_link); 3473 3474 if (!$online_userlist) 3475 { 3476 $online_userlist = $user->lang['NO_ONLINE_USERS']; 3477 } 3478 3479 if ($item_id === 0) 3480 { 3481 $online_userlist = $user->lang['REGISTERED_USERS'] . ' ' . $online_userlist; 3482 } 3483 else if ($config['load_online_guests']) 3484 { 3485 $online_userlist = $user->lang('BROWSING_' . $item_caps . '_GUESTS', $online_users['guests_online'], $online_userlist); 3486 } 3487 else 3488 { 3489 $online_userlist = sprintf($user->lang['BROWSING_' . $item_caps], $online_userlist); 3490 } 3491 // Build online listing 3492 $visible_online = $user->lang('REG_USERS_TOTAL', (int) $online_users['visible_online']); 3493 $hidden_online = $user->lang('HIDDEN_USERS_TOTAL', (int) $online_users['hidden_online']); 3494 3495 if ($config['load_online_guests']) 3496 { 3497 $guests_online = $user->lang('GUEST_USERS_TOTAL', (int) $online_users['guests_online']); 3498 $l_online_users = $user->lang('ONLINE_USERS_TOTAL_GUESTS', (int) $online_users['total_online'], $visible_online, $hidden_online, $guests_online); 3499 } 3500 else 3501 { 3502 $l_online_users = $user->lang('ONLINE_USERS_TOTAL', (int) $online_users['total_online'], $visible_online, $hidden_online); 3503 } 3504 3505 /** 3506 * Modify online userlist data 3507 * 3508 * @event core.obtain_users_online_string_modify 3509 * @var array online_users Array with online users data 3510 * from obtain_users_online() 3511 * @var int item_id Restrict online users to item id 3512 * @var string item Restrict online users to a certain 3513 * session item, e.g. forum for 3514 * session_forum_id 3515 * @var array rowset Array with online users data 3516 * @var array user_online_link Array with online users items (usernames) 3517 * @var string online_userlist String containing users online list 3518 * @var string l_online_users String with total online users count info 3519 * @since 3.1.4-RC1 3520 */ 3521 $vars = array( 3522 'online_users', 3523 'item_id', 3524 'item', 3525 'rowset', 3526 'user_online_link', 3527 'online_userlist', 3528 'l_online_users', 3529 ); 3530 extract($phpbb_dispatcher->trigger_event('core.obtain_users_online_string_modify', compact($vars))); 3531 3532 return array( 3533 'online_userlist' => $online_userlist, 3534 'l_online_users' => $l_online_users, 3535 ); 3536 } 3537 3538 /** 3539 * Get option bitfield from custom data 3540 * 3541 * @param int $bit The bit/value to get 3542 * @param int $data Current bitfield to check 3543 * @return bool Returns true if value of constant is set in bitfield, else false 3544 */ 3545 function phpbb_optionget($bit, $data) 3546 { 3547 return ($data & 1 << (int) $bit) ? true : false; 3548 } 3549 3550 /** 3551 * Set option bitfield 3552 * 3553 * @param int $bit The bit/value to set/unset 3554 * @param bool $set True if option should be set, false if option should be unset. 3555 * @param int $data Current bitfield to change 3556 * 3557 * @return int The new bitfield 3558 */ 3559 function phpbb_optionset($bit, $set, $data) 3560 { 3561 if ($set && !($data & 1 << $bit)) 3562 { 3563 $data += 1 << $bit; 3564 } 3565 else if (!$set && ($data & 1 << $bit)) 3566 { 3567 $data -= 1 << $bit; 3568 } 3569 3570 return $data; 3571 } 3572 3573 3574 /** 3575 * Escapes and quotes a string for use as an HTML/XML attribute value. 3576 * 3577 * This is a port of Python xml.sax.saxutils quoteattr. 3578 * 3579 * The function will attempt to choose a quote character in such a way as to 3580 * avoid escaping quotes in the string. If this is not possible the string will 3581 * be wrapped in double quotes and double quotes will be escaped. 3582 * 3583 * @param string $data The string to be escaped 3584 * @param array $entities Associative array of additional entities to be escaped 3585 * @return string Escaped and quoted string 3586 */ 3587 function phpbb_quoteattr($data, $entities = null) 3588 { 3589 $data = str_replace('&', '&', $data); 3590 $data = str_replace('>', '>', $data); 3591 $data = str_replace('<', '<', $data); 3592 3593 $data = str_replace("\n", ' ', $data); 3594 $data = str_replace("\r", ' ', $data); 3595 $data = str_replace("\t", '	', $data); 3596 3597 if (!empty($entities)) 3598 { 3599 $data = str_replace(array_keys($entities), array_values($entities), $data); 3600 } 3601 3602 if (strpos($data, '"') !== false) 3603 { 3604 if (strpos($data, "'") !== false) 3605 { 3606 $data = '"' . str_replace('"', '"', $data) . '"'; 3607 } 3608 else 3609 { 3610 $data = "'" . $data . "'"; 3611 } 3612 } 3613 else 3614 { 3615 $data = '"' . $data . '"'; 3616 } 3617 3618 return $data; 3619 } 3620 3621 /** 3622 * Get user avatar 3623 * 3624 * @param array $user_row Row from the users table 3625 * @param string $alt Optional language string for alt tag within image, can be a language key or text 3626 * @param bool $ignore_config Ignores the config-setting, to be still able to view the avatar in the UCP 3627 * @param bool $lazy If true, will be lazy loaded (requires JS) 3628 * 3629 * @return string Avatar html 3630 */ 3631 function phpbb_get_user_avatar($user_row, $alt = 'USER_AVATAR', $ignore_config = false, $lazy = false) 3632 { 3633 $row = \phpbb\avatar\manager::clean_row($user_row, 'user'); 3634 return phpbb_get_avatar($row, $alt, $ignore_config, $lazy); 3635 } 3636 3637 /** 3638 * Get group avatar 3639 * 3640 * @param array $group_row Row from the groups table 3641 * @param string $alt Optional language string for alt tag within image, can be a language key or text 3642 * @param bool $ignore_config Ignores the config-setting, to be still able to view the avatar in the UCP 3643 * @param bool $lazy If true, will be lazy loaded (requires JS) 3644 * 3645 * @return string Avatar html 3646 */ 3647 function phpbb_get_group_avatar($group_row, $alt = 'GROUP_AVATAR', $ignore_config = false, $lazy = false) 3648 { 3649 $row = \phpbb\avatar\manager::clean_row($group_row, 'group'); 3650 return phpbb_get_avatar($row, $alt, $ignore_config, $lazy); 3651 } 3652 3653 /** 3654 * Get avatar 3655 * 3656 * @param array $row Row cleaned by \phpbb\avatar\manager::clean_row 3657 * @param string $alt Optional language string for alt tag within image, can be a language key or text 3658 * @param bool $ignore_config Ignores the config-setting, to be still able to view the avatar in the UCP 3659 * @param bool $lazy If true, will be lazy loaded (requires JS) 3660 * 3661 * @return string Avatar html 3662 */ 3663 function phpbb_get_avatar($row, $alt, $ignore_config = false, $lazy = false) 3664 { 3665 global $user, $config; 3666 global $phpbb_container, $phpbb_dispatcher; 3667 3668 if (!$config['allow_avatar'] && !$ignore_config) 3669 { 3670 return ''; 3671 } 3672 3673 $avatar_data = array( 3674 'src' => $row['avatar'], 3675 'width' => $row['avatar_width'], 3676 'height' => $row['avatar_height'], 3677 ); 3678 3679 /* @var $phpbb_avatar_manager \phpbb\avatar\manager */ 3680 $phpbb_avatar_manager = $phpbb_container->get('avatar.manager'); 3681 $driver = $phpbb_avatar_manager->get_driver($row['avatar_type'], !$ignore_config); 3682 $html = ''; 3683 3684 if ($driver) 3685 { 3686 $html = $driver->get_custom_html($user, $row, $alt); 3687 $avatar_data = $driver->get_data($row); 3688 } 3689 else 3690 { 3691 $avatar_data['src'] = ''; 3692 } 3693 3694 if (empty($html) && !empty($avatar_data['src'])) 3695 { 3696 if ($lazy) 3697 { 3698 // Determine board url - we may need it later 3699 $board_url = generate_board_url() . '/'; 3700 // This path is sent with the base template paths in the assign_vars() 3701 // call below. We need to correct it in case we are accessing from a 3702 // controller because the web paths will be incorrect otherwise. 3703 $phpbb_path_helper = $phpbb_container->get('path_helper'); 3704 $corrected_path = $phpbb_path_helper->get_web_root_path(); 3705 3706 $web_path = (defined('PHPBB_USE_BOARD_URL_PATH') && PHPBB_USE_BOARD_URL_PATH) ? $board_url : $corrected_path; 3707 3708 $theme = "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/theme'; 3709 3710 $src = 'src="' . $theme . '/images/no_avatar.gif" data-src="' . $avatar_data['src'] . '"'; 3711 } 3712 else 3713 { 3714 $src = 'src="' . $avatar_data['src'] . '"'; 3715 } 3716 3717 $html = '<img class="avatar" ' . $src . ' ' . 3718 ($avatar_data['width'] ? ('width="' . $avatar_data['width'] . '" ') : '') . 3719 ($avatar_data['height'] ? ('height="' . $avatar_data['height'] . '" ') : '') . 3720 'alt="' . ((!empty($user->lang[$alt])) ? $user->lang[$alt] : $alt) . '" />'; 3721 } 3722 3723 /** 3724 * Event to modify HTML <img> tag of avatar 3725 * 3726 * @event core.get_avatar_after 3727 * @var array row Row cleaned by \phpbb\avatar\manager::clean_row 3728 * @var string alt Optional language string for alt tag within image, can be a language key or text 3729 * @var bool ignore_config Ignores the config-setting, to be still able to view the avatar in the UCP 3730 * @var array avatar_data The HTML attributes for avatar <img> tag 3731 * @var string html The HTML <img> tag of generated avatar 3732 * @since 3.1.6-RC1 3733 */ 3734 $vars = array('row', 'alt', 'ignore_config', 'avatar_data', 'html'); 3735 extract($phpbb_dispatcher->trigger_event('core.get_avatar_after', compact($vars))); 3736 3737 return $html; 3738 } 3739 3740 /** 3741 * Generate page header 3742 */ 3743 function page_header($page_title = '', $display_online_list = false, $item_id = 0, $item = 'forum', $send_headers = true) 3744 { 3745 global $db, $config, $template, $SID, $_SID, $_EXTRA_URL, $user, $auth, $phpEx, $phpbb_root_path; 3746 global $phpbb_dispatcher, $request, $phpbb_container, $phpbb_admin_path; 3747 3748 if (defined('HEADER_INC')) 3749 { 3750 return; 3751 } 3752 3753 define('HEADER_INC', true); 3754 3755 // A listener can set this variable to `true` when it overrides this function 3756 $page_header_override = false; 3757 3758 /** 3759 * Execute code and/or overwrite page_header() 3760 * 3761 * @event core.page_header 3762 * @var string page_title Page title 3763 * @var bool display_online_list Do we display online users list 3764 * @var string item Restrict online users to a certain 3765 * session item, e.g. forum for 3766 * session_forum_id 3767 * @var int item_id Restrict online users to item id 3768 * @var bool page_header_override Shall we return instead of running 3769 * the rest of page_header() 3770 * @since 3.1.0-a1 3771 */ 3772 $vars = array('page_title', 'display_online_list', 'item_id', 'item', 'page_header_override'); 3773 extract($phpbb_dispatcher->trigger_event('core.page_header', compact($vars))); 3774 3775 if ($page_header_override) 3776 { 3777 return; 3778 } 3779 3780 // gzip_compression 3781 if ($config['gzip_compress']) 3782 { 3783 // to avoid partially compressed output resulting in blank pages in 3784 // the browser or error messages, compression is disabled in a few cases: 3785 // 3786 // 1) if headers have already been sent, this indicates plaintext output 3787 // has been started so further content must not be compressed 3788 // 2) the length of the current output buffer is non-zero. This means 3789 // there is already some uncompressed content in this output buffer 3790 // so further output must not be compressed 3791 // 3) if more than one level of output buffering is used because we 3792 // cannot test all output buffer level content lengths. One level 3793 // could be caused by php.ini output_buffering. Anything 3794 // beyond that is manual, so the code wrapping phpBB in output buffering 3795 // can easily compress the output itself. 3796 // 3797 if (@extension_loaded('zlib') && !headers_sent() && ob_get_level() <= 1 && ob_get_length() == 0) 3798 { 3799 ob_start('ob_gzhandler'); 3800 } 3801 } 3802 3803 $user->update_session_infos(); 3804 3805 // Generate logged in/logged out status 3806 if ($user->data['user_id'] != ANONYMOUS) 3807 { 3808 $u_login_logout = append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=logout', true, $user->session_id); 3809 $l_login_logout = $user->lang['LOGOUT']; 3810 } 3811 else 3812 { 3813 $redirect = $request->variable('redirect', rawurlencode($user->page['page'])); 3814 $u_login_logout = append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=login&redirect=' . $redirect); 3815 $l_login_logout = $user->lang['LOGIN']; 3816 } 3817 3818 // Last visit date/time 3819 $s_last_visit = ($user->data['user_id'] != ANONYMOUS) ? $user->format_date($user->data['session_last_visit']) : ''; 3820 3821 // Get users online list ... if required 3822 $l_online_users = $online_userlist = $l_online_record = $l_online_time = ''; 3823 3824 if ($config['load_online'] && $config['load_online_time'] && $display_online_list) 3825 { 3826 /** 3827 * Load online data: 3828 * For obtaining another session column use $item and $item_id in the function-parameter, whereby the column is session_{$item}_id. 3829 */ 3830 $item_id = max($item_id, 0); 3831 3832 $online_users = obtain_users_online($item_id, $item); 3833 $user_online_strings = obtain_users_online_string($online_users, $item_id, $item); 3834 3835 $l_online_users = $user_online_strings['l_online_users']; 3836 $online_userlist = $user_online_strings['online_userlist']; 3837 $total_online_users = $online_users['total_online']; 3838 3839 if ($total_online_users > $config['record_online_users']) 3840 { 3841 $config->set('record_online_users', $total_online_users, false); 3842 $config->set('record_online_date', time(), false); 3843 } 3844 3845 $l_online_record = $user->lang('RECORD_ONLINE_USERS', (int) $config['record_online_users'], $user->format_date($config['record_online_date'], false, true)); 3846 3847 $l_online_time = $user->lang('VIEW_ONLINE_TIMES', (int) $config['load_online_time']); 3848 } 3849 3850 $s_privmsg_new = false; 3851 3852 // Check for new private messages if user is logged in 3853 if (!empty($user->data['is_registered'])) 3854 { 3855 if ($user->data['user_new_privmsg']) 3856 { 3857 if (!$user->data['user_last_privmsg'] || $user->data['user_last_privmsg'] > $user->data['session_last_visit']) 3858 { 3859 $sql = 'UPDATE ' . USERS_TABLE . ' 3860 SET user_last_privmsg = ' . $user->data['session_last_visit'] . ' 3861 WHERE user_id = ' . $user->data['user_id']; 3862 $db->sql_query($sql); 3863 3864 $s_privmsg_new = true; 3865 } 3866 else 3867 { 3868 $s_privmsg_new = false; 3869 } 3870 } 3871 else 3872 { 3873 $s_privmsg_new = false; 3874 } 3875 } 3876 3877 // Negative forum and topic IDs are not allowed 3878 $forum_id = max(0, $request->variable('f', 0)); 3879 $topic_id = max(0, $request->variable('t', 0)); 3880 3881 $s_feed_news = false; 3882 3883 // Get option for news 3884 if ($config['feed_enable']) 3885 { 3886 $sql = 'SELECT forum_id 3887 FROM ' . FORUMS_TABLE . ' 3888 WHERE ' . $db->sql_bit_and('forum_options', FORUM_OPTION_FEED_NEWS, '<> 0'); 3889 $result = $db->sql_query_limit($sql, 1, 0, 600); 3890 $s_feed_news = (int) $db->sql_fetchfield('forum_id'); 3891 $db->sql_freeresult($result); 3892 } 3893 3894 // Determine board url - we may need it later 3895 $board_url = generate_board_url() . '/'; 3896 // This path is sent with the base template paths in the assign_vars() 3897 // call below. We need to correct it in case we are accessing from a 3898 // controller because the web paths will be incorrect otherwise. 3899 /* @var $phpbb_path_helper \phpbb\path_helper */ 3900 $phpbb_path_helper = $phpbb_container->get('path_helper'); 3901 $corrected_path = $phpbb_path_helper->get_web_root_path(); 3902 $web_path = (defined('PHPBB_USE_BOARD_URL_PATH') && PHPBB_USE_BOARD_URL_PATH) ? $board_url : $corrected_path; 3903 3904 // Send a proper content-language to the output 3905 $user_lang = $user->lang['USER_LANG']; 3906 if (strpos($user_lang, '-x-') !== false) 3907 { 3908 $user_lang = substr($user_lang, 0, strpos($user_lang, '-x-')); 3909 } 3910 3911 $s_search_hidden_fields = array(); 3912 if ($_SID) 3913 { 3914 $s_search_hidden_fields['sid'] = $_SID; 3915 } 3916 3917 if (!empty($_EXTRA_URL)) 3918 { 3919 foreach ($_EXTRA_URL as $url_param) 3920 { 3921 $url_param = explode('=', $url_param, 2); 3922 $s_search_hidden_fields[$url_param[0]] = $url_param[1]; 3923 } 3924 } 3925 3926 $dt = $user->create_datetime(); 3927 $timezone_offset = $user->lang(array('timezones', 'UTC_OFFSET'), phpbb_format_timezone_offset($dt->getOffset())); 3928 $timezone_name = $user->timezone->getName(); 3929 if (isset($user->lang['timezones'][$timezone_name])) 3930 { 3931 $timezone_name = $user->lang['timezones'][$timezone_name]; 3932 } 3933 3934 // Output the notifications 3935 $notifications = false; 3936 if ($config['load_notifications'] && $config['allow_board_notifications'] && $user->data['user_id'] != ANONYMOUS && $user->data['user_type'] != USER_IGNORE) 3937 { 3938 /* @var $phpbb_notifications \phpbb\notification\manager */ 3939 $phpbb_notifications = $phpbb_container->get('notification_manager'); 3940 3941 $notifications = $phpbb_notifications->load_notifications('notification.method.board', array( 3942 'all_unread' => true, 3943 'limit' => 5, 3944 )); 3945 3946 foreach ($notifications['notifications'] as $notification) 3947 { 3948 $template->assign_block_vars('notifications', $notification->prepare_for_display()); 3949 } 3950 } 3951 3952 /** @var \phpbb\controller\helper $controller_helper */ 3953 $controller_helper = $phpbb_container->get('controller.helper'); 3954 $notification_mark_hash = generate_link_hash('mark_all_notifications_read'); 3955 3956 $s_login_redirect = build_hidden_fields(array('redirect' => $phpbb_path_helper->remove_web_root_path(build_url()))); 3957 3958 // Add form token for login box, in case page is presenting a login form. 3959 add_form_key('login', '_LOGIN'); 3960 3961 /** 3962 * Workaround for missing template variable in pre phpBB 3.2.6 styles. 3963 * @deprecated 3.2.7 (To be removed: 4.0.0-a1) 3964 */ 3965 $form_token_login = $template->retrieve_var('S_FORM_TOKEN_LOGIN'); 3966 if (!empty($form_token_login)) 3967 { 3968 $s_login_redirect .= $form_token_login; 3969 // Remove S_FORM_TOKEN_LOGIN as it's already appended to S_LOGIN_REDIRECT 3970 $template->assign_var('S_FORM_TOKEN_LOGIN', ''); 3971 } 3972 3973 // The following assigns all _common_ variables that may be used at any point in a template. 3974 $template->assign_vars(array( 3975 'SITENAME' => $config['sitename'], 3976 'SITE_DESCRIPTION' => $config['site_desc'], 3977 'PAGE_TITLE' => $page_title, 3978 'SCRIPT_NAME' => str_replace('.' . $phpEx, '', $user->page['page_name']), 3979 'LAST_VISIT_DATE' => sprintf($user->lang['YOU_LAST_VISIT'], $s_last_visit), 3980 'LAST_VISIT_YOU' => $s_last_visit, 3981 'CURRENT_TIME' => sprintf($user->lang['CURRENT_TIME'], $user->format_date(time(), false, true)), 3982 'TOTAL_USERS_ONLINE' => $l_online_users, 3983 'LOGGED_IN_USER_LIST' => $online_userlist, 3984 'RECORD_USERS' => $l_online_record, 3985 3986 'PRIVATE_MESSAGE_COUNT' => (!empty($user->data['user_unread_privmsg'])) ? $user->data['user_unread_privmsg'] : 0, 3987 'CURRENT_USER_AVATAR' => phpbb_get_user_avatar($user->data), 3988 'CURRENT_USERNAME_SIMPLE' => get_username_string('no_profile', $user->data['user_id'], $user->data['username'], $user->data['user_colour']), 3989 'CURRENT_USERNAME_FULL' => get_username_string('full', $user->data['user_id'], $user->data['username'], $user->data['user_colour']), 3990 'UNREAD_NOTIFICATIONS_COUNT' => ($notifications !== false) ? $notifications['unread_count'] : '', 3991 'NOTIFICATIONS_COUNT' => ($notifications !== false) ? $notifications['unread_count'] : '', 3992 'U_VIEW_ALL_NOTIFICATIONS' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=ucp_notifications'), 3993 'U_MARK_ALL_NOTIFICATIONS' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=ucp_notifications&mode=notification_list&mark=all&token=' . $notification_mark_hash), 3994 'U_NOTIFICATION_SETTINGS' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=ucp_notifications&mode=notification_options'), 3995 'S_NOTIFICATIONS_DISPLAY' => $config['load_notifications'] && $config['allow_board_notifications'], 3996 3997 'S_USER_NEW_PRIVMSG' => $user->data['user_new_privmsg'], 3998 'S_USER_UNREAD_PRIVMSG' => $user->data['user_unread_privmsg'], 3999 'S_USER_NEW' => $user->data['user_new'], 4000 4001 'SID' => $SID, 4002 '_SID' => $_SID, 4003 'SESSION_ID' => $user->session_id, 4004 'ROOT_PATH' => $web_path, 4005 'BOARD_URL' => $board_url, 4006 4007 'L_LOGIN_LOGOUT' => $l_login_logout, 4008 'L_INDEX' => ($config['board_index_text'] !== '') ? $config['board_index_text'] : $user->lang['FORUM_INDEX'], 4009 'L_SITE_HOME' => ($config['site_home_text'] !== '') ? $config['site_home_text'] : $user->lang['HOME'], 4010 'L_ONLINE_EXPLAIN' => $l_online_time, 4011 4012 'U_PRIVATEMSGS' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&folder=inbox'), 4013 'U_RETURN_INBOX' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&folder=inbox'), 4014 'U_MEMBERLIST' => append_sid("{$phpbb_root_path}memberlist.$phpEx"), 4015 'U_VIEWONLINE' => ($auth->acl_gets('u_viewprofile', 'a_user', 'a_useradd', 'a_userdel')) ? append_sid("{$phpbb_root_path}viewonline.$phpEx") : '', 4016 'U_LOGIN_LOGOUT' => $u_login_logout, 4017 'U_INDEX' => append_sid("{$phpbb_root_path}index.$phpEx"), 4018 'U_SEARCH' => append_sid("{$phpbb_root_path}search.$phpEx"), 4019 'U_SITE_HOME' => $config['site_home_url'], 4020 'U_REGISTER' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=register'), 4021 'U_PROFILE' => append_sid("{$phpbb_root_path}ucp.$phpEx"), 4022 'U_USER_PROFILE' => get_username_string('profile', $user->data['user_id'], $user->data['username'], $user->data['user_colour']), 4023 'U_MODCP' => append_sid("{$phpbb_root_path}mcp.$phpEx", false, true, $user->session_id), 4024 'U_FAQ' => $controller_helper->route('phpbb_help_faq_controller'), 4025 'U_SEARCH_SELF' => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=egosearch'), 4026 'U_SEARCH_NEW' => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=newposts'), 4027 'U_SEARCH_UNANSWERED' => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=unanswered'), 4028 'U_SEARCH_UNREAD' => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=unreadposts'), 4029 'U_SEARCH_ACTIVE_TOPICS'=> append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=active_topics'), 4030 'U_DELETE_COOKIES' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=delete_cookies'), 4031 'U_CONTACT_US' => ($config['contact_admin_form_enable'] && $config['email_enable']) ? append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=contactadmin') : '', 4032 'U_TEAM' => (!$auth->acl_get('u_viewprofile')) ? '' : append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=team'), 4033 'U_TERMS_USE' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=terms'), 4034 'U_PRIVACY' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy'), 4035 'UA_PRIVACY' => addslashes(append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy')), 4036 'U_RESTORE_PERMISSIONS' => ($user->data['user_perm_from'] && $auth->acl_get('a_switchperm')) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=restore_perm') : '', 4037 'U_FEED' => $controller_helper->route('phpbb_feed_index'), 4038 4039 'S_USER_LOGGED_IN' => ($user->data['user_id'] != ANONYMOUS) ? true : false, 4040 'S_AUTOLOGIN_ENABLED' => ($config['allow_autologin']) ? true : false, 4041 'S_BOARD_DISABLED' => ($config['board_disable']) ? true : false, 4042 'S_REGISTERED_USER' => (!empty($user->data['is_registered'])) ? true : false, 4043 'S_IS_BOT' => (!empty($user->data['is_bot'])) ? true : false, 4044 'S_USER_LANG' => $user_lang, 4045 'S_USER_BROWSER' => (isset($user->data['session_browser'])) ? $user->data['session_browser'] : $user->lang['UNKNOWN_BROWSER'], 4046 'S_USERNAME' => $user->data['username'], 4047 'S_CONTENT_DIRECTION' => $user->lang['DIRECTION'], 4048 'S_CONTENT_FLOW_BEGIN' => ($user->lang['DIRECTION'] == 'ltr') ? 'left' : 'right', 4049 'S_CONTENT_FLOW_END' => ($user->lang['DIRECTION'] == 'ltr') ? 'right' : 'left', 4050 'S_CONTENT_ENCODING' => 'UTF-8', 4051 'S_TIMEZONE' => sprintf($user->lang['ALL_TIMES'], $timezone_offset, $timezone_name), 4052 'S_DISPLAY_ONLINE_LIST' => ($l_online_time) ? 1 : 0, 4053 'S_DISPLAY_SEARCH' => (!$config['load_search']) ? 0 : (isset($auth) ? ($auth->acl_get('u_search') && $auth->acl_getf_global('f_search')) : 1), 4054 'S_DISPLAY_PM' => ($config['allow_privmsg'] && !empty($user->data['is_registered']) && ($auth->acl_get('u_readpm') || $auth->acl_get('u_sendpm'))) ? true : false, 4055 'S_DISPLAY_MEMBERLIST' => (isset($auth)) ? $auth->acl_get('u_viewprofile') : 0, 4056 'S_NEW_PM' => ($s_privmsg_new) ? 1 : 0, 4057 'S_REGISTER_ENABLED' => ($config['require_activation'] != USER_ACTIVATION_DISABLE) ? true : false, 4058 'S_FORUM_ID' => $forum_id, 4059 'S_TOPIC_ID' => $topic_id, 4060 4061 '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)), 4062 'S_LOGIN_REDIRECT' => $s_login_redirect, 4063 4064 'S_ENABLE_FEEDS' => ($config['feed_enable']) ? true : false, 4065 'S_ENABLE_FEEDS_OVERALL' => ($config['feed_overall']) ? true : false, 4066 'S_ENABLE_FEEDS_FORUMS' => ($config['feed_overall_forums']) ? true : false, 4067 'S_ENABLE_FEEDS_TOPICS' => ($config['feed_topics_new']) ? true : false, 4068 'S_ENABLE_FEEDS_TOPICS_ACTIVE' => ($config['feed_topics_active']) ? true : false, 4069 'S_ENABLE_FEEDS_NEWS' => ($s_feed_news) ? true : false, 4070 4071 'S_LOAD_UNREADS' => (bool) $config['load_unreads_search'] && ($config['load_anon_lastread'] || !empty($user->data['is_registered'])), 4072 4073 'S_SEARCH_HIDDEN_FIELDS' => build_hidden_fields($s_search_hidden_fields), 4074 4075 'T_ASSETS_VERSION' => $config['assets_version'], 4076 'T_ASSETS_PATH' => "{$web_path}assets", 4077 'T_THEME_PATH' => "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/theme', 4078 'T_TEMPLATE_PATH' => "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/template', 4079 'T_SUPER_TEMPLATE_PATH' => "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/template', 4080 'T_IMAGES_PATH' => "{$web_path}images/", 4081 'T_SMILIES_PATH' => "{$web_path}{$config['smilies_path']}/", 4082 'T_AVATAR_PATH' => "{$web_path}{$config['avatar_path']}/", 4083 'T_AVATAR_GALLERY_PATH' => "{$web_path}{$config['avatar_gallery_path']}/", 4084 'T_ICONS_PATH' => "{$web_path}{$config['icons_path']}/", 4085 'T_RANKS_PATH' => "{$web_path}{$config['ranks_path']}/", 4086 'T_UPLOAD_PATH' => "{$web_path}{$config['upload_path']}/", 4087 'T_STYLESHEET_LINK' => "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/theme/stylesheet.css?assets_version=' . $config['assets_version'], 4088 'T_STYLESHEET_LANG_LINK'=> "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/theme/' . $user->lang_name . '/stylesheet.css?assets_version=' . $config['assets_version'], 4089 4090 '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'], 4091 4092 'T_JQUERY_LINK' => !empty($config['allow_cdn']) && !empty($config['load_jquery_url']) ? $config['load_jquery_url'] : "{$web_path}assets/javascript/jquery-3.6.0.min.js?assets_version=" . $config['assets_version'], 4093 'S_ALLOW_CDN' => !empty($config['allow_cdn']), 4094 'S_COOKIE_NOTICE' => !empty($config['cookie_notice']), 4095 4096 'T_THEME_NAME' => rawurlencode($user->style['style_path']), 4097 'T_THEME_LANG_NAME' => $user->lang_name, 4098 'T_TEMPLATE_NAME' => $user->style['style_path'], 4099 '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']), 4100 'T_IMAGES' => 'images', 4101 'T_SMILIES' => $config['smilies_path'], 4102 'T_AVATAR' => $config['avatar_path'], 4103 'T_AVATAR_GALLERY' => $config['avatar_gallery_path'], 4104 'T_ICONS' => $config['icons_path'], 4105 'T_RANKS' => $config['ranks_path'], 4106 'T_UPLOAD' => $config['upload_path'], 4107 4108 'SITE_LOGO_IMG' => $user->img('site_logo'), 4109 )); 4110 4111 $http_headers = array(); 4112 4113 if ($send_headers) 4114 { 4115 // An array of http headers that phpBB will set. The following event may override these. 4116 $http_headers += array( 4117 // application/xhtml+xml not used because of IE 4118 'Content-type' => 'text/html; charset=UTF-8', 4119 'Cache-Control' => 'private, no-cache="set-cookie"', 4120 'Expires' => gmdate('D, d M Y H:i:s', time()) . ' GMT', 4121 'Referrer-Policy' => 'strict-origin-when-cross-origin', 4122 ); 4123 if (!empty($user->data['is_bot'])) 4124 { 4125 // Let reverse proxies know we detected a bot. 4126 $http_headers['X-PHPBB-IS-BOT'] = 'yes'; 4127 } 4128 } 4129 4130 /** 4131 * Execute code and/or overwrite _common_ template variables after they have been assigned. 4132 * 4133 * @event core.page_header_after 4134 * @var string page_title Page title 4135 * @var bool display_online_list Do we display online users list 4136 * @var string item Restrict online users to a certain 4137 * session item, e.g. forum for 4138 * session_forum_id 4139 * @var int item_id Restrict online users to item id 4140 * @var array http_headers HTTP headers that should be set by phpbb 4141 * 4142 * @since 3.1.0-b3 4143 */ 4144 $vars = array('page_title', 'display_online_list', 'item_id', 'item', 'http_headers'); 4145 extract($phpbb_dispatcher->trigger_event('core.page_header_after', compact($vars))); 4146 4147 foreach ($http_headers as $hname => $hval) 4148 { 4149 header((string) $hname . ': ' . (string) $hval); 4150 } 4151 4152 return; 4153 } 4154 4155 /** 4156 * Check and display the SQL report if requested. 4157 * 4158 * @param \phpbb\request\request_interface $request Request object 4159 * @param \phpbb\auth\auth $auth Auth object 4160 * @param \phpbb\db\driver\driver_interface $db Database connection 4161 * 4162 * @deprecated 3.3.1 (To be removed: 4.0.0-a1); use controller helper's display_sql_report() 4163 */ 4164 function phpbb_check_and_display_sql_report(\phpbb\request\request_interface $request, \phpbb\auth\auth $auth, \phpbb\db\driver\driver_interface $db) 4165 { 4166 global $phpbb_container; 4167 4168 /** @var \phpbb\controller\helper $controller_helper */ 4169 $controller_helper = $phpbb_container->get('controller.helper'); 4170 4171 $controller_helper->display_sql_report(); 4172 } 4173 4174 /** 4175 * Generate the debug output string 4176 * 4177 * @param \phpbb\db\driver\driver_interface $db Database connection 4178 * @param \phpbb\config\config $config Config object 4179 * @param \phpbb\auth\auth $auth Auth object 4180 * @param \phpbb\user $user User object 4181 * @param \phpbb\event\dispatcher_interface $phpbb_dispatcher Event dispatcher 4182 * @return string 4183 */ 4184 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) 4185 { 4186 global $phpbb_container; 4187 4188 $debug_info = array(); 4189 4190 // Output page creation time 4191 if ($phpbb_container->getParameter('debug.load_time')) 4192 { 4193 if (isset($GLOBALS['starttime'])) 4194 { 4195 $totaltime = microtime(true) - $GLOBALS['starttime']; 4196 $debug_info[] = sprintf('<span title="SQL time: %.3fs / PHP time: %.3fs">Time: %.3fs</span>', $db->get_sql_time(), ($totaltime - $db->get_sql_time()), $totaltime); 4197 } 4198 } 4199 4200 if ($phpbb_container->getParameter('debug.memory')) 4201 { 4202 $memory_usage = memory_get_peak_usage(); 4203 if ($memory_usage) 4204 { 4205 $memory_usage = get_formatted_filesize($memory_usage); 4206 4207 $debug_info[] = 'Peak Memory Usage: ' . $memory_usage; 4208 } 4209 4210 $debug_info[] = 'GZIP: ' . (($config['gzip_compress'] && @extension_loaded('zlib')) ? 'On' : 'Off'); 4211 4212 if ($user->load) 4213 { 4214 $debug_info[] = 'Load: ' . $user->load; 4215 } 4216 } 4217 4218 if ($phpbb_container->getParameter('debug.sql_explain')) 4219 { 4220 $debug_info[] = sprintf('<span title="Cached: %d">Queries: %d</span>', $db->sql_num_queries(true), $db->sql_num_queries()); 4221 4222 if ($auth->acl_get('a_')) 4223 { 4224 $debug_info[] = '<a href="' . build_url() . '&explain=1">SQL Explain</a>'; 4225 } 4226 } 4227 4228 /** 4229 * Modify debug output information 4230 * 4231 * @event core.phpbb_generate_debug_output 4232 * @var array debug_info Array of strings with debug information 4233 * 4234 * @since 3.1.0-RC3 4235 */ 4236 $vars = array('debug_info'); 4237 extract($phpbb_dispatcher->trigger_event('core.phpbb_generate_debug_output', compact($vars))); 4238 4239 return implode(' | ', $debug_info); 4240 } 4241 4242 /** 4243 * Generate page footer 4244 * 4245 * @param bool $run_cron Whether or not to run the cron 4246 * @param bool $display_template Whether or not to display the template 4247 * @param bool $exit_handler Whether or not to run the exit_handler() 4248 */ 4249 function page_footer($run_cron = true, $display_template = true, $exit_handler = true) 4250 { 4251 global $phpbb_dispatcher, $phpbb_container, $template; 4252 4253 // A listener can set this variable to `true` when it overrides this function 4254 $page_footer_override = false; 4255 4256 /** 4257 * Execute code and/or overwrite page_footer() 4258 * 4259 * @event core.page_footer 4260 * @var bool run_cron Shall we run cron tasks 4261 * @var bool page_footer_override Shall we return instead of running 4262 * the rest of page_footer() 4263 * @since 3.1.0-a1 4264 */ 4265 $vars = array('run_cron', 'page_footer_override'); 4266 extract($phpbb_dispatcher->trigger_event('core.page_footer', compact($vars))); 4267 4268 if ($page_footer_override) 4269 { 4270 return; 4271 } 4272 4273 /** @var \phpbb\controller\helper $controller_helper */ 4274 $controller_helper = $phpbb_container->get('controller.helper'); 4275 4276 $controller_helper->display_footer($run_cron); 4277 4278 /** 4279 * Execute code and/or modify output before displaying the template. 4280 * 4281 * @event core.page_footer_after 4282 * @var bool display_template Whether or not to display the template 4283 * @var bool exit_handler Whether or not to run the exit_handler() 4284 * 4285 * @since 3.1.0-RC5 4286 */ 4287 $vars = array('display_template', 'exit_handler'); 4288 extract($phpbb_dispatcher->trigger_event('core.page_footer_after', compact($vars))); 4289 4290 if ($display_template) 4291 { 4292 $template->display('body'); 4293 } 4294 4295 garbage_collection(); 4296 4297 if ($exit_handler) 4298 { 4299 exit_handler(); 4300 } 4301 } 4302 4303 /** 4304 * Closing the cache object and the database 4305 * Cool function name, eh? We might want to add operations to it later 4306 */ 4307 function garbage_collection() 4308 { 4309 global $cache, $db; 4310 global $phpbb_dispatcher; 4311 4312 if (!empty($phpbb_dispatcher)) 4313 { 4314 /** 4315 * Unload some objects, to free some memory, before we finish our task 4316 * 4317 * @event core.garbage_collection 4318 * @since 3.1.0-a1 4319 */ 4320 $phpbb_dispatcher->dispatch('core.garbage_collection'); 4321 } 4322 4323 // Unload cache, must be done before the DB connection if closed 4324 if (!empty($cache)) 4325 { 4326 $cache->unload(); 4327 } 4328 4329 // Close our DB connection. 4330 if (!empty($db)) 4331 { 4332 $db->sql_close(); 4333 } 4334 } 4335 4336 /** 4337 * Handler for exit calls in phpBB. 4338 * This function supports hooks. 4339 * 4340 * Note: This function is called after the template has been outputted. 4341 */ 4342 function exit_handler() 4343 { 4344 global $phpbb_hook; 4345 4346 if (!empty($phpbb_hook) && $phpbb_hook->call_hook(__FUNCTION__)) 4347 { 4348 if ($phpbb_hook->hook_return(__FUNCTION__)) 4349 { 4350 return $phpbb_hook->hook_return_result(__FUNCTION__); 4351 } 4352 } 4353 4354 // As a pre-caution... some setups display a blank page if the flush() is not there. 4355 (ob_get_level() > 0) ? @ob_flush() : @flush(); 4356 4357 exit; 4358 } 4359 4360 /** 4361 * Handler for init calls in phpBB. This function is called in \phpbb\user::setup(); 4362 * This function supports hooks. 4363 */ 4364 function phpbb_user_session_handler() 4365 { 4366 global $phpbb_hook; 4367 4368 if (!empty($phpbb_hook) && $phpbb_hook->call_hook(__FUNCTION__)) 4369 { 4370 if ($phpbb_hook->hook_return(__FUNCTION__)) 4371 { 4372 return $phpbb_hook->hook_return_result(__FUNCTION__); 4373 } 4374 } 4375 4376 return; 4377 } 4378 4379 /** 4380 * Casts a numeric string $input to an appropriate numeric type (i.e. integer or float) 4381 * 4382 * @param string $input A numeric string. 4383 * 4384 * @return int|float Integer $input if $input fits integer, 4385 * float $input otherwise. 4386 */ 4387 function phpbb_to_numeric($input) 4388 { 4389 return ($input > PHP_INT_MAX) ? (float) $input : (int) $input; 4390 } 4391 4392 /** 4393 * Get the board contact details (e.g. for emails) 4394 * 4395 * @param \phpbb\config\config $config 4396 * @param string $phpEx 4397 * @return string 4398 */ 4399 function phpbb_get_board_contact(\phpbb\config\config $config, $phpEx) 4400 { 4401 if ($config['contact_admin_form_enable']) 4402 { 4403 return generate_board_url() . '/memberlist.' . $phpEx . '?mode=contactadmin'; 4404 } 4405 else 4406 { 4407 return $config['board_contact']; 4408 } 4409 } 4410 4411 /** 4412 * Get a clickable board contact details link 4413 * 4414 * @param \phpbb\config\config $config 4415 * @param string $phpbb_root_path 4416 * @param string $phpEx 4417 * @return string 4418 */ 4419 function phpbb_get_board_contact_link(\phpbb\config\config $config, $phpbb_root_path, $phpEx) 4420 { 4421 if ($config['contact_admin_form_enable'] && $config['email_enable']) 4422 { 4423 return append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=contactadmin'); 4424 } 4425 else 4426 { 4427 return 'mailto:' . htmlspecialchars($config['board_contact'], ENT_COMPAT); 4428 } 4429 }