aboutsummaryrefslogtreecommitdiffstats
path: root/phpBB/includes/acp/acp_modules.php
Commit message (Expand)AuthorAgeFilesLines
* [ticket/12557] Fix doc block errors found by Samin-aleha2014-08-031-1/+2
* [ticket/12508] Fix usages of the finderJoas Schilling2014-06-101-2/+2
* [ticket/12594] Remove @package tags and update file headersYuriy Rusko2014-05-271-6/+7
* [ticket/12038] AJAXify move up/down buttons in the module management pages.Cesar G2013-12-051-0/+8
* [ticket/11871] Allow backslash in classnames for ModulesJoas Schilling2013-09-271-1/+1
* [ticket/11700] Correctly load extensions with nonprefixed namespacesNils Adermann2013-09-171-1/+1
* Merge remote-tracking branch 'github-phpbb/develop' into ticket/11700Nils Adermann2013-09-161-0/+1
|\
| * [ticket/11713] Do not remove module if it couldn't be deletedMarc Alexander2013-07-181-0/+1
* | [ticket/11700] Move all recent code to namespacesNils Adermann2013-09-161-1/+1
|/
* [ticket/11690] Old module class names may get autoloaded by class_existsNils Adermann2013-07-131-5/+12
* [ticket/11465] Check if class exists before including info fileMarc Alexander2013-04-141-1/+1
* [ticket/11465] The info file does not have _info suffixJoas Schilling2013-04-101-3/+4
* [ticket/11465] Use extension finder when adding extensions' acp modulesMarc Alexander2013-03-211-47/+25
* [ticket/11395] Prevent acp_modules::get_modules_info from reincluding filesNathaniel Guse2013-03-031-2/+2
* [ticket/11363] Fix to make get_module_infos get from all extensionsNathaniel Guse2013-03-011-2/+8
* [ticket/11305] Retrieve cache driver from container rather than cache service.Oleg Pudeyev2013-01-021-3/+3
* [feature/ajax] Unify phpbb_json_response instantiationIgor Wiedler2012-03-311-1/+1
* [feature/ajax] Replace static call to phpbb_request with OOIgor Wiedler2012-03-311-1/+2
* [feature/ajax] Handle acp_modules error cases with JSON responseIgor Wiedler2012-03-311-1/+9
* [ticket/9916] Updating header license and removing Version $Id$Unknown2011-12-311-2/+1
* [feature/extension-manager] Rename default methods to core methods on finder.Nils Adermann2011-11-181-6/+4
* [feature/extension-manager] Avoid unecessary loading of acp classesNils Adermann2011-09-291-10/+6
* [feature/extension-manager] Fix whitespace in acp_modulesNils Adermann2011-09-291-13/+13
* [feature/extension-manager] Load (A/U/M)CP modules from extensionsNils Adermann2011-09-291-32/+53
* [ticket/9556] Drop php closing tags, add trailing newlineIgor Wiedler2010-11-111-2/+0
* Copy 3.0.x branch to trunkMeik Sievertsen2009-10-041-0/+1065
* put acp, mcp and ucp into modules/ directoryMeik Sievertsen2008-12-241-1068/+0
* merge in r9090, r9170, r9174, r9179Chris Smith2008-12-231-0/+2
* - updated all code to use the request class instead of any direct access toNils Adermann2008-11-241-1/+1
* Remove caching of templates from the database completely, themes is cut down ...Chris Smith2008-09-041-1/+1
* PHP5.3 compatibility.Marek A. R2008-08-151-1/+7
* ok... i hope i haven't messed too much with the code and everything is still ...Meik Sievertsen2008-05-291-8/+5
* dumdidum... sorry. ;)Meik Sievertsen2007-10-051-6/+14
* #i62 - #i65Meik Sievertsen2007-09-221-1/+1
* + some fixesMeik Sievertsen2007-07-241-1/+6
* try to normalize everything...Meik Sievertsen2007-07-221-2/+2
* some fixesMeik Sievertsen2007-07-111-22/+25
* fixing some bugsMeik Sievertsen2007-05-071-1/+1
* #9828, #10545, #10541, #10533, #10529, #10527, #10521, #10503, #10481Meik Sievertsen2007-05-061-2/+16
* my take on getting the bugs down... thanks to those also providing (usable) s...Meik Sievertsen2007-04-121-0/+4
* #9659Meik Sievertsen2007-04-081-5/+4
* Be gone thou hardcoded image dimensions! There is not enough room for both of...Dominik Dröscher2007-03-061-2/+2
* fixing some bugsMeik Sievertsen2007-02-181-5/+5
* fixing some bugs, most being submitted grammatical/spelling errors.Meik Sievertsen2007-01-211-8/+0
* - fixing some bugsMeik Sievertsen2007-01-201-1/+7
* minor bugfixingMeik Sievertsen2006-12-061-0/+11
* rather large update, most important things done:Meik Sievertsen2006-11-241-2/+2
* - fixing a bunch of bugsMeik Sievertsen2006-11-211-1/+2
* some tiny fixes.Meik Sievertsen2006-11-191-6/+1
* - some fixesMeik Sievertsen2006-11-101-3/+15
619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712
#!/usr/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>
#                 Frédéric Buclin <LpSolit@gmail.com>

