Commit 91b171e7 authored by myk%mozilla.org's avatar myk%mozilla.org

Fix for bug 98801: Implementation of the request tracker, a set of enhancements…

Fix for bug 98801: Implementation of the request tracker, a set of enhancements to attachment statuses. r=gerv,bbaetz
parent 90975fe9
......@@ -31,10 +31,32 @@ package Attachment;
# This module requires that its caller have said "require CGI.pl" to import
# relevant functions from that script and its companion globals.pl.
# Use the Flag module to handle flags.
use Bugzilla::Flag;
############################################################################
# Functions
############################################################################
sub new {
# Returns a hash of information about the attachment with the given ID.
my ($invocant, $id) = @_;
return undef if !$id;
my $self = { 'id' => $id };
my $class = ref($invocant) || $invocant;
bless($self, $class);
&::PushGlobalSQLState();
&::SendSQL("SELECT 1, description, bug_id FROM attachments " .
"WHERE attach_id = $id");
($self->{'exists'}, $self->{'summary'}, $self->{'bug_id'}) =
&::FetchSQLData();
&::PopGlobalSQLState();
return $self;
}
sub query
{
# Retrieves and returns an array of attachment records for a given bug.
......@@ -65,23 +87,9 @@ sub query
$a{'date'} = "$1-$2-$3 $4:$5";
}
# Retrieve a list of status flags that have been set on the attachment.
&::PushGlobalSQLState();
&::SendSQL("
SELECT name
FROM attachstatuses, attachstatusdefs
WHERE attach_id = $a{'attachid'}
AND attachstatuses.statusid = attachstatusdefs.id
ORDER BY sortkey
");
my @statuses = ();
while (&::MoreSQLData()) {
my ($status) = &::FetchSQLData();
push @statuses , $status;
}
$a{'statuses'} = \@statuses;
&::PopGlobalSQLState();
# Retrieve a list of flags for this attachment.
$a{'flags'} = Bugzilla::Flag::match({ 'attach_id' => $a{'attachid'} });
# We will display the edit link if the user can edit the attachment;
# ie the are the submitter, or they have canedit.
# Also show the link if the user is not logged in - in that cae,
......
......@@ -31,10 +31,32 @@ package Attachment;
# This module requires that its caller have said "require CGI.pl" to import
# relevant functions from that script and its companion globals.pl.
# Use the Flag module to handle flags.
use Bugzilla::Flag;
############################################################################
# Functions
############################################################################
sub new {
# Returns a hash of information about the attachment with the given ID.
my ($invocant, $id) = @_;
return undef if !$id;
my $self = { 'id' => $id };
my $class = ref($invocant) || $invocant;
bless($self, $class);
&::PushGlobalSQLState();
&::SendSQL("SELECT 1, description, bug_id FROM attachments " .
"WHERE attach_id = $id");
($self->{'exists'}, $self->{'summary'}, $self->{'bug_id'}) =
&::FetchSQLData();
&::PopGlobalSQLState();
return $self;
}
sub query
{
# Retrieves and returns an array of attachment records for a given bug.
......@@ -65,23 +87,9 @@ sub query
$a{'date'} = "$1-$2-$3 $4:$5";
}
# Retrieve a list of status flags that have been set on the attachment.
&::PushGlobalSQLState();
&::SendSQL("
SELECT name
FROM attachstatuses, attachstatusdefs
WHERE attach_id = $a{'attachid'}
AND attachstatuses.statusid = attachstatusdefs.id
ORDER BY sortkey
");
my @statuses = ();
while (&::MoreSQLData()) {
my ($status) = &::FetchSQLData();
push @statuses , $status;
}
$a{'statuses'} = \@statuses;
&::PopGlobalSQLState();
# Retrieve a list of flags for this attachment.
$a{'flags'} = Bugzilla::Flag::match({ 'attach_id' => $a{'attachid'} });
# We will display the edit link if the user can edit the attachment;
# ie the are the submitter, or they have canedit.
# Also show the link if the user is not logged in - in that cae,
......
......@@ -62,6 +62,7 @@ sub init {
my @fields;
my @supptables;
my @wherepart;
my @having = ("(cntuseringroups = cntbugingroups OR canseeanyway)");
@fields = @$fieldsref if $fieldsref;
my %F;
my %M;
......@@ -265,8 +266,8 @@ sub init {
}
my $chartid;
# $statusid is used by the code that queries for attachment statuses.
my $statusid = 0;
# $type_id is used by the code that queries for attachment flags.
my $type_id = 0;
my $f;
my $ff;
my $t;
......@@ -358,69 +359,61 @@ sub init {
}
$f = "$table.$field";
},
"^attachstatusdefs.name," => sub {
# The below has Fun with the names for attachment statuses. This
# isn't needed for changed* queries, so exclude those - the
# generic stuff will cope
"^flagtypes.name," => sub {
# Matches bugs by flag name/status.
# Note that--for the purposes of querying--a flag comprises
# its name plus its status (i.e. a flag named "review"
# with a status of "+" can be found by searching for "review+").
# Don't do anything if this condition is about changes to flags,
# as the generic change condition processors can handle those.
return if ($t =~ m/^changed/);
# Searching for "status != 'bar'" wants us to look for an
# attachment without the 'bar' status, not for an attachment with
# a status not equal to 'bar' (Which would pick up an attachment
# with more than one status). We do this by LEFT JOINS, after
# grabbing the matching attachment status ids.
# Note that this still won't find bugs with no attachments, since
# that isn't really what people would expect.
# First, get the attachment status ids, using the other funcs
# to match the WHERE term.
# Note that we need to reverse the negated bits for this to work
# This somewhat abuses the definitions of the various terms -
# eg, does 'contains all' mean that the status has to contain all
# those words, or that all those words must be exact matches to
# statuses, which must all be on a single attachment, or should
# the match on the status descriptions be a contains match, too?
my $inverted = 0;
if ($t =~ m/not(.*)/) {
$t = $1;
$inverted = 1;
}
$ref = $funcsbykey{",$t"};
&$ref;
&::SendSQL("SELECT id FROM attachstatusdefs WHERE $term");
my @as_ids;
while (&::MoreSQLData()) {
push @as_ids, &::FetchOneColumn();
}
# When searching for multiple statuses within a single boolean chart,
# we want to match each status record separately. In other words,
# "status = 'foo' AND status = 'bar'" should match attachments with
# one status record equal to "foo" and another one equal to "bar",
# not attachments where the same status record equals both "foo" and
# "bar" (which is nonsensical). In order to do this we must add an
# additional counter to the end of the "attachstatuses" table
# reference.
++$statusid;
my $attachtable = "attachments_$chartid";
my $statustable = "attachstatuses_${chartid}_$statusid";
push(@supptables, "attachments $attachtable");
my $join = "LEFT JOIN attachstatuses $statustable ON ".
"($attachtable.attach_id = $statustable.attach_id AND " .
"$statustable.statusid IN (" . join(",", @as_ids) . "))";
push(@supptables, $join);
push(@wherepart, "bugs.bug_id = $attachtable.bug_id");
if ($inverted) {
$term = "$statustable.statusid IS NULL";
} else {
$term = "$statustable.statusid IS NOT NULL";
# Add the flags and flagtypes tables to the query. We do
# a left join here so bugs without any flags still match
# negative conditions (f.e. "flag isn't review+").
my $flags = "flags_$chartid";
push(@supptables, "LEFT JOIN flags $flags " .
"ON bugs.bug_id = $flags.bug_id");
my $flagtypes = "flagtypes_$chartid";
push(@supptables, "LEFT JOIN flagtypes $flagtypes " .
"ON $flags.type_id = $flagtypes.id");
# Generate the condition by running the operator-specific function.
# Afterwards the condition resides in the global $term variable.
$ff = "CONCAT($flagtypes.name, $flags.status)";
&{$funcsbykey{",$t"}};
# If this is a negative condition (f.e. flag isn't "review+"),
# we only want bugs where all flags match the condition, not
# those where any flag matches, which needs special magic.
# Instead of adding the condition to the WHERE clause, we select
# the number of flags matching the condition and the total number
# of flags on each bug, then compare them in a HAVING clause.
# If the numbers are the same, all flags match the condition,
# so this bug should be included.
if ($t =~ m/not/) {
push(@fields, "SUM($ff IS NOT NULL) AS allflags_$chartid");
push(@fields, "SUM($term) AS matchingflags_$chartid");
push(@having, "allflags_$chartid = matchingflags_$chartid");
$term = "0=0";
}
},
"^requesters.login_name," => sub {
push(@supptables, "flags flags_$chartid");
push(@wherepart, "bugs.bug_id = flags_$chartid.bug_id");
push(@supptables, "profiles requesters_$chartid");
push(@wherepart, "flags_$chartid.requester_id = requesters_$chartid.userid");
$f = "requesters_$chartid.login_name";
},
"^setters.login_name," => sub {
push(@supptables, "flags flags_$chartid");
push(@wherepart, "bugs.bug_id = flags_$chartid.bug_id");
push(@supptables, "profiles setters_$chartid");
push(@wherepart, "flags_$chartid.setter_id = setters_$chartid.userid");
$f = "setters_$chartid.login_name";
},
"^changedin," => sub {
$f = "(to_days(now()) - to_days(bugs.delta_ts))";
},
......@@ -817,8 +810,7 @@ sub init {
# Make sure we create a legal SQL query.
@andlist = ("1 = 1") if !@andlist;
my $query = ("SELECT DISTINCT " .
join(', ', @fields) .
my $query = ("SELECT " . join(', ', @fields) .
", COUNT(DISTINCT ugmap.group_id) AS cntuseringroups, " .
" COUNT(DISTINCT bgmap.group_id) AS cntbugingroups, " .
" ((COUNT(DISTINCT ccmap.who) AND cclist_accessible) " .
......@@ -834,11 +826,9 @@ sub init {
" LEFT JOIN cc AS ccmap " .
" ON ccmap.who = $::userid AND ccmap.bug_id = bugs.bug_id " .
" WHERE " . join(' AND ', (@wherepart, @andlist)) .
" GROUP BY bugs.bug_id " .
" HAVING cntuseringroups = cntbugingroups" .
" OR canseeanyway"
);
" GROUP BY bugs.bug_id" .
" HAVING " . join(" AND ", @having));
if ($debug) {
print "<p><code>" . value_quote($query) . "</code></p>\n";
exit;
......
# -*- 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 utilities for dealing with Bugzilla users.
package Bugzilla::User;
################################################################################
# Functions
################################################################################
my $user_cache = {};
sub new {
# Returns a hash of information about a particular user.
my $invocant = shift;
my $class = ref($invocant) || $invocant;
my $exists = 1;
my ($id, $name, $email) = @_;
return undef if !$id;
return $user_cache->{$id} if exists($user_cache->{$id});
my $self = { 'id' => $id };
bless($self, $class);
if (!$name && !$email) {
&::PushGlobalSQLState();
&::SendSQL("SELECT 1, realname, login_name FROM profiles WHERE userid = $id");
($exists, $name, $email) = &::FetchSQLData();
&::PopGlobalSQLState();
}
$self->{'name'} = $name;
$self->{'email'} = $email;
$self->{'exists'} = $exists;
# Generate a string to identify the user by name + email if the user
# has a name or by email only if she doesn't.
$self->{'identity'} = $name ? "$name <$email>" : $email;
# Generate a user "nickname" -- i.e. a shorter, not-necessarily-unique name
# by which to identify the user. Currently the part of the user's email
# address before the at sign (@), but that could change, especially if we
# implement usernames not dependent on email address.
my @email_components = split("@", $email);
$self->{'nick'} = $email_components[0];
$user_cache->{$id} = $self;
return $self;
}
sub match {
# Generates a list of users whose login name (email address) or real name
# matches a substring.
# $str contains the string to match against, while $limit contains the
# maximum number of records to retrieve.
my ($str, $limit, $exclude_disabled) = @_;
# Build the query.
my $sqlstr = &::SqlQuote($str);
my $qry = "
SELECT userid, realname, login_name
FROM profiles
WHERE (INSTR(login_name, $sqlstr) OR INSTR(realname, $sqlstr))
";
$qry .= "AND disabledtext = '' " if $exclude_disabled;
$qry .= "ORDER BY realname, login_name ";
$qry .= "LIMIT $limit " if $limit;
# Execute the query, retrieve the results, and make them into User objects.
my @users;
&::PushGlobalSQLState();
&::SendSQL($qry);
push(@users, new Bugzilla::User(&::FetchSQLData())) while &::MoreSQLData();
&::PopGlobalSQLState();
return \@users;
}
sub email_prefs {
# Get or set (not implemented) the user's email notification preferences.
my $self = shift;
# If the calling code is setting the email preferences, update the object
# but don't do anything else. This needs to write email preferences back
# to the database.
if (@_) { $self->{email_prefs} = shift; return; }
# If we already got them from the database, return the existing values.
return $self->{email_prefs} if $self->{email_prefs};
# Retrieve the values from the database.
&::SendSQL("SELECT emailflags FROM profiles WHERE userid = $self->{id}");
my ($flags) = &::FetchSQLData();
my @roles = qw(Owner Reporter QAcontact CClist Voter);
my @reasons = qw(Removeme Comments Attachments Status Resolved Keywords
CC Other Unconfirmed);
# If the prefs are empty, this user hasn't visited the email pane
# of userprefs.cgi since before the change to use the "emailflags"
# column, so initialize that field with the default prefs.
if (!$flags) {
# Create a default prefs string that causes the user to get all email.
$flags = "ExcludeSelf~on~FlagRequestee~on~FlagRequester~on~";
foreach my $role (@roles) {
foreach my $reason (@reasons) {
$flags .= "email$role$reason~on~";
}
}
chop $flags;
}
# Convert the prefs from the flags string from the database into
# a Perl record. The 255 param is here because split will trim
# any trailing null fields without a third param, which causes Perl
# to eject lots of warnings. Any suitably large number would do.
my $prefs = { split(/~/, $flags, 255) };
# Determine the value of the "excludeself" global email preference.
# Note that the value of "excludeself" is assumed to be off if the
# preference does not exist in the user's list, unlike other
# preferences whose value is assumed to be on if they do not exist.
$prefs->{ExcludeSelf} =
exists($prefs->{ExcludeSelf}) && $prefs->{ExcludeSelf} eq "on";
# Determine the value of the global request preferences.
foreach my $pref qw(FlagRequestee FlagRequester) {
$prefs->{$pref} = !exists($prefs->{$pref}) || $prefs->{$pref} eq "on";
}
# Determine the value of the rest of the preferences by looping over
# all roles and reasons and converting their values to Perl booleans.
foreach my $role (@roles) {
foreach my $reason (@reasons) {
my $key = "email$role$reason";
$prefs->{$key} = !exists($prefs->{$key}) || $prefs->{$key} eq "on";
}
}
$self->{email_prefs} = $prefs;
return $self->{email_prefs};
}
1;
......@@ -28,6 +28,10 @@ use RelationSet;
# Use the Attachment module to display attachments for the bug.
use Attachment;
# Use the Flag modules to display flags on the bug.
use Bugzilla::Flag;
use Bugzilla::FlagType;
sub show_bug {
# Shut up misguided -w warnings about "used only once". For some reason,
# "use vars" chokes on me when I try it here.
......@@ -76,10 +80,10 @@ sub show_bug {
# Populate the bug hash with the info we get directly from the DB.
my $query = "
SELECT bugs.bug_id, alias, products.name, version, rep_platform,
op_sys, bug_status, resolution, priority,
bug_severity, components.name, assigned_to, reporter,
bug_file_loc, short_desc, target_milestone,
SELECT bugs.bug_id, alias, bugs.product_id, products.name, version,
rep_platform, op_sys, bug_status, resolution, priority,
bug_severity, bugs.component_id, components.name, assigned_to,
reporter, bug_file_loc, short_desc, target_milestone,
qa_contact, status_whiteboard,
date_format(creation_ts,'%Y-%m-%d %H:%i'),
delta_ts, sum(votes.count), delta_ts calc_disp_date
......@@ -101,12 +105,12 @@ sub show_bug {
my $value;
my $disp_date;
my @row = FetchSQLData();
foreach my $field ("bug_id", "alias", "product", "version", "rep_platform",
"op_sys", "bug_status", "resolution", "priority",
"bug_severity", "component", "assigned_to", "reporter",
"bug_file_loc", "short_desc", "target_milestone",
"qa_contact", "status_whiteboard", "creation_ts",
"delta_ts", "votes", "calc_disp_date")
foreach my $field ("bug_id", "alias", "product_id", "product", "version",
"rep_platform", "op_sys", "bug_status", "resolution",
"priority", "bug_severity", "component_id", "component",
"assigned_to", "reporter", "bug_file_loc", "short_desc",
"target_milestone", "qa_contact", "status_whiteboard",
"creation_ts", "delta_ts", "votes", "calc_disp_date")
{
$value = shift(@row);
if ($field eq "calc_disp_date") {
......@@ -197,6 +201,28 @@ sub show_bug {
# Attachments
$bug{'attachments'} = Attachment::query($id);
# The types of flags that can be set on this bug.
# If none, no UI for setting flags will be displayed.
my $flag_types =
Bugzilla::FlagType::match({ 'target_type' => 'bug',
'product_id' => $bug{'product_id'},
'component_id' => $bug{'component_id'},
'is_active' => 1 });
foreach my $flag_type (@$flag_types) {
$flag_type->{'flags'} =
Bugzilla::Flag::match({ 'bug_id' => $id ,
'target_type' => 'bug' });
}
$vars->{'flag_types'} = $flag_types;
# The number of types of flags that can be set on attachments
# to this bug. If none, flags won't be shown in the list of attachments.
$vars->{'num_attachment_flag_types'} =
Bugzilla::FlagType::count({ 'target_type' => 'a',
'product_id' => $bug{'product_id'},
'component_id' => $bug{'component_id'},
'is_active' => 1 });
# Dependencies
my @list;
......
......@@ -581,7 +581,9 @@ if ($action eq 'delete') {
bugs WRITE,
bugs_activity WRITE,
components WRITE,
dependencies WRITE");
dependencies WRITE,
flaginclusions WRITE,
flagexclusions WRITE");
# According to MySQL doc I cannot do a DELETE x.* FROM x JOIN Y,
# so I have to iterate over bugs and delete all the indivial entries
......@@ -610,6 +612,12 @@ if ($action eq 'delete') {
print "Bugs deleted.<BR>\n";
}
SendSQL("DELETE FROM flaginclusions
WHERE component_id=$component_id");
SendSQL("DELETE FROM flagexclusions
WHERE component_id=$component_id");
print "Flag inclusions and exclusions deleted.<BR>\n";
SendSQL("DELETE FROM components
WHERE id=$component_id");
print "Components deleted.<P>\n";
......
......@@ -539,7 +539,9 @@ if ($action eq 'delete') {
products WRITE,
groups WRITE,
profiles WRITE,
milestones WRITE");
milestones WRITE,
flaginclusions WRITE,
flagexclusions WRITE);
# According to MySQL doc I cannot do a DELETE x.* FROM x JOIN Y,
# so I have to iterate over bugs and delete all the indivial entries
......@@ -581,6 +583,12 @@ if ($action eq 'delete') {
WHERE product_id=$product_id");
print "Milestones deleted.<BR>\n";
SendSQL("DELETE FROM flaginclusions
WHERE product_id=$product_id");
SendSQL("DELETE FROM flagexclusions
WHERE product_id=$product_id");
print "Flag inclusions and exclusions deleted.<BR>\n";
SendSQL("DELETE FROM products
WHERE id=$product_id");
print "Product '$product' deleted.<BR>\n";
......
......@@ -300,7 +300,12 @@ sub FetchOneColumn {
"status", "resolution", "summary");
sub AppendComment {
my ($bugid,$who,$comment,$isprivate) = (@_);
my ($bugid, $who, $comment, $isprivate, $timestamp) = @_;
# Use the date/time we were given if possible (allowing calling code
# to synchronize the comment's timestamp with those of other records).
$timestamp = ($timestamp ? SqlQuote($timestamp) : "NOW()");
$comment =~ s/\r\n/\n/g; # Get rid of windows-style line endings.
$comment =~ s/\r/\n/g; # Get rid of mac-style line endings.
if ($comment =~ /^\s*$/) { # Nothin' but whitespace.
......@@ -310,7 +315,7 @@ sub AppendComment {
my $whoid = DBNameToIdAndCheck($who);
my $privacyval = $isprivate ? 1 : 0 ;
SendSQL("INSERT INTO longdescs (bug_id, who, bug_when, thetext, isprivate) " .
"VALUES($bugid, $whoid, now(), " . SqlQuote($comment) . ", " .
"VALUES($bugid, $whoid, $timestamp, " . SqlQuote($comment) . ", " .
$privacyval . ")");
SendSQL("UPDATE bugs SET delta_ts = now() WHERE bug_id = $bugid");
......@@ -902,8 +907,7 @@ sub get_product_name {
sub get_component_id {
my ($prod_id, $comp) = @_;
die "non-numeric prod_id '$prod_id' passed to get_component_id"
unless ($prod_id =~ /^\d+$/);
return undef unless ($prod_id =~ /^\d+$/);
PushGlobalSQLState();
SendSQL("SELECT id FROM components " .
"WHERE product_id = $prod_id AND name = " . SqlQuote($comp));
......
......@@ -36,6 +36,9 @@ require "bug_form.pl";
use RelationSet;
# Use the Flag module to modify flag data if the user set flags.
use Bugzilla::Flag;
# Shut up misguided -w warnings about "used only once":
use vars qw(%versions
......@@ -1052,8 +1055,9 @@ foreach my $id (@idlist) {
"profiles $write, dependencies $write, votes $write, " .
"products READ, components READ, " .
"keywords $write, longdescs $write, fielddefs $write, " .
"bug_group_map $write, " .
"user_group_map READ, " .
"bug_group_map $write, flags $write, " .
"user_group_map READ, flagtypes READ, " .
"flaginclusions AS i READ, flagexclusions AS e READ, " .
"keyworddefs READ, groups READ, attachments READ");
my @oldvalues = SnapShotBug($id);
my %oldhash;
......@@ -1238,7 +1242,7 @@ foreach my $id (@idlist) {
LogActivityEntry($id, "bug_group", $groupDelNames, $groupAddNames);
if (defined $::FORM{'comment'}) {
AppendComment($id, $::COOKIE{'Bugzilla_login'}, $::FORM{'comment'},
$::FORM{'commentprivacy'});
$::FORM{'commentprivacy'}, $timestamp);
}
my $removedCcString = "";
......@@ -1399,6 +1403,14 @@ foreach my $id (@idlist) {
# what has changed since before we wrote out the new values.
#
my @newvalues = SnapShotBug($id);
my %newhash;
$i = 0;
foreach my $col (@::log_columns) {
# Consider NULL db entries to be equivalent to the empty string
$newvalues[$i] ||= '';
$newhash{$col} = $newvalues[$i];
$i++;
}
# for passing to processmail to ensure that when someone is removed
# from one of these fields, they get notified of that fact (if desired)
......@@ -1411,12 +1423,6 @@ foreach my $id (@idlist) {
# values in place.
my $old = shift @oldvalues;
my $new = shift @newvalues;
if (!defined $old) {
$old = "";
}
if (!defined $new) {
$new = "";
}
if ($old ne $new) {
# Products and components are now stored in the DB using ID's
......@@ -1461,6 +1467,11 @@ foreach my $id (@idlist) {
LogActivityEntry($id,$col,$old,$new);
}
}
# Set and update flags.
if ($UserInEditGroupSet) {
my $target = Bugzilla::Flag::GetTarget($id);
Bugzilla::Flag::process($target, $timestamp, \%::FORM);
}
if ($bug_changed) {
SendSQL("UPDATE bugs SET delta_ts = " . SqlQuote($timestamp) . " WHERE bug_id = $id");
}
......
// Adds to the target select object all elements in array that
// correspond to the elements selected in source.
// - array should be a array of arrays, indexed by product name. the
// array should contain the elements that correspont to that
// product. Example:
// var array = Array();
// array['ProductOne'] = [ 'ComponentA', 'ComponentB' ];
// updateSelect(array, source, target);
// - sel is a list of selected items, either whole or a diff
// depending on sel_is_diff.
// - sel_is_diff determines if we are sending in just a diff or the
// whole selection. a diff is used to optimize adding selections.
// - target should be the target select object.
// - single specifies if we selected a single item. if we did, no
// need to merge.
function updateSelect( array, sel, target, sel_is_diff, single, blank ) {
var i, j, comp;
// if single, even if it's a diff (happens when you have nothing
// selected and select one item alone), skip this.
if ( ! single ) {
// array merging/sorting in the case of multiple selections
if ( sel_is_diff ) {
// merge in the current options with the first selection
comp = merge_arrays( array[sel[0]], target.options, 1 );
// merge the rest of the selection with the results
for ( i = 1 ; i < sel.length ; i++ ) {
comp = merge_arrays( array[sel[i]], comp, 0 );
}
} else {
// here we micro-optimize for two arrays to avoid merging with a
// null array
comp = merge_arrays( array[sel[0]],array[sel[1]], 0 );
// merge the arrays. not very good for multiple selections.
for ( i = 2; i < sel.length; i++ ) {
comp = merge_arrays( comp, array[sel[i]], 0 );
}
}
} else {
// single item in selection, just get me the list
comp = array[sel[0]];
}
// save the selection in the target select so we can restore it later
var selections = new Array();
for ( i = 0; i < target.options.length; i++ )
if (target.options[i].selected) selections.push(target.options[i].value);
// clear select
target.options.length = 0;
// add empty "Any" value back to the list
if (blank) target.options[0] = new Option( blank, "" );
// load elements of list into select
for ( i = 0; i < comp.length; i++ ) {
target.options[target.options.length] = new Option( comp[i], comp[i] );
}
// restore the selection
for ( i=0 ; i<selections.length ; i++ )
for ( j=0 ; j<target.options.length ; j++ )
if (target.options[j].value == selections[i]) target.options[j].selected = true;
}
// Returns elements in a that are not in b.
// NOT A REAL DIFF: does not check the reverse.
// - a,b: arrays of values to be compare.
function fake_diff_array( a, b ) {
var newsel = new Array();
// do a boring array diff to see who's new
for ( var ia in a ) {
var found = 0;
for ( var ib in b ) {
if ( a[ia] == b[ib] ) {
found = 1;
}
}
if ( ! found ) {
newsel[newsel.length] = a[ia];
}
found = 0;
}
return newsel;
}
// takes two arrays and sorts them by string, returning a new, sorted
// array. the merge removes dupes, too.
// - a, b: arrays to be merge.
// - b_is_select: if true, then b is actually an optionitem and as
// such we need to use item.value on it.
function merge_arrays( a, b, b_is_select ) {
var pos_a = 0;
var pos_b = 0;
var ret = new Array();
var bitem, aitem;
// iterate through both arrays and add the larger item to the return
// list. remove dupes, too. Use toLowerCase to provide
// case-insensitivity.
while ( ( pos_a < a.length ) && ( pos_b < b.length ) ) {
if ( b_is_select ) {
bitem = b[pos_b].value;
} else {
bitem = b[pos_b];
}
aitem = a[pos_a];
// smaller item in list a
if ( aitem.toLowerCase() < bitem.toLowerCase() ) {
ret[ret.length] = aitem;
pos_a++;
} else {
// smaller item in list b
if ( aitem.toLowerCase() > bitem.toLowerCase() ) {
ret[ret.length] = bitem;
pos_b++;
} else {
// list contents are equal, inc both counters.
ret[ret.length] = aitem;
pos_a++;
pos_b++;
}
}
}
// catch leftovers here. these sections are ugly code-copying.
if ( pos_a < a.length ) {
for ( ; pos_a < a.length ; pos_a++ ) {
ret[ret.length] = a[pos_a];
}
}
if ( pos_b < b.length ) {
for ( ; pos_b < b.length; pos_b++ ) {
if ( b_is_select ) {
bitem = b[pos_b].value;
} else {
bitem = b[pos_b];
}
ret[ret.length] = bitem;
}
}
return ret;
}
// selectProduct reads the selection from f[productfield] and updates
// f.version, component and target_milestone accordingly.
// - f: a form containing product, component, varsion and
// target_milestone select boxes.
// globals (3vil!):
// - cpts, vers, tms: array of arrays, indexed by product name. the
// subarrays contain a list of names to be fed to the respective
// selectboxes. For bugzilla, these are generated with perl code
// at page start.
// - usetms: this is a global boolean that is defined if the
// bugzilla installation has it turned on. generated in perl too.
// - first_load: boolean, specifying if it's the first time we load
// the query page.
// - last_sel: saves our last selection list so we know what has
// changed, and optimize for additions.
function selectProduct( f , productfield, componentfield, blank ) {
// this is to avoid handling events that occur before the form
// itself is ready, which happens in buggy browsers.
if ( ( !f ) || ( ! f[productfield] ) ) {
return;
}
// if this is the first load and nothing is selected, no need to
// merge and sort all components; perl gives it to us sorted.
if ( ( first_load ) && ( f[productfield].selectedIndex == -1 ) ) {
first_load = 0;
return;
}
// turn first_load off. this is tricky, since it seems to be
// redundant with the above clause. It's not: if when we first load
// the page there is _one_ element selected, it won't fall into that
// clause, and first_load will remain 1. Then, if we unselect that
// item, selectProduct will be called but the clause will be valid
// (since selectedIndex == -1), and we will return - incorrectly -
// without merge/sorting.
first_load = 0;
// - sel keeps the array of products we are selected.
// - is_diff says if it's a full list or just a list of products that
// were added to the current selection.
// - single indicates if a single item was selected
var sel = Array();
var is_diff = 0;
var single;
// if nothing selected, pick all
if ( f[productfield].selectedIndex == -1 ) {
for ( var i = 0 ; i < f[productfield].length ; i++ ) {
sel[sel.length] = f[productfield].options[i].value;
}
single = 0;
} else {
for ( i = 0 ; i < f[productfield].length ; i++ ) {
if ( f[productfield].options[i].selected ) {
sel[sel.length] = f[productfield].options[i].value;
}
}
single = ( sel.length == 1 );
// save last_sel before we kill it
var tmp = last_sel;
last_sel = sel;
// this is an optimization: if we've added components, no need
// to remerge them; just merge the new ones with the existing
// options.
if ( ( tmp ) && ( tmp.length < sel.length ) ) {
sel = fake_diff_array(sel, tmp);
is_diff = 1;
}
}
// do the actual fill/update
updateSelect( cpts, sel, f[componentfield], is_diff, single, blank );
}
This diff is collapsed. Click to expand it.
......@@ -232,11 +232,11 @@ CrossCheck("fielddefs", "fieldid",
["bugs_activity", "fieldid"]);
CrossCheck("attachments", "attach_id",
["attachstatuses", "attach_id"],
["flags", "attach_id"],
["bugs_activity", "attach_id"]);
CrossCheck("attachstatusdefs", "id",
["attachstatuses", "statusid"]);
CrossCheck("flagtypes", "id",
["flags", "type_id"]);
CrossCheck("bugs", "bug_id",
["bugs_activity", "bug_id"],
......@@ -280,7 +280,7 @@ CrossCheck("products", "id",
["components", "product_id", "name"],
["milestones", "product_id", "value"],
["versions", "product_id", "value"],
["attachstatusdefs", "product_id", "name"]);
["flagtypes", "product_id", "name"]);
DateCheck("groups", "last_changed");
DateCheck("profiles", "refreshed_when");
......
......@@ -83,9 +83,27 @@
<tr>
<td width="150"></td>
<td>
<label for="ExcludeSelf">Only email me reports of changes made by other people</label>
<input type="checkbox" name="ExcludeSelf" id="ExcludeSelf" value="on"
[% " checked" IF excludeself %]>
<label for="ExcludeSelf">Only email me reports of changes made by other people</label>
<br>
</td>
</tr>
<tr>
<td width="150"></td>
<td>
<input type="checkbox" name="FlagRequestee" id="FlagRequestee" value="on"
[% " checked" IF FlagRequestee %]>
<label for="FlagRequestee">Email me when someone asks me to set a flag</label>
<br>
</td>
</tr>
<tr>
<td width="150"></td>
<td>
<input type="checkbox" name="FlagRequester" id="FlagRequester" value="on"
[% " checked" IF FlagRequester %]>
<label for="FlagRequester">Email me when someone sets a flag I asked for</label>
<br>
</td>
</tr>
......
<!-- 1.0@bugzilla.org -->
[%# 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>
#%]
[%# Filter off the name here to be used multiple times below %]
[% name = BLOCK %][% flag_type.name FILTER html %][% END %]
[% PROCESS global/header.html.tmpl
title = "Confirm Deletion of Flag Type '$name'"
%]
<p>
There are [% flag_count %] flags of type [% name %].
If you delete this type, those flags will also be deleted. Note that
instead of deleting the type you can
<a href="editflagtypes.cgi?action=deactivate&id=[% flag_type.id %]">deactivate it</a>,
in which case the type and its flags will remain in the database
but will not appear in the Bugzilla UI.
</p>
<table>
<tr>
<td colspan=2>
Do you really want to delete this type?
</td>
</tr>
<tr>
<td>
<a href="editflagtypes.cgi?action=delete&id=[% flag_type.id %]">
Yes, delete
</a>
</td>
<td align="right">
<a href="editflagtypes.cgi">
No, don't delete
</a>
</td>
</tr>
</table>
[% PROCESS global/footer.html.tmpl %]
[%# 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>
#%]
[%# The javascript and header_html blocks get used in header.html.tmpl. %]
[% javascript = BLOCK %]
var usetms = 0; // do we have target milestone?
var first_load = 1; // is this the first time we load the page?
var last_sel = []; // caches last selection
var cpts = new Array();
[% FOREACH p = products %]
cpts['[% p FILTER js %]'] = [
[%- FOREACH item = components_by_product.$p %]'[% item FILTER js %]'[% ", " UNLESS loop.last %] [%- END -%] ];
[% END %]
[% END %]
[% header_html = BLOCK %]
<script language="JavaScript" type="text/javascript" src="productmenu.js"></script>
[% END %]
[% IF type.target_type == "bug" %]
[% title = "Create Flag Type for Bugs" %]
[% ELSE %]
[% title = "Create Flag Type for Attachments" %]
[% END %]
[% IF last_action == "copy" %]
[% title = "Create Flag Type Based on $type.name" %]
[% ELSIF last_action == "edit" %]
[% title = "Edit Flag Type $type.name" %]
[% END %]
[% PROCESS global/header.html.tmpl
title = title
style = "
table#form th { text-align: right; vertical-align: baseline; white-space: nowrap; }
table#form td { text-align: left; vertical-align: baseline; }
"
onload="selectProduct(forms[0], 'product', 'component', '__Any__');"
%]
<form method="post" action="editflagtypes.cgi">
<input type="hidden" name="action" value="[% action %]">
<input type="hidden" name="id" value="[% type.id %]">
<input type="hidden" name="target_type" value="[% type.target_type %]">
[% FOREACH category = type.inclusions %]
<input type="hidden" name="inclusions" value="[% category %]">
[% END %]
[% FOREACH category = type.exclusions %]
<input type="hidden" name="exclusions" value="[% category %]">
[% END %]
<table id="form" cellspacing="0" cellpadding="4" border="0">
<tr>
<th>Name:</th>
<td>
a short name identifying this type<br>
<input type="text" name="name" value="[% type.name FILTER html %]"
size="50" maxlength="50">
</td>
</tr>
<tr>
<th>Description:</th>
<td>
a comprehensive description of this type<br>
<textarea name="description" rows="4" cols="80">[% type.description FILTER html %]</textarea>
</td>
</tr>
<tr>
<th>Category:</th>
<td>
the products/components to which [% type.target_type %]s must
(inclusions) or must not (exclusions) belong in order for users
to be able to set flags of this type for them
<table>
<tr>
<td style="vertical-align: top;">
<b>Product/Component:</b><br>
<select name="product" onChange="selectProduct(this.form, 'product', 'component', '__Any__');">
<option value="">__Any__</option>
[% FOREACH item = products %]
<option value="[% item %]" [% "selected" IF type.product.name == item %]>[% item %]</option>
[% END %]
</select><br>
<select name="component">
<option value="">__Any__</option>
[% FOREACH item = components %]
<option value="[% item %]" [% "selected" IF type.component.name == item %]>[% item %]</option>
[% END %]
</select><br>
<input type="submit" name="categoryAction" value="Include">
<input type="submit" name="categoryAction" value="Exclude">
</td>
<td style="vertical-align: top;">
<b>Inclusions:</b><br>
[% PROCESS "global/select-menu.html.tmpl" name="inclusion_to_remove" multiple=1 size=4 options=type.inclusions %]<br>
<input type="submit" name="categoryAction" value="Remove Inclusion">
</td>
<td style="vertical-align: top;">
<b>Exclusions:</b><br>
[% PROCESS "global/select-menu.html.tmpl" name="exclusion_to_remove" multiple=1 size=4 options=type.exclusions %]<br>
<input type="submit" name="categoryAction" value="Remove Exclusion">
</td>
</tr>
</table>
</td>
</tr>
<tr>
<th>Sort Key:</th>
<td>
a number between 1 and 32767 by which this type will be sorted
when displayed to users in a list; ignore if you don't care
what order the types appear in or if you want them to appear
in alphabetical order<br>
<input type="text" name="sortkey" value="[% type.sortkey || 1 %]" size="5" maxlength="5">
</td>
</tr>
<tr>
<th>&nbsp;</th>
<td>
<input type="checkbox" name="is_active" [% "checked" IF type.is_active || !type.is_active.defined %]>
active (flags of this type appear in the UI and can be set)
</td>
</tr>
<tr>
<th>&nbsp;</th>
<td>
<input type="checkbox" name="is_requestable" [% "checked" IF type.is_requestable || !type.is_requestable.defined %]>
requestable (users can ask for flags of this type to be set)
</td>
</tr>
<tr>
<th>CC List:</th>
<td>
if requestable, who should get carbon copied on email notification of requests<br>
<input type="text" name="cc_list" value="[% type.cc_list FILTER html %]" size="80" maxlength="200">
</td>
</tr>
<tr>
<th>&nbsp;</th>
<td>
<input type="checkbox" name="is_requesteeble" [% "checked" IF type.is_requesteeble || !type.is_requesteeble.defined %]>
specifically requestable (users can ask specific other users to set flags of this type as opposed to just asking the wind)
</td>
</tr>
<tr>
<th>&nbsp;</th>
<td>
<input type="checkbox" name="is_multiplicable" [% "checked" IF type.is_multiplicable || !type.is_multiplicable.defined %]>
multiplicable (multiple flags of this type can be set on the same [% type.target_type %])
</td>
</tr>
<tr>
<th></th>
<td>
<input type="submit" value="[% (last_action == "enter" || last_action == "copy") ? "Create" : "Save Changes" %]">
</td>
</tr>
</table>
</form>
[% PROCESS global/footer.html.tmpl %]
[%# 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>
#%]
[% PROCESS global/header.html.tmpl
title = 'Administer Flag Types'
style = "
table#flag_types tr th { text-align: left; }
.inactive { color: #787878; }
"
%]
<p>
Flags are markers that identify whether a bug or attachment has been granted
or denied some status. Flags appear in the UI as a name and a status symbol
("+" for granted, "-" for denied, and "?" for statuses requested by users).
</p>
<p>
For example, you might define a "review" status for users to request review
for their patches. When a patch writer requests review, the string "review?"
will appear in the attachment. When a patch reviewer reviews the patch,
either the string "review+" or the string "review-" will appear in the patch,
depending on whether the patch passed or failed review.
</p>
<h3>Flag Types for Bugs</h3>
[% PROCESS display_flag_types types=bug_types %]
<p>
<a href="editflagtypes.cgi?action=enter&target_type=bug">Create Flag Type for Bugs</a>
</p>
<h3>Flag Types for Attachments</h3>
[% PROCESS display_flag_types types=attachment_types %]
<p>
<a href="editflagtypes.cgi?action=enter&target_type=attachment">Create Flag Type For Attachments</a>
</p>
<script language="JavaScript">
<!--
function confirmDelete(id, name, count)
{
if (count > 0) {
var msg = 'There are ' + count + ' flags of type ' + name + '. ' +
'If you delete this type, those flags will also be ' +
'deleted.\n\nNote: to deactivate the type instead ' +
'of deleting it, edit it and uncheck its "is active" ' +
'flag.\n\nDo you really want to delete this flag type?';
if (!confirm(msg)) return false;
}
location.href = "editflagtypes.cgi?action=delete&id=" + id;
return false; // prevent strict JavaScript warning that this function
// does not always return a value
}
//-->
</script>
[% PROCESS global/footer.html.tmpl %]
[% BLOCK display_flag_types %]
<table id="flag_types" cellspacing="0" cellpadding="4" border="1">
<tr>
<th>Name</th>
<th>Description</th>
<th>Actions</th>
</tr>
[% FOREACH type = types %]
<tr class="[% type.is_active ? "active" : "inactive" %]">
<td>[% type.name FILTER html %]</td>
<td>[% type.description FILTER html %]</td>
<td>
<a href="editflagtypes.cgi?action=edit&id=[% type.id %]">Edit</a>
| <a href="editflagtypes.cgi?action=copy&id=[% type.id %]">Copy</a>
| <a href="editflagtypes.cgi?action=confirmdelete&id=[% type.id %]"
onclick="return confirmDelete([% type.id %], '[% type.name FILTER js %]',
[% type.flag_count %]);">Delete</a>
</td>
</tr>
[% END %]
</table>
[% END %]
......@@ -32,6 +32,8 @@
table.attachment_info th { text-align: right; vertical-align: top; }
table.attachment_info td { text-align: left; vertical-align: top; }
#noview { text-align: left; vertical-align: center; }
table#flags th, table#flags td { font-size: small; vertical-align: baseline; }
"
%]
......@@ -158,8 +160,7 @@
<b>MIME Type:</b><br>
<input type="text" size="20" name="contenttypeentry" value="[% contenttype FILTER html %]"><br>
<b>Flags:</b><br>
<input type="checkbox" id="ispatch" name="ispatch" value="1"
[% 'checked="checked"' IF ispatch %]>
<label for="ispatch">patch</label>
......@@ -168,20 +169,14 @@
<label for="isobsolete">obsolete</label><br>
[% IF (Param("insidergroup") && UserInGroup(Param("insidergroup"))) %]
<input type="checkbox" name="isprivate" value="1"[% " checked" IF isprivate %]> private<br><br>
[% ELSE %]<br>
[% END %]
[% IF statusdefs.size %]
<b>Status:</b><br>
[% FOREACH def = statusdefs %]
<input type="checkbox" id="status-[% def.id %]" name="status"
value="[% def.id %]"
[% 'checked="checked"' IF statuses.${def.id} %]>
<label for="status-[% def.id %]">
[% def.name FILTER html %]
</label><br>
[% END %]
[% IF flag_types.size > 0 %]
<b>Flags:</b><br>
[% PROCESS "flag/list.html.tmpl" bug_id=bugid attach_id=attachid %]<br>
[% END %]
<div id="smallCommentFrame">
<b>Comment (on the bug):</b><br>
<textarea name="comment" rows="5" cols="25" wrap="soft"></textarea><br>
......
......@@ -19,13 +19,18 @@
# Contributor(s): Myk Melez <myk@mozilla.org>
#%]
[%# Whether or not to include flags. %]
[% display_flags = num_attachment_flag_types > 0 %]
<br>
<table cellspacing="0" cellpadding="4" border="1">
<tr>
<th bgcolor="#cccccc" align="left">Attachment</th>
<th bgcolor="#cccccc" align="left">Type</th>
<th bgcolor="#cccccc" align="left">Created</th>
<th bgcolor="#cccccc" align="left">Status</th>
[% IF display_flags %]
<th bgcolor="#cccccc" align="left">Flags</th>
[% END %]
<th bgcolor="#cccccc" align="left">Actions</th>
</tr>
[% canseeprivate = !Param("insidergroup") || UserInGroup(Param("insidergroup")) %]
......@@ -50,16 +55,24 @@
<td valign="top">[% attachment.date %]</td>
<td valign="top">
[% IF attachment.statuses.size == 0 %]
<i>none</i>
[% ELSE %]
[% FOREACH s = attachment.statuses %]
[% s FILTER html FILTER replace('\s', '&nbsp;') %]<br>
[% IF display_flags %]
<td valign="top">
[% IF attachment.flags.size == 0 %]
<i>none</i>
[% ELSE %]
[% FOR flag = attachment.flags %]
[% IF flag.setter %]
[% flag.setter.nick FILTER html %]:
[% END %]
[%+ flag.type.name %][% flag.status %]
[%+ IF flag.status == "?" && flag.requestee %]
([% flag.requestee.nick %])
[% END %]<br>
[% END %]
[% END %]
[% END %]
</td>
</td>
[% END %]
<td valign="top">
[% IF attachment.canedit %]
<a href="attachment.cgi?id=[% attachment.attachid %]&amp;action=edit">Edit</a>
......@@ -72,7 +85,7 @@
[% END %]
<tr>
<td colspan="4">
<td colspan="[% display_flags ? 4 : 3 %]">
<a href="attachment.cgi?bugid=[% bugid %]&amp;action=enter">Create a New Attachment</a> (proposed patch, testcase, etc.)
</td>
<td colspan="1">
......
......@@ -194,7 +194,7 @@
[% END %]
</tr>
[%# *** QAContact URL Summary Whiteboard Keywords *** %]
[%# *** QAContact URL Requests Summary Whiteboard Keywords *** %]
[% IF Param('useqacontact') %]
<tr>
......@@ -218,17 +218,23 @@
[% END %]
</b>
</td>
<td colspan="7">
<td colspan="5">
<input name="bug_file_loc" accesskey="u"
value="[% bug.bug_file_loc FILTER html %]" size="60">
</td>
<td rowspan="4" colspan="2" valign="top">
[% IF flag_types.size > 0 %]
<b>Flags:</b><br>
[% PROCESS "flag/list.html.tmpl" %]
[% END %]
</td>
</tr>
<tr>
<td align="right">
<b><u>S</u>ummary:</b>
</td>
<td colspan="7">
<td colspan="5">
<input name="short_desc" accesskey="s"
value="[% bug.short_desc FILTER html %]" size="60">
</td>
......@@ -239,7 +245,7 @@
<td align="right">
<b>Status <u>W</u>hiteboard:</b>
</td>
<td colspan="7">
<td colspan="5">
<input name="status_whiteboard" accesskey="w"
value="[% bug.status_whiteboard FILTER html %]" size="60">
</td>
......@@ -252,7 +258,7 @@
<b>
<a href="describekeywords.cgi"><u>K</u>eywords</a>:
</b>
<td colspan="7">
<td colspan="5">
<input name="keywords" accesskey="k"
value="[% bug.keywords.join(', ') FILTER html %]" size="60">
</td>
......@@ -263,8 +269,8 @@
[%# *** Attachments *** %]
[% PROCESS attachment/list.html.tmpl
attachments = bug.attachments
bugid = bug.bug_id %]
attachments = bug.attachments
bugid = bug.bug_id %]
[%# *** Dependencies Votes *** %]
......
[%# 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>
#%]
<table id="flags">
[% FOREACH type = flag_types %]
[% FOREACH flag = type.flags %]
<tr>
<td>
[% flag.setter.nick FILTER html %]:
</td>
<td>
[% type.name FILTER html %]
</td>
<td>
<select name="flag-[% flag.id %]">
<option value="X"></option>
<option value="+" [% "selected" IF flag.status == "+" %]>+</option>
<option value="-" [% "selected" IF flag.status == "-" %]>-</option>
<option value="?" [% "selected" IF flag.status == "?" %]>?</option>
</select>
</td>
<td>
[% IF flag.status == "?" && flag.requestee %]([% flag.requestee.nick FILTER html %])[% END %]
</td>
</tr>
[% END %]
[% IF !type.flags || type.flags.size == 0 %]
<tr>
<td>&nbsp;</td>
<td>[% type.name %]</td>
<td>
<select name="flag_type-[% type.id %]">
<option value="X"></option>
<option value="+">+</option>
<option value="-">-</option>
[% IF type.is_requestable %]
<option value="?">?</option>
[% END %]
</select>
</td>
<td>
[% IF type.is_requestable && type.is_requesteeble %]
(<input type="text" name="requestee-[% type.id %]" size="8" maxlength="255">)
[% END %]
</td>
</tr>
[% END %]
[% END %]
[% FOREACH type = flag_types %]
[% NEXT UNLESS type.flags.size > 0 && type.is_multiplicable %]
[% IF !separator_displayed %]
<tr><td colspan="3"><hr></td></tr>
[% separator_displayed = 1 %]
[% END %]
<tr>
<td colspan="2">addl. [% type.name %]</td>
<td>
<select name="flag_type-[% type.id %]">
<option value="X"></option>
<option value="+">+</option>
<option value="-">-</option>
[% IF type.is_requestable %]
<option value="?">?</option>
[% END %]
</select>
</td>
<td>
[% IF type.is_requestable && type.is_requesteeble %]
(<input type="text" name="requestee-[% type.id %]" size="8" maxlength="255">)
[% END %]
</td>
</tr>
[% END %]
</table>
......@@ -40,6 +40,10 @@
to any [% parameters %] which you may have set before calling
ThrowCodeError.
[% ELSIF error == "action_unrecognized" %]
I don't recognize the value (<em>[% variables.action FILTER html %]</em>)
of the <em>action</em> variable.
[% ELSIF error == "attachment_already_obsolete" %]
Attachment #[% attachid FILTER html %] ([% description FILTER html %])
is already obsolete.
......@@ -78,10 +82,40 @@
[% ELSIF error == "no_bug_data" %]
No data when fetching bug [% bug_id %].
[% ELSIF error == "flag_nonexistent" %]
There is no flag with ID #[% variables.id %].
[% ELSIF error == "flag_status_invalid" %]
The flag status <em>[% variables.status FILTER html %]</em> is invalid.
[% ELSIF error == "flag_type_component_nonexistent" %]
The component <em>[% variables.component FILTER html %] does not exist
in the product <em>[% variables.product FILTER html %]</em>.
[% ELSIF error == "flag_type_component_without_product" %]
A component was selected without a product being selected.
[% ELSIF error == "flag_type_id_invalid" %]
The flag type ID <em>[% variables.id FILTER html %]</em> is not
a positive integer.
[% ELSIF error == "flag_type_nonexistent" %]
There is no flag type with the ID <em>[% variables.id %]</em>.
[% ELSIF error == "flag_type_product_nonexistent" %]
The product <em>[% variables.product FILTER html %]</em> does not exist.
[% ELSIF error == "flag_type_target_type_invalid" %]
The target type was neither <em>bug</em> nor <em>attachment</em>
but rather <em>[% variables.target_type FILTER html %]</em>.
[% ELSIF error == "no_y_axis_defined" %]
No Y axis was defined when creating report. The X axis is optional,
but the Y axis is compulsory.
[% ELSIF error == "request_queue_group_invalid" %]
The group field <em>[% group FILTER html %]</em> is invalid.
[% ELSIF error == "template_error" %]
[% template_error_msg %]
......@@ -91,6 +125,14 @@
[% ELSIF error == "unknown_action" %]
Unknown action [% action FILTER html %]!
[% ELSIF error == "unknown_component" %]
[% title = "Unknown Component" %]
There is no component named <em>[% variables.component FILTER html %]</em>.
[% ELSIF error == "unknown_product" %]
[% title = "Unknown Product" %]
There is no product named <em>[% variables.product FILTER html %]</em>.
[% ELSE %]
[%# Give sensible error if error functions are used incorrectly.
#%]
......
......@@ -81,6 +81,34 @@
[% title = "Password Changed" %]
Your password has been changed.
[% ELSIF message_tag == "flag_type_created" %]
[% title = "Flag Type Created" %]
The flag type <em>[% name FILTER html %]</em> has been created.
<a href="editflagtypes.cgi">Back to flag types.</a>
[% ELSIF message_tag == "flag_type_changes_saved" %]
[% title = "Flag Type Changes Saved" %]
<p>
Your changes to the flag type <em>[% name FILTER html %]</em>
have been saved.
<a href="editflagtypes.cgi">Back to flag types.</a>
</p>
[% ELSIF message_tag == "flag_type_deleted" %]
[% title = "Flag Type Deleted" %]
<p>
The flag type <em>[% name FILTER html %]</em> has been deleted.
<a href="editflagtypes.cgi">Back to flag types.</a>
</p>
[% ELSIF message_tag == "flag_type_deactivated" %]
[% title = "Flag Type Deactivated" %]
<p>
The flag type <em>[% flag_type.name FILTER html %]</em>
has been deactivated.
<a href="editflagtypes.cgi">Back to flag types.</a>
</p>
[% ELSIF message_tag == "shutdown" %]
[% title = "Bugzilla is Down" %]
[% Param("shutdownhtml") %]
......
......@@ -22,12 +22,18 @@
[%# INTERFACE:
# name: string; the name of the menu.
#
# multiple: boolean; whether or not the menu is multi-select
#
# size: integer; if multi-select, the number of items to display at once
#
# options: array or hash; the items with which to populate the array.
# If a hash is passed, the hash keys become the names displayed
# to the user while the hash values become the value of the item.
#
# default: string; the item selected in the menu by default.
#
# onchange: code; JavaScript to be run when the user changes the value
# selected in the menu.
#%]
[%# Get the scalar representation of the options reference,
......@@ -37,7 +43,9 @@
#%]
[% options_type = BLOCK %][% options %][% END %]
<select name="[% name FILTER html %]">
<select name="[% name FILTER html %]"
[% IF onchange %]onchange="[% onchange %]"[% END %]
[% IF multiple %] multiple [% IF size %] size="[% size %]" [% END %] [% END %]>
[% IF options_type.search("ARRAY") %]
[% FOREACH value = options %]
<option value="[% value FILTER html %]"
......@@ -45,7 +53,7 @@
[% value FILTER html %]
</option>
[% END %]
[% ELSIF values_type.search("HASH") %]
[% ELSIF options_type.search("HASH") %]
[% FOREACH option = options %]
<option value="[% option.value FILTER html %]"
[% " selected" IF option.value == default %]>
......
......@@ -50,6 +50,8 @@
<input name="id" size="6"> |
<a href="reports.cgi">Reports</a>
| <a href="request.cgi">Requests</a>
[% IF user.login && Param('usevotes') %]
| <a href="votes.cgi?action=show_user">My Votes</a>
......@@ -68,7 +70,7 @@
|| user.canblessany %]
[% ', <a href="editproducts.cgi">products</a>'
IF user.groups.editcomponents %]
[% ', <a href="editattachstatuses.cgi"> attachment&nbsp;statuses</a>'
[% ', <a href="editflagtypes.cgi">flags</a>'
IF user.groups.editcomponents %]
[% ', <a href="editgroups.cgi">groups</a>'
IF user.groups.creategroups %]
......
......@@ -30,7 +30,7 @@
#%]
[% DEFAULT title = "Error" %]
[% error_message = BLOCK %]
[% IF error == "aaa_example_error_tag" %]
[% title = "Example Error" %]
......@@ -75,6 +75,10 @@
Bug aliases cannot be longer than 20 characters.
Please choose a shorter alias.
[% ELSIF error == "authorization_failure" %]
[% title = "Authorization Failed" %]
You are not allowed to [% action %].
[% ELSIF error == "attachment_access_denied" %]
[% title = "Access Denied" %]
You are not permitted access to this attachment.
......@@ -129,6 +133,23 @@
format like JPG or PNG, or put it elsewhere on the web and
link to it from the bug's URL field or in a comment on the bug.
[% ELSIF error == "flag_type_cc_list_invalid" %]
[% title = "Flag Type CC List Invalid" %]
The CC list [% cc_list FILTER html %] must be less than 200 characters long.
[% ELSIF error == "flag_type_description_invalid" %]
[% title = "Flag Type Description Invalid" %]
The description must be less than 32K.
[% ELSIF error == "flag_type_name_invalid" %]
[% title = "Flag Type Name Invalid" %]
The name <em>[% name FILTER html %]</em> must be 1-50 characters long.
[% ELSIF error == "flag_type_sortkey_invalid" %]
[% title = "Flag Type Sort Key Invalid" %]
The sort key must be an integer between 0 and 32767 inclusive.
It cannot be <em>[% variables.sortkey %]</em>.
[% ELSIF error == "illegal_at_least_x_votes" %]
[% title = "Your Query Makes No Sense" %]
The <em>At least ___ votes</em> field must be a simple number.
......@@ -176,10 +197,6 @@
[% title = "Invalid Attachment ID" %]
The attachment id [% attach_id FILTER html %] is invalid.
[% ELSIF error == "invalid_attach_status" %]
[% title = "Invalid Attachment Status" %]
One of the statuses you entered is not a valid status for this attachment.
[% ELSIF error == "invalid_content_type" %]
[% title = "Invalid Content-Type" %]
The content type <em>[% contenttype FILTER html %]</em> is invalid.
......@@ -281,6 +298,18 @@
intentionally cleared out the "Reassign bug to"
field, [% Param("browserbugmessage") %]
[% ELSIF error == "requestee_too_short" %]
[% title = "Requestee Name Too Short" %]
One or two characters match too many users, so please enter at least
three characters of the name/email address of the user you want to set
the flag.
[% ELSIF error == "requestee_too_many_matches" %]
[% title = "Requestee String Matched Too Many Times" %]
The string <em>[% requestee FILTER html %]</em> matched more than
100 users. Enter more of the name to bring the number of matches
down to a reasonable amount.
[% ELSIF error == "unknown_keyword" %]
[% title = "Unknown Keyword" %]
<code>[% keyword FILTER html %]</code> is not a known keyword.
......
[%# 1.0@bugzilla.org %]
[%# 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>
#%]
From: bugzilla-request-daemon
To: [% flag.requestee.email IF flag.requestee.email_prefs.FlagRequestee %]
CC: [% flag.type.cc_list %]
Subject: [% flag.type.name %]: [Bug [% flag.target.bug.id %]] [% flag.target.bug.summary %]
[%- IF flag.target.attachment.exists %] :
[Attachment [% flag.target.attachment.id %]] [% flag.target.attachment.summary %][% END %]
[%+ USE wrap -%]
[%- FILTER bullet = wrap(80) -%]
[% flag.setter.identity %] has asked you for [% flag.type.name %] on bug #
[%- flag.target.bug.id %] ([% flag.target.bug.summary %])
[%- IF flag.target.attachment.exists %], attachment #
[%- flag.target.attachment.id %] ([% flag.target.attachment.summary %])[% END %].
[%+ IF flag.target.type == 'bug' -%]
[% Param('urlbase') %]show_bug.cgi?id=[% flag.target.bug.id %]
[%- ELSIF flag.target.type == 'attachment' -%]
[% Param('urlbase') %]attachment.cgi?id=[% flag.target.attachment.id %]&action=edit
[%- END %]
[%- END %]
[%# 1.0@bugzilla.org %]
[%# 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>
#%]
[% statuses = { '+' => "approved" , '-' => 'denied' , 'X' => "cancelled" } %]
From: bugzilla-request-daemon
To: [% flag.setter.email IF flag.setter.email_prefs.FlagRequester %]
CC: [% flag.type.cc_list %]
Subject: [% flag.type.name %]: [Bug [% flag.target.bug.id %]] [% flag.target.bug.summary %]
[%- IF flag.target.attachment.exists %] :
[Attachment [% flag.target.attachment.id %]] [% flag.target.attachment.summary %][% END %]
[%+ USE wrap -%]
[%- FILTER bullet = wrap(80) -%]
[% user.realname %] <[% user.login %]> has [% statuses.${flag.status} %] your request for [% flag.type.name %] on bug #
[%- flag.target.bug.id %] ([% flag.target.bug.summary %])
[%- IF flag.target.attachment.exists %], attachment #
[%- flag.target.attachment.id %] ([% flag.target.attachment.summary %])[% END %].
[%+ IF flag.target.type == 'bug' -%]
[% Param('urlbase') %]show_bug.cgi?id=[% flag.target.bug.id %]
[%- ELSIF flag.target.type == 'attachment' -%]
[% Param('urlbase') %]attachment.cgi?id=[% flag.target.attachment.id %]&action=edit
[%- END %]
[%- END %]
[%# 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>
#%]
[%# The javascript and header_html blocks get used in header.html.tmpl. %]
[% javascript = BLOCK %]
var usetms = 0; // do we have target milestone?
var first_load = 1; // is this the first time we load the page?
var last_sel = []; // caches last selection
var cpts = new Array();
[% FOREACH p = products %]
cpts['[% p FILTER js %]'] = [
[%- FOREACH item = components_by_product.$p %]'[% item FILTER js %]'[% ", " UNLESS loop.last %] [%- END -%] ];
[% END %]
[% END %]
[% header_html = BLOCK %]
<script language="JavaScript" type="text/javascript" src="productmenu.js"></script>
[% END %]
[% PROCESS global/header.html.tmpl
title="Request Queue"
style = "
table.requests th { text-align: left; }
table#filter th { text-align: right; }
"
%]
[% column_headers = {
"type" => "Flag" ,
"status" => "Status" ,
"bug" => "Bug" ,
"attachment" => "Attachment" ,
"requester" => "Requester" ,
"requestee" => "Requestee" ,
"created" => "Created" ,
"category" => "Product/Component" } %]
[% DEFAULT display_columns = ["requester", "requestee", "type", "bug", "attachment", "created"]
group_field = "Requestee"
group_value = ""
%]
[% IF requests.size == 0 %]
<p>
No requests.
</p>
[% ELSE %]
[% FOREACH request = requests %]
[% PROCESS start_new_table IF request.$group_field != group_value %]
<tr>
[% FOREACH column = display_columns %]
[% NEXT IF column == group_field || excluded_columns.contains(column) %]
<td>[% PROCESS "display_$column" %]</td>
[% END %]
</tr>
[% END %]
</table>
[% END %]
<h3>Filter the Queue</h3>
<form action="request.cgi" method="get">
<input type="hidden" name="action" value="queue">
<table id="filter">
<tr>
<th>Requester:</th>
<td><input type="text" name="requester" value="[% form.requester FILTER html %]" size="20"></td>
<th>Product:</th>
<td>
<select name="product" onChange="selectProduct(this.form, 'product', 'component', 'Any');">
<option value="">Any</option>
[% FOREACH item = products %]
<option value="[% item FILTER html %]"
[% "selected" IF form.product == item %]>[% item FILTER html %]</option>
[% END %]
</select>
</td>
<th>Flag:</th>
<td>
[% PROCESS "global/select-menu.html.tmpl"
name="type"
options=types
default=form.type %]
</td>
[%# We could let people see a "queue" of non-pending requests. %]
<!--
<th>Status:</th>
<td>
[%# PROCESS "global/select-menu.html.tmpl"
name="status"
options=["all", "?", "+-", "+", "-"]
default=form.status %]
</td>
-->
</tr>
<tr>
<th>Requestee:</th>
<td><input type="text" name="requestee" value="[% form.requestee FILTER html %]" size="20"></td>
<th>Component:</th>
<td>
<select name="component">
<option value="">Any</option>
[% FOREACH item = components %]
<option value="[% item FILTER html %]" [% "selected" IF form.component == item %]>
[% item FILTER html %]</option>
[% END %]
</select>
</td>
<th>Group By:</th>
<td>
[% groups = {
"Requester" => 'requester' ,
"Requestee" => 'requestee',
"Flag" => 'type' ,
"Product/Component" => 'category'
} %]
[% PROCESS "global/select-menu.html.tmpl" name="group" options=groups default=form.group %]
</td>
<td><input type="submit" value="Filter"></td>
</tr>
</table>
</form>
[% PROCESS global/footer.html.tmpl %]
[% BLOCK start_new_table %]
[% "</table>" UNLESS group_value == "" %]
<h3>[% column_headers.$group_field %]: [% request.$group_field FILTER html %]</h3>
<table class="requests" cellspacing="0" cellpadding="4" border="1">
<tr>
[% FOREACH column = display_columns %]
[% NEXT IF column == group_field || excluded_columns.contains(column) %]
<th>[% column_headers.$column %]</th>
[% END %]
</tr>
[% group_value = request.$group_field %]
[% END %]
[% BLOCK display_type %]
[% request.type FILTER html %]
[% END %]
[% BLOCK display_status %]
[% request.status %]
[% END %]
[% BLOCK display_bug %]
<a href="show_bug.cgi?id=[% request.bug_id %]">
[% request.bug_id %]: [%+ request.bug_summary FILTER html %]</a>
[% END %]
[% BLOCK display_attachment %]
[% IF request.attach_id %]
<a href="attachment.cgi?id=[% request.attach_id %]&action=edit">
[% request.attach_id %]: [%+ request.attach_summary FILTER html %]</a>
[% ELSE %]
N/A
[% END %]
[% END %]
[% BLOCK display_requestee %]
[% request.requestee FILTER html %]
[% END %]
[% BLOCK display_requester %]
[% request.requester FILTER html %]
[% END %]
[% BLOCK display_created %]
[% request.created FILTER html %]
[% END %]
<!-- 1.0@bugzilla.org -->
[%# 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>
#%]
[%# INTERFACE:
# form, mform: hashes; the form values submitted to the script, used by
# hidden-fields to generate hidden form fields replicating
# the original form
# flags: array; the flags the user made, including information about
# potential requestees for those flags (based on
# the string the user typed into the requestee fields)
# target: record; the bug/attachment for which the flags are being made
#%]
[% UNLESS header_done %]
[% title = BLOCK %]
Verify Requests for Bug #[% target.bug.id %]
[% IF target.attachment %], Attachment #[% target.attachment.id %][% END %]
[% END %]
[% h1 = BLOCK %]
Verify Requests for <a href="show_bug.cgi?id=[% target.bug.id %]">Bug #[% target.bug.id %]</a>
[% IF target.attachment.exists %],
<a href="attachment.cgi?id=[% target.attachment.id %]&action=edit">Attachment #[% target.attachment.id %]</a>
[% END %]
[% END %]
[% h2 = BLOCK %]
[% target.bug.summary FILTER html %]
[% IF target.attachment.exists %]
: [% target.attachment.summary FILTER html %]
[% END %]
[% END %]
[% PROCESS global/header.html.tmpl %]
[% END %]
<form method="post">
[% PROCESS "global/hidden-fields.html.tmpl"
exclude=("^(flag_type|requestee)-") %]
[% FOREACH flag = flags %]
[% IF flag.requestees.size == 0 %]
<p>
Sorry, I can't find a user whose name or email address contains
the string <em>[% flag.requestee_str FILTER html %]</em>.
Double-check that the user's name or email address contains that
string, or try entering a shorter string.
</p>
<p>
Ask <input type="text" size="20" maxlength="255"
name="requestee-[% flag.type.id %]"
value="[% flag.requestee_str FILTER html %]">
for [% flag.type.name FILTER html %]
<input type="hidden" name="flag_type-[% flag.type.id %]" value="?">
</p>
[% ELSIF flag.requestees.size == 1 %]
<input type="hidden"
name="requestee-[% flag.type.id %]"
value="[% flag.requestee.email FILTER html %]">
<input type="hidden" name="flag_type-[% flag.type.id %]" value="?">
[% ELSE %]
<p>
More than one user's name or email address contains the string
<em>[% flag.requestee_str FILTER html %]</em>. Choose the user
you meant from the following menu or click the back button and try
again with a more specific string.
</p>
<p>
Ask <select name="requestee-[% flag.type.id %]">
[% FOREACH requestee = flag.requestees %]
<option value="[% requestee.email FILTER html %]">
[% requestee.identity FILTER html%]</option>
[% END %]
</select>
for [% flag.type.name %]
<input type="hidden" name="flag_type-[% flag.type.id %]" value="?">
</p>
[% END %]
[% END %]
<input type="submit" value="Commit">
</form>
[% PROCESS global/footer.html.tmpl %]
......@@ -207,6 +207,11 @@ sub DoEmail {
$vars->{'excludeself'} = 0;
}
foreach my $flag qw(FlagRequestee FlagRequester) {
$vars->{$flag} =
!exists($emailflags{$flag}) || $emailflags{$flag} eq 'on';
}
# Parse the info into a hash of hashes; the first hash keyed by role,
# the second by reason, and the value being 1 or 0 for (on or off).
# Preferences not existing in the user's list are assumed to be on.
......@@ -234,6 +239,10 @@ sub SaveEmail {
$updateString .= 'ExcludeSelf~';
}
foreach my $flag qw(FlagRequestee FlagRequester) {
$updateString .= "~$flag~" . (defined($::FORM{$flag}) ? "on" : "");
}
foreach my $role (@roles) {
foreach my $reason (@reasons) {
# Add this preference to the list without giving it a value,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment