# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # # This Source Code Form is "Incompatible With Secondary Licenses", as # defined by the Mozilla Public License, v. 2.0. package Bugzilla::Classification; use 5.10.1; use strict; use Bugzilla::Constants; use Bugzilla::Field; use Bugzilla::Util; use Bugzilla::Error; use Bugzilla::Product; use parent qw(Bugzilla::Field::ChoiceInterface Bugzilla::Object Exporter); @Bugzilla::Classification::EXPORT = qw(sort_products_by_classification); ############################### #### Initialization #### ############################### use constant IS_CONFIG => 1; use constant DB_TABLE => 'classifications'; use constant LIST_ORDER => 'sortkey, name'; use constant DB_COLUMNS => qw( id name description sortkey ); use constant UPDATE_COLUMNS => qw( name description sortkey ); use constant VALIDATORS => { name => \&_check_name, description => \&_check_description, sortkey => \&_check_sortkey, }; ############################### #### Constructors ##### ############################### sub remove_from_db { my $self = shift; my $dbh = Bugzilla->dbh; ThrowUserError("classification_not_deletable") if ($self->id == 1); $dbh->bz_start_transaction(); # Reclassify products to the default classification, if needed. my $product_ids = $dbh->selectcol_arrayref( 'SELECT id FROM products WHERE classification_id = ?', undef, $self->id); if (@$product_ids) { $dbh->do('UPDATE products SET classification_id = 1 WHERE ' . $dbh->sql_in('id', $product_ids)); foreach my $id (@$product_ids) { Bugzilla->memcached->clear({ table => 'products', id => $id }); } Bugzilla->memcached->clear_config(); } $self->SUPER::remove_from_db(); $dbh->bz_commit_transaction(); } ############################### #### Validators #### ############################### sub _check_name { my ($invocant, $name) = @_; $name = trim($name); $name || ThrowUserError('classification_not_specified'); if (length($name) > MAX_CLASSIFICATION_SIZE) { ThrowUserError('classification_name_too_long', {'name' => $name}); } my $classification = new Bugzilla::Classification({name => $name}); if ($classification && (!ref $invocant || $classification->id != $invocant->id)) { ThrowUserError("classification_already_exists", { name => $classification->name }); } return $name; } sub _check_description { my ($invocant, $description) = @_; $description = trim($description || ''); return $description; } sub _check_sortkey { my ($invocant, $sortkey) = @_; $sortkey ||= 0; my $stored_sortkey = $sortkey; if (!detaint_natural($sortkey) || $sortkey > MAX_SMALLINT) { ThrowUserError('classification_invalid_sortkey', { 'sortkey' => $stored_sortkey }); } return $sortkey; } ##################################### # Implement Bugzilla::Field::Choice # ##################################### use constant FIELD_NAME => 'classification'; use constant is_default => 0; use constant is_active => 1; ############################### #### Methods #### ############################### sub set_name { $_[0]->set('name', $_[1]); } sub set_description { $_[0]->set('description', $_[1]); } sub set_sortkey { $_[0]->set('sortkey', $_[1]); } 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) || 0; } return $self->{'product_count'}; } sub products { my $self = shift; my $dbh = Bugzilla->dbh; if (!$self->{'products'}) { my $product_ids = $dbh->selectcol_arrayref(q{ SELECT id FROM products WHERE classification_id = ? ORDER BY name}, undef, $self->id); $self->{'products'} = Bugzilla::Product->new_from_list($product_ids); } return $self->{'products'}; } ############################### #### Accessors #### ############################### sub description { return $_[0]->{'description'}; } sub sortkey { return $_[0]->{'sortkey'}; } ############################### #### Helpers #### ############################### # This function is a helper to sort products to be listed # in global/choose-product.html.tmpl. sub sort_products_by_classification { my $products = shift; my $list; if (Bugzilla->params->{'useclassification'}) { my $class = {}; # Get all classifications with at least one product. foreach my $product (@$products) { $class->{$product->classification_id}->{'object'} ||= new Bugzilla::Classification($product->classification_id); # Nice way to group products per classification, without querying # the DB again. push(@{$class->{$product->classification_id}->{'products'}}, $product); } $list = [sort {$a->{'object'}->sortkey <=> $b->{'object'}->sortkey || lc($a->{'object'}->name) cmp lc($b->{'object'}->name)} (values %$class)]; } else { $list = [{object => undef, products => $products}]; } return $list; } 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 $sortkey = $classification->sortkey; my $product_count = $classification->product_count; my $products = $classification->products; =head1 DESCRIPTION Classification.pm represents a classification object. It is an implementation of L, and thus provides all methods that L provides. The methods that are specific to C are listed below. A Classification is a higher-level grouping of Products. =head1 METHODS =over =item C Description: Returns the total number of products that belong to the classification. Params: none. Returns: Integer - The total of products inside the classification. =item C Description: Returns all products of the classification. Params: none. Returns: A reference to an array of Bugzilla::Product objects. =back =head1 SUBROUTINES =over =item C Description: This is a helper which returns a list of products sorted by classification in a form suitable to be passed to the global/choose-product.html.tmpl template. Params: An arrayref of product objects. Returns: An arrayref of hashes suitable to be passed to global/choose-product.html.tmpl. =back =cut =head1 B =over =item set_description =item sortkey =item set_name =item description =item remove_from_db =item set_sortkey =back