aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Bugzilla/Classification.pm193
-rw-r--r--Bugzilla/Component.pm198
-rw-r--r--Bugzilla/Constants.pm5
-rw-r--r--Bugzilla/Group.pm72
-rw-r--r--Bugzilla/Milestone.pm172
-rw-r--r--Bugzilla/Product.pm336
-rw-r--r--Bugzilla/Version.pm170
-rw-r--r--template/en/default/global/code-error.html.tmpl5
8 files changed, 1137 insertions, 14 deletions
diff --git a/Bugzilla/Classification.pm b/Bugzilla/Classification.pm
new file mode 100644
index 000000000..fd011e6fe
--- /dev/null
+++ b/Bugzilla/Classification.pm
@@ -0,0 +1,193 @@
+# -*- 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::Classification;
+
+use Bugzilla;
+use Bugzilla::Util;
+
+###############################
+#### Initialization ####
+###############################
+
+use constant DB_COLUMNS => qw(
+ classifications.id
+ classifications.name
+ classifications.description
+);
+
+our $columns = join(", ", DB_COLUMNS);
+
+###############################
+#### Methods ####
+###############################
+
+sub new {
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+ my $self = {};
+ bless($self, $class);
+ return $self->_init(@_);
+}
+
+sub _init {
+ my $self = shift;
+ my ($param) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my $id = $param unless (ref $param eq 'HASH');
+ my $classification;
+
+ if (defined $id && detaint_natural($id)) {
+
+ $classification = $dbh->selectrow_hashref(qq{
+ SELECT $columns FROM classifications
+ WHERE id = ?}, undef, $id);
+
+ } elsif (defined $param->{'name'}) {
+
+ trick_taint($param->{'name'});
+ $classification = $dbh->selectrow_hashref(qq{
+ SELECT $columns FROM classifications
+ WHERE name = ?}, undef, $param->{'name'});
+ } else {
+ ThrowCodeError('bad_arg',
+ {argument => 'param',
+ function => 'Bugzilla::Classification::_init'});
+ }
+
+ return undef unless (defined $classification);
+
+ foreach my $field (keys %$classification) {
+ $self->{$field} = $classification->{$field};
+ }
+ return $self;
+}
+
+sub product_count {
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ if (!defined $self->{'product_count'}) {
+ $self->{'product_count'} = $dbh->selectrow_array(q{
+ SELECT COUNT(*) FROM products
+ WHERE classification_id = ?}, undef, $self->id);
+ }
+ return $self->{'product_count'};
+}
+
+###############################
+#### Accessors ####
+###############################
+
+sub id { return $_[0]->{'id'}; }
+sub name { return $_[0]->{'name'}; }
+sub description { return $_[0]->{'description'}; }
+
+###############################
+#### Subroutines ####
+###############################
+
+sub get_all_classifications () {
+ my $dbh = Bugzilla->dbh;
+
+ my $ids = $dbh->selectcol_arrayref(q{
+ SELECT id FROM classifications});
+
+ my $classifications;
+ foreach my $id (@$ids) {
+ $classifications->{$id} = new Bugzilla::Classification($id);
+ }
+ return $classifications;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Classification - Bugzilla classification class.
+
+=head1 SYNOPSIS
+
+ use Bugzilla::Classification;
+
+ my $classification = new Bugzilla::Classification(1);
+ my $classification = new Bugzilla::Classification({name => 'Acme'});
+
+ my $id = $classification->id;
+ my $name = $classification->name;
+ my $description = $classification->description;
+ my $product_count = $classification->product_count;
+
+ my $hash_ref = Bugzilla::Classification::get_all_classifications();
+ my $classification = $hash_ref->{1};
+
+=head1 DESCRIPTION
+
+Classification.pm represents a Classification object.
+
+A Classification is a higher-level grouping of Bugzilla Products.
+
+=head1 METHODS
+
+=over
+
+=item C<new($param)>
+
+ Description: The constructor is used to load an existing
+ classification by passing a classification
+ id or classification name using a hash.
+
+ Params: $param - If you pass an integer, the integer is the
+ classification_id from the database that we
+ want to read in. If you pass in a hash with
+ 'name' key, then the value of the name key
+ is the name of a classification from the DB.
+
+ Returns: A Bugzilla::Classification object.
+
+=item C<product_count()>
+
+ Description: Returns the total number of products that belong to
+ the classification.
+
+ Params: none.
+
+ Returns: Integer - The total of products inside the classification.
+
+=back
+
+=head1 SUBROUTINES
+
+=over
+
+=item C<get_all_classifications()>
+
+ Description: Returns all Bugzilla classifications.
+
+ Params: none.
+
+ Returns: A hash with classification id as key and
+ Bugzilla::Classification object as value.
+
+=back
+
+=cut
diff --git a/Bugzilla/Component.pm b/Bugzilla/Component.pm
new file mode 100644
index 000000000..216616d98
--- /dev/null
+++ b/Bugzilla/Component.pm
@@ -0,0 +1,198 @@
+# -*- 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::Component;
+
+use Bugzilla;
+use Bugzilla::Util;
+use Bugzilla::Error;
+
+###############################
+#### Initialization ####
+###############################
+
+use constant DB_COLUMNS => qw(
+ components.id
+ components.name
+ components.product_id
+ components.initialowner
+ components.initialqacontact
+ components.description
+);
+
+our $columns = join(", ", DB_COLUMNS);
+
+###############################
+#### Methods ####
+###############################
+
+sub new {
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+ my $self = {};
+ bless($self, $class);
+ return $self->_init(@_);
+}
+
+sub _init {
+ my $self = shift;
+ my ($param) = (@_);
+ my $dbh = Bugzilla->dbh;
+
+ my $id = $param unless (ref $param eq 'HASH');
+ my $component;
+
+ if (defined $id && detaint_natural($id)) {
+
+ $component = $dbh->selectrow_hashref(qq{
+ SELECT $columns FROM components
+ WHERE id = ?}, undef, $id);
+
+ } elsif (defined $param->{'product_id'}
+ && detaint_natural($param->{'product_id'})
+ && defined $param->{'name'}) {
+
+ trick_taint($param->{'name'});
+
+ $component = $dbh->selectrow_hashref(qq{
+ SELECT $columns FROM components
+ WHERE name = ? AND product_id = ?}, undef,
+ ($param->{'name'}, $param->{'product_id'}));
+ } else {
+ ThrowCodeError('bad_arg',
+ {argument => 'param',
+ function => 'Bugzilla::Component::_init'});
+ }
+
+ return undef unless (defined $component);
+
+ foreach my $field (keys %$component) {
+ $self->{$field} = $component->{$field};
+ }
+ return $self;
+}
+
+###############################
+#### Accessors ####
+###############################
+
+sub id { return $_[0]->{'id'}; }
+sub name { return $_[0]->{'name'}; }
+sub description { return $_[0]->{'description'}; }
+sub product_id { return $_[0]->{'product_id'}; }
+sub default_assignee { return $_[0]->{'initialowner'}; }
+sub default_qa_contact { return $_[0]->{'initialqacontact'}; }
+
+###############################
+#### Subroutines ####
+###############################
+
+sub get_components_by_product ($) {
+ my ($product_id) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my $stored_product_id = $product_id;
+ unless (detaint_natural($product_id)) {
+ ThrowCodeError(
+ 'invalid_numeric_argument',
+ {argument => 'product_id',
+ value => $stored_product_id,
+ function =>
+ 'Bugzilla::Component::get_components_by_product'}
+ );
+ }
+
+ my $ids = $dbh->selectcol_arrayref(q{
+ SELECT id FROM components
+ WHERE product_id = ?}, undef, $product_id);
+
+ my $components;
+ foreach my $id (@$ids) {
+ $components->{$id} = new Bugzilla::Component($id);
+ }
+ return $components;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Component - Bugzilla product component class.
+
+=head1 SYNOPSIS
+
+ use Bugzilla::Component;
+
+ my $component = new Bugzilla::Component(1);
+ my $component = new Bugzilla::Component({product_id => 1,
+ name => 'AcmeComp'});
+
+ 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 $hash_ref = Bugzilla::Component::get_components_by_product(1);
+ my $component = $hash_ref->{1};
+
+=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
+ id 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 'name' key,
+ then the value of the name key is the name of a
+ component from the DB.
+
+ Returns: A Bugzilla::Component object.
+
+=back
+
+=head1 SUBROUTINES
+
+=over
+
+=item C<get_components_by_product($product_id)>
+
+ Description: Returns all Bugzilla components that belong to the
+ supplied product.
+
+ Params: $product_id - Integer with a Bugzilla product id.
+
+ Returns: A hash with component id as key and Bugzilla::Component
+ object as value.
+
+=back
+
+=cut
diff --git a/Bugzilla/Constants.pm b/Bugzilla/Constants.pm
index a62629420..95b8af98c 100644
--- a/Bugzilla/Constants.pm
+++ b/Bugzilla/Constants.pm
@@ -87,6 +87,8 @@ use base qw(Exporter);
EVT_FLAG_REQUESTED EVT_REQUESTED_FLAG
FULLTEXT_BUGLIST_LIMIT
+
+ ADMIN_GROUP_NAME
);
@Bugzilla::Constants::EXPORT_OK = qw(contenttypes);
@@ -233,4 +235,7 @@ use constant GLOBAL_EVENTS => EVT_FLAG_REQUESTED, EVT_REQUESTED_FLAG;
# a fulltext search.
use constant FULLTEXT_BUGLIST_LIMIT => 200;
+# Default administration group name.
+use constant ADMIN_GROUP_NAME => 'admin';
+
1;
diff --git a/Bugzilla/Group.pm b/Bugzilla/Group.pm
index 85e6de5b1..808860274 100644
--- a/Bugzilla/Group.pm
+++ b/Bugzilla/Group.pm
@@ -106,7 +106,7 @@ sub is_active { return $_[0]->{'isactive'}; }
##### Module Subroutines ###
################################
-sub ValidateGroupName {
+sub ValidateGroupName ($$) {
my ($name, @users) = (@_);
my $dbh = Bugzilla->dbh;
my $query = "SELECT id FROM groups " .
@@ -125,13 +125,34 @@ sub ValidateGroupName {
return $ret;
}
+sub get_group_controls_by_product ($) {
+ my ($product_id) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my $query = qq{SELECT
+ $columns,
+ group_control_map.entry,
+ group_control_map.membercontrol,
+ group_control_map.othercontrol,
+ group_control_map.canedit
+ 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};
+ my $groups = $dbh->selectall_hashref($query, 'id', undef,
+ ($product_id));
+ return $groups;
+}
+
1;
__END__
=head1 NAME
-Bugzilla::Group - Object for a Bugzilla group.
+Bugzilla::Group - Bugzilla group class.
=head1 SYNOPSIS
@@ -147,6 +168,10 @@ Bugzilla::Group - Object for a Bugzilla group.
my $user_reg_exp = $group->user_reg_exp;
my $is_active = $group->is_active;
+ my $group_id = Bugzilla::Group::ValidateGroupName('admin', @users);
+
+ my $grops = Bugzilla::Group::get_group_controls_by_product(1);
+
=head1 DESCRIPTION
Group.pm represents a Bugzilla Group object.
@@ -157,8 +182,16 @@ Group.pm represents a Bugzilla Group object.
=item C<new($param)>
-The constructor is used to load an existing group by passing
-a group id or a hash with the group name.
+ Description: The constructor is used to load an existing group
+ by passing a group id or a hash with the group name.
+
+ Params: $param - If you pass an integer, the integer is the
+ group id from the database that we want to
+ read in. If you pass in a hash with 'name'
+ key, then the value of the name key is the
+ name of a product from the DB.
+
+ Returns: A Bugzilla::Group object.
=back
@@ -166,18 +199,29 @@ a group id or a hash with the group name.
=over
-=item C<ValidateGroupName($group_name, @users)>
+=item C<ValidateGroupName($name, @users)>
-ValidateGroupName checks to see if ANY of the users in the provided list
-of user objects can see the named group. It returns the group id if
-successful and undef otherwise.
+ Description: ValidateGroupName checks to see if ANY of the users
+ in the provided list of user objects can see the
+ named group.
-=back
+ Params: $name - String with the group name.
+ @users - An array with Bugzilla::User objects.
+
+ Returns: It returns the group id if successful
+ and undef otherwise.
+
+=item C<get_group_controls_by_product($product_id)>
+
+ Description: Returns all group controls of a specific product.
+ It is encouraged to use Bugzilla::Product object
+ instead of directly calling this routine.
-=head1 AUTHOR
-
- Joel Peshkin <bugreport@peshkin.net>
- Erik Stambaugh <erik@dasbistro.com>
- Tiago R. Mello <timello@async.com.br>
+ Params: $product_id - Integer with a Bugzilla product id.
+
+ Returns: A hash with group id as key and hash containing the
+ group data as value.
+
+=back
=cut
diff --git a/Bugzilla/Milestone.pm b/Bugzilla/Milestone.pm
new file mode 100644
index 000000000..dad8b6c11
--- /dev/null
+++ b/Bugzilla/Milestone.pm
@@ -0,0 +1,172 @@
+# -*- 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::Milestone;
+
+use Bugzilla;
+use Bugzilla::Util;
+use Bugzilla::Error;
+
+################################
+##### Initialization #####
+################################
+
+use constant DEFAULT_SORTKEY => 0;
+
+use constant DB_COLUMNS => qw(
+ milestones.value
+ milestones.product_id
+ milestones.sortkey
+);
+
+my $columns = join(", ", DB_COLUMNS);
+
+sub new {
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+ my $self = {};
+ bless($self, $class);
+ return $self->_init(@_);
+}
+
+sub _init {
+ my $self = shift;
+ my ($product_id, $value) = (@_);
+ my $dbh = Bugzilla->dbh;
+
+ my $milestone;
+
+ if (defined $product_id
+ && detaint_natural($product_id)
+ && defined $value) {
+
+ trick_taint($value);
+ $milestone = $dbh->selectrow_hashref(qq{
+ SELECT $columns FROM milestones
+ WHERE value = ?
+ AND product_id = ?}, undef, ($value, $product_id));
+ } else {
+ ThrowCodeError('bad_arg',
+ {argument => 'product_id/value',
+ function => 'Bugzilla::Milestone::_init'});
+ }
+
+ return undef unless (defined $milestone);
+
+ foreach my $field (keys %$milestone) {
+ $self->{$field} = $milestone->{$field};
+ }
+ return $self;
+}
+
+################################
+##### Accessors ######
+################################
+
+sub value { return $_[0]->{'value'}; }
+sub product_id { return $_[0]->{'product_id'}; }
+sub sortkey { return $_[0]->{'sortkey'}; }
+
+################################
+##### Subroutines #####
+################################
+
+sub get_milestones_by_product ($) {
+ my ($product_id) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my $stored_product_id = $product_id;
+ unless (detaint_natural($product_id)) {
+ ThrowCodeError(
+ 'invalid_numeric_argument',
+ {argument => 'product_id',
+ value => $stored_product_id,
+ function =>
+ 'Bugzilla::Milestone::get_milestones_by_product'}
+ );
+ }
+
+ my $values = $dbh->selectcol_arrayref(q{
+ SELECT value FROM milestones
+ WHERE product_id = ?}, undef, $product_id);
+
+ my $milestones;
+ foreach my $value (@$values) {
+ $milestones->{$value} = new Bugzilla::Milestone($product_id,
+ $value);
+ }
+ return $milestones;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Milestone - Bugzilla product milestone class.
+
+=head1 SYNOPSIS
+
+ use Bugzilla::Milestone;
+
+ my $milestone = new Bugzilla::Milestone(1, 'milestone_value');
+
+ my $product_id = $milestone->product_id;
+ my $value = $milestone->value;
+
+ my $hash_ref = Bugzilla::Milestone::get_milestones_by_product(1);
+ my $milestone = $hash_ref->{'milestone_value'};
+
+=head1 DESCRIPTION
+
+Milestone.pm represents a Product Milestone object.
+
+=head1 METHODS
+
+=over
+
+=item C<new($product_id, $value)>
+
+ Description: The constructor is used to load an existing milestone
+ by passing a product id and a milestone value.
+
+ Params: $product_id - Integer with a Bugzilla product id.
+ $value - String with a milestone value.
+
+ Returns: A Bugzilla::Milestone object.
+
+=back
+
+=head1 SUBROUTINES
+
+=over
+
+=item C<get_milestones_by_product($product_id)>
+
+ Description: Returns all Bugzilla product milestones that belong
+ to the supplied product.
+
+ Params: $product_id - Integer with a Bugzilla product id.
+
+ Returns: A hash with milestone value as key and a
+ Bugzilla::Milestone object as hash value.
+
+=back
+
+=cut
diff --git a/Bugzilla/Product.pm b/Bugzilla/Product.pm
new file mode 100644
index 000000000..28f1e73c0
--- /dev/null
+++ b/Bugzilla/Product.pm
@@ -0,0 +1,336 @@
+# -*- 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;
+use Bugzilla::Component;
+use Bugzilla::Classification;
+use Bugzilla::Version;
+use Bugzilla::Milestone;
+
+use Bugzilla::Util;
+use Bugzilla::Group;
+use Bugzilla::Error;
+
+use constant DEFAULT_CLASSIFICATION_ID => 1;
+
+###############################
+#### Initialization ####
+###############################
+
+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
+);
+
+my $columns = join(", ", DB_COLUMNS);
+
+sub new {
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+ my $self = {};
+ bless($self, $class);
+ return $self->_init(@_);
+}
+
+sub _init {
+ my $self = shift;
+ my ($param) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my $id = $param unless (ref $param eq 'HASH');
+ my $product;
+
+ if (defined $id && detaint_natural($id)) {
+
+ $product = $dbh->selectrow_hashref(qq{
+ SELECT $columns FROM products
+ WHERE id = ?}, undef, $id);
+
+ } elsif (defined $param->{'name'}) {
+
+ trick_taint($param->{'name'});
+ $product = $dbh->selectrow_hashref(qq{
+ SELECT $columns FROM products
+ WHERE name = ?}, undef, $param->{'name'});
+ } else {
+ ThrowCodeError('bad_arg',
+ {argument => 'param',
+ function => 'Bugzilla::Product::_init'});
+ }
+
+ return undef unless (defined $product);
+
+ foreach my $field (keys %$product) {
+ $self->{$field} = $product->{$field};
+ }
+ return $self;
+}
+
+###############################
+#### Methods ####
+###############################
+
+sub components {
+ my $self = shift;
+
+ if (!defined $self->{components}) {
+ $self->{components} =
+ Bugzilla::Component::get_components_by_product($self->id);
+ }
+ return $self->{components}
+}
+
+sub classification {
+ my $self = shift;
+
+ if (!defined $self->{'classification'}) {
+ $self->{'classification'} =
+ new Bugzilla::Classification($self->classification_id);
+ }
+ return $self->{'classification'};
+}
+
+sub group_controls {
+ my $self = shift;
+
+ if (!defined $self->{group_controls}) {
+ $self->{group_controls} =
+ Bugzilla::Group::get_group_controls_by_product($self->id);
+ }
+ return $self->{group_controls};
+}
+
+sub versions {
+ my $self = shift;
+
+ if (!defined $self->{versions}) {
+ $self->{versions} =
+ Bugzilla::Version::get_versions_by_product($self->id);
+
+ }
+ return $self->{versions};
+}
+
+sub milestones {
+ my $self = shift;
+
+ if (!defined $self->{milestones}) {
+ $self->{milestones} =
+ Bugzilla::Milestone::get_milestones_by_product($self->id);
+ }
+ 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'};
+}
+
+###############################
+#### Accessors ######
+###############################
+
+sub id { return $_[0]->{'id'}; }
+sub name { return $_[0]->{'name'}; }
+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 get_products_by_classification ($) {
+ my ($class_id) = @_;
+ my $dbh = Bugzilla->dbh;
+ $class_id ||= DEFAULT_CLASSIFICATION_ID;
+
+ my $stored_class_id = $class_id;
+ unless (detaint_natural($class_id)) {
+ ThrowCodeError(
+ 'invalid_numeric_argument',
+ {argument => 'product_id',
+ value => $stored_class_id,
+ function =>
+ 'Bugzilla::Product::get_classification_products'}
+ );
+ }
+
+ my $ids = $dbh->selectcol_arrayref(q{
+ SELECT id FROM products
+ WHERE classification_id = ?}, undef, $class_id);
+
+ my $products;
+ foreach my $id (@$ids) {
+ $products->{$id} = new Bugzilla::Product($id);
+ }
+ return $products;
+}
+
+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('AcmeProduct');
+
+ my $components = $product->components();
+ my $classification = $product->classification();
+ my $hash_ref = $product->group_controls();
+ my $hash_ref = $product->milestones();
+ my $hash_ref = $product->versions();
+ my $bugcount = $product->bug_count();
+
+ 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;
+
+ my $hash_ref = Bugzilla::Product::get_products_by_classification(1);
+ my $product = $hash_ref->{1};
+
+=head1 DESCRIPTION
+
+Product.pm represents a product object.
+
+=head1 METHODS
+
+=over
+
+=item C<new($param)>
+
+ Description: The constructor is used to load an existing product
+ by passing a product id or a hash.
+
+ Params: $param - If you pass an integer, the integer is the
+ product id from the database that we want to
+ read in. If you pass in a hash with 'name' key,
+ then the value of the name key is the name of a
+ product from the DB.
+
+ Returns: A Bugzilla::Product object.
+
+=item C<components()>
+
+ Description: Returns a hash with all product components.
+
+ Params: none.
+
+ Returns: A hash where component id is the hash key and
+ Bugzilla::Component object is the hash value.
+
+=item C<classification()>
+
+ Description: Returns a Bugzilla::Classification object for
+ the product classification.
+
+ Params: none.
+
+ Returns: A Bugzilla::Classification 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 the
+ group data as value.
+
+=item C<versions()>
+
+ Description: Returns a hash with of all product versions.
+
+ Params: none.
+
+ Returns: A hash with version id as key and a Bugzilla::Version
+ as value.
+
+=item C<milestones()>
+
+ Description: Returns a hash with of all product milestones.
+
+ Params: none.
+
+ Returns: A hash with milestone id as key and a Bugzilla::Milestone
+ as value.
+
+=item C<bug_count()>
+
+ Description: Returns the total of bugs that belong to the product.
+
+ Params: none.
+
+ Returns: Integer with the number of bugs.
+
+=back
+
+=head1 SUBROUTINES
+
+=over
+
+=item C<get_products_by_classification($class_id)>
+
+ Description: Returns all products for a specific classification id.
+
+ Params: none.
+
+ Returns: A hash with product id as key and a Bugzilla::Product
+ object as value.
+
+=back
+
+=cut
diff --git a/Bugzilla/Version.pm b/Bugzilla/Version.pm
new file mode 100644
index 000000000..5cd5b2aeb
--- /dev/null
+++ b/Bugzilla/Version.pm
@@ -0,0 +1,170 @@
+# -*- 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::Version;
+
+use Bugzilla;
+use Bugzilla::Util;
+use Bugzilla::Error;
+
+################################
+##### Initialization #####
+################################
+
+use constant DEFAULT_VERSION => 'unspecified';
+
+use constant DB_COLUMNS => qw(
+ versions.value
+ versions.product_id
+);
+
+our $columns = join(", ", DB_COLUMNS);
+
+sub new {
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+ my $self = {};
+ bless($self, $class);
+ return $self->_init(@_);
+}
+
+sub _init {
+ my $self = shift;
+ my ($product_id, $value) = (@_);
+ my $dbh = Bugzilla->dbh;
+
+ my $version;
+
+ if (defined $product_id
+ && detaint_natural($product_id)
+ && defined $value) {
+
+ trick_taint($value);
+ $version = $dbh->selectrow_hashref(qq{
+ SELECT $columns FROM versions
+ WHERE value = ?
+ AND product_id = ?}, undef, ($value, $product_id));
+ } else {
+ ThrowCodeError('bad_arg',
+ {argument => 'product_id/value',
+ function => 'Bugzilla::Version::_init'});
+ }
+
+ return undef unless (defined $version);
+
+ foreach my $field (keys %$version) {
+ $self->{$field} = $version->{$field};
+ }
+ return $self;
+}
+
+###############################
+##### Accessors ####
+###############################
+
+sub value { return $_[0]->{'value'}; }
+sub product_id { return $_[0]->{'product_id'}; }
+
+###############################
+##### Subroutines ###
+###############################
+
+sub get_versions_by_product ($) {
+ my ($product_id) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my $stored_product_id = $product_id;
+ unless (detaint_natural($product_id)) {
+ ThrowCodeError(
+ 'invalid_numeric_argument',
+ {argument => 'product_id',
+ value => $stored_product_id,
+ function =>
+ 'Bugzilla::Version::get_versions_by_product'}
+ );
+ }
+
+ my $values = $dbh->selectcol_arrayref(q{
+ SELECT value FROM versions
+ WHERE product_id = ?}, undef, $product_id);
+
+ my $versions;
+ foreach my $value (@$values) {
+ $versions->{$value} = new Bugzilla::Version($product_id,
+ $value);
+ }
+ return $versions;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Version - Bugzilla product version class.
+
+=head1 SYNOPSIS
+
+ use Bugzilla::Version;
+
+ my $version = new Bugzilla::Version(1, 'version_value');
+
+ my $product_id = $version->product_id;
+ my $value = $version->value;
+
+ my $hash_ref = Bugzilla::Version::get_versions_by_product(1);
+ my $version = $hash_ref->{'version_value'};
+
+=head1 DESCRIPTION
+
+Version.pm represents a Product Version object.
+
+=head1 METHODS
+
+=over
+
+=item C<new($product_id, $value)>
+
+ Description: The constructor is used to load an existing version
+ by passing a product id and a version value.
+
+ Params: $product_id - Integer with a Bugzilla product id.
+ $value - String with a version value.
+
+ Returns: A Bugzilla::Version object.
+
+=back
+
+=head1 SUBROUTINES
+
+=over
+
+=item C<get_versions_by_product($product_id)>
+
+ Description: Returns all Bugzilla product versions that belong
+ to the supplied product.
+
+ Params: $product_id - Integer with a Bugzilla product id.
+
+ Returns: A hash with version value as key and a Bugzilla::Version
+ objects as value.
+
+=back
+
+=cut
diff --git a/template/en/default/global/code-error.html.tmpl b/template/en/default/global/code-error.html.tmpl
index 1b7af3fea..5ad057061 100644
--- a/template/en/default/global/code-error.html.tmpl
+++ b/template/en/default/global/code-error.html.tmpl
@@ -149,6 +149,11 @@
The active flag was improperly set. There may be
a problem with [% terms.Bugzilla %] or [% terms.abug %] in your browser.
+ [% ELSIF error == "invalid_numeric_argument" %]
+ [% title = "Invalid number argument" %]
+ The argument <code>[% argument FILTER html %] = [% value FILTER html %]</code>
+ of <code>[% function FILTER html %]</code> is not a natural number.
+
[% ELSIF error == "invalid_series_id" %]
[% title = "Invalid Series" %]
The series_id [% series_id FILTER html %] is not valid. It may be that