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; ...@@ -31,10 +31,32 @@ package Attachment;
# This module requires that its caller have said "require CGI.pl" to import # This module requires that its caller have said "require CGI.pl" to import
# relevant functions from that script and its companion globals.pl. # relevant functions from that script and its companion globals.pl.
# Use the Flag module to handle flags.
use Bugzilla::Flag;
############################################################################ ############################################################################
# Functions # 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 sub query
{ {
# Retrieves and returns an array of attachment records for a given bug. # Retrieves and returns an array of attachment records for a given bug.
...@@ -65,22 +87,8 @@ sub query ...@@ -65,22 +87,8 @@ sub query
$a{'date'} = "$1-$2-$3 $4:$5"; $a{'date'} = "$1-$2-$3 $4:$5";
} }
# Retrieve a list of status flags that have been set on the attachment. # Retrieve a list of flags for this attachment.
&::PushGlobalSQLState(); $a{'flags'} = Bugzilla::Flag::match({ 'attach_id' => $a{'attachid'} });
&::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();
# We will display the edit link if the user can edit the attachment; # We will display the edit link if the user can edit the attachment;
# ie the are the submitter, or they have canedit. # ie the are the submitter, or they have canedit.
......
...@@ -31,10 +31,32 @@ package Attachment; ...@@ -31,10 +31,32 @@ package Attachment;
# This module requires that its caller have said "require CGI.pl" to import # This module requires that its caller have said "require CGI.pl" to import
# relevant functions from that script and its companion globals.pl. # relevant functions from that script and its companion globals.pl.
# Use the Flag module to handle flags.
use Bugzilla::Flag;
############################################################################ ############################################################################
# Functions # 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 sub query
{ {
# Retrieves and returns an array of attachment records for a given bug. # Retrieves and returns an array of attachment records for a given bug.
...@@ -65,22 +87,8 @@ sub query ...@@ -65,22 +87,8 @@ sub query
$a{'date'} = "$1-$2-$3 $4:$5"; $a{'date'} = "$1-$2-$3 $4:$5";
} }
# Retrieve a list of status flags that have been set on the attachment. # Retrieve a list of flags for this attachment.
&::PushGlobalSQLState(); $a{'flags'} = Bugzilla::Flag::match({ 'attach_id' => $a{'attachid'} });
&::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();
# We will display the edit link if the user can edit the attachment; # We will display the edit link if the user can edit the attachment;
# ie the are the submitter, or they have canedit. # ie the are the submitter, or they have canedit.
......
...@@ -62,6 +62,7 @@ sub init { ...@@ -62,6 +62,7 @@ sub init {
my @fields; my @fields;
my @supptables; my @supptables;
my @wherepart; my @wherepart;
my @having = ("(cntuseringroups = cntbugingroups OR canseeanyway)");
@fields = @$fieldsref if $fieldsref; @fields = @$fieldsref if $fieldsref;
my %F; my %F;
my %M; my %M;
...@@ -265,8 +266,8 @@ sub init { ...@@ -265,8 +266,8 @@ sub init {
} }
my $chartid; my $chartid;
# $statusid is used by the code that queries for attachment statuses. # $type_id is used by the code that queries for attachment flags.
my $statusid = 0; my $type_id = 0;
my $f; my $f;
my $ff; my $ff;
my $t; my $t;
...@@ -358,69 +359,61 @@ sub init { ...@@ -358,69 +359,61 @@ sub init {
} }
$f = "$table.$field"; $f = "$table.$field";
}, },
"^attachstatusdefs.name," => sub { "^flagtypes.name," => sub {
# The below has Fun with the names for attachment statuses. This # Matches bugs by flag name/status.
# isn't needed for changed* queries, so exclude those - the # Note that--for the purposes of querying--a flag comprises
# generic stuff will cope # its name plus its status (i.e. a flag named "review"
return if ($t =~ m/^changed/); # with a status of "+" can be found by searching for "review+").
# 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"}; # Don't do anything if this condition is about changes to flags,
&$ref; # as the generic change condition processors can handle those.
&::SendSQL("SELECT id FROM attachstatusdefs WHERE $term"); return if ($t =~ m/^changed/);
my @as_ids; # Add the flags and flagtypes tables to the query. We do
while (&::MoreSQLData()) { # a left join here so bugs without any flags still match
push @as_ids, &::FetchOneColumn(); # negative conditions (f.e. "flag isn't review+").
} my $flags = "flags_$chartid";
push(@supptables, "LEFT JOIN flags $flags " .
# When searching for multiple statuses within a single boolean chart, "ON bugs.bug_id = $flags.bug_id");
# we want to match each status record separately. In other words, my $flagtypes = "flagtypes_$chartid";
# "status = 'foo' AND status = 'bar'" should match attachments with push(@supptables, "LEFT JOIN flagtypes $flagtypes " .
# one status record equal to "foo" and another one equal to "bar", "ON $flags.type_id = $flagtypes.id");
# not attachments where the same status record equals both "foo" and
# "bar" (which is nonsensical). In order to do this we must add an # Generate the condition by running the operator-specific function.
# additional counter to the end of the "attachstatuses" table # Afterwards the condition resides in the global $term variable.
# reference. $ff = "CONCAT($flagtypes.name, $flags.status)";
++$statusid; &{$funcsbykey{",$t"}};
my $attachtable = "attachments_$chartid"; # If this is a negative condition (f.e. flag isn't "review+"),
my $statustable = "attachstatuses_${chartid}_$statusid"; # we only want bugs where all flags match the condition, not
# those where any flag matches, which needs special magic.
push(@supptables, "attachments $attachtable"); # Instead of adding the condition to the WHERE clause, we select
my $join = "LEFT JOIN attachstatuses $statustable ON ". # the number of flags matching the condition and the total number
"($attachtable.attach_id = $statustable.attach_id AND " . # of flags on each bug, then compare them in a HAVING clause.
"$statustable.statusid IN (" . join(",", @as_ids) . "))"; # If the numbers are the same, all flags match the condition,
push(@supptables, $join); # so this bug should be included.
push(@wherepart, "bugs.bug_id = $attachtable.bug_id"); if ($t =~ m/not/) {
if ($inverted) { push(@fields, "SUM($ff IS NOT NULL) AS allflags_$chartid");
$term = "$statustable.statusid IS NULL"; push(@fields, "SUM($term) AS matchingflags_$chartid");
} else { push(@having, "allflags_$chartid = matchingflags_$chartid");
$term = "$statustable.statusid IS NOT NULL"; $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 { "^changedin," => sub {
$f = "(to_days(now()) - to_days(bugs.delta_ts))"; $f = "(to_days(now()) - to_days(bugs.delta_ts))";
}, },
...@@ -817,8 +810,7 @@ sub init { ...@@ -817,8 +810,7 @@ sub init {
# Make sure we create a legal SQL query. # Make sure we create a legal SQL query.
@andlist = ("1 = 1") if !@andlist; @andlist = ("1 = 1") if !@andlist;
my $query = ("SELECT DISTINCT " . my $query = ("SELECT " . join(', ', @fields) .
join(', ', @fields) .
", COUNT(DISTINCT ugmap.group_id) AS cntuseringroups, " . ", COUNT(DISTINCT ugmap.group_id) AS cntuseringroups, " .
" COUNT(DISTINCT bgmap.group_id) AS cntbugingroups, " . " COUNT(DISTINCT bgmap.group_id) AS cntbugingroups, " .
" ((COUNT(DISTINCT ccmap.who) AND cclist_accessible) " . " ((COUNT(DISTINCT ccmap.who) AND cclist_accessible) " .
...@@ -834,10 +826,8 @@ sub init { ...@@ -834,10 +826,8 @@ sub init {
" LEFT JOIN cc AS ccmap " . " LEFT JOIN cc AS ccmap " .
" ON ccmap.who = $::userid AND ccmap.bug_id = bugs.bug_id " . " ON ccmap.who = $::userid AND ccmap.bug_id = bugs.bug_id " .
" WHERE " . join(' AND ', (@wherepart, @andlist)) . " WHERE " . join(' AND ', (@wherepart, @andlist)) .
" GROUP BY bugs.bug_id " . " GROUP BY bugs.bug_id" .
" HAVING cntuseringroups = cntbugingroups" . " HAVING " . join(" AND ", @having));
" OR canseeanyway"
);
if ($debug) { if ($debug) {
print "<p><code>" . value_quote($query) . "</code></p>\n"; print "<p><code>" . value_quote($query) . "</code></p>\n";
......
# -*- 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; ...@@ -28,6 +28,10 @@ use RelationSet;
# Use the Attachment module to display attachments for the bug. # Use the Attachment module to display attachments for the bug.
use Attachment; use Attachment;
# Use the Flag modules to display flags on the bug.
use Bugzilla::Flag;
use Bugzilla::FlagType;
sub show_bug { sub show_bug {
# Shut up misguided -w warnings about "used only once". For some reason, # Shut up misguided -w warnings about "used only once". For some reason,
# "use vars" chokes on me when I try it here. # "use vars" chokes on me when I try it here.
...@@ -76,10 +80,10 @@ sub show_bug { ...@@ -76,10 +80,10 @@ sub show_bug {
# Populate the bug hash with the info we get directly from the DB. # Populate the bug hash with the info we get directly from the DB.
my $query = " my $query = "
SELECT bugs.bug_id, alias, products.name, version, rep_platform, SELECT bugs.bug_id, alias, bugs.product_id, products.name, version,
op_sys, bug_status, resolution, priority, rep_platform, op_sys, bug_status, resolution, priority,
bug_severity, components.name, assigned_to, reporter, bug_severity, bugs.component_id, components.name, assigned_to,
bug_file_loc, short_desc, target_milestone, reporter, bug_file_loc, short_desc, target_milestone,
qa_contact, status_whiteboard, qa_contact, status_whiteboard,
date_format(creation_ts,'%Y-%m-%d %H:%i'), date_format(creation_ts,'%Y-%m-%d %H:%i'),
delta_ts, sum(votes.count), delta_ts calc_disp_date delta_ts, sum(votes.count), delta_ts calc_disp_date
...@@ -101,12 +105,12 @@ sub show_bug { ...@@ -101,12 +105,12 @@ sub show_bug {
my $value; my $value;
my $disp_date; my $disp_date;
my @row = FetchSQLData(); my @row = FetchSQLData();
foreach my $field ("bug_id", "alias", "product", "version", "rep_platform", foreach my $field ("bug_id", "alias", "product_id", "product", "version",
"op_sys", "bug_status", "resolution", "priority", "rep_platform", "op_sys", "bug_status", "resolution",
"bug_severity", "component", "assigned_to", "reporter", "priority", "bug_severity", "component_id", "component",
"bug_file_loc", "short_desc", "target_milestone", "assigned_to", "reporter", "bug_file_loc", "short_desc",
"qa_contact", "status_whiteboard", "creation_ts", "target_milestone", "qa_contact", "status_whiteboard",
"delta_ts", "votes", "calc_disp_date") "creation_ts", "delta_ts", "votes", "calc_disp_date")
{ {
$value = shift(@row); $value = shift(@row);
if ($field eq "calc_disp_date") { if ($field eq "calc_disp_date") {
...@@ -198,6 +202,28 @@ sub show_bug { ...@@ -198,6 +202,28 @@ sub show_bug {
# Attachments # Attachments
$bug{'attachments'} = Attachment::query($id); $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 # Dependencies
my @list; my @list;
SendSQL("SELECT dependson FROM dependencies WHERE SendSQL("SELECT dependson FROM dependencies WHERE
......
...@@ -581,7 +581,9 @@ if ($action eq 'delete') { ...@@ -581,7 +581,9 @@ if ($action eq 'delete') {
bugs WRITE, bugs WRITE,
bugs_activity WRITE, bugs_activity WRITE,
components 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, # 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 # so I have to iterate over bugs and delete all the indivial entries
...@@ -610,6 +612,12 @@ if ($action eq 'delete') { ...@@ -610,6 +612,12 @@ if ($action eq 'delete') {
print "Bugs deleted.<BR>\n"; 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 SendSQL("DELETE FROM components
WHERE id=$component_id"); WHERE id=$component_id");
print "Components deleted.<P>\n"; print "Components deleted.<P>\n";
......
...@@ -539,7 +539,9 @@ if ($action eq 'delete') { ...@@ -539,7 +539,9 @@ if ($action eq 'delete') {
products WRITE, products WRITE,
groups WRITE, groups WRITE,
profiles 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, # 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 # so I have to iterate over bugs and delete all the indivial entries
...@@ -581,6 +583,12 @@ if ($action eq 'delete') { ...@@ -581,6 +583,12 @@ if ($action eq 'delete') {
WHERE product_id=$product_id"); WHERE product_id=$product_id");
print "Milestones deleted.<BR>\n"; 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 SendSQL("DELETE FROM products
WHERE id=$product_id"); WHERE id=$product_id");
print "Product '$product' deleted.<BR>\n"; print "Product '$product' deleted.<BR>\n";
......
...@@ -300,7 +300,12 @@ sub FetchOneColumn { ...@@ -300,7 +300,12 @@ sub FetchOneColumn {
"status", "resolution", "summary"); "status", "resolution", "summary");
sub AppendComment { 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/\n/g; # Get rid of windows-style line endings.
$comment =~ s/\r/\n/g; # Get rid of mac-style line endings. $comment =~ s/\r/\n/g; # Get rid of mac-style line endings.
if ($comment =~ /^\s*$/) { # Nothin' but whitespace. if ($comment =~ /^\s*$/) { # Nothin' but whitespace.
...@@ -310,7 +315,7 @@ sub AppendComment { ...@@ -310,7 +315,7 @@ sub AppendComment {
my $whoid = DBNameToIdAndCheck($who); my $whoid = DBNameToIdAndCheck($who);
my $privacyval = $isprivate ? 1 : 0 ; my $privacyval = $isprivate ? 1 : 0 ;
SendSQL("INSERT INTO longdescs (bug_id, who, bug_when, thetext, isprivate) " . SendSQL("INSERT INTO longdescs (bug_id, who, bug_when, thetext, isprivate) " .
"VALUES($bugid, $whoid, now(), " . SqlQuote($comment) . ", " . "VALUES($bugid, $whoid, $timestamp, " . SqlQuote($comment) . ", " .
$privacyval . ")"); $privacyval . ")");
SendSQL("UPDATE bugs SET delta_ts = now() WHERE bug_id = $bugid"); SendSQL("UPDATE bugs SET delta_ts = now() WHERE bug_id = $bugid");
...@@ -902,8 +907,7 @@ sub get_product_name { ...@@ -902,8 +907,7 @@ sub get_product_name {
sub get_component_id { sub get_component_id {
my ($prod_id, $comp) = @_; my ($prod_id, $comp) = @_;
die "non-numeric prod_id '$prod_id' passed to get_component_id" return undef unless ($prod_id =~ /^\d+$/);
unless ($prod_id =~ /^\d+$/);
PushGlobalSQLState(); PushGlobalSQLState();
SendSQL("SELECT id FROM components " . SendSQL("SELECT id FROM components " .
"WHERE product_id = $prod_id AND name = " . SqlQuote($comp)); "WHERE product_id = $prod_id AND name = " . SqlQuote($comp));
......
...@@ -36,6 +36,9 @@ require "bug_form.pl"; ...@@ -36,6 +36,9 @@ require "bug_form.pl";
use RelationSet; 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": # Shut up misguided -w warnings about "used only once":
use vars qw(%versions use vars qw(%versions
...@@ -1052,8 +1055,9 @@ foreach my $id (@idlist) { ...@@ -1052,8 +1055,9 @@ foreach my $id (@idlist) {
"profiles $write, dependencies $write, votes $write, " . "profiles $write, dependencies $write, votes $write, " .
"products READ, components READ, " . "products READ, components READ, " .
"keywords $write, longdescs $write, fielddefs $write, " . "keywords $write, longdescs $write, fielddefs $write, " .
"bug_group_map $write, " . "bug_group_map $write, flags $write, " .
"user_group_map READ, " . "user_group_map READ, flagtypes READ, " .
"flaginclusions AS i READ, flagexclusions AS e READ, " .
"keyworddefs READ, groups READ, attachments READ"); "keyworddefs READ, groups READ, attachments READ");
my @oldvalues = SnapShotBug($id); my @oldvalues = SnapShotBug($id);
my %oldhash; my %oldhash;
...@@ -1238,7 +1242,7 @@ foreach my $id (@idlist) { ...@@ -1238,7 +1242,7 @@ foreach my $id (@idlist) {
LogActivityEntry($id, "bug_group", $groupDelNames, $groupAddNames); LogActivityEntry($id, "bug_group", $groupDelNames, $groupAddNames);
if (defined $::FORM{'comment'}) { if (defined $::FORM{'comment'}) {
AppendComment($id, $::COOKIE{'Bugzilla_login'}, $::FORM{'comment'}, AppendComment($id, $::COOKIE{'Bugzilla_login'}, $::FORM{'comment'},
$::FORM{'commentprivacy'}); $::FORM{'commentprivacy'}, $timestamp);
} }
my $removedCcString = ""; my $removedCcString = "";
...@@ -1399,6 +1403,14 @@ foreach my $id (@idlist) { ...@@ -1399,6 +1403,14 @@ foreach my $id (@idlist) {
# what has changed since before we wrote out the new values. # what has changed since before we wrote out the new values.
# #
my @newvalues = SnapShotBug($id); 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 # for passing to processmail to ensure that when someone is removed
# from one of these fields, they get notified of that fact (if desired) # from one of these fields, they get notified of that fact (if desired)
...@@ -1411,12 +1423,6 @@ foreach my $id (@idlist) { ...@@ -1411,12 +1423,6 @@ foreach my $id (@idlist) {
# values in place. # values in place.
my $old = shift @oldvalues; my $old = shift @oldvalues;
my $new = shift @newvalues; my $new = shift @newvalues;
if (!defined $old) {
$old = "";
}
if (!defined $new) {
$new = "";
}
if ($old ne $new) { if ($old ne $new) {
# Products and components are now stored in the DB using ID's # Products and components are now stored in the DB using ID's
...@@ -1461,6 +1467,11 @@ foreach my $id (@idlist) { ...@@ -1461,6 +1467,11 @@ foreach my $id (@idlist) {
LogActivityEntry($id,$col,$old,$new); 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) { if ($bug_changed) {
SendSQL("UPDATE bugs SET delta_ts = " . SqlQuote($timestamp) . " WHERE bug_id = $id"); 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", ...@@ -232,11 +232,11 @@ CrossCheck("fielddefs", "fieldid",
["bugs_activity", "fieldid"]); ["bugs_activity", "fieldid"]);
CrossCheck("attachments", "attach_id", CrossCheck("attachments", "attach_id",
["attachstatuses", "attach_id"], ["flags", "attach_id"],
["bugs_activity", "attach_id"]); ["bugs_activity", "attach_id"]);
CrossCheck("attachstatusdefs", "id", CrossCheck("flagtypes", "id",
["attachstatuses", "statusid"]); ["flags", "type_id"]);
CrossCheck("bugs", "bug_id", CrossCheck("bugs", "bug_id",
["bugs_activity", "bug_id"], ["bugs_activity", "bug_id"],
...@@ -280,7 +280,7 @@ CrossCheck("products", "id", ...@@ -280,7 +280,7 @@ CrossCheck("products", "id",
["components", "product_id", "name"], ["components", "product_id", "name"],
["milestones", "product_id", "value"], ["milestones", "product_id", "value"],
["versions", "product_id", "value"], ["versions", "product_id", "value"],
["attachstatusdefs", "product_id", "name"]); ["flagtypes", "product_id", "name"]);
DateCheck("groups", "last_changed"); DateCheck("groups", "last_changed");
DateCheck("profiles", "refreshed_when"); DateCheck("profiles", "refreshed_when");
......
...@@ -83,9 +83,27 @@ ...@@ -83,9 +83,27 @@
<tr> <tr>
<td width="150"></td> <td width="150"></td>
<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" <input type="checkbox" name="ExcludeSelf" id="ExcludeSelf" value="on"
[% " checked" IF excludeself %]> [% " 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> <br>
</td> </td>
</tr> </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 @@ ...@@ -32,6 +32,8 @@
table.attachment_info th { text-align: right; vertical-align: top; } table.attachment_info th { text-align: right; vertical-align: top; }
table.attachment_info td { text-align: left; vertical-align: top; } table.attachment_info td { text-align: left; vertical-align: top; }
#noview { text-align: left; vertical-align: center; } #noview { text-align: left; vertical-align: center; }
table#flags th, table#flags td { font-size: small; vertical-align: baseline; }
" "
%] %]
...@@ -159,7 +161,6 @@ ...@@ -159,7 +161,6 @@
<b>MIME Type:</b><br> <b>MIME Type:</b><br>
<input type="text" size="20" name="contenttypeentry" value="[% contenttype FILTER html %]"><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" <input type="checkbox" id="ispatch" name="ispatch" value="1"
[% 'checked="checked"' IF ispatch %]> [% 'checked="checked"' IF ispatch %]>
<label for="ispatch">patch</label> <label for="ispatch">patch</label>
...@@ -168,18 +169,12 @@ ...@@ -168,18 +169,12 @@
<label for="isobsolete">obsolete</label><br> <label for="isobsolete">obsolete</label><br>
[% IF (Param("insidergroup") && UserInGroup(Param("insidergroup"))) %] [% IF (Param("insidergroup") && UserInGroup(Param("insidergroup"))) %]
<input type="checkbox" name="isprivate" value="1"[% " checked" IF isprivate %]> private<br><br> <input type="checkbox" name="isprivate" value="1"[% " checked" IF isprivate %]> private<br><br>
[% ELSE %]<br>
[% END %] [% END %]
[% IF statusdefs.size %] [% IF flag_types.size > 0 %]
<b>Status:</b><br> <b>Flags:</b><br>
[% FOREACH def = statusdefs %] [% PROCESS "flag/list.html.tmpl" bug_id=bugid attach_id=attachid %]<br>
<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 %]
[% END %] [% END %]
<div id="smallCommentFrame"> <div id="smallCommentFrame">
......
...@@ -19,13 +19,18 @@ ...@@ -19,13 +19,18 @@
# Contributor(s): Myk Melez <myk@mozilla.org> # Contributor(s): Myk Melez <myk@mozilla.org>
#%] #%]
[%# Whether or not to include flags. %]
[% display_flags = num_attachment_flag_types > 0 %]
<br> <br>
<table cellspacing="0" cellpadding="4" border="1"> <table cellspacing="0" cellpadding="4" border="1">
<tr> <tr>
<th bgcolor="#cccccc" align="left">Attachment</th> <th bgcolor="#cccccc" align="left">Attachment</th>
<th bgcolor="#cccccc" align="left">Type</th> <th bgcolor="#cccccc" align="left">Type</th>
<th bgcolor="#cccccc" align="left">Created</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> <th bgcolor="#cccccc" align="left">Actions</th>
</tr> </tr>
[% canseeprivate = !Param("insidergroup") || UserInGroup(Param("insidergroup")) %] [% canseeprivate = !Param("insidergroup") || UserInGroup(Param("insidergroup")) %]
...@@ -50,15 +55,23 @@ ...@@ -50,15 +55,23 @@
<td valign="top">[% attachment.date %]</td> <td valign="top">[% attachment.date %]</td>
[% IF display_flags %]
<td valign="top"> <td valign="top">
[% IF attachment.statuses.size == 0 %] [% IF attachment.flags.size == 0 %]
<i>none</i> <i>none</i>
[% ELSE %] [% ELSE %]
[% FOREACH s = attachment.statuses %] [% FOR flag = attachment.flags %]
[% s FILTER html FILTER replace('\s', '&nbsp;') %]<br> [% 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 %] [% END %]
</td> </td>
[% END %]
<td valign="top"> <td valign="top">
[% IF attachment.canedit %] [% IF attachment.canedit %]
...@@ -72,7 +85,7 @@ ...@@ -72,7 +85,7 @@
[% END %] [% END %]
<tr> <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.) <a href="attachment.cgi?bugid=[% bugid %]&amp;action=enter">Create a New Attachment</a> (proposed patch, testcase, etc.)
</td> </td>
<td colspan="1"> <td colspan="1">
......
...@@ -194,7 +194,7 @@ ...@@ -194,7 +194,7 @@
[% END %] [% END %]
</tr> </tr>
[%# *** QAContact URL Summary Whiteboard Keywords *** %] [%# *** QAContact URL Requests Summary Whiteboard Keywords *** %]
[% IF Param('useqacontact') %] [% IF Param('useqacontact') %]
<tr> <tr>
...@@ -218,17 +218,23 @@ ...@@ -218,17 +218,23 @@
[% END %] [% END %]
</b> </b>
</td> </td>
<td colspan="7"> <td colspan="5">
<input name="bug_file_loc" accesskey="u" <input name="bug_file_loc" accesskey="u"
value="[% bug.bug_file_loc FILTER html %]" size="60"> value="[% bug.bug_file_loc FILTER html %]" size="60">
</td> </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>
<tr> <tr>
<td align="right"> <td align="right">
<b><u>S</u>ummary:</b> <b><u>S</u>ummary:</b>
</td> </td>
<td colspan="7"> <td colspan="5">
<input name="short_desc" accesskey="s" <input name="short_desc" accesskey="s"
value="[% bug.short_desc FILTER html %]" size="60"> value="[% bug.short_desc FILTER html %]" size="60">
</td> </td>
...@@ -239,7 +245,7 @@ ...@@ -239,7 +245,7 @@
<td align="right"> <td align="right">
<b>Status <u>W</u>hiteboard:</b> <b>Status <u>W</u>hiteboard:</b>
</td> </td>
<td colspan="7"> <td colspan="5">
<input name="status_whiteboard" accesskey="w" <input name="status_whiteboard" accesskey="w"
value="[% bug.status_whiteboard FILTER html %]" size="60"> value="[% bug.status_whiteboard FILTER html %]" size="60">
</td> </td>
...@@ -252,7 +258,7 @@ ...@@ -252,7 +258,7 @@
<b> <b>
<a href="describekeywords.cgi"><u>K</u>eywords</a>: <a href="describekeywords.cgi"><u>K</u>eywords</a>:
</b> </b>
<td colspan="7"> <td colspan="5">
<input name="keywords" accesskey="k" <input name="keywords" accesskey="k"
value="[% bug.keywords.join(', ') FILTER html %]" size="60"> value="[% bug.keywords.join(', ') FILTER html %]" size="60">
</td> </td>
......
[%# 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 @@ ...@@ -40,6 +40,10 @@
to any [% parameters %] which you may have set before calling to any [% parameters %] which you may have set before calling
ThrowCodeError. 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" %] [% ELSIF error == "attachment_already_obsolete" %]
Attachment #[% attachid FILTER html %] ([% description FILTER html %]) Attachment #[% attachid FILTER html %] ([% description FILTER html %])
is already obsolete. is already obsolete.
...@@ -78,10 +82,40 @@ ...@@ -78,10 +82,40 @@
[% ELSIF error == "no_bug_data" %] [% ELSIF error == "no_bug_data" %]
No data when fetching bug [% bug_id %]. 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" %] [% ELSIF error == "no_y_axis_defined" %]
No Y axis was defined when creating report. The X axis is optional, No Y axis was defined when creating report. The X axis is optional,
but the Y axis is compulsory. 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" %] [% ELSIF error == "template_error" %]
[% template_error_msg %] [% template_error_msg %]
...@@ -91,6 +125,14 @@ ...@@ -91,6 +125,14 @@
[% ELSIF error == "unknown_action" %] [% ELSIF error == "unknown_action" %]
Unknown action [% action FILTER html %]! 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 %] [% ELSE %]
[%# Give sensible error if error functions are used incorrectly. [%# Give sensible error if error functions are used incorrectly.
#%] #%]
......
...@@ -81,6 +81,34 @@ ...@@ -81,6 +81,34 @@
[% title = "Password Changed" %] [% title = "Password Changed" %]
Your password has been 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" %] [% ELSIF message_tag == "shutdown" %]
[% title = "Bugzilla is Down" %] [% title = "Bugzilla is Down" %]
[% Param("shutdownhtml") %] [% Param("shutdownhtml") %]
......
...@@ -22,12 +22,18 @@ ...@@ -22,12 +22,18 @@
[%# INTERFACE: [%# INTERFACE:
# name: string; the name of the menu. # 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. # options: array or hash; the items with which to populate the array.
# If a hash is passed, the hash keys become the names displayed # 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. # to the user while the hash values become the value of the item.
# #
# default: string; the item selected in the menu by default. # 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, [%# Get the scalar representation of the options reference,
...@@ -37,7 +43,9 @@ ...@@ -37,7 +43,9 @@
#%] #%]
[% options_type = BLOCK %][% options %][% END %] [% 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") %] [% IF options_type.search("ARRAY") %]
[% FOREACH value = options %] [% FOREACH value = options %]
<option value="[% value FILTER html %]" <option value="[% value FILTER html %]"
...@@ -45,7 +53,7 @@ ...@@ -45,7 +53,7 @@
[% value FILTER html %] [% value FILTER html %]
</option> </option>
[% END %] [% END %]
[% ELSIF values_type.search("HASH") %] [% ELSIF options_type.search("HASH") %]
[% FOREACH option = options %] [% FOREACH option = options %]
<option value="[% option.value FILTER html %]" <option value="[% option.value FILTER html %]"
[% " selected" IF option.value == default %]> [% " selected" IF option.value == default %]>
......
...@@ -51,6 +51,8 @@ ...@@ -51,6 +51,8 @@
<a href="reports.cgi">Reports</a> <a href="reports.cgi">Reports</a>
| <a href="request.cgi">Requests</a>
[% IF user.login && Param('usevotes') %] [% IF user.login && Param('usevotes') %]
| <a href="votes.cgi?action=show_user">My Votes</a> | <a href="votes.cgi?action=show_user">My Votes</a>
[% END %] [% END %]
...@@ -68,7 +70,7 @@ ...@@ -68,7 +70,7 @@
|| user.canblessany %] || user.canblessany %]
[% ', <a href="editproducts.cgi">products</a>' [% ', <a href="editproducts.cgi">products</a>'
IF user.groups.editcomponents %] IF user.groups.editcomponents %]
[% ', <a href="editattachstatuses.cgi"> attachment&nbsp;statuses</a>' [% ', <a href="editflagtypes.cgi">flags</a>'
IF user.groups.editcomponents %] IF user.groups.editcomponents %]
[% ', <a href="editgroups.cgi">groups</a>' [% ', <a href="editgroups.cgi">groups</a>'
IF user.groups.creategroups %] IF user.groups.creategroups %]
......
...@@ -75,6 +75,10 @@ ...@@ -75,6 +75,10 @@
Bug aliases cannot be longer than 20 characters. Bug aliases cannot be longer than 20 characters.
Please choose a shorter alias. Please choose a shorter alias.
[% ELSIF error == "authorization_failure" %]
[% title = "Authorization Failed" %]
You are not allowed to [% action %].
[% ELSIF error == "attachment_access_denied" %] [% ELSIF error == "attachment_access_denied" %]
[% title = "Access Denied" %] [% title = "Access Denied" %]
You are not permitted access to this attachment. You are not permitted access to this attachment.
...@@ -129,6 +133,23 @@ ...@@ -129,6 +133,23 @@
format like JPG or PNG, or put it elsewhere on the web and 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. 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" %] [% ELSIF error == "illegal_at_least_x_votes" %]
[% title = "Your Query Makes No Sense" %] [% title = "Your Query Makes No Sense" %]
The <em>At least ___ votes</em> field must be a simple number. The <em>At least ___ votes</em> field must be a simple number.
...@@ -176,10 +197,6 @@ ...@@ -176,10 +197,6 @@
[% title = "Invalid Attachment ID" %] [% title = "Invalid Attachment ID" %]
The attachment id [% attach_id FILTER html %] is invalid. 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" %] [% ELSIF error == "invalid_content_type" %]
[% title = "Invalid Content-Type" %] [% title = "Invalid Content-Type" %]
The content type <em>[% contenttype FILTER html %]</em> is invalid. The content type <em>[% contenttype FILTER html %]</em> is invalid.
...@@ -281,6 +298,18 @@ ...@@ -281,6 +298,18 @@
intentionally cleared out the "Reassign bug to" intentionally cleared out the "Reassign bug to"
field, [% Param("browserbugmessage") %] 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" %] [% ELSIF error == "unknown_keyword" %]
[% title = "Unknown Keyword" %] [% title = "Unknown Keyword" %]
<code>[% keyword FILTER html %]</code> is not a known 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 { ...@@ -207,6 +207,11 @@ sub DoEmail {
$vars->{'excludeself'} = 0; $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, # 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). # 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. # Preferences not existing in the user's list are assumed to be on.
...@@ -234,6 +239,10 @@ sub SaveEmail { ...@@ -234,6 +239,10 @@ sub SaveEmail {
$updateString .= 'ExcludeSelf~'; $updateString .= 'ExcludeSelf~';
} }
foreach my $flag qw(FlagRequestee FlagRequester) {
$updateString .= "~$flag~" . (defined($::FORM{$flag}) ? "on" : "");
}
foreach my $role (@roles) { foreach my $role (@roles) {
foreach my $reason (@reasons) { foreach my $reason (@reasons) {
# Add this preference to the list without giving it a value, # 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