bugzilla-4intranet/Bugzilla/Component.pm

785 lines
21 KiB
Perl

# 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>
use strict;
package Bugzilla::Component;
use base qw(Bugzilla::Field::Choice);
use Bugzilla::Constants;
use Bugzilla::Util;
use Bugzilla::Error;
use Bugzilla::User;
use Bugzilla::FlagType;
use Bugzilla::FlagType::UserList;
use Bugzilla::Series;
###############################
#### Initialization ####
###############################
use constant DB_TABLE => 'components';
use constant FIELD_NAME => 'component';
use constant NAME_FIELD => 'name';
use constant LIST_ORDER => 'product_id, name';
use constant DB_COLUMNS => qw(
id
name
product_id
initialowner
initialqacontact
description
wiki_url
default_version
isactive
);
use constant REQUIRED_CREATE_FIELDS => qw(
name
product
initialowner
description
);
use constant UPDATE_COLUMNS => qw(
name
initialowner
initialqacontact
description
wiki_url
default_version
isactive
);
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,
isactive => \&Bugzilla::Object::check_boolean,
};
use constant UPDATE_VALIDATORS => {
name => \&_check_name,
default_version => \&_check_default_version,
};
###############################
sub new
{
my $class = shift;
my $param = shift;
my $dbh = Bugzilla->dbh;
my $product;
if (ref $param)
{
$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 $component = $class->insert_create_data($params);
# Fill visibility values
$component->set_visibility_values([ $component->product_id ]);
# 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;
# Duplicate default version in field_defaults.
Bugzilla->get_field('version')->update_default_values($component->id, $component->default_version);
Bugzilla->get_field(FIELD_NAME)->touch;
$dbh->bz_commit_transaction();
return $component;
}
sub run_create_validators
{
my $class = shift;
my $params = $class->SUPER::run_create_validators(@_);
my $product = delete $params->{product};
$params->{product_id} = $product->id;
$params->{name} = $class->_check_name($params->{name}, $product);
$params->{default_version} = $class->_check_default_version($params->{default_version}, $product);
return $params;
}
sub update
{
my $self = shift;
# Bugzilla::Field::Choice is not a threat as we don't have 'value' field
# Yet do not call its update() for the future
my $changes = Bugzilla::Object::update($self, @_);
# Fill visibility values
$self->set_visibility_values([ $self->product_id ]);
# 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;
}
# Duplicate default version in field_defaults.
Bugzilla->get_field('version')->update_default_values($self->id, $self->default_version);
Bugzilla->get_field(FIELD_NAME)->touch;
return $changes;
}
sub remove_from_db
{
my $self = shift;
my $dbh = Bugzilla->dbh;
$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);
# Remove visibility values
$self->set_visibility_values(undef);
Bugzilla->get_field(FIELD_NAME)->touch;
$dbh->bz_commit_transaction();
}
################################
# Validators
################################
sub _check_name
{
my ($invocant, $name, $product) = @_;
$name = trim($name);
$name || ThrowUserError('component_blank_name');
if (length($name) > MAX_COMPONENT_SIZE)
{
ThrowUserError('component_name_too_long', { name => $name });
}
$product = $invocant->product if (ref $invocant);
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->get_field('qa_contact')->enabled)
{
$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) = @_;
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 ];
}
# CustIS Bug 53725 - Версия по умолчанию
sub _check_default_version
{
my ($invocant, $version, $product) = @_;
$version = trim($version);
return undef unless $version;
$product = $invocant->product unless ref $product;
$version = Bugzilla::Version->check({ product => $product, name => $version });
return $version->id;
}
###############################
#### 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_is_active { $_[0]->set('isactive', $_[1]); }
sub set_wiki_url { $_[0]->set('wiki_url', $_[1]); }
sub set_name { $_[0]->set('name', $_[1]); }
sub set_description { $_[0]->set('description', $_[1]); }
sub set_default_version
{
my ($self, $version) = @_;
$self->set('default_version', $version);
# Reset the default version object
delete $self->{default_version_obj};
}
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(
'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(
'SELECT bug_id FROM bugs WHERE component_id = ?',
undef, $self->id
);
}
return $self->{bugs_ids};
}
sub default_assignee
{
my $self = shift;
if (!exists $self->{default_assignee})
{
$self->{default_assignee} = $self->{initialowner}
? new Bugzilla::User($self->{initialowner}) : undef;
}
return $self->{default_assignee};
}
sub default_qa_contact
{
my $self = shift;
if (!exists $self->{default_qa_contact})
{
$self->{default_qa_contact} = $self->{initialqacontact}
? new Bugzilla::User($self->{initialqacontact}) : undef;
}
return $self->{default_qa_contact};
}
sub flag_types
{
my $self = shift;
if (!defined $self->{flag_types})
{
my $flagtypes = Bugzilla::FlagType::match({
product_id => $self->product_id,
component_id => $self->id,
});
$self->{flag_types} = {};
$self->{flag_types}->{bug} = [ grep { $_->target_type eq 'bug' } @$flagtypes ];
$self->{flag_types}->{attachment} = [ grep { $_->target_type eq 'attachment' } @$flagtypes ];
foreach my $type (@{$self->{flag_types}->{bug}}, @{$self->{flag_types}->{attachment}})
{
# Build custom userlist for setting flag (for enter_bug.cgi)
my $cl = new Bugzilla::FlagType::UserList;
$cl->add(DefaultAssignee => $_) for $self->default_assignee || ();
$cl->add(CompQA => $_) for $self->default_qa_contact || ();
$cl->add(CC => @{ $self->initial_cc || [] });
$type->{custom_list} = $cl;
$type->{allow_other} = 1;
}
}
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 id { return $_[0]->{id}; }
sub name { return $_[0]->{name}; }
sub description { return $_[0]->{description}; }
sub wiki_url { return $_[0]->{wiki_url}; }
sub product_id { return $_[0]->{product_id}; }
sub is_active { return $_[0]->{isactive}; }
sub default_version { return $_[0]->{default_version}; }
sub default_version_obj
{
my $self = shift;
return $self->{default_version_obj} if $self->{default_version_obj} || !$self->{default_version};
my $version = Bugzilla::Version->new($self->{default_version});
return $self->{default_version_obj} = $version;
}
sub default_version_name
{
my $self = shift;
return $self->{default_version} && $self->default_version_obj->name;
}
###############################
#### 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