2009-07-29 15:21:49 +04:00
|
|
|
# -*- 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 Initial Developer of the Original Code is NASA.
|
|
|
|
# Portions created by NASA are Copyright (C) 2006 San Jose State
|
|
|
|
# University Foundation. All Rights Reserved.
|
|
|
|
#
|
|
|
|
# The Original Code is the Bugzilla Bug Tracking System.
|
|
|
|
#
|
|
|
|
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
2010-05-15 00:02:34 +04:00
|
|
|
# Greg Hendricks <ghendricks@novell.com>
|
2009-09-07 20:47:01 +04:00
|
|
|
# Vitaliy Filippov <vitalif@mail.ru>
|
2009-07-29 15:21:49 +04:00
|
|
|
|
|
|
|
use strict;
|
|
|
|
|
2009-09-07 20:47:01 +04:00
|
|
|
##############################################
|
|
|
|
# Class representing single value of a field #
|
|
|
|
##############################################
|
|
|
|
|
2009-07-29 15:21:49 +04:00
|
|
|
package Bugzilla::Field::Choice;
|
|
|
|
|
|
|
|
use base qw(Bugzilla::Object);
|
|
|
|
|
|
|
|
use Bugzilla::Config qw(SetParam write_params);
|
|
|
|
use Bugzilla::Constants;
|
|
|
|
use Bugzilla::Error;
|
|
|
|
use Bugzilla::Field;
|
2009-09-07 20:47:01 +04:00
|
|
|
use Bugzilla::Util qw(trim detaint_natural trick_taint diff_arrays);
|
2009-07-29 15:21:49 +04:00
|
|
|
|
|
|
|
use Scalar::Util qw(blessed);
|
|
|
|
|
|
|
|
##################
|
|
|
|
# Initialization #
|
|
|
|
##################
|
|
|
|
|
|
|
|
use constant DB_COLUMNS => qw(
|
|
|
|
id
|
|
|
|
value
|
|
|
|
sortkey
|
2010-05-15 00:02:34 +04:00
|
|
|
isactive
|
2009-07-29 15:21:49 +04:00
|
|
|
);
|
|
|
|
|
|
|
|
use constant UPDATE_COLUMNS => qw(
|
|
|
|
value
|
|
|
|
sortkey
|
2010-05-15 00:02:34 +04:00
|
|
|
isactive
|
2009-07-29 15:21:49 +04:00
|
|
|
);
|
|
|
|
|
|
|
|
use constant NAME_FIELD => 'value';
|
|
|
|
use constant LIST_ORDER => 'sortkey, value';
|
|
|
|
|
|
|
|
use constant REQUIRED_CREATE_FIELDS => qw(value);
|
|
|
|
|
|
|
|
use constant VALIDATORS => {
|
2010-11-24 21:25:51 +03:00
|
|
|
value => \&_check_value,
|
|
|
|
sortkey => \&_check_sortkey,
|
2010-05-15 00:02:34 +04:00
|
|
|
isactive => \&Bugzilla::Object::check_boolean,
|
2009-07-29 15:21:49 +04:00
|
|
|
};
|
|
|
|
|
|
|
|
use constant CLASS_MAP => {
|
2010-11-10 21:21:19 +03:00
|
|
|
bug_status => 'Bugzilla::Status',
|
|
|
|
product => 'Bugzilla::Product',
|
|
|
|
component => 'Bugzilla::Component',
|
|
|
|
version => 'Bugzilla::Version',
|
|
|
|
target_milestone => 'Bugzilla::Milestone',
|
|
|
|
classification => 'Bugzilla::Classification',
|
2009-07-29 15:21:49 +04:00
|
|
|
};
|
|
|
|
|
|
|
|
use constant DEFAULT_MAP => {
|
|
|
|
op_sys => 'defaultopsys',
|
|
|
|
rep_platform => 'defaultplatform',
|
|
|
|
priority => 'defaultpriority',
|
|
|
|
bug_severity => 'defaultseverity',
|
|
|
|
};
|
|
|
|
|
2010-11-10 21:21:19 +03:00
|
|
|
use constant EXCLUDE_CONTROLLED_FIELDS => ();
|
|
|
|
|
2009-07-29 15:21:49 +04:00
|
|
|
#################
|
|
|
|
# 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.
|
|
|
|
|
2010-11-10 21:21:19 +03:00
|
|
|
sub type
|
|
|
|
{
|
2009-07-29 15:21:49 +04:00
|
|
|
my ($class, $field) = @_;
|
2010-10-14 14:31:30 +04:00
|
|
|
my $field_obj = blessed $field ? $field : Bugzilla->get_field($field, THROW_ERROR);
|
2009-07-29 15:21:49 +04:00
|
|
|
my $field_name = $field_obj->name;
|
|
|
|
|
2010-11-10 21:21:19 +03:00
|
|
|
my $package;
|
|
|
|
if ($class->CLASS_MAP->{$field_name})
|
|
|
|
{
|
|
|
|
$package = $class->CLASS_MAP->{$field_name};
|
2010-11-11 16:36:23 +03:00
|
|
|
if (!defined *{"${package}::DB_TABLE"})
|
|
|
|
{
|
|
|
|
eval "require $package";
|
|
|
|
}
|
2010-11-10 21:21:19 +03:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
# For generic classes, we use a lowercase class name, so as
|
|
|
|
# not to interfere with any real subclasses we might make some day.
|
|
|
|
$package = "Bugzilla::Field::Choice::$field_name";
|
2010-11-11 16:36:23 +03:00
|
|
|
|
|
|
|
# The 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 base qw(Bugzilla::Field::Choice);
|
|
|
|
use constant DB_TABLE => '$field_name';
|
|
|
|
use constant FIELD_NAME => '$field_name';
|
2009-07-29 15:21:49 +04:00
|
|
|
EOC
|
2010-11-11 16:36:23 +03:00
|
|
|
}
|
2009-07-29 15:21:49 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
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 #
|
|
|
|
#########################
|
|
|
|
|
2010-11-11 16:36:23 +03:00
|
|
|
# vitalif@mail.ru 2010-11-11 //
|
|
|
|
# This is incorrect in create() to remove arguments that are not valid DB columns
|
|
|
|
# BEFORE calling run_create_validators etc, as these methods can change
|
|
|
|
# params hash (for example turn Bugzilla::Product to product_id field)
|
2009-07-29 15:21:49 +04:00
|
|
|
|
2010-12-10 02:53:33 +03:00
|
|
|
sub create
|
|
|
|
{
|
|
|
|
my $self = shift;
|
|
|
|
$self->field->touch;
|
|
|
|
$self->SUPER::create(@_);
|
|
|
|
}
|
|
|
|
|
2009-07-29 15:21:49 +04:00
|
|
|
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(@_);
|
2010-11-16 18:25:39 +03:00
|
|
|
if (exists $changes->{$self->NAME_FIELD}) {
|
|
|
|
my ($old, $new) = @{ $changes->{$self->NAME_FIELD} };
|
2009-07-29 15:21:49 +04:00
|
|
|
if ($self->field->type == FIELD_TYPE_MULTI_SELECT) {
|
|
|
|
$dbh->do("UPDATE bug_$fname SET value = ? WHERE value = ?",
|
|
|
|
undef, $new, $old);
|
|
|
|
}
|
|
|
|
else {
|
2010-11-16 17:42:14 +03:00
|
|
|
$dbh->do(
|
|
|
|
"INSERT INTO bugs_activity (bug_id, who, bug_when, fieldid, added, removed)".
|
2010-11-16 17:46:24 +03:00
|
|
|
" SELECT bug_id, ?, NOW(), ?, ?, ? FROM bugs WHERE $fname = ?", undef,
|
2010-11-16 18:03:38 +03:00
|
|
|
Bugzilla->user->id, $self->field->id, $new, $old, $old
|
2010-11-16 17:42:14 +03:00
|
|
|
);
|
|
|
|
$dbh->do("UPDATE bugs SET $fname = ?, lastdiffed = NOW() WHERE $fname = ?",
|
2009-07-29 15:21:49 +04:00
|
|
|
undef, $new, $old);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($old_self->is_default) {
|
|
|
|
my $param = $self->DEFAULT_MAP->{$self->field->name};
|
|
|
|
SetParam($param, $self->name);
|
|
|
|
write_params();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-12-10 02:53:33 +03:00
|
|
|
$self->field->touch;
|
2009-07-29 15:21:49 +04:00
|
|
|
$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) {
|
2010-10-27 19:53:21 +04:00
|
|
|
ThrowUserError('fieldvalue_still_has_bugs',
|
2009-07-29 15:21:49 +04:00
|
|
|
{ field => $self->field, value => $self });
|
|
|
|
}
|
|
|
|
$self->_check_if_controller();
|
2010-03-12 22:18:39 +03:00
|
|
|
$self->set_visibility_values(undef);
|
2010-12-10 02:53:33 +03:00
|
|
|
$self->field->touch;
|
2009-07-29 15:21:49 +04:00
|
|
|
$self->SUPER::remove_from_db();
|
|
|
|
}
|
|
|
|
|
2010-11-10 21:21:19 +03:00
|
|
|
# Product field is a special case: it has access controls applied.
|
|
|
|
# So if our values are controlled by product field value,
|
|
|
|
# return only ones visible inside products visible to current user.
|
|
|
|
sub get_all
|
|
|
|
{
|
|
|
|
my $class = shift;
|
|
|
|
my @all = $class->SUPER::get_all();
|
|
|
|
my $f = $class->field;
|
|
|
|
if (!$f->value_field_id || $f->value_field->name ne 'product')
|
|
|
|
{
|
|
|
|
return @all;
|
|
|
|
}
|
|
|
|
my $h = Bugzilla->fieldvaluecontrol_hash
|
|
|
|
->{Bugzilla->get_field('product')->id}
|
|
|
|
->{values}
|
|
|
|
->{$f->id};
|
|
|
|
my $visible_ids = { map { $_->id => 1 } Bugzilla::Product->get_all };
|
|
|
|
my $vis;
|
|
|
|
my @filtered;
|
|
|
|
for my $value (@all)
|
|
|
|
{
|
2010-12-10 02:53:33 +03:00
|
|
|
$vis = !$h->{$value->id} || !%{$h->{$value->id}} ? 1 : 0;
|
2010-11-10 21:21:19 +03:00
|
|
|
for (keys %{$h->{$value->id}})
|
|
|
|
{
|
|
|
|
if ($visible_ids->{$_})
|
|
|
|
{
|
|
|
|
$vis = 1;
|
|
|
|
last;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
push @filtered, $value if $vis;
|
|
|
|
}
|
|
|
|
return @filtered;
|
|
|
|
}
|
|
|
|
|
2010-12-10 02:53:33 +03:00
|
|
|
sub get_all_names
|
|
|
|
{
|
|
|
|
my $class = shift;
|
|
|
|
my $f = $class->field;
|
|
|
|
my $where = grep { $_ eq 'isactive' } $class->DB_COLUMNS;
|
|
|
|
$where = $where ? [ "$where>0" ] : [];
|
|
|
|
# Apply product access controls
|
|
|
|
if ($f->value_field_id && $f->value_field->name eq 'product')
|
|
|
|
{
|
|
|
|
my $h = Bugzilla->fieldvaluecontrol_hash
|
|
|
|
->{Bugzilla->get_field('product')->id}
|
|
|
|
->{values}
|
|
|
|
->{$f->id};
|
|
|
|
# Products visible to current user
|
|
|
|
my $visible_prods = { map { $_->id => 1 } Bugzilla::Product->get_all };
|
|
|
|
# IDs of invisible values
|
|
|
|
my $invisible_ids = [];
|
|
|
|
for my $id (keys %$h)
|
|
|
|
{
|
|
|
|
if ($h->{$id} && %{$h->{$id}})
|
|
|
|
{
|
|
|
|
VISIBLE: {
|
|
|
|
for (keys %{$h->{$id}})
|
|
|
|
{
|
|
|
|
last VISIBLE if $visible_prods->{$_};
|
|
|
|
}
|
|
|
|
push @$invisible_ids, $id;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (@$invisible_ids)
|
|
|
|
{
|
|
|
|
push @$where, 'id NOT IN ('.join(',', @$invisible_ids).')';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$where = join ' AND ', @$where;
|
|
|
|
$where and $where = " WHERE $where";
|
|
|
|
my $order = $class->LIST_ORDER;
|
|
|
|
$order =~ s/(\s+(A|DE)SC)(?!\w)//giso;
|
|
|
|
my $col = Bugzilla->dbh->selectcol_arrayref(
|
|
|
|
'SELECT DISTINCT '.$class->NAME_FIELD.','.$class->LIST_ORDER.
|
|
|
|
' FROM '.$class->DB_TABLE.$where.
|
|
|
|
' ORDER BY '.$class->LIST_ORDER
|
|
|
|
);
|
|
|
|
my $dup = {};
|
|
|
|
my $names = [];
|
|
|
|
for (@$col)
|
|
|
|
{
|
|
|
|
if (!$dup->{$_})
|
|
|
|
{
|
|
|
|
push @$names, $_;
|
|
|
|
$dup->{$_} = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $names;
|
|
|
|
}
|
|
|
|
|
2010-11-10 21:21:19 +03:00
|
|
|
sub _check_if_controller
|
|
|
|
{
|
2009-07-29 15:21:49 +04:00
|
|
|
my $self = shift;
|
2010-11-10 21:21:19 +03:00
|
|
|
my %exclude = map { $_ => 1 } $self->EXCLUDE_CONTROLLED_FIELDS;
|
|
|
|
my $c_fields = $self->controls_visibility_of_fields;
|
|
|
|
my $c_values = $self->controls_visibility_of_field_values;
|
|
|
|
$c_fields = [ grep { !$exclude{$_->name} } @$c_fields ];
|
|
|
|
$c_values = {
|
|
|
|
map { $_ => $c_values->{$_} }
|
|
|
|
grep { !$exclude{$_} && $c_values->{$_} }
|
|
|
|
keys %$c_values
|
|
|
|
};
|
|
|
|
if (@$c_fields || %$c_values)
|
|
|
|
{
|
|
|
|
ThrowUserError('fieldvalue_is_controller', {
|
|
|
|
value => $self,
|
|
|
|
fields => [ map { $_->name } @$c_fields ],
|
|
|
|
vals => $c_values,
|
|
|
|
});
|
2009-07-29 15:21:49 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#############
|
|
|
|
# Accessors #
|
|
|
|
#############
|
|
|
|
|
2010-05-15 00:02:34 +04:00
|
|
|
sub is_active { return $_[0]->{'isactive'}; }
|
|
|
|
sub sortkey { return $_[0]->{'sortkey'}; }
|
2009-07-29 15:21:49 +04:00
|
|
|
|
|
|
|
sub bug_count {
|
|
|
|
my $self = shift;
|
|
|
|
return $self->{bug_count} if defined $self->{bug_count};
|
|
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
my $fname = $self->field->name;
|
|
|
|
my $count;
|
|
|
|
if ($self->field->type == FIELD_TYPE_MULTI_SELECT) {
|
|
|
|
$count = $dbh->selectrow_array("SELECT COUNT(*) FROM bug_$fname
|
|
|
|
WHERE value = ?", undef, $self->name);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$count = $dbh->selectrow_array("SELECT COUNT(*) FROM bugs
|
|
|
|
WHERE $fname = ?",
|
|
|
|
undef, $self->name);
|
|
|
|
}
|
|
|
|
$self->{bug_count} = $count;
|
|
|
|
return $count;
|
|
|
|
}
|
|
|
|
|
2010-11-11 16:36:23 +03:00
|
|
|
sub field
|
|
|
|
{
|
2009-07-29 15:21:49 +04:00
|
|
|
my $invocant = shift;
|
2010-11-11 16:36:23 +03:00
|
|
|
return Bugzilla->get_field($invocant->FIELD_NAME);
|
2009-07-29 15:21:49 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
sub is_default {
|
|
|
|
my $self = shift;
|
|
|
|
my $name = $self->DEFAULT_MAP->{$self->field->name};
|
|
|
|
# If it doesn't exist in DEFAULT_MAP, then there is no parameter
|
|
|
|
# related to this field.
|
|
|
|
return 0 unless $name;
|
|
|
|
return ($self->name eq Bugzilla->params->{$name}) ? 1 : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub is_static {
|
|
|
|
my $self = shift;
|
|
|
|
# If we need to special-case Resolution for *anything* else, it should
|
|
|
|
# get its own subclass.
|
|
|
|
if ($self->field->name eq 'resolution') {
|
|
|
|
return grep($_ eq $self->name, ('', 'FIXED', 'MOVED', 'DUPLICATE'))
|
|
|
|
? 1 : 0;
|
|
|
|
}
|
|
|
|
elsif ($self->field->custom) {
|
|
|
|
return $self->name eq '---' ? 1 : 0;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-03-12 22:18:39 +03:00
|
|
|
sub controls_visibility_of_fields
|
|
|
|
{
|
2009-07-29 15:21:49 +04:00
|
|
|
my $self = shift;
|
2010-10-27 19:53:21 +04:00
|
|
|
my $vid = $self->id;
|
|
|
|
my $fid = $self->field->id;
|
|
|
|
$self->{controls_visibility_of_fields} ||= [
|
|
|
|
map { Bugzilla->get_field($_->{field_id}) }
|
|
|
|
grep { !$_->{value_id} &&
|
|
|
|
$_->{visibility_value_id} == $vid &&
|
|
|
|
$_->{visibility_field_id} == $fid }
|
|
|
|
@{Bugzilla->fieldvaluecontrol}
|
|
|
|
];
|
|
|
|
return $self->{controls_visibility_of_fields};
|
2009-07-29 15:21:49 +04:00
|
|
|
}
|
|
|
|
|
2010-10-27 19:53:21 +04:00
|
|
|
sub controls_visibility_of_field_values
|
2009-09-07 21:19:11 +04:00
|
|
|
{
|
|
|
|
my $self = shift;
|
2010-10-27 19:53:21 +04:00
|
|
|
my $vid = $self->id;
|
|
|
|
my $fid = $self->field->id;
|
|
|
|
if (!$self->{controls_visibility_of_field_values})
|
2010-03-12 22:18:39 +03:00
|
|
|
{
|
2010-10-27 19:53:21 +04:00
|
|
|
my $r = {};
|
|
|
|
for (@{Bugzilla->fieldvaluecontrol})
|
2010-03-12 22:18:39 +03:00
|
|
|
{
|
2010-10-27 19:53:21 +04:00
|
|
|
if ($_->{value_id} &&
|
|
|
|
$_->{visibility_value_id} == $vid &&
|
|
|
|
$_->{visibility_field_id} == $fid)
|
2010-03-12 22:18:39 +03:00
|
|
|
{
|
2010-10-27 19:53:21 +04:00
|
|
|
push @{$r->{$_->{field_id}}}, $_->{value_id};
|
2010-03-12 22:18:39 +03:00
|
|
|
}
|
|
|
|
}
|
2010-10-27 19:53:21 +04:00
|
|
|
$self->{controls_visibility_of_field_values} = { map {
|
|
|
|
Bugzilla->get_field($_)->name =>
|
|
|
|
Bugzilla::Field::Choice->type(Bugzilla->get_field($_))->new_from_list($r->{$_})
|
|
|
|
} keys %$r };
|
2009-09-07 21:19:11 +04:00
|
|
|
}
|
2010-10-27 19:53:21 +04:00
|
|
|
return $self->{controls_visibility_of_field_values};
|
2009-09-07 21:19:11 +04:00
|
|
|
}
|
|
|
|
|
2009-09-07 20:47:01 +04:00
|
|
|
sub visibility_values
|
|
|
|
{
|
|
|
|
my $self = shift;
|
|
|
|
my $f;
|
2010-10-27 19:53:21 +04:00
|
|
|
if ($self->field->value_field_id && !($f = $self->{visibility_values}))
|
2009-09-07 20:47:01 +04:00
|
|
|
{
|
2010-10-27 19:53:21 +04:00
|
|
|
my $hash = Bugzilla->fieldvaluecontrol_hash
|
|
|
|
->{$self->field->value_field_id}
|
|
|
|
->{values}
|
|
|
|
->{$self->field->id}
|
|
|
|
->{$self->id};
|
|
|
|
$f = $hash ? [ keys %$hash ] : [];
|
2010-03-12 22:18:39 +03:00
|
|
|
if (@$f)
|
|
|
|
{
|
|
|
|
my $type = Bugzilla::Field::Choice->type($self->field->value_field);
|
2010-10-27 19:53:21 +04:00
|
|
|
$f = $type->new_from_list($f);
|
2010-03-12 22:18:39 +03:00
|
|
|
}
|
2009-09-07 20:47:01 +04:00
|
|
|
$self->{visibility_values} = $f;
|
|
|
|
}
|
|
|
|
return $f;
|
|
|
|
}
|
|
|
|
|
2010-03-12 22:18:39 +03:00
|
|
|
sub has_visibility_value
|
|
|
|
{
|
|
|
|
my $self = shift;
|
2010-11-24 20:22:45 +03:00
|
|
|
return 1 if $self->name eq '---';
|
2010-03-12 22:18:39 +03:00
|
|
|
my ($value) = @_;
|
2010-11-24 16:41:09 +03:00
|
|
|
return 1 if !$self->field->value_field_id;
|
|
|
|
$value = $value->id if ref $value;
|
|
|
|
my $hash = Bugzilla->fieldvaluecontrol_hash
|
2010-10-27 19:53:21 +04:00
|
|
|
->{$self->field->value_field_id}
|
|
|
|
->{values}
|
|
|
|
->{$self->field->id}
|
2010-11-24 16:41:09 +03:00
|
|
|
->{$self->id};
|
|
|
|
return !$hash || !%$hash || $hash->{$value};
|
|
|
|
}
|
|
|
|
|
|
|
|
# Check visibility of field value for a bug
|
|
|
|
sub check_visibility
|
|
|
|
{
|
|
|
|
my $self = shift;
|
2010-11-24 20:22:45 +03:00
|
|
|
return 1 if $self->name eq '---';
|
2010-11-24 16:41:09 +03:00
|
|
|
my $bug = shift || return 1;
|
|
|
|
my $vf = $self->field->value_field || return 1;
|
|
|
|
my $m = $vf->name;
|
|
|
|
$m = blessed $bug ? $bug->$m : $bug->{$m};
|
|
|
|
my $value = Bugzilla::Field::Choice->type($vf)->new({ name => $m }) || return 1;
|
|
|
|
return $self->has_visibility_value($value);
|
2010-03-12 22:18:39 +03:00
|
|
|
}
|
|
|
|
|
2009-07-29 15:21:49 +04:00
|
|
|
############
|
|
|
|
# Mutators #
|
|
|
|
############
|
|
|
|
|
2010-05-15 00:02:34 +04:00
|
|
|
sub set_is_active { $_[0]->set('isactive', $_[1]); }
|
2010-11-26 16:51:53 +03:00
|
|
|
*set_isactive = *set_is_active;
|
|
|
|
|
2010-05-15 00:02:34 +04:00
|
|
|
sub set_name { $_[0]->set('value', $_[1]); }
|
|
|
|
sub set_sortkey { $_[0]->set('sortkey', $_[1]); }
|
2009-09-07 20:47:01 +04:00
|
|
|
|
|
|
|
sub set_visibility_values
|
|
|
|
{
|
|
|
|
my $self = shift;
|
|
|
|
my ($value_ids) = @_;
|
2010-11-10 21:21:19 +03:00
|
|
|
update_visibility_values($self->field, $self->id, $value_ids);
|
2009-09-07 20:47:01 +04:00
|
|
|
delete $self->{visibility_values};
|
2010-11-10 21:21:19 +03:00
|
|
|
return 1;
|
2009-07-29 15:21:49 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
##############
|
|
|
|
# Validators #
|
|
|
|
##############
|
|
|
|
|
|
|
|
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)
|
|
|
|
|| ThrowUserError('fieldvalue_sortkey_invalid',
|
|
|
|
{ sortkey => $orig_value,
|
|
|
|
field => $invocant->field });
|
|
|
|
return $value;
|
|
|
|
}
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
=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 Accessors
|
|
|
|
|
|
|
|
These are in addition to the standard L<Bugzilla::Object> accessors.
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
|
|
|
=item C<sortkey>
|
|
|
|
|
|
|
|
The key that determines the sort order of this item.
|
|
|
|
|
|
|
|
=item C<field>
|
|
|
|
|
|
|
|
The L<Bugzilla::Field> object that this field value belongs to.
|
|
|
|
|
|
|
|
=item C<controlled_values>
|
|
|
|
|
|
|
|
Tells you which values in B<other> fields appear (become visible) when this
|
|
|
|
value is set in its field.
|
|
|
|
|
|
|
|
Returns a hashref of arrayrefs. The hash keys are the names of fields,
|
|
|
|
and the values are arrays of C<Bugzilla::Field::Choice> objects,
|
|
|
|
representing values that this value controls the visibility of, for
|
|
|
|
that field.
|
|
|
|
|
|
|
|
=back
|