2008-12-15 15:53:33 +03:00
|
|
|
# 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>
|
2009-07-29 15:21:49 +04:00
|
|
|
# Frédéric Buclin <LpSolit@gmail.com>
|
2008-12-15 15:53:33 +03:00
|
|
|
|
|
|
|
use strict;
|
|
|
|
|
|
|
|
package Bugzilla::Product;
|
|
|
|
|
|
|
|
use Bugzilla::Constants;
|
|
|
|
use Bugzilla::Util;
|
2010-10-05 21:40:17 +04:00
|
|
|
use Bugzilla::User;
|
2008-12-15 15:53:33 +03:00
|
|
|
use Bugzilla::Error;
|
2009-07-29 15:21:49 +04:00
|
|
|
use Bugzilla::Group;
|
|
|
|
use Bugzilla::Version;
|
|
|
|
use Bugzilla::Milestone;
|
|
|
|
use Bugzilla::Field;
|
|
|
|
use Bugzilla::Status;
|
2008-12-15 15:53:33 +03:00
|
|
|
use Bugzilla::Install::Requirements;
|
2009-07-29 15:21:49 +04:00
|
|
|
use Bugzilla::Mailer;
|
|
|
|
use Bugzilla::Series;
|
2009-12-18 20:14:04 +03:00
|
|
|
use Bugzilla::FlagType::UserList;
|
2010-05-15 00:02:34 +04:00
|
|
|
use Bugzilla::Hook;
|
2008-12-15 15:53:33 +03:00
|
|
|
|
2009-07-29 15:21:49 +04:00
|
|
|
use base qw(Bugzilla::Field::Choice);
|
2008-12-15 15:53:33 +03:00
|
|
|
|
|
|
|
use constant DEFAULT_CLASSIFICATION_ID => 1;
|
|
|
|
|
|
|
|
###############################
|
|
|
|
#### Initialization ####
|
|
|
|
###############################
|
|
|
|
|
|
|
|
use constant DB_TABLE => 'products';
|
2010-11-11 16:36:23 +03:00
|
|
|
use constant FIELD_NAME => 'product';
|
2009-07-29 15:21:49 +04:00
|
|
|
# Reset these back to the Bugzilla::Object defaults, instead of the
|
|
|
|
# Bugzilla::Field::Choice defaults.
|
|
|
|
use constant NAME_FIELD => 'name';
|
|
|
|
use constant LIST_ORDER => 'name';
|
2008-12-15 15:53:33 +03:00
|
|
|
|
|
|
|
use constant DB_COLUMNS => qw(
|
2010-10-22 21:38:12 +04:00
|
|
|
id
|
|
|
|
name
|
|
|
|
wiki_url
|
|
|
|
notimetracking
|
|
|
|
extproduct
|
|
|
|
classification_id
|
|
|
|
description
|
|
|
|
isactive
|
|
|
|
votesperuser
|
|
|
|
maxvotesperbug
|
|
|
|
votestoconfirm
|
|
|
|
allows_unconfirmed
|
2013-12-12 17:55:18 +04:00
|
|
|
cc_group
|
2014-08-20 18:13:13 +04:00
|
|
|
entryheaderhtml
|
2009-07-29 15:21:49 +04:00
|
|
|
);
|
|
|
|
|
|
|
|
use constant REQUIRED_CREATE_FIELDS => qw(
|
|
|
|
name
|
|
|
|
description
|
|
|
|
version
|
2008-12-15 15:53:33 +03:00
|
|
|
);
|
|
|
|
|
2014-08-20 18:13:13 +04:00
|
|
|
# Allow to update every valid DB column
|
|
|
|
*UPDATE_COLUMNS = *DB_COLUMNS;
|
2009-07-29 15:21:49 +04:00
|
|
|
|
|
|
|
use constant VALIDATORS => {
|
2010-05-15 00:02:34 +04:00
|
|
|
allows_unconfirmed => \&Bugzilla::Object::check_boolean,
|
2012-02-17 16:13:44 +04:00
|
|
|
classification_id => \&_check_classification_id,
|
2009-07-29 15:21:49 +04:00
|
|
|
name => \&_check_name,
|
|
|
|
description => \&_check_description,
|
|
|
|
version => \&_check_version,
|
2010-05-15 00:02:34 +04:00
|
|
|
isactive => \&Bugzilla::Object::check_boolean,
|
2009-07-29 15:21:49 +04:00
|
|
|
votesperuser => \&_check_votes_per_user,
|
|
|
|
maxvotesperbug => \&_check_votes_per_bug,
|
|
|
|
votestoconfirm => \&_check_votes_to_confirm,
|
2010-05-12 18:11:37 +04:00
|
|
|
notimetracking => \&Bugzilla::Object::check_boolean,
|
2010-10-22 21:38:12 +04:00
|
|
|
extproduct => \&_check_extproduct,
|
2014-07-31 15:00:05 +04:00
|
|
|
# CustIS Bug 38616 - CC list restriction
|
|
|
|
# FIXME: Maybe also disallow bug access for these users?
|
2013-12-12 17:55:18 +04:00
|
|
|
cc_group => \&_check_cc_group,
|
2009-07-29 15:21:49 +04:00
|
|
|
};
|
|
|
|
|
2008-12-15 15:53:33 +03:00
|
|
|
###############################
|
|
|
|
#### Constructors #####
|
|
|
|
###############################
|
|
|
|
|
2010-11-10 21:21:19 +03:00
|
|
|
sub create
|
|
|
|
{
|
2009-07-29 15:21:49 +04:00
|
|
|
my $class = shift;
|
2014-03-28 16:39:40 +04:00
|
|
|
my ($params) = @_;
|
2009-07-29 15:21:49 +04:00
|
|
|
|
2014-03-28 16:39:40 +04:00
|
|
|
my $dbh = Bugzilla->dbh;
|
2009-07-29 15:21:49 +04:00
|
|
|
$dbh->bz_start_transaction();
|
|
|
|
|
2014-03-28 16:39:40 +04:00
|
|
|
$class->check_required_create_fields($params);
|
2009-07-29 15:21:49 +04:00
|
|
|
|
|
|
|
# Some fields do not exist in the DB as is.
|
2014-03-21 17:19:31 +04:00
|
|
|
if (defined $params->{classification})
|
|
|
|
{
|
2014-03-28 16:39:40 +04:00
|
|
|
$params->{classification_id} = delete $params->{classification};
|
2010-05-15 00:02:34 +04:00
|
|
|
}
|
2009-07-29 15:21:49 +04:00
|
|
|
my $version = delete $params->{version};
|
|
|
|
|
2012-03-05 18:03:35 +04:00
|
|
|
my $field_values = $class->run_create_validators($params);
|
|
|
|
my $product = $class->insert_create_data($field_values);
|
2010-05-15 00:02:34 +04:00
|
|
|
Bugzilla->user->clear_product_cache();
|
2009-07-29 15:21:49 +04:00
|
|
|
|
2014-03-21 17:19:31 +04:00
|
|
|
# Add the new version into the DB as valid values.
|
2014-06-20 18:28:01 +04:00
|
|
|
if ($version)
|
|
|
|
{
|
|
|
|
Bugzilla::Version->create({
|
|
|
|
name => $version,
|
|
|
|
product => $product,
|
|
|
|
});
|
|
|
|
}
|
2010-11-10 21:21:19 +03:00
|
|
|
|
|
|
|
# Fill visibility values
|
|
|
|
$product->set_visibility_values([ $product->classification_id ]);
|
2009-07-29 15:21:49 +04:00
|
|
|
|
2010-05-15 00:02:34 +04:00
|
|
|
Bugzilla::Hook::process('product_end_of_create', { product => $product });
|
|
|
|
|
2011-02-16 20:02:17 +03:00
|
|
|
Bugzilla->get_field(FIELD_NAME)->touch;
|
|
|
|
|
2009-07-29 15:21:49 +04:00
|
|
|
$dbh->bz_commit_transaction();
|
|
|
|
return $product;
|
|
|
|
}
|
|
|
|
|
2008-12-15 15:53:33 +03:00
|
|
|
# This is considerably faster than calling new_from_list three times
|
|
|
|
# for each product in the list, particularly with hundreds or thousands
|
|
|
|
# of products.
|
2014-04-15 18:09:43 +04:00
|
|
|
sub preload
|
|
|
|
{
|
2008-12-15 15:53:33 +03:00
|
|
|
my ($products) = @_;
|
|
|
|
my %prods = map { $_->id => $_ } @$products;
|
|
|
|
my @prod_ids = keys %prods;
|
|
|
|
return unless @prod_ids;
|
|
|
|
|
|
|
|
my $dbh = Bugzilla->dbh;
|
2014-04-15 18:09:43 +04:00
|
|
|
foreach my $field (qw(component version milestone))
|
|
|
|
{
|
2008-12-15 15:53:33 +03:00
|
|
|
my $classname = "Bugzilla::" . ucfirst($field);
|
|
|
|
my $objects = $classname->match({ product_id => \@prod_ids });
|
|
|
|
|
|
|
|
# Now populate the products with this set of objects.
|
2014-04-15 18:09:43 +04:00
|
|
|
foreach my $obj (@$objects)
|
|
|
|
{
|
2008-12-15 15:53:33 +03:00
|
|
|
my $product_id = $obj->product_id;
|
|
|
|
$prods{$product_id}->{"${field}s"} ||= [];
|
|
|
|
push(@{$prods{$product_id}->{"${field}s"}}, $obj);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-04-15 18:09:43 +04:00
|
|
|
sub update
|
|
|
|
{
|
2009-07-29 15:21:49 +04:00
|
|
|
my $self = shift;
|
|
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
|
|
|
|
# Don't update the DB if something goes wrong below -> transaction.
|
|
|
|
$dbh->bz_start_transaction();
|
2010-11-16 18:12:08 +03:00
|
|
|
# Bugzilla::Field::Choice is not a threat as we don't have 'value' field
|
|
|
|
# Yet do not call its update() for the future
|
2010-11-17 16:41:00 +03:00
|
|
|
my ($changes, $old_self) = Bugzilla::Object::update($self, @_);
|
2009-07-29 15:21:49 +04:00
|
|
|
|
2014-04-15 18:09:43 +04:00
|
|
|
# FIXME when renaming a product, try to rename it in all named queries
|
|
|
|
|
2014-07-28 12:57:30 +04:00
|
|
|
# We also have to fix votes.
|
2014-04-15 18:09:43 +04:00
|
|
|
if ($changes->{maxvotesperbug} || $changes->{votesperuser} || $changes->{votestoconfirm})
|
|
|
|
{
|
2009-07-29 15:21:49 +04:00
|
|
|
# We cannot |use| these modules, due to dependency loops.
|
|
|
|
require Bugzilla::Bug;
|
|
|
|
require Bugzilla::User;
|
|
|
|
|
|
|
|
# 1. too many votes for a single user on a single bug.
|
|
|
|
my @toomanyvotes_list = ();
|
2014-04-15 18:09:43 +04:00
|
|
|
if ($self->max_votes_per_bug < $self->votes_per_user)
|
|
|
|
{
|
2009-07-29 15:21:49 +04:00
|
|
|
my $votes = $dbh->selectall_arrayref(
|
2014-08-28 20:44:09 +04:00
|
|
|
'SELECT votes.who, votes.bug_id, profiles.login_name FROM votes, bugs, profiles'.
|
|
|
|
' WHERE bugs.product_id = ? AND votes.vote_count > ?'.
|
|
|
|
' AND bugs.bug_id=votes.bug_id AND profiles.id=votes.who',
|
|
|
|
undef, $self->id, $self->max_votes_per_bug
|
2014-04-15 18:09:43 +04:00
|
|
|
);
|
|
|
|
foreach my $vote (@$votes)
|
|
|
|
{
|
2014-08-28 20:44:09 +04:00
|
|
|
my ($who, $id, $name) = (@$vote);
|
2014-07-28 12:57:30 +04:00
|
|
|
Bugzilla::Bug::RemoveVotes($id, $who, 'votes_too_many_per_bug');
|
|
|
|
push @toomanyvotes_list, { id => $id, name => $name };
|
2009-07-29 15:21:49 +04:00
|
|
|
}
|
|
|
|
}
|
2014-04-15 18:09:43 +04:00
|
|
|
$changes->{too_many_votes} = \@toomanyvotes_list;
|
2009-07-29 15:21:49 +04:00
|
|
|
|
|
|
|
# 2. too many total votes for a single user.
|
|
|
|
# This part doesn't work in the general case because RemoveVotes
|
|
|
|
# doesn't enforce votesperuser (except per-bug when it's less
|
|
|
|
# than maxvotesperbug). See Bugzilla::Bug::RemoveVotes().
|
|
|
|
|
|
|
|
my $votes = $dbh->selectall_arrayref(
|
2014-08-28 20:44:09 +04:00
|
|
|
'SELECT votes.who, votes.vote_count, profiles.login_name FROM votes, bugs, profiles'.
|
|
|
|
' WHERE bugs.product_id = ? AND bugs.bug_id = votes.bug_id AND profiles.userid=votes.who', undef, $self->id
|
2014-04-15 18:09:43 +04:00
|
|
|
);
|
2009-07-29 15:21:49 +04:00
|
|
|
|
|
|
|
my %counts;
|
2014-04-15 18:09:43 +04:00
|
|
|
foreach my $vote (@$votes)
|
|
|
|
{
|
2014-08-28 20:44:09 +04:00
|
|
|
my ($who, $count, $name) = @$vote;
|
2014-04-15 18:09:43 +04:00
|
|
|
if (!defined $counts{$who})
|
|
|
|
{
|
2014-08-28 20:44:09 +04:00
|
|
|
$counts{$who} = [ $count, $name ];
|
2014-04-15 18:09:43 +04:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2014-08-28 20:44:09 +04:00
|
|
|
$counts{$who}[0] += $count;
|
2009-07-29 15:21:49 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
my @toomanytotalvotes_list = ();
|
2014-04-15 18:09:43 +04:00
|
|
|
foreach my $who (keys(%counts))
|
|
|
|
{
|
2014-08-28 20:44:09 +04:00
|
|
|
if ($counts{$who}[0] > $self->votes_per_user)
|
2014-04-15 18:09:43 +04:00
|
|
|
{
|
2014-08-28 20:44:09 +04:00
|
|
|
my $name = $counts{$who}[1];
|
2009-07-29 15:21:49 +04:00
|
|
|
my $bug_ids = $dbh->selectcol_arrayref(
|
2014-08-28 20:44:09 +04:00
|
|
|
'SELECT votes.bug_id FROM votes, bugs'.
|
2014-04-15 18:09:43 +04:00
|
|
|
' WHERE bugs.product_id = ? AND votes.who = ?',
|
|
|
|
undef, ($self->id, $who)
|
|
|
|
);
|
|
|
|
foreach my $bug_id (@$bug_ids)
|
|
|
|
{
|
2014-07-28 12:57:30 +04:00
|
|
|
Bugzilla::Bug::RemoveVotes($bug_id, $who, 'votes_too_many_per_user');
|
2014-04-15 18:09:43 +04:00
|
|
|
push @toomanytotalvotes_list, {id => $bug_id, name => $name};
|
2009-07-29 15:21:49 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-04-15 18:09:43 +04:00
|
|
|
$changes->{too_many_total_votes} = \@toomanytotalvotes_list;
|
2009-07-29 15:21:49 +04:00
|
|
|
|
|
|
|
# 3. enough votes to confirm
|
2014-04-15 18:09:43 +04:00
|
|
|
my $bug_list = $dbh->selectcol_arrayref(
|
|
|
|
'SELECT bug_id FROM bugs, bug_status WHERE product_id = ?'.
|
|
|
|
' AND bugs.bug_status = bug_status.id AND NOT bug_status.is_confirmed AND votes >= ?',
|
|
|
|
undef, ($self->id, $self->votes_to_confirm)
|
|
|
|
);
|
2009-07-29 15:21:49 +04:00
|
|
|
|
|
|
|
my @updated_bugs = ();
|
2014-04-15 18:09:43 +04:00
|
|
|
foreach my $bug_id (@$bug_list)
|
|
|
|
{
|
2014-07-28 12:57:30 +04:00
|
|
|
my $bug = Bugzilla::Bug->new($bug_id);
|
|
|
|
if ($bug->check_if_voted_confirmed)
|
|
|
|
{
|
|
|
|
$bug->update;
|
|
|
|
push @updated_bugs, $bug_id;
|
|
|
|
}
|
2009-07-29 15:21:49 +04:00
|
|
|
}
|
2014-04-15 18:09:43 +04:00
|
|
|
$changes->{confirmed_bugs} = \@updated_bugs;
|
2009-07-29 15:21:49 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
# Also update group settings.
|
2014-04-15 18:09:43 +04:00
|
|
|
if ($self->{check_group_controls})
|
|
|
|
{
|
2009-07-29 15:21:49 +04:00
|
|
|
require Bugzilla::Bug;
|
|
|
|
|
|
|
|
my $old_settings = $old_self->group_controls;
|
|
|
|
my $new_settings = $self->group_controls;
|
|
|
|
my $timestamp = $dbh->selectrow_array('SELECT NOW()');
|
|
|
|
|
2014-04-15 18:09:43 +04:00
|
|
|
foreach my $gid (keys %$new_settings)
|
|
|
|
{
|
2009-07-29 15:21:49 +04:00
|
|
|
my $old_setting = $old_settings->{$gid} || {};
|
|
|
|
my $new_setting = $new_settings->{$gid};
|
|
|
|
# If all new settings are 0 for a given group, we delete the entry
|
|
|
|
# from group_control_map, so we have to track it here.
|
|
|
|
my $all_zero = 1;
|
|
|
|
my @fields;
|
|
|
|
my @values;
|
|
|
|
|
|
|
|
foreach my $field ('entry', 'membercontrol', 'othercontrol', 'canedit',
|
2014-04-15 18:09:43 +04:00
|
|
|
'editcomponents', 'editbugs', 'canconfirm')
|
2009-07-29 15:21:49 +04:00
|
|
|
{
|
|
|
|
my $old_value = $old_setting->{$field};
|
|
|
|
my $new_value = $new_setting->{$field};
|
|
|
|
$all_zero = 0 if $new_value;
|
|
|
|
next if (defined $old_value && $old_value == $new_value);
|
|
|
|
push(@fields, $field);
|
|
|
|
# The value has already been validated.
|
|
|
|
detaint_natural($new_value);
|
|
|
|
push(@values, $new_value);
|
|
|
|
}
|
|
|
|
|
2012-07-09 16:13:45 +04:00
|
|
|
# Is there anything to update?
|
|
|
|
if (@fields)
|
|
|
|
{
|
2014-04-15 18:09:43 +04:00
|
|
|
if ($all_zero)
|
|
|
|
{
|
|
|
|
$dbh->do(
|
|
|
|
'DELETE FROM group_control_map WHERE product_id = ? AND group_id = ?',
|
|
|
|
undef, $self->id, $gid
|
|
|
|
);
|
2009-07-29 15:21:49 +04:00
|
|
|
}
|
2014-04-15 18:09:43 +04:00
|
|
|
else
|
|
|
|
{
|
|
|
|
if (exists $old_setting->{group})
|
|
|
|
{
|
2012-07-09 16:13:45 +04:00
|
|
|
# There is already an entry in the DB.
|
|
|
|
my $set_fields = join(', ', map {"$_ = ?"} @fields);
|
2014-04-15 18:09:43 +04:00
|
|
|
$dbh->do(
|
|
|
|
"UPDATE group_control_map SET $set_fields".
|
|
|
|
" WHERE product_id = ? AND group_id = ?",
|
|
|
|
undef, (@values, $self->id, $gid)
|
|
|
|
);
|
2012-07-09 16:13:45 +04:00
|
|
|
}
|
2014-04-15 18:09:43 +04:00
|
|
|
else
|
|
|
|
{
|
2012-07-09 16:13:45 +04:00
|
|
|
# No entry yet.
|
|
|
|
my $fields = join(', ', @fields);
|
|
|
|
# +2 because of the product and group IDs.
|
|
|
|
my $qmarks = join(',', ('?') x (scalar @fields + 2));
|
2014-04-15 18:09:43 +04:00
|
|
|
$dbh->do(
|
|
|
|
"INSERT INTO group_control_map (product_id, group_id, $fields) VALUES ($qmarks)",
|
|
|
|
undef, ($self->id, $gid, @values)
|
|
|
|
);
|
2012-07-09 16:13:45 +04:00
|
|
|
}
|
2009-07-29 15:21:49 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# If the group is mandatory, restrict all bugs to it.
|
2014-04-15 18:09:43 +04:00
|
|
|
if ($new_setting->{membercontrol} == CONTROLMAPMANDATORY)
|
|
|
|
{
|
|
|
|
my $bug_ids = $dbh->selectcol_arrayref(
|
|
|
|
'SELECT bugs.bug_id FROM bugs'.
|
|
|
|
' LEFT JOIN bug_group_map ON bug_group_map.bug_id = bugs.bug_id AND group_id = ?'.
|
|
|
|
' WHERE product_id = ? AND bug_group_map.bug_id IS NULL',
|
|
|
|
undef, $gid, $self->id
|
|
|
|
);
|
|
|
|
|
|
|
|
if (scalar @$bug_ids)
|
|
|
|
{
|
|
|
|
my $sth = $dbh->prepare('INSERT INTO bug_group_map (bug_id, group_id) VALUES (?, ?)');
|
|
|
|
|
|
|
|
foreach my $bug_id (@$bug_ids)
|
|
|
|
{
|
2009-07-29 15:21:49 +04:00
|
|
|
$sth->execute($bug_id, $gid);
|
|
|
|
# Add this change to the bug history.
|
2014-05-06 18:45:29 +04:00
|
|
|
Bugzilla::Bug::LogActivityEntry(
|
2014-04-15 18:09:43 +04:00
|
|
|
$bug_id, 'bug_group', '', $new_setting->{group}->name,
|
|
|
|
Bugzilla->user->id, $timestamp
|
|
|
|
);
|
2009-07-29 15:21:49 +04:00
|
|
|
}
|
2014-04-15 18:09:43 +04:00
|
|
|
push @{$changes->{group_controls}->{now_mandatory}}, {
|
|
|
|
name => $new_setting->{group}->name,
|
|
|
|
bug_count => scalar @$bug_ids,
|
|
|
|
};
|
2009-07-29 15:21:49 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
# If the group can no longer be used to restrict bugs, remove them.
|
2014-04-15 18:09:43 +04:00
|
|
|
elsif ($new_setting->{membercontrol} == CONTROLMAPNA)
|
|
|
|
{
|
|
|
|
my $bug_ids = $dbh->selectcol_arrayref(
|
|
|
|
'SELECT bugs.bug_id FROM bugs'.
|
|
|
|
' INNER JOIN bug_group_map ON bug_group_map.bug_id = bugs.bug_id'.
|
|
|
|
' WHERE product_id = ? AND group_id = ?', undef, $self->id, $gid
|
|
|
|
);
|
|
|
|
|
|
|
|
if (scalar @$bug_ids)
|
|
|
|
{
|
|
|
|
$dbh->do(
|
|
|
|
'DELETE FROM bug_group_map WHERE group_id = ? AND ' .
|
|
|
|
$dbh->sql_in('bug_id', $bug_ids), undef, $gid
|
|
|
|
);
|
2009-07-29 15:21:49 +04:00
|
|
|
|
|
|
|
# Add this change to the bug history.
|
2014-04-15 18:09:43 +04:00
|
|
|
foreach my $bug_id (@$bug_ids)
|
|
|
|
{
|
2014-05-06 18:45:29 +04:00
|
|
|
Bugzilla::Bug::LogActivityEntry(
|
2014-04-15 18:09:43 +04:00
|
|
|
$bug_id, 'bug_group', $old_setting->{group}->name, '',
|
|
|
|
Bugzilla->user->id, $timestamp
|
|
|
|
);
|
2009-07-29 15:21:49 +04:00
|
|
|
}
|
2014-04-15 18:09:43 +04:00
|
|
|
push @{$changes->{group_controls}->{now_na}}, {
|
|
|
|
name => $old_setting->{group}->name,
|
|
|
|
bug_count => scalar @$bug_ids
|
|
|
|
};
|
2009-07-29 15:21:49 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2010-11-10 21:21:19 +03:00
|
|
|
|
|
|
|
# Fill visibility values
|
|
|
|
$self->set_visibility_values([ $self->classification_id ]);
|
|
|
|
|
2009-07-29 15:21:49 +04:00
|
|
|
$dbh->bz_commit_transaction();
|
|
|
|
# Changes have been committed.
|
|
|
|
delete $self->{check_group_controls};
|
2010-05-15 00:02:34 +04:00
|
|
|
Bugzilla->user->clear_product_cache();
|
2009-07-29 15:21:49 +04:00
|
|
|
|
|
|
|
# Now that changes have been committed, we can send emails to voters.
|
2014-07-28 12:57:30 +04:00
|
|
|
Bugzilla->send_mail;
|
2010-05-15 00:02:34 +04:00
|
|
|
|
2011-02-16 20:02:17 +03:00
|
|
|
Bugzilla->get_field(FIELD_NAME)->touch;
|
|
|
|
|
2009-07-29 15:21:49 +04:00
|
|
|
return $changes;
|
|
|
|
}
|
|
|
|
|
2014-04-15 18:09:43 +04:00
|
|
|
sub remove_from_db
|
|
|
|
{
|
2010-05-15 00:02:34 +04:00
|
|
|
my ($self, $params) = @_;
|
2009-07-29 15:21:49 +04:00
|
|
|
my $user = Bugzilla->user;
|
|
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
|
|
|
|
$dbh->bz_start_transaction();
|
|
|
|
|
2014-04-15 18:09:43 +04:00
|
|
|
if ($self->bug_count)
|
|
|
|
{
|
|
|
|
if (Bugzilla->params->{allowbugdeletion})
|
|
|
|
{
|
2009-07-29 15:21:49 +04:00
|
|
|
require Bugzilla::Bug;
|
2014-04-15 18:09:43 +04:00
|
|
|
foreach my $bug_id (@{$self->bug_ids})
|
|
|
|
{
|
2009-07-29 15:21:49 +04:00
|
|
|
# Note that we allow the user to delete bugs he can't see,
|
|
|
|
# which is okay, because he's deleting the whole Product.
|
|
|
|
my $bug = new Bugzilla::Bug($bug_id);
|
|
|
|
$bug->remove_from_db();
|
|
|
|
}
|
|
|
|
}
|
2014-04-15 18:09:43 +04:00
|
|
|
else
|
|
|
|
{
|
2009-07-29 15:21:49 +04:00
|
|
|
ThrowUserError('product_has_bugs', { nb => $self->bug_count });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-04-15 18:09:43 +04:00
|
|
|
if ($params->{delete_series})
|
|
|
|
{
|
|
|
|
my $series_ids = $dbh->selectcol_arrayref(
|
|
|
|
'SELECT series_id FROM series'.
|
|
|
|
' INNER JOIN series_categories ON series_categories.id = series.category'.
|
|
|
|
' WHERE series_categories.name = ?',
|
|
|
|
undef, $self->name
|
|
|
|
);
|
2010-05-15 00:02:34 +04:00
|
|
|
|
2014-04-15 18:09:43 +04:00
|
|
|
if (scalar @$series_ids)
|
|
|
|
{
|
2010-05-15 00:02:34 +04:00
|
|
|
$dbh->do('DELETE FROM series WHERE ' . $dbh->sql_in('series_id', $series_ids));
|
|
|
|
}
|
|
|
|
|
|
|
|
# If no subcategory uses this product name, completely purge it.
|
2014-04-15 18:09:43 +04:00
|
|
|
my $in_use = $dbh->selectrow_array(
|
|
|
|
'SELECT 1 FROM series'.
|
|
|
|
' INNER JOIN series_categories ON series_categories.id = series.subcategory'.
|
|
|
|
' WHERE series_categories.name = ? ' . $dbh->sql_limit(1),
|
|
|
|
undef, $self->name
|
|
|
|
);
|
|
|
|
if (!$in_use)
|
|
|
|
{
|
2010-05-15 00:02:34 +04:00
|
|
|
$dbh->do('DELETE FROM series_categories WHERE name = ?', undef, $self->name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-14 17:12:51 +04:00
|
|
|
$self->SUPER::remove_from_db();
|
2011-02-16 20:02:17 +03:00
|
|
|
|
2009-07-29 15:21:49 +04:00
|
|
|
$dbh->bz_commit_transaction();
|
|
|
|
|
|
|
|
# We have to delete these internal variables, else we get
|
|
|
|
# the old lists of products and classifications again.
|
|
|
|
delete $user->{selectable_products};
|
|
|
|
delete $user->{selectable_classifications};
|
|
|
|
}
|
|
|
|
|
|
|
|
###############################
|
|
|
|
#### Validators ####
|
|
|
|
###############################
|
|
|
|
|
2010-10-22 21:38:12 +04:00
|
|
|
sub _check_extproduct
|
|
|
|
{
|
|
|
|
my ($invocant, $product) = @_;
|
|
|
|
$product = $product ? Bugzilla::Product->check({ id => $product }) : undef;
|
|
|
|
return $product ? $product->id : undef;
|
|
|
|
}
|
|
|
|
|
2013-12-12 17:55:18 +04:00
|
|
|
sub _check_cc_group
|
|
|
|
{
|
|
|
|
my ($invocant, $cc_group) = @_;
|
2014-01-31 15:42:19 +04:00
|
|
|
$cc_group = trim($cc_group);
|
2014-07-31 15:00:05 +04:00
|
|
|
$cc_group = $cc_group ? Bugzilla::Group->check({ name => $cc_group })->id : undef;
|
|
|
|
return $cc_group;
|
2013-12-12 17:55:18 +04:00
|
|
|
}
|
|
|
|
|
2014-04-15 18:09:43 +04:00
|
|
|
sub _check_classification_id
|
|
|
|
{
|
2009-07-29 15:21:49 +04:00
|
|
|
my ($invocant, $classification_name) = @_;
|
|
|
|
|
|
|
|
my $classification_id = 1;
|
2014-08-01 21:13:45 +04:00
|
|
|
if (Bugzilla->get_field('classification')->enabled)
|
2014-04-15 18:09:43 +04:00
|
|
|
{
|
|
|
|
my $classification = ref $classification_name
|
|
|
|
? $classification_name
|
2012-02-17 16:13:44 +04:00
|
|
|
: Bugzilla::Classification->check($classification_name);
|
2009-07-29 15:21:49 +04:00
|
|
|
$classification_id = $classification->id;
|
|
|
|
}
|
2014-06-25 18:59:31 +04:00
|
|
|
delete $invocant->{classification_obj} if ref $invocant;
|
2009-07-29 15:21:49 +04:00
|
|
|
return $classification_id;
|
|
|
|
}
|
|
|
|
|
2014-04-15 18:09:43 +04:00
|
|
|
sub _check_name
|
|
|
|
{
|
2009-07-29 15:21:49 +04:00
|
|
|
my ($invocant, $name) = @_;
|
|
|
|
|
|
|
|
$name = trim($name);
|
|
|
|
$name || ThrowUserError('product_blank_name');
|
|
|
|
|
2014-08-14 19:30:42 +04:00
|
|
|
if (length($name) > MAX_FIELD_VALUE_SIZE)
|
2014-04-15 18:09:43 +04:00
|
|
|
{
|
|
|
|
ThrowUserError('product_name_too_long', { name => $name });
|
2009-07-29 15:21:49 +04:00
|
|
|
}
|
|
|
|
|
2014-04-15 18:09:43 +04:00
|
|
|
my $product = new Bugzilla::Product({ name => $name });
|
|
|
|
if ($product && (!ref $invocant || $product->id != $invocant->id))
|
|
|
|
{
|
2009-07-29 15:21:49 +04:00
|
|
|
# Check for exact case sensitive match:
|
2014-04-15 18:09:43 +04:00
|
|
|
if ($product->name eq $name)
|
|
|
|
{
|
|
|
|
ThrowUserError('product_name_already_in_use', { product => $product->name });
|
2009-07-29 15:21:49 +04:00
|
|
|
}
|
2014-04-15 18:09:43 +04:00
|
|
|
else
|
|
|
|
{
|
|
|
|
ThrowUserError('product_name_diff_in_case', {
|
|
|
|
product => $name,
|
|
|
|
existing_product => $product->name,
|
|
|
|
});
|
2009-07-29 15:21:49 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return $name;
|
|
|
|
}
|
|
|
|
|
2014-04-15 18:09:43 +04:00
|
|
|
sub _check_description
|
|
|
|
{
|
2009-07-29 15:21:49 +04:00
|
|
|
my ($invocant, $description) = @_;
|
|
|
|
|
2014-05-21 17:53:50 +04:00
|
|
|
$description = trim($description);
|
2009-07-29 15:21:49 +04:00
|
|
|
$description || ThrowUserError('product_must_have_description');
|
|
|
|
return $description;
|
|
|
|
}
|
|
|
|
|
2014-04-15 02:19:28 +04:00
|
|
|
sub _check_version
|
|
|
|
{
|
2009-07-29 15:21:49 +04:00
|
|
|
my ($invocant, $version) = @_;
|
|
|
|
$version = trim($version);
|
|
|
|
# We will check the version length when Bugzilla::Version->create will do it.
|
|
|
|
return $version;
|
|
|
|
}
|
|
|
|
|
2014-04-15 18:09:43 +04:00
|
|
|
sub _check_votes_per_user
|
|
|
|
{
|
2009-07-29 15:21:49 +04:00
|
|
|
return _check_votes(@_, 0);
|
|
|
|
}
|
|
|
|
|
2014-04-15 18:09:43 +04:00
|
|
|
sub _check_votes_per_bug
|
|
|
|
{
|
2009-07-29 15:21:49 +04:00
|
|
|
return _check_votes(@_, 10000);
|
|
|
|
}
|
|
|
|
|
2014-04-15 18:09:43 +04:00
|
|
|
sub _check_votes_to_confirm
|
|
|
|
{
|
2009-07-29 15:21:49 +04:00
|
|
|
return _check_votes(@_, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
# This subroutine is only used internally by other _check_votes_* validators.
|
2014-04-15 18:09:43 +04:00
|
|
|
sub _check_votes
|
|
|
|
{
|
2009-07-29 15:21:49 +04:00
|
|
|
my ($invocant, $votes, $field, $default) = @_;
|
|
|
|
|
|
|
|
detaint_natural($votes);
|
|
|
|
# On product creation, if the number of votes is not a valid integer,
|
|
|
|
# we silently fall back to the given default value.
|
|
|
|
# If the product already exists and the change is illegal, we complain.
|
2014-04-15 18:09:43 +04:00
|
|
|
if (!defined $votes)
|
|
|
|
{
|
|
|
|
if (ref $invocant)
|
|
|
|
{
|
|
|
|
ThrowUserError('product_illegal_votes', { field => $field, votes => $_[1] });
|
2009-07-29 15:21:49 +04:00
|
|
|
}
|
2014-04-15 18:09:43 +04:00
|
|
|
else
|
|
|
|
{
|
2009-07-29 15:21:49 +04:00
|
|
|
$votes = $default;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $votes;
|
|
|
|
}
|
|
|
|
|
|
|
|
#####################################
|
|
|
|
# Implement Bugzilla::Field::Choice #
|
|
|
|
#####################################
|
|
|
|
|
|
|
|
use constant is_default => 0;
|
|
|
|
|
2008-12-15 15:53:33 +03:00
|
|
|
###############################
|
|
|
|
#### Methods ####
|
|
|
|
###############################
|
|
|
|
|
2014-03-24 17:13:56 +04:00
|
|
|
sub _create_bug_group
|
|
|
|
{
|
2009-07-29 15:21:49 +04:00
|
|
|
my $self = shift;
|
|
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
|
|
|
|
my $group_name = $self->name;
|
2014-03-24 17:13:56 +04:00
|
|
|
my $i = 1;
|
|
|
|
while (new Bugzilla::Group({ name => $group_name }))
|
|
|
|
{
|
|
|
|
$group_name = $self->name . ($i++);
|
2009-07-29 15:21:49 +04:00
|
|
|
}
|
2014-03-24 17:13:56 +04:00
|
|
|
my $group_description = get_text('bug_group_description', { product => $self });
|
2009-07-29 15:21:49 +04:00
|
|
|
|
2014-03-24 17:13:56 +04:00
|
|
|
my $group = Bugzilla::Group->create({
|
|
|
|
name => $group_name,
|
|
|
|
description => $group_description,
|
|
|
|
isbuggroup => 1,
|
|
|
|
});
|
2009-07-29 15:21:49 +04:00
|
|
|
|
|
|
|
# Associate the new group and new product.
|
2014-03-24 17:13:56 +04:00
|
|
|
$dbh->do(
|
|
|
|
'INSERT INTO group_control_map (group_id, product_id, membercontrol, othercontrol)'.
|
|
|
|
' VALUES (?, ?, ?, ?)', undef, $group->id, $self->id, CONTROLMAPDEFAULT, CONTROLMAPNA
|
|
|
|
);
|
2009-07-29 15:21:49 +04:00
|
|
|
}
|
|
|
|
|
2014-03-24 17:13:56 +04:00
|
|
|
sub _create_series
|
|
|
|
{
|
2009-07-29 15:21:49 +04:00
|
|
|
my $self = shift;
|
|
|
|
|
|
|
|
my @series;
|
|
|
|
# We do every status, every resolution, and an "opened" one as well.
|
2014-03-24 17:13:56 +04:00
|
|
|
foreach my $bug_status (@{ Bugzilla->get_field('bug_status')->legal_value_names })
|
|
|
|
{
|
2009-07-29 15:21:49 +04:00
|
|
|
push(@series, [$bug_status, "bug_status=" . url_quote($bug_status)]);
|
|
|
|
}
|
|
|
|
|
2014-03-24 17:13:56 +04:00
|
|
|
foreach my $resolution (@{ Bugzilla->get_field('resolution')->legal_value_names })
|
|
|
|
{
|
2009-07-29 15:21:49 +04:00
|
|
|
next if !$resolution;
|
|
|
|
push(@series, [$resolution, "resolution=" . url_quote($resolution)]);
|
|
|
|
}
|
|
|
|
|
2014-07-23 19:47:56 +04:00
|
|
|
my @openedstatuses = map { $_->name } grep { $_->is_open } @{ Bugzilla->get_field('bug_status')->legal_values };
|
2009-07-29 15:21:49 +04:00
|
|
|
my $query = join("&", map { "bug_status=" . url_quote($_) } @openedstatuses);
|
|
|
|
push(@series, [get_text('series_all_open'), $query]);
|
|
|
|
|
2014-03-24 17:13:56 +04:00
|
|
|
foreach my $sdata (@series)
|
|
|
|
{
|
2014-10-07 18:24:25 +04:00
|
|
|
my $series = new Bugzilla::Series({
|
|
|
|
category => $self->name,
|
|
|
|
subcategory => get_text('series_subcategory'),
|
|
|
|
name => $sdata->[0],
|
|
|
|
frequency => 1,
|
|
|
|
query => $sdata->[1] . "&product=" . url_quote($self->name),
|
|
|
|
public => 1,
|
|
|
|
});
|
2009-07-29 15:21:49 +04:00
|
|
|
$series->writeToDatabase();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sub set_name { $_[0]->set('name', $_[1]); }
|
2010-05-15 00:02:34 +04:00
|
|
|
sub set_wiki_url { $_[0]->set('wiki_url', $_[1]); }
|
|
|
|
sub set_notimetracking { $_[0]->set('notimetracking', $_[1]); }
|
2009-07-29 15:21:49 +04:00
|
|
|
sub set_description { $_[0]->set('description', $_[1]); }
|
2010-05-15 00:02:34 +04:00
|
|
|
sub set_is_active { $_[0]->set('isactive', $_[1]); }
|
2009-07-29 15:21:49 +04:00
|
|
|
sub set_votes_per_user { $_[0]->set('votesperuser', $_[1]); }
|
|
|
|
sub set_votes_per_bug { $_[0]->set('maxvotesperbug', $_[1]); }
|
|
|
|
sub set_votes_to_confirm { $_[0]->set('votestoconfirm', $_[1]); }
|
2010-05-15 00:02:34 +04:00
|
|
|
sub set_allows_unconfirmed { $_[0]->set('allows_unconfirmed', $_[1]); }
|
2012-02-17 16:13:44 +04:00
|
|
|
sub set_classification { $_[0]->set('classification_id', $_[1]); }
|
2014-08-20 18:13:13 +04:00
|
|
|
sub set_entryheaderhtml { $_[0]->set('entryheaderhtml', $_[1]); }
|
2014-05-21 17:53:50 +04:00
|
|
|
|
2014-07-31 15:00:05 +04:00
|
|
|
sub set_cc_group
|
|
|
|
{
|
|
|
|
my ($self, $g) = @_;
|
|
|
|
$self->set('cc_group', $g);
|
|
|
|
delete $self->{cc_group_obj};
|
|
|
|
}
|
2009-07-29 15:21:49 +04:00
|
|
|
|
2010-10-22 20:57:14 +04:00
|
|
|
sub set_extproduct
|
|
|
|
{
|
|
|
|
my ($self, $product) = @_;
|
2010-10-22 21:38:12 +04:00
|
|
|
$product = Bugzilla::Product->check({ id => $product }) if $product && !ref $product;
|
2010-10-22 20:57:14 +04:00
|
|
|
$self->set('extproduct', $product ? $product->id : undef);
|
2014-07-31 15:00:05 +04:00
|
|
|
if ($self->{extproduct_obj})
|
|
|
|
{
|
|
|
|
delete $self->{extproduct_obj}->{intproduct_name};
|
|
|
|
}
|
|
|
|
delete $self->{extproduct_name};
|
|
|
|
delete $self->{extproduct_obj};
|
2010-10-22 20:57:14 +04:00
|
|
|
}
|
|
|
|
|
2014-04-15 18:09:43 +04:00
|
|
|
sub set_group_controls
|
|
|
|
{
|
2009-07-29 15:21:49 +04:00
|
|
|
my ($self, $group, $settings) = @_;
|
|
|
|
|
2014-04-15 18:09:43 +04:00
|
|
|
$group->is_active_bug_group || ThrowUserError('product_illegal_group', { group => $group });
|
2009-07-29 15:21:49 +04:00
|
|
|
|
2014-04-15 18:09:43 +04:00
|
|
|
scalar(keys %$settings) || ThrowCodeError('product_empty_group_controls', { group => $group });
|
2009-07-29 15:21:49 +04:00
|
|
|
|
|
|
|
# We store current settings for this group.
|
|
|
|
my $gs = $self->group_controls->{$group->id};
|
|
|
|
# If there is no entry for this group yet, create a default hash.
|
2014-04-15 18:09:43 +04:00
|
|
|
unless (defined $gs)
|
|
|
|
{
|
|
|
|
$gs = {
|
|
|
|
entry => 0,
|
|
|
|
membercontrol => CONTROLMAPNA,
|
|
|
|
othercontrol => CONTROLMAPNA,
|
|
|
|
canedit => 0,
|
|
|
|
editcomponents => 0,
|
|
|
|
editbugs => 0,
|
|
|
|
canconfirm => 0,
|
|
|
|
group => $group,
|
|
|
|
};
|
2009-07-29 15:21:49 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
# Both settings must be defined, or none of them can be updated.
|
2014-04-15 18:09:43 +04:00
|
|
|
if (defined $settings->{membercontrol} && defined $settings->{othercontrol})
|
|
|
|
{
|
|
|
|
# Legality of control combination is a function of
|
|
|
|
# membercontrol\othercontrol
|
|
|
|
# NA SH DE MA
|
|
|
|
# NA + - - -
|
|
|
|
# SH + + + +
|
|
|
|
# DE + - + +
|
|
|
|
# MA - - - +
|
|
|
|
foreach my $field ('membercontrol', 'othercontrol')
|
|
|
|
{
|
2009-07-29 15:21:49 +04:00
|
|
|
my ($is_legal) = grep { $settings->{$field} == $_ }
|
2014-04-15 18:09:43 +04:00
|
|
|
(CONTROLMAPNA, CONTROLMAPSHOWN, CONTROLMAPDEFAULT, CONTROLMAPMANDATORY);
|
|
|
|
if (!defined $is_legal)
|
|
|
|
{
|
|
|
|
ThrowCodeError('product_illegal_group_control', {
|
|
|
|
field => $field,
|
|
|
|
value => $settings->{$field},
|
|
|
|
});
|
|
|
|
}
|
2009-07-29 15:21:49 +04:00
|
|
|
}
|
2014-04-15 18:09:43 +04:00
|
|
|
unless ($settings->{membercontrol} == $settings->{othercontrol} ||
|
|
|
|
$settings->{membercontrol} == CONTROLMAPSHOWN ||
|
|
|
|
($settings->{membercontrol} == CONTROLMAPDEFAULT && $settings->{othercontrol} != CONTROLMAPSHOWN))
|
2009-07-29 15:21:49 +04:00
|
|
|
{
|
2014-04-15 18:09:43 +04:00
|
|
|
ThrowUserError('illegal_group_control_combination', { groupname => $group->name });
|
2009-07-29 15:21:49 +04:00
|
|
|
}
|
|
|
|
$gs->{membercontrol} = $settings->{membercontrol};
|
|
|
|
$gs->{othercontrol} = $settings->{othercontrol};
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach my $field ('entry', 'canedit', 'editcomponents', 'editbugs', 'canconfirm') {
|
|
|
|
next unless defined $settings->{$field};
|
|
|
|
$gs->{$field} = $settings->{$field} ? 1 : 0;
|
|
|
|
}
|
|
|
|
$self->{group_controls}->{$group->id} = $gs;
|
|
|
|
$self->{check_group_controls} = 1;
|
|
|
|
}
|
|
|
|
|
2010-09-10 19:13:28 +04:00
|
|
|
sub active_components
|
|
|
|
{
|
|
|
|
my $self = shift;
|
|
|
|
if (!defined $self->{active_components})
|
|
|
|
{
|
|
|
|
require Bugzilla::Component;
|
2014-06-17 17:56:12 +04:00
|
|
|
$self->{active_components} = Bugzilla::Component->match({ product_id => $self->id, isactive => 1 });
|
2010-09-10 19:13:28 +04:00
|
|
|
}
|
|
|
|
return $self->{active_components};
|
|
|
|
}
|
|
|
|
|
2014-04-15 18:09:43 +04:00
|
|
|
sub components
|
|
|
|
{
|
2008-12-15 15:53:33 +03:00
|
|
|
my $self = shift;
|
|
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
|
2014-04-15 18:09:43 +04:00
|
|
|
if (!defined $self->{components})
|
|
|
|
{
|
|
|
|
my $ids = $dbh->selectcol_arrayref(
|
|
|
|
'SELECT id FROM components WHERE product_id = ? ORDER BY name',
|
|
|
|
undef, $self->id
|
|
|
|
);
|
2008-12-15 15:53:33 +03:00
|
|
|
|
|
|
|
require Bugzilla::Component;
|
|
|
|
$self->{components} = Bugzilla::Component->new_from_list($ids);
|
|
|
|
}
|
|
|
|
return $self->{components};
|
|
|
|
}
|
|
|
|
|
2014-04-15 18:09:43 +04:00
|
|
|
sub group_controls_full_data
|
|
|
|
{
|
2012-07-05 14:21:34 +04:00
|
|
|
return $_[0]->group_controls(1);
|
|
|
|
}
|
|
|
|
|
2014-04-15 18:09:43 +04:00
|
|
|
sub group_controls
|
|
|
|
{
|
2009-07-29 15:21:49 +04:00
|
|
|
my ($self, $full_data) = @_;
|
2008-12-15 15:53:33 +03:00
|
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
|
2009-07-29 15:21:49 +04:00
|
|
|
# By default, we don't return groups which are not listed in
|
|
|
|
# group_control_map. If $full_data is true, then we also
|
|
|
|
# return groups whose settings could be set for the product.
|
|
|
|
my $where_or_and = 'WHERE';
|
|
|
|
my $and_or_where = 'AND';
|
2014-04-15 18:09:43 +04:00
|
|
|
if ($full_data)
|
|
|
|
{
|
2009-07-29 15:21:49 +04:00
|
|
|
$where_or_and = 'AND';
|
|
|
|
$and_or_where = 'WHERE';
|
|
|
|
}
|
|
|
|
|
|
|
|
# If $full_data is true, we collect all the data in all cases,
|
|
|
|
# even if the cache is already populated.
|
|
|
|
# $full_data is never used except in the very special case where
|
|
|
|
# all configurable bug groups are displayed to administrators,
|
|
|
|
# so we don't care about collecting all the data again in this case.
|
2014-04-15 18:09:43 +04:00
|
|
|
if (!defined $self->{group_controls} || $full_data)
|
|
|
|
{
|
2009-07-29 15:21:49 +04:00
|
|
|
# Include name to the list, to allow us sorting data more easily.
|
2014-04-15 18:09:43 +04:00
|
|
|
my $query =
|
|
|
|
"SELECT id, name, entry, membercontrol, othercontrol,".
|
|
|
|
" canedit, editcomponents, editbugs, canconfirm".
|
|
|
|
" FROM groups LEFT JOIN group_control_map ON id = group_id".
|
|
|
|
" $where_or_and product_id = ?".
|
|
|
|
" $and_or_where isbuggroup = 1";
|
|
|
|
$self->{group_controls} = $dbh->selectall_hashref($query, 'id', undef, $self->id);
|
2008-12-15 15:53:33 +03:00
|
|
|
|
|
|
|
# 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;
|
|
|
|
}
|
2009-07-29 15:21:49 +04:00
|
|
|
|
|
|
|
# We never cache bug counts, for the same reason as above.
|
2014-04-15 18:09:43 +04:00
|
|
|
if ($full_data)
|
|
|
|
{
|
|
|
|
my $counts = $dbh->selectall_arrayref(
|
|
|
|
'SELECT group_id, COUNT(bugs.bug_id) AS bug_count FROM bug_group_map'.
|
|
|
|
' INNER JOIN bugs ON bugs.bug_id = bug_group_map.bug_id'.
|
|
|
|
' WHERE bugs.product_id = ? ' . $dbh->sql_group_by('group_id'),
|
|
|
|
{Slice => {}}, $self->id
|
|
|
|
);
|
|
|
|
foreach my $data (@$counts)
|
|
|
|
{
|
2009-07-29 15:21:49 +04:00
|
|
|
$self->{group_controls}->{$data->{group_id}}->{bug_count} = $data->{bug_count};
|
|
|
|
}
|
|
|
|
}
|
2008-12-15 15:53:33 +03:00
|
|
|
return $self->{group_controls};
|
|
|
|
}
|
|
|
|
|
2014-04-15 18:09:43 +04:00
|
|
|
sub groups_mandatory_for
|
|
|
|
{
|
2008-12-15 15:53:33 +03:00
|
|
|
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(
|
2014-04-15 18:09:43 +04:00
|
|
|
"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
|
|
|
|
);
|
2008-12-15 15:53:33 +03:00
|
|
|
return Bugzilla::Group->new_from_list($ids);
|
|
|
|
}
|
|
|
|
|
2014-04-15 18:09:43 +04:00
|
|
|
sub groups_valid
|
|
|
|
{
|
2008-12-15 15:53:33 +03:00
|
|
|
my ($self) = @_;
|
|
|
|
return $self->{groups_valid} if defined $self->{groups_valid};
|
2010-11-10 21:21:19 +03:00
|
|
|
|
2008-12-15 15:53:33 +03:00
|
|
|
# Note that we don't check OtherControl below, because there is no
|
|
|
|
# valid NA/* combination.
|
|
|
|
my $ids = Bugzilla->dbh->selectcol_arrayref(
|
2014-04-15 18:09:43 +04:00
|
|
|
'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
|
|
|
|
);
|
2008-12-15 15:53:33 +03:00
|
|
|
$self->{groups_valid} = Bugzilla::Group->new_from_list($ids);
|
|
|
|
return $self->{groups_valid};
|
|
|
|
}
|
|
|
|
|
2014-04-15 18:09:43 +04:00
|
|
|
sub versions
|
|
|
|
{
|
2008-12-15 15:53:33 +03:00
|
|
|
my $self = shift;
|
|
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
|
2014-04-15 18:09:43 +04:00
|
|
|
if (!defined $self->{versions})
|
|
|
|
{
|
|
|
|
my $ids = $dbh->selectcol_arrayref(
|
|
|
|
'SELECT id FROM versions WHERE product_id = ?',
|
|
|
|
undef, $self->id
|
|
|
|
);
|
2008-12-15 15:53:33 +03:00
|
|
|
$self->{versions} = Bugzilla::Version->new_from_list($ids);
|
|
|
|
}
|
|
|
|
return $self->{versions};
|
|
|
|
}
|
|
|
|
|
2014-04-15 18:09:43 +04:00
|
|
|
sub milestones
|
|
|
|
{
|
2008-12-15 15:53:33 +03:00
|
|
|
my $self = shift;
|
|
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
|
2014-04-15 18:09:43 +04:00
|
|
|
if (!defined $self->{milestones})
|
|
|
|
{
|
|
|
|
my $ids = $dbh->selectcol_arrayref(
|
|
|
|
'SELECT id FROM milestones WHERE product_id = ?',
|
|
|
|
undef, $self->id
|
|
|
|
);
|
2008-12-15 15:53:33 +03:00
|
|
|
$self->{milestones} = Bugzilla::Milestone->new_from_list($ids);
|
|
|
|
}
|
|
|
|
return $self->{milestones};
|
|
|
|
}
|
|
|
|
|
2014-04-15 18:09:43 +04:00
|
|
|
sub bug_count
|
|
|
|
{
|
2008-12-15 15:53:33 +03:00
|
|
|
my $self = shift;
|
|
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
|
2014-04-15 18:09:43 +04:00
|
|
|
if (!defined $self->{bug_count})
|
|
|
|
{
|
|
|
|
$self->{bug_count} = $dbh->selectrow_array(
|
|
|
|
'SELECT COUNT(bug_id) FROM bugs WHERE product_id = ?',
|
|
|
|
undef, $self->id
|
|
|
|
);
|
2008-12-15 15:53:33 +03:00
|
|
|
}
|
2014-04-15 18:09:43 +04:00
|
|
|
return $self->{bug_count};
|
2008-12-15 15:53:33 +03:00
|
|
|
}
|
|
|
|
|
2014-04-15 18:09:43 +04:00
|
|
|
sub bug_ids
|
|
|
|
{
|
2008-12-15 15:53:33 +03:00
|
|
|
my $self = shift;
|
|
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
|
2014-04-15 18:09:43 +04:00
|
|
|
if (!defined $self->{bug_ids})
|
|
|
|
{
|
|
|
|
$self->{bug_ids} = $dbh->selectcol_arrayref(
|
|
|
|
'SELECT bug_id FROM bugs WHERE product_id = ?',
|
|
|
|
undef, $self->id
|
|
|
|
);
|
2008-12-15 15:53:33 +03:00
|
|
|
}
|
2014-04-15 18:09:43 +04:00
|
|
|
return $self->{bug_ids};
|
2008-12-15 15:53:33 +03:00
|
|
|
}
|
|
|
|
|
2014-04-15 18:09:43 +04:00
|
|
|
sub user_has_access
|
|
|
|
{
|
2008-12-15 15:53:33 +03:00
|
|
|
my ($self, $user) = @_;
|
|
|
|
|
|
|
|
return Bugzilla->dbh->selectrow_array(
|
2014-04-15 18:09:43 +04:00
|
|
|
'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
|
|
|
|
);
|
2008-12-15 15:53:33 +03:00
|
|
|
}
|
|
|
|
|
2009-12-18 20:14:04 +03:00
|
|
|
sub flag_types
|
|
|
|
{
|
2008-12-15 15:53:33 +03:00
|
|
|
my $self = shift;
|
|
|
|
|
2009-12-18 20:14:04 +03:00
|
|
|
if (!defined $self->{flag_types})
|
|
|
|
{
|
|
|
|
$self->{flag_types} = {};
|
|
|
|
foreach my $type ('bug', 'attachment')
|
|
|
|
{
|
2008-12-15 15:53:33 +03:00
|
|
|
my %flagtypes;
|
2010-09-10 19:13:28 +04:00
|
|
|
foreach my $component (@{$self->active_components})
|
2009-12-18 20:14:04 +03:00
|
|
|
{
|
|
|
|
foreach my $flagtype (@{$component->flag_types->{$type}})
|
|
|
|
{
|
|
|
|
if (!$flagtypes{$flagtype->{id}})
|
|
|
|
{
|
|
|
|
$flagtypes{$flagtype->{id}} = $flagtype;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
# Merge custom user lists
|
|
|
|
my $cl = new Bugzilla::FlagType::UserList;
|
|
|
|
$cl->merge($flagtypes{$flagtype->{id}}->{custom_list});
|
|
|
|
$cl->merge($flagtype->{custom_list});
|
|
|
|
$flagtypes{$flagtype->{id}}->{custom_list} = $cl;
|
2010-06-28 16:42:56 +04:00
|
|
|
}
|
2008-12-15 15:53:33 +03:00
|
|
|
}
|
|
|
|
}
|
2009-12-18 20:14:04 +03:00
|
|
|
$self->{flag_types}->{$type} = [
|
2014-04-15 18:09:43 +04:00
|
|
|
sort { $a->{sortkey} <=> $b->{sortkey}
|
|
|
|
|| $a->{name} cmp $b->{name} }
|
2009-12-18 20:14:04 +03:00
|
|
|
values %flagtypes
|
|
|
|
];
|
2010-06-28 16:42:56 +04:00
|
|
|
}
|
2008-12-15 15:53:33 +03:00
|
|
|
}
|
2009-12-18 20:14:04 +03:00
|
|
|
|
2014-04-15 18:09:43 +04:00
|
|
|
return $self->{flag_types};
|
2008-12-15 15:53:33 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
###############################
|
|
|
|
#### Accessors ######
|
|
|
|
###############################
|
|
|
|
|
2014-04-15 18:09:43 +04:00
|
|
|
sub allows_unconfirmed { return $_[0]->{allows_unconfirmed}; }
|
|
|
|
sub description { return $_[0]->{description}; }
|
|
|
|
sub is_active { return $_[0]->{isactive}; }
|
|
|
|
sub votes_per_user { return $_[0]->{votesperuser}; }
|
|
|
|
sub max_votes_per_bug { return $_[0]->{maxvotesperbug}; }
|
|
|
|
sub votes_to_confirm { return $_[0]->{votestoconfirm}; }
|
|
|
|
sub classification_id { return $_[0]->{classification_id}; }
|
|
|
|
sub wiki_url { return $_[0]->{wiki_url}; }
|
|
|
|
sub notimetracking { return $_[0]->{notimetracking}; }
|
|
|
|
sub extproduct { return $_[0]->{extproduct}; }
|
|
|
|
sub cc_group { return $_[0]->{cc_group}; }
|
2014-08-20 18:13:13 +04:00
|
|
|
sub entryheaderhtml { return $_[0]->{entryheaderhtml}; }
|
2008-12-15 15:53:33 +03:00
|
|
|
|
|
|
|
###############################
|
|
|
|
#### Subroutines ######
|
|
|
|
###############################
|
|
|
|
|
2014-03-28 16:39:40 +04:00
|
|
|
sub classification_obj
|
|
|
|
{
|
|
|
|
my $self = shift;
|
|
|
|
$self->{classification_obj} ||= Bugzilla::Classification->new($self->classification_id);
|
|
|
|
return $self->{classification_obj};
|
|
|
|
}
|
|
|
|
|
2010-12-20 22:05:49 +03:00
|
|
|
sub extproduct_obj
|
|
|
|
{
|
|
|
|
my $self = shift;
|
|
|
|
if (!exists $self->{extproduct_obj})
|
|
|
|
{
|
|
|
|
$self->{extproduct_obj} = $self->{extproduct} ? $self->new($self->{extproduct}) : undef;
|
|
|
|
}
|
|
|
|
return $self->{extproduct_obj};
|
|
|
|
}
|
|
|
|
|
2014-07-31 15:00:05 +04:00
|
|
|
sub cc_group_obj
|
|
|
|
{
|
|
|
|
my $self = shift;
|
|
|
|
if (!exists $self->{cc_group_obj})
|
|
|
|
{
|
|
|
|
$self->{cc_group_obj} = $self->{cc_group} ? Bugzilla::Group->new($self->{cc_group}) : undef;
|
|
|
|
}
|
|
|
|
return $self->{cc_group_obj};
|
|
|
|
}
|
|
|
|
|
2010-12-16 02:54:54 +03:00
|
|
|
sub enterable_extproduct_name
|
|
|
|
{
|
|
|
|
my $self = shift;
|
|
|
|
if (!exists $self->{extproduct_name})
|
|
|
|
{
|
2010-12-20 22:05:49 +03:00
|
|
|
my $n = $self->{extproduct} ? $self->extproduct_obj->name : '';
|
2010-12-16 02:54:54 +03:00
|
|
|
$n = '' if $n ne '' && !Bugzilla->user->can_enter_product($n);
|
|
|
|
$self->{extproduct_name} = $n;
|
|
|
|
}
|
|
|
|
return $self->{extproduct_name};
|
|
|
|
}
|
|
|
|
|
|
|
|
sub enterable_intproduct_name
|
|
|
|
{
|
|
|
|
my $self = shift;
|
|
|
|
if (!exists $self->{intproduct_name})
|
|
|
|
{
|
|
|
|
my $n = $self->{extproduct} ? [] : $self->match({ extproduct => $self->id });
|
|
|
|
$n = @$n ? $n->[0]->name : '';
|
|
|
|
$n = '' if $n ne '' && !Bugzilla->user->can_enter_product($n);
|
|
|
|
$self->{intproduct_name} = $n;
|
|
|
|
}
|
|
|
|
return $self->{intproduct_name};
|
|
|
|
}
|
|
|
|
|
2014-04-15 18:09:43 +04:00
|
|
|
sub check_product
|
|
|
|
{
|
2008-12-15 15:53:33 +03:00
|
|
|
my ($product_name) = @_;
|
|
|
|
|
2014-04-15 18:09:43 +04:00
|
|
|
unless ($product_name)
|
|
|
|
{
|
2008-12-15 15:53:33 +03:00
|
|
|
ThrowUserError('product_not_specified');
|
|
|
|
}
|
2014-04-15 18:09:43 +04:00
|
|
|
my $product = new Bugzilla::Product({ name => $product_name });
|
|
|
|
unless ($product)
|
|
|
|
{
|
|
|
|
ThrowUserError('product_doesnt_exist', { product => $product_name });
|
2008-12-15 15:53:33 +03:00
|
|
|
}
|
|
|
|
return $product;
|
|
|
|
}
|
|
|
|
|
2014-04-15 18:09:43 +04:00
|
|
|
sub check
|
|
|
|
{
|
2010-05-15 00:02:34 +04:00
|
|
|
my ($class, $params) = @_;
|
|
|
|
$params = { name => $params } if !ref $params;
|
|
|
|
$params->{_error} = 'product_access_denied';
|
|
|
|
my $product = $class->SUPER::check($params);
|
2014-04-15 18:09:43 +04:00
|
|
|
if (!Bugzilla->user->can_see_product($product))
|
|
|
|
{
|
2010-05-15 00:02:34 +04:00
|
|
|
ThrowUserError('product_access_denied', $params);
|
|
|
|
}
|
|
|
|
return $product;
|
|
|
|
}
|
|
|
|
|
2010-11-10 21:21:19 +03:00
|
|
|
# Product is a special case: it has access controls applied.
|
|
|
|
# So return all products visible to current user.
|
|
|
|
sub get_all { @{ Bugzilla->user->get_selectable_products } }
|
|
|
|
|
2014-08-28 15:36:37 +04:00
|
|
|
###############################
|
|
|
|
#### Class Methods ######
|
|
|
|
###############################
|
|
|
|
|
|
|
|
# FIXME: This is a "controller" method and should probably be moved out from "model" class Product
|
|
|
|
sub choose_product
|
|
|
|
{
|
|
|
|
my $class = shift;
|
|
|
|
my ($products, $query_params, $target) = @_;
|
|
|
|
$products ||= Bugzilla->user->get_enterable_products;
|
|
|
|
ThrowUserError('no_products') unless @$products;
|
|
|
|
return $products->[0] if @$products == 1;
|
|
|
|
|
2014-10-10 15:15:48 +04:00
|
|
|
my $qp = { %{ $query_params || Bugzilla->input_params } };
|
2014-10-08 16:30:57 +04:00
|
|
|
delete $qp->{classification};
|
|
|
|
$qp = http_build_query($qp);
|
|
|
|
$qp .= '&' if length $qp;
|
2014-08-28 15:36:37 +04:00
|
|
|
if (!$target)
|
|
|
|
{
|
|
|
|
$target = $ENV{REQUEST_URI};
|
|
|
|
$target =~ s/\?.*//so;
|
|
|
|
$target =~ s/^\/+//so;
|
|
|
|
}
|
|
|
|
my $vars = {
|
|
|
|
target => $target,
|
2014-10-08 16:30:57 +04:00
|
|
|
query_params => $qp,
|
2014-08-28 15:36:37 +04:00
|
|
|
};
|
|
|
|
if (Bugzilla->get_field('classification')->enabled)
|
|
|
|
{
|
|
|
|
my $classifs;
|
|
|
|
push @{$classifs->{$_->classification_id}}, $_ for @$products;
|
|
|
|
$classifs = [
|
|
|
|
map { { object => $_, products => $classifs->{$_->id} } }
|
|
|
|
@{ Bugzilla::Classification->new_from_list([ keys %$classifs ]) }
|
|
|
|
];
|
|
|
|
if (scalar @$classifs == 1)
|
|
|
|
{
|
|
|
|
$vars->{classifications} = [ $classifs->[0] ];
|
|
|
|
if (scalar @{$classifs->[0]->{products}} == 1)
|
|
|
|
{
|
|
|
|
return $classifs->[0]->{products}->[0];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
my $cl = Bugzilla->input_params->{classification};
|
|
|
|
if (!$cl || $cl ne '__all' && !(($cl) = grep { $_->{object}->name eq $cl } @$classifs))
|
|
|
|
{
|
|
|
|
$vars->{classifications} = [ map { $_->{object} } @$classifs ];
|
|
|
|
Bugzilla->template->process("global/choose-classification.html.tmpl", $vars)
|
|
|
|
|| ThrowTemplateError(Bugzilla->template->error());
|
|
|
|
exit;
|
|
|
|
}
|
|
|
|
elsif ($cl eq '__all')
|
|
|
|
{
|
|
|
|
$vars->{classifications} = $classifs;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$vars->{classifications} = [ $cl ];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$vars->{classifications} = [ { object => undef, products => $products } ];
|
|
|
|
}
|
2008-12-15 15:53:33 +03:00
|
|
|
|
2014-08-28 15:36:37 +04:00
|
|
|
Bugzilla->template->process('global/choose-product.html.tmpl', $vars)
|
|
|
|
|| ThrowTemplateError(Bugzilla->template->error());
|
|
|
|
exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
1;
|
2008-12-15 15:53:33 +03:00
|
|
|
__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' });
|
|
|
|
|
2010-09-10 19:13:28 +04:00
|
|
|
my @components = @{ $product->components() };
|
|
|
|
my @active_components = @{ $product->active_components() };
|
2008-12-15 15:53:33 +03:00
|
|
|
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;
|
2010-05-15 00:02:34 +04:00
|
|
|
my $isactive = $product->is_active;
|
2009-08-18 16:08:16 +04:00
|
|
|
my $votesperuser = $product->votes_per_user;
|
|
|
|
my $maxvotesperbug = $product->max_votes_per_bug;
|
|
|
|
my $votestoconfirm = $product->votes_to_confirm;
|
|
|
|
my $wiki_url = $product->wiki_url;
|
2010-05-12 18:11:37 +04:00
|
|
|
my $notimetracking = $product->notimetracking;
|
2008-12-15 15:53:33 +03:00
|
|
|
my $classificationid = $product->classification_id;
|
2010-05-15 00:02:34 +04:00
|
|
|
my $allows_unconfirmed = $product->allows_unconfirmed;
|
2008-12-15 15:53:33 +03:00
|
|
|
|
|
|
|
=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.
|
|
|
|
|
2009-07-29 15:21:49 +04:00
|
|
|
Params: $full_data (optional, false by default) - when true,
|
|
|
|
the number of bugs per group applicable to the product
|
|
|
|
is also returned. Moreover, bug groups which have no
|
|
|
|
special settings for the product are also returned.
|
2008-12-15 15:53:33 +03:00
|
|
|
|
|
|
|
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
|