Commit 5309f887 authored by mkanat%bugzilla.org's avatar mkanat%bugzilla.org

Bug 373689: Implement set_product: Move product changing from process_bug into Bugzilla::Bug

Patch By Max Kanat-Alexander <mkanat@bugzilla.org> r=LpSolit, a=LpSolit
parent 61ac61f0
...@@ -156,10 +156,12 @@ sub VALIDATORS { ...@@ -156,10 +156,12 @@ sub VALIDATORS {
}; };
use constant UPDATE_VALIDATORS => { use constant UPDATE_VALIDATORS => {
bug_status => \&_check_bug_status, bug_status => \&_check_bug_status,
cclist_accessible => \&Bugzilla::Object::check_boolean, cclist_accessible => \&Bugzilla::Object::check_boolean,
reporter_accessible => \&Bugzilla::Object::check_boolean, reporter_accessible => \&Bugzilla::Object::check_boolean,
resolution => \&_check_resolution, resolution => \&_check_resolution,
target_milestone => \&_check_target_milestone,
version => \&_check_version,
}; };
sub UPDATE_COLUMNS { sub UPDATE_COLUMNS {
...@@ -169,6 +171,7 @@ sub UPDATE_COLUMNS { ...@@ -169,6 +171,7 @@ sub UPDATE_COLUMNS {
my @columns = qw( my @columns = qw(
alias alias
cclist_accessible cclist_accessible
component_id
deadline deadline
estimated_time estimated_time
everconfirmed everconfirmed
...@@ -177,12 +180,15 @@ sub UPDATE_COLUMNS { ...@@ -177,12 +180,15 @@ sub UPDATE_COLUMNS {
bug_status bug_status
op_sys op_sys
priority priority
product_id
remaining_time remaining_time
rep_platform rep_platform
reporter_accessible reporter_accessible
resolution resolution
short_desc short_desc
status_whiteboard status_whiteboard
target_milestone
version
); );
push(@columns, @custom_names); push(@columns, @custom_names);
return @columns; return @columns;
...@@ -429,17 +435,17 @@ sub run_create_validators { ...@@ -429,17 +435,17 @@ sub run_create_validators {
$vars->{comment_exists} = ($params->{comment} =~ /\S+/) ? 1 : 0; $vars->{comment_exists} = ($params->{comment} =~ /\S+/) ? 1 : 0;
Bugzilla::Bug->check_status_change_triggers($params->{bug_status}, [], $vars); Bugzilla::Bug->check_status_change_triggers($params->{bug_status}, [], $vars);
$params->{target_milestone} = $class->_check_target_milestone($product, $params->{target_milestone} = $class->_check_target_milestone(
$params->{target_milestone}); $params->{target_milestone}, $product);
$params->{version} = $class->_check_version($product, $params->{version}); $params->{version} = $class->_check_version($params->{version}, $product);
$params->{keywords} = $class->_check_keywords($params->{keywords}, $product); $params->{keywords} = $class->_check_keywords($params->{keywords}, $product);
$params->{groups} = $class->_check_groups($product, $params->{groups} = $class->_check_groups($product,
$params->{groups}); $params->{groups});
my $component = $class->_check_component($product, $params->{component}); my $component = $class->_check_component($params->{component}, $product);
$params->{component_id} = $component->id; $params->{component_id} = $component->id;
delete $params->{component}; delete $params->{component};
...@@ -521,6 +527,12 @@ sub update { ...@@ -521,6 +527,12 @@ sub update {
my $change = $changes->{$field}; my $change = $changes->{$field};
my $from = defined $change->[0] ? $change->[0] : ''; my $from = defined $change->[0] ? $change->[0] : '';
my $to = defined $change->[1] ? $change->[1] : ''; my $to = defined $change->[1] ? $change->[1] : '';
# Certain fields have their name stored in bugs_activity, not their id.
if ( grep($_ eq $field, qw(product_id component_id)) ) {
$field =~ s/_id$//;
$from = $self->{"_old_${field}_name"};
$to = $self->$field;
}
LogActivityEntry($self->id, $field, $from, $to, Bugzilla->user->id, LogActivityEntry($self->id, $field, $from, $to, Bugzilla->user->id,
$delta_ts); $delta_ts);
} }
...@@ -912,9 +924,10 @@ sub _check_comment_type { ...@@ -912,9 +924,10 @@ sub _check_comment_type {
} }
sub _check_component { sub _check_component {
my ($invocant, $product, $name) = @_; my ($invocant, $name, $product) = @_;
$name = trim($name); $name = trim($name);
$name || ThrowUserError("require_component"); $name || ThrowUserError("require_component");
($product = $invocant->product_obj) if ref $invocant;
my $obj = Bugzilla::Component::check_component($product, $name); my $obj = Bugzilla::Component::check_component($product, $name);
return $obj; return $obj;
} }
...@@ -1071,20 +1084,18 @@ sub _check_keywords { ...@@ -1071,20 +1084,18 @@ sub _check_keywords {
sub _check_product { sub _check_product {
my ($invocant, $name) = @_; my ($invocant, $name) = @_;
my $obj; $name = trim($name);
if (ref $invocant) { # If we're updating the bug and they haven't changed the product,
$obj = Bugzilla::Product::check_product($name); # always allow it.
} if (ref $invocant && lc($invocant->product_obj->name) eq lc($name)) {
else { return $invocant->product_obj;
# Check that the product exists and that the user
# is allowed to enter bugs into this product.
Bugzilla->user->can_enter_product($name, THROW_ERROR);
# can_enter_product already does everything that check_product
# would do for us, so we don't need to use it.
$obj = new Bugzilla::Product({ name => $name });
} }
# Check that the product exists and that the user
return $obj; # is allowed to enter bugs into this product.
Bugzilla->user->can_enter_product($name, THROW_ERROR);
# can_enter_product already does everything that check_product
# would do for us, so we don't need to use it.
return new Bugzilla::Product({ name => $name });
} }
sub _check_op_sys { sub _check_op_sys {
...@@ -1168,11 +1179,10 @@ sub _check_strict_isolation { ...@@ -1168,11 +1179,10 @@ sub _check_strict_isolation {
} }
sub _check_target_milestone { sub _check_target_milestone {
my ($invocant, $product, $target) = @_; my ($invocant, $target, $product) = @_;
$product = $invocant->product_obj if ref $invocant;
$target = trim($target); $target = trim($target);
if (ref $invocant) {
return undef if !Bugzilla->params->{'usetargetmilestone'};
}
$target = $product->default_milestone if !defined $target; $target = $product->default_milestone if !defined $target;
check_field('target_milestone', $target, check_field('target_milestone', $target,
[map($_->name, @{$product->milestones})]); [map($_->name, @{$product->milestones})]);
...@@ -1214,8 +1224,9 @@ sub _check_qa_contact { ...@@ -1214,8 +1224,9 @@ sub _check_qa_contact {
} }
sub _check_version { sub _check_version {
my ($invocant, $product, $version) = @_; my ($invocant, $version, $product) = @_;
$version = trim($version); $version = trim($version);
($product = $invocant->product_obj) if ref $invocant;
check_field('version', $version, [map($_->name, @{$product->versions})]); check_field('version', $version, [map($_->name, @{$product->versions})]);
return $version; return $version;
} }
...@@ -1295,6 +1306,22 @@ sub _set_global_validator { ...@@ -1295,6 +1306,22 @@ sub _set_global_validator {
sub set_alias { $_[0]->set('alias', $_[1]); } sub set_alias { $_[0]->set('alias', $_[1]); }
sub set_cclist_accessible { $_[0]->set('cclist_accessible', $_[1]); } sub set_cclist_accessible { $_[0]->set('cclist_accessible', $_[1]); }
sub set_component {
my ($self, $name) = @_;
my $old_comp = $self->component_obj;
my $component = $self->_check_component($name);
if ($old_comp->id != $component->id) {
$self->{component_id} = $component->id;
$self->{component} = $component->name;
$self->{component_obj} = $component;
# For update()
$self->{_old_component_name} = $old_comp->name;
# Add in the Default CC of the new Component;
foreach my $cc (@{$component->initial_cc}) {
$self->add_cc($cc);
}
}
}
sub set_custom_field { sub set_custom_field {
my ($self, $field, $value) = @_; my ($self, $field, $value) = @_;
if (ref $value eq 'ARRAY' && $field->type != FIELD_TYPE_MULTI_SELECT) { if (ref $value eq 'ARRAY' && $field->type != FIELD_TYPE_MULTI_SELECT) {
...@@ -1319,6 +1346,111 @@ sub _set_everconfirmed { $_[0]->set('everconfirmed', $_[1]); } ...@@ -1319,6 +1346,111 @@ sub _set_everconfirmed { $_[0]->set('everconfirmed', $_[1]); }
sub set_op_sys { $_[0]->set('op_sys', $_[1]); } sub set_op_sys { $_[0]->set('op_sys', $_[1]); }
sub set_platform { $_[0]->set('rep_platform', $_[1]); } sub set_platform { $_[0]->set('rep_platform', $_[1]); }
sub set_priority { $_[0]->set('priority', $_[1]); } sub set_priority { $_[0]->set('priority', $_[1]); }
sub set_product {
my ($self, $name, $params) = @_;
my $old_product = $self->product_obj;
my $product = $self->_check_product($name);
my $product_changed = 0;
if ($old_product->id != $product->id) {
$self->{product_id} = $product->id;
$self->{product} = $product->name;
$self->{product_obj} = $product;
# For update()
$self->{_old_product_name} = $old_product->name;
# Delete fields that depend upon the old Product value.
delete $self->{choices};
delete $self->{milestoneurl};
$product_changed = 1;
}
$params ||= {};
my $comp_name = $params->{component} || $self->component;
my $vers_name = $params->{version} || $self->version;
my $tm_name = $params->{target_milestone};
# This way, if usetargetmilestone is off and we've changed products,
# set_target_milestone will reset our target_milestone to
# $product->default_milestone. But if we haven't changed products,
# we don't reset anything.
if (!defined $tm_name
&& (Bugzilla->params->{'usetargetmilestone'} || !$product_changed))
{
$tm_name = $self->target_milestone;
}
if ($product_changed && Bugzilla->usage_mode == USAGE_MODE_BROWSER) {
# Try to set each value with the new product.
# Have to set error_mode because Throw*Error calls exit() otherwise.
my $old_error_mode = Bugzilla->error_mode;
Bugzilla->error_mode(ERROR_MODE_DIE);
my $component_ok = eval { $self->set_component($comp_name); 1; };
my $version_ok = eval { $self->set_version($vers_name); 1; };
my $milestone_ok = eval { $self->set_target_milestone($tm_name); 1; };
# If there were any errors thrown, make sure we don't mess up any
# other part of Bugzilla that checks $@.
undef $@;
Bugzilla->error_mode($old_error_mode);
my $verified = $params->{change_confirmed};
my %vars;
if (!$verified || !$component_ok || !$version_ok || !$milestone_ok) {
$vars{defaults} = {
# Note that because of the eval { set } above, these are
# already set correctly if they're valid, otherwise they're
# set to some invalid value which the template will ignore.
component => $self->component,
version => $self->version,
milestone => $milestone_ok ? $self->target_milestone
: $product->default_milestone
};
$vars{components} = [map { $_->name } @{$product->components}];
$vars{milestones} = [map { $_->name } @{$product->milestones}];
$vars{versions} = [map { $_->name } @{$product->versions}];
}
if (!$verified) {
$vars{verify_bug_groups} = 1;
my $dbh = Bugzilla->dbh;
my @idlist = ($self->id);
push(@idlist, map {$_->id} @{ $params->{other_bugs} })
if $params->{other_bugs};
# Get the ID of groups which are no longer valid in the new product.
my $gids = $dbh->selectcol_arrayref(
'SELECT bgm.group_id
FROM bug_group_map AS bgm
WHERE bgm.bug_id IN (' . join(',', ('?' x @idlist)) . ')
AND bgm.group_id NOT IN
(SELECT gcm.group_id
FROM group_control_map AS gcm
WHERE gcm.product_id = ?
AND ( (gcm.membercontrol != ?
AND gcm.group_id IN ('
. Bugzilla->user->groups_as_string . '))
OR gcm.othercontrol != ?) )',
undef, (@idlist, $product->id, CONTROLMAPNA, CONTROLMAPNA));
$vars{'old_groups'} = Bugzilla::Group->new_from_list($gids);
}
if (%vars) {
$vars{product} = $product;
my $template = Bugzilla->template;
$template->process("bug/process/verify-new-product.html.tmpl",
\%vars) || ThrowTemplateError($template->error());
exit;
}
}
else {
# When we're not in the browser (or we didn't change the product), we
# just die if any of these are invalid.
$self->set_component($comp_name);
$self->set_version($vers_name);
$self->set_target_milestone($tm_name);
}
# XXX This is temporary until all of process_bug uses update();
return $product_changed;
}
sub set_remaining_time { $_[0]->set('remaining_time', $_[1]); } sub set_remaining_time { $_[0]->set('remaining_time', $_[1]); }
# Used only when closing a bug or moving between closed states. # Used only when closing a bug or moving between closed states.
sub _zero_remaining_time { $_[0]->{'remaining_time'} = 0; } sub _zero_remaining_time { $_[0]->{'remaining_time'} = 0; }
...@@ -1332,8 +1464,10 @@ sub set_status { ...@@ -1332,8 +1464,10 @@ sub set_status {
$self->_set_everconfirmed(1) if (is_open_state($status) && $status ne 'UNCONFIRMED'); $self->_set_everconfirmed(1) if (is_open_state($status) && $status ne 'UNCONFIRMED');
} }
sub set_status_whiteboard { $_[0]->set('status_whiteboard', $_[1]); } sub set_status_whiteboard { $_[0]->set('status_whiteboard', $_[1]); }
sub set_summary { $_[0]->set('short_desc', $_[1]); } sub set_summary { $_[0]->set('short_desc', $_[1]); }
sub set_url { $_[0]->set('bug_file_loc', $_[1]); } sub set_target_milestone { $_[0]->set('target_milestone', $_[1]); }
sub set_url { $_[0]->set('bug_file_loc', $_[1]); }
sub set_version { $_[0]->set('version', $_[1]); }
######################## ########################
# "Add/Remove" Methods # # "Add/Remove" Methods #
...@@ -1344,16 +1478,13 @@ sub set_url { $_[0]->set('bug_file_loc', $_[1]); } ...@@ -1344,16 +1478,13 @@ sub set_url { $_[0]->set('bug_file_loc', $_[1]); }
# Accepts a User object or a username. Adds the user only if they # Accepts a User object or a username. Adds the user only if they
# don't already exist as a CC on the bug. # don't already exist as a CC on the bug.
sub add_cc { sub add_cc {
# XXX $product is a temporary hack until all of process_bug uses Bug my ($self, $user_or_name) = @_;
# objects for updating.
my ($self, $user_or_name, $product) = @_;
return if !$user_or_name; return if !$user_or_name;
my $user = ref $user_or_name ? $user_or_name my $user = ref $user_or_name ? $user_or_name
: Bugzilla::User->check($user_or_name); : Bugzilla::User->check($user_or_name);
my $product_id = $product ? $product->id : $self->{product_id};
if (Bugzilla->params->{strict_isolation} if (Bugzilla->params->{strict_isolation}
&& !$user->can_edit_product($product_id)) && !$user->can_edit_product($self->product_obj->id))
{ {
ThrowUserError('invalid_user_group', { users => $user->login, ThrowUserError('invalid_user_group', { users => $user->login,
bug_id => $self->id }); bug_id => $self->id });
...@@ -1598,6 +1729,15 @@ sub component { ...@@ -1598,6 +1729,15 @@ sub component {
return $self->{component}; return $self->{component};
} }
# XXX Eventually this will replace component()
sub component_obj {
my ($self) = @_;
return $self->{component_obj} if defined $self->{component_obj};
return {} if $self->{error};
$self->{component_obj} = new Bugzilla::Component($self->{component_id});
return $self->{component_obj};
}
sub classification_id { sub classification_id {
my ($self) = @_; my ($self) = @_;
return $self->{classification_id} if exists $self->{classification_id}; return $self->{classification_id} if exists $self->{classification_id};
...@@ -1710,8 +1850,8 @@ sub product { ...@@ -1710,8 +1850,8 @@ sub product {
sub product_obj { sub product_obj {
my $self = shift; my $self = shift;
return {} if $self->{error}; return {} if $self->{error};
$self->{prod_obj} ||= new Bugzilla::Product($self->{product_id}); $self->{product_obj} ||= new Bugzilla::Product($self->{product_id});
return $self->{prod_obj}; return $self->{product_obj};
} }
sub qa_contact { sub qa_contact {
......
...@@ -95,6 +95,20 @@ sub send_results { ...@@ -95,6 +95,20 @@ sub send_results {
$vars->{'header_done'} = 1; $vars->{'header_done'} = 1;
} }
# Tells us whether or not a field should be changed by process_bug, by
# checking that it's defined and not set to dontchange.
sub should_set {
my ($field) = @_;
my $cgi = Bugzilla->cgi;
if (defined $cgi->param($field)
&& (!$cgi->param('dontchange')
|| $cgi->param($field) ne $cgi->param('dontchange')))
{
return 1;
}
return 0;
}
sub comment_exists { sub comment_exists {
my $cgi = Bugzilla->cgi; my $cgi = Bugzilla->cgi;
return ($cgi->param('comment') && $cgi->param('comment') =~ /\S+/) ? 1 : 0; return ($cgi->param('comment') && $cgi->param('comment') =~ /\S+/) ? 1 : 0;
...@@ -223,167 +237,29 @@ if ($cgi->cookie("BUGLIST") && defined $cgi->param('id')) { ...@@ -223,167 +237,29 @@ if ($cgi->cookie("BUGLIST") && defined $cgi->param('id')) {
$vars->{'bug_list'} = \@bug_list; $vars->{'bug_list'} = \@bug_list;
} }
# Figure out whether or not the user is trying to change the product my $product_change; # XXX Temporary until all of process_bug uses update()
# (either the "product" variable is not set to "don't change" or the if (should_set('product')) {
# user is changing a single bug and has changed the bug's product), # We only pass the fields if they're defined and not set to dontchange.
# and make the user verify the version, component, target milestone, # This is because when you haven't changed the product, --do-not-change--
# and bug groups if so. # isn't a valid component, version, or target_milestone. (When you're
# At this point, the product must be defined, even if set to "dontchange". # doing a mass-change, some bugs might already be in the new product.)
defined($cgi->param('product')) my %product_fields;
|| ThrowCodeError('undefined_field', { field => 'product' }); foreach my $field (qw(component version target_milestone)) {
if (should_set($field)) {
my $product_change = 0; $product_fields{$field} = $cgi->param($field);
if ((defined $cgi->param('id') && $cgi->param('product') ne $bug->product)
|| (!$cgi->param('id')
&& $cgi->param('product') ne $cgi->param('dontchange')))
{
if (Bugzilla->params->{'commentonreassignbycomponent'} && !comment_exists()) {
ThrowUserError('comment_required');
}
# Check to make sure they actually have the right to change the product
my $oldproduct = (defined $cgi->param('id')) ? $bug->product : '';
if (!$bug->check_can_change_field('product', $oldproduct, $cgi->param('product'),
\$PrivilegesRequired))
{
$vars->{'oldvalue'} = $oldproduct;
$vars->{'newvalue'} = $cgi->param('product');
$vars->{'field'} = 'product';
$vars->{'privs'} = $PrivilegesRequired;
ThrowUserError("illegal_change", $vars);
}
my $product_name = $cgi->param('product');
my $product = new Bugzilla::Product({name => $product_name});
# If at least one bug does not belong to the product we are
# moving to, we have to check whether or not the user is
# allowed to enter bugs into that product.
# Note that this check must be done early to avoid the leakage
# of component, version and target milestone names.
my $check_can_enter = 1;
if ($product) {
$check_can_enter =
$dbh->selectrow_array("SELECT 1 FROM bugs
WHERE product_id != ?
AND bugs.bug_id IN
(" . join(',', @idlist) . ") " .
$dbh->sql_limit(1),
undef, $product->id);
}
if ($check_can_enter) { $user->can_enter_product($product_name, 1) }
# note that when this script is called from buglist.cgi (rather
# than show_bug.cgi), it's possible that the product will be changed
# but that the version and/or component will be set to
# "--dont_change--" but still happen to be correct. in this case,
# the if statement will incorrectly trigger anyway. this is a
# pretty weird case, and not terribly unreasonable behavior, but
# worthy of a comment, perhaps.
#
my @version_names = map($_->name, @{$product->versions});
my @component_names = map($_->name, @{$product->components});
my $vok = 0;
if (defined $cgi->param('version')) {
$vok = lsearch(\@version_names, $cgi->param('version')) >= 0;
}
my $cok = 0;
if (defined $cgi->param('component')) {
$cok = lsearch(\@component_names, $cgi->param('component')) >= 0;
}
my $mok = 1; # so it won't affect the 'if' statement if milestones aren't used
my @milestone_names = ();
if ( Bugzilla->params->{"usetargetmilestone"} ) {
@milestone_names = map($_->name, @{$product->milestones});
$mok = 0;
if (defined $cgi->param('target_milestone')) {
$mok = lsearch(\@milestone_names, $cgi->param('target_milestone')) >= 0;
}
}
# We cannot be sure if the component is the same by only checking $cok; the
# current component name could exist in the new product. So always display
# the form and use the confirm_product_change param to check if that was
# shown. Also show the verification form if the product-specific fields
# somehow still need to be verified, or if we need to verify whether or not
# to add the bugs to their new product's group.
if (!$vok || !$cok || !$mok || !defined $cgi->param('confirm_product_change')) {
if (Bugzilla->usage_mode == USAGE_MODE_EMAIL) {
if (!$vok) {
ThrowUserError('version_not_valid', {
version => $cgi->param('version'),
product => $product->name});
}
if (!$cok) {
ThrowUserError('component_not_valid', {
product => $product->name,
name => $cgi->param('component')});
}
if (!$mok) {
ThrowUserError('milestone_not_valid', {
product => $product->name,
milestone => $cgi->param('target_milestone')});
}
}
$vars->{'product'} = $product;
my %defaults;
# We set the defaults to these fields to the old value,
# if it's a valid option, otherwise we use the default where
# that's appropriate
$vars->{'versions'} = \@version_names;
if ($vok) {
$defaults{'version'} = $cgi->param('version');
}
elsif (scalar(@version_names) == 1) {
$defaults{'version'} = $version_names[0];
}
$vars->{'components'} = \@component_names;
if ($cok) {
$defaults{'component'} = $cgi->param('component');
}
elsif (scalar(@component_names) == 1) {
$defaults{'component'} = $component_names[0];
} }
}
if (Bugzilla->params->{"usetargetmilestone"}) { foreach my $b (@bug_objects) {
$vars->{'milestones'} = \@milestone_names; my $changed = $b->set_product(scalar $cgi->param('product'),
if ($mok) { { %product_fields,
$defaults{'target_milestone'} = $cgi->param('target_milestone'); change_confirmed => scalar $cgi->param('confirm_product_change'),
} else { other_bugs => \@bug_objects,
$defaults{'target_milestone'} = $product->default_milestone; });
} $product_change ||= $changed;
}
$vars->{'defaults'} = \%defaults;
# Get the ID of groups which are no longer valid in the new product.
my $gids =
$dbh->selectcol_arrayref('SELECT bgm.group_id
FROM bug_group_map AS bgm
WHERE bgm.bug_id IN (' . join(', ', @idlist) . ')
AND bgm.group_id NOT IN
(SELECT gcm.group_id
FROM group_control_map AS gcm
WHERE gcm.product_id = ?
AND ((gcm.membercontrol != ?
AND gcm.group_id IN (' . $grouplist . '))
OR gcm.othercontrol != ?))',
undef, ($product->id, CONTROLMAPNA, CONTROLMAPNA));
$vars->{'old_groups'} = Bugzilla::Group->new_from_list($gids);
$template->process("bug/process/verify-new-product.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
exit;
} }
$product_change = 1;
} }
# At this point, the component must be defined, even if set to "dontchange".
defined($cgi->param('component'))
|| ThrowCodeError('undefined_field', { field => 'component' });
# Confirm that the reporter of the current bug can access the bug we are duping to. # Confirm that the reporter of the current bug can access the bug we are duping to.
sub DuplicateUserConfirm { sub DuplicateUserConfirm {
my ($dupe, $original) = @_; my ($dupe, $original) = @_;
...@@ -426,27 +302,6 @@ sub DuplicateUserConfirm { ...@@ -426,27 +302,6 @@ sub DuplicateUserConfirm {
exit; exit;
} }
if (defined $cgi->param('id')) {
# since this means that we were called from show_bug.cgi, now is a good
# time to do a whole bunch of error checking that can't easily happen when
# we've been called from buglist.cgi, because buglist.cgi only tweaks
# values that have been changed instead of submitting all the new values.
# (XXX those error checks need to happen too, but implementing them
# is more work in the current architecture of this script...)
my $prod = $bug->_check_product($cgi->param('product'));
$cgi->param('product', $prod->name);
my $comp = $bug->_check_component($prod,
scalar $cgi->param('component'));
$cgi->param('component', $comp->name);
$cgi->param('version', $bug->_check_version($prod,
scalar $cgi->param('version')));
$cgi->param('target_milestone', $bug->_check_target_milestone($prod,
scalar $cgi->param('target_milestone')));
}
my %methods = ( my %methods = (
bug_severity => 'set_severity', bug_severity => 'set_severity',
rep_platform => 'set_platform', rep_platform => 'set_platform',
...@@ -454,7 +309,11 @@ my %methods = ( ...@@ -454,7 +309,11 @@ my %methods = (
bug_file_loc => 'set_url', bug_file_loc => 'set_url',
); );
foreach my $b (@bug_objects) { foreach my $b (@bug_objects) {
# Component, target_milestone, and version are in here just in case
# the 'product' field wasn't defined in the CGI. It doesn't hurt to set
# them twice.
foreach my $field_name (qw(op_sys rep_platform priority bug_severity foreach my $field_name (qw(op_sys rep_platform priority bug_severity
component target_milestone version
bug_file_loc status_whiteboard short_desc bug_file_loc status_whiteboard short_desc
deadline remaining_time estimated_time)) deadline remaining_time estimated_time))
{ {
...@@ -559,19 +418,6 @@ sub DoComma { ...@@ -559,19 +418,6 @@ sub DoComma {
$::comma = ","; $::comma = ",";
} }
foreach my $field ("version", "target_milestone") {
if (defined $cgi->param($field)) {
if (!$cgi->param('dontchange')
|| $cgi->param($field) ne $cgi->param('dontchange')) {
DoComma();
$::query .= "$field = ?";
my $value = trim($cgi->param($field));
trick_taint($value);
push(@values, $value);
}
}
}
# Add custom fields data to the query that will update the database. # Add custom fields data to the query that will update the database.
foreach my $field (Bugzilla->get_fields({custom => 1, obsolete => 0})) { foreach my $field (Bugzilla->get_fields({custom => 1, obsolete => 0})) {
my $fname = $field->name; my $fname = $field->name;
...@@ -583,24 +429,10 @@ foreach my $field (Bugzilla->get_fields({custom => 1, obsolete => 0})) { ...@@ -583,24 +429,10 @@ foreach my $field (Bugzilla->get_fields({custom => 1, obsolete => 0})) {
} }
} }
my $product; my ($product, @newprod_ids);
my $prod_changed = 0;
my @newprod_ids;
if ($cgi->param('product') ne $cgi->param('dontchange')) { if ($cgi->param('product') ne $cgi->param('dontchange')) {
$product = Bugzilla::Product::check_product(scalar $cgi->param('product')); $product = Bugzilla::Product::check_product(scalar $cgi->param('product'));
DoComma();
$::query .= "product_id = ?";
push(@values, $product->id);
@newprod_ids = ($product->id); @newprod_ids = ($product->id);
# If the bug remains in the same product, leave $prod_changed set to 0.
# Even with 'strict_isolation' turned on, we ignore users who already
# play a role for the bug; else you would never be able to edit it.
# If you want to move the bug to another product, then you first have to
# remove these users from the bug.
unless (defined $cgi->param('id') && $bug->product_id == $product->id) {
$prod_changed = 1;
}
} else { } else {
@newprod_ids = @{$dbh->selectcol_arrayref("SELECT DISTINCT product_id @newprod_ids = @{$dbh->selectcol_arrayref("SELECT DISTINCT product_id
FROM bugs FROM bugs
...@@ -612,33 +444,8 @@ if ($cgi->param('product') ne $cgi->param('dontchange')) { ...@@ -612,33 +444,8 @@ if ($cgi->param('product') ne $cgi->param('dontchange')) {
} }
} }
my $component;
my (@cc_add, @cc_remove); my (@cc_add, @cc_remove);
if ($cgi->param('component') ne $cgi->param('dontchange')) {
if (scalar(@newprod_ids) > 1) {
ThrowUserError("no_component_change_for_multiple_products");
}
$component =
Bugzilla::Component::check_component($product, scalar $cgi->param('component'));
# This parameter is required later when checking fields the user can change.
$cgi->param('component_id', $component->id);
DoComma();
$::query .= "component_id = ?";
push(@values, $component->id);
# Add in the default CC list for the component if we are moving bugs.
if (!$cgi->param('id') || $component->id != $bug->component_id) {
foreach my $cc (@{$component->initial_cc}) {
# NewCC must be defined or the code below won't insert
# any CCs.
$cgi->param('newcc') || $cgi->param('newcc', "");
push(@cc_add, $cc->login);
}
}
}
# Certain changes can only happen on individual bugs, never on mass-changes. # Certain changes can only happen on individual bugs, never on mass-changes.
if (defined $cgi->param('id')) { if (defined $cgi->param('id')) {
my $bug = $bug_objects[0]; my $bug = $bug_objects[0];
...@@ -721,7 +528,7 @@ if (defined $cgi->param('newcc') ...@@ -721,7 +528,7 @@ if (defined $cgi->param('newcc')
foreach my $b (@bug_objects) { foreach my $b (@bug_objects) {
$b->remove_cc($_) foreach @cc_remove; $b->remove_cc($_) foreach @cc_remove;
$b->add_cc($_, $product) foreach @cc_add; $b->add_cc($_) foreach @cc_add;
} }
# Store the new assignee and QA contact IDs (if any). This is the # Store the new assignee and QA contact IDs (if any). This is the
...@@ -891,7 +698,7 @@ sub SnapShotBug { ...@@ -891,7 +698,7 @@ sub SnapShotBug {
my $timestamp; my $timestamp;
if ($prod_changed && Bugzilla->params->{"strict_isolation"}) { if ($product_change && Bugzilla->params->{"strict_isolation"}) {
my $sth_cc = $dbh->prepare("SELECT who my $sth_cc = $dbh->prepare("SELECT who
FROM cc FROM cc
WHERE bug_id = ?"); WHERE bug_id = ?");
...@@ -982,10 +789,9 @@ foreach my $id (@idlist) { ...@@ -982,10 +789,9 @@ foreach my $id (@idlist) {
} }
# We have to check whether the bug is moved to another product # We have to check whether the bug is moved to another product
# and/or component before reassigning. If $component is defined, # and/or component before reassigning.
# use it; else use the product/component the bug is already in.
if ($cgi->param('set_default_assignee')) { if ($cgi->param('set_default_assignee')) {
my $new_comp_id = $component ? $component->id : $old_bug_obj->component_id; my $new_comp_id = $bug_objects{$id}->component_id;
$assignee = $dbh->selectrow_array('SELECT initialowner $assignee = $dbh->selectrow_array('SELECT initialowner
FROM components FROM components
WHERE components.id = ?', WHERE components.id = ?',
...@@ -996,7 +802,7 @@ foreach my $id (@idlist) { ...@@ -996,7 +802,7 @@ foreach my $id (@idlist) {
} }
if (Bugzilla->params->{'useqacontact'} && $cgi->param('set_default_qa_contact')) { if (Bugzilla->params->{'useqacontact'} && $cgi->param('set_default_qa_contact')) {
my $new_comp_id = $component ? $component->id : $old_bug_obj->component_id; my $new_comp_id = $bug_objects{$id}->component_id;
$qacontact = $dbh->selectrow_array('SELECT initialqacontact $qacontact = $dbh->selectrow_array('SELECT initialqacontact
FROM components FROM components
WHERE components.id = ?', WHERE components.id = ?',
...@@ -1096,25 +902,16 @@ foreach my $id (@idlist) { ...@@ -1096,25 +902,16 @@ foreach my $id (@idlist) {
{ product => $oldhash{'product'} }); { product => $oldhash{'product'} });
} }
# If we are editing a single bug or if bugs are being moved into my $new_product = $bug_objects{$id}->product_obj;
# a specific product, $product is defined. If $product is undefined, # musthavemilestoneonaccept applies only if at least two
# then we don't move bugs, so we can use their current product. # target milestones are defined for the product.
my $new_product = $product || new Bugzilla::Product({name => $oldhash{'product'}}); if ($requiremilestone
if ($requiremilestone) { && scalar(@{ $new_product->milestones }) > 1
# musthavemilestoneonaccept applies only if at least two && $bug_objects{$id}->target_milestone
# target milestones are defined for the product. eq $new_product->default_milestone)
if (scalar(@{$new_product->milestones}) > 1) { {
my $value = $cgi->param('target_milestone'); ThrowUserError("milestone_required", { bug_id => $id });
if (!defined $value || $value eq $cgi->param('dontchange')) { }
$value = $oldhash{'target_milestone'};
}
# if musthavemilestoneonaccept == 1, then the target
# milestone must be different from the default one.
if ($value eq $new_product->default_milestone) {
ThrowUserError("milestone_required", { bug_id => $id });
}
}
}
if (defined $cgi->param('delta_ts') && $cgi->param('delta_ts') ne $delta_ts) if (defined $cgi->param('delta_ts') && $cgi->param('delta_ts') ne $delta_ts)
{ {
($vars->{'operations'}) = ($vars->{'operations'}) =
...@@ -1301,19 +1098,6 @@ foreach my $id (@idlist) { ...@@ -1301,19 +1098,6 @@ foreach my $id (@idlist) {
my $new = shift @newvalues; my $new = shift @newvalues;
if ($old ne $new) { if ($old ne $new) {
# Products and components are now stored in the DB using ID's
# We need to translate this to English before logging it
if ($col eq 'product_id') {
$old = $old_bug_obj->product;
$new = $new_bug_obj->product;
$col = 'product';
}
if ($col eq 'component_id') {
$old = $old_bug_obj->component;
$new = $new_bug_obj->component;
$col = 'component';
}
# save off the old value for passing to Bugzilla::BugMail so # save off the old value for passing to Bugzilla::BugMail so
# the old assignee can be notified # the old assignee can be notified
# #
...@@ -1333,6 +1117,8 @@ foreach my $id (@idlist) { ...@@ -1333,6 +1117,8 @@ foreach my $id (@idlist) {
# Bugzilla::Bug does these for us already. # Bugzilla::Bug does these for us already.
next if grep($_ eq $col, qw(keywords op_sys rep_platform priority next if grep($_ eq $col, qw(keywords op_sys rep_platform priority
product_id component_id version
target_milestone
bug_severity short_desc alias bug_severity short_desc alias
deadline estimated_time remaining_time deadline estimated_time remaining_time
reporter_accessible cclist_accessible reporter_accessible cclist_accessible
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
# #
# Contributor(s): Myk Melez <myk@mozilla.org> # Contributor(s): Myk Melez <myk@mozilla.org>
# Frédéric Buclin <LpSolit@gmail.com> # Frédéric Buclin <LpSolit@gmail.com>
# Max Kanat-Alexander <mkanat@bugzilla.org>
#%] #%]
[%# INTERFACE: [%# INTERFACE:
...@@ -26,12 +27,10 @@ ...@@ -26,12 +27,10 @@
# milestones: array; milestones for the new product. # milestones: array; milestones for the new product.
# defaults: hash; keys are names of fields, values are defaults for # defaults: hash; keys are names of fields, values are defaults for
# those fields # those fields
#
# verify_bug_groups: If groups need to be confirmed in addition to fields.
#%] #%]
[%# The global Bugzilla->cgi object is used to obtain form variable values. %]
[% USE Bugzilla %]
[% cgi = Bugzilla.cgi %]
[% PROCESS global/variables.none.tmpl %] [% PROCESS global/variables.none.tmpl %]
[% PROCESS global/header.html.tmpl [% PROCESS global/header.html.tmpl
...@@ -39,60 +38,77 @@ ...@@ -39,60 +38,77 @@
<form action="process_bug.cgi" method="post"> <form action="process_bug.cgi" method="post">
[% SET exclude_items = ['version', 'component', 'target_milestone'] %]
[% IF verify_bug_groups %]
[% exclude_items.push('bit-\d+') %]
[% END %]
[% PROCESS "global/hidden-fields.html.tmpl" [% PROCESS "global/hidden-fields.html.tmpl"
exclude=("^version|component|target_milestone|bit-\\d+$") %] exclude = '^' _ exclude_items.join('|') _ '$' %]
<input type="hidden" name="confirm_product_change" value="1"> <input type="hidden" name="confirm_product_change" value="1">
[%# Verify the version, component, and target milestone fields. %] [%# Verify the version, component, and target milestone fields. %]
<h3>Verify Version, Component[% ", Target Milestone" IF Param("usetargetmilestone") %]</h3> <h3>Verify Version, Component[% ", Target Milestone" IF Param("usetargetmilestone") %]</h3>
<p> <p>
[% IF Param("usetargetmilestone") %] [% IF Param("usetargetmilestone") %]
You are moving the [% terms.bug %](s) to the product You are moving the [% terms.bug %](s) to the product
<b>[% cgi.param("product") FILTER html %]</b>, <b>[% product.name FILTER html %]</b>,
and the version, component, and/or target milestone fields are no longer and the version, component, and/or target milestone fields are no longer
correct. Please set the correct version, component, and target milestone now: correct. Please set the correct version, component, and target milestone now:
[% ELSE %] [% ELSE %]
You are moving the [% terms.bug %](s) to the product You are moving the [% terms.bug %](s) to the product
<b>[% cgi.param("product") FILTER html %]</b>, <b>[% product.name FILTER html %]</b>,
and the version and component fields are no longer correct. and the version and component fields are no longer correct.
Please set the correct version and component now: Please set the correct version and component now:
[% END %] [% END %]
</p> </p>
<table> <table>
<tr> <tr>
<td> <td>
<b>Version:</b><br> <b>Version:</b><br>
[% PROCESS "global/select-menu.html.tmpl" [% IF versions.size == 1 %]
name="version" [% SET default_version = versions.0 %]
options=versions [% ELSE %]
default=defaults.version [% SET default_version = defaults.version %]
size=10 %] [% END %]
</td> [% PROCESS "global/select-menu.html.tmpl"
name="version"
options=versions
default=default_version
size=10 %]
</td>
<td>
<b>Component:</b><br>
[% IF components.size == 1 %]
[% SET default_component = components.0 %]
[% ELSE %]
[% SET default_component = defaults.component %]
[% END %]
[% PROCESS "global/select-menu.html.tmpl"
name="component"
options=components
default=default_component
size=10 %]
</td>
[% IF Param("usetargetmilestone") %]
<td> <td>
<b>Component:</b><br> <b>Target Milestone:</b><br>
[% PROCESS "global/select-menu.html.tmpl" [% PROCESS "global/select-menu.html.tmpl"
name="component" name="target_milestone"
options=components options=milestones
default=defaults.component default=defaults.milestone
size=10 %] size=10 %]
</td> </td>
[% IF Param("usetargetmilestone") %] [% END %]
<td> </tr>
<b>Target Milestone:</b><br> </table>
[% PROCESS "global/select-menu.html.tmpl"
name="target_milestone"
options=milestones
default=defaults.target_milestone
size=10 %]
</td>
[% END %]
</tr>
</table>
[% IF verify_bug_groups %]
<h3>Verify [% terms.Bug %] Group</h3> <h3>Verify [% terms.Bug %] Group</h3>
[% IF old_groups.size %] [% IF old_groups.size %]
<p>These groups are not legal for the '[% product.name FILTER html %]' <p>These groups are not legal for the '[% product.name FILTER html %]'
product or you are not allowed to restrict [% terms.bugs %] to these groups. product or you are not allowed to restrict [% terms.bugs %] to these groups.
...@@ -157,6 +173,7 @@ ...@@ -157,6 +173,7 @@
[% END %] [% END %]
</p> </p>
[% END %] [% END %]
[% END %]
<input type="submit" id="change_product" value="Commit"> <input type="submit" id="change_product" value="Commit">
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment