summaryrefslogtreecommitdiffstats
path: root/Bugzilla/FlagType.pm
diff options
context:
space:
mode:
authormyk%mozilla.org <>2002-09-29 03:42:23 +0200
committermyk%mozilla.org <>2002-09-29 03:42:23 +0200
commit91b171e7584920d03abb9c45e779c84f3dee975c (patch)
treefc59becfe02d1a4dc84e5f3501f0139effcf1c7a /Bugzilla/FlagType.pm
parent90975fe914d066726d06f53abe8696399b13a61a (diff)
downloadbugzilla-91b171e7584920d03abb9c45e779c84f3dee975c.tar.gz
bugzilla-91b171e7584920d03abb9c45e779c84f3dee975c.tar.xz
Fix for bug 98801: Implementation of the request tracker, a set of enhancements to attachment statuses.
r=gerv,bbaetz
Diffstat (limited to 'Bugzilla/FlagType.pm')
-rw-r--r--Bugzilla/FlagType.pm325
1 files changed, 325 insertions, 0 deletions
diff --git a/Bugzilla/FlagType.pm b/Bugzilla/FlagType.pm
new file mode 100644
index 000000000..2e272f67c
--- /dev/null
+++ b/Bugzilla/FlagType.pm
@@ -0,0 +1,325 @@
+# -*- 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.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Myk Melez <myk@mozilla.org>
+
+################################################################################
+# Module Initialization
+################################################################################
+
+# Make it harder for us to do dangerous things in Perl.
+use strict;
+
+# This module implements flag types for the flag tracker.
+package Bugzilla::FlagType;
+
+# Use Bugzilla's User module which contains utilities for handling users.
+use Bugzilla::User;
+
+# Note! This module requires that its caller have said "require CGI.pl"
+# to import relevant functions from that script and its companion globals.pl.
+
+################################################################################
+# Global Variables
+################################################################################
+
+# basic sets of columns and tables for getting flag types from the database
+
+my @base_columns =
+ ("1", "flagtypes.id", "flagtypes.name", "flagtypes.description",
+ "flagtypes.cc_list", "flagtypes.target_type", "flagtypes.sortkey",
+ "flagtypes.is_active", "flagtypes.is_requestable",
+ "flagtypes.is_requesteeble", "flagtypes.is_multiplicable");
+
+# Note: when adding tables to @base_tables, make sure to include the separator
+# (i.e. a comma or words like "LEFT OUTER JOIN") before the table name,
+# since tables take multiple separators based on the join type, and therefore
+# it is not possible to join them later using a single known separator.
+
+my @base_tables = ("flagtypes");
+
+################################################################################
+# Public Functions
+################################################################################
+
+sub get {
+ # Returns a hash of information about a flag type.
+
+ my ($id) = @_;
+
+ my $select_clause = "SELECT " . join(", ", @base_columns);
+ my $from_clause = "FROM " . join(" ", @base_tables);
+
+ &::PushGlobalSQLState();
+ &::SendSQL("$select_clause $from_clause WHERE flagtypes.id = $id");
+ my @data = &::FetchSQLData();
+ my $type = perlify_record(@data);
+ &::PopGlobalSQLState();
+
+ return $type;
+}
+
+sub get_inclusions {
+ my ($id) = @_;
+ return get_clusions($id, "in");
+}
+
+sub get_exclusions {
+ my ($id) = @_;
+ return get_clusions($id, "ex");
+}
+
+sub get_clusions {
+ my ($id, $type) = @_;
+
+ &::PushGlobalSQLState();
+ &::SendSQL("SELECT products.name, components.name " .
+ "FROM flagtypes, flag${type}clusions " .
+ "LEFT OUTER JOIN products ON flag${type}clusions.product_id = products.id " .
+ "LEFT OUTER JOIN components ON flag${type}clusions.component_id = components.id " .
+ "WHERE flagtypes.id = $id AND flag${type}clusions.type_id = flagtypes.id");
+ my @clusions = ();
+ while (&::MoreSQLData()) {
+ my ($product, $component) = &::FetchSQLData();
+ $product ||= "Any";
+ $component ||= "Any";
+ push(@clusions, "$product:$component");
+ }
+ &::PopGlobalSQLState();
+
+ return \@clusions;
+}
+
+sub match {
+ # Queries the database for flag types matching the given criteria
+ # and returns the set of matching types.
+
+ my ($criteria, $include_count) = @_;
+
+ my @tables = @base_tables;
+ my @columns = @base_columns;
+ my $having = "";
+
+ # Include a count of the number of flags per type if requested.
+ if ($include_count) {
+ push(@columns, "COUNT(flags.id)");
+ push(@tables, "LEFT OUTER JOIN flags ON flagtypes.id = flags.type_id");
+ }
+
+ # Generate the SQL WHERE criteria.
+ my @criteria = sqlify_criteria($criteria, \@tables, \@columns, \$having);
+
+ # Build the query, grouping the types if we are counting flags.
+ my $select_clause = "SELECT " . join(", ", @columns);
+ my $from_clause = "FROM " . join(" ", @tables);
+ my $where_clause = "WHERE " . join(" AND ", @criteria);
+
+ my $query = "$select_clause $from_clause $where_clause";
+ $query .= " GROUP BY flagtypes.id " if ($include_count || $having ne "");
+ $query .= " HAVING $having " if $having ne "";
+ $query .= " ORDER BY flagtypes.sortkey, flagtypes.name";
+
+ # Execute the query and retrieve the results.
+ &::PushGlobalSQLState();
+ &::SendSQL($query);
+ my @types;
+ while (&::MoreSQLData()) {
+ my @data = &::FetchSQLData();
+ my $type = perlify_record(@data);
+ push(@types, $type);
+ }
+ &::PopGlobalSQLState();
+
+ return \@types;
+}
+
+sub count {
+ # Returns the total number of flag types matching the given criteria.
+
+ my ($criteria) = @_;
+
+ # Generate query components.
+ my @tables = @base_tables;
+ my @columns = ("COUNT(flagtypes.id)");
+ my $having = "";
+ my @criteria = sqlify_criteria($criteria, \@tables, \@columns, \$having);
+
+ # Build the query.
+ my $select_clause = "SELECT " . join(", ", @columns);
+ my $from_clause = "FROM " . join(" ", @tables);
+ my $where_clause = "WHERE " . join(" AND ", @criteria);
+ my $query = "$select_clause $from_clause $where_clause";
+ $query .= " GROUP BY flagtypes.id HAVING $having " if $having ne "";
+
+ # Execute the query and get the results.
+ &::PushGlobalSQLState();
+ &::SendSQL($query);
+ my $count = &::FetchOneColumn();
+ &::PopGlobalSQLState();
+
+ return $count;
+}
+
+sub validate {
+ my ($data) = @_;
+
+ # Get a list of flags types to validate. Uses the "map" function
+ # to extract flag type IDs from form field names by matching columns
+ # whose name looks like "flag_type-nnn", where "nnn" is the ID,
+ # and returning just the ID portion of matching field names.
+ my @ids = map(/^flag_type-(\d+)$/ ? $1 : (), keys %$data);
+
+ foreach my $id (@ids)
+ {
+ my $status = $data->{"flag_type-$id"};
+
+ # Don't bother validating types the user didn't touch.
+ next if $status eq "X";
+
+ # Make sure the flag exists.
+ get($id)
+ || &::ThrowCodeError("flag_type_nonexistent", { id => $id });
+
+ # Make sure the value of the field is a valid status.
+ grep($status eq $_, qw(X + - ?))
+ || &::ThrowCodeError("flag_status_invalid",
+ { id => $id , status => $status });
+ }
+}
+
+sub normalize {
+ # Given a list of flag types, checks its flags to make sure they should
+ # still exist after a change to the inclusions/exclusions lists.
+
+ # A list of IDs of flag types to normalize.
+ my (@ids) = @_;
+
+ my $ids = join(", ", @ids);
+
+ # Check for flags whose product/component is no longer included.
+ &::SendSQL("
+ SELECT flags.id
+ FROM flags, bugs LEFT OUTER JOIN flaginclusions AS i
+ ON (flags.type_id = i.type_id
+ AND (bugs.product_id = i.product_id OR i.product_id IS NULL)
+ AND (bugs.component_id = i.component_id OR i.component_id IS NULL))
+ WHERE flags.type_id IN ($ids)
+ AND flags.bug_id = bugs.bug_id
+ AND i.type_id IS NULL
+ ");
+ Bugzilla::Flag::clear(&::FetchOneColumn()) while &::MoreSQLData();
+
+ &::SendSQL("
+ SELECT flags.id
+ FROM flags, bugs, flagexclusions AS e
+ WHERE flags.type_id IN ($ids)
+ AND flags.bug_id = bugs.bug_id
+ AND flags.type_id = e.type_id
+ AND (bugs.product_id = e.product_id OR e.product_id IS NULL)
+ AND (bugs.component_id = e.component_id OR e.component_id IS NULL)
+ ");
+ Bugzilla::Flag::clear(&::FetchOneColumn()) while &::MoreSQLData();
+}
+
+################################################################################
+# Private Functions
+################################################################################
+
+sub sqlify_criteria {
+ # Converts a hash of criteria into a list of SQL criteria.
+ # $criteria is a reference to the criteria (field => value),
+ # $tables is a reference to an array of tables being accessed
+ # by the query, $columns is a reference to an array of columns
+ # being returned by the query, and $having is a reference to
+ # a criterion to put into the HAVING clause.
+ my ($criteria, $tables, $columns, $having) = @_;
+
+ # the generated list of SQL criteria; "1=1" is a clever way of making sure
+ # there's something in the list so calling code doesn't have to check list
+ # size before building a WHERE clause out of it
+ my @criteria = ("1=1");
+
+ if ($criteria->{name}) {
+ push(@criteria, "flagtypes.name = " . &::SqlQuote($criteria->{name}));
+ }
+ if ($criteria->{target_type}) {
+ # The target type is stored in the database as a one-character string
+ # ("a" for attachment and "b" for bug), but this function takes complete
+ # names ("attachment" and "bug") for clarity, so we must convert them.
+ my $target_type = &::SqlQuote(substr($criteria->{target_type}, 0, 1));
+ push(@criteria, "flagtypes.target_type = $target_type");
+ }
+ if (exists($criteria->{is_active})) {
+ my $is_active = $criteria->{is_active} ? "1" : "0";
+ push(@criteria, "flagtypes.is_active = $is_active");
+ }
+ if ($criteria->{product_id} && $criteria->{'component_id'}) {
+ my $product_id = $criteria->{product_id};
+ my $component_id = $criteria->{component_id};
+
+ # Add inclusions to the query, which simply involves joining the table
+ # by flag type ID and target product/component.
+ push(@$tables, ", flaginclusions");
+ push(@criteria, "flagtypes.id = flaginclusions.type_id");
+ push(@criteria, "(flaginclusions.product_id = $product_id " .
+ " OR flaginclusions.product_id IS NULL)");
+ push(@criteria, "(flaginclusions.component_id = $component_id " .
+ " OR flaginclusions.component_id IS NULL)");
+
+ # Add exclusions to the query, which is more complicated. First of all,
+ # we do a LEFT JOIN so we don't miss flag types with no exclusions.
+ # Then, as with inclusions, we join on flag type ID and target product/
+ # component. However, since we want flag types that *aren't* on the
+ # exclusions list, we count the number of exclusions records returned
+ # and use a HAVING clause to weed out types with one or more exclusions.
+ my $join_clause = "flagtypes.id = flagexclusions.type_id " .
+ "AND (flagexclusions.product_id = $product_id " .
+ "OR flagexclusions.product_id IS NULL) " .
+ "AND (flagexclusions.component_id = $component_id " .
+ "OR flagexclusions.component_id IS NULL)";
+ push(@$tables, "LEFT JOIN flagexclusions ON ($join_clause)");
+ push(@$columns, "COUNT(flagexclusions.type_id) AS num_exclusions");
+ $$having = "num_exclusions = 0";
+ }
+
+ return @criteria;
+}
+
+sub perlify_record {
+ # Converts data retrieved from the database into a Perl record.
+
+ my $type = {};
+
+ $type->{'exists'} = $_[0];
+ $type->{'id'} = $_[1];
+ $type->{'name'} = $_[2];
+ $type->{'description'} = $_[3];
+ $type->{'cc_list'} = $_[4];
+ $type->{'target_type'} = $_[5] eq "b" ? "bug" : "attachment";
+ $type->{'sortkey'} = $_[6];
+ $type->{'is_active'} = $_[7];
+ $type->{'is_requestable'} = $_[8];
+ $type->{'is_requesteeble'} = $_[9];
+ $type->{'is_multiplicable'} = $_[10];
+ $type->{'flag_count'} = $_[11];
+
+ return $type;
+}
+
+1;