From 199d6ed76f022232c3799036c75661604a6d70d4 Mon Sep 17 00:00:00 2001 From: "myk%mozilla.org" <> Date: Fri, 6 Jan 2006 22:22:55 +0000 Subject: Bug 287325: an initial implementation of custom fields, including the ability to add text custom fields via the command-line script customfield.pl, search them via the boolean charts, display and edit them on the show bug page, and see changes to them in bug activity; r=mkanat, glob --- Bugzilla/Bug.pm | 13 +- Bugzilla/Constants.pm | 13 ++ Bugzilla/DB/Schema.pm | 5 + Bugzilla/Field.pm | 342 +++++++++++++++++++++++++++++++++++++++++++------- 4 files changed, 326 insertions(+), 47 deletions(-) (limited to 'Bugzilla') diff --git a/Bugzilla/Bug.pm b/Bugzilla/Bug.pm index 61798f7cb..cfa5f49f6 100755 --- a/Bugzilla/Bug.pm +++ b/Bugzilla/Bug.pm @@ -90,6 +90,8 @@ sub fields { push @fields, qw(estimated_time remaining_time actual_time deadline); } + push(@fields, Bugzilla->custom_field_names); + return @fields; } @@ -162,6 +164,11 @@ sub initBug { $self->{'who'} = new Bugzilla::User($user_id); + my $custom_fields = ""; + if (length(Bugzilla->custom_field_names) > 0) { + $custom_fields = ", " . join(", ", Bugzilla->custom_field_names); + } + my $query = " SELECT bugs.bug_id, alias, products.classification_id, classifications.name, @@ -175,7 +182,8 @@ sub initBug { delta_ts, COALESCE(SUM(votes.vote_count), 0), reporter_accessible, cclist_accessible, estimated_time, remaining_time, " . - $dbh->sql_date_format('deadline', '%Y-%m-%d') . " + $dbh->sql_date_format('deadline', '%Y-%m-%d') . + $custom_fields . " FROM bugs LEFT JOIN votes ON bugs.bug_id = votes.bug_id @@ -212,7 +220,8 @@ sub initBug { "target_milestone", "qa_contact_id", "status_whiteboard", "creation_ts", "delta_ts", "votes", "reporter_accessible", "cclist_accessible", - "estimated_time", "remaining_time", "deadline") + "estimated_time", "remaining_time", "deadline", + Bugzilla->custom_field_names) { $fields{$field} = shift @row; if (defined $fields{$field}) { diff --git a/Bugzilla/Constants.pm b/Bugzilla/Constants.pm index 09717486e..6ff7d8fa9 100644 --- a/Bugzilla/Constants.pm +++ b/Bugzilla/Constants.pm @@ -91,6 +91,9 @@ use base qw(Exporter); ADMIN_GROUP_NAME SENDMAIL_EXE + + FIELD_TYPE_UNKNOWN + FIELD_TYPE_FREETEXT ); @Bugzilla::Constants::EXPORT_OK = qw(contenttypes); @@ -243,4 +246,14 @@ use constant ADMIN_GROUP_NAME => 'admin'; # Path to sendmail.exe (Windows only) use constant SENDMAIL_EXE => '/usr/lib/sendmail.exe'; +# Field types. Match values in fielddefs.type column. These are purposely +# not named after database column types, since Bugzilla fields comprise not +# only storage but also logic. For example, we might add a "user" field type +# whose values are stored in an integer column in the database but for which +# we do more than we would do for a standard integer type (f.e. we might +# display a user picker). + +use constant FIELD_TYPE_UNKNOWN => 0; +use constant FIELD_TYPE_FREETEXT => 1; + 1; diff --git a/Bugzilla/DB/Schema.pm b/Bugzilla/DB/Schema.pm index 63b19578d..3caeba707 100644 --- a/Bugzilla/DB/Schema.pm +++ b/Bugzilla/DB/Schema.pm @@ -36,6 +36,7 @@ package Bugzilla::DB::Schema; use strict; use Bugzilla::Error; use Bugzilla::Util; +use Bugzilla::Constants; use Safe; # Historical, needed for SCHEMA_VERSION = '1.00' @@ -453,6 +454,10 @@ use constant ABSTRACT_SCHEMA => { fieldid => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1}, name => {TYPE => 'varchar(64)', NOTNULL => 1}, + type => {TYPE => 'INT2', NOTNULL => 1, + DEFAULT => FIELD_TYPE_UNKNOWN}, + custom => {TYPE => 'BOOLEAN', NOTNULL => 1, + DEFAULT => 'FALSE'}, description => {TYPE => 'MEDIUMTEXT', NOTNULL => 1}, mailhead => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}, diff --git a/Bugzilla/Field.pm b/Bugzilla/Field.pm index 09c4731ac..8585ff760 100644 --- a/Bugzilla/Field.pm +++ b/Bugzilla/Field.pm @@ -14,6 +14,57 @@ # # Contributor(s): Dan Mosedale # Frédéric Buclin +# Myk Melez + +=head1 NAME + +Bugzilla::Field - a particular piece of information about bugs + and useful routines for form field manipulation + +=head1 SYNOPSIS + + use Bugzilla; + use Data::Dumper; + + # Display information about all fields. + print Dumper(Bugzilla->get_fields()); + + # Display information about non-obsolete custom fields. + print Dumper(Bugzilla->get_fields({ obsolete => 1, custom => 1 })); + + # Display a list of the names of non-obsolete custom fields. + print Bugzilla->custom_field_names; + + use Bugzilla::Field; + + # Display information about non-obsolete custom fields. + # Bugzilla->get_fields() is a wrapper around Bugzilla::Field::match(), + # so both methods take the same arguments. + print Dumper(Bugzilla::Field::match({ obsolete => 1, custom => 1 })); + + # Create a custom field. + my $field = Bugzilla::Field::create("hilarity", "Hilarity"); + print "$field->{description} is a custom field\n"; + + # Instantiate a Field object for an existing field. + my $field = new Bugzilla::Field('qacontact_accessible'); + if ($field->{obsolete}) { + print "$field->{description} is obsolete\n"; + } + + # Validation Routines + check_form_field($cgi, $fieldname, \@legal_values); + check_form_field_defined($cgi, $fieldname); + $fieldid = get_field_id($fieldname); + +=head1 DESCRIPTION + +Field.pm defines field objects, which represent the particular pieces +of information that Bugzilla stores about bugs. + +This package also provides functions for dealing with CGI form fields. + +=cut package Bugzilla::Field; @@ -24,73 +75,214 @@ use base qw(Exporter); get_field_id); use Bugzilla::Util; +use Bugzilla::Constants; use Bugzilla::Error; +use constant DB_COLUMNS => ( + 'fieldid AS id', + 'name', + 'description', + 'type', + 'custom', + 'obsolete' +); + +our $columns = join(", ", DB_COLUMNS); + +sub new { + my $invocant = shift; + my $name = shift; + my $self = shift || Bugzilla->dbh->selectrow_hashref( + "SELECT $columns FROM fielddefs WHERE name = ?", + undef, + $name + ); + bless($self, $invocant); + return $self; +} -sub check_form_field { - my ($cgi, $fieldname, $legalsRef) = @_; - my $dbh = Bugzilla->dbh; +=pod - if (!defined $cgi->param($fieldname) - || trim($cgi->param($fieldname)) eq "" - || (defined($legalsRef) - && lsearch($legalsRef, $cgi->param($fieldname)) < 0)) - { - trick_taint($fieldname); - my ($result) = $dbh->selectrow_array("SELECT description FROM fielddefs - WHERE name = ?", undef, $fieldname); - - my $field = $result || $fieldname; - ThrowCodeError("illegal_field", { field => $field }); - } -} +=head2 Instance Properties -sub check_form_field_defined { - my ($cgi, $fieldname) = @_; +=over - if (!defined $cgi->param($fieldname)) { - ThrowCodeError("undefined_field", { field => $fieldname }); - } -} +=item C + +the unique identifier for the field; + +=back + +=cut + +sub id { return $_[0]->{id} } + +=over + +=item C + +the name of the field in the database; begins with "cf_" if field +is a custom field, but test the value of the boolean "custom" property +to determine if a given field is a custom field; + +=back + +=cut + +sub name { return $_[0]->{name} } + +=over + +=item C + +a short string describing the field; displayed to Bugzilla users +in several places within Bugzilla's UI, f.e. as the form field label +on the "show bug" page; + +=back + +=cut + +sub description { return $_[0]->{description} } + +=over + +=item C + +an integer specifying the kind of field this is; values correspond to +the FIELD_TYPE_* constants in Constants.pm + +=back + +=cut + +sub type { return $_[0]->{type} } + +=over + +=item C + +a boolean specifying whether or not the field is a custom field; +if true, field name should start "cf_", but use this property to determine +which fields are custom fields; + +=back + +=cut + +sub custom { return $_[0]->{custom} } + +=over + +=item C + +a boolean specifying whether or not the field is obsolete; + +=back + +=cut + +sub obsolete { return $_[0]->{obsolete} } + + +=pod + +=head2 Class Methods + +=over + +=item C + +Description: creates a new custom field. + +Params: C<$name> - string - the name of the field; + C<$desc> - string - the field label to display in the UI. + +Returns: a field object. + +=back + +=cut + +sub create { + my ($name, $desc, $custom) = @_; + + # Convert the $custom argument into a DB-compatible value. + $custom = $custom ? 1 : 0; -sub get_field_id { - my ($name) = @_; my $dbh = Bugzilla->dbh; - trick_taint($name); - my $id = $dbh->selectrow_array('SELECT fieldid FROM fielddefs - WHERE name = ?', undef, $name); + # Some day we'll allow invocants to specify the sort key. + my ($sortkey) = + $dbh->selectrow_array("SELECT MAX(sortkey) + 1 FROM fielddefs"); - ThrowCodeError('invalid_field_name', {field => $name}) unless $id; - return $id + # Some day we'll require invocants to specify the field type. + my $type = FIELD_TYPE_FREETEXT; + + # Create the database column that stores the data for this field. + $dbh->bz_add_column("bugs", $name, { TYPE => 'varchar(255)' }); + + # Add the field to the list of fields at this Bugzilla installation. + my $sth = $dbh->prepare( + "INSERT INTO fielddefs (name, description, sortkey, type, + custom, mailhead) + VALUES (?, ?, ?, ?, ?, 1)" + ); + $sth->execute($name, $desc, $sortkey, $type, $custom); + + return new Bugzilla::Field($name); } -1; -__END__ +=pod -=head1 NAME +=over -Bugzilla::Field - Useful routines for fields manipulation +=item C -=head1 SYNOPSIS +Description: returns a list of fields that match the specified criteria. - use Bugzilla::Field; +Params: C<$criteria> - hash reference - the criteria to match against. + Hash keys represent field properties; hash values represent + their values. All criteria are optional. Valid criteria are + "custom" and "obsolete", and both take boolean values. - # Validation Routines - check_form_field($cgi, $fieldname, \@legal_values); - check_form_field_defined($cgi, $fieldname); - $fieldid = get_field_id($fieldname); + Note: Bugzilla->get_fields() and Bugzilla->custom_field_names + wrap this method for most callers. -=head1 DESCRIPTION +Returns: a list of field objects. -This package provides functions for dealing with CGI form fields. +=back -=head1 FUNCTIONS +=cut -This package provides several types of routines: +sub match { + my ($criteria) = @_; + + my @terms; + if (defined $criteria->{name}) { + push(@terms, "name=" . Bugzilla->dbh->quote($criteria->{name})); + } + if (defined $criteria->{custom}) { + push(@terms, "custom=" . ($criteria->{custom} ? "1" : "0")); + } + if (defined $criteria->{obsolete}) { + push(@terms, "obsolete=" . ($criteria->{obsolete} ? "1" : "0")); + } + my $where = (scalar(@terms) > 0) ? "WHERE " . join(" AND ", @terms) : ""; + + my $records = Bugzilla->dbh->selectall_arrayref( + "SELECT $columns FROM fielddefs $where ORDER BY sortkey", + { Slice => {}} + ); + # Generate a array of field objects from the array of field records. + my @fields = map( new Bugzilla::Field(undef, $_), @$records ); + return @fields; +} + +=pod -=head2 Validation +=head2 Data Validation =over @@ -108,6 +300,32 @@ Params: $cgi - a CGI object Returns: nothing +=back + +=cut + +sub check_form_field { + my ($cgi, $fieldname, $legalsRef) = @_; + my $dbh = Bugzilla->dbh; + + if (!defined $cgi->param($fieldname) + || trim($cgi->param($fieldname)) eq "" + || (defined($legalsRef) + && lsearch($legalsRef, $cgi->param($fieldname)) < 0)) + { + trick_taint($fieldname); + my ($result) = $dbh->selectrow_array("SELECT description FROM fielddefs + WHERE name = ?", undef, $fieldname); + + my $field = $result || $fieldname; + ThrowCodeError("illegal_field", { field => $field }); + } +} + +=pod + +=over + =item C Description: Makes sure the field $fieldname is defined and its value @@ -118,14 +336,48 @@ Params: $cgi - a CGI object Returns: nothing +=back + +=cut + +sub check_form_field_defined { + my ($cgi, $fieldname) = @_; + + if (!defined $cgi->param($fieldname)) { + ThrowCodeError("undefined_field", { field => $fieldname }); + } +} + +=pod + +=over + =item C Description: Returns the ID of the specified field name and throws an error if this field does not exist. -Params: $fieldname - a field name +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) = @_; + my $dbh = Bugzilla->dbh; + + trick_taint($name); + my $id = $dbh->selectrow_array('SELECT fieldid FROM fielddefs + WHERE name = ?', undef, $name); + + ThrowCodeError('invalid_field_name', {field => $name}) unless $id; + return $id +} + +1; + +__END__ -- cgit v1.2.3-24-g4f1b