################################################################################
# Script Initialization
################################################################################

# Make it harder for us to do dangerous things in Perl.
use strict;
use lib qw(. lib);

# Use Bugzilla's flag modules for handling flag types.
use Bugzilla;
use Bugzilla::Constants;
use Bugzilla::Flag;
use Bugzilla::FlagType;
use Bugzilla::Group;
use Bugzilla::Util;
use Bugzilla::Error;
use Bugzilla::Product;
use Bugzilla::Component;
use Bugzilla::Bug;
use Bugzilla::Attachment;
use Bugzilla::Token;

local our $cgi = Bugzilla->cgi;
local our $template = Bugzilla->template;
local our $vars = {};

# Make sure the user is logged in and is an administrator.
my $user = Bugzilla->login(LOGIN_REQUIRED);
$user->in_group('editcomponents')
  || ThrowUserError("auth_failure", {group  => "editcomponents",
                                     action => "edit",
                                     object => "flagtypes"});

################################################################################
# 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 = $cgi->param('action') || 'list';
my $token  = $cgi->param('token');
my @categoryActions;

if (@categoryActions = grep(/^categoryAction-.+/, $cgi->param())) {
    $categoryActions[0] =~ s/^categoryAction-//;
    processCategoryChange($categoryActions[0], $token);
    exit;
}

if    ($action eq 'list')           { list();           }
elsif ($action eq 'enter')          { edit($action);    }
elsif ($action eq 'copy')           { edit($action);    }
elsif ($action eq 'edit')           { edit($action);    }
elsif ($action eq 'insert')         { insert($token);   }
elsif ($action eq 'update')         { update($token);   }
elsif ($action eq 'confirmdelete')  { confirmDelete();  } 
elsif ($action eq 'delete')         { deleteType(undef, $token); }
elsif ($action eq 'deactivate')     { deactivate($token); }
else { 
    ThrowCodeError("action_unrecognized", { action => $action });
}

exit;

################################################################################
# Functions
################################################################################

