# -*- 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 # Jacob Steenhagen # Matthew Tuck # Bradley Baetz # J. Paul Reed use strict; package Bugzilla::BugMail; use base qw(Exporter); @Bugzilla::BugMail::EXPORT = qw( PerformSubsts ); use Bugzilla::Config qw(:DEFAULT $datadir); use Bugzilla::Util; use Mail::Mailer; use Mail::Header; # This code is really ugly. It was a commandline interface, then it was moved # There are package-global variables which we rely on ProcessOneBug to clean # up each time, and other sorts of fun. # This really needs to be cleaned at some point. my $nametoexclude = ""; my %nomail; my $last_changed; my @excludedAddresses = (); my $sitespec = '@'.Param('urlbase'); $sitespec =~ s/:\/\//\./; # Make the protocol look like part of the domain $sitespec =~ s/^([^:\/]+):(\d+)/$1/; # Remove a port number, to relocate if ($2) { $sitespec = "-$2$sitespec"; # Put the port number back in, before the '@' } my %force; my %seen; my @sentlist; # I got sick of adding &:: to everything. # However, 'Yuck!' # I can't require, cause that pulls it in only once, so it won't then be # in the global package, and these aren't modules, so I can't use globals.pl # Remove this evilness once our stuff uses real packages. sub AUTOLOAD { no strict 'refs'; use vars qw($AUTOLOAD); my $subName = $AUTOLOAD; $subName =~ s/.*::/::/; # remove package name *$AUTOLOAD = \&$subName; goto &$AUTOLOAD; } # This is run when we load the package if (open(NOMAIL, '<', "$datadir/nomail")) { while () { $nomail{trim($_)} = 1; } close(NOMAIL); } 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; } # This is a bit of a hack, basically keeping the old system() # cmd line interface. Should clean this up at some point. # # args: bug_id, and an optional hash ref which may have keys for: # changer, owner, qa, reporter, cc # Optional hash contains values of people which will be forced to those # roles when the email is sent. # All the names are email addresses, not userids # values are scalars, except for cc, which is a list sub Send($;$) { my ($id, $recipients) = (@_); # This only works in a sub. Probably something to do with the # require abuse we do. GetVersionTable(); # Make sure to clean up _all_ package vars here. Yuck... $nametoexclude = $recipients->{'changer'} || ""; @{$force{'CClist'}} = (exists $recipients->{'cc'} && scalar($recipients->{'cc'}) > 0) ? map(trim($_), @{$recipients->{'cc'}}) : (); @{$force{'Owner'}} = $recipients->{'owner'} ? (trim($recipients->{'owner'})) : (); @{$force{'QAcontact'}} = $recipients->{'qacontact'} ? (trim($recipients->{'qacontact'})) : (); @{$force{'Reporter'}} = $recipients->{'reporter'} ? (trim($recipients->{'reporter'})) : (); @{$force{'Voter'}} = (); %seen = (); @excludedAddresses = (); @sentlist = (); return ProcessOneBug($id); } sub ProcessOneBug($) { 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); } $values{product} = get_product_name($values{product_id}); $values{component} = get_component_name($values{component_id}); my ($start, $end) = (@row); # $start and $end are considered safe because users can't touch them trick_taint($start); trick_taint($end); my $cc_ref = Bugzilla->dbh->selectcol_arrayref( q{SELECT profiles.login_name FROM cc, profiles WHERE bug_id = ? AND cc.who = profiles.userid ORDER BY profiles.login_name}, undef, $id); $values{'cc'} = join(',', @$cc_ref); 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'}); } $values{'estimated_time'} = format_time_decimal($values{'estimated_time'}); if ($values{'deadline'}) { $values{'deadline'} = time2str("%Y-%m-%d", str2time($values{'deadline'})); } my @dependslist; SendSQL("SELECT dependson FROM dependencies WHERE blocked = $id ORDER BY dependson"); while (MoreSQLData()) { push(@dependslist, FetchOneColumn()); } $values{'dependson'} = join(",", @dependslist); my @blockedlist; SendSQL("SELECT blocked FROM dependencies WHERE dependson = $id ORDER BY blocked"); while (MoreSQLData()) { push(@blockedlist, FetchOneColumn()); } $values{'blocked'} = join(",", @blockedlist); my @diffs; SendSQL("SELECT profiles.login_name, fielddefs.description, " . " bug_when, removed, added, attach_id, fielddefs.name " . "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 $diffheader = ""; my @diffparts; my $lastwho = ""; foreach my $ref (@diffs) { my ($who, $what, $when, $old, $new, $attachid, $fieldname) = (@$ref); my $diffpart = {}; if ($who ne $lastwho) { $lastwho = $who; $diffheader = "\n$who" . Param('emailsuffix') . " changed:\n\n"; $diffheader .= FormatTriple("What ", "Removed", "Added"); $diffheader .= ('-' x 76) . "\n"; } $what =~ s/^(Attachment )?/Attachment #$attachid / if $attachid; if( $fieldname eq 'estimated_time' || $fieldname eq 'remaining_time' ) { $old = format_time_decimal($old); $new = format_time_decimal($new); } if ($attachid) { SendSQL("SELECT isprivate FROM attachments WHERE attach_id = $attachid"); $diffpart->{'isprivate'} = FetchOneColumn(); } $difftext = FormatTriple($what, $old, $new); $diffpart->{'header'} = $diffheader; $diffpart->{'fieldname'} = $fieldname; $diffpart->{'text'} = $difftext; push(@diffparts, $diffpart); } my $deptext = ""; SendSQL("SELECT bugs_activity.bug_id, bugs.short_desc, fielddefs.name, " . " removed, added " . "FROM bugs_activity, bugs, dependencies, fielddefs ". "WHERE bugs_activity.bug_id = dependencies.dependson " . " AND bugs.bug_id = bugs_activity.bug_id ". " 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; my $depbug = 0; my @depbugs; while (MoreSQLData()) { my ($summary, $what, $old, $new); ($depbug, $summary, $what, $old, $new) = (FetchSQLData()); if ($depbug ne $lastbug) { if ($interestingchange) { $deptext .= $thisdiff; } $lastbug = $depbug; my $urlbase = Param("urlbase"); $thisdiff = "\nBug $id depends on bug $depbug, which changed state.\n\n" . "Bug $depbug Summary: $summary\n" . "${urlbase}show_bug.cgi?id=$depbug\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; } push(@depbugs, $depbug); } if ($interestingchange) { $deptext .= $thisdiff; } $deptext = trim($deptext); if ($deptext) { my $diffpart = {}; $diffpart->{'text'} = "\n" . trim("\n\n" . $deptext); push(@diffparts, $diffpart); } my ($newcomments, $anyprivate) = GetLongDescriptionAsText($id, $start, $end); # # Start of email filtering code # my $count = 0; # Get a list of the reasons a user might receive email about the bug. my @currentEmailAttributes = getEmailAttributes(\%values, \@diffs, $newcomments); 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 = (); my $checkperson = ""; foreach my $person (@emailList) { # don't modify the original so it sends out with the right case # based on who came first. $checkperson = lc($person); if ( !($AlreadySeen{$checkperson}) ) { push(@allEmail,$person); $AlreadySeen{$checkperson}++; } } #print LOG "\nbug $id email sent: " . join(',', @allEmail) . "\n"; @excludedAddresses = filterExcludeList(\@excludedAddresses, \@allEmail); # print LOG "excluded: " . join(',',@excludedAddresses) . "\n\n"; foreach my $person ( @allEmail ) { my @reasons; $count++; push(@reasons, 'AssignedTo') if lsearch(\@assigned_toList, $person) != -1; push(@reasons, 'Reporter') if lsearch(\@reporterList, $person) != -1; push(@reasons, 'QAcontact') if lsearch(\@qa_contactList, $person) != -1; push(@reasons, 'CC') if lsearch(\@ccList, $person) != -1; push(@reasons, 'Voter') if lsearch(\@voterList, $person) != -1; if ( !defined(NewProcessOnePerson($person, $count, \@headerlist, \@reasons, \%values, \%defmailhead, \%fielddescription, \@diffparts, $newcomments, $anyprivate, $start, $id, \@depbugs))) { # 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' WHERE bug_id = $id"); # Filter the exclude list for dupes one last time @excludedAddresses = filterExcludeList(\@excludedAddresses, \@sentlist); return { sent => \@sentlist, excluded => \@excludedAddresses }; } # 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 (lc($included) eq lc($excluded)) { pop(@result); last; } } } # only need one entry per person my $checkperson = ""; foreach my $person (@result) { $checkperson = lc($person); if ( !($alreadySeen{$checkperson}) ) { push(@uniqueResult,$person); $alreadySeen{$checkperson}++; } } return @uniqueResult; } # if the Status was changed to Resolved or Verified # set the Resolved flag # # else if Severity, Status, Target Milestone 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 ($bug, $fieldDiffs, $commentField) = @_; my (@flags,@uniqueFlags,%alreadySeen) = (); # Add a flag if the status of the bug is "unconfirmed". if ($bug->{'bug_status'} eq 'UNCONFIRMED') { push (@flags, 'Unconfirmed') }; foreach my $ref (@$fieldDiffs) { my ($who, $fieldName, $when, $old, $new) = (@$ref); #print qq{field: $fieldName $new
}; # the STATUS will be flagged for Severity, Status, Target Milestone and # Priority changes # if ( $fieldName eq 'Status' && ($new eq 'RESOLVED' || $new eq 'VERIFIED')) { push (@flags, 'Resolved'); } elsif ( $fieldName eq 'Severity' || $fieldName eq 'Status' || $fieldName eq 'Priority' || $fieldName eq 'Target Milestone') { push (@flags, 'Status'); } elsif ( $fieldName eq 'Keywords') { push (@flags, 'Keywords'); } elsif ( $fieldName eq 'CC') { push (@flags, 'CC'); } # These next few lines find out who has been added # to the Owner, QA, CC, etc. fields. They do not affect # the @flags array at all, but are run here because they # affect filtering later and we're already in the loop. if ($fieldName eq 'AssignedTo') { push (@{$force{'Owner'}}, $new); } elsif ($fieldName eq 'QAcontact') { push (@{$force{'QAcontact'}}, $new); } elsif ($fieldName eq 'CC') { my @added = split (/[ ,]/, $new); 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 ($$$) { # This function figures out who should receive email about the bug # based on the user's role with respect to the bug (assignee, reporter, # etc.), the changes that occurred (new comments, attachment added, # status changed, etc.) and the user's email preferences. # Returns a filtered list of those users who would receive email # about these changes, and adds the names of those users who would # not receive email about them to the global @excludedAddresses list. my ($role, $reasons, $users) = @_; # Make a list of users to filter. my @users = split( /,/ , $users ); # Treat users who are in the process of being removed from this role # as if they still have it. push @users, @{$force{$role}}; # If this installation supports user watching, add to the list those # users who are watching other users already on the list. By doing # this here, we allow watchers to inherit the roles of the people # they are watching while at the same time filtering email for watchers # based on their own preferences. if (Param("supportwatchers") && scalar(@users)) { # Convert the unfiltered list of users into an SQL-quoted, # comma-separated string suitable for use in an SQL query. my $watched = join(",", map(SqlQuote($_), @users)); SendSQL("SELECT watchers.login_name FROM watch, profiles AS watchers, profiles AS watched WHERE watched.login_name IN ($watched) AND watched.userid = watch.watched AND watch.watcher = watchers.userid"); push (@users, FetchOneColumn()) while MoreSQLData(); } # Initialize the list of recipients. my @recipients = (); USER: foreach my $user (@users) { next unless $user; # Get the user's unique ID, and if the user is not registered # (no idea why unregistered users should even be on this list, # but the code that was here before I re-wrote it allows this), # then we do not have any preferences for them, so assume the # default preference is to receive all mail. my $userid = login_to_id($user); if (!$userid) { push(@recipients, $user); next; } # Get the user's email preferences from the database. SendSQL("SELECT emailflags FROM profiles WHERE userid = $userid"); my $prefs = FetchOneColumn(); # Write the user's preferences into a Perl record indexed by # preference name. We pass the value "255" to the split function # because otherwise split will trim trailing null fields, causing # Perl to generate lots of warnings. Any suitably large number # would do. my %prefs = split(/~/, $prefs, 255); # If this user is the one who made the change in the first place, # and they prefer not to receive mail about their own changes, # filter them from the list. if (lc($user) eq lc($nametoexclude) && $prefs{'ExcludeSelf'} eq 'on') { push(@excludedAddresses, $user); next; } # If the user doesn't want to receive email about unconfirmed # bugs, that setting overrides their other preferences, including # the preference to receive email when they are added to or removed # from a role, so remove them from the list before checking their # other preferences. if (grep(/Unconfirmed/, @$reasons) && exists($prefs{"email${role}Unconfirmed"}) && $prefs{"email${role}Unconfirmed"} eq '') { push(@excludedAddresses, $user); next; } # If the user was added to or removed from this role, and they # prefer to receive email when that happens, send them mail. # Note: This was originally written to send email when users # were removed from roles and was later enhanced for additions, # but for simplicity's sake the name "Removeme" was retained. if (grep($_ eq $user, @{$force{$role}}) && $prefs{"email${role}Removeme"} eq 'on') { push (@recipients, $user); next; } # If the user prefers to be included in mail about this change, # add them to the list of recipients. foreach my $reason (@$reasons) { my $pref = "email$role$reason"; if (!exists($prefs{$pref}) || $prefs{$pref} eq 'on') { push(@recipients, $user); next USER; } } # At this point there's no way the user wants to receive email # about this change, so exclude them from the list of recipients. push(@excludedAddresses, $user); } # for each user on the unfiltered list return @recipients; } sub NewProcessOnePerson ($$$$$$$$$$$$$) { my ($person, $count, $hlRef, $reasonsRef, $valueRef, $dmhRef, $fdRef, $diffRef, $newcomments, $anyprivate, $start, $id, $depbugsRef) = @_; my %values = %$valueRef; my @headerlist = @$hlRef; my @reasons = @$reasonsRef; my %defmailhead = %$dmhRef; my %fielddescription = %$fdRef; my @diffparts = @$diffRef; my @depbugs = @$depbugsRef; if ($seen{$person}) { return; } if ($nomail{$person}) { return; } # This routine should really get passed a userid # This rederives groups as a side effect my $user = Bugzilla::User->new_from_login($person); if (!$user) { # person doesn't exist, probably changed email return; } my $userid = $user->id; $seen{$person} = 1; # if this person doesn't have permission to see info on this bug, # return. # # XXX - 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. # return unless $user->can_see_bug($id); # Drop any non-insiders if the comment is private return if (Param("insidergroup") && ($anyprivate != 0) && (!$user->groups->{Param("insidergroup")})); # We shouldn't send changedmail if this is a dependency mail, and any of # the depending bugs are not visible to the user. foreach my $dep_id (@depbugs) { my $save_id = $dep_id; detaint_natural($dep_id) || warn("Unexpected Error: \@depbugs contains a non-numeric value: '$save_id'") && return; return unless $user->can_see_bug($dep_id); } my %mailhead = %defmailhead; my $head = ""; foreach my $f (@headerlist) { if ($mailhead{$f}) { my $value = $values{$f}; # If there isn't anything to show, don't include this header if (! $value) { next; } # Only send estimated_time if it is enabled and the user is in the group if (($f ne 'estimated_time' && $f ne 'deadline') || $user->groups->{Param('timetrackinggroup')}) { my $desc = $fielddescription{$f}; $head .= FormatDouble($desc, $value); } } } # Build difftext (the actions) by verifying the user should see them my $difftext = ""; my $diffheader = ""; my $add_diff; foreach my $diff (@diffparts) { $add_diff = 0; if (exists($diff->{'fieldname'}) && ($diff->{'fieldname'} eq 'estimated_time' || $diff->{'fieldname'} eq 'remaining_time' || $diff->{'fieldname'} eq 'work_time' || $diff->{'fieldname'} eq 'deadline')){ if ($user->groups->{Param("timetrackinggroup")}) { $add_diff = 1; } } elsif (($diff->{'isprivate'}) && Param('insidergroup') && !($user->groups->{Param('insidergroup')}) ) { $add_diff = 0; } else { $add_diff = 1; } if ($add_diff) { if (exists($diff->{'header'}) && ($diffheader ne $diff->{'header'})) { $diffheader = $diff->{'header'}; $difftext .= $diffheader; } $difftext .= $diff->{'text'}; } } if ($difftext eq "" && $newcomments eq "") { # Whoops, no differences! return; } my $reasonsbody = "------- You are receiving this mail because: -------\n"; if (scalar(@reasons) == 0) { $reasonsbody .= "Whoops! I have no idea!\n"; } else { foreach my $reason (@reasons) { if ($reason eq 'AssignedTo') { $reasonsbody .= "You are the assignee for the bug, or are watching the assignee.\n"; } elsif ($reason eq 'Reporter') { $reasonsbody .= "You reported the bug, or are watching the reporter.\n"; } elsif ($reason eq 'QAcontact') { $reasonsbody .= "You are the QA contact for the bug, or are watching the QA contact.\n"; } elsif ($reason eq 'CC') { $reasonsbody .= "You are on the CC list for the bug, or are watching someone who is.\n"; } elsif ($reason eq 'Voter') { $reasonsbody .= "You are a voter for the bug, or are watching someone who is.\n"; } else { $reasonsbody .= "Whoops! There is an unknown reason!\n"; } } } my $isnew = ($start !~ m/[1-9]/); my %substs; # If an attachment was created, then add an URL. (Note: the 'g'lobal # replace should work with comments with multiple attachments.) if ( $newcomments =~ /Created an attachment \(/ ) { my $showattachurlbase = Param('urlbase') . "attachment.cgi?id="; $newcomments =~ s/(Created an attachment \(id=([0-9]+)\))/$1\n --> \(${showattachurlbase}$2&action=view\)/g; } $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{"product"} = $values{'product'}; $substs{"component"} = $values{'component'}; $substs{"summary"} = $values{'short_desc'}; $substs{"reasonsheader"} = join(" ", @reasons); $substs{"reasonsbody"} = $reasonsbody; $substs{"space"} = " "; if ($isnew) { $substs{'threadingmarker'} = "Message-ID: "; } else { $substs{'threadingmarker'} = "In-Reply-To: "; } my $template = Param("newchangedmail"); my $msg = PerformSubsts($template, \%substs); MessageToMTA($msg); push(@sentlist, $person); return 1; } sub MessageToMTA ($) { my ($msg) = (@_); return unless Param('enable_mail_sending'); my @args; if (Param("maildeliverymethod") eq "sendmail" && !Param("sendmailnow")) { push @args, "-ODeliveryMode=deferred"; } if (Param("maildeliverymethod") eq "smtp") { push @args, Server => Param("smtpserver"); } my $mailer = new Mail::Mailer Param("maildeliverymethod"), @args; if (Param("maildeliverymethod") eq "testfile") { $Mail::Mailer::testfile::config{outfile} = "$datadir/mailer.testfile"; } $msg =~ /(.*?)\n\n(.*)/ms; my @header_lines = split(/\n/, $1); my $body = $2; my $headers = new Mail::Header \@header_lines, Modify => 0; $mailer->open($headers->header_hashref); print $mailer $body; $mailer->close; } # Performs substitutions for sending out email with variables in it, # or for inserting a parameter into some other string. # # Takes a string and a reference to a hash containing substitution # variables and their values. # # If the hash is not specified, or if we need to substitute something # that's not in the hash, then we will use parameters to do the # substitution instead. # # Substitutions are always enclosed with '%' symbols. So they look like: # %some_variable_name%. If "some_variable_name" is a key in the hash, then # its value will be placed into the string. If it's not a key in the hash, # then the value of the parameter called "some_variable_name" will be placed # into the string. sub PerformSubsts ($;$) { my ($str, $substs) = (@_); $str =~ s/%([a-z]*)%/(defined $substs->{$1} ? $substs->{$1} : Param($1))/eg; return $str; } 1;