Commit 8b0e4c41 authored by Per Cederqvist's avatar Per Cederqvist

Imported Bugzilla 4.4.1.

parent 640e2f5e
This is a Bazaar control directory.
Do not change any files in this directory.
See http://bazaar-vcs.org/ for more information about Bazaar.
See http://bazaar.canonical.com/ for more information about Bazaar.
No preview for this file type
......@@ -799,10 +799,10 @@ not an arrayref.
=item C<user>
C<undef> if there is no currently logged in user or if the login code has not
yet been run. If an sudo session is in progress, the C<Bugzilla::User>
corresponding to the person who is being impersonated. If no session is in
progress, the current C<Bugzilla::User>.
Default C<Bugzilla::User> object if there is no currently logged in user or
if the login code has not yet been run. If an sudo session is in progress,
the C<Bugzilla::User> corresponding to the person who is being impersonated.
If no session is in progress, the current C<Bugzilla::User>.
=item C<set_user>
......
......@@ -268,8 +268,7 @@ sub setup_template_patch_reader {
&& Bugzilla->params->{'cvsroot_get'} && !$vars->{'newid'};
# Print everything out.
print $cgi->header(-type => 'text/html',
-expires => '+3M');
print $cgi->header(-type => 'text/html');
$last_reader->sends_data_to(new PatchReader::DiffPrinter::template($template,
"attachment/diff-header.$format.tmpl",
......
......@@ -50,8 +50,8 @@ sub get_login_info {
trick_taint($login_cookie);
detaint_natural($user_id);
my $is_valid =
$dbh->selectrow_array('SELECT 1
my $db_cookie =
$dbh->selectrow_array('SELECT cookie
FROM logincookies
WHERE cookie = ?
AND userid = ?
......@@ -59,7 +59,7 @@ sub get_login_info {
undef, ($login_cookie, $user_id, $ip_addr));
# If the cookie is valid, return a valid username.
if ($is_valid) {
if (defined $db_cookie && $login_cookie eq $db_cookie) {
# If we logged in successfully, then update the lastused
# time on the login cookie
$dbh->do("UPDATE logincookies SET lastused = NOW()
......
......@@ -2508,6 +2508,7 @@ sub _set_product {
my @idlist = ($self->id);
push(@idlist, map {$_->id} @{ $params->{other_bugs} })
if $params->{other_bugs};
@idlist = uniq @idlist;
# Get the ID of groups which are no longer valid in the new product.
my $gids = $dbh->selectcol_arrayref(
'SELECT bgm.group_id
......@@ -2522,9 +2523,13 @@ sub _set_product {
. Bugzilla->user->groups_as_string . '))
OR gcm.othercontrol != ?) )',
undef, (@idlist, $product->id, CONTROLMAPNA, CONTROLMAPNA));
$vars{'old_groups'} = Bugzilla::Group->new_from_list($gids);
$vars{'old_groups'} = Bugzilla::Group->new_from_list($gids);
# Did we come here from editing multiple bugs? (affects how we
# show optional group changes)
$vars{multiple_bugs} = (@idlist > 1) ? 1 : 0;
}
if (%vars) {
$vars{product} = $product;
$vars{bug} = $self;
......@@ -3895,11 +3900,12 @@ sub get_activity {
if ($operation->{'who'} && $who eq $operation->{'who'}
&& $when eq $operation->{'when'}
&& $fieldname eq $operation->{'fieldname'}
&& ($comment_id || 0) == ($operation->{'comment_id'} || 0)
&& ($attachid || 0) == ($operation->{'attachid'} || 0))
{
my $old_change = pop @$changes;
$removed = _join_activity_entries($fieldname, $old_change->{'removed'}, $removed);
$added = _join_activity_entries($fieldname, $old_change->{'added'}, $added);
$removed = join_activity_entries($fieldname, $old_change->{'removed'}, $removed);
$added = join_activity_entries($fieldname, $old_change->{'added'}, $added);
}
$operation->{'who'} = $who;
$operation->{'when'} = $when;
......@@ -3909,7 +3915,7 @@ sub get_activity {
$change{'added'} = $added;
if ($comment_id) {
$change{'comment'} = Bugzilla::Comment->new($comment_id);
$operation->{comment_id} = $change{'comment'} = Bugzilla::Comment->new($comment_id);
}
push (@$changes, \%change);
......@@ -3924,35 +3930,6 @@ sub get_activity {
return(\@operations, $incomplete_data);
}
sub _join_activity_entries {
my ($field, $current_change, $new_change) = @_;
# We need to insert characters as these were removed by old
# LogActivityEntry code.
return $new_change if $current_change eq '';
# Buglists and see_also need the comma restored
if ($field eq 'dependson' || $field eq 'blocked' || $field eq 'see_also') {
if (substr($new_change, 0, 1) eq ',' || substr($new_change, 0, 1) eq ' ') {
return $current_change . $new_change;
} else {
return $current_change . ', ' . $new_change;
}
}
# Assume bug_file_loc contain a single url, don't insert a delimiter
if ($field eq 'bug_file_loc') {
return $current_change . $new_change;
}
# All other fields get a space
if (substr($new_change, 0, 1) eq ' ') {
return $current_change . $new_change;
} else {
return $current_change . ' ' . $new_change;
}
}
# Update the bugs_activity table to reflect changes made in bugs.
sub LogActivityEntry {
my ($i, $col, $removed, $added, $whoid, $timestamp, $comment_id) = @_;
......
......@@ -418,7 +418,8 @@ sub _get_diffs {
ON fielddefs.id = bugs_activity.fieldid
WHERE bugs_activity.bug_id = ?
$when_restriction
ORDER BY bugs_activity.bug_when", {Slice=>{}}, @args);
ORDER BY bugs_activity.bug_when, bugs_activity.id",
{Slice=>{}}, @args);
foreach my $diff (@$diffs) {
$user_cache->{$diff->{who}} ||= new Bugzilla::User($diff->{who});
......@@ -435,7 +436,25 @@ sub _get_diffs {
}
}
return @$diffs;
my @changes = ();
foreach my $diff (@$diffs) {
# If this is the same field as the previous item, then concatenate
# the data into the same change.
if (scalar(@changes)
&& $diff->{field_name} eq $changes[-1]->{field_name}
&& $diff->{bug_when} eq $changes[-1]->{bug_when}
&& $diff->{who} eq $changes[-1]->{who}
&& ($diff->{attach_id} || 0) == ($changes[-1]->{attach_id} || 0)
&& ($diff->{comment_id} || 0) == ($changes[-1]->{comment_id} || 0)
) {
my $old_change = pop @changes;
$diff->{old} = join_activity_entries($diff->{field_name}, $old_change->{old}, $diff->{old});
$diff->{new} = join_activity_entries($diff->{field_name}, $old_change->{new}, $diff->{new});
}
push @changes, $diff;
}
return @changes;
}
sub _get_new_bugmail_fields {
......
......@@ -466,9 +466,9 @@ sub redirect_search_url {
# GET requests that lacked a list_id are always redirected. POST requests
# are only redirected if they're under the CGI_URI_LIMIT though.
my $uri_length = length($self->self_url());
if ($self->request_method() ne 'POST' or $uri_length < CGI_URI_LIMIT) {
print $self->redirect(-url => $self->self_url());
my $self_url = $self->self_url();
if ($self->request_method() ne 'POST' or length($self_url) < CGI_URI_LIMIT) {
print $self->redirect(-url => $self_url);
exit;
}
}
......@@ -522,7 +522,7 @@ sub url_is_attachment_base {
$regex =~ s/\\\%bugid\\\%/\\d+/;
}
$regex = "^$regex";
return ($self->self_url =~ $regex) ? 1 : 0;
return ($self->url =~ $regex) ? 1 : 0;
}
##########################
......
......@@ -182,7 +182,7 @@ use Memoize;
# CONSTANTS
#
# Bugzilla version
use constant BUGZILLA_VERSION => "4.4";
use constant BUGZILLA_VERSION => "4.4.1";
# Location of the remote and local XML files to track new releases.
use constant REMOTE_FILE => 'http://updates.bugzilla.org/bugzilla-update.xml';
......
......@@ -204,6 +204,10 @@ sub get_add_column_ddl {
}
else {
@sql = $self->SUPER::get_add_column_ddl(@_);
# Create triggers to deal with empty string.
if ($definition->{TYPE} =~ /varchar|TEXT/i && $definition->{NOTNULL}) {
push(@sql, _get_notnull_trigger_ddl($table, $column));
}
}
return @sql;
......
......@@ -399,7 +399,7 @@ sub _validate {
my $old_requestee_id = $obj_flag->requestee_id;
$obj_flag->_set_status($params->{status});
$obj_flag->_set_requestee($params->{requestee}, $attachment, $params->{skip_roe});
$obj_flag->_set_requestee($params->{requestee}, $bug, $attachment, $params->{skip_roe});
# The requestee ID can be undefined.
my $requestee_changed = ($obj_flag->requestee_id || 0) != ($old_requestee_id || 0);
......@@ -625,10 +625,10 @@ sub force_retarget {
###############################
sub _set_requestee {
my ($self, $requestee, $attachment, $skip_requestee_on_error) = @_;
my ($self, $requestee, $bug, $attachment, $skip_requestee_on_error) = @_;
$self->{requestee} =
$self->_check_requestee($requestee, $attachment, $skip_requestee_on_error);
$self->_check_requestee($requestee, $bug, $attachment, $skip_requestee_on_error);
$self->{requestee_id} =
$self->{requestee} ? $self->{requestee}->id : undef;
......@@ -650,7 +650,7 @@ sub _set_status {
}
sub _check_requestee {
my ($self, $requestee, $attachment, $skip_requestee_on_error) = @_;
my ($self, $requestee, $bug, $attachment, $skip_requestee_on_error) = @_;
# If the flag status is not "?", then no requestee can be defined.
return undef if ($self->status ne '?');
......@@ -677,8 +677,16 @@ sub _check_requestee {
# Note that can_see_bug() will query the DB, so if the bug
# is being added/removed from some groups and these changes
# haven't been committed to the DB yet, they won't be taken
# into account here. In this case, old restrictions matters.
if (!$requestee->can_see_bug($self->bug_id)) {
# into account here. In this case, old group restrictions matter.
# However, if the user has just been changed to the assignee,
# qa_contact, or added to the cc list of the bug and the bug
# is cclist_accessible, the requestee is allowed.
if (!$requestee->can_see_bug($self->bug_id)
&& (!$bug->cclist_accessible
|| !grep($_->id == $requestee->id, @{ $bug->cc_users })
&& $requestee->id != $bug->assigned_to->id
&& (!$bug->qa_contact || $requestee->id != $bug->qa_contact->id)))
{
if ($skip_requestee_on_error) {
undef $requestee;
}
......
......@@ -24,7 +24,6 @@ use Config;
use CPAN;
use Cwd qw(abs_path);
use File::Path qw(rmtree);
use List::Util qw(shuffle);
# These are required for install-module.pl to be able to install
# all modules properly.
......@@ -86,12 +85,7 @@ use constant CPAN_DEFAULTS => {
unzip => bin_loc('unzip'),
wget => bin_loc('wget'),
urllist => [shuffle qw(
http://cpan.pair.com/
http://mirror.hiwaay.net/CPAN/
ftp://ftp.dc.aleron.net/pub/CPAN/
http://mirrors.kernel.org/cpan/
http://mirrors2.kernel.org/cpan/)],
urllist => ['http://www.cpan.org/'],
};
sub check_cpan_requirements {
......
......@@ -272,6 +272,8 @@ sub OPTIONAL_MODULES {
version => 0,
feature => ['auth_radius'],
},
# XXX - Once we require XMLRPC::Lite 0.717 or higher, we can
# remove SOAP::Lite from the list.
{
package => 'SOAP-Lite',
module => 'SOAP::Lite',
......@@ -280,6 +282,14 @@ sub OPTIONAL_MODULES {
version => '0.712',
feature => ['xmlrpc'],
},
# Since SOAP::Lite 1.0, XMLRPC::Lite is no longer included
# and so it must be checked separately.
{
package => 'XMLRPC-Lite',
module => 'XMLRPC::Lite',
version => '0.712',
feature => ['xmlrpc'],
},
{
package => 'JSON-RPC',
module => 'JSON::RPC',
......@@ -345,7 +355,8 @@ sub OPTIONAL_MODULES {
{
package => 'TheSchwartz',
module => 'TheSchwartz',
version => 0,
# 1.07 supports the prioritization of jobs.
version => 1.07,
feature => ['jobqueue'],
},
{
......@@ -673,8 +684,15 @@ sub have_vers {
Bugzilla::Install::Util::set_output_encoding();
# VERSION is provided by UNIVERSAL::, and can be called even if
# the module isn't loaded.
my $vnum = $module->VERSION || -1;
# the module isn't loaded. We eval'uate ->VERSION because it can die
# when the version is not valid (yes, this happens from time to time).
# In that case, we use an uglier method to get the version.
my $vnum = eval { $module->VERSION };
if ($@) {
no strict 'refs';
$vnum = ${"${module}::VERSION"};
}
$vnum ||= -1;
# CGI's versioning scheme went 2.75, 2.751, 2.752, 2.753, 2.76
# That breaks the standard version tests, so we need to manually correct
......
......@@ -104,7 +104,7 @@ sub _check_name {
# We only want to validate the non-existence of the name if
# we're creating a new Keyword or actually renaming the keyword.
if (!ref($self) || $self->name ne $name) {
if (!ref($self) || lc($self->name) ne lc($name)) {
my $keyword = new Bugzilla::Keyword({ name => $name });
ThrowUserError("keyword_already_exists", { name => $name }) if $keyword;
}
......
......@@ -1477,6 +1477,8 @@ sub _special_parse_chfield {
@fields = map { $_ eq '[Bug creation]' ? 'creation_ts' : $_ } @fields;
return undef unless ($date_from ne '' || $date_to ne '' || $value_to ne '');
my $clause = new Bugzilla::Search::Clause();
# It is always safe and useful to push delta_ts into the charts
......@@ -1498,44 +1500,21 @@ sub _special_parse_chfield {
$clause->add('delta_ts', 'lessthaneq', $date_to);
}
# Basically, we construct the chart like:
#
# (added_for_field1 = value OR added_for_field2 = value)
# AND (date_field1_changed >= date_from OR date_field2_changed >= date_from)
# AND (date_field1_changed <= date_to OR date_field2_changed <= date_to)
#
# Theoretically, all we *really* would need to do is look for the field id
# in the bugs_activity table, because we've already limited the search
# by delta_ts above, but there's no chart to do that, so we check the
# change date of the fields.
if ($value_to ne '') {
my $value_clause = new Bugzilla::Search::Clause('OR');
foreach my $field (@fields) {
$value_clause->add($field, 'changedto', $value_to);
}
$clause->add($value_clause);
}
# chfieldto is supposed to be a relative date or a date of the form
# YYYY-MM-DD, i.e. without the time appended to it. We append the
# time ourselves so that the end date is correctly taken into account.
$date_to .= ' 23:59:59' if $date_to =~ /^\d{4}-\d{1,2}-\d{1,2}$/;
if ($date_from ne '') {
my $from_clause = new Bugzilla::Search::Clause('OR');
foreach my $field (@fields) {
$from_clause->add($field, 'changedafter', $date_from);
}
$clause->add($from_clause);
}
if ($date_to ne '') {
# chfieldto is supposed to be a relative date or a date of the form
# YYYY-MM-DD, i.e. without the time appended to it. We append the
# time ourselves so that the end date is correctly taken into account.
$date_to .= ' 23:59:59' if $date_to =~ /^\d{4}-\d{1,2}-\d{1,2}$/;
my $join_clause = new Bugzilla::Search::Clause('OR');
my $to_clause = new Bugzilla::Search::Clause('OR');
foreach my $field (@fields) {
$to_clause->add($field, 'changedbefore', $date_to);
}
$clause->add($to_clause);
foreach my $field (@fields) {
my $sub_clause = new Bugzilla::Search::ClauseGroup();
$sub_clause->add(condition($field, 'changedto', $value_to)) if $value_to ne '';
$sub_clause->add(condition($field, 'changedafter', $date_from)) if $date_from ne '';
$sub_clause->add(condition($field, 'changedbefore', $date_to)) if $date_to ne '';
$join_clause->add($sub_clause);
}
$clause->add($join_clause);
return @{$clause->children} ? $clause : undef;
}
......@@ -2659,7 +2638,7 @@ sub _owner_idle_time_greater_less {
"$ld_table.who IS NULL AND $act_table.who IS NULL";
} else {
$args->{term} =
"$ld_table.who IS NOT NULL OR $act_table.who IS NOT NULL";
"($ld_table.who IS NOT NULL OR $act_table.who IS NOT NULL)";
}
}
......@@ -2903,14 +2882,14 @@ sub _anywordsubstr {
my ($self, $args) = @_;
my @terms = $self->_substring_terms($args);
$args->{term} = join("\n\tOR ", @terms);
$args->{term} = @terms ? '(' . join("\n\tOR ", @terms) . ')' : '';
}
sub _allwordssubstr {
my ($self, $args) = @_;
my @terms = $self->_substring_terms($args);
$args->{term} = join("\n\tAND ", @terms);
$args->{term} = @terms ? '(' . join("\n\tAND ", @terms) . ')' : '';
}
sub _nowordssubstr {
......@@ -2922,19 +2901,19 @@ sub _nowordssubstr {
sub _anywords {
my ($self, $args) = @_;
my @terms = $self->_word_terms($args);
# Because _word_terms uses AND, we need to parenthesize its terms
# if there are more than one.
@terms = map("($_)", @terms) if scalar(@terms) > 1;
$args->{term} = join("\n\tOR ", @terms);
$args->{term} = @terms ? '(' . join("\n\tOR ", @terms) . ')' : '';
}
sub _allwords {
my ($self, $args) = @_;
my @terms = $self->_word_terms($args);
$args->{term} = join("\n\tAND ", @terms);
$args->{term} = @terms ? '(' . join("\n\tAND ", @terms) . ')' : '';
}
sub _nowords {
......
......@@ -65,7 +65,10 @@ sub add {
# Unsupported fields
if (grep { $_ eq $field } UNSUPPORTED_FIELDS ) {
ThrowUserError('search_grouped_field_invalid', { field => $field });
# XXX - Hack till bug 916882 is fixed.
my $operator = scalar(@args) == 3 ? $args[1] : $args[0]->{operator};
ThrowUserError('search_grouped_field_invalid', { field => $field })
unless (($field eq 'product' || $field eq 'component') && $operator =~ /^changed/);
}
$self->SUPER::add(@args);
......
......@@ -138,7 +138,7 @@ sub quicksearch {
# Retain backslashes and quotes, to know which strings are quoted,
# and which ones are not.
my @words = parse_line('\s+', 1, $searchstring);
my @words = _parse_line('\s+', 1, $searchstring);
# If parse_line() returns no data, this means strings are badly quoted.
# Rather than trying to guess what the user wanted to do, we throw an error.
scalar(@words)
......@@ -194,7 +194,7 @@ sub quicksearch {
# Loop over all main-level QuickSearch words.
foreach my $qsword (@qswords) {
my @or_operand = parse_line('\|', 1, $qsword);
my @or_operand = _parse_line('\|', 1, $qsword);
foreach my $term (@or_operand) {
my $negate = substr($term, 0, 1) eq '-';
if ($negate) {
......@@ -208,7 +208,7 @@ sub quicksearch {
# Having ruled out the special cases, we may now split
# by comma, which is another legal boolean OR indicator.
# Remove quotes from quoted words, if any.
@words = parse_line(',', 0, $term);
@words = _parse_line(',', 0, $term);
foreach my $word (@words) {
if (!_special_field_syntax($word, $negate)) {
_default_quicksearch_word($word, $negate);
......@@ -260,6 +260,27 @@ sub quicksearch {
# Parts of quicksearch() #
##########################
sub _parse_line {
my ($delim, $keep, $line) = @_;
# parse_line always treats ' as a quote character, making it impossible
# to sanely search for contractions. As this behavour isn't
# configurable, we replace ' with a placeholder to hide it from the
# parser.
# only treat ' at the start or end of words as quotes
# it's easier to do this in reverse with regexes
$line =~ s/(^|\s|:)'/$1\001/g;
$line =~ s/'($|\s)/\001$1/g;
$line =~ s/\\?'/\000/g;
$line =~ tr/\001/'/;
my @words = parse_line($delim, $keep, $line);
foreach my $word (@words) {
$word =~ tr/\000/'/;
}
return @words;
}
sub _bug_numbers_only {
my $searchstring = shift;
my $cgi = Bugzilla->cgi;
......@@ -363,25 +384,12 @@ sub _handle_special_first_chars {
sub _handle_field_names {
my ($or_operand, $negate, $unknownFields, $ambiguous_fields) = @_;
# Flag and requestee shortcut
if ($or_operand =~ /^(?:flag:)?([^\?]+\?)([^\?]*)$/) {
my ($flagtype, $requestee) = ($1, $2);
addChart('flagtypes.name', 'substring', $flagtype, $negate);
if ($requestee) {
# AND
$chart++;
$and = $or = 0;
addChart('requestees.login_name', 'substring', $requestee, $negate);
}
return 1;
}
# Generic field1,field2,field3:value1,value2 notation.
# We have to correctly ignore commas and colons in quotes.
my @field_values = parse_line(':', 1, $or_operand);
my @field_values = _parse_line(':', 1, $or_operand);