зеркало из https://github.com/mozilla/pjs.git
1219 строки
38 KiB
Perl
Executable File
1219 строки
38 KiB
Perl
Executable File
#!/usr/bonsaitools/bin/perl -w
|
|
# -*- 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): Terry Weissman <terry@mozilla.org>,
|
|
# Bryce Nesbitt <bryce-mozilla@nextbus.com>
|
|
# Dan Mosedale <dmose@mozilla.org>
|
|
# Alan Raetz <al_raetz@yahoo.com>
|
|
#
|
|
|
|
# To recreate the shadow database, run "processmail regenerate" .
|
|
|
|
use diagnostics;
|
|
use strict;
|
|
|
|
require "globals.pl";
|
|
|
|
use RelationSet;
|
|
|
|
$| = 1;
|
|
|
|
umask(0);
|
|
|
|
$::lockcount = 0;
|
|
my $regenerate = 0;
|
|
my $nametoexclude = "";
|
|
|
|
my @excludedAddresses = ();
|
|
|
|
# disable email flag for offline debugging work
|
|
my $enableSendMail = 1;
|
|
|
|
my %force;
|
|
@{$force{'QAcontact'}} = ();
|
|
@{$force{'Owner'}} = ();
|
|
@{$force{'Reporter'}} = ();
|
|
@{$force{'CClist'}} = ();
|
|
@{$force{'Voter'}} = ();
|
|
|
|
sub Lock {
|
|
if ($::lockcount <= 0) {
|
|
$::lockcount = 0;
|
|
if (!open(LOCKFID, ">>data/maillock")) {
|
|
mkdir "data", 0777;
|
|
chmod 0777, "data";
|
|
open(LOCKFID, ">>data/maillock") || die "Can't open lockfile.";
|
|
}
|
|
my $val = flock(LOCKFID,2);
|
|
if (!$val) { # '2' is magic 'exclusive lock' const.
|
|
print "Lock failed: $val\n";
|
|
}
|
|
chmod 0666, "data/maillock";
|
|
}
|
|
$::lockcount++;
|
|
}
|
|
|
|
sub Unlock {
|
|
$::lockcount--;
|
|
if ($::lockcount <= 0) {
|
|
flock(LOCKFID,8); # '8' is magic 'unlock' const.
|
|
close LOCKFID;
|
|
}
|
|
}
|
|
|
|
sub FileSize {
|
|
my ($filename) = (@_);
|
|
my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
|
|
$atime,$mtime,$ctime,$blksize,$blocks)
|
|
= stat($filename);
|
|
if (defined $size) {
|
|
return $size;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
|
|
sub Different {
|
|
my ($file1, $file2) = (@_);
|
|
my $size1 = FileSize($file1);
|
|
my $size2 = FileSize($file2);
|
|
if ($size1 != $size2) {
|
|
return 1;
|
|
}
|
|
open(FID1, "<$file1") || die "Can't open $file1";
|
|
open(FID2, "<$file2") || die "Can't open $file2";
|
|
my $d1;
|
|
my $d2;
|
|
if (read(FID1, $d1, $size1) ne $size1) {
|
|
die "Can't read $size1 bytes from $file1";
|
|
}
|
|
if (read(FID2, $d2, $size2) ne $size2) {
|
|
die "Can't read $size2 bytes from $file2";
|
|
}
|
|
close FID1;
|
|
close FID2;
|
|
return ($d1 ne $d2);
|
|
}
|
|
|
|
|
|
sub DescCC {
|
|
my $cclist = shift();
|
|
|
|
return "" if ( $cclist->size() == 0 );
|
|
|
|
return "Cc: " . $cclist->toString() . "\n";
|
|
}
|
|
|
|
|
|
sub DescDependencies {
|
|
my ($id) = (@_);
|
|
if (!Param("usedependencies")) {
|
|
return "";
|
|
}
|
|
my $result = "";
|
|
my $me = "blocked";
|
|
my $target = "dependson";
|
|
my $title = "BugsThisDependsOn";
|
|
for (1..2) {
|
|
SendSQL("select $target from dependencies where $me = $id order by $target");
|
|
my @list;
|
|
while (MoreSQLData()) {
|
|
push(@list, FetchOneColumn());
|
|
}
|
|
if (@list) {
|
|
my @verbose;
|
|
my $count = 0;
|
|
foreach my $i (@list) {
|
|
SendSQL("select bug_status, resolution from bugs where bug_id = $i");
|
|
my ($bug_status, $resolution) = (FetchSQLData());
|
|
my $desc;
|
|
if ($bug_status eq "NEW" || $bug_status eq "ASSIGNED" ||
|
|
$bug_status eq "REOPENED" || $bug_status eq "UNCONFIRMED") {
|
|
$desc = "";
|
|
} else {
|
|
$desc = "[$resolution]";
|
|
}
|
|
push(@verbose, $i . "$desc");
|
|
$count++;
|
|
}
|
|
if ($count > 5) {
|
|
$result .= "$title: Big list (more than 5) has been omitted\n";
|
|
} else {
|
|
$result .= "$title: " . join(', ', @verbose) . "\n";
|
|
}
|
|
}
|
|
my $tmp = $me;
|
|
$me = $target;
|
|
$target = $tmp;
|
|
$title = "OtherBugsDependingOnThis";
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
|
|
|
|
sub GetBugText {
|
|
my ($id) = (@_);
|
|
undef %::bug;
|
|
|
|
my @collist = ("bug_id", "product", "version", "rep_platform", "op_sys",
|
|
"bug_status", "resolution", "priority", "bug_severity",
|
|
"assigned_to", "reporter", "bug_file_loc",
|
|
"short_desc", "component", "qa_contact", "target_milestone",
|
|
"status_whiteboard", "groupset");
|
|
|
|
my $query = "select " . join(", ", @collist) .
|
|
" from bugs where bug_id = $id";
|
|
|
|
SendSQL($query);
|
|
|
|
my @row;
|
|
if (!(@row = FetchSQLData())) {
|
|
return "";
|
|
}
|
|
foreach my $field (@collist) {
|
|
$::bug{$field} = shift @row;
|
|
if (!defined $::bug{$field}) {
|
|
$::bug{$field} = "";
|
|
}
|
|
}
|
|
|
|
$::bug{'assigned_to'} = DBID_to_name($::bug{'assigned_to'});
|
|
$::bug{'reporter'} = DBID_to_name($::bug{'reporter'});
|
|
my $qa_contact = "";
|
|
my $target_milestone = "";
|
|
my $status_whiteboard = "";
|
|
if (Param('useqacontact') && $::bug{'qa_contact'} > 0) {
|
|
$::bug{'qa_contact'} = DBID_to_name($::bug{'qa_contact'});
|
|
$qa_contact = "QAContact: $::bug{'qa_contact'}\n";
|
|
} else {
|
|
$::bug{'qa_contact'} = "";
|
|
}
|
|
if (Param('usetargetmilestone') && $::bug{'target_milestone'} ne "") {
|
|
$target_milestone = "TargetMilestone: $::bug{'target_milestone'}\n";
|
|
}
|
|
if (Param('usestatuswhiteboard') && $::bug{'status_whiteboard'} ne "") {
|
|
$status_whiteboard = "StatusWhiteboard: $::bug{'status_whiteboard'}\n";
|
|
}
|
|
|
|
$::bug{'long_desc'} = GetLongDescriptionAsText($id);
|
|
|
|
my $cclist = new RelationSet();
|
|
$cclist->mergeFromDB("select who from cc where bug_id = $id");
|
|
my @voterlist;
|
|
SendSQL("select profiles.login_name from votes, profiles where votes.bug_id = $id and profiles.userid = votes.who");
|
|
while (MoreSQLData()) {
|
|
my $v = FetchOneColumn();
|
|
push(@voterlist, $v);
|
|
}
|
|
$::bug{'cclist'} = $cclist->toString();
|
|
$::bug{'voterlist'} = join(',', @voterlist);
|
|
|
|
if (Param("prettyasciimail")) {
|
|
$^A = "";
|
|
my $temp = formline <<'END',$::bug{'short_desc'},$id,$::bug{'product'},$::bug{'bug_status'},$::bug{'version'},$::bug{'resolution'},$::bug{'rep_platform'},$::bug{'bug_severity'},$::bug{'op_sys'},$::bug{'priority'},$::bug{'component'},$::bug{'assigned_to'},$::bug{'reporter'},$qa_contact,DescCC($cclist),$target_milestone,${status_whiteboard},$::bug{'bug_file_loc'},DescDependencies($id);
|
|
+============================================================================+
|
|
| @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< |
|
|
+----------------------------------------------------------------------------+
|
|
| Bug #: @<<<<<<<<<<< Product: @<<<<<<<<<<<<<<<<<<<<<< |
|
|
| Status: @<<<<<<<<<<<<<<<<<< Version: @<<<<<<<<<<<<<<<<<<<<<< |
|
|
| Resolution: @<<<<<<<<<<<<<<<<<< Platform: @<<<<<<<<<<<<<<<<<<<<<< |
|
|
| Severity: @<<<<<<<<<<<<<<<<<< OS/Version: @<<<<<<<<<<<<<<<<<<<<<< |
|
|
| Priority: @<<<<<<<<<<<<<<<<<< Component: @<<<<<<<<<<<<<<<<<<<<<< |
|
|
+----------------------------------------------------------------------------+
|
|
| Assigned To: @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< |
|
|
| Reported By: @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< |
|
|
| ~QA Contact: ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< |
|
|
| ~ CC list: ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< |
|
|
+----------------------------------------------------------------------------+
|
|
| ~ Milestone: ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< |
|
|
|~ Whiteboard: ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< |
|
|
| URL: @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< |
|
|
|~Dependencies: ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< |
|
|
+============================================================================+
|
|
| DESCRIPTION |
|
|
END
|
|
|
|
my $prettymail = $^A . $::bug{'long_desc'};
|
|
return $prettymail;
|
|
|
|
|
|
} else {
|
|
return "Bug\#: $id
|
|
Product: $::bug{'product'}
|
|
Version: $::bug{'version'}
|
|
Platform: $::bug{'rep_platform'}
|
|
OS/Version: $::bug{'op_sys'}
|
|
Status: $::bug{'bug_status'}
|
|
Resolution: $::bug{'resolution'}
|
|
Severity: $::bug{'bug_severity'}
|
|
Priority: $::bug{'priority'}
|
|
Component: $::bug{'component'}
|
|
AssignedTo: $::bug{'assigned_to'}
|
|
ReportedBy: $::bug{'reporter'}
|
|
$qa_contact$target_milestone${status_whiteboard}URL: $::bug{'bug_file_loc'}
|
|
" . DescCC($cclist) . "Summary: $::bug{'short_desc'}
|
|
" . DescDependencies($id) . "
|
|
$::bug{'long_desc'}
|
|
";
|
|
}
|
|
|
|
}
|
|
|
|
|
|
my %seen;
|
|
my @sentlist;
|
|
sub fixaddresses {
|
|
my ($field, $list) = (@_);
|
|
my @result;
|
|
foreach my $i (@$list) {
|
|
if (!defined $i || $i eq "") {
|
|
next;
|
|
}
|
|
SendSQL("select emailnotification, groupset & $::bug{'groupset'} from profiles where login_name = " .
|
|
SqlQuote($i));
|
|
my ($emailnotification, $groupset) = (FetchSQLData());
|
|
if ($groupset ne $::bug{'groupset'}) {
|
|
next;
|
|
}
|
|
if ($emailnotification eq "CConly") {
|
|
if ($field ne "cc") {
|
|
next;
|
|
}
|
|
}
|
|
if ($emailnotification eq "ExcludeSelfChanges" &&
|
|
(lc($i) eq $nametoexclude)) {
|
|
push @excludedAddresses, $nametoexclude;
|
|
next;
|
|
}
|
|
|
|
if (!defined $::nomail{$i} && !defined $seen{$i}) {
|
|
push(@result, $i . Param('emailsuffix'));
|
|
$seen{$i} = 1;
|
|
}
|
|
}
|
|
return join(", ", @result);
|
|
}
|
|
|
|
|
|
sub Log {
|
|
my ($str) = (@_);
|
|
Lock();
|
|
open(FID, ">>data/maillog") || die "Can't write to data/maillog";
|
|
print FID time2str("%D %H:%M", time()) . ": $str\n";
|
|
close FID;
|
|
Unlock();
|
|
}
|
|
|
|
|
|
sub FormatTriple {
|
|
my ($a, $b, $c) = (@_);
|
|
$^A = "";
|
|
my $temp = formline << 'END', $a, $b, $c;
|
|
^>>>>>>>>>>>>>>>>>>|^<<<<<<<<<<<<<<<<<<<<<<<<<<<|^<<<<<<<<<<<<<<<<<<<<<<<<<<<~~
|
|
END
|
|
; # This semicolon appeases my emacs editor macros. :-)
|
|
return $^A;
|
|
}
|
|
|
|
sub FormatDouble {
|
|
my ($a, $b) = (@_);
|
|
$a .= ":";
|
|
$^A = "";
|
|
my $temp = formline << 'END', $a, $b;
|
|
^>>>>>>>>>>>>>>>>>> ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<~~
|
|
END
|
|
; # This semicolon appeases my emacs editor macros. :-)
|
|
return $^A;
|
|
}
|
|
|
|
|
|
sub NewProcessOneBug {
|
|
my ($id) = (@_);
|
|
|
|
my @headerlist;
|
|
my %values;
|
|
my %defmailhead;
|
|
my %fielddescription;
|
|
|
|
my $msg = "";
|
|
|
|
SendSQL("SELECT name, description, mailhead FROM fielddefs " .
|
|
"ORDER BY sortkey");
|
|
while (MoreSQLData()) {
|
|
my ($field, $description, $mailhead) = (FetchSQLData());
|
|
push(@headerlist, $field);
|
|
$defmailhead{$field} = $mailhead;
|
|
$fielddescription{$field} = $description;
|
|
}
|
|
SendSQL("SELECT " . join(',', @::log_columns) . ", lastdiffed, now() " .
|
|
"FROM bugs WHERE bug_id = $id");
|
|
my @row = FetchSQLData();
|
|
foreach my $i (@::log_columns) {
|
|
$values{$i} = shift(@row);
|
|
}
|
|
my ($start, $end) = (@row);
|
|
my $ccSet = new RelationSet();
|
|
$ccSet->mergeFromDB("SELECT who FROM cc WHERE bug_id = $id");
|
|
$values{'cc'} = $ccSet->toString();
|
|
|
|
my @voterlist;
|
|
SendSQL("SELECT profiles.login_name FROM votes, profiles " .
|
|
"WHERE votes.bug_id = $id AND profiles.userid = votes.who");
|
|
while (MoreSQLData()) {
|
|
push(@voterlist, FetchOneColumn());
|
|
}
|
|
|
|
$values{'assigned_to'} = DBID_to_name($values{'assigned_to'});
|
|
$values{'reporter'} = DBID_to_name($values{'reporter'});
|
|
if ($values{'qa_contact'}) {
|
|
$values{'qa_contact'} = DBID_to_name($values{'qa_contact'});
|
|
}
|
|
|
|
my @diffs;
|
|
|
|
|
|
SendSQL("SELECT profiles.login_name, fielddefs.description, " .
|
|
" bug_when, oldvalue, newvalue " .
|
|
"FROM bugs_activity, fielddefs, profiles " .
|
|
"WHERE bug_id = $id " .
|
|
" AND fielddefs.fieldid = bugs_activity.fieldid " .
|
|
" AND profiles.userid = who " .
|
|
" AND bug_when > '$start' " .
|
|
" AND bug_when <= '$end' " .
|
|
"ORDER BY bug_when"
|
|
);
|
|
|
|
while (MoreSQLData()) {
|
|
my @row = FetchSQLData();
|
|
push(@diffs, \@row);
|
|
}
|
|
|
|
my $difftext = "";
|
|
my $lastwho = "";
|
|
foreach my $ref (@diffs) {
|
|
my ($who, $what, $when, $old, $new) = (@$ref);
|
|
if ($who ne $lastwho) {
|
|
$lastwho = $who;
|
|
$difftext .= "\n$who" . Param('emailsuffix') . " changed:\n\n";
|
|
$difftext .= FormatTriple("What ", "Old Value", "New Value");
|
|
$difftext .= ('-' x 76) . "\n";
|
|
}
|
|
$difftext .= FormatTriple($what, $old, $new);
|
|
}
|
|
|
|
$difftext = trim($difftext);
|
|
|
|
|
|
my $deptext = "";
|
|
|
|
my $resid =
|
|
|
|
SendSQL("SELECT bugs_activity.bug_id, fielddefs.name, " .
|
|
" oldvalue, newvalue " .
|
|
"FROM bugs_activity, dependencies, fielddefs ".
|
|
"WHERE bugs_activity.bug_id = dependencies.dependson " .
|
|
" AND dependencies.blocked = $id " .
|
|
" AND fielddefs.fieldid = bugs_activity.fieldid" .
|
|
" AND (fielddefs.name = 'bug_status' " .
|
|
" OR fielddefs.name = 'resolution') " .
|
|
" AND bug_when > '$start' " .
|
|
" AND bug_when <= '$end' " .
|
|
"ORDER BY bug_when, bug_id");
|
|
|
|
my $thisdiff = "";
|
|
my $lastbug = "";
|
|
my $interestingchange = 0;
|
|
while (MoreSQLData()) {
|
|
my ($bug, $what, $old, $new) = (FetchSQLData());
|
|
if ($bug ne $lastbug) {
|
|
if ($interestingchange) {
|
|
$deptext .= $thisdiff;
|
|
}
|
|
$lastbug = $bug;
|
|
$thisdiff =
|
|
"\nThis bug depends on bug $bug, which changed state:\n\n";
|
|
$thisdiff .= FormatTriple("What ", "Old Value", "New Value");
|
|
$thisdiff .= ('-' x 76) . "\n";
|
|
$interestingchange = 0;
|
|
}
|
|
$thisdiff .= FormatTriple($fielddescription{$what}, $old, $new);
|
|
if ($what eq 'bug_status' && IsOpenedState($old) ne IsOpenedState($new)) {
|
|
$interestingchange = 1;
|
|
}
|
|
}
|
|
if ($interestingchange) {
|
|
$deptext .= $thisdiff;
|
|
}
|
|
|
|
$deptext = trim($deptext);
|
|
|
|
if ($deptext) {
|
|
$difftext = trim($difftext . "\n\n" . $deptext);
|
|
}
|
|
|
|
|
|
my $newcomments = GetLongDescriptionAsText($id, $start, $end);
|
|
|
|
if (Param('newemailtech')) {
|
|
|
|
#
|
|
# Start of email filtering code
|
|
#
|
|
# Even if the user sending the email has not enabled #
|
|
# 'newEmailTech', we still want to filter the email
|
|
# based on other user's email preferences if the global Param
|
|
# 'newemailtech' is enabled.
|
|
#
|
|
# Note: users who have not enabled newEmailTech will default
|
|
# to no filtering (they will get all email Bugzilla sends).
|
|
|
|
my $count = 0;
|
|
|
|
my @currentEmailAttributes = getEmailAttributes($newcomments,
|
|
@diffs);
|
|
my (@assigned_toList,@reporterList,@qa_contactList,@ccList) =
|
|
();
|
|
|
|
#open(LOG, ">>/tmp/maillog");
|
|
#print LOG "\nBug ID: $id CurrentEmailAttributes:";
|
|
#print LOG join(',', @currentEmailAttributes) . "\n";
|
|
|
|
@excludedAddresses = (); # zero out global list
|
|
|
|
@assigned_toList = filterEmailGroup('Owner',
|
|
\@currentEmailAttributes,
|
|
$values{'assigned_to'});
|
|
@reporterList = filterEmailGroup('Reporter',
|
|
\@currentEmailAttributes,
|
|
$values{'reporter'});
|
|
if (Param('useqacontact') && $values{'qa_contact'}) {
|
|
@qa_contactList = filterEmailGroup('QAcontact',
|
|
\@currentEmailAttributes,
|
|
$values{'qa_contact'});
|
|
} else {
|
|
@qa_contactList = ();
|
|
}
|
|
|
|
@ccList = filterEmailGroup('CClist', \@currentEmailAttributes,
|
|
$values{'cc'});
|
|
|
|
@voterlist = filterEmailGroup('Voter', \@currentEmailAttributes,
|
|
join(',',@voterlist));
|
|
|
|
my @emailList = (@assigned_toList, @reporterList,
|
|
@qa_contactList, @ccList, @voterlist);
|
|
|
|
# only need one entry per person
|
|
my @allEmail = ();
|
|
my %AlreadySeen = ();
|
|
foreach my $person (@emailList) {
|
|
if ( !($AlreadySeen{$person}) ) {
|
|
push(@allEmail,$person);
|
|
$AlreadySeen{$person}++;
|
|
}
|
|
}
|
|
|
|
#print LOG "\nbug $id email sent: " . join(',', @allEmail) . "\n";
|
|
|
|
@excludedAddresses = filterExcludeList(\@excludedAddresses,
|
|
\@allEmail);
|
|
|
|
# print LOG "excluded: " . join(',',@excludedAddresses) . "\n\n";
|
|
|
|
foreach my $person ( @allEmail ) {
|
|
$count++;
|
|
if ( !defined(NewProcessOnePerson($person, $count, \@headerlist,
|
|
\%values, \%defmailhead,
|
|
\%fielddescription, $difftext,
|
|
$newcomments, $start, $id))) {
|
|
|
|
# if a value is not returned, this means that the person
|
|
# was not sent mail. add them to the excludedAddresses list.
|
|
# it will be filtered later for dups.
|
|
#
|
|
push @excludedAddresses, $person;
|
|
|
|
}
|
|
}
|
|
|
|
} else {
|
|
my $count = 0;
|
|
my @personlist = ($values{'assigned_to'}, $values{'reporter'},
|
|
split(/,/, $values{'cc'}),
|
|
@voterlist,
|
|
$force{'CClist'});
|
|
if ($values{'qa_contact'}) { push @personlist, $values{'qa_contact'} }
|
|
for my $person (@personlist) {
|
|
$count++;
|
|
|
|
my $match = "^[^@, ]*@[^@, ]*\.[^@, ]*\$";
|
|
if ($person !~ /$match/) {
|
|
$person = $person . Param('emailsuffix');
|
|
}
|
|
|
|
if ( !defined(NewProcessOnePerson($person, $count, \@headerlist,
|
|
\%values, \%defmailhead,
|
|
\%fielddescription, $difftext,
|
|
$newcomments, $start, $id))) {
|
|
|
|
# if a value is not returned, this means that the person
|
|
# was not sent mail. add them to the excludedAddresses list.
|
|
# it will be filtered later for dups.
|
|
#
|
|
push @excludedAddresses, $person;
|
|
}
|
|
}
|
|
}
|
|
|
|
SendSQL("UPDATE bugs SET lastdiffed = '$end', delta_ts = delta_ts " .
|
|
"WHERE bug_id = $id");
|
|
}
|
|
|
|
# When one person is in different fields on one bug, they may be
|
|
# excluded from email because of one relationship to the bug (eg
|
|
# they're the QA contact) but included because of another (eg they
|
|
# also reported the bug). Inclusion takes precedence, so this
|
|
# function looks for and removes any users from the exclude list who
|
|
# are also on the include list. Additionally, it removes duplicate
|
|
# entries from the exclude list.
|
|
#
|
|
# Arguments are the exclude list and the include list; the cleaned up
|
|
# exclude list is returned.
|
|
#
|
|
sub filterExcludeList ($$) {
|
|
|
|
if ($#_ != 1) {
|
|
die ("filterExcludeList called with wrong number of args");
|
|
}
|
|
|
|
my ($refExcluded, $refAll) = @_;
|
|
|
|
my @excludedAddrs = @$refExcluded;
|
|
my @allEmail = @$refAll;
|
|
my @tmpList = @excludedAddrs;
|
|
my (@result,@uniqueResult) = ();
|
|
my %alreadySeen;
|
|
|
|
foreach my $excluded (@tmpList) {
|
|
|
|
push (@result,$excluded);
|
|
foreach my $included (@allEmail) {
|
|
|
|
# match found, so we remove the entry
|
|
if ($included eq $excluded) {
|
|
pop(@result);
|
|
}
|
|
}
|
|
}
|
|
|
|
# only need one entry per person
|
|
foreach my $person (@result) {
|
|
if ( !($alreadySeen{$person}) ) {
|
|
push(@uniqueResult,$person);
|
|
$alreadySeen{$person}++;
|
|
}
|
|
}
|
|
|
|
return @uniqueResult;
|
|
}
|
|
|
|
# if the Status was changed to Resolved or Verified
|
|
# set the Resolved flag
|
|
#
|
|
# else if Severity, Status OR Priority fields have any change
|
|
# set the Status flag
|
|
#
|
|
# else if Keywords has changed
|
|
# set the Keywords flag
|
|
#
|
|
# else if CC has changed
|
|
# set the CC flag
|
|
#
|
|
# if the Comments field shows an attachment
|
|
# set the Attachment flag
|
|
#
|
|
# else if Comments exist
|
|
# set the Comments flag
|
|
#
|
|
# if no flags are set and there was some other field change
|
|
# set the Status flag
|
|
#
|
|
sub getEmailAttributes ($@) {
|
|
|
|
my ($commentField,@fieldDiffs) = @_;
|
|
my (@flags,@uniqueFlags,%alreadySeen) = ();
|
|
|
|
foreach my $ref (@fieldDiffs) {
|
|
my ($who, $fieldName, $when, $old, $new) = (@$ref);
|
|
|
|
#print qq{field: $fieldName $new<br>};
|
|
|
|
# the STATUS will be flagged for Severity, Status and
|
|
# Priority changes
|
|
#
|
|
if ( $fieldName eq 'Status') {
|
|
if ($new eq 'RESOLVED' || $new eq 'VERIFIED') {
|
|
push (@flags, 'Resolved');
|
|
}
|
|
}
|
|
elsif ( $fieldName eq 'Severity' || $fieldName eq 'Status' ||
|
|
$fieldName eq 'Priority' ) {
|
|
push (@flags, 'Status');
|
|
} elsif ( $fieldName eq 'Keywords') {
|
|
push (@flags, 'Keywords');
|
|
} elsif ( $fieldName eq 'CC') {
|
|
push (@flags, 'CC');
|
|
}
|
|
|
|
# These next few lines are for finding out who's been added
|
|
# to the Owner, QA, CC, etc. fields. It does not effect
|
|
# the @flags array at all, but is run here because it does
|
|
# effect filtering later and we're already in the loop.
|
|
if ($fieldName eq 'Owner') {
|
|
push (@{$force{'Owner'}}, $new);
|
|
} elsif ($fieldName eq 'QAContact') {
|
|
push (@{$force{'QAContact'}}, $new);
|
|
} elsif ($fieldName eq 'CC') {
|
|
my @oldVal = split (/,/, $old);
|
|
my @newVal = split (/,/, $new);
|
|
my @added = filterExcludeList(\@newVal, \@oldVal);
|
|
push (@{$force{'CClist'}}, @added);
|
|
}
|
|
}
|
|
|
|
if ( $commentField =~ /Created an attachment \(/ ) {
|
|
push (@flags, 'Attachments');
|
|
}
|
|
elsif ( ($commentField ne '') && !(scalar(@flags) == 1 && $flags[0] eq 'Resolved')) {
|
|
push (@flags, 'Comments');
|
|
}
|
|
|
|
# default fallthrough for any unflagged change is 'Other'
|
|
if ( @flags == 0 && @fieldDiffs != 0 ) {
|
|
push (@flags, 'Other');
|
|
}
|
|
|
|
# only need one flag per attribute type
|
|
foreach my $flag (@flags) {
|
|
if ( !($alreadySeen{$flag}) ) {
|
|
push(@uniqueFlags,$flag);
|
|
$alreadySeen{$flag}++;
|
|
}
|
|
}
|
|
#print "\nEmail Attributes: ", join(' ,',@uniqueFlags), "<br>\n";
|
|
|
|
# catch-all default, just in case the above logic is faulty
|
|
if ( @uniqueFlags == 0) {
|
|
push (@uniqueFlags, 'Comments');
|
|
}
|
|
|
|
return @uniqueFlags;
|
|
}
|
|
|
|
sub filterEmailGroup ($$$) {
|
|
|
|
my ($emailGroup,$refAttributes,$emailList) = @_;
|
|
my @emailAttributes = @$refAttributes;
|
|
my @emailList = split(/,/,$emailList);
|
|
my @filteredList = ();
|
|
|
|
|
|
# the force list for this email group needs to be checked as well
|
|
#
|
|
push @emailList, @{$force{$emailGroup}};
|
|
|
|
# Check this user for any watchers... doing this here allows them to inhert the
|
|
# relationship to the bug of the person they are watching (if the person they
|
|
# are watching is an owner, their mail is filtered as if they were the owner).
|
|
if (Param("supportwatchers")) {
|
|
my @watchers;
|
|
foreach my $person(@emailList) {
|
|
my $personId = DBname_to_id($person);
|
|
SendSQL("SELECT watcher FROM watch WHERE watched = $personId");
|
|
while(MoreSQLData()) {
|
|
my ($watcher) = FetchSQLData();
|
|
if ($watcher) {
|
|
push (@watchers, DBID_to_name($watcher));
|
|
}
|
|
}
|
|
}
|
|
push(@emailList, @watchers);
|
|
}
|
|
|
|
|
|
foreach my $person (@emailList) {
|
|
|
|
my $userid;
|
|
my $lastCount = @filteredList;
|
|
|
|
if ( $person eq '' ) { next; }
|
|
|
|
SendSQL("SELECT userid FROM profiles WHERE login_name = "
|
|
. SqlQuote($person) );
|
|
|
|
if ( !($userid = FetchSQLData()) ) {
|
|
push(@filteredList,$person);
|
|
next;
|
|
}
|
|
|
|
SendSQL("SELECT emailflags, newemailtech FROM profiles WHERE " .
|
|
"userid = $userid" );
|
|
|
|
my ($userFlagString, $newemailtech) = FetchSQLData();
|
|
|
|
# people who are not using newemailtech get skipped; they will
|
|
# be dealt with later by the old email tech code in
|
|
# ProcessOneBug().
|
|
#
|
|
if (!defined($newemailtech) || $newemailtech == 0) {
|
|
next;
|
|
}
|
|
|
|
# If the sender doesn't want email, exclude them from list
|
|
|
|
if (lc($person) eq $nametoexclude) {
|
|
|
|
if ( defined ($userFlagString) &&
|
|
$userFlagString =~ /ExcludeSelf\~on/ ) {
|
|
|
|
push (@excludedAddresses,$person);
|
|
next;
|
|
}
|
|
}
|
|
|
|
# if the users database entry is empty, send them all email
|
|
# by default (they have not accessed userprefs.cgi recently).
|
|
|
|
if ( !defined($userFlagString) || !($userFlagString =~ /email/) ) {
|
|
push(@filteredList,$person);
|
|
}
|
|
else {
|
|
|
|
# the 255 param is here, because without a third param,
|
|
# split will trim any trailing null fields, which causes perl
|
|
# to eject lots of warnings. any suitably large number would
|
|
# do.
|
|
|
|
my %userFlags = split(/~/, $userFlagString, 255);
|
|
|
|
# The default condition is to send each person email.
|
|
# If we match the email attribute with the user flag, and
|
|
# they do not want email, then remove them from the list.
|
|
|
|
push(@filteredList,$person);
|
|
|
|
my $detectedOn = 0;
|
|
|
|
foreach my $attribute (@emailAttributes) {
|
|
|
|
my $matchName = 'email' . $emailGroup . $attribute;
|
|
|
|
# **** Kludge... quick and dirty fix for 2.12
|
|
# http://bugzilla.mozilla.org/show_bug.cgi?id=73665
|
|
# If this pref is new (it's been added since this user
|
|
# last updated their filtering prefs, $userFlags{$matchName}
|
|
# will be undefined. This should be considered a match
|
|
# so that new prefs will default to 'on'
|
|
if (!defined($userFlags{$matchName})) {
|
|
$detectedOn = 1;
|
|
}
|
|
|
|
while ((my $flagName, my $flagValue) = each %userFlags) {
|
|
|
|
if ($flagName !~ /$emailGroup/) {
|
|
next;
|
|
}
|
|
|
|
if ($flagName eq $matchName){
|
|
if ($flagValue eq 'on') {
|
|
$detectedOn = 1;
|
|
}
|
|
}
|
|
|
|
} # for each userFlag
|
|
|
|
} # for each email attribute
|
|
|
|
# if the current flag hasn't been detected on at least once,
|
|
# this person gets filtered from this group.
|
|
#
|
|
if (! $detectedOn) {
|
|
pop(@filteredList);
|
|
}
|
|
|
|
# check to see if the person was added to or removed from
|
|
# this email group.
|
|
# Note: This was originally written as only removed from
|
|
# and was rewritten to be Added/Removed, but for simplicity
|
|
# sake, the name "Removeme" wasn't changed.
|
|
# http://bugzilla.mozilla.org/show_bug.cgi?id=71912
|
|
|
|
if ( grep ($_ eq $person, @{$force{$emailGroup}} ) ) {
|
|
|
|
# if so, see if they want mail about that
|
|
#
|
|
if ( $userFlags{'email' . $emailGroup . 'Removeme'} eq 'on' ) {
|
|
|
|
# we definitely want mail sent to this person, since
|
|
# inclusion on a mail takes precedence over the previous
|
|
# exclusion
|
|
|
|
# have they been filtered for some other reason?
|
|
#
|
|
if (@filteredList == $lastCount) {
|
|
|
|
# if so, put them back
|
|
#
|
|
push (@filteredList, $person);
|
|
}
|
|
}
|
|
}
|
|
|
|
} # if $userFlagString is valid
|
|
|
|
# has the person been moved off the filtered list?
|
|
#
|
|
if (@filteredList == $lastCount ) {
|
|
|
|
# mark them as excluded
|
|
#
|
|
push (@excludedAddresses,$person);
|
|
}
|
|
|
|
} # for each person
|
|
|
|
return @filteredList;
|
|
}
|
|
|
|
sub NewProcessOnePerson ($$$$$$$$$$) {
|
|
my ($person, $count, $hlRef, $valueRef, $dmhRef, $fdRef, $difftext,
|
|
$newcomments, $start, $id) = @_;
|
|
|
|
my %values = %$valueRef;
|
|
my @headerlist = @$hlRef;
|
|
my %defmailhead = %$dmhRef;
|
|
my %fielddescription = %$fdRef;
|
|
|
|
if ($seen{$person}) {
|
|
return;
|
|
}
|
|
|
|
SendSQL("SELECT userid, emailnotification, newemailtech," .
|
|
" groupset & $values{'groupset'} " .
|
|
"FROM profiles WHERE login_name = " . SqlQuote($person));
|
|
my ($userid, $emailnotification, $newemailtech,
|
|
$groupset) = (FetchSQLData());
|
|
|
|
if (!$newemailtech || !Param('newemailtech')) {
|
|
return;
|
|
}
|
|
$seen{$person} = 1;
|
|
|
|
|
|
# if this person doesn't have permission to see info on this bug,
|
|
# return.
|
|
#
|
|
# XXX - I _think_ this currently means that if a bug is suddenly given
|
|
# more restrictive permissions, people without those permissions won't
|
|
# see the action of restricting the bug itself; the bug will just
|
|
# quietly disappear from their radar.
|
|
#
|
|
if ($groupset ne $values{'groupset'}) {
|
|
return;
|
|
}
|
|
if ($emailnotification eq "ExcludeSelfChanges" &&
|
|
lc($person) eq $nametoexclude) {
|
|
push @excludedAddresses, $nametoexclude;
|
|
return;
|
|
}
|
|
# "$count < 3" means "this person is either assigned_to or reporter"
|
|
#
|
|
if ($emailnotification eq "CCOnly" && $count < 3) {
|
|
return;
|
|
}
|
|
|
|
my %mailhead = %defmailhead;
|
|
|
|
my $head = "";
|
|
|
|
foreach my $f (@headerlist) {
|
|
if ($mailhead{$f}) {
|
|
my $value = $values{$f};
|
|
if (!defined $value) {
|
|
# Probaby ought to whine or something. ###
|
|
next;
|
|
}
|
|
my $desc = $fielddescription{$f};
|
|
$head .= FormatDouble($desc, $value);
|
|
}
|
|
}
|
|
|
|
if ($difftext eq "" && $newcomments eq "") {
|
|
# Whoops, no differences!
|
|
return;
|
|
}
|
|
|
|
my $isnew = ($start !~ m/[1-9]/);
|
|
|
|
my %substs;
|
|
|
|
$person .= Param('emailsuffix');
|
|
# 09/13/2000 cyeh@bluemartini.com
|
|
# If a bug is changed, don't put the word "Changed" in the subject mail
|
|
# since if the bug didn't change, you wouldn't be getting mail
|
|
# in the first place! see http://bugzilla.mozilla.org/show_bug.cgi?id=29820
|
|
# for details.
|
|
$substs{"neworchanged"} = $isnew ? 'New: ' : '';
|
|
$substs{"to"} = $person;
|
|
$substs{"cc"} = '';
|
|
$substs{"bugid"} = $id;
|
|
if ($isnew) {
|
|
$substs{"diffs"} = $head . "\n\n" . $newcomments;
|
|
} else {
|
|
$substs{"diffs"} = $difftext . "\n\n" . $newcomments;
|
|
}
|
|
$substs{"summary"} = $values{'short_desc'};
|
|
|
|
my $template = Param("newchangedmail");
|
|
|
|
my $msg = PerformSubsts($template, \%substs);
|
|
|
|
my $sendmailparam = "-ODeliveryMode=deferred";
|
|
if (Param("sendmailnow")) {
|
|
$sendmailparam = "";
|
|
}
|
|
|
|
if ($enableSendMail == 1) {
|
|
open(SENDMAIL, "|/usr/lib/sendmail $sendmailparam -t") ||
|
|
die "Can't open sendmail";
|
|
|
|
print SENDMAIL trim($msg) . "\n";
|
|
close SENDMAIL;
|
|
}
|
|
push(@sentlist, $person);
|
|
return 1;
|
|
}
|
|
|
|
|
|
sub ProcessOneBug {
|
|
my $i = $_[0];
|
|
NewProcessOneBug($i);
|
|
|
|
# Make sure that everyone who was excluded because of the advanced
|
|
# filtering options (and thus are using new email tech) has the
|
|
# corresponding element in %seen set. This is so that they won't
|
|
# also be processed by the old email tech code, which follows.
|
|
#
|
|
# It's necessary because people who are excluded by the advanced
|
|
# filtering code never make it into NewProcessOnePerson(), which is
|
|
# where %seen would have otherwise been touched for them.
|
|
#
|
|
foreach my $person (@excludedAddresses) {
|
|
$seen{$person} = 1;
|
|
}
|
|
|
|
my $old = "shadow/$i";
|
|
my $new = "shadow/$i.tmp.$$";
|
|
my $diffs = "shadow/$i.diffs.$$";
|
|
my $verb = "Changed";
|
|
if (!stat($old)) {
|
|
mkdir "shadow", 0777;
|
|
chmod 0777, "shadow";
|
|
open(OLD, ">$old") || die "Couldn't create null $old";
|
|
close OLD;
|
|
$verb = "New";
|
|
}
|
|
my $text = GetBugText($i);
|
|
if ($text eq "") {
|
|
die "Couldn't find bug $i.";
|
|
}
|
|
open(FID, ">$new") || die "Couldn't create $new";
|
|
print FID $text;
|
|
close FID;
|
|
if (Different($old, $new)) {
|
|
system("diff -c -b $old $new > $diffs");
|
|
my $tolist = fixaddresses("to",
|
|
[$::bug{'assigned_to'}, $::bug{'reporter'},
|
|
$::bug{'qa_contact'}]);
|
|
my @combinedcc;
|
|
foreach my $v (split(/,/, "$::bug{'cclist'},$::bug{'voterlist'}")) {
|
|
push @combinedcc, $v;
|
|
}
|
|
push (@combinedcc, (@{$force{'CClist'}}, @{$force{'QAcontact'}},
|
|
@{$force{'Reporter'}}, @{$force{'Owner'}}));
|
|
my $cclist = fixaddresses("cc", \@combinedcc);
|
|
my $logstr = "Bug $i $verb";
|
|
if ($tolist ne "" || $cclist ne "") {
|
|
my %substs;
|
|
|
|
$substs{"fullbugreport"} = $text; # added ability to include the full bug report
|
|
$substs{"to"} = $tolist;
|
|
$substs{"cc"} = $cclist;
|
|
$substs{"bugid"} = $i;
|
|
$substs{"diffs"} = "";
|
|
open(DIFFS, "<$diffs") || die "Can't open $diffs";
|
|
while (<DIFFS>) {
|
|
$substs{"diffs"} .= $_;
|
|
}
|
|
close DIFFS;
|
|
$substs{"neworchanged"} = ($verb eq "New") ? "New: " : "";
|
|
$substs{"summary"} = $::bug{'short_desc'};
|
|
my $msg = PerformSubsts(Param("changedmail"), \%substs);
|
|
|
|
if (!$regenerate) {
|
|
# Note: fixaddresses may result in a Cc: only. This seems
|
|
# harmless.
|
|
my $sendmailparam = "-ODeliveryMode=deferred";
|
|
if (Param("sendmailnow")) {
|
|
$sendmailparam = "";
|
|
}
|
|
if ($enableSendMail==1) {
|
|
open(SENDMAIL,
|
|
"|/usr/lib/sendmail $sendmailparam -t") ||
|
|
die "Can't open sendmail";
|
|
|
|
print SENDMAIL $msg;
|
|
close SENDMAIL;
|
|
}
|
|
foreach my $n (split(/[, ]+/, "$tolist,$cclist")) {
|
|
if ($n ne "") {
|
|
push(@sentlist, $n);
|
|
}
|
|
}
|
|
|
|
$logstr = "$logstr; mail sent to $tolist, $cclist";
|
|
}
|
|
}
|
|
unlink($diffs);
|
|
Log($logstr);
|
|
}
|
|
|
|
# on the off chance that there are duplicate addresses in the exclude list,
|
|
# we filter it for dups one more time. They could have gotten there in
|
|
# fixaddresses(), NewProcessOnePerson(), or NewProcessOneBug.
|
|
#
|
|
@excludedAddresses = filterExcludeList(\@excludedAddresses,
|
|
\@sentlist);
|
|
|
|
if (!$regenerate) {
|
|
if (@sentlist) {
|
|
print "<B>Email sent to:</B> " . join(", ", @sentlist) . "<br>\n";
|
|
} else {
|
|
print "<B>Email sent to:</B> no one<br>\n";
|
|
}
|
|
|
|
if ( @excludedAddresses ) {
|
|
print "<br><B>Excluding: </B>" . join(", ", @excludedAddresses) .
|
|
"\n";
|
|
}
|
|
|
|
print "<br><br><center>New: <a href=\"userprefs.cgi\?bank=diffs\">" .
|
|
"change your email preferences<\/a> if you wish to tweak the " .
|
|
"kinds of Bugzilla email you get.<\/center>\n";
|
|
}
|
|
rename($new, $old) || die "Can't rename $new to $old";
|
|
chmod 0666, $old;
|
|
if ($regenerate) {
|
|
print "$i ";
|
|
}
|
|
%seen = ();
|
|
@sentlist = ();
|
|
}
|
|
|
|
# Code starts here
|
|
|
|
ConnectToDatabase();
|
|
GetVersionTable();
|
|
Lock();
|
|
|
|
if (open(FID, "<data/nomail")) {
|
|
while (<FID>) {
|
|
$::nomail{trim($_)} = 1;
|
|
}
|
|
close FID;
|
|
}
|
|
|
|
# To recreate the shadow database, run "processmail regenerate" .
|
|
if ($#ARGV >= 0 && $ARGV[0] eq "regenerate") {
|
|
$regenerate = 1;
|
|
shift @ARGV;
|
|
SendSQL("select bug_id from bugs order by bug_id");
|
|
my @regenerate_list;
|
|
while (my @row = FetchSQLData()) {
|
|
push @regenerate_list, $row[0];
|
|
}
|
|
foreach my $i (@regenerate_list) {
|
|
ProcessOneBug($i);
|
|
Unlock();
|
|
Lock();
|
|
}
|
|
print("\n");
|
|
exit;
|
|
}
|
|
|
|
if ($#ARGV >= 0 && $ARGV[0] eq "-forcecc") {
|
|
shift(@ARGV);
|
|
foreach my $i (split(/,/, shift(@ARGV))) {
|
|
push(@{$force{'CClist'}}, trim($i));
|
|
}
|
|
}
|
|
|
|
if ($#ARGV >= 0 && $ARGV[0] eq "-forceowner") {
|
|
shift(@ARGV);
|
|
@{$force{'Owner'}} = (trim(shift(@ARGV)));
|
|
}
|
|
|
|
if ($#ARGV >= 0 && $ARGV[0] eq "-forceqacontact") {
|
|
shift(@ARGV);
|
|
@{$force{'QAcontact'}} = (trim(shift(@ARGV)));
|
|
}
|
|
|
|
if (($#ARGV < 0) || ($#ARGV > 1)) {
|
|
print "Usage:\n processmail {bugid} {nametoexclude} " .
|
|
"[-forcecc list,of,users]\n [-forceowner name] " .
|
|
"[-forceqacontact name]\nor\n processmail regenerate\nor\n" .
|
|
" processmail rescanall\n";
|
|
exit;
|
|
}
|
|
|
|
if ($#ARGV == 1) {
|
|
$nametoexclude = lc($ARGV[1]);
|
|
}
|
|
|
|
if ($ARGV[0] eq "rescanall") {
|
|
print "<br> Collecting bug ids...\n";
|
|
SendSQL("select bug_id from bugs where to_days(now()) - to_days(delta_ts) <= 2 order by bug_id");
|
|
my @list;
|
|
while (my @row = FetchSQLData()) {
|
|
push @list, $row[0];
|
|
}
|
|
foreach my $id (@list) {
|
|
$ARGV[0] = $id;
|
|
print "<br> Doing bug $id\n";
|
|
ProcessOneBug($ARGV[0]);
|
|
}
|
|
} else {
|
|
ProcessOneBug($ARGV[0]);
|
|
}
|
|
|
|
exit;
|