sub list {
    # Restrict the list to the given product and component, if given.
    $vars = get_products_and_components($vars);

    my $product = validateProduct(scalar $cgi->param('product'));
    my $component = validateComponent($product, scalar $cgi->param('component'));
    my $product_id = $product ? $product->id : 0;
    my $component_id = $component ? $component->id : 0;

    # Define the variables and functions that will be passed to the UI template.
    $vars->{'selected_product'} = $cgi->param('product');
    $vars->{'selected_component'} = $cgi->param('component');

    my $bug_flagtypes;
    my $attach_flagtypes;

    # If a component is given, restrict the list to flag types available
    # for this component.
    if ($component) {
        $bug_flagtypes = $component->flag_types->{'bug'};
        $attach_flagtypes = $component->flag_types->{'attachment'};

        # Filter flag types if a group ID is given.
        $bug_flagtypes = filter_group($bug_flagtypes);
        $attach_flagtypes = filter_group($attach_flagtypes);

    }
    # If only a product is specified but no component, then restrict the list
    # to flag types available in at least one component of that product.
    elsif ($product) {
        $bug_flagtypes = $product->flag_types->{'bug'};
        $attach_flagtypes = $product->flag_types->{'attachment'};

        # Filter flag types if a group ID is given.
        $bug_flagtypes = filter_group($bug_flagtypes);
        $attach_flagtypes = filter_group($attach_flagtypes);
    }
    # If no product is given, then show all flag types available.
    else {
        $bug_flagtypes =
            Bugzilla::FlagType::match({'target_type' => 'bug',
                                       'group' => scalar $cgi->param('group')});

        $attach_flagtypes =
            Bugzilla::FlagType::match({'target_type' => 'attachment',
                                         'group' => scalar $cgi->param('group')});
    }

    $vars->{'bug_types'} = $bug_flagtypes;
    $vars->{'attachment_types'} = $attach_flagtypes;

    # Return the appropriate HTTP response headers.
    print $cgi->header();

    # 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 {
    my ($action) = @_;

    my $flag_type;
    if ($action eq 'enter') {
        validateTargetType();
    }
    else {
        $flag_type = validateID();
    }

    # Fill $vars with products and components data.
    $vars = get_products_and_components($vars);

    $vars->{'last_action'} = $cgi->param('action');
    if ($cgi->param('action') eq 'enter' || $cgi->param('action') eq 'copy') {
        $vars->{'action'} = "insert";
        $vars->{'token'} = issue_session_token('add_flagtype');
    }
    else { 
        $vars->{'action'} = "update";
        $vars->{'token'} = issue_session_token('edit_flagtype');
    }

    # If copying or editing an existing flag type, retrieve it.
    if ($cgi->param('action') eq 'copy' || $cgi->param('action') eq 'edit') { 
        $vars->{'type'} = $flag_type;
    }
    # Otherwise set the target type (the minimal information about the type
    # that the template needs to know) from the URL parameter and default
    # the list of inclusions to all categories.
    else {
        my %inclusions;
        $inclusions{"__Any__:__Any__"} = "0:0";
        $vars->{'type'} = { 'target_type' => scalar $cgi->param('target_type'),
                            'inclusions'  => \%inclusions };
    }
    # Get a list of groups available to restrict this flag type against.
    my @groups = Bugzilla::Group->get_all;
    $vars->{'groups'} = \@groups;
    # Return the appropriate HTTP response headers.
    print $cgi->header();

    # 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 {
    my ($categoryAction, $token) = @_;
    validateIsActive();
    validateIsRequestable();
    validateIsRequesteeble();
    validateAllowMultiple();
    
    my @inclusions = $cgi->param('inclusions');
    my @exclusions = $cgi->param('exclusions');
    if ($categoryAction eq 'include') {
        my $product = validateProduct(scalar $cgi->param('product'));
        my $component = validateComponent($product, scalar $cgi->param('component'));
        my $category = ($product ? $product->id : 0) . ":" .
                       ($component ? $component->id : 0);
        push(@inclusions, $category) unless grep($_ eq $category, @inclusions);
    }
    elsif ($categoryAction eq 'exclude') {
        my $product = validateProduct(scalar $cgi->param('product'));
        my $component = validateComponent($product, scalar $cgi->param('component'));
        my $category = ($product ? $product->id : 0) . ":" .
                       ($component ? $component->id : 0);
        push(@exclusions, $category) unless grep($_ eq $category, @exclusions);
    }
    elsif ($categoryAction eq 'removeInclusion') {
        my @inclusion_to_remove = $cgi->param('inclusion_to_remove');
        @inclusions = map {(lsearch(\@inclusion_to_remove, $_) < 0) ? $_ : ()} @inclusions;
    }
    elsif ($categoryAction eq 'removeExclusion') {
        my @exclusion_to_remove = $cgi->param('exclusion_to_remove');
        @exclusions = map {(lsearch(\@exclusion_to_remove, $_) < 0) ? $_ : ()} @exclusions;
    }
    
    # Convert the array @clusions('prod_ID:comp_ID') back to a hash of
    # the form %clusions{'prod_name:comp_name'} = 'prod_ID:comp_ID'
    my %inclusions = clusion_array_to_hash(\@inclusions);
    my %exclusions = clusion_array_to_hash(\@exclusions);

    # Fill $vars with products and components data.
    $vars = get_products_and_components($vars);

    my @groups = Bugzilla::Group->get_all;
    $vars->{'groups'} = \@groups;
    $vars->{'action'} = $cgi->param('action');

    my $type = {};
    foreach my $key ($cgi->param()) { $type->{$key} = $cgi->param($key) }
    # That's what I call a big hack. The template expects to see a group object.
    # This script needs some rewrite anyway.
    $type->{'grant_group'} = {};
    $type->{'grant_group'}->{'name'} = $cgi->param('grant_group');
    $type->{'request_group'} = {};
    $type->{'request_group'}->{'name'} = $cgi->param('request_group');

    $type->{'inclusions'} = \%inclusions;
    $type->{'exclusions'} = \%exclusions;
    $vars->{'type'} = $type;
    $vars->{'token'} = $token;

    # Return the appropriate HTTP response headers.
    print $cgi->header();

    # Generate and return the UI (HTML page) from the appropriate template.
    $template->process("admin/flag-type/edit.html.tmpl", $vars)
      || ThrowTemplateError($template->error());
}

