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