Putting 4.2.0 on top of 4.0.17
[usit-rt.git] / lib / RT / Action / SendEmail.pm
index 0ff7e6d..5f02e43 100644 (file)
@@ -135,13 +135,15 @@ Builds an outgoing email we're going to send using scrip's template.
 sub Prepare {
     my $self = shift;
 
-    my ( $result, $message ) = $self->TemplateObj->Parse(
-        Argument       => $self->Argument,
-        TicketObj      => $self->TicketObj,
-        TransactionObj => $self->TransactionObj
-    );
-    if ( !$result ) {
-        return (undef);
+    unless ( $self->TemplateObj->MIMEObj ) {
+        my ( $result, $message ) = $self->TemplateObj->Parse(
+            Argument       => $self->Argument,
+            TicketObj      => $self->TicketObj,
+            TransactionObj => $self->TransactionObj
+        );
+        if ( !$result ) {
+            return (undef);
+        }
     }
 
     my $MIMEObj = $self->TemplateObj->MIMEObj;
@@ -179,12 +181,6 @@ sub Prepare {
             && !$MIMEObj->head->get('To')
             && ( $MIMEObj->head->get('Cc') or $MIMEObj->head->get('Bcc') );
 
-    # We should never have to set the MIME-Version header
-    $self->SetHeader( 'MIME-Version', '1.0' );
-
-    # fsck.com #5959: Since RT sends 8bit mail, we should say so.
-    $self->SetHeader( 'Content-Transfer-Encoding', '8bit' );
-
     # For security reasons, we only send out textual mails.
     foreach my $part ( grep !$_->is_multipart, $MIMEObj->parts_DFS ) {
         my $type = $part->mime_type || 'text/plain';
@@ -195,9 +191,12 @@ sub Prepare {
         $part->head->mime_attr( "Content-Type.charset" => 'utf-8' );
     }
 
-    RT::I18N::SetMIMEEntityToEncoding( $MIMEObj,
-        RT->Config->Get('EmailOutputEncoding'),
-        'mime_words_ok', );
+    RT::I18N::SetMIMEEntityToEncoding(
+        Entity        => $MIMEObj,
+        Encoding      => RT->Config->Get('EmailOutputEncoding'),
+        PreserveWords => 1,
+        IsOut         => 1,
+    );
 
     # Build up a MIME::Entity that looks like the original message.
     $self->AddAttachments if ( $MIMEObj->head->get('RT-Attach-Message')
@@ -218,7 +217,7 @@ sub Prepare {
             'Success';
     }
 
-    return $result;
+    return 1;
 }
 
 =head2 To
@@ -407,6 +406,7 @@ sub AddAttachment {
         Data        => $attach->OriginalContent,
         Disposition => $disp,
         Filename    => $self->MIMEEncodeString( $attach->Filename ),
+        Id          => $attach->GetHeader('Content-ID'),
         'RT-Attachment:' => $self->TicketObj->Id . "/"
             . $self->TransactionObj->Id . "/"
             . $attach->id,
@@ -601,16 +601,10 @@ sub SetRTSpecialHeaders {
         }
     }
 
-    if (my $precedence = RT->Config->Get('DefaultMailPrecedence')
-        and !$self->TemplateObj->MIMEObj->head->get("Precedence")
-    ) {
-        $self->SetHeader( 'Precedence', $precedence );
-    }
-
     $self->SetHeader( 'X-RT-Loop-Prevention', RT->Config->Get('rtname') );
-    $self->SetHeader( 'RT-Ticket',
+    $self->SetHeader( 'X-RT-Ticket',
         RT->Config->Get('rtname') . " #" . $self->TicketObj->id() );
-    $self->SetHeader( 'Managed-by',
+    $self->SetHeader( 'X-Managed-by',
         "RT $RT::VERSION (http://www.bestpractical.com/rt/)" );
 
 # XXX, TODO: use /ShowUser/ShowUserEntry(or something like that) when it would be
@@ -618,7 +612,7 @@ sub SetRTSpecialHeaders {
     if ( my $email = $self->TransactionObj->CreatorObj->EmailAddress
          and RT->Config->Get('UseOriginatorHeader')
     ) {
-        $self->SetHeader( 'RT-Originator', $email );
+        $self->SetHeader( 'X-RT-Originator', $email );
     }
 
 }
@@ -738,15 +732,29 @@ Remove addresses that are RT addresses or that are on this transaction's blackli
 
 =cut
 
+my %squelch_reasons = (
+    'not privileged'
+        => "because autogenerated messages are configured to only be sent to privileged users (RedistributeAutoGeneratedMessages)",
+    'squelch:attachment'
+        => "by RT-Squelch-Replies-To header in the incoming message",
+    'squelch:transaction'
+        => "by notification checkboxes for this transaction",
+    'squelch:ticket'
+        => "by notification checkboxes on this ticket's People page",
+);
+
+
 sub RemoveInappropriateRecipients {
     my $self = shift;
 
-    my @blacklist = ();
+    my %blacklist = ();
 
     # If there are no recipients, don't try to send the message.
     # If the transaction has content and has the header RT-Squelch-Replies-To
 
     my $msgid = $self->TemplateObj->MIMEObj->head->get('Message-Id');
+    chomp $msgid;
+
     if ( my $attachment = $self->TransactionObj->Attachments->First ) {
 
         if ( $attachment->GetHeader('RT-DetectedAutoGenerated') ) {
@@ -755,7 +763,9 @@ sub RemoveInappropriateRecipients {
             # caused by one of the watcher addresses being broken.
             # Default ("true") is to redistribute, for historical reasons.
 
-            if ( !RT->Config->Get('RedistributeAutoGeneratedMessages') ) {
+            my $redistribute = RT->Config->Get('RedistributeAutoGeneratedMessages');
+
+            if ( !$redistribute ) {
 
                 # Don't send to any watchers.
                 @{ $self->{$_} } = () for (@EMAIL_RECIPIENT_HEADERS);
@@ -763,16 +773,15 @@ sub RemoveInappropriateRecipients {
                         . " The incoming message was autogenerated. "
                         . "Not redistributing this message based on site configuration."
                 );
-            } elsif ( RT->Config->Get('RedistributeAutoGeneratedMessages') eq
-                'privileged' )
-            {
+            } elsif ( $redistribute eq 'privileged' ) {
 
                 # Only send to "privileged" watchers.
                 foreach my $type (@EMAIL_RECIPIENT_HEADERS) {
                     foreach my $addr ( @{ $self->{$type} } ) {
                         my $user = RT::User->new(RT->SystemUser);
                         $user->LoadByEmail($addr);
-                        push @blacklist, $addr unless $user->id && $user->Privileged;
+                        $blacklist{ $addr } ||= 'not privileged'
+                            unless $user->id && $user->Privileged;
                     }
                 }
                 $RT::Logger->info( $msgid
@@ -783,48 +792,88 @@ sub RemoveInappropriateRecipients {
         }
 
         if ( my $squelch = $attachment->GetHeader('RT-Squelch-Replies-To') ) {
-            push @blacklist, split( /,/, $squelch );
+            $blacklist{ $_->address } ||= 'squelch:attachment'
+                foreach Email::Address->parse( $squelch );
         }
     }
 
-    # Let's grab the SquelchMailTo attributes and push those entries into the @blacklisted
-    push @blacklist, map $_->Content, $self->TicketObj->SquelchMailTo, $self->TransactionObj->SquelchMailTo;
+    # Let's grab the SquelchMailTo attributes and push those entries
+    # into the blacklisted
+    $blacklist{ $_->Content } ||= 'squelch:transaction'
+        foreach $self->TransactionObj->SquelchMailTo;
+    $blacklist{ $_->Content } ||= 'squelch:ticket'
+        foreach $self->TicketObj->SquelchMailTo;
+
+    # canonicalize emails
+    foreach my $address ( keys %blacklist ) {
+        my $reason = delete $blacklist{ $address };
+        $blacklist{ lc $_ } = $reason
+            foreach map RT::User->CanonicalizeEmailAddress( $_->address ),
+            Email::Address->parse( $address );
+    }
 
-    # Cycle through the people we're sending to and pull out anyone on the
-    # system blacklist
+    $self->RecipientFilter(
+        Callback => sub {
+            return unless RT::EmailParser->IsRTAddress( $_[0] );
+            return "$_[0] appears to point to this RT instance. Skipping";
+        },
+        All => 1,
+    );
 
-    # Trim leading and trailing spaces. 
-    @blacklist = map { RT::User->CanonicalizeEmailAddress( $_->address ) }
-        Email::Address->parse( join ', ', grep defined, @blacklist );
+    $self->RecipientFilter(
+        Callback => sub {
+            return unless $blacklist{ lc $_[0] };
+            return "$_[0] is blacklisted $squelch_reasons{ $blacklist{ lc $_[0] } }. Skipping";
+        },
+    );
 
-    foreach my $type (@EMAIL_RECIPIENT_HEADERS) {
+
+    # Cycle through the people we're sending to and pull out anyone that meets any of the callbacks
+    for my $type (@EMAIL_RECIPIENT_HEADERS) {
         my @addrs;
-        foreach my $addr ( @{ $self->{$type} } ) {
 
-         # Weed out any RT addresses. We really don't want to talk to ourselves!
-         # If we get a reply back, that means it's not an RT address
-            if ( !RT::EmailParser->CullRTAddresses($addr) ) {
-                $RT::Logger->info( $msgid . "$addr appears to point to this RT instance. Skipping" );
-                next;
-            }
-            if ( grep $addr eq $_, @blacklist ) {
-                $RT::Logger->info( $msgid . "$addr was blacklisted for outbound mail on this transaction. Skipping");
-                next;
+      ADDRESS:
+        for my $addr ( @{ $self->{$type} } ) {
+            for my $filter ( map {$_->{Callback}} @{$self->{RecipientFilter}} ) {
+                my $skip = $filter->($addr);
+                next unless $skip;
+                $RT::Logger->info( "$msgid $skip" );
+                next ADDRESS;
             }
             push @addrs, $addr;
         }
-        foreach my $addr ( @{ $self->{'NoSquelch'}{$type} || [] } ) {
-            # never send email to itself
-            if ( !RT::EmailParser->CullRTAddresses($addr) ) {
-                $RT::Logger->info( $msgid . "$addr appears to point to this RT instance. Skipping" );
-                next;
+
+      NOSQUELCH_ADDRESS:
+        for my $addr ( @{ $self->{NoSquelch}{$type} } ) {
+            for my $filter ( map {$_->{Callback}} grep {$_->{All}} @{$self->{RecipientFilter}} ) {
+                my $skip = $filter->($addr);
+                next unless $skip;
+                $RT::Logger->info( "$msgid $skip" );
+                next NOSQUELCH_ADDRESS;
             }
             push @addrs, $addr;
         }
+
         @{ $self->{$type} } = @addrs;
     }
 }
 
+=head2 RecipientFilter Callback => SUB, [All => 1]
+
+Registers a filter to be applied to addresses by
+L<RemoveInappropriateRecipients>.  The C<Callback> will be called with
+one address at a time, and should return false if the address should
+receive mail, or a message explaining why it should not be.  Passing a
+true value for C<All> will cause the filter to also be applied to
+NoSquelch (one-time Cc and Bcc) recipients as well.
+
+=cut
+
+sub RecipientFilter {
+    my $self = shift;
+    push @{ $self->{RecipientFilter}}, {@_};
+}
+
 =head2 SetReturnAddress is_comment => BOOLEAN
 
 Calculate and set From and Reply-To headers based on the is_comment flag.
@@ -972,7 +1021,7 @@ sub SetSubject {
 
     $subject =~ s/(\r\n|\n|\s)/ /g;
 
-    $self->SetHeader( 'Subject', $subject );
+    $self->SetHeader( 'Subject', Encode::encode_utf8( $subject ) );
 
 }
 
@@ -986,11 +1035,14 @@ sub SetSubjectToken {
     my $self = shift;
 
     my $head = $self->TemplateObj->MIMEObj->head;
-    $head->replace(
-        Subject => RT::Interface::Email::AddSubjectTag(
-            Encode::decode_utf8( $head->get('Subject') ),
-            $self->TicketObj,
-        ),
+    $self->SetHeader(
+        Subject =>
+            Encode::encode_utf8(
+                RT::Interface::Email::AddSubjectTag(
+                    Encode::decode_utf8( $head->get('Subject') ),
+                    $self->TicketObj,
+                ),
+            ),
     );
 }
 
@@ -1072,13 +1124,8 @@ Returns a fake Message-ID: header for the ticket to allow a base level of thread
 =cut
 
 sub PseudoReference {
-
     my $self = shift;
-    my $pseudo_ref
-        = '<RT-Ticket-'
-        . $self->TicketObj->id . '@'
-        . RT->Config->Get('Organization') . '>';
-    return $pseudo_ref;
+    return RT::Interface::Email::PseudoReference( $self->TicketObj );
 }
 
 =head2 SetHeaderAsEncoding($field_name, $charset_encoding)
@@ -1093,11 +1140,6 @@ sub SetHeaderAsEncoding {
 
     my $head = $self->TemplateObj->MIMEObj->head;
 
-    if ( lc($field) eq 'from' and RT->Config->Get('SMTPFrom') ) {
-        $head->replace( $field, RT->Config->Get('SMTPFrom') );
-        return;
-    }
-
     my $value = $head->get( $field );
     $value = $self->MIMEEncodeString( $value, $enc );
     $head->replace( $field, $value );