summaryrefslogtreecommitdiffstats
path: root/editflagtypes.cgi
diff options
context:
space:
mode:
Diffstat (limited to 'editflagtypes.cgi')
-rwxr-xr-xeditflagtypes.cgi494
1 files changed, 494 insertions, 0 deletions
diff --git a/editflagtypes.cgi b/editflagtypes.cgi
new file mode 100755
index 000000000..aed73f284
--- /dev/null
+++ b/editflagtypes.cgi
@@ -0,0 +1,494 @@
+#!/usr/bonsaitools/bin/perl -wT
+# -*- 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>
+
+################################################################################
+# Script Initialization
+################################################################################
+
+# Make it harder for us to do dangerous things in Perl.
+use strict;
+use lib ".";
+
+# Include the Bugzilla CGI and general utility library.
+require "CGI.pl";
+
+# Establish a connection to the database backend.
+ConnectToDatabase();
+
+# Use Bugzilla's flag modules for handling flag types.
+use Bugzilla::Flag;
+use Bugzilla::FlagType;
+
+use vars qw( $template $vars );
+
+# Make sure the user is logged in and is an administrator.
+confirm_login();
+UserInGroup("editcomponents")
+ || ThrowUserError("authorization_failure",
+ { action => "administer flag types" });
+
+# Suppress "used only once" warnings.
+use vars qw(@legal_product @legal_components %components);
+
+my $product_id;
+my $component_id;
+
+################################################################################
+# Main Body Execution
+################################################################################
+
+# All calls to this script should contain an "action" variable whose value
+# determines what the user wants to do. The code below checks the value of
+# that variable and runs the appropriate code.
+
+# Determine whether to use the action specified by the user or the default.
+my $action = $::FORM{'action'} || 'list';
+
+if ($::FORM{'categoryAction'}) {
+ processCategoryChange();
+ exit;
+}
+
+if ($action eq 'list') { list(); }
+elsif ($action eq 'enter') { edit(); }
+elsif ($action eq 'copy') { edit(); }
+elsif ($action eq 'edit') { edit(); }
+elsif ($action eq 'insert') { insert(); }
+elsif ($action eq 'update') { update(); }
+elsif ($action eq 'confirmdelete') { confirmDelete(); }
+elsif ($action eq 'delete') { &delete(); }
+elsif ($action eq 'deactivate') { deactivate(); }
+else {
+ ThrowCodeError("action_unrecognized", { action => $action });
+}
+
+exit;
+
+################################################################################
+# Functions
+################################################################################
+
+sub list {
+ # Define the variables and functions that will be passed to the UI template.
+ $vars->{'bug_types'} = Bugzilla::FlagType::match({ 'target_type' => 'bug' }, 1);
+ $vars->{'attachment_types'} =
+ Bugzilla::FlagType::match({ 'target_type' => 'attachment' }, 1);
+
+ # Return the appropriate HTTP response headers.
+ print "Content-type: text/html\n\n";
+
+ # Generate and return the UI (HTML page) from the appropriate template.
+ $template->process("admin/flag-type/list.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+}
+
+
+sub edit {
+ $action eq 'enter' ? validateTargetType() : validateID();
+
+ # Get this installation's products and components.
+ GetVersionTable();
+
+ # products and components and the function used to modify the components
+ # menu when the products menu changes; used by the template to populate
+ # the menus and keep the components menu consistent with the products menu
+ $vars->{'products'} = \@::legal_product;
+ $vars->{'components'} = \@::legal_components;
+ $vars->{'components_by_product'} = \%::components;
+
+ $vars->{'last_action'} = $::FORM{'action'};
+ if ($::FORM{'action'} eq 'enter' || $::FORM{'action'} eq 'copy') {
+ $vars->{'action'} = "insert";
+ }
+ else {
+ $vars->{'action'} = "update";
+ }
+
+ # If copying or editing an existing flag type, retrieve it.
+ if ($::FORM{'action'} eq 'copy' || $::FORM{'action'} eq 'edit') {
+ $vars->{'type'} = Bugzilla::FlagType::get($::FORM{'id'});
+ $vars->{'type'}->{'inclusions'} = Bugzilla::FlagType::get_inclusions($::FORM{'id'});
+ $vars->{'type'}->{'exclusions'} = Bugzilla::FlagType::get_exclusions($::FORM{'id'});
+ }
+ # Otherwise set the target type (the minimal information about the type
+ # that the template needs to know) from the URL parameter.
+ else {
+ $vars->{'type'} = { 'target_type' => $::FORM{'target_type'} };
+ }
+
+ # Return the appropriate HTTP response headers.
+ print "Content-type: text/html\n\n";
+
+ # Generate and return the UI (HTML page) from the appropriate template.
+ $template->process("admin/flag-type/edit.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+}
+
+sub processCategoryChange {
+ validateIsActive();
+ validateIsRequestable();
+ validateIsRequesteeble();
+ validateAllowMultiple();
+
+ my @inclusions = $::MFORM{'inclusions'} ? @{$::MFORM{'inclusions'}} : ();
+ my @exclusions = $::MFORM{'exclusions'} ? @{$::MFORM{'exclusions'}} : ();
+ if ($::FORM{'categoryAction'} eq "Include") {
+ validateProduct();
+ validateComponent();
+ my $category = ($::FORM{'product'} || "__Any__") . ":" . ($::FORM{'component'} || "__Any__");
+ push(@inclusions, $category) unless grep($_ eq $category, @inclusions);
+ }
+ elsif ($::FORM{'categoryAction'} eq "Exclude") {
+ validateProduct();
+ validateComponent();
+ my $category = ($::FORM{'product'} || "__Any__") . ":" . ($::FORM{'component'} || "__Any__");
+ push(@exclusions, $category) unless grep($_ eq $category, @exclusions);
+ }
+ elsif ($::FORM{'categoryAction'} eq "Remove Inclusion") {
+ @inclusions = map(($_ eq $::FORM{'inclusion_to_remove'} ? () : $_), @inclusions);
+ }
+ elsif ($::FORM{'categoryAction'} eq "Remove Exclusion") {
+ @exclusions = map(($_ eq $::FORM{'exclusion_to_remove'} ? () : $_), @exclusions);
+ }
+
+ # Get this installation's products and components.
+ GetVersionTable();
+
+ # products and components; used by the template to populate the menus
+ # and keep the components menu consistent with the products menu
+ $vars->{'products'} = \@::legal_product;
+ $vars->{'components'} = \@::legal_components;
+ $vars->{'components_by_product'} = \%::components;
+
+ $vars->{'action'} = $::FORM{'action'};
+ my $type = {};
+ foreach my $key (keys %::FORM) { $type->{$key} = $::FORM{$key} }
+ $type->{'inclusions'} = \@inclusions;
+ $type->{'exclusions'} = \@exclusions;
+ $vars->{'type'} = $type;
+
+ # Return the appropriate HTTP response headers.
+ print "Content-type: text/html\n\n";
+
+ # Generate and return the UI (HTML page) from the appropriate template.
+ $template->process("admin/flag-type/edit.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+}
+
+sub insert {
+ validateName();
+ validateDescription();
+ validateCCList();
+ validateTargetType();
+ validateSortKey();
+ validateIsActive();
+ validateIsRequestable();
+ validateIsRequesteeble();
+ validateAllowMultiple();
+
+ my $name = SqlQuote($::FORM{'name'});
+ my $description = SqlQuote($::FORM{'description'});
+ my $cc_list = SqlQuote($::FORM{'cc_list'});
+ my $target_type = $::FORM{'target_type'} eq "bug" ? "b" : "a";
+
+ SendSQL("LOCK TABLES flagtypes WRITE, products READ, components READ, " .
+ "flaginclusions WRITE, flagexclusions WRITE");
+
+ # Determine the new flag type's unique identifier.
+ SendSQL("SELECT MAX(id) FROM flagtypes");
+ my $id = FetchSQLData() + 1;
+
+ # Insert a record for the new flag type into the database.
+ SendSQL("INSERT INTO flagtypes (id, name, description, cc_list,
+ target_type, sortkey, is_active, is_requestable,
+ is_requesteeble, is_multiplicable)
+ VALUES ($id, $name, $description, $cc_list, '$target_type',
+ $::FORM{'sortkey'}, $::FORM{'is_active'},
+ $::FORM{'is_requestable'}, $::FORM{'is_requesteeble'},
+ $::FORM{'is_multiplicable'})");
+
+ # Populate the list of inclusions/exclusions for this flag type.
+ foreach my $category_type ("inclusions", "exclusions") {
+ foreach my $category (@{$::MFORM{$category_type}}) {
+ my ($product, $component) = split(/:/, $category);
+ my $product_id = get_product_id($product) || "NULL";
+ my $component_id =
+ get_component_id($product_id, $component) || "NULL";
+ SendSQL("INSERT INTO flag$category_type (type_id, product_id, " .
+ "component_id) VALUES ($id, $product_id, $component_id)");
+ }
+ }
+
+ SendSQL("UNLOCK TABLES");
+
+ $vars->{'name'} = $::FORM{'name'};
+ $vars->{'message'} = "flag_type_created";
+
+ # Return the appropriate HTTP response headers.
+ print "Content-type: text/html\n\n";
+
+ # Generate and return the UI (HTML page) from the appropriate template.
+ $template->process("global/message.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+}
+
+
+sub update {
+ validateID();
+ validateName();
+ validateDescription();
+ validateCCList();
+ validateTargetType();
+ validateSortKey();
+ validateIsActive();
+ validateIsRequestable();
+ validateIsRequesteeble();
+ validateAllowMultiple();
+
+ my $name = SqlQuote($::FORM{'name'});
+ my $description = SqlQuote($::FORM{'description'});
+ my $cc_list = SqlQuote($::FORM{'cc_list'});
+
+ SendSQL("LOCK TABLES flagtypes WRITE, products READ, components READ, " .
+ "flaginclusions WRITE, flagexclusions WRITE");
+ SendSQL("UPDATE flagtypes
+ SET name = $name ,
+ description = $description ,
+ cc_list = $cc_list ,
+ sortkey = $::FORM{'sortkey'} ,
+ is_active = $::FORM{'is_active'} ,
+ is_requestable = $::FORM{'is_requestable'} ,
+ is_requesteeble = $::FORM{'is_requesteeble'} ,
+ is_multiplicable = $::FORM{'is_multiplicable'}
+ WHERE id = $::FORM{'id'}");
+
+ # Update the list of inclusions/exclusions for this flag type.
+ foreach my $category_type ("inclusions", "exclusions") {
+ SendSQL("DELETE FROM flag$category_type WHERE type_id = $::FORM{'id'}");
+ foreach my $category (@{$::MFORM{$category_type}}) {
+ my ($product, $component) = split(/:/, $category);
+ my $product_id = get_product_id($product) || "NULL";
+ my $component_id =
+ get_component_id($product_id, $component) || "NULL";
+ SendSQL("INSERT INTO flag$category_type (type_id, product_id, " .
+ "component_id) VALUES ($::FORM{'id'}, $product_id, " .
+ "$component_id)");
+ }
+ }
+
+ SendSQL("UNLOCK TABLES");
+
+ # Clear existing flags for bugs/attachments in categories no longer on
+ # the list of inclusions or that have been added to the list of exclusions.
+ 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 = $::FORM{'id'}
+ 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 = $::FORM{'id'}
+ 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();
+
+ $vars->{'name'} = $::FORM{'name'};
+ $vars->{'message'} = "flag_type_changes_saved";
+
+ # Return the appropriate HTTP response headers.
+ print "Content-type: text/html\n\n";
+
+ # Generate and return the UI (HTML page) from the appropriate template.
+ $template->process("global/message.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+}
+
+
+sub confirmDelete
+{
+ validateID();
+ # check if we need confirmation to delete:
+
+ my $count = Bugzilla::Flag::count({ 'type_id' => $::FORM{'id'} });
+
+ if ($count > 0) {
+ $vars->{'flag_type'} = Bugzilla::FlagType::get($::FORM{'id'});
+ $vars->{'flag_count'} = scalar($count);
+
+ # Return the appropriate HTTP response headers.
+ print "Content-type: text/html\n\n";
+
+ # Generate and return the UI (HTML page) from the appropriate template.
+ $template->process("admin/flag-type/confirm-delete.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ }
+ else {
+ deleteType();
+ }
+}
+
+
+sub delete {
+ validateID();
+
+ SendSQL("LOCK TABLES flagtypes WRITE, flags WRITE, " .
+ "flaginclusions WRITE, flagexclusions WRITE");
+
+ # Get the name of the flag type so we can tell users
+ # what was deleted.
+ SendSQL("SELECT name FROM flagtypes WHERE id = $::FORM{'id'}");
+ $vars->{'name'} = FetchOneColumn();
+
+ SendSQL("DELETE FROM flags WHERE type_id = $::FORM{'id'}");
+ SendSQL("DELETE FROM flaginclusions WHERE type_id = $::FORM{'id'}");
+ SendSQL("DELETE FROM flagexclusions WHERE type_id = $::FORM{'id'}");
+ SendSQL("DELETE FROM flagtypes WHERE id = $::FORM{'id'}");
+ SendSQL("UNLOCK TABLES");
+
+ $vars->{'message'} = "flag_type_deleted";
+
+ # Return the appropriate HTTP response headers.
+ print "Content-type: text/html\n\n";
+
+ # Generate and return the UI (HTML page) from the appropriate template.
+ $template->process("global/message.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+}
+
+
+sub deactivate {
+ validateID();
+ validateIsActive();
+
+ SendSQL("LOCK TABLES flagtypes WRITE");
+ SendSQL("UPDATE flagtypes SET is_active = 0 WHERE id = $::FORM{'id'}");
+ SendSQL("UNLOCK TABLES");
+
+ $vars->{'message'} = "flag_type_deactivated";
+ $vars->{'flag_type'} = Bugzilla::FlagType::get($::FORM{'id'});
+
+ # Return the appropriate HTTP response headers.
+ print "Content-type: text/html\n\n";
+
+ # Generate and return the UI (HTML page) from the appropriate template.
+ $template->process("global/message.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+}
+
+
+################################################################################
+# Data Validation / Security Authorization
+################################################################################
+
+sub validateID {
+ detaint_natural($::FORM{'id'})
+ || ThrowCodeError("flag_type_id_invalid", { id => $::FORM{'id'} });
+
+ SendSQL("SELECT 1 FROM flagtypes WHERE id = $::FORM{'id'}");
+ FetchOneColumn()
+ || ThrowCodeError("flag_type_nonexistent", { id => $::FORM{'id'} });
+}
+
+sub validateName {
+ $::FORM{'name'}
+ && length($::FORM{'name'}) <= 50
+ || ThrowUserError("flag_type_name_invalid", { name => $::FORM{'name'} });
+}
+
+sub validateDescription {
+ length($::FORM{'description'}) < 2^16-1
+ || ThrowUserError("flag_type_description_invalid");
+}
+
+sub validateCCList {
+ length($::FORM{'cc_list'}) <= 200
+ || ThrowUserError("flag_type_cc_list_invalid",
+ { cc_list => $::FORM{'cc_list'} });
+
+ my @addresses = split(/[, ]+/, $::FORM{'cc_list'});
+ foreach my $address (@addresses) { CheckEmailSyntax($address) }
+}
+
+sub validateProduct {
+ return if !$::FORM{'product'};
+
+ $product_id = get_product_id($::FORM{'product'});
+
+ defined($product_id)
+ || ThrowCodeError("flag_type_product_nonexistent",
+ { product => $::FORM{'product'} });
+}
+
+sub validateComponent {
+ return if !$::FORM{'component'};
+
+ $product_id
+ || ThrowCodeError("flag_type_component_without_product");
+
+ $component_id = get_component_id($product_id, $::FORM{'component'});
+
+ defined($component_id)
+ || ThrowCodeError("flag_type_component_nonexistent",
+ { product => $::FORM{'product'},
+ component => $::FORM{'component'} });
+}
+
+sub validateSortKey {
+ detaint_natural($::FORM{'sortkey'})
+ && $::FORM{'sortkey'} < 32768
+ || ThrowUserError("flag_type_sortkey_invalid",
+ { sortkey => $::FORM{'sortkey'} });
+}
+
+sub validateTargetType {
+ grep($::FORM{'target_type'} eq $_, ("bug", "attachment"))
+ || ThrowCodeError("flag_type_target_type_invalid",
+ { target_type => $::FORM{'target_type'} });
+}
+
+sub validateIsActive {
+ $::FORM{'is_active'} = $::FORM{'is_active'} ? 1 : 0;
+}
+
+sub validateIsRequestable {
+ $::FORM{'is_requestable'} = $::FORM{'is_requestable'} ? 1 : 0;
+}
+
+sub validateIsRequesteeble {
+ $::FORM{'is_requesteeble'} = $::FORM{'is_requesteeble'} ? 1 : 0;
+}
+
+sub validateAllowMultiple {
+ $::FORM{'is_multiplicable'} = $::FORM{'is_multiplicable'} ? 1 : 0;
+}
+