# -*- 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 Original Code is the Bugzilla Bug Tracking System. # # 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; use strict; use base qw(Exporter); @Bugzilla::Field::EXPORT = qw(check_form_field check_form_field_defined 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; } =pod =head2 Instance Properties =over =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; my $dbh = Bugzilla->dbh; # Some day we'll allow invocants to specify the sort key. my ($sortkey) = $dbh->selectrow_array("SELECT MAX(sortkey) + 1 FROM fielddefs"); # 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); } =pod =over =item C Description: returns a list of fields that match the specified criteria. 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. Note: Bugzilla->get_fields() and Bugzilla->custom_field_names wrap this method for most callers. Returns: a list of field objects. =back =cut 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 Data Validation =over =item C Description: Makes sure the field $fieldname is defined and its value is non empty. If @legal_values is defined, this routine also checks whether its value is one of the legal values associated with this field. If the test fails, an error is thrown. Params: $cgi - a CGI object $fieldname - the field name to check @legal_values - (optional) ref to a list of legal values 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 is non empty. Else an error is thrown. Params: $cgi - a CGI object $fieldname - the field name to check 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: $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__