summaryrefslogtreecommitdiffstats
path: root/Bugzilla
diff options
context:
space:
mode:
authorlpsolit%gmail.com <>2006-02-21 22:08:18 +0100
committerlpsolit%gmail.com <>2006-02-21 22:08:18 +0100
commitda9ac9431cc959eedef78a5118ac3b4c6fbf7d03 (patch)
tree840e490e59e6a28c083b31a7281877a74105b997 /Bugzilla
parent7780ace7e977d39ef9c904697e355248becf192b (diff)
downloadbugzilla-da9ac9431cc959eedef78a5118ac3b4c6fbf7d03.tar.gz
bugzilla-da9ac9431cc959eedef78a5118ac3b4c6fbf7d03.tar.xz
Bug 287325: Ability to add custom plain-text fields to a Bug - Patch by Myk Melez <myk@mozilla.org> r=mkanat a=justdave
Diffstat (limited to 'Bugzilla')
-rwxr-xr-xBugzilla/Bug.pm116
-rw-r--r--Bugzilla/Constants.pm13
-rw-r--r--Bugzilla/DB/Schema.pm5
-rw-r--r--Bugzilla/Field.pm342
4 files changed, 392 insertions, 84 deletions
diff --git a/Bugzilla/Bug.pm b/Bugzilla/Bug.pm
index cab54d7da..22f62f186 100755
--- a/Bugzilla/Bug.pm
+++ b/Bugzilla/Bug.pm
@@ -69,41 +69,6 @@ use constant MAX_COMMENT_LENGTH => 65535;
#####################################################################
-sub fields {
- # Keep this ordering in sync with bugzilla.dtd
- my @fields = qw(bug_id alias creation_ts short_desc delta_ts
- reporter_accessible cclist_accessible
- classification_id classification
- product component version rep_platform op_sys
- bug_status resolution
- bug_file_loc status_whiteboard keywords
- priority bug_severity target_milestone
- dependson blocked votes everconfirmed
- reporter assigned_to cc
- );
-
- if (Param('useqacontact')) {
- push @fields, "qa_contact";
- }
-
- if (Param('timetrackinggroup')) {
- push @fields, qw(estimated_time remaining_time actual_time deadline);
- }
-
- return @fields;
-}
-
-my %ok_field;
-foreach my $key (qw(error groups
- longdescs milestoneurl attachments
- isopened isunconfirmed
- flag_types num_attachment_flag_types
- show_attachment_flags use_keywords any_flags_requesteeble
- ),
- fields()) {
- $ok_field{$key}++;
-}
-
# create a new empty bug
#
sub new {
@@ -162,6 +127,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 +145,8 @@ sub initBug {
delta_ts, COALESCE(SUM(votes.vote_count), 0), everconfirmed,
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 +183,8 @@ sub initBug {
"target_milestone", "qa_contact_id", "status_whiteboard",
"creation_ts", "delta_ts", "votes", "everconfirmed",
"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}) {
@@ -290,8 +262,41 @@ sub remove_from_db {
return $self;
}
+
#####################################################################
-# Accessors
+# Class Accessors
+#####################################################################
+
+sub fields {
+ my $class = shift;
+
+ return (
+ # Standard Fields
+ # Keep this ordering in sync with bugzilla.dtd.
+ qw(bug_id alias creation_ts short_desc delta_ts
+ reporter_accessible cclist_accessible
+ classification_id classification
+ product component version rep_platform op_sys
+ bug_status resolution
+ bug_file_loc status_whiteboard keywords
+ priority bug_severity target_milestone
+ dependson blocked votes
+ reporter assigned_to cc),
+
+ # Conditional Fields
+ Param('useqacontact') ? "qa_contact" : (),
+ Param('timetrackinggroup') ? qw(estimated_time remaining_time
+ actual_time deadline)
+ : (),
+
+ # Custom Fields
+ Bugzilla->custom_field_names
+ );
+}
+
+
+#####################################################################
+# Instance Accessors
#####################################################################
# These subs are in alphabetical order, as much as possible.
@@ -1299,13 +1304,46 @@ sub ValidateDependencies {
return %deps;
}
+
+#####################################################################
+# Autoloaded Accessors
+#####################################################################
+
+# Determines whether an attribute access trapped by the AUTOLOAD function
+# is for a valid bug attribute. Bug attributes are properties and methods
+# predefined by this module as well as bug fields for which an accessor
+# can be defined by AUTOLOAD at runtime when the accessor is first accessed.
+#
+# XXX Strangely, some predefined attributes are on the list, but others aren't,
+# and the original code didn't specify why that is. Presumably the only
+# attributes that need to be on this list are those that aren't predefined;
+# we should verify that and update the list accordingly.
+#
+sub _validate_attribute {
+ my ($attribute) = @_;
+
+ my @valid_attributes = (
+ # Miscellaneous properties and methods.
+ qw(error groups
+ longdescs milestoneurl attachments
+ isopened isunconfirmed
+ flag_types num_attachment_flag_types
+ show_attachment_flags use_keywords any_flags_requesteeble),
+
+ # Bug fields.
+ Bugzilla::Bug->fields
+ );
+
+ return grep($attribute eq $_, @valid_attributes) ? 1 : 0;
+}
+
sub AUTOLOAD {
use vars qw($AUTOLOAD);
my $attr = $AUTOLOAD;
$attr =~ s/.*:://;
return unless $attr=~ /[^A-Z]/;
- confess ("invalid bug attribute $attr") unless $ok_field{$attr};
+ confess("invalid bug attribute $attr") unless _validate_attribute($attr);
no strict 'refs';
*$AUTOLOAD = sub {
diff --git a/Bugzilla/Constants.pm b/Bugzilla/Constants.pm
index c00518732..afb621f78 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 <dmose@mozilla.org>
# Frédéric Buclin <LpSolit@gmail.com>
+# Myk Melez <myk@mozilla.org>
+
+=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<id>
+
+the unique identifier for the field;
+
+=back
+
+=cut
+
+sub id { return $_[0]->{id} }
+
+=over
+
+=item C<name>
+
+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<description>
+
+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<type>
+
+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<custom>
+
+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<obsolete>
+
+a boolean specifying whether or not the field is obsolete;
+
+=back
+
+=cut
+
+sub obsolete { return $_[0]->{obsolete} }
+
+
+=pod
+
+=head2 Class Methods
+
+=over
+
+=item C<create($name, $desc)>
+
+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<match($criteria)>
-=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<check_form_field_defined($cgi, $fieldname)>
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<get_field_id($fieldname)>
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__