From 91b171e7584920d03abb9c45e779c84f3dee975c Mon Sep 17 00:00:00 2001 From: "myk%mozilla.org" <> Date: Sun, 29 Sep 2002 01:42:23 +0000 Subject: Fix for bug 98801: Implementation of the request tracker, a set of enhancements to attachment statuses. r=gerv,bbaetz --- Bugzilla/FlagType.pm | 325 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 325 insertions(+) create mode 100644 Bugzilla/FlagType.pm (limited to 'Bugzilla/FlagType.pm') 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 + +################################################################################ +# 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; -- cgit v1.2.3-24-g4f1b