Add clone_field_id attribute, make enter_bug.cgi honor it, remove obsolete APIs field.new_choice and get_field_id

hinted-selects
Vitaliy Filippov 2014-07-08 15:44:05 +04:00
parent 3a45e17953
commit c2c97c06af
15 changed files with 134 additions and 165 deletions

View File

@ -933,10 +933,16 @@ sub fieldvaluecontrol
if (!$cache->{fieldvaluecontrol})
{
my $rows = $class->dbh->selectall_arrayref(
'SELECT c.*, (CASE WHEN c.value_id=-1 THEN f.null_field_id'.
' WHEN c.value_id=0 THEN f.visibility_field_id ELSE f.value_field_id END) visibility_field_id'.
'SELECT c.*, (CASE WHEN c.value_id='.FLAG_CLONED.' THEN f.clone_field_id'.
' WHEN c.value_id='.FLAG_NULLABLE.' THEN f.null_field_id'.
' WHEN c.value_id='.FLAG_VISIBLE.' THEN f.visibility_field_id ELSE f.value_field_id END) visibility_field_id'.
' FROM fieldvaluecontrol c, fielddefs f WHERE f.id=c.field_id', {Slice=>{}}
);
my $keys = {
FLAG_VISIBLE() => 'fields',
FLAG_NULLABLE() => 'null',
FLAG_CLONED() => 'clone',
};
my $has = {};
for (@$rows)
{
@ -949,19 +955,11 @@ sub fieldvaluecontrol
->{$_->{value_id}}
->{$_->{visibility_value_id}} = 1;
}
elsif (!$_->{value_id})
else
{
# Show field if visibility_field==visibility_value_id
# Show field / allow NULL / clone value if visibility_field==visibility_value_id
$has->{$_->{visibility_field_id}}
->{fields}
->{$_->{field_id}}
->{$_->{visibility_value_id}} = 1;
}
elsif ($_->{value_id} == -1)
{
# Allow NULL if visibility_field==visibility_value_id
$has->{$_->{visibility_field_id}}
->{null}
->{$keys->{$_->{value_id}}}
->{$_->{field_id}}
->{$_->{visibility_value_id}} = 1;
}

View File

@ -1064,8 +1064,8 @@ sub transform_id_changes
{
my $name = $f->name;
next if $name eq 'product' || $name eq 'component' || $name eq 'classification' || !$changes->{$name};
$changes->{$name}->[0] = $f->new_choice($changes->{$name}->[0])->name if $changes->{$name}->[0];
$changes->{$name}->[1] = $f->new_choice($changes->{$name}->[1])->name if $changes->{$name}->[1];
$changes->{$name}->[0] = $self->{_old_self}->get_string($name) if $changes->{$name}->[0];
$changes->{$name}->[1] = $self->get_string($name) if $changes->{$name}->[1];
}
# Transform user IDs to names
@ -4132,7 +4132,8 @@ sub get_string
my $value;
if ($field && $field->type == FIELD_TYPE_SINGLE_SELECT)
{
$value = $self->get_object($f) && $self->get_object($f)->name;
$value = $self->get_object($f);
$value = $value && $value->name;
}
elsif ($field && $field->type == FIELD_TYPE_MULTI_SELECT)
{

View File

@ -48,7 +48,7 @@ sub check_resolution
{
my $resolution = shift;
my $f;
if (!$f->new_choice({ name => $resolution }))
if (!$f->value_type->new({ name => $resolution }))
{
return "Must be a valid resolution: one of " . join(', ', $f->legal_value_names);
}

View File

@ -132,6 +132,10 @@ use Cwd qw(abs_path);
FIELD_TYPE_EXTURL
FIELD_TYPE_BUG_ID_REV
FLAG_VISIBLE
FLAG_NULLABLE
FLAG_CLONED
TIMETRACKING_FIELDS
USAGE_MODE_BROWSER
@ -385,6 +389,10 @@ use constant FIELD_TYPE_NUMERIC => 30;
use constant FIELD_TYPE_EXTURL => 31;
use constant FIELD_TYPE_BUG_ID_REV => 32;
use constant FLAG_VISIBLE => 0;
use constant FLAG_NULLABLE => -1;
use constant FLAG_CLONED => -2;
use constant BUG_ID_ADD_TO_BLOCKED => 1;
use constant BUG_ID_ADD_TO_DEPENDSON => 2;

View File

@ -569,6 +569,7 @@ use constant ABSTRACT_SCHEMA => {
value_field_id => {TYPE => 'INT4', REFERENCES => {TABLE => 'fielddefs', COLUMN => 'id'}},
null_field_id => {TYPE => 'INT4', REFERENCES => {TABLE => 'fielddefs', COLUMN => 'id'}},
default_field_id => {TYPE => 'INT4', REFERENCES => {TABLE => 'fielddefs', COLUMN => 'id'}},
clone_field_id => {TYPE => 'INT4', REFERENCES => {TABLE => 'fielddefs', COLUMN => 'id'}},
],
INDEXES => [
fielddefs_name_idx => {FIELDS => ['name'], TYPE => 'UNIQUE'},

View File

@ -51,9 +51,6 @@ Bugzilla::Field - a particular piece of information about bugs
print $field->description . " is obsolete\n";
}
# Validation Routines
$fieldid = get_field_id($fieldname);
=head1 DESCRIPTION
Field.pm defines field objects, which represent the particular pieces
@ -72,7 +69,7 @@ package Bugzilla::Field;
use strict;
use base qw(Exporter Bugzilla::Object);
@Bugzilla::Field::EXPORT = qw(get_field_id update_visibility_values);
@Bugzilla::Field::EXPORT = qw(update_visibility_values);
use Bugzilla::Constants;
use Bugzilla::Error;
@ -111,6 +108,7 @@ use constant DB_COLUMNS => qw(
value_field_id
null_field_id
default_field_id
clone_field_id
);
use constant REQUIRED_CREATE_FIELDS => qw(name description);
@ -128,6 +126,7 @@ use constant VALIDATORS => {
add_to_deps => \&_check_add_to_deps,
null_field_id => \&_check_visibility_field_id,
default_field_id => \&_check_visibility_field_id,
clone_field_id => \&_check_visibility_field_id,
};
use constant UPDATE_VALIDATORS => {
@ -156,11 +155,11 @@ use constant SQL_DEFINITIONS => {
use constant DEFAULT_FIELD_COLUMNS => [ qw(name description is_mandatory mailhead clone_bug type value_field null_field default_field) ];
use constant DEFAULT_FIELDS => (map { my $i = 0; $_ = { (map { (DEFAULT_FIELD_COLUMNS->[$i++] => $_) } @$_) } } (
[ 'bug_id', 'Bug ID', 1, 1, 0 ],
[ 'short_desc', 'Summary', 1, 1, 0, FIELD_TYPE_FREETEXT ],
[ 'short_desc', 'Summary', 1, 1, 1, FIELD_TYPE_FREETEXT ],
[ 'classification', 'Classification', 1, 1, 0, FIELD_TYPE_SINGLE_SELECT ],
[ 'product', 'Product', 1, 1, 0, FIELD_TYPE_SINGLE_SELECT ],
[ 'version', 'Version', 0, 1, 1, FIELD_TYPE_SINGLE_SELECT, 'product', 'product', 'component' ],
[ 'rep_platform', 'Platform', 0, 1, 0, FIELD_TYPE_SINGLE_SELECT ],
[ 'rep_platform', 'Platform', 0, 1, 1, FIELD_TYPE_SINGLE_SELECT ],
[ 'bug_file_loc', 'URL', 0, 1, 1 ],
[ 'op_sys', 'OS/Version', 0, 1, 1, FIELD_TYPE_SINGLE_SELECT ],
[ 'bug_status', 'Status', 1, 1, 0, FIELD_TYPE_SINGLE_SELECT ],
@ -545,12 +544,6 @@ sub value_type
return Bugzilla::Field::Choice->type($self);
}
sub new_choice
{
my $self = shift;
return $self->value_type->new(@_);
}
# Includes disabled values is $include_disabled = true
sub legal_values
{
@ -596,37 +589,6 @@ sub restricted_legal_values
return $rc_cache->{$self}->{restricted_legal_values}->{$controller_value};
}
=pod
=over
=item C<visibility_field>
What field controls this field's visibility? Returns a C<Bugzilla::Field>
object representing the field that controls this field's visibility.
Returns undef if there is no field that controls this field's visibility.
=back
=cut
sub visibility_field
{
my $self = shift;
if ($self->{visibility_field_id})
{
return Bugzilla->get_field($self->{visibility_field_id});
}
return undef;
}
sub visibility_field_id
{
my $self = shift;
return $self->{visibility_field_id};
}
sub visibility_values
{
my $self = shift;
@ -656,6 +618,15 @@ sub null_visibility_values
return $h && %$h ? $h : undef;
}
sub clone_visibility_values
{
my $self = shift;
return undef if !$self->null_field_id;
my $h = Bugzilla->fieldvaluecontrol
->{$self->clone_field_id}->{clone}->{$self->id};
return $h && %$h ? $h : undef;
}
# Check visibility of field for a bug or for a hashref with default value names
sub check_visibility
{
@ -666,6 +637,7 @@ sub check_visibility
return $value ? $self->has_visibility_value($value) : 1;
}
# Check if a field is nullable for a bug or for a hashref with default value names
sub check_is_nullable
{
my $self = shift;
@ -677,6 +649,19 @@ sub check_is_nullable
return $value ? $self->null_visibility_values->{$value} : 1;
}
# Check if a field should be copied when cloning $bug
sub check_clone
{
my $self = shift;
$self->clone_bug || return 0;
my $vf = $self->clone_field || return 1;
$self->clone_visibility_values || return 1;
my $bug = shift || return 1;
my $value = bug_or_hash_value($bug, $vf);
return $value ? $self->clone_visibility_values->{$value} : 1;
}
# Get default value for this field in bug $bug
sub get_default
{
my $self = shift;
@ -720,29 +705,10 @@ sub controls_visibility_of
return $self->{controls_visibility_of};
}
=pod
=over
=item C<value_field>
The Bugzilla::Field that controls the list of values for this field.
Returns undef if there is no field that controls this field's visibility.
=back
=cut
sub value_field
{
my $self = shift;
if (my $id = $self->value_field_id)
{
$self->{value_field} ||= Bugzilla::Field->new($id);
}
return $self->{value_field};
}
sub visibility_field_id { $_[0]->{visibility_field_id} }
sub null_field_id { $_[0]->{null_field_id} }
sub default_field_id { $_[0]->{default_field_id} }
sub clone_field_id { $_[0]->{clone_field_id} }
sub value_field_id
{
@ -751,6 +717,30 @@ sub value_field_id
return $self->{value_field_id};
}
# Field that controls visibility of this one
sub visibility_field
{
my $self = shift;
if ($self->{visibility_field_id})
{
return Bugzilla->get_field($self->{visibility_field_id});
}
return undef;
}
# Field that controls values of this one, if this one is a select,
# and related direct BUG_ID field, if this one is BUG_ID_REV
sub value_field
{
my $self = shift;
if (my $id = $self->value_field_id)
{
return Bugzilla->get_field($id);
}
return undef;
}
# Field that allows/forbids empty value for this one
sub null_field
{
my $self = shift;
@ -761,12 +751,7 @@ sub null_field
return undef;
}
sub null_field_id
{
my $self = shift;
return $self->{null_field_id};
}
# Field that controls default values for this one
sub default_field
{
my $self = shift;
@ -777,10 +762,15 @@ sub default_field
return undef;
}
sub default_field_id
# Field that controls copying the value of this field when cloning
sub clone_field
{
my $self = shift;
return $self->{default_field_id};
if ($self->{clone_field_id})
{
return Bugzilla->get_field($self->{clone_field_id});
}
return undef;
}
=pod
@ -849,14 +839,13 @@ sub set_visibility_field
{
my ($self, $value) = @_;
$self->set('visibility_field_id', $value);
delete $self->{visibility_field};
}
sub set_visibility_values
{
my $self = shift;
my ($value_ids) = @_;
update_visibility_values($self, 0, $value_ids);
update_visibility_values($self, FLAG_VISIBLE, $value_ids);
return $value_ids && @$value_ids;
}
@ -864,7 +853,15 @@ sub set_null_visibility_values
{
my $self = shift;
my ($value_ids) = @_;
update_visibility_values($self, -1, $value_ids);
update_visibility_values($self, FLAG_NULLABLE, $value_ids);
return $value_ids && @$value_ids;
}
sub set_clone_visibility_values
{
my $self = shift;
my ($value_ids) = @_;
update_visibility_values($self, FLAG_CLONED, $value_ids);
return $value_ids && @$value_ids;
}
@ -872,21 +869,18 @@ sub set_value_field
{
my ($self, $value) = @_;
$self->set('value_field_id', $value);
delete $self->{value_field};
}
sub set_null_field
{
my ($self, $value) = @_;
$self->set('null_field_id', $value);
delete $self->{null_field};
}
sub set_default_field
{
my ($self, $value) = @_;
$self->set('default_field_id', $value);
delete $self->{default_field};
}
# This is only used internally by upgrade code in Bugzilla::Field.
@ -983,6 +977,8 @@ sub remove_from_db
}
$self->set_visibility_values(undef);
$self->set_null_visibility_values(undef);
$self->set_clone_visibility_values(undef);
# Update some other field (refresh the cache)
Bugzilla->get_field('delta_ts')->touch;
@ -1265,33 +1261,6 @@ sub populate_field_definitions
}
}
=pod
=over
=item C<get_field_id($fieldname)>
Description: Returns the ID of the specified field name and throws
an error if this field does not exist.
Params: $name - a field name
Returns: the corresponding field ID or an error if the field name
does not exist.
=back
=cut
sub get_field_id
{
my ($name) = @_;
trick_taint($name);
my $field = Bugzilla->get_field($name);
ThrowCodeError('invalid_field_name', { field => $name }) unless $field;
return $field->id;
}
# Get choice value object for a bug or for a hashref with default value names
sub bug_or_hash_value
{
@ -1316,15 +1285,21 @@ sub bug_or_hash_value
return $value;
}
sub flag_field
{
my ($self, $flag) = @_;
return $self->value_field if $flag > 0;
return $self->visibility_field if $flag == FLAG_VISIBLE;
return $self->null_field if $flag == FLAG_NULLABLE;
return $self->clone_field if $flag == FLAG_CLONED;
}
# Shared between Bugzilla::Field and Bugzilla::Field::Choice
sub update_visibility_values
{
my ($controlled_field, $controlled_value_id, $visibility_value_ids) = @_;
$visibility_value_ids ||= [];
my $vis_field = $controlled_value_id == -1
? $controlled_field->null_field : ($controlled_value_id > 0
? $controlled_field->value_field
: $controlled_field->visibility_field);
my $vis_field = $controlled_field->flag_field($controlled_value_id);
if (!$vis_field)
{
return undef;
@ -1373,8 +1348,7 @@ sub toggle_value
}
elsif (!$enable && !$any)
{
my $obj = Bugzilla->get_field($f);
$obj = $v > 0 ? $obj->value_field : ($v == 0 ? $obj->visibility_field : $obj->null_field);
my $obj = Bugzilla->get_field($f)->flag_field($v);
# FIXME: Taint bug (5.18.2): $obj->value_type->DB_TABLE becomes tainted
# and taints SQL query if substituted directly into dbh->do()...
my $t = $obj->value_type->DB_TABLE;

View File

@ -65,7 +65,7 @@ sub update_fielddefs_definition
# Add columns that don't require special logic
for my $c (qw(type custom clone_bug url is_mandatory add_to_deps default_value
visibility_field_id value_field_id null_field_id default_field_id))
visibility_field_id value_field_id null_field_id default_field_id clone_field_id))
{
$dbh->bz_add_column('fielddefs', $c);
}

View File

@ -2014,7 +2014,7 @@ sub changed
}
if (%f)
{
$ba_term->{where} .= " AND $ba.fieldid IN (".join(",", map { get_field_id($_) } keys %f).")";
$ba_term->{where} .= " AND $ba.fieldid IN (".join(",", map { Bugzilla->get_field($_)->id } keys %f).")";
$v->{fields} = [ keys %f ];
}
elsif ($any_fields)

View File

@ -1795,7 +1795,7 @@ sub create
# $who is the user who created the new user account, i.e. either an
# admin or the new user himself.
my $who = Bugzilla->user->id || $user->id;
my $creation_date_fieldid = get_field_id('creation_ts');
my $creation_date_fieldid = Bugzilla->get_field('creation_ts')->id;
$dbh->do('INSERT INTO profiles_activity
(userid, who, profiles_when, fieldid, newvalue)

View File

@ -175,7 +175,7 @@ my %dupe_relation = @{$dbh->selectcol_arrayref(
{Columns => [1,2]})};
add_indirect_dups(\%total_dups, \%dupe_relation);
my $reso_field_id = get_field_id('resolution');
my $reso_field_id = Bugzilla->get_field('resolution')->id;
my %since_dups = @{$dbh->selectcol_arrayref(
"SELECT dupe_of, COUNT(dupe)
FROM duplicates INNER JOIN bugs_activity

View File

@ -113,11 +113,9 @@ elsif ($action eq 'update')
$field->set_is_mandatory(!scalar $cgi->param('nullable'));
$field->set_url(scalar $cgi->param('url'));
$field->set_default_value($field->type == FIELD_TYPE_MULTI_SELECT ? [ $cgi->param('default_value') ] : scalar $cgi->param('default_value'));
$field->set_clone_bug(scalar $cgi->param('clone_bug'));
if ($field->custom)
{
# TODO enter_bug could be edited for non-custom fields, too.
# At the moment, though, it has no effect for non-custom fields.
$field->set_clone_bug($cgi->param('clone_bug'));
$field->set_value_field($cgi->param('value_field_id'));
$field->set_default_field($cgi->param('default_field_id'));
$field->set_add_to_deps($cgi->param('add_to_deps'));

View File

@ -332,7 +332,7 @@ if ($action eq 'search') {
},
undef,
($otherUserID, $userid,
get_field_id('bug_group'),
Bugzilla->get_field('bug_group')->id,
join(', ', @groupsRemovedFrom), join(', ', @groupsAddedTo)));
}
# XXX: should create profiles_activity entries for blesser changes.
@ -506,7 +506,7 @@ if ($action eq 'search') {
# so we have to log these changes manually.
my %bugs;
push(@{$bugs{$_->bug_id}->{$_->attach_id || 0}}, $_) foreach @$flags;
my $fieldid = get_field_id('flagtypes.name');
my $fieldid = Bugzilla->get_field('flagtypes.name')->id;
foreach my $bug_id (keys %bugs) {
foreach my $attach_id (keys %{$bugs{$bug_id}}) {
my @old_summaries = Bugzilla::Flag->snapshot($bugs{$bug_id}->{$attach_id});

View File

@ -197,6 +197,8 @@ sub pick_valid_field_value (@)
return undef;
}
# FIXME Regexps for platform and operating system should be stored in the database,
# probably in a property of Platform/OpSys objects.
sub pickplatform
{
my ($ARGS) = @_;
@ -477,22 +479,11 @@ $default{product_obj} = $product;
if ($cloned_bug_id)
{
$default{component_} = $cloned_bug->component;
$default{priority} = $cloned_bug->priority_obj->name if $cloned_bug->priority;
$default{bug_severity} = $cloned_bug->bug_severity_obj->name if $cloned_bug->bug_severity;
$default{rep_platform} = $cloned_bug->rep_platform_obj->name if Bugzilla->params->{useplatform} && $cloned_bug->rep_platform;
$default{op_sys} = $cloned_bug->op_sys_obj->name if Bugzilla->params->{useopsys} && $cloned_bug->op_sys;
$default{short_desc} = $cloned_bug->short_desc;
$default{bug_file_loc} = $cloned_bug->bug_file_loc;
$default{keywords} = $cloned_bug->keywords;
$default{dependson} = "";
$default{blocked} = $cloned_bug_id;
$default{deadline} = $cloned_bug->deadline;
$default{status_whiteboard} = $cloned_bug->status_whiteboard;
$default{dependson} = "";
$default{blocked} = $cloned_bug_id;
my @cc;
my $comp = Bugzilla::Component->new({ product => $product, name => $cloned_bug->component });
my $comp = $cloned_bug->component_obj;
if ($comp && $product->id != $cloned_bug->product_id)
{
@cc = map { $_->login } @{$comp->initial_cc || []};
@ -525,25 +516,23 @@ if ($cloned_bug_id)
$vars->{cc} = join ', ', @cc;
# Copy values of custom fields marked with 'clone_bug = TRUE'
# But don't copy values of custom fields which are invisible for the new product
my @clone_bug_fields = grep { $_->clone_bug &&
(!$_->visibility_field || $_->visibility_field->name ne 'product' ||
$_->has_visibility_value($product))
} Bugzilla->active_custom_fields;
foreach my $field (@clone_bug_fields)
# Copy values of fields marked with 'clone_bug = TRUE'
foreach my $field (Bugzilla->get_fields({ obsolete => 0, clone_bug => 1 }))
{
my $field_name = $field->name;
next if $field_name eq 'product' || $field_name eq 'classification' ||
$field->type == FIELD_TYPE_BUG_ID_REV ||
!$field->check_clone($cloned_bug);
if ($field->type == FIELD_TYPE_SINGLE_SELECT)
{
$default{$field_name} = $cloned_bug->get_object($field_name);
$default{$field_name} = $default{$field_name}->name if $default{$field_name};
# component is a keyword in TT... :-X.($field_name eq 'component' ? '_' : '')
$default{$field_name} = $cloned_bug->get_string($field_name);
}
elsif ($field->type == FIELD_TYPE_MULTI_SELECT)
{
$default{$field_name} = [ map { $_->name } @{ $cloned_bug->get_object($field_name) } ];
}
else
elsif (Bugzilla::Bug::_validate_attribute($field_name))
{
$default{$field_name} = $cloned_bug->$field_name;
}

View File

@ -107,7 +107,7 @@ var close_status_array = [
<select id="component" name="component" size="7" aria-required="true" class="required">
[%- FOREACH c = product.active_components %]
<option id="v[% c.id %]_component" value="[% c.name | html %]"
[% " selected=\"selected\"" IF c.name == default.component_ %]>
[% " selected=\"selected\"" IF c.name == default.component %]>
[% c.name | html -%]
</option>
[%- END %]

View File

@ -179,7 +179,7 @@
[% IF value.defined %]
[% FOR v = value %]
[%# FIXME use _obj accessor? %]
[% v = field.new_choice(v) %]
[% v = field.value_type.new(v) %]
<option selected="selected" value="[% v.name | html %]" id="v[% v.id | html %]_[% field.name | html %]">[% v.name | html %]</option>
[% END %]
[% END %]