1<?php 2 3/* User flags */ 4define('BUGS_NORMAL_USER', 1<<0); 5define('BUGS_DEV_USER', 1<<1); 6define('BUGS_TRUSTED_DEV', 1<<2); 7define('BUGS_SECURITY_DEV', 1<<3); 8 9/* Contains functions and variables used throughout the bug system */ 10 11// used in mail_bug_updates(), below, and class for search results 12$tla = [ 13 'Open' => 'Opn', 14 'Not a bug' => 'Nab', 15 'Feedback' => 'Fbk', 16 'No Feedback' => 'NoF', 17 'Wont fix' => 'Wfx', 18 'Duplicate' => 'Dup', 19 'Critical' => 'Ctl', 20 'Assigned' => 'Asn', 21 'Analyzed' => 'Ana', 22 'Verified' => 'Ver', 23 'Suspended' => 'Sus', 24 'Closed' => 'Csd', 25 'Spam' => 'Spm', 26 'Re-Opened' => 'ReO', 27]; 28 29$bug_types = [ 30 'Bug' => 'Bug', 31 'Feature/Change Request' => 'Req', 32 'Documentation Problem' => 'Doc', 33 'Security' => 'Sec Bug' 34]; 35 36// Used in show_state_options() 37$state_types = [ 38 'Open' => 2, 39 'Closed' => 2, 40 'Re-Opened' => 1, 41 'Duplicate' => 1, 42 'Critical' => 1, 43 'Assigned' => 2, 44 'Not Assigned' => 0, 45 'Analyzed' => 1, 46 'Verified' => 1, 47 'Suspended' => 1, 48 'Wont fix' => 1, 49 'No Feedback' => 1, 50 'Feedback' => 1, 51 'Old Feedback' => 0, 52 'Stale' => 0, 53 'Fresh' => 0, 54 'Not a bug' => 1, 55 'Spam' => 1, 56 'All' => 0, 57]; 58 59/** 60 * Authentication 61 */ 62function verify_user_password($user, $pass) 63{ 64 global $errors; 65 66 $post = http_build_query( 67 [ 68 'token' => getenv('AUTH_TOKEN'), 69 'username' => $user, 70 'password' => $pass, 71 ] 72 ); 73 74 $opts = [ 75 'method' => 'POST', 76 'header' => 'Content-type: application/x-www-form-urlencoded', 77 'content' => $post, 78 ]; 79 80 $ctx = stream_context_create(['http' => $opts]); 81 82 $s = file_get_contents('https://main.php.net/fetch/cvsauth.php', false, $ctx); 83 84 $a = @unserialize($s); 85 if (!is_array($a)) { 86 $errors[] = "Failed to get authentication information.\nMaybe master is down?\n"; 87 return false; 88 } 89 if (isset($a['errno'])) { 90 $errors[] = "Authentication failed: {$a['errstr']}\n"; 91 return false; 92 } 93 94 $_SESSION["user"] = $user; 95 96 return true; 97} 98 99function bugs_has_access ($bug_id, $bug, $pw, $user_flags) 100{ 101 global $auth_user; 102 103 if ($bug['private'] != 'Y') { 104 return true; 105 } 106 107 // When the bug is private, only the submitter, trusted devs, security devs and assigned dev 108 // should see the report info 109 if ($user_flags & (BUGS_SECURITY_DEV | BUGS_TRUSTED_DEV)) { 110 // trusted and security dev 111 return true; 112 } else if (($user_flags == BUGS_NORMAL_USER) && $pw != '' && verify_bug_passwd($bug_id, bugs_get_hash($pw))) { 113 // The submitter 114 return true; 115 } else if (($user_flags & BUGS_DEV_USER) && $bug['reporter_name'] != '' && 116 strtolower($bug['reporter_name']) == strtolower($auth_user->handle)) { 117 // The submitter (php developer) 118 return true; 119 } else if (($user_flags & BUGS_DEV_USER) && $bug['assign'] != '' && 120 strtolower($bug['assign']) == strtolower($auth_user->handle)) { 121 // The assigned dev 122 return true; 123 } 124 125 return false; 126} 127 128function bugs_authenticate (&$user, &$pw, &$logged_in, &$user_flags) 129{ 130 global $auth_user, $ROOT_DIR; 131 132 // Default values 133 $user = ''; 134 $pw = ''; 135 $logged_in = false; 136 137 $user_flags = BUGS_NORMAL_USER; 138 139 // Set username and password 140 if (!empty($_POST['pw'])) { 141 if (empty($_POST['user'])) { 142 $user = ''; 143 } else { 144 $user = htmlspecialchars($_POST['user']); 145 } 146 $user = strtolower($user); 147 $pw = $_POST['pw']; 148 } elseif (isset($auth_user) && is_object($auth_user) && $auth_user->handle) { 149 $user = $auth_user->handle; 150 $pw = $auth_user->password; 151 } 152 153 // Authentication and user level check 154 // User levels are: reader (0), commenter/patcher/etc. (edit = 3), submitter (edit = 2), developer (edit = 1) 155 if (!empty($_SESSION["user"])) { 156 $user = $_SESSION["user"]; 157 $user_flags = BUGS_DEV_USER; 158 $logged_in = 'developer'; 159 $auth_user = new stdClass; 160 $auth_user->handle = $user; 161 $auth_user->email = "{$user}@php.net"; 162 $auth_user->name = $user; 163 } elseif ($user != '' && $pw != '' && verify_user_password($user, $pw)) { 164 $user_flags = BUGS_DEV_USER; 165 $logged_in = 'developer'; 166 $auth_user = new stdClass; 167 $auth_user->handle = $user; 168 $auth_user->email = "{$user}@php.net"; 169 $auth_user->name = $user; 170 } else { 171 $auth_user = new stdClass; 172 $auth_user->email = isset($_POST['in']['email']) ? $_POST['in']['email'] : ''; 173 $auth_user->handle = ''; 174 $auth_user->name = ''; 175 } 176 177 // Check if developer is trusted 178 if ($logged_in == 'developer') { 179 require_once "{$ROOT_DIR}/include/trusted-devs.php"; 180 181 if (in_array(strtolower($user), $trusted_developers)) { 182 $user_flags |= BUGS_TRUSTED_DEV; 183 } 184 if (in_array(strtolower($user), $security_developers)) { 185 $user_flags |= BUGS_SECURITY_DEV; 186 } 187 } 188} 189 190/* Primitive check for SPAM. Add more later. */ 191function is_spam($string) 192{ 193 // @php.net users are given permission to spam... we gotta eat! See also bug #48126 194 if (!empty($GLOBALS['auth_user']->handle)) { 195 return false; 196 } 197 198 if (preg_match_all('/https?:\/\/(\S+)/', $string, $matches)) { 199 foreach ($matches[1] as $match) { 200 if (!preg_match('/^[^\/)]*(php\.net|github\.com)/', $match)) { 201 return "Due to large amounts of spam, only links to php.net and github.com (including subdomains like gist.github.com) are allowed."; 202 } 203 } 204 } 205 206 $keywords = [ 207 'spy', 208 'bdsm', 209 'massage', 210 'mortage', 211 'sex', 212 '11nong', 213 'oxycontin', 214 'distance-education', 215 'sismatech', 216 'justiceplan', 217 'prednisolone', 218 'baclofen', 219 'diflucan', 220 'unbra.se', 221 'objectis', 222 'angosso', 223 'colchicine', 224 'zovirax', 225 'korsbest', 226 'coachbags', 227 'chaneljpoutlet', 228 '\/Members\/', 229 'michaelkorsshop', 230 'mkmichaelkors', 231 'Burberrysale4u', 232 'gadboisphotos', 233 'oakleysunglasseslol', 234 'partydressuk', 235 'leslunettesdesoleil', 236 'PaulRGuthrie', 237 '[a-z]*?fuck[a-z]*?', 238 'jerseys', 239 'wholesale', 240 'fashionretailshop01', 241 'amoxicillin', 242 'helpdeskaustralia', 243 'porn', 244 'aarinkaur', 245 'lildurk', 246 'tvfun', 247 ]; 248 249 if (preg_match('/\b('. implode('|', $keywords) . ')\b/i', $string)) { 250 return "Comment contains spam word, consider rewording."; 251 } 252 253 return false; 254} 255 256/* Primitive check for SPAMmy user. Add more later. */ 257function is_spam_user($email) 258{ 259 if (preg_match("/(rhsoft|reindl|phpbugreports|bugreprtsz|bugreports\d*@gmail|training365)/i", $email)) { 260 return true; 261 } 262 return false; 263} 264 265/** 266 * Obfuscates email addresses to hinder spammer's spiders 267 * 268 * Turns "@" into character entities that get interpreted as "at" and 269 * turns "." into character entities that get interpreted as "dot". 270 * 271 * @param string $txt the email address to be obfuscated 272 * @param string $format how the output will be displayed ('html', 'text', 'reverse') 273 * 274 * @return string the altered email address 275 */ 276function spam_protect($txt, $format = 'html') 277{ 278 /* php.net addresses are not protected! */ 279 if (preg_match('/^(.+)@php\.net$/i', $txt)) { 280 return $txt; 281 } 282 if ($format == 'html') { 283 $translate = [ 284 '@' => ' at ', 285 '.' => ' dot ', 286 ]; 287 } else { 288 $translate = [ 289 '@' => ' at ', 290 '.' => ' dot ', 291 ]; 292 if ($format == 'reverse') { 293 $translate = array_flip($translate); 294 } 295 } 296 return strtr($txt, $translate); 297} 298 299/** 300 * Goes through each variable submitted and returns the value 301 * from the first variable which has a non-empty value 302 * 303 * Handy function for when you're dealing with user input or a default. 304 * 305 * @param mixed as many variables as you wish to check 306 * 307 * @return mixed the value, if any 308 * 309 * @see field(), txfield() 310 */ 311function oneof() 312{ 313 foreach (func_get_args() as $arg) { 314 if ($arg) { 315 return $arg; 316 } 317 } 318} 319 320/** 321 * Returns the data from the field requested and sanitizes 322 * it for use as HTML 323 * 324 * If the data from a form submission exists, that is used. 325 * But if that's not there, the info is obtained from the database. 326 * 327 * @param string $n the name of the field to be looked for 328 * 329 * @return mixed the data requested 330 * 331 * @see oneof(), txfield() 332 */ 333function field($n) 334{ 335 return oneof(isset($_POST['in']) ? 336 htmlspecialchars(isset($_POST['in'][$n]) ? $_POST['in'][$n] : '') : null, 337 htmlspecialchars($GLOBALS['bug'][$n] ?? '')); 338} 339 340/** 341 * Escape string so it can be used as HTML 342 * 343 * @param string $in the string to be sanitized 344 * 345 * @return string the sanitized string 346 * 347 * @see txfield() 348 */ 349function clean($in) 350{ 351 return mb_encode_numericentity($in, 352 [ 353 0x0, 0x8, 0, 0xffffff, 354 0xb, 0xc, 0, 0xffffff, 355 0xe, 0x1f, 0, 0xffffff, 356 0x22, 0x22, 0, 0xffffff, 357 0x26, 0x27, 0, 0xffffff, 358 0x3c, 0x3c, 0, 0xffffff, 359 0x3e, 0x3e, 0, 0xffffff, 360 0x7f, 0x84, 0, 0xffffff, 361 0x86, 0x9f, 0, 0xffffff, 362 0xfdd0, 0xfdef, 0, 0xffffff, 363 0x1fffe, 0x1ffff, 0, 0xffffff, 364 0x2fffe, 0x2ffff, 0, 0xffffff, 365 0x3fffe, 0x3ffff, 0, 0xffffff, 366 0x4fffe, 0x4ffff, 0, 0xffffff, 367 0x5fffe, 0x5ffff, 0, 0xffffff, 368 0x6fffe, 0x6ffff, 0, 0xffffff, 369 0x7fffe, 0x7ffff, 0, 0xffffff, 370 0x8fffe, 0x8ffff, 0, 0xffffff, 371 0x9fffe, 0x9ffff, 0, 0xffffff, 372 0xafffe, 0xaffff, 0, 0xffffff, 373 0xbfffe, 0xbffff, 0, 0xffffff, 374 0xcfffe, 0xcffff, 0, 0xffffff, 375 0xdfffe, 0xdffff, 0, 0xffffff, 376 0xefffe, 0xeffff, 0, 0xffffff, 377 0xffffe, 0xfffff, 0, 0xffffff, 378 0x10fffe, 0x10ffff, 0, 0xffffff, 379 ], 380 'UTF-8'); 381} 382 383/** 384 * Returns the data from the field requested and sanitizes 385 * it for use as plain text 386 * 387 * If the data from a form submission exists, that is used. 388 * But if that's not there, the info is obtained from the database. 389 * 390 * @param string $n the name of the field to be looked for 391 * 392 * @return mixed the data requested 393 * 394 * @see clean() 395 */ 396function txfield($n, $bug = null, $in = null) 397{ 398 $one = (isset($in) && isset($in[$n])) ? $in[$n] : false; 399 if ($one) { 400 return $one; 401 } 402 403 $two = (isset($bug) && isset($bug[$n])) ? $bug[$n] : false; 404 if ($two) { 405 return $two; 406 } 407} 408 409/** 410 * Prints age <option>'s for use in a <select> 411 * 412 * @param string $current the field's current value 413 * 414 * @return void 415 */ 416function show_byage_options($current) 417{ 418 $opts = [ 419 '0' => 'the beginning', 420 '1' => 'yesterday', 421 '7' => '7 days ago', 422 '15' => '15 days ago', 423 '30' => '30 days ago', 424 '90' => '90 days ago', 425 ]; 426 foreach ($opts as $k => $v) { 427 echo "<option value=\"$k\"", ($current==$k ? ' selected="selected"' : ''), ">$v</option>\n"; 428 } 429} 430 431/** 432 * Prints a list of <option>'s for use in a <select> element 433 * asking how many bugs to display 434 * 435 * @param int $limit the presently selected limit to be used as the default 436 * 437 * @return void 438 */ 439function show_limit_options($limit = 30) 440{ 441 for ($i = 10; $i < 100; $i += 10) { 442 echo '<option value="' . $i . '"'; 443 if ($limit == $i) { 444 echo ' selected="selected"'; 445 } 446 echo ">$i bugs</option>\n"; 447 } 448 449 echo '<option value="All"'; 450 if ($limit == 'All') { 451 echo ' selected="selected"'; 452 } 453 echo ">All</option>\n"; 454} 455 456/** 457 * Prints bug type <option>'s for use in a <select> 458 * 459 * Options include "Bug", "Documentation Problem" and "Feature/Change Request." 460 * 461 * @param string $current bug's current type 462 * @param bool $deprecated whether or not deprecated types should be shown 463 * @param bool $all whether or not 'All' should be an option 464 * 465 * @retun void 466 */ 467function show_type_options($current, $deprecated, $all = false) 468{ 469 global $bug_types; 470 471 if ($all) { 472 if (!$current) { 473 $current = 'All'; 474 } 475 echo '<option value="All"'; 476 if ($current == 'All') { 477 echo ' selected="selected"'; 478 } 479 echo ">All</option>\n"; 480 } elseif (!$current) { 481 $current = 'bug'; 482 } 483 484 foreach ($bug_types as $k => $v) { 485 if ($k !== 'Security' && !$deprecated) { 486 continue; 487 } 488 $selected = strcasecmp($current, $k) ? '' : ' selected="selected"'; 489 $k = htmlentities($k, ENT_QUOTES); 490 echo "<option value=\"$k\"$selected>$k</option>"; 491 } 492} 493 494/** 495 * Prints bug state <option>'s for use in a <select> list 496 * 497 * @param string $state the bug's present state 498 * @param int $user_mode the 'edit' mode 499 * @param string $default the default value 500 * 501 * @return void 502 */ 503function show_state_options($state, $user_mode = 0, $default = '', $assigned = 0) 504{ 505 global $state_types, $tla; 506 507 if (!$state && !$default) { 508 $state = $assigned ? 'Assigned' : 'Open'; 509 } elseif (!$state) { 510 $state = $default; 511 } 512 513 /* regular users can only pick states with type 2 for unclosed bugs */ 514 if ($state != 'All' && isset($state_types[$state]) && $state_types[$state] == 1 && $user_mode == 2) { 515 switch ($state) 516 { 517 /* If state was 'Feedback', set state automatically to 'Assigned' if the bug was 518 * assigned to someone before it to be set to 'Feedback', otherwise set it to 'Open'. 519 */ 520 case 'Feedback': 521 if ($assigned) { 522 echo '<option class="'.$tla['Assigned'].'">Assigned</option>'."\n"; 523 } else { 524 echo '<option class="'.$tla['Open'].'">Open</option>'."\n"; 525 } 526 break; 527 case 'No Feedback': 528 echo '<option class="'.$tla['Re-Opened'].'">Re-Opened</option>'."\n"; 529 break; 530 default: 531 echo '<option'; 532 if (isset($tla[$state])) { 533 echo ' class="'.$tla[$state].'"'; 534 } 535 echo '>'.$state.'</option>'."\n"; 536 break; 537 } 538 /* Allow state 'Closed' always when current state is not 'Not a bug' */ 539 if ($state != 'Not a bug') { 540 echo '<option class="'.$tla['Closed'].'">Closed</option>'."\n"; 541 } 542 } else { 543 foreach($state_types as $type => $mode) { 544 if (($state == 'Closed' && $type == 'Open') 545 || ($state == 'Open' && $type == 'Re-Opened')) { 546 continue; 547 } 548 if ($mode >= $user_mode) { 549 echo '<option'; 550 if (isset($tla[$type])) { 551 echo ' class="'.$tla[$type].'"'; 552 } 553 if ($type == $state) { 554 echo ' selected="selected"'; 555 } 556 echo ">$type</option>\n"; 557 } 558 } 559 } 560} 561 562/** 563 * Prints bug resolution <option>'s for use in a <select> list 564 * 565 * @param string $current the bug's present state 566 * @param int $expande whether or not a longer explanation should be displayed 567 * 568 * @return void 569 */ 570function show_reason_types($current = '', $expanded = 0) 571{ 572 global $RESOLVE_REASONS; 573 574 if ($expanded) { 575 echo '<option value=""></option>' . "\n"; 576 } 577 foreach ($RESOLVE_REASONS as $val) 578 { 579 if (empty($val['package_name'])) { 580 $sel = ($current == $val['name']) ? " selected='selected'" : ''; 581 echo "<option value='{$val['name']}' {$sel} >{$val['title']}"; 582 if ($expanded) { 583 echo " ({$val['status']})"; 584 } 585 echo "</option>\n"; 586 } 587 } 588} 589 590/** 591 * Prints PHP version number <option>'s for use in a <select> list 592 * 593 * @param string $current the bug's current version number 594 * 595 * @return void 596 */ 597function show_version_options($current) 598{ 599 global $ROOT_DIR, $versions; 600 601 $use = 0; 602 603 echo '<option value="">--Please Select--</option>' , "\n"; 604 foreach($versions as $v) { 605 echo '<option'; 606 if ($current == $v) { 607 echo ' selected="selected"'; 608 } 609 echo '>' , htmlspecialchars($v) , "</option>\n"; 610 if ($current == $v) { 611 $use++; 612 } 613 } 614 if (!$use && $current) { 615 echo '<option selected="selected">' , htmlspecialchars($current) , "</option>\n"; 616 } 617 echo '<option value="earlier">Earlier? Upgrade first!</option>', "\n"; 618} 619 620/** 621 * Prints package name <option>'s for use in a <select> list 622 * 623 * @param string $current the bug's present state 624 * @param int $show_any whether or not 'Any' should be an option. 'Any' 625 * will only be printed if no $current value exists. 626 * @param string $default the default value 627 * 628 * @return void 629 */ 630function show_package_options($current, $show_any, $default = '') 631{ 632 global $pseudo_pkgs; 633 $disabled_style = ' style="background-color:#eee;"'; 634 static $bug_groups; 635 636 if (!isset($bug_groups)) { 637 $bug_groups = array_filter( 638 $pseudo_pkgs, 639 function ($value) { 640 return is_array($value[2]); 641 } 642 ); 643 } 644 645 if (!$current && (!$default || $default == 'none') && !$show_any) { 646 echo "<option value=\"none\">--Please Select--</option>\n"; 647 } elseif (!$current && $show_any == 1) { 648 $current = 'Any'; 649 } elseif (!$current) { 650 $current = $default; 651 } 652 653 if (!is_array($bug_groups)) { 654 return; 655 } 656 657 658 foreach ($bug_groups as $key => $bug_group) { 659 echo "<optgroup label=\"{$bug_group[0]}\"" . 660 (($bug_group[1]) ? $disabled_style : ''), "\n>"; 661 662 array_unshift($bug_group[2], $key); 663 foreach ($bug_group[2] as $name) { 664 $child = $pseudo_pkgs[$name]; 665 if ($show_any == 1 || $key != 'Any') { 666 echo "<option value=\"$name\""; 667 if ((is_array($current) && in_array($name, $current)) || ($name == $current)) { 668 echo ' selected="selected"'; 669 } 670 // Show disabled categories with different background color in listing 671 echo (($child[1]) ? $disabled_style : ''), ">{$child[0]}</option>\n"; 672 } 673 } 674 echo "</optgroup>\n"; 675 } 676} 677 678/** 679 * Prints a series of radio inputs to determine how the search 680 * term should be looked for 681 * 682 * @param string $current the users present selection 683 * 684 * @return void 685 */ 686function show_boolean_options($current) 687{ 688 $options = ['any', 'all', 'raw']; 689 foreach ($options as $val => $type) { 690 echo '<input type="radio" id="boolean' . $val . '" name="boolean" value="', $val, '"'; 691 if ($val === $current) { 692 echo ' checked="checked"'; 693 } 694 echo '><label for="boolean' . $val . '">'.$type.'</label>'; 695 } 696} 697 698/** 699 * Display errors or warnings as a <ul> inside a <div> 700 * 701 * Here's what happens depending on $in: 702 * + string: value is printed 703 * + array: looped through and each value is printed. 704 * If array is empty, nothing is displayed. 705 * 706 * @param string|array $in see long description 707 * @param string $class name of the HTML class for the <div> tag. ("errors", "warnings") 708 * @param string $head string to be put above the message 709 * 710 * @return bool true if errors were submitted, false if not 711 */ 712function display_bug_error($in, $class = 'errors', $head = 'ERROR:') 713{ 714 if (!is_array($in)) { 715 $in = [$in]; 716 } elseif (!count($in)) { 717 return false; 718 } 719 720 echo "<div class='{$class}'>{$head}<ul>"; 721 foreach ($in as $msg) { 722 echo '<li>' , htmlspecialchars($msg) , "</li>\n"; 723 } 724 echo "</ul></div>\n"; 725 return true; 726} 727 728/** 729 * Returns array of changes going to be made 730 */ 731function bug_diff($bug, $in) 732{ 733 $changed = []; 734 735 if (!empty($in['email']) && (trim($in['email']) != trim($bug['email']))) { 736 $changed['reported_by']['from'] = spam_protect($bug['email'], 'text'); 737 $changed['reported_by']['to'] = spam_protect(txfield('email', $bug, $in), 'text'); 738 } 739 740 $fields = [ 741 'sdesc' => 'Summary', 742 'status' => 'Status', 743 'bug_type' => 'Type', 744 'package_name' => 'Package', 745 'php_os' => 'Operating System', 746 'php_version' => 'PHP Version', 747 'assign' => 'Assigned To', 748 'block_user_comment' => 'Block user comment', 749 'private' => 'Private report', 750 'cve_id' => 'CVE-ID' 751 ]; 752 753 foreach (array_keys($fields) as $name) { 754 if (array_key_exists($name, $in) && array_key_exists($name, $bug)) { 755 $to = trim($in[$name]); 756 $from = trim($bug[$name]); 757 if ($from != $to) { 758 if (in_array($name, ['private', 'block_user_comment'])) { 759 $from = $from == 'Y' ? 'Yes' : 'No'; 760 $to = $to == 'Y' ? 'Yes' : 'No'; 761 } 762 $changed[$name]['from'] = $from; 763 $changed[$name]['to'] = $to; 764 } 765 } 766 } 767 768 return $changed; 769} 770 771function bug_diff_render_html($diff) 772{ 773 $fields = [ 774 'sdesc' => 'Summary', 775 'status' => 'Status', 776 'bug_type' => 'Type', 777 'package_name' => 'Package', 778 'php_os' => 'Operating System', 779 'php_version' => 'PHP Version', 780 'assign' => 'Assigned To', 781 'block_user_comment' => 'Block user comment', 782 'private' => 'Private report', 783 'cve_id' => 'CVE-ID' 784 ]; 785 786 // make diff output aligned 787 $actlength = $maxlength = 0; 788 foreach (array_keys($diff) as $v) { 789 $actlength = strlen($fields[$v]) + 2; 790 $maxlength = ($maxlength < $actlength) ? $actlength : $maxlength; 791 } 792 793 $changes = '<div class="changeset">' . "\n"; 794 $spaces = str_repeat(' ', $maxlength + 1); 795 foreach ($diff as $name => $content) { 796 // align header content with headers (if a header contains 797 // more than one line, wrap it intelligently) 798 $field = str_pad($fields[$name] . ':', $maxlength); 799 $from = wordwrap('-'.$field.$content['from'], 72 - $maxlength, "\n$spaces"); // wrap and indent 800 $from = rtrim($from); // wordwrap may add spacer to last line 801 $to = wordwrap('+'.$field.$content['to'], 72 - $maxlength, "\n$spaces"); // wrap and indent 802 $to = rtrim($to); // wordwrap may add spacer to last line 803 $changes .= '<span class="removed">' . clean($from) . '</span>' . "\n"; 804 $changes .= '<span class="added">' . clean($to) . '</span>' . "\n"; 805 } 806 $changes .= '</div>'; 807 808 return $changes; 809} 810 811/** 812 * Send an email notice about bug aditions and edits 813 * 814 * @param 815 * 816 * @return void 817 */ 818function mail_bug_updates($bug, $in, $from, $ncomment, $edit = 1, $id = false) 819{ 820 global $tla, $bug_types, $siteBig, $site_method, $site_url, $basedir; 821 822 $text = []; 823 $headers = []; 824 $changed = bug_diff($bug, $in); 825 $from = str_replace(["\n", "\r"], '', $from); 826 827 /* Default addresses */ 828 list($mailto, $mailfrom, $bcc, $params) = get_package_mail(oneof(@$in['package_name'], $bug['package_name']), $id, oneof(@$in['bug_type'], $bug['bug_type'])); 829 830 $headers[] = [' ID', $bug['id']]; 831 832 switch ($edit) { 833 case 4: 834 $headers[] = [' Patch added by', $from]; 835 $from = "\"{$from}\" <{$mailfrom}>"; 836 break; 837 case 3: 838 $headers[] = [' Comment by', $from]; 839 $from = "\"{$from}\" <{$mailfrom}>"; 840 break; 841 case 2: 842 $from = spam_protect(txfield('email', $bug, $in), 'text'); 843 $headers[] = [' User updated by', $from]; 844 $from = "\"{$from}\" <{$mailfrom}>"; 845 break; 846 default: 847 $headers[] = [' Updated by', $from]; 848 } 849 850 $fields = [ 851 'email' => 'Reported by', 852 'sdesc' => 'Summary', 853 'status' => 'Status', 854 'bug_type' => 'Type', 855 'package_name' => 'Package', 856 'php_os' => 'Operating System', 857 'php_version' => 'PHP Version', 858 'assign' => 'Assigned To', 859 'block_user_comment' => 'Block user comment', 860 'private' => 'Private report', 861 'cve_id' => 'CVE-ID', 862 ]; 863 864 foreach ($fields as $name => $desc) { 865 $prefix = ' '; 866 if (isset($changed[$name])) { 867 $headers[] = ["-{$desc}", $changed[$name]['from']]; 868 $prefix = '+'; 869 } 870 871 /* only fields that are set get added. */ 872 if ($f = txfield($name, $bug, $in)) { 873 if ($name == 'email') { 874 $f = spam_protect($f, 'text'); 875 } 876 $foo = isset($changed[$name]['to']) ? $changed[$name]['to'] : $f; 877 $headers[] = [$prefix.$desc, $foo]; 878 } 879 } 880 881 /* Make header output aligned */ 882 $maxlength = 0; 883 $actlength = 0; 884 foreach ($headers as $v) { 885 $actlength = strlen($v[0]) + 1; 886 $maxlength = (($maxlength < $actlength) ? $actlength : $maxlength); 887 } 888 889 /* Align header content with headers (if a header contains more than one line, wrap it intelligently) */ 890 $header_text = ''; 891 892 $spaces = str_repeat(' ', $maxlength + 1); 893 foreach ($headers as $v) { 894 $hcontent = wordwrap($v[1], 72 - $maxlength, "\n{$spaces}"); // wrap and indent 895 $hcontent = rtrim($hcontent); // wordwrap may add spacer to last line 896 $header_text .= str_pad($v[0] . ':', $maxlength) . " {$hcontent}\n"; 897 } 898 899 if ($ncomment) { 900# $ncomment = preg_replace('#<div class="changeset">(.*)</div>#sUe', "ltrim(strip_tags('\\1'))", $ncomment); 901 $ncomment = preg_replace_callback('#<div class="changeset">(.*)</div>#sU', function ($m) { return ltrim(strip_tags($m[0])); }, $ncomment); 902 903 $text[] = " New Comment:\n\n{$ncomment}"; 904 } 905 906 $old_comments = get_old_comments($bug['id'], empty($ncomment)); 907# $old_comments = preg_replace('#<div class="changeset">(.*)</div>#sUe', "ltrim(strip_tags('\\1'))", $old_comments); 908 $old_comments = preg_replace_callback('#<div class="changeset">(.*)</div>#sU', function ($m) { return ltrim(strip_tags($m[0])); }, $old_comments); 909 910 $text[] = $old_comments; 911 912 $wrapped_text = join("\n", $text); 913 914 /* user text with attention, headers and previous messages */ 915 $user_text = <<< USER_TEXT 916ATTENTION! Do NOT reply to this email! 917To reply, use the web interface found at 918{$site_method}://{$site_url}{$basedir}/bug.php?id={$bug['id']}&edit=2 919 920{$header_text} 921{$wrapped_text} 922USER_TEXT; 923 924 /* developer text with headers, previous messages, and edit link */ 925 $dev_text = <<< DEV_TEXT 926Edit report at {$site_method}://{$site_url}{$basedir}/bug.php?id={$bug['id']}&edit=1 927 928{$header_text} 929{$wrapped_text} 930 931-- 932Edit this bug report at {$site_method}://{$site_url}{$basedir}/bug.php?id={$bug['id']}&edit=1 933DEV_TEXT; 934 935 if (preg_match('/.*@php\.net\z/', $bug['email'])) { 936 $user_text = $dev_text; 937 } 938 939 // Defaults 940 $subj = $bug_types[$bug['bug_type']]; 941 $sdesc = txfield('sdesc', $bug, $in); 942 943 /* send mail if status was changed, there is a comment, private turned on/off or the bug type was changed to/from Security */ 944 if (empty($in['status']) || $in['status'] != $bug['status'] || $ncomment != '' || 945 (isset($in['private']) && $in['private'] != $bug['private']) || 946 (isset($in['bug_type']) && $in['bug_type'] != $bug['bug_type'] && 947 ($in['bug_type'] == 'Security' || $bug['bug_type'] == 'Security'))) { 948 if (isset($in['bug_type']) && $in['bug_type'] != $bug['bug_type']) { 949 $subj = $bug_types[$bug['bug_type']] . '->' . $bug_types[$in['bug_type']]; 950 } 951 952 $old_status = $bug['status']; 953 $new_status = $bug['status']; 954 955 if (isset($in['status']) && $in['status'] != $bug['status'] && $edit != 3) { /* status changed */ 956 $new_status = $in['status']; 957 $subj .= " #{$bug['id']} [{$tla[$old_status]}->{$tla[$new_status]}]"; 958 } elseif ($edit == 4) { /* patch */ 959 $subj .= " #{$bug['id']} [PATCH]"; 960 } elseif ($edit == 3) { /* comment */ 961 $subj .= " #{$bug['id']} [Com]"; 962 } else { /* status did not change and not comment */ 963 $subj .= " #{$bug['id']} [{$tla[$bug['status']]}]"; 964 } 965 966 // the user gets sent mail with an envelope sender that ignores bounces 967 bugs_mail( 968 $bug['email'], 969 "{$subj}: {$sdesc}", 970 $user_text, 971 "From: {$siteBig} Bug Database <{$mailfrom}>\r\n" . 972 "Bcc: {$bcc}\r\n" . 973 "X-PHP-Bug: {$bug['id']}\r\n" . 974 "X-PHP-Site: {$siteBig}\r\n" . 975 "In-Reply-To: <bug-{$bug['id']}@{$site_url}>" 976 ); 977 978 // Spam protection 979 $tmp = $edit != 3 ? $in : $bug; 980 $tmp['new_status'] = $new_status; 981 $tmp['old_status'] = $old_status; 982 foreach (['bug_type', 'php_version', 'package_name', 'php_os'] as $field) { 983 $tmp[$field] = strtok($tmp[$field], "\r\n"); 984 } 985 986 // but we go ahead and let the default sender get used for the list 987 bugs_mail( 988 $mailto, 989 "{$subj}: {$sdesc}", 990 $dev_text, 991 "From: {$from}\r\n". 992 "X-PHP-Bug: {$bug['id']}\r\n" . 993 "X-PHP-Site: {$siteBig}\r\n" . 994 "X-PHP-Type: {$tmp['bug_type']}\r\n" . 995 "X-PHP-Version: {$tmp['php_version']}\r\n" . 996 "X-PHP-Category: {$tmp['package_name']}\r\n" . 997 "X-PHP-OS: {$tmp['php_os']}\r\n" . 998 "X-PHP-Status: {$tmp['new_status']}\r\n" . 999 "X-PHP-Old-Status: {$tmp['old_status']}\r\n" . 1000 "In-Reply-To: <bug-{$bug['id']}@{$site_url}>", 1001 $params 1002 ); 1003 } 1004 1005 /* if a developer assigns someone else, let that other person know about it */ 1006 if ($edit == 1 && $in['assign'] && $in['assign'] != $bug['assign']) { 1007 1008 $email = $in['assign'] . '@php.net'; 1009 1010 // If the developer assigns him self then skip 1011 if ($email == $from) { 1012 return; 1013 } 1014 1015 bugs_mail( 1016 $email, 1017 $bug_types[$bug['bug_type']] . ' #' . $bug['id'] . ' ' . txfield('sdesc', $bug, $in), 1018 "{$in['assign']} you have just been assigned to this bug by {$from}\n\n{$dev_text}", 1019 "From: {$from}\r\n" . 1020 "X-PHP-Bug: {$bug['id']}\r\n" . 1021 "In-Reply-To: <bug-{$bug['id']}@{$site_url}>" 1022 ); 1023 } 1024} 1025 1026/** 1027 * Turns a unix timestamp into a uniformly formatted date 1028 * 1029 * If the date is during the current year, the year is omitted. 1030 * 1031 * @param int $ts the unix timestamp to be formatted 1032 * @param string $format format to use 1033 * 1034 * @return string the formatted date 1035 */ 1036function format_date($ts = null, $format = 'Y-m-d H:i e') 1037{ 1038 if (!$ts) { 1039 $ts = time(); 1040 } 1041 return gmdate($format, (int)$ts - date('Z', (int)$ts)); 1042} 1043 1044/** 1045 * Produces a string containing the bug's prior comments 1046 * 1047 * @param int $bug_id the bug's id number 1048 * @param int $all should all existing comments be returned? 1049 * 1050 * @return string the comments 1051 */ 1052function get_old_comments($bug_id, $all = 0) 1053{ 1054 global $dbh, $site_method, $site_url, $basedir; 1055 1056 $divider = str_repeat('-', 72); 1057 $max_message_length = 10 * 1024; 1058 $max_comments = 5; 1059 $output = ''; 1060 $count = 0; 1061 1062 $res = $dbh->prepare(" 1063 SELECT ts, email, comment 1064 FROM bugdb_comments 1065 WHERE bug = ? AND comment_type != 'log' 1066 ORDER BY ts DESC 1067 ")->execute([$bug_id]); 1068 1069 // skip the most recent unless the caller wanted all comments 1070 if (!$all) { 1071 $row = $res->fetch(\PDO::FETCH_NUM); 1072 if (!$row) { 1073 return ''; 1074 } 1075 } 1076 1077 while (($row = $res->fetch(\PDO::FETCH_NUM)) && strlen($output) < $max_message_length && $count++ < $max_comments) { 1078 $email = spam_protect($row[1], 'text'); 1079 $output .= "[{$row[0]}] {$email}\n\n{$row[2]}\n\n{$divider}\n"; 1080 } 1081 1082 if (strlen($output) < $max_message_length && $count < $max_comments) { 1083 $res = $dbh->prepare("SELECT ts1, email, ldesc FROM bugdb WHERE id = ?")->execute([$bug_id]); 1084 if (!$res) { 1085 return $output; 1086 } 1087 $row = $res->fetch(\PDO::FETCH_NUM); 1088 if (!$row) { 1089 return $output; 1090 } 1091 $email = spam_protect($row[1], 'text'); 1092 return (" 1093 1094Previous Comments: 1095{$divider} 1096{$output}[{$row[0]}] {$email} 1097 1098{$row[2]} 1099 1100{$divider} 1101 1102"); 1103 } else { 1104 return " 1105 1106Previous Comments: 1107{$divider} 1108{$output} 1109 1110The remainder of the comments for this report are too long. To view 1111the rest of the comments, please view the bug report online at 1112 1113 {$site_method}://{$site_url}{$basedir}/bug.php?id={$bug_id} 1114"; 1115 } 1116 1117 return ''; 1118} 1119 1120/** 1121 * Converts any URI's found in the string to hyperlinks 1122 * 1123 * @param string $text the text to be examined 1124 * 1125 * @return string the converted string 1126 */ 1127function addlinks($text) 1128{ 1129 $text = htmlspecialchars($text); 1130 $text = preg_replace("/((mailto|http|https|ftp|nntp|news):.+?)(>|\\s|\\)|\\.\\s|,\\s|$)/i","<a href=\"\\1\" rel=\"nofollow\">\\1</a>\\3",$text); 1131 1132 # what the heck is this for? 1133 $text = preg_replace("/[.,]?-=-\"/", '"', $text); 1134 return $text; 1135} 1136 1137/** 1138 * Determine if the given package name is legitimate 1139 * 1140 * @param string $package_name the name of the package 1141 * 1142 * @return bool 1143 */ 1144function package_exists($package_name) 1145{ 1146 global $pseudo_pkgs; 1147 1148 return isset($pseudo_pkgs[$package_name]); 1149} 1150 1151/** 1152 * Validate an email address 1153 */ 1154function is_valid_email($email, $phpnet_allowed = true) 1155{ 1156 if (!$phpnet_allowed) { 1157 if (false !== stripos($email, '@php.net')) { 1158 return false; 1159 } 1160 } 1161 return (bool)filter_var($email, FILTER_VALIDATE_EMAIL); 1162} 1163 1164/** 1165 * Validate an incoming bug report 1166 * 1167 * @param mixed $in usually $_POST['in'] 1168 * @param bool $initial 1169 * @param bool $logged_in 1170 * 1171 * @return array 1172 */ 1173function incoming_details_are_valid($in, $initial = 0, $logged_in = false) 1174{ 1175 global $bug, $dbh, $bug_types, $versions; 1176 1177 $errors = []; 1178 if (!is_array($in)) { 1179 $errors[] = 'Invalid data submitted!'; 1180 return $errors; 1181 } 1182 if ($initial || (!empty($in['email']) && $bug['email'] != $in['email'])) { 1183 if (!is_valid_email($in['email'])) { 1184 $errors[] = 'Please provide a valid email address.'; 1185 } 1186 } 1187 1188 if (!$logged_in && $initial && (empty($in['passwd']) || !is_string($in['passwd']))) { 1189 $errors[] = 'Please provide a password for this bug report.'; 1190 } 1191 1192 if (isset($in['php_version']) && $in['php_version'] == 'earlier') { 1193 $errors[] = 'Please select a valid PHP version. If your PHP version is too old, please upgrade first and see if the problem has not already been fixed.'; 1194 } 1195 1196 if (empty($in['php_version']) || !is_string($in['php_version']) || ($initial && !in_array($in['php_version'], $versions))) { 1197 $errors[] = 'Please select a valid PHP version.'; 1198 } 1199 1200 if (empty($in['package_name']) || !is_string($in['package_name']) || $in['package_name'] == 'none') { 1201 $errors[] = 'Please select an appropriate package.'; 1202 } else if (!package_exists($in['package_name'])) { 1203 $errors[] = 'Please select an appropriate package.'; 1204 } 1205 1206 if (empty($in['bug_type']) || !is_string($in['bug_type']) || !array_key_exists($in['bug_type'], $bug_types)) { 1207 $errors[] = 'Please select a valid bug type.'; 1208 } 1209 1210 if (empty($in['sdesc']) || !is_string($in['sdesc'])) { 1211 $errors[] = 'You must supply a short description of the bug you are reporting.'; 1212 } 1213 1214 if ($initial && (empty($in['ldesc']) || !is_string($in['ldesc']))) { 1215 $errors[] = 'You must supply a long description of the bug you are reporting.'; 1216 } 1217 1218 return $errors; 1219} 1220 1221/** 1222 * Produces an array of email addresses the report should go to 1223 * 1224 * @param string $package_name the package's name 1225 * 1226 * @return array an array of email addresses 1227 */ 1228function get_package_mail($package_name, $bug_id = false, $bug_type = 'Bug') 1229{ 1230 global $dbh, $bugEmail, $docBugEmail, $secBugEmail, $security_distro_people; 1231 1232 $to = []; 1233 $params = '-f noreply@php.net'; 1234 $mailfrom = $bugEmail; 1235 1236 if ($bug_type === 'Documentation Problem') { 1237 // Documentation problems *always* go to the doc team 1238 $to[] = $docBugEmail; 1239 } else if ($bug_type == 'Security') { 1240 // Security problems *always* go to the sec team 1241 $to[] = $secBugEmail; 1242 foreach ($security_distro_people as $user) { 1243 $to[] = "{$user}@php.net"; 1244 } 1245 $params = '-f bounce-no-user@php.net'; 1246 } 1247 else { 1248 /* Get package mailing list address */ 1249 $res = $dbh->prepare(' 1250 SELECT list_email, project 1251 FROM bugdb_pseudo_packages 1252 WHERE name = ? 1253 ')->execute([$package_name]); 1254 1255 list($list_email, $project) = $res->fetch(\PDO::FETCH_NUM); 1256 1257 if ($project == 'pecl') { 1258 $mailfrom = 'pecl-dev@lists.php.net'; 1259 } 1260 1261 if ($list_email) { 1262 if ($list_email == 'systems@php.net') { 1263 $params = '-f bounce-no-user@php.net'; 1264 } 1265 $to[] = $list_email; 1266 } else { 1267 // Get the maintainers handle 1268 if ($project == 'pecl') { 1269 $handles = $dbh->prepare("SELECT GROUP_CONCAT(handle) FROM bugdb_packages_maintainers WHERE package_name = ?")->execute([$package_name])->fetch(\PDO::FETCH_NUM)[0]; 1270 1271 if ($handles) { 1272 foreach (explode(',', $handles) as $handle) { 1273 $to[] = $handle .'@php.net'; 1274 } 1275 } else { 1276 $to[] = $mailfrom; 1277 } 1278 } else { 1279 // Fall back to default mailing list 1280 $to[] = $bugEmail; 1281 } 1282 } 1283 } 1284 1285 /* Include assigned to To list and subscribers in Bcc list */ 1286 if ($bug_id) { 1287 $bug_id = (int) $bug_id; 1288 1289 $assigned = $dbh->prepare("SELECT assign FROM bugdb WHERE id= ? ")->execute([$bug_id])->fetch(\PDO::FETCH_NUM)[0]; 1290 if ($assigned) { 1291 $assigned .= '@php.net'; 1292 if ($assigned && !in_array($assigned, $to)) { 1293 $to[] = $assigned; 1294 } 1295 } 1296 $bcc = $dbh->prepare("SELECT email FROM bugdb_subscribe WHERE bug_id=?")->execute([$bug_id])->fetchAll(); 1297 1298 $bcc = array_unique($bcc); 1299 return [implode(', ', $to), $mailfrom, implode(', ', $bcc), $params]; 1300 } else { 1301 return [implode(', ', $to), $mailfrom, '', $params]; 1302 } 1303} 1304 1305/** 1306 * Prepare a query string with the search terms 1307 * 1308 * @param string $search the term to be searched for 1309 * 1310 * @return array 1311 */ 1312function format_search_string($search, $boolean_search = false) 1313{ 1314 global $dbh; 1315 1316 // Function will be updated to make results more relevant. 1317 // Quick hack for indicating ignored words. 1318 $min_word_len=3; 1319 1320 $words = preg_split("/\s+/", $search); 1321 $ignored = $used = []; 1322 foreach($words as $match) 1323 { 1324 if (strlen($match) < $min_word_len) { 1325 array_push($ignored, $match); 1326 } else { 1327 array_push($used, $match); 1328 } 1329 } 1330 1331 if ($boolean_search) { 1332 // require all used words (all) 1333 if ($boolean_search === 1) { 1334 $newsearch = ''; 1335 foreach ($used as $word) { 1336 $newsearch .= "+$word "; 1337 } 1338 return [" AND MATCH (bugdb.email,sdesc,ldesc) AGAINST (" . $dbh->quote($newsearch) . " IN BOOLEAN MODE)", $ignored]; 1339 1340 // allow custom boolean search (raw) 1341 } elseif ($boolean_search === 2) { 1342 return [" AND MATCH (bugdb.email,sdesc,ldesc) AGAINST (" . $dbh->quote($search) . " IN BOOLEAN MODE)", $ignored]; 1343 } 1344 } 1345 // require any of the words (any) 1346 return [" AND MATCH (bugdb.email,sdesc,ldesc) AGAINST (" . $dbh->quote($search) . ")", $ignored]; 1347} 1348 1349/** 1350 * Send the confirmation mail to confirm a subscription removal 1351 * 1352 * @param integer bug ID 1353 * @param string email to remove 1354 * @param array bug data 1355 * 1356 * @return void 1357 */ 1358function unsubscribe_hash($bug_id, $email) 1359{ 1360 global $dbh, $siteBig, $site_method, $site_url, $bugEmail; 1361 1362 $now = time(); 1363 $hash = crypt($email . $bug_id, $now); 1364 1365 $query = " 1366 UPDATE bugdb_subscribe 1367 SET unsubscribe_date = '{$now}', 1368 unsubscribe_hash = ? 1369 WHERE bug_id = ? AND email = ? 1370 "; 1371 1372 $affected = $dbh->prepare($query, null, null)->execute([$hash, $bug_id, $email]); 1373 1374 if ($affected > 0) { 1375 $hash = urlencode($hash); 1376 /* user text with attention, headers and previous messages */ 1377 $user_text = <<< USER_TEXT 1378ATTENTION! Do NOT reply to this email! 1379 1380A request has been made to remove your subscription to 1381{$siteBig} bug #{$bug_id} 1382 1383To view the bug in question please use this link: 1384{$site_method}://{$site_url}{$basedir}/bug.php?id={$bug_id} 1385 1386To confirm the removal please use this link: 1387{$site_method}://{$site_url}{$basedir}/bug.php?id={$bug_id}&unsubscribe=1&t={$hash} 1388 1389 1390USER_TEXT; 1391 1392 bugs_mail( 1393 $email, 1394 "[$siteBig-BUG-unsubscribe] #{$bug_id}", 1395 $user_text, 1396 "From: {$siteBig} Bug Database <{$bugEmail}>\r\n". 1397 "X-PHP-Bug: {$bug_id}\r\n". 1398 "In-Reply-To: <bug-{$bug_id}@{$site_url}>" 1399 ); 1400 } 1401} 1402 1403 1404/** 1405 * Remove a subscribtion 1406 * 1407 * @param integer bug ID 1408 * @param string hash 1409 * 1410 * @return void 1411 */ 1412function unsubscribe($bug_id, $hash) 1413{ 1414 global $dbh; 1415 1416 $bug_id = (int) $bug_id; 1417 1418 $query = " 1419 SELECT bug_id, email, unsubscribe_date, unsubscribe_hash 1420 FROM bugdb_subscribe 1421 WHERE bug_id = ? AND unsubscribe_hash = ? LIMIT 1 1422 "; 1423 1424 $sub = $dbh->prepare($query)->execute([$bug_id, $hash])->fetch(); 1425 1426 if (!$sub) { 1427 return false; 1428 } 1429 1430 $now = time(); 1431 $requested_on = $sub['unsubscribe_date']; 1432 /* 24hours delay to answer the mail */ 1433 if (($now - $requested_on) > 86400) { 1434 return false; 1435 } 1436 1437 $query = " 1438 DELETE FROM bugdb_subscribe 1439 WHERE bug_id = ? AND unsubscribe_hash = ? AND email = ? 1440 "; 1441 $dbh->prepare($query)->execute([$bug_id, $hash, $sub['email']]); 1442 return true; 1443} 1444 1445/** 1446 * Add bug comment 1447 */ 1448function bugs_add_comment($bug_id, $from, $from_name, $comment, $type = 'comment') 1449{ 1450 global $dbh; 1451 1452 return $dbh->prepare(" 1453 INSERT INTO bugdb_comments (bug, email, reporter_name, comment, comment_type, ts) 1454 VALUES (?, ?, ?, ?, ?, NOW()) 1455 ")->execute([ 1456 $bug_id, $from, $from_name, $comment, $type 1457 ]); 1458} 1459 1460/** 1461 * Change bug status 1462 */ 1463function bugs_status_change($bug_id, $new_status) 1464{ 1465 global $dbh; 1466 1467 return $dbh->prepare(" 1468 UPDATE bugdb SET status = ? WHERE id = ? LIMIT 1 1469 ")->execute([$new_status, $bug_id]); 1470} 1471 1472/** 1473 * Verify bug password 1474 * 1475 * @return bool 1476 */ 1477 1478function verify_bug_passwd($bug_id, $passwd) 1479{ 1480 global $dbh; 1481 1482 return (bool) $dbh->prepare('SELECT 1 FROM bugdb WHERE id = ? AND passwd = ?')->execute([$bug_id, $passwd])->fetch(\PDO::FETCH_NUM)[0]; 1483} 1484 1485/** 1486 * Mailer function. When DEVBOX is defined, this only outputs the parameters as-is. 1487 * 1488 * @return bool 1489 * 1490 */ 1491function bugs_mail($to, $subject, $message, $headers = '', $params = '') 1492{ 1493 if (empty($params)) { 1494 $params = '-f noreply@php.net'; 1495 } 1496 if (DEVBOX === true) { 1497 if (defined('DEBUG_MAILS')) { 1498 echo '<pre>'; 1499 var_dump(htmlspecialchars($to), htmlspecialchars($subject), htmlspecialchars($message), htmlspecialchars($headers)); 1500 echo '</pre>'; 1501 } 1502 return true; 1503 } 1504 return @mail($to, $subject, $message, $headers, $params); 1505} 1506 1507/** 1508 * Prints out the XHTML headers and top of the page. 1509 * 1510 * @param string $title a string to go into the header's <title> 1511 * @return void 1512 */ 1513function response_header($title, $extraHeaders = '') 1514{ 1515 global $_header_done, $self, $auth_user, $logged_in, $siteBig, $site_method, $site_url, $basedir; 1516 1517 $is_logged = false; 1518 1519 if ($_header_done) { 1520 return; 1521 } 1522 1523 if ($logged_in === 'developer') { 1524 $is_logged = true; 1525 $username = $auth_user->handle; 1526 } else if (!empty($_SESSION['user'])) { 1527 $is_logged = true; 1528 $username = $_SESSION['user']; 1529 } 1530 1531 $_header_done = true; 1532 1533 header('Content-Type: text/html; charset=UTF-8'); 1534 header('X-Frame-Options: SAMEORIGIN'); 1535?> 1536<!DOCTYPE html> 1537<html lang="en"> 1538<head> 1539 <meta charset="utf-8"> 1540 <?php echo $extraHeaders; ?> 1541 <base href="<?php echo $site_method?>://<?php echo $site_url, $basedir; ?>/"> 1542 <title><?php echo $siteBig; ?> :: <?php echo $title; ?></title> 1543 <link rel="shortcut icon" href="<?php echo $site_method?>://<?php echo $site_url, $basedir; ?>/images/favicon.ico"> 1544 <link rel="stylesheet" href="<?php echo $site_method?>://<?php echo $site_url, $basedir; ?>/css/style.css"> 1545</head> 1546 1547<body> 1548 1549<table id="top" class="head" cellspacing="0" cellpadding="0"> 1550 <tr> 1551 <td class="head-logo"> 1552 <a href="/"><img src="images/logo.png" alt="Bugs" vspace="2" hspace="2"></a> 1553 </td> 1554 1555 <td class="head-menu"> 1556 <a href="https://php.net/">php.net</a> | 1557 <a href="https://php.net/support.php">support</a> | 1558 <a href="https://php.net/docs.php">documentation</a> | 1559 <a href="report.php">report a bug</a> | 1560 <a href="search.php">advanced search</a> | 1561 <a href="search-howto.php">search howto</a> | 1562 <a href="stats.php">statistics</a> | 1563 <a href="random">random bug</a> | 1564<?php if ($is_logged) { ?> 1565 <a href="search.php?cmd=display&assign=<?php echo $username;?>">my bugs</a> | 1566<?php if ($logged_in === 'developer') { ?> 1567 <a href="/admin/">admin</a> | 1568<?php } ?> 1569 <a href="logout.php">logout</a> 1570<?php } else { ?> 1571 <a href="login.php">login</a> 1572<?php } ?> 1573 </td> 1574 </tr> 1575 1576 <tr> 1577 <td class="head-search" colspan="2"> 1578 <form method="get" action="search.php"> 1579 <p class="head-search"> 1580 <input type="hidden" name="cmd" value="display"> 1581 <small>go to bug id or search bugs for</small> 1582 <input class="small" type="text" name="search_for" value="<?php print isset($_GET['search_for']) ? htmlspecialchars($_GET['search_for']) : ''; ?>" size="30"> 1583 <input type="image" src="images/small_submit_white.gif" alt="search" style="vertical-align: middle;"> 1584 </p> 1585 </form> 1586 </td> 1587 </tr> 1588</table> 1589 1590<table class="middle" cellspacing="0" cellpadding="0"> 1591 <tr> 1592 <td class="content"> 1593<?php 1594} 1595 1596 1597function response_footer($extra_html = '') 1598{ 1599 global $_footer_done, $LAST_UPDATED, $basedir; 1600 1601 if ($_footer_done) { 1602 return; 1603 } 1604 $_footer_done = true; 1605?> 1606 </td> 1607 </tr> 1608</table> 1609 1610<?php echo $extra_html; ?> 1611 1612<table class="foot" cellspacing="0" cellpadding="0"> 1613 <tr> 1614 <td class="foot-bar" colspan="2"> </td> 1615 </tr> 1616 1617 <tr> 1618 <td class="foot-copy"> 1619 <small> 1620 <a href="https://php.net/"><img src="images/logo-small.gif" align="left" valign="middle" hspace="3" alt="PHP"></a> 1621 <a href="https://php.net/copyright.php">Copyright © 2001-<?php echo date('Y'); ?> The PHP Group</a><br> 1622 All rights reserved. 1623 </small> 1624 </td> 1625 <td class="foot-source"> 1626 <small>Last updated: <?php echo $LAST_UPDATED; ?></small> 1627 </td> 1628 </tr> 1629</table> 1630</body> 1631</html> 1632<?php 1633} 1634 1635 1636/** 1637 * Redirects to the given full or partial URL. 1638 * 1639 * @param string $url Full/partial url to redirect to 1640 */ 1641function redirect($url) 1642{ 1643 header("Location: {$url}"); 1644 exit; 1645} 1646 1647 1648/** 1649 * Turns the provided email address into a "mailto:" hyperlink. 1650 * 1651 * The link and link text are obfuscated by alternating Ord and Hex 1652 * entities. 1653 * 1654 * @param string $email the email address to make the link for 1655 * @param string $linktext a string for the visible part of the link. 1656 * If not provided, the email address is used. 1657 * @param string $extras a string of extra attributes for the <a> element 1658 * 1659 * @return string the HTML hyperlink of an email address 1660 */ 1661function make_mailto_link($email, $linktext = '', $extras = '') 1662{ 1663 $tmp = ''; 1664 for ($i = 0, $l = strlen($email); $i<$l; $i++) { 1665 if ($i % 2) { 1666 $tmp .= '&#' . ord($email[$i]) . ';'; 1667 } else { 1668 $tmp .= '&#x' . dechex(ord($email[$i])) . ';'; 1669 } 1670 } 1671 1672 return "<a {$extras} href='mailto:{$tmp}'>" . ($linktext != '' ? $linktext : $tmp) . '</a>'; 1673} 1674 1675/** 1676 * Turns bug/feature request numbers into hyperlinks 1677 * 1678 * @param string $text the text to check for bug numbers 1679 * 1680 * @return string the string with bug numbers hyperlinked 1681 */ 1682function make_ticket_links($text) 1683{ 1684 return preg_replace( 1685 '/(?<![>a-z])(bug(?:fix)?|feat(?:ure)?|doc(?:umentation)?|req(?:uest)?|duplicated of)\s+#?([0-9]+)/i', 1686 "<a href='bug.php?id=\\2'>\\0</a>", 1687 $text 1688 ); 1689} 1690 1691function get_ticket_links($text) 1692{ 1693 $matches = []; 1694 1695 preg_match_all('/(?<![>a-z])(?:bug(?:fix)?|feat(?:ure)?|doc(?:umentation)?|req(?:uest)?|duplicated of)\s+#?([0-9]+)/i', $text, $matches); 1696 1697 return $matches[1]; 1698} 1699 1700/** 1701 * Generates a random password 1702 */ 1703function bugs_gen_passwd($length = 8) 1704{ 1705 return substr(md5(uniqid(time(), true)), 0, $length); 1706} 1707 1708function bugs_get_hash($passwd) 1709{ 1710 return hash_hmac('sha256', $passwd, getenv('USER_PWD_SALT')); 1711} 1712