Commit eb19089b authored by Per Cederqvist's avatar Per Cederqvist

Imported Bugzilla 4.2.1.

parent cf1da041
No preview for this file type
......@@ -46,7 +46,8 @@ use constant get_param_list => (
{
name => 'inbound_proxies',
type => 't',
default => ''
default => '',
checker => \&check_ip
},
{
......
......@@ -48,7 +48,7 @@ use base qw(Exporter);
qw(check_multi check_numeric check_regexp check_url check_group
check_sslbase check_priority check_severity check_platform
check_opsys check_shadowdb check_urlbase check_webdotbase
check_user_verify_class
check_user_verify_class check_ip
check_mail_delivery_method check_notification check_utf8
check_bug_status check_smtp_auth check_theschwartz_available
check_maxattachmentsize check_email
......@@ -129,6 +129,15 @@ sub check_sslbase {
return "";
}
sub check_ip {
my $inbound_proxies = shift;
my @proxies = split(/[\s,]+/, $inbound_proxies);
foreach my $proxy (@proxies) {
validate_ip($proxy) || return "$proxy is not a valid IPv4 or IPv6 address";
}
return "";
}
sub check_utf8 {
my $utf8 = shift;
# You cannot turn off the UTF-8 parameter if you've already converted
......
......@@ -202,7 +202,7 @@ use Memoize;
# CONSTANTS
#
# Bugzilla version
use constant BUGZILLA_VERSION => "4.2";
use constant BUGZILLA_VERSION => "4.2.1";
# Location of the remote and local XML files to track new releases.
use constant REMOTE_FILE => 'http://updates.bugzilla.org/bugzilla-update.xml';
......
......@@ -2938,7 +2938,7 @@ unsigned)
=item C<SMALLSERIAL>
An auto-increment L</INT1>
An auto-increment L</INT2>
=item C<MEDIUMSERIAL>
......
......@@ -92,57 +92,62 @@ sub _throw_error {
}
my $template = Bugzilla->template;
if (Bugzilla->error_mode == ERROR_MODE_WEBPAGE) {
print Bugzilla->cgi->header();
$template->process($name, $vars)
|| ThrowTemplateError($template->error());
}
my $message;
# There are some tests that throw and catch a lot of errors,
# and calling $template->process over and over for those errors
# is too slow. So instead, we just "die" with a dump of the arguments.
if (Bugzilla->error_mode != ERROR_MODE_TEST) {
$template->process($name, $vars, \$message)
|| ThrowTemplateError($template->error());
}
# Let's call the hook first, so that extensions can override
# or extend the default behavior, or add their own error codes.
require Bugzilla::Hook;
Bugzilla::Hook::process('error_catch', { error => $error, vars => $vars,
message => \$message });
if (Bugzilla->error_mode == ERROR_MODE_WEBPAGE) {
print Bugzilla->cgi->header();
print $message;
}
elsif (Bugzilla->error_mode == ERROR_MODE_TEST) {
die Dumper($vars);
}
else {
my $message;
$template->process($name, $vars, \$message)
|| ThrowTemplateError($template->error());
if (Bugzilla->error_mode == ERROR_MODE_DIE) {
die("$message\n");
elsif (Bugzilla->error_mode == ERROR_MODE_DIE) {
die("$message\n");
}
elsif (Bugzilla->error_mode == ERROR_MODE_DIE_SOAP_FAULT
|| Bugzilla->error_mode == ERROR_MODE_JSON_RPC)
{
# Clone the hash so we aren't modifying the constant.
my %error_map = %{ WS_ERROR_CODE() };
Bugzilla::Hook::process('webservice_error_codes',
{ error_map => \%error_map });
my $code = $error_map{$error};
if (!$code) {
$code = ERROR_UNKNOWN_FATAL if $name =~ /code/i;
$code = ERROR_UNKNOWN_TRANSIENT if $name =~ /user/i;
}
if (Bugzilla->error_mode == ERROR_MODE_DIE_SOAP_FAULT) {
die SOAP::Fault->faultcode($code)->faultstring($message);
}
elsif (Bugzilla->error_mode == ERROR_MODE_DIE_SOAP_FAULT
|| Bugzilla->error_mode == ERROR_MODE_JSON_RPC)
{
# Clone the hash so we aren't modifying the constant.
my %error_map = %{ WS_ERROR_CODE() };
require Bugzilla::Hook;
Bugzilla::Hook::process('webservice_error_codes',
{ error_map => \%error_map });
my $code = $error_map{$error};
if (!$code) {
$code = ERROR_UNKNOWN_FATAL if $name =~ /code/i;
$code = ERROR_UNKNOWN_TRANSIENT if $name =~ /user/i;
}
if (Bugzilla->error_mode == ERROR_MODE_DIE_SOAP_FAULT) {
die SOAP::Fault->faultcode($code)->faultstring($message);
}
else {
my $server = Bugzilla->_json_server;
# Technically JSON-RPC isn't allowed to have error numbers
# higher than 999, but we do this to avoid conflicts with
# the internal JSON::RPC error codes.
$server->raise_error(code => 100000 + $code,
message => $message,
id => $server->{_bz_request_id},
version => $server->version);
# Most JSON-RPC Throw*Error calls happen within an eval inside
# of JSON::RPC. So, in that circumstance, instead of exiting,
# we die with no message. JSON::RPC checks raise_error before
# it checks $@, so it returns the proper error.
die if _in_eval();
$server->response($server->error_response_header);
}
else {
my $server = Bugzilla->_json_server;
# Technically JSON-RPC isn't allowed to have error numbers
# higher than 999, but we do this to avoid conflicts with
# the internal JSON::RPC error codes.
$server->raise_error(code => 100000 + $code,
message => $message,
id => $server->{_bz_request_id},
version => $server->version);
# Most JSON-RPC Throw*Error calls happen within an eval inside
# of JSON::RPC. So, in that circumstance, instead of exiting,
# we die with no message. JSON::RPC checks raise_error before
# it checks $@, so it returns the proper error.
die if _in_eval();
$server->response($server->error_response_header);
}
}
exit;
......
......@@ -127,6 +127,30 @@ This describes what hooks exist in Bugzilla currently. They are mostly
in alphabetical order, but some related hooks are near each other instead
of being alphabetical.
=head2 admin_editusers_action
This hook allows you to add additional actions to the admin Users page.
Params:
=over
=item C<vars>
You can add as many new key/value pairs as you want to this hashref.
It will be passed to the template.
=item C<action>
A text which indicates the different behaviors that editusers.cgi will have.
With this hook you can change the behavior of an action or add new actions.
=item C<user>
This is a Bugzilla::User object of the user.
=back
=head2 attachment_process_data
This happens at the very beginning process of the attachment creation.
......@@ -432,6 +456,41 @@ The definition is structured as:
=back
=head2 buglist_column_joins
This allows you to join additional tables to display additional columns
in buglists. This hook is generally used in combination with the
C<buglist_columns> hook.
Params:
=over
=item C<column_joins> - A hashref containing data to return back to
L<Bugzilla::Search>. This hashref contains names of the columns as keys and
a hashref about table to join as values. This hashref has the following keys:
=over
=item C<table> - The name of the additional table to join.
=item C<as> - (optional) The alias used for the additional table. This alias
must not conflict with an existing alias already used in the query.
=item C<from> - (optional) The name of the column in the C<bugs> table which
the additional table should be linked to. If omitted, C<bug_id> will be used.
=item C<to> - (optional) The name of the column in the additional table which
should be linked to the column in the C<bugs> table, see C<from> above.
If omitted, C<bug_id> will be used.
=item C<join> - (optional) Either INNER or LEFT. Determine how the additional
table should be joined with the C<bugs> table. If omitted, LEFT is used.
=back
=back
=head2 search_operator_field_override
This allows you to modify L<Bugzilla::Search/OPERATOR_FIELD_OVERRIDE>,
......@@ -628,6 +687,37 @@ Params:
=back
=head2 error_catch
This hook allows extensions to catch errors thrown by Bugzilla and
take the appropriate actions.
Params:
=over
=item C<error>
A string representing the error code thrown by Bugzilla. This string
matches the C<error> variable in C<global/user-error.html.tmpl> and
C<global/code-error.html.tmpl>.
=item C<message>
If the error mode is set to C<ERROR_MODE_WEBPAGE>, you get a reference to
the whole HTML page with the error message in it, including its header and
footer. If you need to extract the error message itself, you can do it by
looking at the content of the table cell whose ID is C<error_msg>.
If the error mode is not set to C<ERROR_MODE_WEBPAGE>, you get a reference
to the error message itself.
=item C<vars>
This hash contains all the data passed to the error template. Its content
depends on the error thrown.
=back
=head2 flag_end_of_update
This happens at the end of L<Bugzilla::Flag/update_flags>, after all other
......
......@@ -3600,7 +3600,7 @@ sub _populate_bug_see_also_class {
if ($dbh->bz_column_info('bug_see_also', 'class')) {
# The length was incorrectly set to 64 instead of 255.
$dbh->bz_alter_column('bug_see_also', 'class',
{TYPE => 'varchar(255)', NOTNULL => 1});
{TYPE => 'varchar(255)', NOTNULL => 1, DEFAULT => "''"});
return;
}
......
......@@ -792,8 +792,8 @@ sub _param_array {
}
sub _params { $_[0]->{params} }
sub _user { return $_[0]->{user} }
sub _sharer_id { $_[0]->{sharer} }
##############################
# Internal Accessors: SELECT #
......@@ -809,7 +809,7 @@ sub _extra_columns {
my ($self) = @_;
# Everything that's going to be in the ORDER BY must also be
# in the SELECT.
$self->{extra_columns} ||= [ $self->_input_order_columns ];
push(@{ $self->{extra_columns} }, $self->_input_order_columns);
return @{ $self->{extra_columns} };
}
......@@ -959,7 +959,8 @@ sub _column_join {
my ($self, $field) = @_;
# The _realname fields require the same join as the username fields.
$field =~ s/_realname$//;
my $join_info = COLUMN_JOINS->{$field};
my $column_joins = $self->_get_column_joins();
my $join_info = $column_joins->{$field};
if ($join_info) {
# Don't allow callers to modify the constant.
$join_info = dclone($join_info);
......@@ -1402,6 +1403,11 @@ sub _special_parse_chfield {
$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 $to_clause = new Bugzilla::Search::Clause('OR');
foreach my $field (@fields) {
$to_clause->add($field, 'changedbefore', $date_to);
......@@ -1668,7 +1674,11 @@ sub _handle_chart {
$search_args{quoted} = $self->_quote_unless_numeric(\%search_args);
# This should add a "term" selement to %search_args.
$self->do_search_function(\%search_args);
# If term is left empty, then this means the criteria
# has no effect and can be ignored.
return unless $search_args{term};
# All the things here that don't get pulled out of
# %search_args are their original values before
# do_search_function modified them.
......@@ -1788,6 +1798,20 @@ sub _get_operator_field_override {
return $cache->{operator_field_override};
}
sub _get_column_joins {
my $self = shift;
my $cache = Bugzilla->request_cache;
return $cache->{column_joins} if defined $cache->{column_joins};
my %column_joins = %{ COLUMN_JOINS() };
Bugzilla::Hook::process('buglist_column_joins',
{ column_joins => \%column_joins });
$cache->{column_joins} = \%column_joins;
return $cache->{column_joins};
}
###########################
# Search Function Helpers #
###########################
......@@ -1900,16 +1924,22 @@ sub _timestamp_translate {
my $value = $args->{value};
my $dbh = Bugzilla->dbh;
return if $value !~ /^[\+\-]?\d+[hdwmy]$/i;
$args->{value} = SqlifyDate($value);
$args->{quoted} = $dbh->quote($args->{value});
return if $value !~ /^(?:[\+\-]?\d+[hdwmy]s?|now)$/i;
# By default, the time is appended to the date, which we don't want
# for deadlines.
$value = SqlifyDate($value);
if ($args->{field} eq 'deadline') {
($value) = split(/\s/, $value);
}
$args->{value} = $value;
$args->{quoted} = $dbh->quote($value);
}
sub SqlifyDate {
my ($str) = @_;
my $fmt = "%Y-%m-%d %H:%M:%S";
$str = "" if !defined $str;
$str = "" if (!defined $str || lc($str) eq 'now');
if ($str eq "") {
my ($sec, $min, $hour, $mday, $month, $year, $wday) = localtime(time());
return sprintf("%4d-%02d-%02d 00:00:00", $year+1900, $month+1, $mday);
......@@ -2537,8 +2567,8 @@ sub _multiselect_table {
}
elsif ($field eq 'tag') {
$args->{full_field} = 'tag.name';
return "bug_tag INNER JOIN tag ON bug_tag.tag_id = tag.id"
. " AND user_id = " . $self->_user->id;
return "bug_tag INNER JOIN tag ON bug_tag.tag_id = tag.id AND user_id = "
. ($self->_sharer_id || $self->_user->id);
}
elsif ($field eq 'bug_group') {
$args->{full_field} = 'groups.name';
......
......@@ -32,6 +32,7 @@ use Bugzilla::Util;
use List::Util qw(min max);
use List::MoreUtils qw(firstidx);
use Text::ParseWords qw(parse_line);
use base qw(Exporter);
@Bugzilla::Search::Quicksearch::EXPORT = qw(quicksearch);
......@@ -128,7 +129,7 @@ use constant COMPONENT_EXCEPTIONS => (
);
# Quicksearch-wide globals for boolean charts.
our ($chart, $and, $or, $fulltext);
our ($chart, $and, $or, $fulltext, $bug_status_set);
sub quicksearch {
my ($searchstring) = (@_);
......@@ -142,54 +143,102 @@ sub quicksearch {
$searchstring =~ s/(^[\s,]+|[\s,]+$)//g;
ThrowUserError('buglist_parameters_required') unless ($searchstring);
$fulltext = Bugzilla->user->setting('quicksearch_fulltext') eq 'on' ? 1 : 0;
if ($searchstring =~ m/^[0-9,\s]*$/) {
_bug_numbers_only($searchstring);
}
else {
_handle_alias($searchstring);
# Globally translate " AND ", " OR ", " NOT " to space, pipe, dash.
$searchstring =~ s/\s+AND\s+/ /g;
$searchstring =~ s/\s+OR\s+/|/g;
$searchstring =~ s/\s+NOT\s+/ -/g;
# Retain backslashes and quotes, to know which strings are quoted,
# and which ones are not.
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)
|| ThrowUserError('quicksearch_unbalanced_quotes', {string => $searchstring});
# A query cannot start with AND or OR, nor can it end with AND, OR or NOT.
ThrowUserError('quicksearch_invalid_query')
if ($words[0] =~ /^(?:AND|OR)$/ || $words[$#words] =~ /^(?:AND|OR|NOT)$/);
my (@qswords, @or_group);
while (scalar @words) {
my $word = shift @words;
# AND is the default word separator, similar to a whitespace,
# but |a AND OR b| is not a valid combination.
if ($word eq 'AND') {
ThrowUserError('quicksearch_invalid_query', {operators => ['AND', 'OR']})
if $words[0] eq 'OR';
}
# |a OR AND b| is not a valid combination.
# |a OR OR b| is equivalent to |a OR b| and so is harmless.
elsif ($word eq 'OR') {
ThrowUserError('quicksearch_invalid_query', {operators => ['OR', 'AND']})
if $words[0] eq 'AND';
}
# NOT negates the following word.
# |NOT AND| and |NOT OR| are not valid combinations.
# |NOT NOT| is fine but has no effect as they cancel themselves.
elsif ($word eq 'NOT') {
$word = shift @words;
next if $word eq 'NOT';
if ($word eq 'AND' || $word eq 'OR') {
ThrowUserError('quicksearch_invalid_query', {operators => ['NOT', $word]});
}
unshift(@words, "-$word");
}
else {
# OR groups words together, as OR has higher precedence than AND.
push(@or_group, $word);
# If the next word is not OR, then we are not in a OR group,
# or we are leaving it.
if (!defined $words[0] || $words[0] ne 'OR') {
push(@qswords, join('|', @or_group));
@or_group = ();
}
}
}
my @words = splitString($searchstring);
_handle_status_and_resolution(\@words);
_handle_status_and_resolution($qswords[0]);
shift(@qswords) if $bug_status_set;
my (@unknownFields, %ambiguous_fields);
$fulltext = Bugzilla->user->setting('quicksearch_fulltext') eq 'on' ? 1 : 0;
# Loop over all main-level QuickSearch words.
foreach my $qsword (@words) {
my $negate = substr($qsword, 0, 1) eq '-';
if ($negate) {
$qsword = substr($qsword, 1);
}
foreach my $qsword (@qswords) {
my @or_operand = parse_line('\|', 1, $qsword);
foreach my $term (@or_operand) {
my $negate = substr($term, 0, 1) eq '-';
if ($negate) {
$term = substr($term, 1);
}
# No special first char
if (!_handle_special_first_chars($qsword, $negate)) {
# Split by '|' to get all operands for a boolean OR.
foreach my $or_operand (split(/\|/, $qsword)) {
if (!_handle_field_names($or_operand, $negate,
\@unknownFields,
\%ambiguous_fields))
{
# Having ruled out the special cases, we may now split
# by comma, which is another legal boolean OR indicator.
foreach my $word (split(/,/, $or_operand)) {
if (!_special_field_syntax($word, $negate)) {
_default_quicksearch_word($word, $negate);
}
_handle_urls($word, $negate);
}
next if _handle_special_first_chars($term, $negate);
next if _handle_field_names($term, $negate, \@unknownFields,
\%ambiguous_fields);
# 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);
foreach my $word (@words) {
if (!_special_field_syntax($word, $negate)) {
_default_quicksearch_word($word, $negate);
}
_handle_urls($word, $negate);
}
}
$chart++;
$and = 0;
$or = 0;
} # foreach (@words)
}
# If there is no mention of a bug status, we restrict the query
# to open bugs by default.
unless ($bug_status_set) {
$cgi->param('bug_status', BUG_STATE_OPEN);
}
# Inform user about any unknown fields
if (scalar(@unknownFields) || scalar(keys %ambiguous_fields)) {
......@@ -261,48 +310,26 @@ sub _handle_alias {
}
sub _handle_status_and_resolution {
my ($words) = @_;
my $word = shift;
my $legal_statuses = get_legal_field_values('bug_status');
my $legal_resolutions = get_legal_field_values('resolution');
my @openStates = BUG_STATE_OPEN;
my @closedStates;
my (%states, %resolutions);
$bug_status_set = 1;
foreach (@$legal_statuses) {
push(@closedStates, $_) unless is_open_state($_);
}
foreach (@openStates) { $states{$_} = 1 }
if ($words->[0] eq 'ALL') {
foreach (@$legal_statuses) { $states{$_} = 1 }
shift @$words;
}
elsif ($words->[0] eq 'OPEN') {
shift @$words;
}
elsif ($words->[0] =~ /^[A-Z_]+(,[_A-Z]+)*$/) {
# e.g. CON,IN_PR,FIX
undef %states;
if (matchPrefixes(\%states,
\%resolutions,
[split(/,/, $words->[0])],
$legal_statuses,
$legal_resolutions)) {
shift @$words;
}
else {
# Carry on if no match found
foreach (@openStates) { $states{$_} = 1 }
}
if ($word eq 'OPEN') {