# Convert the array @clusions('prod_ID:comp_ID') back to a hash of
# the form %clusions{'prod_name:comp_name'} = 'prod_ID:comp_ID'
sub clusion_array_to_hash {
    my $array = shift;
    my %hash;
    my %products;
    my %components;
    foreach my $ids (@$array) {
        trick_taint($ids);
        my ($product_id, $component_id) = split(":", $ids);
        my $product_name = "__Any__";
        if ($product_id) {
            $products{$product_id} ||= new Bugzilla::Product($product_id);
            $product_name = $products{$product_id}->name if $products{$product_id};
        }
        my $component_name = "__Any__";
        if ($component_id) {
            $components{$component_id} ||= new Bugzilla::Component($component_id);
            $component_name = $components{$component_id}->name if $components{$component_id};
        }
        $hash{"$product_name:$component_name"} = $ids;
    }
    return %hash;
}

sub insert {
    my $token = shift;
    check_token_data($token, 'add_flagtype');
    my $name = validateName();
    my $description = validateDescription();
    my $cc_list = validateCCList();
    validateTargetType();
    validateSortKey();
    validateIsActive();
    validateIsRequestable();
    validateIsRequesteeble();
    validateAllowMultiple();
    validateGroups();

    my $dbh = Bugzilla->dbh;

    my $target_type = $cgi->param('target_type') eq "bug" ? "b" : "a";

    $dbh->bz_start_transaction();

    # Insert a record for the new flag type into the database.
    $dbh->do('INSERT INTO flagtypes
                          (name, description, cc_list, target_type,
                           sortkey, is_active, is_requestable, 
                           is_requesteeble, is_multiplicable, 
                           grant_group_id, request_group_id) 
                   VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
              undef, ($name, $description, $cc_list, $target_type,
                      $cgi->param('sortkey'), $cgi->param('is_active'),
                      $cgi->param('is_requestable'), $cgi->param('is_requesteeble'),
                      $cgi->param('is_multiplicable'), scalar($cgi->param('grant_gid')),
                      scalar($cgi->param('request_gid'))));

    # Get the ID of the new flag type.
    my $id = $dbh->bz_last_key('flagtypes', 'id');

    # Populate the list of inclusions/exclusions for this flag type.
    validateAndSubmit($id);

    $dbh->bz_commit_transaction();

    $vars->{'name'} = $name;
    $vars->{'message'} = "flag_type_created";
    delete_token($token);

    $vars->{'bug_types'} = Bugzilla::FlagType::match({'target_type' => 'bug'});
    $vars->{'attachment_types'} = Bugzilla::FlagType::match({'target_type' => 'attachment'});

    # Return the appropriate HTTP response headers.
    print $cgi->header();

    $template->process("admin/flag-type/list.html.tmpl", $vars)
      || ThrowTemplateError($template->error());
}


