123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648 |
- # -*- 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>
- use strict;
- package Bugzilla::Component;
- use base qw(Bugzilla::Object);
- use Bugzilla::Constants;
- use Bugzilla::Util;
- use Bugzilla::Error;
- use Bugzilla::User;
- use Bugzilla::FlagType;
- use Bugzilla::Series;
- ###############################
- #### Initialization ####
- ###############################
- use constant DB_TABLE => 'components';
- use constant DB_COLUMNS => qw(
- id
- name
- product_id
- initialowner
- initialqacontact
- description
- );
- use constant REQUIRED_CREATE_FIELDS => qw(
- name
- product
- initialowner
- description
- );
- use constant UPDATE_COLUMNS => qw(
- name
- initialowner
- initialqacontact
- description
- );
- use constant VALIDATORS => {
- product => \&_check_product,
- initialowner => \&_check_initialowner,
- initialqacontact => \&_check_initialqacontact,
- description => \&_check_description,
- initial_cc => \&_check_cc_list,
- };
- use constant UPDATE_VALIDATORS => {
- name => \&_check_name,
- };
- ###############################
- 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 $component = $class->insert_create_data($params);
- # We still have to fill the component_cc table.
- $component->_update_cc_list($cc_list);
- # Create series for the new component.
- $component->_create_series();
- $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);
- return $params;
- }
- 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;
- $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, $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->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) = @_;
- 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 id { return $_[0]->{'id'}; }
- sub name { return $_[0]->{'name'}; }
- sub description { return $_[0]->{'description'}; }
- sub product_id { return $_[0]->{'product_id'}; }
- ###############################
- #### 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
|