#!/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 , # Bryce Nesbitt # Dan Mosedale # Alan Raetz # # 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
}; # 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), "
\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 () { $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 "Email sent to: " . join(", ", @sentlist) . "
\n"; } else { print "Email sent to: no one
\n"; } if ( @excludedAddresses ) { print "
Excluding: " . join(", ", @excludedAddresses) . "\n"; } print "

New: " . "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, ") { $::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 "
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 "
Doing bug $id\n"; ProcessOneBug($ARGV[0]); } } else { ProcessOneBug($ARGV[0]); } exit;