| # -*- 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> |
| |
| use strict; |
| |
| package Bugzilla::Product; |
| |
| use Bugzilla::Version; |
| use Bugzilla::Milestone; |
| |
| use Bugzilla::Constants; |
| use Bugzilla::Util; |
| use Bugzilla::Group; |
| use Bugzilla::Error; |
| |
| use Bugzilla::Install::Requirements; |
| |
| use base qw(Bugzilla::Object); |
| |
| use constant DEFAULT_CLASSIFICATION_ID => 1; |
| |
| ############################### |
| #### Initialization #### |
| ############################### |
| |
| use constant DB_TABLE => 'products'; |
| |
| use constant DB_COLUMNS => qw( |
| products.id |
| products.name |
| products.classification_id |
| products.description |
| products.milestoneurl |
| products.disallownew |
| products.votesperuser |
| products.maxvotesperbug |
| products.votestoconfirm |
| products.defaultmilestone |
| ); |
| |
| ############################### |
| #### Constructors ##### |
| ############################### |
| |
| # This is considerably faster than calling new_from_list three times |
| # for each product in the list, particularly with hundreds or thousands |
| # of products. |
| sub preload { |
| my ($products) = @_; |
| my %prods = map { $_->id => $_ } @$products; |
| my @prod_ids = keys %prods; |
| return unless @prod_ids; |
| |
| my $dbh = Bugzilla->dbh; |
| foreach my $field (qw(component version milestone)) { |
| my $classname = "Bugzilla::" . ucfirst($field); |
| my $objects = $classname->match({ product_id => \@prod_ids }); |
| |
| # Now populate the products with this set of objects. |
| foreach my $obj (@$objects) { |
| my $product_id = $obj->product_id; |
| $prods{$product_id}->{"${field}s"} ||= []; |
| push(@{$prods{$product_id}->{"${field}s"}}, $obj); |
| } |
| } |
| } |
| |
| ############################### |
| #### Methods #### |
| ############################### |
| |
| sub components { |
| my $self = shift; |
| my $dbh = Bugzilla->dbh; |
| |
| if (!defined $self->{components}) { |
| my $ids = $dbh->selectcol_arrayref(q{ |
| SELECT id FROM components |
| WHERE product_id = ? |
| ORDER BY name}, undef, $self->id); |
| |
| require Bugzilla::Component; |
| $self->{components} = Bugzilla::Component->new_from_list($ids); |
| } |
| return $self->{components}; |
| } |
| |
| sub group_controls { |
| my $self = shift; |
| my $dbh = Bugzilla->dbh; |
| |
| if (!defined $self->{group_controls}) { |
| my $query = qq{SELECT |
| groups.id, |
| group_control_map.entry, |
| group_control_map.membercontrol, |
| group_control_map.othercontrol, |
| group_control_map.canedit, |
| group_control_map.editcomponents, |
| group_control_map.editbugs, |
| group_control_map.canconfirm |
| FROM groups |
| LEFT JOIN group_control_map |
| ON groups.id = group_control_map.group_id |
| WHERE group_control_map.product_id = ? |
| AND groups.isbuggroup != 0 |
| ORDER BY groups.name}; |
| $self->{group_controls} = |
| $dbh->selectall_hashref($query, 'id', undef, $self->id); |
| |
| # For each group ID listed above, create and store its group object. |
| my @gids = keys %{$self->{group_controls}}; |
| my $groups = Bugzilla::Group->new_from_list(\@gids); |
| $self->{group_controls}->{$_->id}->{group} = $_ foreach @$groups; |
| } |
| return $self->{group_controls}; |
| } |
| |
| sub groups_mandatory_for { |
| my ($self, $user) = @_; |
| my $groups = $user->groups_as_string; |
| my $mandatory = CONTROLMAPMANDATORY; |
| # For membercontrol we don't check group_id IN, because if membercontrol |
| # is Mandatory, the group is Mandatory for everybody, regardless of their |
| # group membership. |
| my $ids = Bugzilla->dbh->selectcol_arrayref( |
| "SELECT group_id FROM group_control_map |
| WHERE product_id = ? |
| AND (membercontrol = $mandatory |
| OR (othercontrol = $mandatory |
| AND group_id NOT IN ($groups)))", |
| undef, $self->id); |
| return Bugzilla::Group->new_from_list($ids); |
| } |
| |
| sub groups_valid { |
| my ($self) = @_; |
| return $self->{groups_valid} if defined $self->{groups_valid}; |
| |
| # Note that we don't check OtherControl below, because there is no |
| # valid NA/* combination. |
| my $ids = Bugzilla->dbh->selectcol_arrayref( |
| "SELECT DISTINCT group_id |
| FROM group_control_map AS gcm |
| INNER JOIN groups ON gcm.group_id = groups.id |
| WHERE product_id = ? AND isbuggroup = 1 |
| AND membercontrol != " . CONTROLMAPNA, undef, $self->id); |
| $self->{groups_valid} = Bugzilla::Group->new_from_list($ids); |
| return $self->{groups_valid}; |
| } |
| |
| sub versions { |
| my $self = shift; |
| my $dbh = Bugzilla->dbh; |
| |
| if (!defined $self->{versions}) { |
| my $ids = $dbh->selectcol_arrayref(q{ |
| SELECT id FROM versions |
| WHERE product_id = ?}, undef, $self->id); |
| |
| $self->{versions} = Bugzilla::Version->new_from_list($ids); |
| } |
| return $self->{versions}; |
| } |
| |
| sub milestones { |
| my $self = shift; |
| my $dbh = Bugzilla->dbh; |
| |
| if (!defined $self->{milestones}) { |
| my $ids = $dbh->selectcol_arrayref(q{ |
| SELECT id FROM milestones |
| WHERE product_id = ?}, undef, $self->id); |
| |
| $self->{milestones} = Bugzilla::Milestone->new_from_list($ids); |
| } |
| return $self->{milestones}; |
| } |
| |
| sub bug_count { |
| my $self = shift; |
| my $dbh = Bugzilla->dbh; |
| |
| if (!defined $self->{'bug_count'}) { |
| $self->{'bug_count'} = $dbh->selectrow_array(qq{ |
| SELECT COUNT(bug_id) FROM bugs |
| WHERE product_id = ?}, undef, $self->id); |
| |
| } |
| return $self->{'bug_count'}; |
| } |
| |
| sub bug_ids { |
| my $self = shift; |
| my $dbh = Bugzilla->dbh; |
| |
| if (!defined $self->{'bug_ids'}) { |
| $self->{'bug_ids'} = |
| $dbh->selectcol_arrayref(q{SELECT bug_id FROM bugs |
| WHERE product_id = ?}, |
| undef, $self->id); |
| } |
| return $self->{'bug_ids'}; |
| } |
| |
| sub user_has_access { |
| my ($self, $user) = @_; |
| |
| return Bugzilla->dbh->selectrow_array( |
| 'SELECT CASE WHEN group_id IS NULL THEN 1 ELSE 0 END |
| FROM products LEFT JOIN group_control_map |
| ON group_control_map.product_id = products.id |
| AND group_control_map.entry != 0 |
| AND group_id NOT IN (' . $user->groups_as_string . ') |
| WHERE products.id = ? ' . Bugzilla->dbh->sql_limit(1), |
| undef, $self->id); |
| } |
| |
| sub flag_types { |
| my $self = shift; |
| |
| if (!defined $self->{'flag_types'}) { |
| $self->{'flag_types'} = {}; |
| foreach my $type ('bug', 'attachment') { |
| my %flagtypes; |
| foreach my $component (@{$self->components}) { |
| foreach my $flagtype (@{$component->flag_types->{$type}}) { |
| $flagtypes{$flagtype->{'id'}} ||= $flagtype; |
| } |
| } |
| $self->{'flag_types'}->{$type} = [sort { $a->{'sortkey'} <=> $b->{'sortkey'} |
| || $a->{'name'} cmp $b->{'name'} } values %flagtypes]; |
| } |
| } |
| return $self->{'flag_types'}; |
| } |
| |
| ############################### |
| #### Accessors ###### |
| ############################### |
| |
| sub description { return $_[0]->{'description'}; } |
| sub milestone_url { return $_[0]->{'milestoneurl'}; } |
| sub disallow_new { return $_[0]->{'disallownew'}; } |
| sub votes_per_user { return $_[0]->{'votesperuser'}; } |
| sub max_votes_per_bug { return $_[0]->{'maxvotesperbug'}; } |
| sub votes_to_confirm { return $_[0]->{'votestoconfirm'}; } |
| sub default_milestone { return $_[0]->{'defaultmilestone'}; } |
| sub classification_id { return $_[0]->{'classification_id'}; } |
| |
| ############################### |
| #### Subroutines ###### |
| ############################### |
| |
| sub check_product { |
| my ($product_name) = @_; |
| |
| unless ($product_name) { |
| ThrowUserError('product_not_specified'); |
| } |
| my $product = new Bugzilla::Product({name => $product_name}); |
| unless ($product) { |
| ThrowUserError('product_doesnt_exist', |
| {'product' => $product_name}); |
| } |
| return $product; |
| } |
| |
| 1; |
| |
| __END__ |
| |
| =head1 NAME |
| |
| Bugzilla::Product - Bugzilla product class. |
| |
| =head1 SYNOPSIS |
| |
| use Bugzilla::Product; |
| |
| my $product = new Bugzilla::Product(1); |
| my $product = new Bugzilla::Product({ name => 'AcmeProduct' }); |
| |
| my @components = $product->components(); |
| my $groups_controls = $product->group_controls(); |
| my @milestones = $product->milestones(); |
| my @versions = $product->versions(); |
| my $bugcount = $product->bug_count(); |
| my $bug_ids = $product->bug_ids(); |
| my $has_access = $product->user_has_access($user); |
| my $flag_types = $product->flag_types(); |
| |
| my $id = $product->id; |
| my $name = $product->name; |
| my $description = $product->description; |
| my $milestoneurl = $product->milestone_url; |
| my disallownew = $product->disallow_new; |
| my votesperuser = $product->votes_per_user; |
| my maxvotesperbug = $product->max_votes_per_bug; |
| my votestoconfirm = $product->votes_to_confirm; |
| my $defaultmilestone = $product->default_milestone; |
| my $classificationid = $product->classification_id; |
| |
| =head1 DESCRIPTION |
| |
| Product.pm represents a product object. It is an implementation |
| of L<Bugzilla::Object>, and thus provides all methods that |
| L<Bugzilla::Object> provides. |
| |
| The methods that are specific to C<Bugzilla::Product> are listed |
| below. |
| |
| =head1 METHODS |
| |
| =over |
| |
| =item C<components> |
| |
| Description: Returns an array of component objects belonging to |
| the product. |
| |
| Params: none. |
| |
| Returns: An array of Bugzilla::Component object. |
| |
| =item C<group_controls()> |
| |
| Description: Returns a hash (group id as key) with all product |
| group controls. |
| |
| Params: none. |
| |
| Returns: A hash with group id as key and hash containing |
| a Bugzilla::Group object and the properties of group |
| relative to the product. |
| |
| =item C<groups_mandatory_for> |
| |
| =over |
| |
| =item B<Description> |
| |
| Tells you what groups are mandatory for bugs in this product. |
| |
| =item B<Params> |
| |
| C<$user> - The user who you want to check. |
| |
| =item B<Returns> An arrayref of C<Bugzilla::Group> objects. |
| |
| =back |
| |
| =item C<groups_valid> |
| |
| =over |
| |
| =item B<Description> |
| |
| Returns an arrayref of L<Bugzilla::Group> objects, representing groups |
| that bugs could validly be restricted to within this product. Used mostly |
| by L<Bugzilla::Bug> to assure that you're adding valid groups to a bug. |
| |
| B<Note>: This doesn't check whether or not the current user can add/remove |
| bugs to/from these groups. It just tells you that bugs I<could be in> these |
| groups, in this product. |
| |
| =item B<Params> (none) |
| |
| =item B<Returns> An arrayref of L<Bugzilla::Group> objects. |
| |
| =back |
| |
| =item C<versions> |
| |
| Description: Returns all valid versions for that product. |
| |
| Params: none. |
| |
| Returns: An array of Bugzilla::Version objects. |
| |
| =item C<milestones> |
| |
| Description: Returns all valid milestones for that product. |
| |
| Params: none. |
| |
| Returns: An array of Bugzilla::Milestone objects. |
| |
| =item C<bug_count()> |
| |
| Description: Returns the total of bugs that belong to the product. |
| |
| Params: none. |
| |
| Returns: Integer with the number of bugs. |
| |
| =item C<bug_ids()> |
| |
| Description: Returns the IDs of bugs that belong to the product. |
| |
| Params: none. |
| |
| Returns: An array of integer. |
| |
| =item C<user_has_access()> |
| |
| Description: Tells you whether or not the user is allowed to enter |
| bugs into this product, based on the C<entry> group |
| control. To see whether or not a user can actually |
| enter a bug into a product, use C<$user->can_enter_product>. |
| |
| Params: C<$user> - A Bugzilla::User object. |
| |
| Returns C<1> If this user's groups allow him C<entry> access to |
| this Product, C<0> otherwise. |
| |
| =item C<flag_types()> |
| |
| Description: Returns flag types available for at least one of |
| its components. |
| |
| Params: none. |
| |
| Returns: Two references to an array of flagtype objects. |
| |
| =back |
| |
| =head1 SUBROUTINES |
| |
| =over |
| |
| =item C<preload> |
| |
| When passed an arrayref of C<Bugzilla::Product> objects, preloads their |
| L</milestones>, L</components>, and L</versions>, which is much faster |
| than calling those accessors on every item in the array individually. |
| |
| This function is not exported, so must be called like |
| C<Bugzilla::Product::preload($products)>. |
| |
| =item C<check_product($product_name)> |
| |
| Description: Checks if the product name was passed in and if is a valid |
| product. |
| |
| Params: $product_name - String with a product name. |
| |
| Returns: Bugzilla::Product object. |
| |
| =back |
| |
| =head1 SEE ALSO |
| |
| L<Bugzilla::Object> |
| |
| =cut |