sub update {
    my $token = shift;
    check_token_data($token, 'edit_flagtype');
    my $flag_type = validateID();
    my $id = $flag_type->id;
    my $name = validateName();
    my $description = validateDescription();
    my $cc_list = validateCCList();
    validateTargetType();
    validateSortKey();
    validateIsActive();
    validateIsRequestable();
    validateIsRequesteeble();
    validateAllowMultiple();
    validateGroups();

    my $dbh = Bugzilla->dbh;
    my $user = Bugzilla->user;
    $dbh->bz_start_transaction();
    $dbh->do('UPDATE flagtypes
                 SET name = ?, description = ?, cc_list = ?,
                     sortkey = ?, is_active = ?, is_requestable = ?,
                     is_requesteeble = ?, is_multiplicable = ?,
                     grant_group_id = ?, request_group_id = ?
               WHERE id = ?',
              undef, ($name, $description, $cc_list, $cgi->param('sortkey'),
                      $cgi->param('is_active'), $cgi->param('is_requestable'),
                      $cgi->param('is_requesteeble'), $cgi->param('is_multiplicable'),
                      scalar($cgi->param('grant_gid')), scalar($cgi->param('request_gid')),
                      $id));
    
    # Update the list of inclusions/exclusions for this flag type.
    validateAndSubmit($id);

    $dbh->bz_commit_transaction();

    # 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.
    my $flag_ids = $dbh->selectcol_arrayref('SELECT DISTINCT flags.id
                                               FROM flags
                                         INNER JOIN bugs
                                                 ON flags.bug_id = bugs.bug_id
                                    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 = ?
                                                AND i.type_id IS NULL',
                                             undef, $id);
    my $flags = Bugzilla::Flag->new_from_list($flag_ids);
    foreach my $flag (@$flags) {
        my $bug = new Bugzilla::Bug($flag->bug_id);
        Bugzilla::Flag::clear($flag, $bug, $flag->attachment);
    }

    $flag_ids = $dbh->selectcol_arrayref('SELECT DISTINCT flags.id
                                            FROM flags
                                      INNER JOIN bugs 
                                              ON flags.bug_id = bugs.bug_id
                                      INNER JOIN flagexclusions AS e
                                              ON flags.type_id = e.type_id
                                           WHERE flags.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)',
                                          undef, $id);
    $flags = Bugzilla::Flag->new_from_list($flag_ids);
    foreach my $flag (@$flags) {
        my $bug = new Bugzilla::Bug($flag->bug_id);
        Bugzilla::Flag::clear($flag, $bug, $flag->attachment);
    }

    # Now silently remove requestees from flags which are no longer
    # specifically requestable.
    if (!$cgi->param('is_requesteeble')) {
        $dbh->do('UPDATE flags SET requestee_id = NULL WHERE type_id = ?',
                 undef, $id);
    }

    $vars->{'name'} = $name;
    $vars->{'message'} = "flag_type_changes_saved";
    delete_token($token);

    $vars->{'bug_types'} = Bugzilla::FlagType::match({'target_type' => 'bug'});
    $vars->{'attachment_types'} = Bugzilla::FlagType::match({'target_type' => 'attachment'});

    # Return the appropriate HTTP response headers.
    print $cgi->header();

    $template->process("admin/flag-type/list.html.tmpl", $vars)
      || ThrowTemplateError($template->error());
}


sub confirmDelete {
  my $flag_type = validateID();

  if ($flag_type->flag_count) {
    $vars->{'flag_type'} = $flag_type;
    $vars->{'token'} = issue_session_token('delete_flagtype');
    # Return the appropriate HTTP response headers.
    print $cgi->header();

    # 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 {
    # We should *always* ask if the admin really wants to delete
    # a flagtype, even if there is no flag belonging to this type.
    my $token = issue_session_token('delete_flagtype');
    deleteType($flag_type, $token);
  }
}


