Commit 8f33041e authored by Max Kanat-Alexander's avatar Max Kanat-Alexander

Bug 572602: Change the way that Bugzilla::Object determines what fields

are required for create(). It now assumes that any column that is NOT NULL and has not DEFAULT in the database is required. We also shift the burden of throwing errors about empty values to the validators. This fixes the bug that Bugzilla::Bug->create() wasn't populating default values for fields if they weren't specified in the create() parameters. r=timello, a=mkanat
parent 0a4878cc
...@@ -87,13 +87,9 @@ sub DB_COLUMNS { ...@@ -87,13 +87,9 @@ sub DB_COLUMNS {
$dbh->sql_date_format('attachments.creation_ts', '%Y.%m.%d %H:%i') . ' AS creation_ts'; $dbh->sql_date_format('attachments.creation_ts', '%Y.%m.%d %H:%i') . ' AS creation_ts';
} }
use constant REQUIRED_CREATE_FIELDS => qw( use constant REQUIRED_FIELD_MAP => {
bug bug_id => 'bug',
data };
description
filename
mimetype
);
use constant UPDATE_COLUMNS => qw( use constant UPDATE_COLUMNS => qw(
description description
...@@ -515,6 +511,10 @@ sub _check_bug { ...@@ -515,6 +511,10 @@ sub _check_bug {
my $user = Bugzilla->user; my $user = Bugzilla->user;
$bug = ref $invocant ? $invocant->bug : $bug; $bug = ref $invocant ? $invocant->bug : $bug;
$bug || ThrowCodeError('param_required',
{ function => "$invocant->create", param => 'bug' });
($user->can_see_bug($bug->id) && $user->can_edit_product($bug->product_id)) ($user->can_see_bug($bug->id) && $user->can_edit_product($bug->product_id))
|| ThrowUserError("illegal_attachment_edit_bug", { bug_id => $bug->id }); || ThrowUserError("illegal_attachment_edit_bug", { bug_id => $bug->id });
...@@ -526,7 +526,7 @@ sub _check_content_type { ...@@ -526,7 +526,7 @@ sub _check_content_type {
$content_type = 'text/plain' if (ref $invocant && ($invocant->isurl || $invocant->ispatch)); $content_type = 'text/plain' if (ref $invocant && ($invocant->isurl || $invocant->ispatch));
my $legal_types = join('|', LEGAL_CONTENT_TYPES); my $legal_types = join('|', LEGAL_CONTENT_TYPES);
if ($content_type !~ /^($legal_types)\/.+$/) { if (!$content_type or $content_type !~ /^($legal_types)\/.+$/) {
ThrowUserError("invalid_content_type", { contenttype => $content_type }); ThrowUserError("invalid_content_type", { contenttype => $content_type });
} }
trick_taint($content_type); trick_taint($content_type);
......
...@@ -115,15 +115,6 @@ sub DB_COLUMNS { ...@@ -115,15 +115,6 @@ sub DB_COLUMNS {
return @columns; return @columns;
} }
use constant REQUIRED_CREATE_FIELDS => qw(
component
product
short_desc
version
);
# There are also other, more complex validators that are called
# from run_create_validators.
sub VALIDATORS { sub VALIDATORS {
my $validators = { my $validators = {
...@@ -290,6 +281,11 @@ use constant FIELD_MAP => { ...@@ -290,6 +281,11 @@ use constant FIELD_MAP => {
offset => 'OFFSET', offset => 'OFFSET',
}; };
use constant REQUIRED_FIELD_MAP => {
product_id => 'product',
component_id => 'component',
};
##################################################################### #####################################################################
sub new { sub new {
...@@ -1696,8 +1692,8 @@ sub _check_reporter { ...@@ -1696,8 +1692,8 @@ sub _check_reporter {
else { else {
# On bug creation, the reporter is the logged in user # On bug creation, the reporter is the logged in user
# (meaning that he must be logged in first!). # (meaning that he must be logged in first!).
Bugzilla->login(LOGIN_REQUIRED);
$reporter = Bugzilla->user->id; $reporter = Bugzilla->user->id;
$reporter || ThrowCodeError('invalid_user');
} }
return $reporter; return $reporter;
} }
......
...@@ -40,10 +40,6 @@ use constant DB_COLUMNS => qw( ...@@ -40,10 +40,6 @@ use constant DB_COLUMNS => qw(
sortkey sortkey
); );
use constant REQUIRED_CREATE_FIELDS => qw(
name
);
use constant UPDATE_COLUMNS => qw( use constant UPDATE_COLUMNS => qw(
name name
description description
......
...@@ -47,13 +47,6 @@ use constant DB_COLUMNS => qw( ...@@ -47,13 +47,6 @@ use constant DB_COLUMNS => qw(
description description
); );
use constant REQUIRED_CREATE_FIELDS => qw(
name
product
initialowner
description
);
use constant UPDATE_COLUMNS => qw( use constant UPDATE_COLUMNS => qw(
name name
initialowner initialowner
...@@ -61,6 +54,10 @@ use constant UPDATE_COLUMNS => qw( ...@@ -61,6 +54,10 @@ use constant UPDATE_COLUMNS => qw(
description description
); );
use constant REQUIRED_FIELD_MAP => {
product_id => 'product',
};
use constant VALIDATORS => { use constant VALIDATORS => {
create_series => \&Bugzilla::Object::check_boolean, create_series => \&Bugzilla::Object::check_boolean,
product => \&_check_product, product => \&_check_product,
...@@ -234,6 +231,8 @@ sub _check_initialqacontact { ...@@ -234,6 +231,8 @@ sub _check_initialqacontact {
sub _check_product { sub _check_product {
my ($invocant, $product) = @_; my ($invocant, $product) = @_;
$product || ThrowCodeError('param_required',
{ function => "$invocant->create", param => 'product' });
return Bugzilla->user->check_can_admin_product($product->name); return Bugzilla->user->check_can_admin_product($product->name);
} }
......
...@@ -105,8 +105,6 @@ use constant DB_COLUMNS => qw( ...@@ -105,8 +105,6 @@ use constant DB_COLUMNS => qw(
is_mandatory is_mandatory
); );
use constant REQUIRED_CREATE_FIELDS => qw(name description sortkey);
use constant VALIDATORS => { use constant VALIDATORS => {
custom => \&_check_custom, custom => \&_check_custom,
description => \&_check_description, description => \&_check_description,
......
...@@ -55,8 +55,6 @@ use constant UPDATE_COLUMNS => qw( ...@@ -55,8 +55,6 @@ use constant UPDATE_COLUMNS => qw(
use constant NAME_FIELD => 'value'; use constant NAME_FIELD => 'value';
use constant LIST_ORDER => 'sortkey, value'; use constant LIST_ORDER => 'sortkey, value';
use constant REQUIRED_CREATE_FIELDS => qw(value);
use constant VALIDATORS => { use constant VALIDATORS => {
value => \&_check_value, value => \&_check_value,
sortkey => \&_check_sortkey, sortkey => \&_check_sortkey,
......
...@@ -87,14 +87,6 @@ use constant DB_COLUMNS => qw( ...@@ -87,14 +87,6 @@ use constant DB_COLUMNS => qw(
status status
); );
use constant REQUIRED_CREATE_FIELDS => qw(
attach_id
bug_id
setter_id
status
type_id
);
use constant UPDATE_COLUMNS => qw( use constant UPDATE_COLUMNS => qw(
requestee_id requestee_id
setter_id setter_id
...@@ -429,6 +421,7 @@ sub create { ...@@ -429,6 +421,7 @@ sub create {
$params->{$_} = $flag->{$_} foreach @columns; $params->{$_} = $flag->{$_} foreach @columns;
$params->{creation_date} = $params->{modification_date} = $timestamp; $params->{creation_date} = $params->{modification_date} = $timestamp;
$flag = $class->SUPER::create($params); $flag = $class->SUPER::create($params);
return $flag; return $flag;
} }
......
...@@ -60,8 +60,6 @@ use constant VALIDATORS => { ...@@ -60,8 +60,6 @@ use constant VALIDATORS => {
icon_url => \&_check_icon_url, icon_url => \&_check_icon_url,
}; };
use constant REQUIRED_CREATE_FIELDS => qw(name description isbuggroup);
use constant UPDATE_COLUMNS => qw( use constant UPDATE_COLUMNS => qw(
name name
description description
......
...@@ -35,8 +35,6 @@ use constant DB_COLUMNS => qw( ...@@ -35,8 +35,6 @@ use constant DB_COLUMNS => qw(
use constant DB_TABLE => 'keyworddefs'; use constant DB_TABLE => 'keyworddefs';
use constant REQUIRED_CREATE_FIELDS => qw(name description);
use constant VALIDATORS => { use constant VALIDATORS => {
name => \&_check_name, name => \&_check_name,
description => \&_check_description, description => \&_check_description,
...@@ -106,7 +104,9 @@ sub _check_name { ...@@ -106,7 +104,9 @@ sub _check_name {
my ($self, $name) = @_; my ($self, $name) = @_;
$name = trim($name); $name = trim($name);
$name eq "" && ThrowUserError("keyword_blank_name"); if (!defined $name or $name eq "") {
ThrowUserError("keyword_blank_name");
}
if ($name =~ /[\s,]/) { if ($name =~ /[\s,]/) {
ThrowUserError("keyword_invalid_name"); ThrowUserError("keyword_invalid_name");
} }
...@@ -124,7 +124,9 @@ sub _check_name { ...@@ -124,7 +124,9 @@ sub _check_name {
sub _check_description { sub _check_description {
my ($self, $desc) = @_; my ($self, $desc) = @_;
$desc = trim($desc); $desc = trim($desc);
$desc eq '' && ThrowUserError("keyword_blank_description"); if (!defined $desc or $desc eq '') {
ThrowUserError("keyword_blank_description");
}
return $desc; return $desc;
} }
......
...@@ -45,10 +45,9 @@ use constant DB_COLUMNS => qw( ...@@ -45,10 +45,9 @@ use constant DB_COLUMNS => qw(
sortkey sortkey
); );
use constant REQUIRED_CREATE_FIELDS => qw( use constant REQUIRED_FIELD_MAP => {
value product_id => 'product',
product };
);
use constant UPDATE_COLUMNS => qw( use constant UPDATE_COLUMNS => qw(
value value
...@@ -195,6 +194,8 @@ sub _check_sortkey { ...@@ -195,6 +194,8 @@ sub _check_sortkey {
sub _check_product { sub _check_product {
my ($invocant, $product) = @_; my ($invocant, $product) = @_;
$product || ThrowCodeError('param_required',
{ function => "$invocant->create", param => "product" });
return Bugzilla->user->check_can_admin_product($product->name); return Bugzilla->user->check_can_admin_product($product->name);
} }
......
...@@ -39,6 +39,8 @@ use constant UPDATE_VALIDATORS => {}; ...@@ -39,6 +39,8 @@ use constant UPDATE_VALIDATORS => {};
use constant NUMERIC_COLUMNS => (); use constant NUMERIC_COLUMNS => ();
use constant DATE_COLUMNS => (); use constant DATE_COLUMNS => ();
use constant VALIDATOR_DEPENDENCIES => {}; use constant VALIDATOR_DEPENDENCIES => {};
# XXX At some point, this will be joined with FIELD_MAP.
use constant REQUIRED_FIELD_MAP => {};
# This allows the JSON-RPC interface to return Bugzilla::Object instances # This allows the JSON-RPC interface to return Bugzilla::Object instances
# as though they were hashes. In the future, this may be modified to return # as though they were hashes. In the future, this may be modified to return
...@@ -447,10 +449,9 @@ sub check_required_create_fields { ...@@ -447,10 +449,9 @@ sub check_required_create_fields {
Bugzilla::Hook::process('object_before_create', { class => $class, Bugzilla::Hook::process('object_before_create', { class => $class,
params => $params }); params => $params });
foreach my $field ($class->REQUIRED_CREATE_FIELDS) { my @check_fields = $class->_required_create_fields();
ThrowCodeError('param_required', foreach my $field (@check_fields) {
{ function => "${class}->create", param => $field }) $params->{$field} = undef if !exists $params->{$field};
if !exists $params->{$field};
} }
} }
...@@ -616,6 +617,30 @@ sub _get_validators { ...@@ -616,6 +617,30 @@ sub _get_validators {
return $cache->{$cache_key}; return $cache->{$cache_key};
} }
# These are all the fields that need to be checked, always, when
# calling create(), because they have no DEFAULT and they are marked
# NOT NULL.
sub _required_create_fields {
my $class = shift;
my $dbh = Bugzilla->dbh;
my $table = $class->DB_TABLE;
my @columns = $dbh->bz_table_columns($table);
my @required;
foreach my $column (@columns) {
my $def = $dbh->bz_column_info($table, $column);
if ($def->{NOTNULL} and !defined $def->{DEFAULT}
# SERIAL fields effectively have a DEFAULT, but they're not
# listed as having a DEFAULT in DB::Schema.
and $def->{TYPE} !~ /serial/i)
{
my $field = $class->REQUIRED_FIELD_MAP->{$column} || $column;
push(@required, $field);
}
}
return @required;
}
1; 1;
__END__ __END__
...@@ -687,11 +712,6 @@ The order that C<new_from_list> and C<get_all> should return objects ...@@ -687,11 +712,6 @@ The order that C<new_from_list> and C<get_all> should return objects
in. This should be the name of a database column. Defaults to in. This should be the name of a database column. Defaults to
L</NAME_FIELD>. L</NAME_FIELD>.
=item C<REQUIRED_CREATE_FIELDS>
The list of fields that B<must> be specified when the user calls
C<create()>. This should be an array.
=item C<VALIDATORS> =item C<VALIDATORS>
A hashref that points to a function that will validate each param to A hashref that points to a function that will validate each param to
...@@ -742,6 +762,15 @@ A list of columns to update when L</update> is called. ...@@ -742,6 +762,15 @@ A list of columns to update when L</update> is called.
If a field can't be changed, it shouldn't be listed here. (For example, If a field can't be changed, it shouldn't be listed here. (For example,
the L</ID_FIELD> usually can't be updated.) the L</ID_FIELD> usually can't be updated.)
=item C<REQUIRED_FIELD_MAP>
This is a hashref that maps database column names to L</create> argument
names. You only need to specify values for fields where the argument passed
to L</create> has a different name in the database than it does in the
L</create> arguments. (For example, L<Bugzilla::Bug/create> takes a
C<product> argument, but the column name in the C<bugs> table is
C<product_id>.)
=item C<NUMERIC_COLUMNS> =item C<NUMERIC_COLUMNS>
When L</update> is called, it compares each column in the object to its When L</update> is called, it compares each column in the object to its
...@@ -915,17 +944,13 @@ Description: Creates a new item in the database. ...@@ -915,17 +944,13 @@ Description: Creates a new item in the database.
are invalid. are invalid.
Params: C<$params> - hashref - A value to put in each database Params: C<$params> - hashref - A value to put in each database
field for this object. Certain values must be set (the field for this object.
ones specified in L</REQUIRED_CREATE_FIELDS>), and
the function will throw a Code Error if you don't set
them.
Returns: The Object just created in the database. Returns: The Object just created in the database.
Notes: In order for this function to work in your subclass, Notes: In order for this function to work in your subclass,
your subclass's L</ID_FIELD> must be of C<SERIAL> your subclass's L</ID_FIELD> must be of C<SERIAL>
type in the database. Your subclass also must type in the database.
define L</REQUIRED_CREATE_FIELDS> and L</VALIDATORS>.
Subclass Implementors: This function basically just Subclass Implementors: This function basically just
calls L</check_required_create_fields>, then calls L</check_required_create_fields>, then
...@@ -940,8 +965,10 @@ Notes: In order for this function to work in your subclass, ...@@ -940,8 +965,10 @@ Notes: In order for this function to work in your subclass,
=item B<Description> =item B<Description>
Part of L</create>. Throws an error if any of the L</REQUIRED_CREATE_FIELDS> Part of L</create>. Modifies the incoming C<$params> argument so that
have not been specified in C<$params> any field that does not have a database default will be checked
later by L</run_create_validators>, even if that field wasn't specified
as an argument to L</create>.
=item B<Params> =item B<Params>
......
...@@ -52,12 +52,6 @@ use constant DB_COLUMNS => qw( ...@@ -52,12 +52,6 @@ use constant DB_COLUMNS => qw(
allows_unconfirmed allows_unconfirmed
); );
use constant REQUIRED_CREATE_FIELDS => qw(
name
description
version
);
use constant UPDATE_COLUMNS => qw( use constant UPDATE_COLUMNS => qw(
name name
description description
......
...@@ -34,8 +34,6 @@ use Bugzilla::Util; ...@@ -34,8 +34,6 @@ use Bugzilla::Util;
use constant DB_TABLE => 'profile_search'; use constant DB_TABLE => 'profile_search';
use constant LIST_ORDER => 'id'; use constant LIST_ORDER => 'id';
use constant REQUIRED_CREATE_FIELDS => ();
use constant DB_COLUMNS => qw( use constant DB_COLUMNS => qw(
id id
user_id user_id
...@@ -120,7 +118,7 @@ sub _check_user_id { ...@@ -120,7 +118,7 @@ sub _check_user_id {
sub _check_bug_list { sub _check_bug_list {
my ($invocant, $list) = @_; my ($invocant, $list) = @_;
my @bug_ids = ref($list) ? @$list : split(',', $list); my @bug_ids = ref($list) ? @$list : split(',', $list || '');
detaint_natural($_) foreach @bug_ids; detaint_natural($_) foreach @bug_ids;
return join(',', @bug_ids); return join(',', @bug_ids);
} }
......
...@@ -48,8 +48,6 @@ use constant DB_COLUMNS => qw( ...@@ -48,8 +48,6 @@ use constant DB_COLUMNS => qw(
query_type query_type
); );
use constant REQUIRED_CREATE_FIELDS => qw(name query);
use constant VALIDATORS => { use constant VALIDATORS => {
name => \&_check_name, name => \&_check_name,
query => \&_check_query, query => \&_check_query,
......
...@@ -102,8 +102,6 @@ use constant NAME_FIELD => 'login_name'; ...@@ -102,8 +102,6 @@ use constant NAME_FIELD => 'login_name';
use constant ID_FIELD => 'userid'; use constant ID_FIELD => 'userid';
use constant LIST_ORDER => NAME_FIELD; use constant LIST_ORDER => NAME_FIELD;
use constant REQUIRED_CREATE_FIELDS => qw(login_name cryptpassword);
use constant VALIDATORS => { use constant VALIDATORS => {
cryptpassword => \&_check_password, cryptpassword => \&_check_password,
disable_mail => \&_check_disable_mail, disable_mail => \&_check_disable_mail,
......
...@@ -592,8 +592,11 @@ sub is_7bit_clean { ...@@ -592,8 +592,11 @@ sub is_7bit_clean {
} }
sub clean_text { sub clean_text {
my ($dtext) = shift; my $dtext = shift;
$dtext =~ s/[\x00-\x1F\x7F]+/ /g; # change control characters into a space if ($dtext) {
# change control characters into a space
$dtext =~ s/[\x00-\x1F\x7F]+/ /g;
}
return trim($dtext); return trim($dtext);
} }
......
...@@ -46,10 +46,9 @@ use constant DB_COLUMNS => qw( ...@@ -46,10 +46,9 @@ use constant DB_COLUMNS => qw(
product_id product_id
); );
use constant REQUIRED_CREATE_FIELDS => qw( use constant REQUIRED_FIELD_MAP => {
value product_id => 'product',
product };
);
use constant UPDATE_COLUMNS => qw( use constant UPDATE_COLUMNS => qw(
value value
...@@ -188,6 +187,8 @@ sub _check_value { ...@@ -188,6 +187,8 @@ sub _check_value {
sub _check_product { sub _check_product {
my ($invocant, $product) = @_; my ($invocant, $product) = @_;
$product || ThrowCodeError('param_required',
{ function => "$invocant->create", param => 'product' });
return Bugzilla->user->check_can_admin_product($product->name); return Bugzilla->user->check_can_admin_product($product->name);
} }
......
...@@ -42,8 +42,6 @@ use constant DB_COLUMNS => qw( ...@@ -42,8 +42,6 @@ use constant DB_COLUMNS => qw(
mailto_type mailto_type
); );
use constant REQUIRED_CREATE_FIELDS => qw(eventid mailto mailto_type);
use constant UPDATE_COLUMNS => qw( use constant UPDATE_COLUMNS => qw(
eventid eventid
run_day run_day
......
...@@ -152,13 +152,6 @@ sub post_bug { ...@@ -152,13 +152,6 @@ sub post_bug {
my $user = Bugzilla->user; my $user = Bugzilla->user;
# Bugzilla::Bug->create throws a confusing CodeError if
# the REQUIRED_CREATE_FIELDS are missing, but much more
# sensible errors if the fields exist but are just undef.
foreach my $field (Bugzilla::Bug::REQUIRED_CREATE_FIELDS) {
$fields->{$field} = undef if !exists $fields->{$field};
}
my ($retval, $non_conclusive_fields) = my ($retval, $non_conclusive_fields) =
Bugzilla::User::match_field({ Bugzilla::User::match_field({
'assigned_to' => { 'type' => 'single' }, 'assigned_to' => { 'type' => 'single' },
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment