are required for create(). It now assumes that any column that is NOT NULL and has not DEFAULT in the database is required. We also shift the burden of throwing errors about empty values to the validators. This fixes the bug that Bugzilla::Bug->create() wasn't populating default values for fields if they weren't specified in the create() parameters. r=timello, a=mkanat git-svn-id: svn://10.0.0.236/trunk@260542 18797224-902f-48f8-a5cc-f745e15eee43
660 lines
19 KiB
Perl
660 lines
19 KiB
Perl
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
|
#
|
|
# The contents of this file are subject to the Mozilla Public
|
|
# License Version 1.1 (the "License"); you may not use this file
|
|
# except in compliance with the License. You may obtain a copy of
|
|
# the License at http://www.mozilla.org/MPL/
|
|
#
|
|
# Software distributed under the License is distributed on an "AS
|
|
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
|
# implied. See the License for the specific language governing
|
|
# rights and limitations under the License.
|
|
#
|
|
# The Original Code is the Bugzilla Bug Tracking System.
|
|
#
|
|
# Contributor(s): Tiago R. Mello <timello@async.com.br>
|
|
# Frédéric Buclin <LpSolit@gmail.com>
|
|
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
|
# Akamai Technologies <bugzilla-dev@akamai.com>
|
|
|
|
package Bugzilla::Component;
|
|
use strict;
|
|
use base qw(Bugzilla::Field::ChoiceInterface Bugzilla::Object);
|
|
|
|
use Bugzilla::Constants;
|
|
use Bugzilla::Util;
|
|
use Bugzilla::Error;
|
|
use Bugzilla::User;
|
|
use Bugzilla::FlagType;
|
|
use Bugzilla::Series;
|
|
|
|
use Scalar::Util qw(blessed);
|
|
|
|
###############################
|
|
#### Initialization ####
|
|
###############################
|
|
|
|
use constant DB_TABLE => 'components';
|
|
# This is mostly for the editfields.cgi case where ->get_all is called.
|
|
use constant LIST_ORDER => 'product_id, name';
|
|
|
|
use constant DB_COLUMNS => qw(
|
|
id
|
|
name
|
|
product_id
|
|
initialowner
|
|
initialqacontact
|
|
description
|
|
);
|
|
|
|
use constant UPDATE_COLUMNS => qw(
|
|
name
|
|
initialowner
|
|
initialqacontact
|
|
description
|
|
);
|
|
|
|
use constant REQUIRED_FIELD_MAP => {
|
|
product_id => 'product',
|
|
};
|
|
|
|
use constant VALIDATORS => {
|
|
create_series => \&Bugzilla::Object::check_boolean,
|
|
product => \&_check_product,
|
|
initialowner => \&_check_initialowner,
|
|
initialqacontact => \&_check_initialqacontact,
|
|
description => \&_check_description,
|
|
initial_cc => \&_check_cc_list,
|
|
name => \&_check_name,
|
|
};
|
|
|
|
use constant VALIDATOR_DEPENDENCIES => {
|
|
name => ['product'],
|
|
};
|
|
|
|
###############################
|
|
|
|
sub new {
|
|
my $class = shift;
|
|
my $param = shift;
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
my $product;
|
|
if (ref $param and !defined $param->{id}) {
|
|
$product = $param->{product};
|
|
my $name = $param->{name};
|
|
if (!defined $product) {
|
|
ThrowCodeError('bad_arg',
|
|
{argument => 'product',
|
|
function => "${class}::new"});
|
|
}
|
|
if (!defined $name) {
|
|
ThrowCodeError('bad_arg',
|
|
{argument => 'name',
|
|
function => "${class}::new"});
|
|
}
|
|
|
|
my $condition = 'product_id = ? AND name = ?';
|
|
my @values = ($product->id, $name);
|
|
$param = { condition => $condition, values => \@values };
|
|
}
|
|
|
|
unshift @_, $param;
|
|
my $component = $class->SUPER::new(@_);
|
|
# Add the product object as attribute only if the component exists.
|
|
$component->{product} = $product if ($component && $product);
|
|
return $component;
|
|
}
|
|
|
|
sub create {
|
|
my $class = shift;
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
$dbh->bz_start_transaction();
|
|
|
|
$class->check_required_create_fields(@_);
|
|
my $params = $class->run_create_validators(@_);
|
|
my $cc_list = delete $params->{initial_cc};
|
|
my $create_series = delete $params->{create_series};
|
|
my $product = delete $params->{product};
|
|
$params->{product_id} = $product->id;
|
|
|
|
my $component = $class->insert_create_data($params);
|
|
$component->{product} = $product;
|
|
|
|
# We still have to fill the component_cc table.
|
|
$component->_update_cc_list($cc_list) if $cc_list;
|
|
|
|
# Create series for the new component.
|
|
$component->_create_series() if $create_series;
|
|
|
|
$dbh->bz_commit_transaction();
|
|
return $component;
|
|
}
|
|
|
|
sub update {
|
|
my $self = shift;
|
|
my $changes = $self->SUPER::update(@_);
|
|
|
|
# Update the component_cc table if necessary.
|
|
if (defined $self->{cc_ids}) {
|
|
my $diff = $self->_update_cc_list($self->{cc_ids});
|
|
$changes->{cc_list} = $diff if defined $diff;
|
|
}
|
|
return $changes;
|
|
}
|
|
|
|
sub remove_from_db {
|
|
my $self = shift;
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
$self->_check_if_controller(); # From ChoiceInterface
|
|
|
|
$dbh->bz_start_transaction();
|
|
|
|
if ($self->bug_count) {
|
|
if (Bugzilla->params->{'allowbugdeletion'}) {
|
|
require Bugzilla::Bug;
|
|
foreach my $bug_id (@{$self->bug_ids}) {
|
|
# Note: We allow admins to delete bugs even if they can't
|
|
# see them, as long as they can see the product.
|
|
my $bug = new Bugzilla::Bug($bug_id);
|
|
$bug->remove_from_db();
|
|
}
|
|
} else {
|
|
ThrowUserError('component_has_bugs', {nb => $self->bug_count});
|
|
}
|
|
}
|
|
|
|
$dbh->do('DELETE FROM flaginclusions WHERE component_id = ?',
|
|
undef, $self->id);
|
|
$dbh->do('DELETE FROM flagexclusions WHERE component_id = ?',
|
|
undef, $self->id);
|
|
$dbh->do('DELETE FROM component_cc WHERE component_id = ?',
|
|
undef, $self->id);
|
|
$dbh->do('DELETE FROM components WHERE id = ?', undef, $self->id);
|
|
|
|
$dbh->bz_commit_transaction();
|
|
}
|
|
|
|
################################
|
|
# Validators
|
|
################################
|
|
|
|
sub _check_name {
|
|
my ($invocant, $name, undef, $params) = @_;
|
|
my $product = blessed($invocant) ? $invocant->product : $params->{product};
|
|
|
|
$name = trim($name);
|
|
$name || ThrowUserError('component_blank_name');
|
|
|
|
if (length($name) > MAX_COMPONENT_SIZE) {
|
|
ThrowUserError('component_name_too_long', {'name' => $name});
|
|
}
|
|
|
|
my $component = new Bugzilla::Component({product => $product, name => $name});
|
|
if ($component && (!ref $invocant || $component->id != $invocant->id)) {
|
|
ThrowUserError('component_already_exists', { name => $component->name,
|
|
product => $product });
|
|
}
|
|
return $name;
|
|
}
|
|
|
|
sub _check_description {
|
|
my ($invocant, $description) = @_;
|
|
|
|
$description = trim($description);
|
|
$description || ThrowUserError('component_blank_description');
|
|
return $description;
|
|
}
|
|
|
|
sub _check_initialowner {
|
|
my ($invocant, $owner) = @_;
|
|
|
|
$owner || ThrowUserError('component_need_initialowner');
|
|
my $owner_id = Bugzilla::User->check($owner)->id;
|
|
return $owner_id;
|
|
}
|
|
|
|
sub _check_initialqacontact {
|
|
my ($invocant, $qa_contact) = @_;
|
|
|
|
my $qa_contact_id;
|
|
if (Bugzilla->params->{'useqacontact'}) {
|
|
$qa_contact_id = Bugzilla::User->check($qa_contact)->id if $qa_contact;
|
|
}
|
|
elsif (ref $invocant) {
|
|
$qa_contact_id = $invocant->{initialqacontact};
|
|
}
|
|
return $qa_contact_id;
|
|
}
|
|
|
|
sub _check_product {
|
|
my ($invocant, $product) = @_;
|
|
$product || ThrowCodeError('param_required',
|
|
{ function => "$invocant->create", param => 'product' });
|
|
return Bugzilla->user->check_can_admin_product($product->name);
|
|
}
|
|
|
|
sub _check_cc_list {
|
|
my ($invocant, $cc_list) = @_;
|
|
|
|
my %cc_ids;
|
|
foreach my $cc (@$cc_list) {
|
|
my $id = login_to_id($cc, THROW_ERROR);
|
|
$cc_ids{$id} = 1;
|
|
}
|
|
return [keys %cc_ids];
|
|
}
|
|
|
|
###############################
|
|
#### Methods ####
|
|
###############################
|
|
|
|
sub _update_cc_list {
|
|
my ($self, $cc_list) = @_;
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
my $old_cc_list =
|
|
$dbh->selectcol_arrayref('SELECT user_id FROM component_cc
|
|
WHERE component_id = ?', undef, $self->id);
|
|
|
|
my ($removed, $added) = diff_arrays($old_cc_list, $cc_list);
|
|
my $diff;
|
|
if (scalar @$removed || scalar @$added) {
|
|
$diff = [join(', ', @$removed), join(', ', @$added)];
|
|
}
|
|
|
|
$dbh->do('DELETE FROM component_cc WHERE component_id = ?', undef, $self->id);
|
|
|
|
my $sth = $dbh->prepare('INSERT INTO component_cc
|
|
(user_id, component_id) VALUES (?, ?)');
|
|
$sth->execute($_, $self->id) foreach (@$cc_list);
|
|
|
|
return $diff;
|
|
}
|
|
|
|
sub _create_series {
|
|
my $self = shift;
|
|
|
|
# Insert default charting queries for this product.
|
|
# If they aren't using charting, this won't do any harm.
|
|
my $prodcomp = "&product=" . url_quote($self->product->name) .
|
|
"&component=" . url_quote($self->name);
|
|
|
|
my $open_query = 'field0-0-0=resolution&type0-0-0=notregexp&value0-0-0=.' .
|
|
$prodcomp;
|
|
my $nonopen_query = 'field0-0-0=resolution&type0-0-0=regexp&value0-0-0=.' .
|
|
$prodcomp;
|
|
|
|
my @series = ([get_text('series_all_open'), $open_query],
|
|
[get_text('series_all_closed'), $nonopen_query]);
|
|
|
|
foreach my $sdata (@series) {
|
|
my $series = new Bugzilla::Series(undef, $self->product->name,
|
|
$self->name, $sdata->[0],
|
|
Bugzilla->user->id, 1, $sdata->[1], 1);
|
|
$series->writeToDatabase();
|
|
}
|
|
}
|
|
|
|
sub set_name { $_[0]->set('name', $_[1]); }
|
|
sub set_description { $_[0]->set('description', $_[1]); }
|
|
sub set_default_assignee {
|
|
my ($self, $owner) = @_;
|
|
|
|
$self->set('initialowner', $owner);
|
|
# Reset the default owner object.
|
|
delete $self->{default_assignee};
|
|
}
|
|
sub set_default_qa_contact {
|
|
my ($self, $qa_contact) = @_;
|
|
|
|
$self->set('initialqacontact', $qa_contact);
|
|
# Reset the default QA contact object.
|
|
delete $self->{default_qa_contact};
|
|
}
|
|
sub set_cc_list {
|
|
my ($self, $cc_list) = @_;
|
|
|
|
$self->{cc_ids} = $self->_check_cc_list($cc_list);
|
|
# Reset the list of CC user objects.
|
|
delete $self->{initial_cc};
|
|
}
|
|
|
|
sub bug_count {
|
|
my $self = shift;
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
if (!defined $self->{'bug_count'}) {
|
|
$self->{'bug_count'} = $dbh->selectrow_array(q{
|
|
SELECT COUNT(*) FROM bugs
|
|
WHERE component_id = ?}, undef, $self->id) || 0;
|
|
}
|
|
return $self->{'bug_count'};
|
|
}
|
|
|
|
sub bug_ids {
|
|
my $self = shift;
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
if (!defined $self->{'bugs_ids'}) {
|
|
$self->{'bugs_ids'} = $dbh->selectcol_arrayref(q{
|
|
SELECT bug_id FROM bugs
|
|
WHERE component_id = ?}, undef, $self->id);
|
|
}
|
|
return $self->{'bugs_ids'};
|
|
}
|
|
|
|
sub default_assignee {
|
|
my $self = shift;
|
|
|
|
if (!defined $self->{'default_assignee'}) {
|
|
$self->{'default_assignee'} =
|
|
new Bugzilla::User($self->{'initialowner'});
|
|
}
|
|
return $self->{'default_assignee'};
|
|
}
|
|
|
|
sub default_qa_contact {
|
|
my $self = shift;
|
|
|
|
if (!defined $self->{'default_qa_contact'}) {
|
|
$self->{'default_qa_contact'} =
|
|
new Bugzilla::User($self->{'initialqacontact'});
|
|
}
|
|
return $self->{'default_qa_contact'};
|
|
}
|
|
|
|
sub flag_types {
|
|
my $self = shift;
|
|
|
|
if (!defined $self->{'flag_types'}) {
|
|
$self->{'flag_types'} = {};
|
|
$self->{'flag_types'}->{'bug'} =
|
|
Bugzilla::FlagType::match({ 'target_type' => 'bug',
|
|
'product_id' => $self->product_id,
|
|
'component_id' => $self->id });
|
|
|
|
$self->{'flag_types'}->{'attachment'} =
|
|
Bugzilla::FlagType::match({ 'target_type' => 'attachment',
|
|
'product_id' => $self->product_id,
|
|
'component_id' => $self->id });
|
|
}
|
|
return $self->{'flag_types'};
|
|
}
|
|
|
|
sub initial_cc {
|
|
my $self = shift;
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
if (!defined $self->{'initial_cc'}) {
|
|
# If set_cc_list() has been called but data are not yet written
|
|
# into the DB, we want the new values defined by it.
|
|
my $cc_ids = $self->{cc_ids}
|
|
|| $dbh->selectcol_arrayref('SELECT user_id FROM component_cc
|
|
WHERE component_id = ?',
|
|
undef, $self->id);
|
|
|
|
$self->{'initial_cc'} = Bugzilla::User->new_from_list($cc_ids);
|
|
}
|
|
return $self->{'initial_cc'};
|
|
}
|
|
|
|
sub product {
|
|
my $self = shift;
|
|
if (!defined $self->{'product'}) {
|
|
require Bugzilla::Product; # We cannot |use| it.
|
|
$self->{'product'} = new Bugzilla::Product($self->product_id);
|
|
}
|
|
return $self->{'product'};
|
|
}
|
|
|
|
###############################
|
|
#### Accessors ####
|
|
###############################
|
|
|
|
sub description { return $_[0]->{'description'}; }
|
|
sub product_id { return $_[0]->{'product_id'}; }
|
|
|
|
##############################################
|
|
# Implement Bugzilla::Field::ChoiceInterface #
|
|
##############################################
|
|
|
|
use constant FIELD_NAME => 'component';
|
|
use constant is_default => 0;
|
|
use constant is_active => 1;
|
|
|
|
sub is_set_on_bug {
|
|
my ($self, $bug) = @_;
|
|
# We treat it like a hash always, so that we don't have to check if it's
|
|
# a hash or an object.
|
|
return 0 if !defined $bug->{component_id};
|
|
$bug->{component_id} == $self->id ? 1 : 0;
|
|
}
|
|
|
|
###############################
|
|
#### Subroutines ####
|
|
###############################
|
|
|
|
1;
|
|
|
|
__END__
|
|
|
|
=head1 NAME
|
|
|
|
Bugzilla::Component - Bugzilla product component class.
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
use Bugzilla::Component;
|
|
|
|
my $component = new Bugzilla::Component($comp_id);
|
|
my $component = new Bugzilla::Component({ product => $product, name => $name });
|
|
|
|
my $bug_count = $component->bug_count();
|
|
my $bug_ids = $component->bug_ids();
|
|
my $id = $component->id;
|
|
my $name = $component->name;
|
|
my $description = $component->description;
|
|
my $product_id = $component->product_id;
|
|
my $default_assignee = $component->default_assignee;
|
|
my $default_qa_contact = $component->default_qa_contact;
|
|
my $initial_cc = $component->initial_cc;
|
|
my $product = $component->product;
|
|
my $bug_flag_types = $component->flag_types->{'bug'};
|
|
my $attach_flag_types = $component->flag_types->{'attachment'};
|
|
|
|
my $component = Bugzilla::Component->check({ product => $product, name => $name });
|
|
|
|
my $component =
|
|
Bugzilla::Component->create({ name => $name,
|
|
product => $product,
|
|
initialowner => $user_login1,
|
|
initialqacontact => $user_login2,
|
|
description => $description});
|
|
|
|
$component->set_name($new_name);
|
|
$component->set_description($new_description);
|
|
$component->set_default_assignee($new_login_name);
|
|
$component->set_default_qa_contact($new_login_name);
|
|
$component->set_cc_list(\@new_login_names);
|
|
$component->update();
|
|
|
|
$component->remove_from_db;
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
Component.pm represents a Product Component object.
|
|
|
|
=head1 METHODS
|
|
|
|
=over
|
|
|
|
=item C<new($param)>
|
|
|
|
Description: The constructor is used to load an existing component
|
|
by passing a component ID or a hash with the product
|
|
object the component belongs to and the component name.
|
|
|
|
Params: $param - If you pass an integer, the integer is the
|
|
component ID from the database that we want to
|
|
read in. If you pass in a hash with the 'name'
|
|
and 'product' keys, then the value of the name
|
|
key is the name of a component being in the given
|
|
product.
|
|
|
|
Returns: A Bugzilla::Component object.
|
|
|
|
=item C<bug_count()>
|
|
|
|
Description: Returns the total of bugs that belong to the component.
|
|
|
|
Params: none.
|
|
|
|
Returns: Integer with the number of bugs.
|
|
|
|
=item C<bugs_ids()>
|
|
|
|
Description: Returns all bug IDs that belong to the component.
|
|
|
|
Params: none.
|
|
|
|
Returns: A reference to an array of bug IDs.
|
|
|
|
=item C<default_assignee()>
|
|
|
|
Description: Returns a user object that represents the default assignee for
|
|
the component.
|
|
|
|
Params: none.
|
|
|
|
Returns: A Bugzilla::User object.
|
|
|
|
=item C<default_qa_contact()>
|
|
|
|
Description: Returns a user object that represents the default QA contact for
|
|
the component.
|
|
|
|
Params: none.
|
|
|
|
Returns: A Bugzilla::User object.
|
|
|
|
=item C<initial_cc>
|
|
|
|
Description: Returns a list of user objects representing users being
|
|
in the initial CC list.
|
|
|
|
Params: none.
|
|
|
|
Returns: An arrayref of L<Bugzilla::User> objects.
|
|
|
|
=item C<flag_types()>
|
|
|
|
Description: Returns all bug and attachment flagtypes available for
|
|
the component.
|
|
|
|
Params: none.
|
|
|
|
Returns: Two references to an array of flagtype objects.
|
|
|
|
=item C<product()>
|
|
|
|
Description: Returns the product the component belongs to.
|
|
|
|
Params: none.
|
|
|
|
Returns: A Bugzilla::Product object.
|
|
|
|
=item C<set_name($new_name)>
|
|
|
|
Description: Changes the name of the component.
|
|
|
|
Params: $new_name - new name of the component (string). This name
|
|
must be unique within the product.
|
|
|
|
Returns: Nothing.
|
|
|
|
=item C<set_description($new_desc)>
|
|
|
|
Description: Changes the description of the component.
|
|
|
|
Params: $new_desc - new description of the component (string).
|
|
|
|
Returns: Nothing.
|
|
|
|
=item C<set_default_assignee($new_assignee)>
|
|
|
|
Description: Changes the default assignee of the component.
|
|
|
|
Params: $new_owner - login name of the new default assignee of
|
|
the component (string). This user account
|
|
must already exist.
|
|
|
|
Returns: Nothing.
|
|
|
|
=item C<set_default_qa_contact($new_qa_contact)>
|
|
|
|
Description: Changes the default QA contact of the component.
|
|
|
|
Params: $new_qa_contact - login name of the new QA contact of
|
|
the component (string). This user
|
|
account must already exist.
|
|
|
|
Returns: Nothing.
|
|
|
|
=item C<set_cc_list(\@cc_list)>
|
|
|
|
Description: Changes the list of users being in the CC list by default.
|
|
|
|
Params: \@cc_list - list of login names (string). All the user
|
|
accounts must already exist.
|
|
|
|
Returns: Nothing.
|
|
|
|
=item C<update()>
|
|
|
|
Description: Write changes made to the component into the DB.
|
|
|
|
Params: none.
|
|
|
|
Returns: A hashref with changes made to the component object.
|
|
|
|
=item C<remove_from_db()>
|
|
|
|
Description: Deletes the current component from the DB. The object itself
|
|
is not destroyed.
|
|
|
|
Params: none.
|
|
|
|
Returns: Nothing.
|
|
|
|
=back
|
|
|
|
=head1 CLASS METHODS
|
|
|
|
=over
|
|
|
|
=item C<create(\%params)>
|
|
|
|
Description: Create a new component for the given product.
|
|
|
|
Params: The hashref must have the following keys:
|
|
name - name of the new component (string). This name
|
|
must be unique within the product.
|
|
product - a Bugzilla::Product object to which
|
|
the Component is being added.
|
|
description - description of the new component (string).
|
|
initialowner - login name of the default assignee (string).
|
|
The following keys are optional:
|
|
initiaqacontact - login name of the default QA contact (string),
|
|
or an empty string to clear it.
|
|
initial_cc - an arrayref of login names to add to the
|
|
CC list by default.
|
|
|
|
Returns: A Bugzilla::Component object.
|
|
|
|
=back
|
|
|
|
=cut
|