sub deleteType {
    my $flag_type = shift || validateID();
    my $token = shift;
    check_token_data($token, 'delete_flagtype');
    my $id = $flag_type->id;
    my $dbh = Bugzilla->dbh;

    $dbh->bz_start_transaction();

    # Get the name of the flag type so we can tell users
    # what was deleted.
    $vars->{'name'} = $flag_type->name;

    $dbh->do('DELETE FROM flags WHERE type_id = ?', undef, $id);
    $dbh->do('DELETE FROM flaginclusions WHERE type_id = ?', undef, $id);
    $dbh->do('DELETE FROM flagexclusions WHERE type_id = ?', undef, $id);
    $dbh->do('DELETE FROM flagtypes WHERE id = ?', undef, $id);
    $dbh->bz_commit_transaction();

    $vars->{'message'} = "flag_type_deleted";
    delete_token($token);

    $vars->{'bug_types'} = Bugzilla::FlagType::match({'target_type' => 'bug'});
    $vars->{'attachment_types'} = Bugzilla::FlagType::match({'target_type' => 'attachment'});

    # Return the appropriate HTTP response headers.
    print $cgi->header();

    $template->process("admin/flag-type/list.html.tmpl", $vars)
      || ThrowTemplateError($template->error());
}


sub deactivate {
    my $token = shift;
    check_token_data($token, 'delete_flagtype');
    my $flag_type = validateID();
    validateIsActive();

    my $dbh = Bugzilla->dbh;

    $dbh->bz_start_transaction();
    $dbh->do('UPDATE flagtypes SET is_active = 0 WHERE id = ?', undef, $flag_type->id);
    $dbh->bz_commit_transaction();

    $vars->{'message'} = "flag_type_deactivated";
    $vars->{'flag_type'} = $flag_type;
    delete_token($token);

    $vars->{'bug_types'} = Bugzilla::FlagType::match({'target_type' => 'bug'});
    $vars->{'attachment_types'} = Bugzilla::FlagType::match({'target_type' => 'attachment'});

    # Return the appropriate HTTP response headers.
    print $cgi->header();

    # Generate and return the UI (HTML page) from the appropriate template.
    $template->process("admin/flag-type/list.html.tmpl", $vars)
      || ThrowTemplateError($template->error());
}

sub get_products_and_components {
    my $vars = shift;

    my @products = Bugzilla::Product->get_all;
    # We require all unique component names.
    my %components;
    foreach my $product (@products) {
        foreach my $component (@{$product->components}) {
            $components{$component->name} = 1;
        }
    }
    $vars->{'products'} = \@products;
    $vars->{'components'} = [sort(keys %components)];
    return $vars;
}

################################################################################
# Data Validation / Security Authorization
################################################################################

sub validateID {
    my $id = $cgi->param('id');
    my $flag_type = new Bugzilla::FlagType($id)
        || ThrowCodeError('flag_type_nonexistent', { id => $id });

    return $flag_type;
}

sub validateName {
    my $name = $cgi->param('name');
    ($name && $name !~ /[ ,]/ && length($name) <= 50)
      || ThrowUserError("flag_type_name_invalid",
                        { name => $name });
    trick_taint($name);
    return $name;
}

sub validateDescription {
    my $description = $cgi->param('description');
    length($description) < 2**16-1
      || ThrowUserError("flag_type_description_invalid");
    trick_taint($description);
    return $description;
}

