зеркало из https://github.com/mozilla/pjs.git
495 строки
17 KiB
Plaintext
495 строки
17 KiB
Plaintext
|
#!/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;
|
||
|
}
|
||
|
|