360 lines
10 KiB
Perl
360 lines
10 KiB
Perl
# 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::Field::Choice;
|
|
|
|
use 5.10.1;
|
|
use strict;
|
|
use warnings;
|
|
|
|
use parent qw(Bugzilla::Field::ChoiceInterface Bugzilla::Object);
|
|
|
|
use Bugzilla::Config qw(SetParam write_params);
|
|
use Bugzilla::Constants;
|
|
use Bugzilla::Error;
|
|
use Bugzilla::Field;
|
|
use Bugzilla::Util qw(trim detaint_natural);
|
|
|
|
use Scalar::Util qw(blessed);
|
|
|
|
##################
|
|
# Initialization #
|
|
##################
|
|
|
|
use constant IS_CONFIG => 1;
|
|
|
|
use constant DB_COLUMNS => qw(
|
|
id
|
|
value
|
|
sortkey
|
|
isactive
|
|
visibility_value_id
|
|
);
|
|
|
|
use constant UPDATE_COLUMNS => qw(
|
|
value
|
|
sortkey
|
|
isactive
|
|
visibility_value_id
|
|
);
|
|
|
|
use constant NAME_FIELD => 'value';
|
|
use constant LIST_ORDER => 'sortkey, value';
|
|
|
|
use constant VALIDATORS => {
|
|
value => \&_check_value,
|
|
sortkey => \&_check_sortkey,
|
|
visibility_value_id => \&_check_visibility_value_id,
|
|
isactive => \&_check_isactive,
|
|
};
|
|
|
|
use constant CLASS_MAP => {
|
|
bug_status => 'Bugzilla::Status',
|
|
classification => 'Bugzilla::Classification',
|
|
component => 'Bugzilla::Component',
|
|
product => 'Bugzilla::Product',
|
|
};
|
|
|
|
use constant DEFAULT_MAP => {
|
|
op_sys => 'defaultopsys',
|
|
rep_platform => 'defaultplatform',
|
|
priority => 'defaultpriority',
|
|
bug_severity => 'defaultseverity',
|
|
};
|
|
|
|
#################
|
|
# Class Factory #
|
|
#################
|
|
|
|
# Bugzilla::Field::Choice is actually an abstract base class. Every field
|
|
# type has its own dynamically-generated class for its values. This allows
|
|
# certain fields to have special types, like how bug_status's values
|
|
# are Bugzilla::Status objects.
|
|
|
|
sub type {
|
|
my ($class, $field) = @_;
|
|
my $field_obj = blessed $field ? $field : Bugzilla::Field->check($field);
|
|
my $field_name = $field_obj->name;
|
|
|
|
if (my $package = $class->CLASS_MAP->{$field_name}) {
|
|
# Callers expect the module to be already loaded.
|
|
eval "require $package";
|
|
return $package;
|
|
}
|
|
|
|
# For generic classes, we use a lowercase class name, so as
|
|
# not to interfere with any real subclasses we might make some day.
|
|
my $package = "Bugzilla::Field::Choice::$field_name";
|
|
Bugzilla->request_cache->{"field_$package"} = $field_obj;
|
|
|
|
# This package only needs to be created once. We check if the DB_TABLE
|
|
# glob for this package already exists, which tells us whether or not
|
|
# we need to create the package (this works even under mod_perl, where
|
|
# this package definition will persist across requests)).
|
|
if (!defined *{"${package}::DB_TABLE"}) {
|
|
eval <<EOC;
|
|
package $package;
|
|
use parent qw(Bugzilla::Field::Choice);
|
|
use constant DB_TABLE => '$field_name';
|
|
EOC
|
|
}
|
|
|
|
return $package;
|
|
}
|
|
|
|
################
|
|
# Constructors #
|
|
################
|
|
|
|
# We just make new() enforce this, which should give developers
|
|
# the understanding that you can't use Bugzilla::Field::Choice
|
|
# without calling type().
|
|
sub new {
|
|
my $class = shift;
|
|
if ($class eq 'Bugzilla::Field::Choice') {
|
|
ThrowCodeError('field_choice_must_use_type');
|
|
}
|
|
$class->SUPER::new(@_);
|
|
}
|
|
|
|
#########################
|
|
# Database Manipulation #
|
|
#########################
|
|
|
|
# Our subclasses can take more arguments than we normally accept.
|
|
# So, we override create() to remove arguments that aren't valid
|
|
# columns. (Normally Bugzilla::Object dies if you pass arguments
|
|
# that aren't valid columns.)
|
|
sub create {
|
|
my $class = shift;
|
|
my ($params) = @_;
|
|
foreach my $key (keys %$params) {
|
|
if (!grep {$_ eq $key} $class->_get_db_columns) {
|
|
delete $params->{$key};
|
|
}
|
|
}
|
|
return $class->SUPER::create(@_);
|
|
}
|
|
|
|
sub update {
|
|
my $self = shift;
|
|
my $dbh = Bugzilla->dbh;
|
|
my $fname = $self->field->name;
|
|
|
|
$dbh->bz_start_transaction();
|
|
|
|
my ($changes, $old_self) = $self->SUPER::update(@_);
|
|
if (exists $changes->{value}) {
|
|
my ($old, $new) = @{ $changes->{value} };
|
|
if ($self->field->type == FIELD_TYPE_MULTI_SELECT) {
|
|
$dbh->do("UPDATE bug_$fname SET value = ? WHERE value = ?",
|
|
undef, $new, $old);
|
|
}
|
|
else {
|
|
$dbh->do("UPDATE bugs SET $fname = ? WHERE $fname = ?",
|
|
undef, $new, $old);
|
|
}
|
|
|
|
if ($old_self->is_default) {
|
|
my $param = $self->DEFAULT_MAP->{$self->field->name};
|
|
SetParam($param, $self->name);
|
|
write_params();
|
|
}
|
|
}
|
|
|
|
$dbh->bz_commit_transaction();
|
|
return wantarray ? ($changes, $old_self) : $changes;
|
|
}
|
|
|
|
sub remove_from_db {
|
|
my $self = shift;
|
|
if ($self->is_default) {
|
|
ThrowUserError('fieldvalue_is_default',
|
|
{ field => $self->field, value => $self,
|
|
param_name => $self->DEFAULT_MAP->{$self->field->name},
|
|
});
|
|
}
|
|
if ($self->is_static) {
|
|
ThrowUserError('fieldvalue_not_deletable',
|
|
{ field => $self->field, value => $self });
|
|
}
|
|
if ($self->bug_count) {
|
|
ThrowUserError("fieldvalue_still_has_bugs",
|
|
{ field => $self->field, value => $self });
|
|
}
|
|
$self->_check_if_controller(); # From ChoiceInterface.
|
|
$self->SUPER::remove_from_db();
|
|
}
|
|
|
|
############
|
|
# Mutators #
|
|
############
|
|
|
|
sub set_is_active { $_[0]->set('isactive', $_[1]); }
|
|
sub set_name { $_[0]->set('value', $_[1]); }
|
|
sub set_sortkey { $_[0]->set('sortkey', $_[1]); }
|
|
sub set_visibility_value {
|
|
my ($self, $value) = @_;
|
|
$self->set('visibility_value_id', $value);
|
|
delete $self->{visibility_value};
|
|
}
|
|
|
|
##############
|
|
# Validators #
|
|
##############
|
|
|
|
sub _check_isactive {
|
|
my ($invocant, $value) = @_;
|
|
$value = Bugzilla::Object::check_boolean($invocant, $value);
|
|
if (!$value and ref $invocant) {
|
|
if ($invocant->is_default) {
|
|
my $field = $invocant->field;
|
|
ThrowUserError('fieldvalue_is_default',
|
|
{ value => $invocant, field => $field,
|
|
param_name => $invocant->DEFAULT_MAP->{$field->name}
|
|
});
|
|
}
|
|
if ($invocant->is_static) {
|
|
ThrowUserError('fieldvalue_not_deletable',
|
|
{ value => $invocant, field => $invocant->field });
|
|
}
|
|
}
|
|
return $value;
|
|
}
|
|
|
|
sub _check_value {
|
|
my ($invocant, $value) = @_;
|
|
|
|
my $field = $invocant->field;
|
|
|
|
$value = trim($value);
|
|
|
|
# Make sure people don't rename static values
|
|
if (blessed($invocant) && $value ne $invocant->name
|
|
&& $invocant->is_static)
|
|
{
|
|
ThrowUserError('fieldvalue_not_editable',
|
|
{ field => $field, old_value => $invocant });
|
|
}
|
|
|
|
ThrowUserError('fieldvalue_undefined') if !defined $value || $value eq "";
|
|
ThrowUserError('fieldvalue_name_too_long', { value => $value })
|
|
if length($value) > MAX_FIELD_VALUE_SIZE;
|
|
|
|
my $exists = $invocant->type($field)->new({ name => $value });
|
|
if ($exists && (!blessed($invocant) || $invocant->id != $exists->id)) {
|
|
ThrowUserError('fieldvalue_already_exists',
|
|
{ field => $field, value => $exists });
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
sub _check_sortkey {
|
|
my ($invocant, $value) = @_;
|
|
$value = trim($value);
|
|
return 0 if !$value;
|
|
# Store for the error message in case detaint_natural clears it.
|
|
my $orig_value = $value;
|
|
(detaint_natural($value) && $value <= MAX_SMALLINT)
|
|
|| ThrowUserError('fieldvalue_sortkey_invalid',
|
|
{ sortkey => $orig_value,
|
|
field => $invocant->field });
|
|
return $value;
|
|
}
|
|
|
|
sub _check_visibility_value_id {
|
|
my ($invocant, $value_id) = @_;
|
|
$value_id = trim($value_id);
|
|
my $field = $invocant->field->value_field;
|
|
return undef if !$field || !$value_id;
|
|
my $value_obj = Bugzilla::Field::Choice->type($field)
|
|
->check({ id => $value_id });
|
|
return $value_obj->id;
|
|
}
|
|
|
|
1;
|
|
|
|
__END__
|
|
|
|
=head1 NAME
|
|
|
|
Bugzilla::Field::Choice - A legal value for a <select>-type field.
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
my $field = new Bugzilla::Field({name => 'bug_status'});
|
|
|
|
my $choice = new Bugzilla::Field::Choice->type($field)->new(1);
|
|
|
|
my $choices = Bugzilla::Field::Choice->type($field)->new_from_list([1,2,3]);
|
|
my $choices = Bugzilla::Field::Choice->type($field)->get_all();
|
|
my $choices = Bugzilla::Field::Choice->type($field->match({ sortkey => 10 });
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
This is an implementation of L<Bugzilla::Object>, but with a twist.
|
|
You can't call any class methods (such as C<new>, C<create>, etc.)
|
|
directly on C<Bugzilla::Field::Choice> itself. Instead, you have to
|
|
call C<Bugzilla::Field::Choice-E<gt>type($field)> to get the class
|
|
you're going to instantiate, and then you call the methods on that.
|
|
|
|
We do that because each field has its own database table for its values, so
|
|
each value type needs its own class.
|
|
|
|
See the L</SYNOPSIS> for examples of how this works.
|
|
|
|
This class implements L<Bugzilla::Field::ChoiceInterface>, and so all
|
|
methods of that class are also available here.
|
|
|
|
=head1 METHODS
|
|
|
|
=head2 Class Factory
|
|
|
|
In object-oriented design, a "class factory" is a method that picks
|
|
and returns the right class for you, based on an argument that you pass.
|
|
|
|
=over
|
|
|
|
=item C<type>
|
|
|
|
Takes a single argument, which is either the name of a field from the
|
|
C<fielddefs> table, or a L<Bugzilla::Field> object representing a field.
|
|
|
|
Returns an appropriate subclass of C<Bugzilla::Field::Choice> that you
|
|
can now call class methods on (like C<new>, C<create>, C<match>, etc.)
|
|
|
|
B<NOTE>: YOU CANNOT CALL CLASS METHODS ON C<Bugzilla::Field::Choice>. You
|
|
must call C<type> to get a class you can call methods on.
|
|
|
|
=back
|
|
|
|
=head2 Mutators
|
|
|
|
This class implements mutators for all of the settable accessors in
|
|
L<Bugzilla::Field::ChoiceInterface>.
|
|
|
|
=head1 B<Methods in need of POD>
|
|
|
|
=over
|
|
|
|
=item create
|
|
|
|
=item remove_from_db
|
|
|
|
=item set_is_active
|
|
|
|
=item set_sortkey
|
|
|
|
=item set_name
|
|
|
|
=item update
|
|
|
|
=item set_visibility_value
|
|
|
|
=back
|