mirror of
https://github.com/php/web-bugs.git
synced 2026-03-24 07:42:08 +01:00
1712 lines
50 KiB
PHP
1712 lines
50 KiB
PHP
<?php
|
|
|
|
/* User flags */
|
|
define('BUGS_NORMAL_USER', 1<<0);
|
|
define('BUGS_DEV_USER', 1<<1);
|
|
define('BUGS_TRUSTED_DEV', 1<<2);
|
|
define('BUGS_SECURITY_DEV', 1<<3);
|
|
|
|
/* Contains functions and variables used throughout the bug system */
|
|
|
|
// used in mail_bug_updates(), below, and class for search results
|
|
$tla = [
|
|
'Open' => 'Opn',
|
|
'Not a bug' => 'Nab',
|
|
'Feedback' => 'Fbk',
|
|
'No Feedback' => 'NoF',
|
|
'Wont fix' => 'Wfx',
|
|
'Duplicate' => 'Dup',
|
|
'Critical' => 'Ctl',
|
|
'Assigned' => 'Asn',
|
|
'Analyzed' => 'Ana',
|
|
'Verified' => 'Ver',
|
|
'Suspended' => 'Sus',
|
|
'Closed' => 'Csd',
|
|
'Spam' => 'Spm',
|
|
'Re-Opened' => 'ReO',
|
|
];
|
|
|
|
$bug_types = [
|
|
'Bug' => 'Bug',
|
|
'Feature/Change Request' => 'Req',
|
|
'Documentation Problem' => 'Doc',
|
|
'Security' => 'Sec Bug'
|
|
];
|
|
|
|
// Used in show_state_options()
|
|
$state_types = [
|
|
'Open' => 2,
|
|
'Closed' => 2,
|
|
'Re-Opened' => 1,
|
|
'Duplicate' => 1,
|
|
'Critical' => 1,
|
|
'Assigned' => 2,
|
|
'Not Assigned' => 0,
|
|
'Analyzed' => 1,
|
|
'Verified' => 1,
|
|
'Suspended' => 1,
|
|
'Wont fix' => 1,
|
|
'No Feedback' => 1,
|
|
'Feedback' => 1,
|
|
'Old Feedback' => 0,
|
|
'Stale' => 0,
|
|
'Fresh' => 0,
|
|
'Not a bug' => 1,
|
|
'Spam' => 1,
|
|
'All' => 0,
|
|
];
|
|
|
|
/**
|
|
* Authentication
|
|
*/
|
|
function verify_user_password($user, $pass)
|
|
{
|
|
global $errors;
|
|
|
|
$post = http_build_query(
|
|
[
|
|
'token' => getenv('AUTH_TOKEN'),
|
|
'username' => $user,
|
|
'password' => $pass,
|
|
]
|
|
);
|
|
|
|
$opts = [
|
|
'method' => 'POST',
|
|
'header' => 'Content-type: application/x-www-form-urlencoded',
|
|
'content' => $post,
|
|
];
|
|
|
|
$ctx = stream_context_create(['http' => $opts]);
|
|
|
|
$s = file_get_contents('https://main.php.net/fetch/cvsauth.php', false, $ctx);
|
|
|
|
$a = @unserialize($s);
|
|
if (!is_array($a)) {
|
|
$errors[] = "Failed to get authentication information.\nMaybe master is down?\n";
|
|
return false;
|
|
}
|
|
if (isset($a['errno'])) {
|
|
$errors[] = "Authentication failed: {$a['errstr']}\n";
|
|
return false;
|
|
}
|
|
|
|
$_SESSION["user"] = $user;
|
|
|
|
return true;
|
|
}
|
|
|
|
function bugs_has_access ($bug_id, $bug, $pw, $user_flags)
|
|
{
|
|
global $auth_user;
|
|
|
|
if ($bug['private'] != 'Y') {
|
|
return true;
|
|
}
|
|
|
|
// When the bug is private, only the submitter, trusted devs, security devs and assigned dev
|
|
// should see the report info
|
|
if ($user_flags & (BUGS_SECURITY_DEV | BUGS_TRUSTED_DEV)) {
|
|
// trusted and security dev
|
|
return true;
|
|
} else if (($user_flags == BUGS_NORMAL_USER) && $pw != '' && verify_bug_passwd($bug_id, bugs_get_hash($pw))) {
|
|
// The submitter
|
|
return true;
|
|
} else if (($user_flags & BUGS_DEV_USER) && $bug['reporter_name'] != '' &&
|
|
strtolower($bug['reporter_name']) == strtolower($auth_user->handle)) {
|
|
// The submitter (php developer)
|
|
return true;
|
|
} else if (($user_flags & BUGS_DEV_USER) && $bug['assign'] != '' &&
|
|
strtolower($bug['assign']) == strtolower($auth_user->handle)) {
|
|
// The assigned dev
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function bugs_authenticate (&$user, &$pw, &$logged_in, &$user_flags)
|
|
{
|
|
global $auth_user, $ROOT_DIR;
|
|
|
|
// Default values
|
|
$user = '';
|
|
$pw = '';
|
|
$logged_in = false;
|
|
|
|
$user_flags = BUGS_NORMAL_USER;
|
|
|
|
// Set username and password
|
|
if (!empty($_POST['pw'])) {
|
|
if (empty($_POST['user'])) {
|
|
$user = '';
|
|
} else {
|
|
$user = htmlspecialchars($_POST['user']);
|
|
}
|
|
$user = strtolower($user);
|
|
$pw = $_POST['pw'];
|
|
} elseif (isset($auth_user) && is_object($auth_user) && $auth_user->handle) {
|
|
$user = $auth_user->handle;
|
|
$pw = $auth_user->password;
|
|
}
|
|
|
|
// Authentication and user level check
|
|
// User levels are: reader (0), commenter/patcher/etc. (edit = 3), submitter (edit = 2), developer (edit = 1)
|
|
if (!empty($_SESSION["user"])) {
|
|
$user = $_SESSION["user"];
|
|
$user_flags = BUGS_DEV_USER;
|
|
$logged_in = 'developer';
|
|
$auth_user = new stdClass;
|
|
$auth_user->handle = $user;
|
|
$auth_user->email = "{$user}@php.net";
|
|
$auth_user->name = $user;
|
|
} elseif ($user != '' && $pw != '' && verify_user_password($user, $pw)) {
|
|
$user_flags = BUGS_DEV_USER;
|
|
$logged_in = 'developer';
|
|
$auth_user = new stdClass;
|
|
$auth_user->handle = $user;
|
|
$auth_user->email = "{$user}@php.net";
|
|
$auth_user->name = $user;
|
|
} else {
|
|
$auth_user = new stdClass;
|
|
$auth_user->email = isset($_POST['in']['email']) ? $_POST['in']['email'] : '';
|
|
$auth_user->handle = '';
|
|
$auth_user->name = '';
|
|
}
|
|
|
|
// Check if developer is trusted
|
|
if ($logged_in == 'developer') {
|
|
require_once "{$ROOT_DIR}/include/trusted-devs.php";
|
|
|
|
if (in_array(strtolower($user), $trusted_developers)) {
|
|
$user_flags |= BUGS_TRUSTED_DEV;
|
|
}
|
|
if (in_array(strtolower($user), $security_developers)) {
|
|
$user_flags |= BUGS_SECURITY_DEV;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Primitive check for SPAM. Add more later. */
|
|
function is_spam($string)
|
|
{
|
|
// @php.net users are given permission to spam... we gotta eat! See also bug #48126
|
|
if (!empty($GLOBALS['auth_user']->handle)) {
|
|
return false;
|
|
}
|
|
|
|
if (preg_match_all('/https?:\/\/(\S+)/', $string, $matches)) {
|
|
foreach ($matches[1] as $match) {
|
|
if (!preg_match('/^[^\/)]*(php\.net|github\.com)/', $match)) {
|
|
return "Due to large amounts of spam, only links to php.net and github.com (including subdomains like gist.github.com) are allowed.";
|
|
}
|
|
}
|
|
}
|
|
|
|
$keywords = [
|
|
'spy',
|
|
'bdsm',
|
|
'massage',
|
|
'mortage',
|
|
'sex',
|
|
'11nong',
|
|
'oxycontin',
|
|
'distance-education',
|
|
'sismatech',
|
|
'justiceplan',
|
|
'prednisolone',
|
|
'baclofen',
|
|
'diflucan',
|
|
'unbra.se',
|
|
'objectis',
|
|
'angosso',
|
|
'colchicine',
|
|
'zovirax',
|
|
'korsbest',
|
|
'coachbags',
|
|
'chaneljpoutlet',
|
|
'\/Members\/',
|
|
'michaelkorsshop',
|
|
'mkmichaelkors',
|
|
'Burberrysale4u',
|
|
'gadboisphotos',
|
|
'oakleysunglasseslol',
|
|
'partydressuk',
|
|
'leslunettesdesoleil',
|
|
'PaulRGuthrie',
|
|
'[a-z]*?fuck[a-z]*?',
|
|
'jerseys',
|
|
'wholesale',
|
|
'fashionretailshop01',
|
|
'amoxicillin',
|
|
'helpdeskaustralia',
|
|
'porn',
|
|
'aarinkaur',
|
|
'lildurk',
|
|
'tvfun',
|
|
];
|
|
|
|
if (preg_match('/\b('. implode('|', $keywords) . ')\b/i', $string)) {
|
|
return "Comment contains spam word, consider rewording.";
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Primitive check for SPAMmy user. Add more later. */
|
|
function is_spam_user($email)
|
|
{
|
|
if (preg_match("/(rhsoft|reindl|phpbugreports|bugreprtsz|bugreports\d*@gmail|training365)/i", $email)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Obfuscates email addresses to hinder spammer's spiders
|
|
*
|
|
* Turns "@" into character entities that get interpreted as "at" and
|
|
* turns "." into character entities that get interpreted as "dot".
|
|
*
|
|
* @param string $txt the email address to be obfuscated
|
|
* @param string $format how the output will be displayed ('html', 'text', 'reverse')
|
|
*
|
|
* @return string the altered email address
|
|
*/
|
|
function spam_protect($txt, $format = 'html')
|
|
{
|
|
/* php.net addresses are not protected! */
|
|
if (preg_match('/^(.+)@php\.net$/i', $txt)) {
|
|
return $txt;
|
|
}
|
|
if ($format == 'html') {
|
|
$translate = [
|
|
'@' => ' at ',
|
|
'.' => ' dot ',
|
|
];
|
|
} else {
|
|
$translate = [
|
|
'@' => ' at ',
|
|
'.' => ' dot ',
|
|
];
|
|
if ($format == 'reverse') {
|
|
$translate = array_flip($translate);
|
|
}
|
|
}
|
|
return strtr($txt, $translate);
|
|
}
|
|
|
|
/**
|
|
* Goes through each variable submitted and returns the value
|
|
* from the first variable which has a non-empty value
|
|
*
|
|
* Handy function for when you're dealing with user input or a default.
|
|
*
|
|
* @param mixed as many variables as you wish to check
|
|
*
|
|
* @return mixed the value, if any
|
|
*
|
|
* @see field(), txfield()
|
|
*/
|
|
function oneof()
|
|
{
|
|
foreach (func_get_args() as $arg) {
|
|
if ($arg) {
|
|
return $arg;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the data from the field requested and sanitizes
|
|
* it for use as HTML
|
|
*
|
|
* If the data from a form submission exists, that is used.
|
|
* But if that's not there, the info is obtained from the database.
|
|
*
|
|
* @param string $n the name of the field to be looked for
|
|
*
|
|
* @return mixed the data requested
|
|
*
|
|
* @see oneof(), txfield()
|
|
*/
|
|
function field($n)
|
|
{
|
|
return oneof(isset($_POST['in']) ?
|
|
htmlspecialchars(isset($_POST['in'][$n]) ? $_POST['in'][$n] : '') : null,
|
|
htmlspecialchars($GLOBALS['bug'][$n] ?? ''));
|
|
}
|
|
|
|
/**
|
|
* Escape string so it can be used as HTML
|
|
*
|
|
* @param string $in the string to be sanitized
|
|
*
|
|
* @return string the sanitized string
|
|
*
|
|
* @see txfield()
|
|
*/
|
|
function clean($in)
|
|
{
|
|
return mb_encode_numericentity($in,
|
|
[
|
|
0x0, 0x8, 0, 0xffffff,
|
|
0xb, 0xc, 0, 0xffffff,
|
|
0xe, 0x1f, 0, 0xffffff,
|
|
0x22, 0x22, 0, 0xffffff,
|
|
0x26, 0x27, 0, 0xffffff,
|
|
0x3c, 0x3c, 0, 0xffffff,
|
|
0x3e, 0x3e, 0, 0xffffff,
|
|
0x7f, 0x84, 0, 0xffffff,
|
|
0x86, 0x9f, 0, 0xffffff,
|
|
0xfdd0, 0xfdef, 0, 0xffffff,
|
|
0x1fffe, 0x1ffff, 0, 0xffffff,
|
|
0x2fffe, 0x2ffff, 0, 0xffffff,
|
|
0x3fffe, 0x3ffff, 0, 0xffffff,
|
|
0x4fffe, 0x4ffff, 0, 0xffffff,
|
|
0x5fffe, 0x5ffff, 0, 0xffffff,
|
|
0x6fffe, 0x6ffff, 0, 0xffffff,
|
|
0x7fffe, 0x7ffff, 0, 0xffffff,
|
|
0x8fffe, 0x8ffff, 0, 0xffffff,
|
|
0x9fffe, 0x9ffff, 0, 0xffffff,
|
|
0xafffe, 0xaffff, 0, 0xffffff,
|
|
0xbfffe, 0xbffff, 0, 0xffffff,
|
|
0xcfffe, 0xcffff, 0, 0xffffff,
|
|
0xdfffe, 0xdffff, 0, 0xffffff,
|
|
0xefffe, 0xeffff, 0, 0xffffff,
|
|
0xffffe, 0xfffff, 0, 0xffffff,
|
|
0x10fffe, 0x10ffff, 0, 0xffffff,
|
|
],
|
|
'UTF-8');
|
|
}
|
|
|
|
/**
|
|
* Returns the data from the field requested and sanitizes
|
|
* it for use as plain text
|
|
*
|
|
* If the data from a form submission exists, that is used.
|
|
* But if that's not there, the info is obtained from the database.
|
|
*
|
|
* @param string $n the name of the field to be looked for
|
|
*
|
|
* @return mixed the data requested
|
|
*
|
|
* @see clean()
|
|
*/
|
|
function txfield($n, $bug = null, $in = null)
|
|
{
|
|
$one = (isset($in) && isset($in[$n])) ? $in[$n] : false;
|
|
if ($one) {
|
|
return $one;
|
|
}
|
|
|
|
$two = (isset($bug) && isset($bug[$n])) ? $bug[$n] : false;
|
|
if ($two) {
|
|
return $two;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Prints age <option>'s for use in a <select>
|
|
*
|
|
* @param string $current the field's current value
|
|
*
|
|
* @return void
|
|
*/
|
|
function show_byage_options($current)
|
|
{
|
|
$opts = [
|
|
'0' => 'the beginning',
|
|
'1' => 'yesterday',
|
|
'7' => '7 days ago',
|
|
'15' => '15 days ago',
|
|
'30' => '30 days ago',
|
|
'90' => '90 days ago',
|
|
];
|
|
foreach ($opts as $k => $v) {
|
|
echo "<option value=\"$k\"", ($current==$k ? ' selected="selected"' : ''), ">$v</option>\n";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Prints a list of <option>'s for use in a <select> element
|
|
* asking how many bugs to display
|
|
*
|
|
* @param int $limit the presently selected limit to be used as the default
|
|
*
|
|
* @return void
|
|
*/
|
|
function show_limit_options($limit = 30)
|
|
{
|
|
for ($i = 10; $i < 100; $i += 10) {
|
|
echo '<option value="' . $i . '"';
|
|
if ($limit == $i) {
|
|
echo ' selected="selected"';
|
|
}
|
|
echo ">$i bugs</option>\n";
|
|
}
|
|
|
|
echo '<option value="All"';
|
|
if ($limit == 'All') {
|
|
echo ' selected="selected"';
|
|
}
|
|
echo ">All</option>\n";
|
|
}
|
|
|
|
/**
|
|
* Prints bug type <option>'s for use in a <select>
|
|
*
|
|
* Options include "Bug", "Documentation Problem" and "Feature/Change Request."
|
|
*
|
|
* @param string $current bug's current type
|
|
* @param bool $deprecated whether or not deprecated types should be shown
|
|
* @param bool $all whether or not 'All' should be an option
|
|
*
|
|
* @retun void
|
|
*/
|
|
function show_type_options($current, $deprecated, $all = false)
|
|
{
|
|
global $bug_types;
|
|
|
|
if ($all) {
|
|
if (!$current) {
|
|
$current = 'All';
|
|
}
|
|
echo '<option value="All"';
|
|
if ($current == 'All') {
|
|
echo ' selected="selected"';
|
|
}
|
|
echo ">All</option>\n";
|
|
} elseif (!$current) {
|
|
$current = 'bug';
|
|
}
|
|
|
|
foreach ($bug_types as $k => $v) {
|
|
if ($k !== 'Security' && !$deprecated) {
|
|
continue;
|
|
}
|
|
$selected = strcasecmp($current, $k) ? '' : ' selected="selected"';
|
|
$k = htmlentities($k, ENT_QUOTES);
|
|
echo "<option value=\"$k\"$selected>$k</option>";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Prints bug state <option>'s for use in a <select> list
|
|
*
|
|
* @param string $state the bug's present state
|
|
* @param int $user_mode the 'edit' mode
|
|
* @param string $default the default value
|
|
*
|
|
* @return void
|
|
*/
|
|
function show_state_options($state, $user_mode = 0, $default = '', $assigned = 0)
|
|
{
|
|
global $state_types, $tla;
|
|
|
|
if (!$state && !$default) {
|
|
$state = $assigned ? 'Assigned' : 'Open';
|
|
} elseif (!$state) {
|
|
$state = $default;
|
|
}
|
|
|
|
/* regular users can only pick states with type 2 for unclosed bugs */
|
|
if ($state != 'All' && isset($state_types[$state]) && $state_types[$state] == 1 && $user_mode == 2) {
|
|
switch ($state)
|
|
{
|
|
/* If state was 'Feedback', set state automatically to 'Assigned' if the bug was
|
|
* assigned to someone before it to be set to 'Feedback', otherwise set it to 'Open'.
|
|
*/
|
|
case 'Feedback':
|
|
if ($assigned) {
|
|
echo '<option class="'.$tla['Assigned'].'">Assigned</option>'."\n";
|
|
} else {
|
|
echo '<option class="'.$tla['Open'].'">Open</option>'."\n";
|
|
}
|
|
break;
|
|
case 'No Feedback':
|
|
echo '<option class="'.$tla['Re-Opened'].'">Re-Opened</option>'."\n";
|
|
break;
|
|
default:
|
|
echo '<option';
|
|
if (isset($tla[$state])) {
|
|
echo ' class="'.$tla[$state].'"';
|
|
}
|
|
echo '>'.$state.'</option>'."\n";
|
|
break;
|
|
}
|
|
/* Allow state 'Closed' always when current state is not 'Not a bug' */
|
|
if ($state != 'Not a bug') {
|
|
echo '<option class="'.$tla['Closed'].'">Closed</option>'."\n";
|
|
}
|
|
} else {
|
|
foreach($state_types as $type => $mode) {
|
|
if (($state == 'Closed' && $type == 'Open')
|
|
|| ($state == 'Open' && $type == 'Re-Opened')) {
|
|
continue;
|
|
}
|
|
if ($mode >= $user_mode) {
|
|
echo '<option';
|
|
if (isset($tla[$type])) {
|
|
echo ' class="'.$tla[$type].'"';
|
|
}
|
|
if ($type == $state) {
|
|
echo ' selected="selected"';
|
|
}
|
|
echo ">$type</option>\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Prints bug resolution <option>'s for use in a <select> list
|
|
*
|
|
* @param string $current the bug's present state
|
|
* @param int $expande whether or not a longer explanation should be displayed
|
|
*
|
|
* @return void
|
|
*/
|
|
function show_reason_types($current = '', $expanded = 0)
|
|
{
|
|
global $RESOLVE_REASONS;
|
|
|
|
if ($expanded) {
|
|
echo '<option value=""></option>' . "\n";
|
|
}
|
|
foreach ($RESOLVE_REASONS as $val)
|
|
{
|
|
if (empty($val['package_name'])) {
|
|
$sel = ($current == $val['name']) ? " selected='selected'" : '';
|
|
echo "<option value='{$val['name']}' {$sel} >{$val['title']}";
|
|
if ($expanded) {
|
|
echo " ({$val['status']})";
|
|
}
|
|
echo "</option>\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Prints PHP version number <option>'s for use in a <select> list
|
|
*
|
|
* @param string $current the bug's current version number
|
|
*
|
|
* @return void
|
|
*/
|
|
function show_version_options($current)
|
|
{
|
|
global $ROOT_DIR, $versions;
|
|
|
|
$use = 0;
|
|
|
|
echo '<option value="">--Please Select--</option>' , "\n";
|
|
foreach($versions as $v) {
|
|
echo '<option';
|
|
if ($current == $v) {
|
|
echo ' selected="selected"';
|
|
}
|
|
echo '>' , htmlspecialchars($v) , "</option>\n";
|
|
if ($current == $v) {
|
|
$use++;
|
|
}
|
|
}
|
|
if (!$use && $current) {
|
|
echo '<option selected="selected">' , htmlspecialchars($current) , "</option>\n";
|
|
}
|
|
echo '<option value="earlier">Earlier? Upgrade first!</option>', "\n";
|
|
}
|
|
|
|
/**
|
|
* Prints package name <option>'s for use in a <select> list
|
|
*
|
|
* @param string $current the bug's present state
|
|
* @param int $show_any whether or not 'Any' should be an option. 'Any'
|
|
* will only be printed if no $current value exists.
|
|
* @param string $default the default value
|
|
*
|
|
* @return void
|
|
*/
|
|
function show_package_options($current, $show_any, $default = '')
|
|
{
|
|
global $pseudo_pkgs;
|
|
$disabled_style = ' style="background-color:#eee;"';
|
|
static $bug_groups;
|
|
|
|
if (!isset($bug_groups)) {
|
|
$bug_groups = array_filter(
|
|
$pseudo_pkgs,
|
|
function ($value) {
|
|
return is_array($value[2]);
|
|
}
|
|
);
|
|
}
|
|
|
|
if (!$current && (!$default || $default == 'none') && !$show_any) {
|
|
echo "<option value=\"none\">--Please Select--</option>\n";
|
|
} elseif (!$current && $show_any == 1) {
|
|
$current = 'Any';
|
|
} elseif (!$current) {
|
|
$current = $default;
|
|
}
|
|
|
|
if (!is_array($bug_groups)) {
|
|
return;
|
|
}
|
|
|
|
|
|
foreach ($bug_groups as $key => $bug_group) {
|
|
echo "<optgroup label=\"{$bug_group[0]}\"" .
|
|
(($bug_group[1]) ? $disabled_style : ''), "\n>";
|
|
|
|
array_unshift($bug_group[2], $key);
|
|
foreach ($bug_group[2] as $name) {
|
|
$child = $pseudo_pkgs[$name];
|
|
if ($show_any == 1 || $key != 'Any') {
|
|
echo "<option value=\"$name\"";
|
|
if ((is_array($current) && in_array($name, $current)) || ($name == $current)) {
|
|
echo ' selected="selected"';
|
|
}
|
|
// Show disabled categories with different background color in listing
|
|
echo (($child[1]) ? $disabled_style : ''), ">{$child[0]}</option>\n";
|
|
}
|
|
}
|
|
echo "</optgroup>\n";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Prints a series of radio inputs to determine how the search
|
|
* term should be looked for
|
|
*
|
|
* @param string $current the users present selection
|
|
*
|
|
* @return void
|
|
*/
|
|
function show_boolean_options($current)
|
|
{
|
|
$options = ['any', 'all', 'raw'];
|
|
foreach ($options as $val => $type) {
|
|
echo '<input type="radio" id="boolean' . $val . '" name="boolean" value="', $val, '"';
|
|
if ($val === $current) {
|
|
echo ' checked="checked"';
|
|
}
|
|
echo '><label for="boolean' . $val . '">'.$type.'</label>';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Display errors or warnings as a <ul> inside a <div>
|
|
*
|
|
* Here's what happens depending on $in:
|
|
* + string: value is printed
|
|
* + array: looped through and each value is printed.
|
|
* If array is empty, nothing is displayed.
|
|
*
|
|
* @param string|array $in see long description
|
|
* @param string $class name of the HTML class for the <div> tag. ("errors", "warnings")
|
|
* @param string $head string to be put above the message
|
|
*
|
|
* @return bool true if errors were submitted, false if not
|
|
*/
|
|
function display_bug_error($in, $class = 'errors', $head = 'ERROR:')
|
|
{
|
|
if (!is_array($in)) {
|
|
$in = [$in];
|
|
} elseif (!count($in)) {
|
|
return false;
|
|
}
|
|
|
|
echo "<div class='{$class}'>{$head}<ul>";
|
|
foreach ($in as $msg) {
|
|
echo '<li>' , htmlspecialchars($msg) , "</li>\n";
|
|
}
|
|
echo "</ul></div>\n";
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Returns array of changes going to be made
|
|
*/
|
|
function bug_diff($bug, $in)
|
|
{
|
|
$changed = [];
|
|
|
|
if (!empty($in['email']) && (trim($in['email']) != trim($bug['email']))) {
|
|
$changed['reported_by']['from'] = spam_protect($bug['email'], 'text');
|
|
$changed['reported_by']['to'] = spam_protect(txfield('email', $bug, $in), 'text');
|
|
}
|
|
|
|
$fields = [
|
|
'sdesc' => 'Summary',
|
|
'status' => 'Status',
|
|
'bug_type' => 'Type',
|
|
'package_name' => 'Package',
|
|
'php_os' => 'Operating System',
|
|
'php_version' => 'PHP Version',
|
|
'assign' => 'Assigned To',
|
|
'block_user_comment' => 'Block user comment',
|
|
'private' => 'Private report',
|
|
'cve_id' => 'CVE-ID'
|
|
];
|
|
|
|
foreach (array_keys($fields) as $name) {
|
|
if (array_key_exists($name, $in) && array_key_exists($name, $bug)) {
|
|
$to = trim($in[$name]);
|
|
$from = trim($bug[$name]);
|
|
if ($from != $to) {
|
|
if (in_array($name, ['private', 'block_user_comment'])) {
|
|
$from = $from == 'Y' ? 'Yes' : 'No';
|
|
$to = $to == 'Y' ? 'Yes' : 'No';
|
|
}
|
|
$changed[$name]['from'] = $from;
|
|
$changed[$name]['to'] = $to;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $changed;
|
|
}
|
|
|
|
function bug_diff_render_html($diff)
|
|
{
|
|
$fields = [
|
|
'sdesc' => 'Summary',
|
|
'status' => 'Status',
|
|
'bug_type' => 'Type',
|
|
'package_name' => 'Package',
|
|
'php_os' => 'Operating System',
|
|
'php_version' => 'PHP Version',
|
|
'assign' => 'Assigned To',
|
|
'block_user_comment' => 'Block user comment',
|
|
'private' => 'Private report',
|
|
'cve_id' => 'CVE-ID'
|
|
];
|
|
|
|
// make diff output aligned
|
|
$actlength = $maxlength = 0;
|
|
foreach (array_keys($diff) as $v) {
|
|
$actlength = strlen($fields[$v]) + 2;
|
|
$maxlength = ($maxlength < $actlength) ? $actlength : $maxlength;
|
|
}
|
|
|
|
$changes = '<div class="changeset">' . "\n";
|
|
$spaces = str_repeat(' ', $maxlength + 1);
|
|
foreach ($diff as $name => $content) {
|
|
// align header content with headers (if a header contains
|
|
// more than one line, wrap it intelligently)
|
|
$field = str_pad($fields[$name] . ':', $maxlength);
|
|
$from = wordwrap('-'.$field.$content['from'], 72 - $maxlength, "\n$spaces"); // wrap and indent
|
|
$from = rtrim($from); // wordwrap may add spacer to last line
|
|
$to = wordwrap('+'.$field.$content['to'], 72 - $maxlength, "\n$spaces"); // wrap and indent
|
|
$to = rtrim($to); // wordwrap may add spacer to last line
|
|
$changes .= '<span class="removed">' . clean($from) . '</span>' . "\n";
|
|
$changes .= '<span class="added">' . clean($to) . '</span>' . "\n";
|
|
}
|
|
$changes .= '</div>';
|
|
|
|
return $changes;
|
|
}
|
|
|
|
/**
|
|
* Send an email notice about bug aditions and edits
|
|
*
|
|
* @param
|
|
*
|
|
* @return void
|
|
*/
|
|
function mail_bug_updates($bug, $in, $from, $ncomment, $edit = 1, $id = false)
|
|
{
|
|
global $tla, $bug_types, $siteBig, $site_method, $site_url, $basedir;
|
|
|
|
$text = [];
|
|
$headers = [];
|
|
$changed = bug_diff($bug, $in);
|
|
$from = str_replace(["\n", "\r"], '', $from);
|
|
|
|
/* Default addresses */
|
|
list($mailto, $mailfrom, $bcc, $params) = get_package_mail(oneof(@$in['package_name'], $bug['package_name']), $id, oneof(@$in['bug_type'], $bug['bug_type']));
|
|
|
|
$headers[] = [' ID', $bug['id']];
|
|
|
|
switch ($edit) {
|
|
case 4:
|
|
$headers[] = [' Patch added by', $from];
|
|
$from = "\"{$from}\" <{$mailfrom}>";
|
|
break;
|
|
case 3:
|
|
$headers[] = [' Comment by', $from];
|
|
$from = "\"{$from}\" <{$mailfrom}>";
|
|
break;
|
|
case 2:
|
|
$from = spam_protect(txfield('email', $bug, $in), 'text');
|
|
$headers[] = [' User updated by', $from];
|
|
$from = "\"{$from}\" <{$mailfrom}>";
|
|
break;
|
|
default:
|
|
$headers[] = [' Updated by', $from];
|
|
}
|
|
|
|
$fields = [
|
|
'email' => 'Reported by',
|
|
'sdesc' => 'Summary',
|
|
'status' => 'Status',
|
|
'bug_type' => 'Type',
|
|
'package_name' => 'Package',
|
|
'php_os' => 'Operating System',
|
|
'php_version' => 'PHP Version',
|
|
'assign' => 'Assigned To',
|
|
'block_user_comment' => 'Block user comment',
|
|
'private' => 'Private report',
|
|
'cve_id' => 'CVE-ID',
|
|
];
|
|
|
|
foreach ($fields as $name => $desc) {
|
|
$prefix = ' ';
|
|
if (isset($changed[$name])) {
|
|
$headers[] = ["-{$desc}", $changed[$name]['from']];
|
|
$prefix = '+';
|
|
}
|
|
|
|
/* only fields that are set get added. */
|
|
if ($f = txfield($name, $bug, $in)) {
|
|
if ($name == 'email') {
|
|
$f = spam_protect($f, 'text');
|
|
}
|
|
$foo = isset($changed[$name]['to']) ? $changed[$name]['to'] : $f;
|
|
$headers[] = [$prefix.$desc, $foo];
|
|
}
|
|
}
|
|
|
|
/* Make header output aligned */
|
|
$maxlength = 0;
|
|
$actlength = 0;
|
|
foreach ($headers as $v) {
|
|
$actlength = strlen($v[0]) + 1;
|
|
$maxlength = (($maxlength < $actlength) ? $actlength : $maxlength);
|
|
}
|
|
|
|
/* Align header content with headers (if a header contains more than one line, wrap it intelligently) */
|
|
$header_text = '';
|
|
|
|
$spaces = str_repeat(' ', $maxlength + 1);
|
|
foreach ($headers as $v) {
|
|
$hcontent = wordwrap($v[1], 72 - $maxlength, "\n{$spaces}"); // wrap and indent
|
|
$hcontent = rtrim($hcontent); // wordwrap may add spacer to last line
|
|
$header_text .= str_pad($v[0] . ':', $maxlength) . " {$hcontent}\n";
|
|
}
|
|
|
|
if ($ncomment) {
|
|
# $ncomment = preg_replace('#<div class="changeset">(.*)</div>#sUe', "ltrim(strip_tags('\\1'))", $ncomment);
|
|
$ncomment = preg_replace_callback('#<div class="changeset">(.*)</div>#sU', function ($m) { return ltrim(strip_tags($m[0])); }, $ncomment);
|
|
|
|
$text[] = " New Comment:\n\n{$ncomment}";
|
|
}
|
|
|
|
$old_comments = get_old_comments($bug['id'], empty($ncomment));
|
|
# $old_comments = preg_replace('#<div class="changeset">(.*)</div>#sUe', "ltrim(strip_tags('\\1'))", $old_comments);
|
|
$old_comments = preg_replace_callback('#<div class="changeset">(.*)</div>#sU', function ($m) { return ltrim(strip_tags($m[0])); }, $old_comments);
|
|
|
|
$text[] = $old_comments;
|
|
|
|
$wrapped_text = join("\n", $text);
|
|
|
|
/* user text with attention, headers and previous messages */
|
|
$user_text = <<< USER_TEXT
|
|
ATTENTION! Do NOT reply to this email!
|
|
To reply, use the web interface found at
|
|
{$site_method}://{$site_url}{$basedir}/bug.php?id={$bug['id']}&edit=2
|
|
|
|
{$header_text}
|
|
{$wrapped_text}
|
|
USER_TEXT;
|
|
|
|
/* developer text with headers, previous messages, and edit link */
|
|
$dev_text = <<< DEV_TEXT
|
|
Edit report at {$site_method}://{$site_url}{$basedir}/bug.php?id={$bug['id']}&edit=1
|
|
|
|
{$header_text}
|
|
{$wrapped_text}
|
|
|
|
--
|
|
Edit this bug report at {$site_method}://{$site_url}{$basedir}/bug.php?id={$bug['id']}&edit=1
|
|
DEV_TEXT;
|
|
|
|
if (preg_match('/.*@php\.net\z/', $bug['email'])) {
|
|
$user_text = $dev_text;
|
|
}
|
|
|
|
// Defaults
|
|
$subj = $bug_types[$bug['bug_type']];
|
|
$sdesc = txfield('sdesc', $bug, $in);
|
|
|
|
/* send mail if status was changed, there is a comment, private turned on/off or the bug type was changed to/from Security */
|
|
if (empty($in['status']) || $in['status'] != $bug['status'] || $ncomment != '' ||
|
|
(isset($in['private']) && $in['private'] != $bug['private']) ||
|
|
(isset($in['bug_type']) && $in['bug_type'] != $bug['bug_type'] &&
|
|
($in['bug_type'] == 'Security' || $bug['bug_type'] == 'Security'))) {
|
|
if (isset($in['bug_type']) && $in['bug_type'] != $bug['bug_type']) {
|
|
$subj = $bug_types[$bug['bug_type']] . '->' . $bug_types[$in['bug_type']];
|
|
}
|
|
|
|
$old_status = $bug['status'];
|
|
$new_status = $bug['status'];
|
|
|
|
if (isset($in['status']) && $in['status'] != $bug['status'] && $edit != 3) { /* status changed */
|
|
$new_status = $in['status'];
|
|
$subj .= " #{$bug['id']} [{$tla[$old_status]}->{$tla[$new_status]}]";
|
|
} elseif ($edit == 4) { /* patch */
|
|
$subj .= " #{$bug['id']} [PATCH]";
|
|
} elseif ($edit == 3) { /* comment */
|
|
$subj .= " #{$bug['id']} [Com]";
|
|
} else { /* status did not change and not comment */
|
|
$subj .= " #{$bug['id']} [{$tla[$bug['status']]}]";
|
|
}
|
|
|
|
// the user gets sent mail with an envelope sender that ignores bounces
|
|
bugs_mail(
|
|
$bug['email'],
|
|
"{$subj}: {$sdesc}",
|
|
$user_text,
|
|
"From: {$siteBig} Bug Database <{$mailfrom}>\r\n" .
|
|
"Bcc: {$bcc}\r\n" .
|
|
"X-PHP-Bug: {$bug['id']}\r\n" .
|
|
"X-PHP-Site: {$siteBig}\r\n" .
|
|
"In-Reply-To: <bug-{$bug['id']}@{$site_url}>"
|
|
);
|
|
|
|
// Spam protection
|
|
$tmp = $edit != 3 ? $in : $bug;
|
|
$tmp['new_status'] = $new_status;
|
|
$tmp['old_status'] = $old_status;
|
|
foreach (['bug_type', 'php_version', 'package_name', 'php_os'] as $field) {
|
|
$tmp[$field] = strtok($tmp[$field], "\r\n");
|
|
}
|
|
|
|
// but we go ahead and let the default sender get used for the list
|
|
bugs_mail(
|
|
$mailto,
|
|
"{$subj}: {$sdesc}",
|
|
$dev_text,
|
|
"From: {$from}\r\n".
|
|
"X-PHP-Bug: {$bug['id']}\r\n" .
|
|
"X-PHP-Site: {$siteBig}\r\n" .
|
|
"X-PHP-Type: {$tmp['bug_type']}\r\n" .
|
|
"X-PHP-Version: {$tmp['php_version']}\r\n" .
|
|
"X-PHP-Category: {$tmp['package_name']}\r\n" .
|
|
"X-PHP-OS: {$tmp['php_os']}\r\n" .
|
|
"X-PHP-Status: {$tmp['new_status']}\r\n" .
|
|
"X-PHP-Old-Status: {$tmp['old_status']}\r\n" .
|
|
"In-Reply-To: <bug-{$bug['id']}@{$site_url}>",
|
|
$params
|
|
);
|
|
}
|
|
|
|
/* if a developer assigns someone else, let that other person know about it */
|
|
if ($edit == 1 && $in['assign'] && $in['assign'] != $bug['assign']) {
|
|
|
|
$email = $in['assign'] . '@php.net';
|
|
|
|
// If the developer assigns him self then skip
|
|
if ($email == $from) {
|
|
return;
|
|
}
|
|
|
|
bugs_mail(
|
|
$email,
|
|
$bug_types[$bug['bug_type']] . ' #' . $bug['id'] . ' ' . txfield('sdesc', $bug, $in),
|
|
"{$in['assign']} you have just been assigned to this bug by {$from}\n\n{$dev_text}",
|
|
"From: {$from}\r\n" .
|
|
"X-PHP-Bug: {$bug['id']}\r\n" .
|
|
"In-Reply-To: <bug-{$bug['id']}@{$site_url}>"
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Turns a unix timestamp into a uniformly formatted date
|
|
*
|
|
* If the date is during the current year, the year is omitted.
|
|
*
|
|
* @param int $ts the unix timestamp to be formatted
|
|
* @param string $format format to use
|
|
*
|
|
* @return string the formatted date
|
|
*/
|
|
function format_date($ts = null, $format = 'Y-m-d H:i e')
|
|
{
|
|
if (!$ts) {
|
|
$ts = time();
|
|
}
|
|
return gmdate($format, (int)$ts - date('Z', (int)$ts));
|
|
}
|
|
|
|
/**
|
|
* Produces a string containing the bug's prior comments
|
|
*
|
|
* @param int $bug_id the bug's id number
|
|
* @param int $all should all existing comments be returned?
|
|
*
|
|
* @return string the comments
|
|
*/
|
|
function get_old_comments($bug_id, $all = 0)
|
|
{
|
|
global $dbh, $site_method, $site_url, $basedir;
|
|
|
|
$divider = str_repeat('-', 72);
|
|
$max_message_length = 10 * 1024;
|
|
$max_comments = 5;
|
|
$output = '';
|
|
$count = 0;
|
|
|
|
$res = $dbh->prepare("
|
|
SELECT ts, email, comment
|
|
FROM bugdb_comments
|
|
WHERE bug = ? AND comment_type != 'log'
|
|
ORDER BY ts DESC
|
|
")->execute([$bug_id]);
|
|
|
|
// skip the most recent unless the caller wanted all comments
|
|
if (!$all) {
|
|
$row = $res->fetch(\PDO::FETCH_NUM);
|
|
if (!$row) {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
while (($row = $res->fetch(\PDO::FETCH_NUM)) && strlen($output) < $max_message_length && $count++ < $max_comments) {
|
|
$email = spam_protect($row[1], 'text');
|
|
$output .= "[{$row[0]}] {$email}\n\n{$row[2]}\n\n{$divider}\n";
|
|
}
|
|
|
|
if (strlen($output) < $max_message_length && $count < $max_comments) {
|
|
$res = $dbh->prepare("SELECT ts1, email, ldesc FROM bugdb WHERE id = ?")->execute([$bug_id]);
|
|
if (!$res) {
|
|
return $output;
|
|
}
|
|
$row = $res->fetch(\PDO::FETCH_NUM);
|
|
if (!$row) {
|
|
return $output;
|
|
}
|
|
$email = spam_protect($row[1], 'text');
|
|
return ("
|
|
|
|
Previous Comments:
|
|
{$divider}
|
|
{$output}[{$row[0]}] {$email}
|
|
|
|
{$row[2]}
|
|
|
|
{$divider}
|
|
|
|
");
|
|
} else {
|
|
return "
|
|
|
|
Previous Comments:
|
|
{$divider}
|
|
{$output}
|
|
|
|
The remainder of the comments for this report are too long. To view
|
|
the rest of the comments, please view the bug report online at
|
|
|
|
{$site_method}://{$site_url}{$basedir}/bug.php?id={$bug_id}
|
|
";
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Converts any URI's found in the string to hyperlinks
|
|
*
|
|
* @param string $text the text to be examined
|
|
*
|
|
* @return string the converted string
|
|
*/
|
|
function addlinks($text)
|
|
{
|
|
$text = htmlspecialchars($text);
|
|
$text = preg_replace("/((mailto|http|https|ftp|nntp|news):.+?)(>|\\s|\\)|\\.\\s|,\\s|$)/i","<a href=\"\\1\" rel=\"nofollow\">\\1</a>\\3",$text);
|
|
|
|
# what the heck is this for?
|
|
$text = preg_replace("/[.,]?-=-\"/", '"', $text);
|
|
return $text;
|
|
}
|
|
|
|
/**
|
|
* Determine if the given package name is legitimate
|
|
*
|
|
* @param string $package_name the name of the package
|
|
*
|
|
* @return bool
|
|
*/
|
|
function package_exists($package_name)
|
|
{
|
|
global $pseudo_pkgs;
|
|
|
|
return isset($pseudo_pkgs[$package_name]);
|
|
}
|
|
|
|
/**
|
|
* Validate an email address
|
|
*/
|
|
function is_valid_email($email, $phpnet_allowed = true)
|
|
{
|
|
if (!$phpnet_allowed) {
|
|
if (false !== stripos($email, '@php.net')) {
|
|
return false;
|
|
}
|
|
}
|
|
return (bool)filter_var($email, FILTER_VALIDATE_EMAIL);
|
|
}
|
|
|
|
/**
|
|
* Validate an incoming bug report
|
|
*
|
|
* @param mixed $in usually $_POST['in']
|
|
* @param bool $initial
|
|
* @param bool $logged_in
|
|
*
|
|
* @return array
|
|
*/
|
|
function incoming_details_are_valid($in, $initial = 0, $logged_in = false)
|
|
{
|
|
global $bug, $dbh, $bug_types, $versions;
|
|
|
|
$errors = [];
|
|
if (!is_array($in)) {
|
|
$errors[] = 'Invalid data submitted!';
|
|
return $errors;
|
|
}
|
|
if ($initial || (!empty($in['email']) && $bug['email'] != $in['email'])) {
|
|
if (!is_valid_email($in['email'])) {
|
|
$errors[] = 'Please provide a valid email address.';
|
|
}
|
|
}
|
|
|
|
if (!$logged_in && $initial && (empty($in['passwd']) || !is_string($in['passwd']))) {
|
|
$errors[] = 'Please provide a password for this bug report.';
|
|
}
|
|
|
|
if (isset($in['php_version']) && $in['php_version'] == 'earlier') {
|
|
$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.';
|
|
}
|
|
|
|
if (empty($in['php_version']) || !is_string($in['php_version']) || ($initial && !in_array($in['php_version'], $versions))) {
|
|
$errors[] = 'Please select a valid PHP version.';
|
|
}
|
|
|
|
if (empty($in['package_name']) || !is_string($in['package_name']) || $in['package_name'] == 'none') {
|
|
$errors[] = 'Please select an appropriate package.';
|
|
} else if (!package_exists($in['package_name'])) {
|
|
$errors[] = 'Please select an appropriate package.';
|
|
}
|
|
|
|
if (empty($in['bug_type']) || !is_string($in['bug_type']) || !array_key_exists($in['bug_type'], $bug_types)) {
|
|
$errors[] = 'Please select a valid bug type.';
|
|
}
|
|
|
|
if (empty($in['sdesc']) || !is_string($in['sdesc'])) {
|
|
$errors[] = 'You must supply a short description of the bug you are reporting.';
|
|
}
|
|
|
|
if ($initial && (empty($in['ldesc']) || !is_string($in['ldesc']))) {
|
|
$errors[] = 'You must supply a long description of the bug you are reporting.';
|
|
}
|
|
|
|
return $errors;
|
|
}
|
|
|
|
/**
|
|
* Produces an array of email addresses the report should go to
|
|
*
|
|
* @param string $package_name the package's name
|
|
*
|
|
* @return array an array of email addresses
|
|
*/
|
|
function get_package_mail($package_name, $bug_id = false, $bug_type = 'Bug')
|
|
{
|
|
global $dbh, $bugEmail, $docBugEmail, $secBugEmail, $security_distro_people;
|
|
|
|
$to = [];
|
|
$params = '-f noreply@php.net';
|
|
$mailfrom = $bugEmail;
|
|
|
|
if ($bug_type === 'Documentation Problem') {
|
|
// Documentation problems *always* go to the doc team
|
|
$to[] = $docBugEmail;
|
|
} else if ($bug_type == 'Security') {
|
|
// Security problems *always* go to the sec team
|
|
$to[] = $secBugEmail;
|
|
foreach ($security_distro_people as $user) {
|
|
$to[] = "{$user}@php.net";
|
|
}
|
|
$params = '-f bounce-no-user@php.net';
|
|
}
|
|
else {
|
|
/* Get package mailing list address */
|
|
$res = $dbh->prepare('
|
|
SELECT list_email, project
|
|
FROM bugdb_pseudo_packages
|
|
WHERE name = ?
|
|
')->execute([$package_name]);
|
|
|
|
list($list_email, $project) = $res->fetch(\PDO::FETCH_NUM);
|
|
|
|
if ($project == 'pecl') {
|
|
$mailfrom = 'pecl-dev@lists.php.net';
|
|
}
|
|
|
|
if ($list_email) {
|
|
if ($list_email == 'systems@php.net') {
|
|
$params = '-f bounce-no-user@php.net';
|
|
}
|
|
$to[] = $list_email;
|
|
} else {
|
|
// Get the maintainers handle
|
|
if ($project == 'pecl') {
|
|
$handles = $dbh->prepare("SELECT GROUP_CONCAT(handle) FROM bugdb_packages_maintainers WHERE package_name = ?")->execute([$package_name])->fetch(\PDO::FETCH_NUM)[0];
|
|
|
|
if ($handles) {
|
|
foreach (explode(',', $handles) as $handle) {
|
|
$to[] = $handle .'@php.net';
|
|
}
|
|
} else {
|
|
$to[] = $mailfrom;
|
|
}
|
|
} else {
|
|
// Fall back to default mailing list
|
|
$to[] = $bugEmail;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Include assigned to To list and subscribers in Bcc list */
|
|
if ($bug_id) {
|
|
$bug_id = (int) $bug_id;
|
|
|
|
$assigned = $dbh->prepare("SELECT assign FROM bugdb WHERE id= ? ")->execute([$bug_id])->fetch(\PDO::FETCH_NUM)[0];
|
|
if ($assigned) {
|
|
$assigned .= '@php.net';
|
|
if ($assigned && !in_array($assigned, $to)) {
|
|
$to[] = $assigned;
|
|
}
|
|
}
|
|
$bcc = $dbh->prepare("SELECT email FROM bugdb_subscribe WHERE bug_id=?")->execute([$bug_id])->fetchAll();
|
|
|
|
$bcc = array_unique($bcc);
|
|
return [implode(', ', $to), $mailfrom, implode(', ', $bcc), $params];
|
|
} else {
|
|
return [implode(', ', $to), $mailfrom, '', $params];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Prepare a query string with the search terms
|
|
*
|
|
* @param string $search the term to be searched for
|
|
*
|
|
* @return array
|
|
*/
|
|
function format_search_string($search, $boolean_search = false)
|
|
{
|
|
global $dbh;
|
|
|
|
// Function will be updated to make results more relevant.
|
|
// Quick hack for indicating ignored words.
|
|
$min_word_len=3;
|
|
|
|
$words = preg_split("/\s+/", $search);
|
|
$ignored = $used = [];
|
|
foreach($words as $match)
|
|
{
|
|
if (strlen($match) < $min_word_len) {
|
|
array_push($ignored, $match);
|
|
} else {
|
|
array_push($used, $match);
|
|
}
|
|
}
|
|
|
|
if ($boolean_search) {
|
|
// require all used words (all)
|
|
if ($boolean_search === 1) {
|
|
$newsearch = '';
|
|
foreach ($used as $word) {
|
|
$newsearch .= "+$word ";
|
|
}
|
|
return [" AND MATCH (bugdb.email,sdesc,ldesc) AGAINST (" . $dbh->quote($newsearch) . " IN BOOLEAN MODE)", $ignored];
|
|
|
|
// allow custom boolean search (raw)
|
|
} elseif ($boolean_search === 2) {
|
|
return [" AND MATCH (bugdb.email,sdesc,ldesc) AGAINST (" . $dbh->quote($search) . " IN BOOLEAN MODE)", $ignored];
|
|
}
|
|
}
|
|
// require any of the words (any)
|
|
return [" AND MATCH (bugdb.email,sdesc,ldesc) AGAINST (" . $dbh->quote($search) . ")", $ignored];
|
|
}
|
|
|
|
/**
|
|
* Send the confirmation mail to confirm a subscription removal
|
|
*
|
|
* @param integer bug ID
|
|
* @param string email to remove
|
|
* @param array bug data
|
|
*
|
|
* @return void
|
|
*/
|
|
function unsubscribe_hash($bug_id, $email)
|
|
{
|
|
global $dbh, $siteBig, $site_method, $site_url, $bugEmail;
|
|
|
|
$now = time();
|
|
$hash = crypt($email . $bug_id, $now);
|
|
|
|
$query = "
|
|
UPDATE bugdb_subscribe
|
|
SET unsubscribe_date = '{$now}',
|
|
unsubscribe_hash = ?
|
|
WHERE bug_id = ? AND email = ?
|
|
";
|
|
|
|
$affected = $dbh->prepare($query, null, null)->execute([$hash, $bug_id, $email]);
|
|
|
|
if ($affected > 0) {
|
|
$hash = urlencode($hash);
|
|
/* user text with attention, headers and previous messages */
|
|
$user_text = <<< USER_TEXT
|
|
ATTENTION! Do NOT reply to this email!
|
|
|
|
A request has been made to remove your subscription to
|
|
{$siteBig} bug #{$bug_id}
|
|
|
|
To view the bug in question please use this link:
|
|
{$site_method}://{$site_url}{$basedir}/bug.php?id={$bug_id}
|
|
|
|
To confirm the removal please use this link:
|
|
{$site_method}://{$site_url}{$basedir}/bug.php?id={$bug_id}&unsubscribe=1&t={$hash}
|
|
|
|
|
|
USER_TEXT;
|
|
|
|
bugs_mail(
|
|
$email,
|
|
"[$siteBig-BUG-unsubscribe] #{$bug_id}",
|
|
$user_text,
|
|
"From: {$siteBig} Bug Database <{$bugEmail}>\r\n".
|
|
"X-PHP-Bug: {$bug_id}\r\n".
|
|
"In-Reply-To: <bug-{$bug_id}@{$site_url}>"
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Remove a subscribtion
|
|
*
|
|
* @param integer bug ID
|
|
* @param string hash
|
|
*
|
|
* @return void
|
|
*/
|
|
function unsubscribe($bug_id, $hash)
|
|
{
|
|
global $dbh;
|
|
|
|
$bug_id = (int) $bug_id;
|
|
|
|
$query = "
|
|
SELECT bug_id, email, unsubscribe_date, unsubscribe_hash
|
|
FROM bugdb_subscribe
|
|
WHERE bug_id = ? AND unsubscribe_hash = ? LIMIT 1
|
|
";
|
|
|
|
$sub = $dbh->prepare($query)->execute([$bug_id, $hash])->fetch();
|
|
|
|
if (!$sub) {
|
|
return false;
|
|
}
|
|
|
|
$now = time();
|
|
$requested_on = $sub['unsubscribe_date'];
|
|
/* 24hours delay to answer the mail */
|
|
if (($now - $requested_on) > 86400) {
|
|
return false;
|
|
}
|
|
|
|
$query = "
|
|
DELETE FROM bugdb_subscribe
|
|
WHERE bug_id = ? AND unsubscribe_hash = ? AND email = ?
|
|
";
|
|
$dbh->prepare($query)->execute([$bug_id, $hash, $sub['email']]);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Add bug comment
|
|
*/
|
|
function bugs_add_comment($bug_id, $from, $from_name, $comment, $type = 'comment')
|
|
{
|
|
global $dbh;
|
|
|
|
return $dbh->prepare("
|
|
INSERT INTO bugdb_comments (bug, email, reporter_name, comment, comment_type, ts)
|
|
VALUES (?, ?, ?, ?, ?, NOW())
|
|
")->execute([
|
|
$bug_id, $from, $from_name, $comment, $type
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Change bug status
|
|
*/
|
|
function bugs_status_change($bug_id, $new_status)
|
|
{
|
|
global $dbh;
|
|
|
|
return $dbh->prepare("
|
|
UPDATE bugdb SET status = ? WHERE id = ? LIMIT 1
|
|
")->execute([$new_status, $bug_id]);
|
|
}
|
|
|
|
/**
|
|
* Verify bug password
|
|
*
|
|
* @return bool
|
|
*/
|
|
|
|
function verify_bug_passwd($bug_id, $passwd)
|
|
{
|
|
global $dbh;
|
|
|
|
return (bool) $dbh->prepare('SELECT 1 FROM bugdb WHERE id = ? AND passwd = ?')->execute([$bug_id, $passwd])->fetch(\PDO::FETCH_NUM)[0];
|
|
}
|
|
|
|
/**
|
|
* Mailer function. When DEVBOX is defined, this only outputs the parameters as-is.
|
|
*
|
|
* @return bool
|
|
*
|
|
*/
|
|
function bugs_mail($to, $subject, $message, $headers = '', $params = '')
|
|
{
|
|
if (empty($params)) {
|
|
$params = '-f noreply@php.net';
|
|
}
|
|
if (DEVBOX === true) {
|
|
if (defined('DEBUG_MAILS')) {
|
|
echo '<pre>';
|
|
var_dump(htmlspecialchars($to), htmlspecialchars($subject), htmlspecialchars($message), htmlspecialchars($headers));
|
|
echo '</pre>';
|
|
}
|
|
return true;
|
|
}
|
|
return @mail($to, $subject, $message, $headers, $params);
|
|
}
|
|
|
|
/**
|
|
* Prints out the XHTML headers and top of the page.
|
|
*
|
|
* @param string $title a string to go into the header's <title>
|
|
* @return void
|
|
*/
|
|
function response_header($title, $extraHeaders = '')
|
|
{
|
|
global $_header_done, $self, $auth_user, $logged_in, $siteBig, $site_method, $site_url, $basedir;
|
|
|
|
$is_logged = false;
|
|
|
|
if ($_header_done) {
|
|
return;
|
|
}
|
|
|
|
if ($logged_in === 'developer') {
|
|
$is_logged = true;
|
|
$username = $auth_user->handle;
|
|
} else if (!empty($_SESSION['user'])) {
|
|
$is_logged = true;
|
|
$username = $_SESSION['user'];
|
|
}
|
|
|
|
$_header_done = true;
|
|
|
|
header('Content-Type: text/html; charset=UTF-8');
|
|
header('X-Frame-Options: SAMEORIGIN');
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<?php echo $extraHeaders; ?>
|
|
<base href="<?php echo $site_method?>://<?php echo $site_url, $basedir; ?>/">
|
|
<title><?php echo $siteBig; ?> :: <?php echo $title; ?></title>
|
|
<link rel="shortcut icon" href="<?php echo $site_method?>://<?php echo $site_url, $basedir; ?>/images/favicon.ico">
|
|
<link rel="stylesheet" href="<?php echo $site_method?>://<?php echo $site_url, $basedir; ?>/css/style.css">
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<table id="top" class="head" cellspacing="0" cellpadding="0">
|
|
<tr>
|
|
<td class="head-logo">
|
|
<a href="/"><img src="images/logo.png" alt="Bugs" vspace="2" hspace="2"></a>
|
|
</td>
|
|
|
|
<td class="head-menu">
|
|
<a href="https://php.net/">php.net</a> |
|
|
<a href="https://php.net/support.php">support</a> |
|
|
<a href="https://php.net/docs.php">documentation</a> |
|
|
<a href="report.php">report a bug</a> |
|
|
<a href="search.php">advanced search</a> |
|
|
<a href="search-howto.php">search howto</a> |
|
|
<a href="stats.php">statistics</a> |
|
|
<a href="random">random bug</a> |
|
|
<?php if ($is_logged) { ?>
|
|
<a href="search.php?cmd=display&assign=<?php echo $username;?>">my bugs</a> |
|
|
<?php if ($logged_in === 'developer') { ?>
|
|
<a href="/admin/">admin</a> |
|
|
<?php } ?>
|
|
<a href="logout.php">logout</a>
|
|
<?php } else { ?>
|
|
<a href="login.php">login</a>
|
|
<?php } ?>
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td class="head-search" colspan="2">
|
|
<form method="get" action="search.php">
|
|
<p class="head-search">
|
|
<input type="hidden" name="cmd" value="display">
|
|
<small>go to bug id or search bugs for</small>
|
|
<input class="small" type="text" name="search_for" value="<?php print isset($_GET['search_for']) ? htmlspecialchars($_GET['search_for']) : ''; ?>" size="30">
|
|
<input type="image" src="images/small_submit_white.gif" alt="search" style="vertical-align: middle;">
|
|
</p>
|
|
</form>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<table class="middle" cellspacing="0" cellpadding="0">
|
|
<tr>
|
|
<td class="content">
|
|
<?php
|
|
}
|
|
|
|
|
|
function response_footer($extra_html = '')
|
|
{
|
|
global $_footer_done, $LAST_UPDATED, $basedir;
|
|
|
|
if ($_footer_done) {
|
|
return;
|
|
}
|
|
$_footer_done = true;
|
|
?>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<?php echo $extra_html; ?>
|
|
|
|
<table class="foot" cellspacing="0" cellpadding="0">
|
|
<tr>
|
|
<td class="foot-bar" colspan="2"> </td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td class="foot-copy">
|
|
<small>
|
|
<a href="https://php.net/"><img src="images/logo-small.gif" align="left" valign="middle" hspace="3" alt="PHP"></a>
|
|
<a href="https://php.net/copyright.php">Copyright © 2001-<?php echo date('Y'); ?> The PHP Group</a><br>
|
|
All rights reserved.
|
|
</small>
|
|
</td>
|
|
<td class="foot-source">
|
|
<small>Last updated: <?php echo $LAST_UPDATED; ?></small>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</body>
|
|
</html>
|
|
<?php
|
|
}
|
|
|
|
|
|
/**
|
|
* Redirects to the given full or partial URL.
|
|
*
|
|
* @param string $url Full/partial url to redirect to
|
|
*/
|
|
function redirect($url)
|
|
{
|
|
header("Location: {$url}");
|
|
exit;
|
|
}
|
|
|
|
|
|
/**
|
|
* Turns the provided email address into a "mailto:" hyperlink.
|
|
*
|
|
* The link and link text are obfuscated by alternating Ord and Hex
|
|
* entities.
|
|
*
|
|
* @param string $email the email address to make the link for
|
|
* @param string $linktext a string for the visible part of the link.
|
|
* If not provided, the email address is used.
|
|
* @param string $extras a string of extra attributes for the <a> element
|
|
*
|
|
* @return string the HTML hyperlink of an email address
|
|
*/
|
|
function make_mailto_link($email, $linktext = '', $extras = '')
|
|
{
|
|
$tmp = '';
|
|
for ($i = 0, $l = strlen($email); $i<$l; $i++) {
|
|
if ($i % 2) {
|
|
$tmp .= '&#' . ord($email[$i]) . ';';
|
|
} else {
|
|
$tmp .= '&#x' . dechex(ord($email[$i])) . ';';
|
|
}
|
|
}
|
|
|
|
return "<a {$extras} href='mailto:{$tmp}'>" . ($linktext != '' ? $linktext : $tmp) . '</a>';
|
|
}
|
|
|
|
/**
|
|
* Turns bug/feature request numbers into hyperlinks
|
|
*
|
|
* @param string $text the text to check for bug numbers
|
|
*
|
|
* @return string the string with bug numbers hyperlinked
|
|
*/
|
|
function make_ticket_links($text)
|
|
{
|
|
return preg_replace(
|
|
'/(?<![>a-z])(bug(?:fix)?|feat(?:ure)?|doc(?:umentation)?|req(?:uest)?|duplicated of)\s+#?([0-9]+)/i',
|
|
"<a href='bug.php?id=\\2'>\\0</a>",
|
|
$text
|
|
);
|
|
}
|
|
|
|
function get_ticket_links($text)
|
|
{
|
|
$matches = [];
|
|
|
|
preg_match_all('/(?<![>a-z])(?:bug(?:fix)?|feat(?:ure)?|doc(?:umentation)?|req(?:uest)?|duplicated of)\s+#?([0-9]+)/i', $text, $matches);
|
|
|
|
return $matches[1];
|
|
}
|
|
|
|
/**
|
|
* Generates a random password
|
|
*/
|
|
function bugs_gen_passwd($length = 8)
|
|
{
|
|
return substr(md5(uniqid(time(), true)), 0, $length);
|
|
}
|
|
|
|
function bugs_get_hash($passwd)
|
|
{
|
|
return hash_hmac('sha256', $passwd, getenv('USER_PWD_SALT'));
|
|
}
|