sub validateCCList {
    my $cc_list = $cgi->param('cc_list');
    length($cc_list) <= 200
      || ThrowUserError("flag_type_cc_list_invalid", 
                        { cc_list => $cc_list });

    my @addresses = split(/[, ]+/, $cc_list);
    # We do not call Util::validate_email_syntax because these
    # addresses do not require to match 'emailregexp' and do not
    # depend on 'emailsuffix'. So we limit ourselves to a simple
    # sanity check:
    # - match the syntax of a fully qualified email address;
    # - do not contain any illegal character.
    foreach my $address (@addresses) {
        ($address =~ /^[\w\.\+\-=]+@[\w\.\-]+\.[\w\-]+$/
           && $address !~ /[\\\(\)<>&,;:"\[\] \t\r\n]/)
          || ThrowUserError('illegal_email_address',
                            {addr => $address, default => 1});
    }
    trick_taint($cc_list);
    return $cc_list;
}

sub validateProduct {
    my $product_name = shift;
    return unless $product_name;

    my $product = Bugzilla::Product::check_product($product_name);
    return $product;
}

sub validateComponent {
    my ($product, $component_name) = @_;
    return unless $component_name;

    ($product && $product->id)
      || ThrowUserError("flag_type_component_without_product");

    my $component = Bugzilla::Component->check({ product => $product,
                                                 name => $component_name });
    return $component;
}

sub validateSortKey {
    # $sortkey is destroyed if detaint_natural fails.
    my $sortkey = $cgi->param('sortkey');
    detaint_natural($sortkey)
      && $sortkey < 32768
      || ThrowUserError("flag_type_sortkey_invalid", 
                        { sortkey => scalar $cgi->param('sortkey') });
    $cgi->param('sortkey', $sortkey);
}

sub validateTargetType {
    grep($cgi->param('target_type') eq $_, ("bug", "attachment"))
      || ThrowCodeError("flag_type_target_type_invalid", 
                        { target_type => scalar $cgi->param('target_type') });
}

sub validateIsActive {
    $cgi->param('is_active', $cgi->param('is_active') ? 1 : 0);
}

sub validateIsRequestable {
    $cgi->param('is_requestable', $cgi->param('is_requestable') ? 1 : 0);
}

sub validateIsRequesteeble {
    $cgi->param('is_requesteeble', $cgi->param('is_requesteeble') ? 1 : 0);
}

sub validateAllowMultiple {
    $cgi->param('is_multiplicable', $cgi->param('is_multiplicable') ? 1 : 0);
}

sub validateGroups {
    my $dbh = Bugzilla->dbh;
    # Convert group names to group IDs
    foreach my $col ('grant', 'request') {
        my $name = $cgi->param($col . '_group');
        if ($name) {
            trick_taint($name);
            my $gid = $dbh->selectrow_array('SELECT id FROM groups
                                             WHERE name = ?', undef, $name);
            $gid || ThrowUserError("group_unknown", { name => $name });
            $cgi->param($col . '_gid', $gid);
        }
    }
}

# At this point, values either come the DB itself or have been recently
# added by the user and have passed all validation tests.
# The only way to have invalid product/component combinations is to
# hack the URL. So we silently ignore them, if any.
sub validateAndSubmit {
    my ($id) = @_;
    my $dbh = Bugzilla->dbh;

    # Cache product objects.
    my %products;
    foreach my $category_type ("inclusions", "exclusions") {
        # Will be used several times below.
        my $sth = $dbh->prepare("INSERT INTO flag$category_type " .
                                "(type_id, product_id, component_id) " .
                                "VALUES (?, ?, ?)");

        $dbh->do("DELETE FROM flag$category_type WHERE type_id = ?", undef, $id);
        foreach my $category ($cgi->param($category_type)) {
            trick_taint($category);
            my ($product_id, $component_id) = split(":", $category);
            # Does the product exist?
            if ($product_id) {
                $products{$product_id} ||= new Bugzilla::Product($product_id);
                next unless defined $products{$product_id};
            }
            # A component was selected without a product being selected.
            next if (!$product_id && $component_id);
            # Does the component belong to this product?
            if ($component_id) {
                my @match = grep {$_->id == $component_id} @{$products{$product_id}->components};
                next unless scalar(@match);
            }
            $product_id ||= undef;
            $component_id ||= undef;
            $sth->execute($id, $product_id, $component_id);
        }
    }
}

sub filter_group {
    my $flag_types = shift;
    return $flag_types unless Bugzilla->cgi->param('group');

    my $gid = scalar $cgi->param('group');
    my @flag_types = grep {($_->grant_group && $_->grant_group->id == $gid)
                           || ($_->request_group && $_->request_group->id == $gid)} @$flag_types;

    return \@flag_types;
}