1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
6 # <jesse@bestpractical.com>
8 # (Except where explicitly superseded by other copyright notices)
13 # This work is made available to you under the terms of Version 2 of
14 # the GNU General Public License. A copy of that license should have
15 # been provided with this software, but in any event can be snarfed
18 # This work is distributed in the hope that it will be useful, but
19 # WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 # General Public License for more details.
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26 # 02110-1301 or visit their web page on the internet at
27 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
30 # CONTRIBUTION SUBMISSION POLICY:
32 # (The following paragraph is not intended to limit the rights granted
33 # to you to modify and distribute this software under the terms of
34 # the GNU General Public License and is only of importance to you if
35 # you choose to contribute your changes and enhancements to the
36 # community by submitting them to Best Practical Solutions, LLC.)
38 # By intentionally submitting any modifications, corrections or
39 # derivatives to this work, or any other work intended for use with
40 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
41 # you are the copyright holder for those contributions and you grant
42 # Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
43 # royalty-free, perpetual, license to use, copy, create derivative
44 # works based on those contributions, and sublicense and distribute
45 # those contributions and any derivatives thereof.
47 # END BPS TAGGED BLOCK }}}
49 # Portions Copyright 2000 Tobias Brox <tobix@cpan.org>
51 package RT::Action::SendEmail;
56 use base qw(RT::Action);
59 use RT::Interface::Email;
61 our @EMAIL_RECIPIENT_HEADERS = qw(To Cc Bcc);
66 RT::Action::SendEmail - An Action which users can use to send mail
67 or can subclassed for more specialized mail sending behavior.
68 RT::Action::AutoReply is a good example subclass.
72 use base 'RT::Action::SendEmail';
76 Basically, you create another module RT::Action::YourAction which ISA
77 RT::Action::SendEmail.
83 Cleans class-wide options, like L</SquelchMailTo> or L</AttachTickets>.
89 $self->SquelchMailTo(undef);
90 $self->AttachTickets(undef);
95 Sends the prepared message and writes outgoing record into DB if the feature is
96 activated in the config.
103 $self->DeferDigestRecipients() if RT->Config->Get('RecordOutgoingEmail');
104 my $message = $self->TemplateObj->MIMEObj;
107 if ( RT->Config->Get('RecordOutgoingEmail')
108 && RT->Config->Get('GnuPG')->{'Enable'} )
111 # it's hacky, but we should know if we're going to crypt things
112 my $attachment = $self->TransactionObj->Attachments->First;
115 foreach my $argument (qw(Sign Encrypt)) {
117 && defined $attachment->GetHeader("X-RT-$argument") )
119 $crypt{$argument} = $attachment->GetHeader("X-RT-$argument");
121 $crypt{$argument} = $self->TicketObj->QueueObj->$argument();
124 if ( $crypt{'Sign'} || $crypt{'Encrypt'} ) {
125 $orig_message = $message->dup;
129 my ($ret) = $self->SendMessage($message);
130 if ( $ret > 0 && RT->Config->Get('RecordOutgoingEmail') ) {
133 Type => 'application/x-rt-original-message',
134 Disposition => 'inline',
135 Data => $orig_message->as_string,
138 $self->RecordOutgoingMailTransaction($message);
139 $self->RecordDeferredRecipients();
148 Builds an outgoing email we're going to send using scrip's template.
155 my ( $result, $message ) = $self->TemplateObj->Parse(
156 Argument => $self->Argument,
157 TicketObj => $self->TicketObj,
158 TransactionObj => $self->TransactionObj
164 my $MIMEObj = $self->TemplateObj->MIMEObj;
167 $self->SetRTSpecialHeaders();
169 $self->RemoveInappropriateRecipients();
172 foreach my $type (@EMAIL_RECIPIENT_HEADERS) {
174 = grep defined && length && !$seen{ lc $_ }++,
178 # Go add all the Tos, Ccs and Bccs that we need to to the message to
179 # make it happy, but only if we actually have values in those arrays.
181 # TODO: We should be pulling the recipients out of the template and shove them into To, Cc and Bcc
183 for my $header (@EMAIL_RECIPIENT_HEADERS) {
185 $self->SetHeader( $header, join( ', ', @{ $self->{$header} } ) )
186 if ( !$MIMEObj->head->get($header)
188 && @{ $self->{$header} } );
190 # PseudoTo (fake to headers) shouldn't get matched for message recipients.
191 # If we don't have any 'To' header (but do have other recipients), drop in
192 # the pseudo-to header.
193 $self->SetHeader( 'To', join( ', ', @{ $self->{'PseudoTo'} } ) )
194 if $self->{'PseudoTo'}
195 && @{ $self->{'PseudoTo'} }
196 && !$MIMEObj->head->get('To')
197 && ( $MIMEObj->head->get('Cc') or $MIMEObj->head->get('Bcc') );
199 # We should never have to set the MIME-Version header
200 $self->SetHeader( 'MIME-Version', '1.0' );
202 # fsck.com #5959: Since RT sends 8bit mail, we should say so.
203 $self->SetHeader( 'Content-Transfer-Encoding', '8bit' );
205 # For security reasons, we only send out textual mails.
206 foreach my $part ( grep !$_->is_multipart, $MIMEObj->parts_DFS ) {
207 my $type = $part->mime_type || 'text/plain';
208 $type = 'text/plain' unless RT::I18N::IsTextualContentType($type);
209 $part->head->mime_attr( "Content-Type" => $type );
210 $part->head->mime_attr( "Content-Type.charset" => 'utf-8' );
213 RT::I18N::SetMIMEEntityToEncoding( $MIMEObj,
214 RT->Config->Get('EmailOutputEncoding'),
217 # Build up a MIME::Entity that looks like the original message.
218 $self->AddAttachments if ( $MIMEObj->head->get('RT-Attach-Message')
219 && ( $MIMEObj->head->get('RT-Attach-Message') !~ /^(n|no|0|off|false)$/i ) );
223 my $attachment = $self->TransactionObj->Attachments->First;
226 $attachment->GetHeader('X-RT-Encrypt')
227 || $self->TicketObj->QueueObj->Encrypt
231 $attachment->SetHeader( 'X-RT-Encrypt' => 1 )
232 if ( $attachment->GetHeader("X-RT-Incoming-Encryption") || '' ) eq
241 Returns an array of L<Email::Address> objects containing all the To: recipients for this notification
247 return ( $self->AddressesFromHeader('To') );
252 Returns an array of L<Email::Address> objects containing all the Cc: recipients for this notification
258 return ( $self->AddressesFromHeader('Cc') );
263 Returns an array of L<Email::Address> objects containing all the Bcc: recipients for this notification
269 return ( $self->AddressesFromHeader('Bcc') );
273 sub AddressesFromHeader {
276 my $header = $self->TemplateObj->MIMEObj->head->get($field);
277 my @addresses = Email::Address->parse($header);
282 =head2 SendMessage MIMEObj
284 sends the message using RT's preferred API.
285 TODO: Break this out to a separate module
291 # DO NOT SHIFT @_ in this subroutine. It breaks Hook::LexWrap's
292 # ability to pass @_ to a 'post' routine.
293 my ( $self, $MIMEObj ) = @_;
295 my $msgid = $MIMEObj->head->get('Message-ID');
298 $self->ScripActionObj->{_Message_ID}++;
300 $RT::Logger->info( $msgid . " #"
301 . $self->TicketObj->id . "/"
302 . $self->TransactionObj->id
304 . ($self->ScripObj->id || '#rule'). " "
305 . ( $self->ScripObj->Description || '' ) );
307 my $status = RT::Interface::Email::SendEmail(
309 Ticket => $self->TicketObj,
310 Transaction => $self->TransactionObj,
314 return $status unless ($status > 0 || exists $self->{'Deferred'});
316 my $success = $msgid . " sent ";
317 foreach (@EMAIL_RECIPIENT_HEADERS) {
318 my $recipients = $MIMEObj->head->get($_);
319 $success .= " $_: " . $recipients if $recipients;
322 if( exists $self->{'Deferred'} ) {
323 for (qw(daily weekly susp)) {
324 $success .= "\nBatched email $_ for: ". join(", ", keys %{ $self->{'Deferred'}{ $_ } } )
325 if exists $self->{'Deferred'}{ $_ };
331 $RT::Logger->info($success);
336 =head2 AddAttachments
338 Takes any attachments to this transaction and attaches them to the message
346 my $MIMEObj = $self->TemplateObj->MIMEObj;
348 $MIMEObj->head->delete('RT-Attach-Message');
350 my $attachments = RT::Attachments->new($RT::SystemUser);
352 FIELD => 'TransactionId',
353 VALUE => $self->TransactionObj->Id
356 # Don't attach anything blank
357 $attachments->LimitNotEmpty;
358 $attachments->OrderBy( FIELD => 'id' );
360 # We want to make sure that we don't include the attachment that's
361 # being used as the "Content" of this message" unless that attachment's
362 # content type is not like text/...
363 my $transaction_content_obj = $self->TransactionObj->ContentObj;
365 if ( $transaction_content_obj
366 && $transaction_content_obj->ContentType =~ m{text/}i )
368 # If this was part of a multipart/alternative, skip all of the kids
369 my $parent = $transaction_content_obj->ParentObj;
370 if ($parent and $parent->Id and $parent->ContentType eq "multipart/alternative") {
372 ENTRYAGGREGATOR => 'AND',
375 VALUE => $parent->Id,
379 ENTRYAGGREGATOR => 'AND',
382 VALUE => $transaction_content_obj->Id,
387 # attach any of this transaction's attachments
388 my $seen_attachment = 0;
389 while ( my $attach = $attachments->Next ) {
390 if ( !$seen_attachment ) {
391 $MIMEObj->make_multipart( 'mixed', Force => 1 );
392 $seen_attachment = 1;
394 $self->AddAttachment($attach);
398 =head2 AddAttachment $attachment
400 Takes one attachment object of L<RT::Attachmment> class and attaches it to the message
408 my $MIMEObj = shift || $self->TemplateObj->MIMEObj;
411 Type => $attach->ContentType,
412 Charset => $attach->OriginalEncoding,
413 Data => $attach->OriginalContent,
414 Filename => $self->MIMEEncodeString( $attach->Filename ),
415 'RT-Attachment:' => $self->TicketObj->Id . "/"
416 . $self->TransactionObj->Id . "/"
418 Encoding => '-SUGGEST',
422 =head2 AttachTickets [@IDs]
424 Returns or set list of ticket's IDs that should be attached to an outgoing message.
426 B<Note> this method works as a class method and setup things global, so you have to
427 clean list by passing undef as argument.
436 $list = [ grep defined, @_ ] if @_;
443 Attaches tickets to the current message, list of tickets' ids get from
444 L</AttachTickets> method.
450 $self->AddTicket($_) foreach $self->AttachTickets;
456 Attaches a ticket with ID to the message.
458 Each ticket is attached as multipart entity and all its messages and attachments
459 are attached as sub entities in order of creation, but only if transaction type
460 is Create or Correspond.
468 # XXX: we need a current user here, but who is current user?
469 my $attachs = RT::Attachments->new($RT::SystemUser);
470 my $txn_alias = $attachs->TransactionAlias;
471 $attachs->Limit( ALIAS => $txn_alias, FIELD => 'Type', VALUE => 'Create' );
475 VALUE => 'Correspond'
477 $attachs->LimitByTicket($tid);
478 $attachs->LimitNotEmpty;
479 $attachs->OrderBy( FIELD => 'Created' );
481 my $ticket_mime = MIME::Entity->build(
482 Type => 'multipart/mixed',
484 Description => "ticket #$tid",
486 while ( my $attachment = $attachs->Next ) {
487 $self->AddAttachment( $attachment, $ticket_mime );
489 if ( $ticket_mime->parts ) {
490 my $email_mime = $self->TemplateObj->MIMEObj;
491 $email_mime->make_multipart;
492 $email_mime->add_part($ticket_mime);
497 =head2 RecordOutgoingMailTransaction MIMEObj
499 Record a transaction in RT with this outgoing message for future record-keeping purposes
503 sub RecordOutgoingMailTransaction {
507 my @parts = $MIMEObj->parts;
510 foreach my $part (@parts) {
511 my $attach = $part->head->get('RT-Attachment');
514 "We found an attachment. we want to not record it.");
515 push @attachments, $attach;
517 $RT::Logger->debug("We found a part. we want to record it.");
521 $MIMEObj->parts( \@keep );
522 foreach my $attachment (@attachments) {
523 $MIMEObj->head->add( 'RT-Attachment', $attachment );
526 RT::I18N::SetMIMEEntityToEncoding( $MIMEObj, 'utf-8', 'mime_words_ok' );
529 = RT::Transaction->new( $self->TransactionObj->CurrentUser );
531 # XXX: TODO -> Record attachments as references to things in the attachments table, maybe.
534 if ( $self->TransactionObj->Type eq 'Comment' ) {
535 $type = 'CommentEmailRecord';
537 $type = 'EmailRecord';
540 my $msgid = $MIMEObj->head->get('Message-ID');
543 my ( $id, $msg ) = $transaction->Create(
544 Ticket => $self->TicketObj->Id,
552 $self->{'OutgoingMailTransaction'} = $id;
554 $RT::Logger->warning(
555 "Could not record outgoing message transaction: $msg");
560 =head2 SetRTSpecialHeaders
562 This routine adds all the random headers that RT wants in a mail message
563 that don't matter much to anybody else.
567 sub SetRTSpecialHeaders {
571 $self->SetSubjectToken();
572 $self->SetHeaderAsEncoding( 'Subject',
573 RT->Config->Get('EmailOutputEncoding') )
574 if ( RT->Config->Get('EmailOutputEncoding') );
575 $self->SetReturnAddress();
576 $self->SetReferencesHeaders();
578 unless ( $self->TemplateObj->MIMEObj->head->get('Message-ID') ) {
580 # Get Message-ID for this txn
582 if ( my $msg = $self->TransactionObj->Message->First ) {
583 $msgid = $msg->GetHeader("RT-Message-ID")
584 || $msg->GetHeader("Message-ID");
587 # If there is one, and we can parse it, then base our Message-ID on it
590 =~ s/<(rt-.*?-\d+-\d+)\.(\d+)-\d+-\d+\@\QRT->Config->Get('Organization')\E>$/
591 "<$1." . $self->TicketObj->id
592 . "-" . $self->ScripObj->id
593 . "-" . $self->ScripActionObj->{_Message_ID}
594 . "@" . RT->Config->Get('Organization') . ">"/eg
595 and $2 == $self->TicketObj->id
598 $self->SetHeader( "Message-ID" => $msgid );
601 'Message-ID' => RT::Interface::Email::GenMessageId(
602 Ticket => $self->TicketObj,
603 Scrip => $self->ScripObj,
604 ScripAction => $self->ScripActionObj
610 if (my $precedence = RT->Config->Get('DefaultMailPrecedence')
611 and !$self->TemplateObj->MIMEObj->head->get("Precedence")
613 $self->SetHeader( 'Precedence', $precedence );
616 $self->SetHeader( 'X-RT-Loop-Prevention', RT->Config->Get('rtname') );
617 $self->SetHeader( 'RT-Ticket',
618 RT->Config->Get('rtname') . " #" . $self->TicketObj->id() );
619 $self->SetHeader( 'Managed-by',
620 "RT $RT::VERSION (http://www.bestpractical.com/rt/)" );
622 # XXX, TODO: use /ShowUser/ShowUserEntry(or something like that) when it would be
623 # refactored into user's method.
624 if ( my $email = $self->TransactionObj->CreatorObj->EmailAddress
625 and RT->Config->Get('UseOriginatorHeader')
627 $self->SetHeader( 'RT-Originator', $email );
633 sub DeferDigestRecipients {
635 $RT::Logger->debug( "Calling SetRecipientDigests for transaction " . $self->TransactionObj . ", id " . $self->TransactionObj->id );
637 # The digest attribute will be an array of notifications that need to
638 # be sent for this transaction. The array will have the following
639 # format for its objects.
640 # $digest_hash -> {daily|weekly|susp} -> address -> {To|Cc|Bcc}
641 # -> sent -> {true|false}
642 # The "sent" flag will be used by the cron job to indicate that it has
643 # run on this transaction.
644 # In a perfect world we might move this hash construction to the
645 # extension module itself.
646 my $digest_hash = {};
648 foreach my $mailfield (@EMAIL_RECIPIENT_HEADERS) {
649 # If we have a "PseudoTo", the "To" contains it, so we don't need to access it
650 next if ( ( $self->{'PseudoTo'} && @{ $self->{'PseudoTo'} } ) && ( $mailfield eq 'To' ) );
651 $RT::Logger->debug( "Working on mailfield $mailfield; recipients are " . join( ',', @{ $self->{$mailfield} } ) );
653 # Store the 'daily digest' folk in an array.
654 my ( @send_now, @daily_digest, @weekly_digest, @suspended );
656 # Have to get the list of addresses directly from the MIME header
658 $RT::Logger->debug( $self->TemplateObj->MIMEObj->head->as_string );
659 foreach my $rcpt ( map { $_->address } $self->AddressesFromHeader($mailfield) ) {
661 my $user_obj = RT::User->new($RT::SystemUser);
662 $user_obj->LoadByEmail($rcpt);
663 if ( ! $user_obj->id ) {
664 # If there's an email address in here without an associated
665 # RT user, pass it on through.
666 $RT::Logger->debug( "User $rcpt is not associated with an RT user object. Send mail.");
667 push( @send_now, $rcpt );
671 my $mailpref = RT->Config->Get( 'EmailFrequency', $user_obj ) || '';
672 $RT::Logger->debug( "Got user mail preference '$mailpref' for user $rcpt");
674 if ( $mailpref =~ /daily/i ) { push( @daily_digest, $rcpt ) }
675 elsif ( $mailpref =~ /weekly/i ) { push( @weekly_digest, $rcpt ) }
676 elsif ( $mailpref =~ /suspend/i ) { push( @suspended, $rcpt ) }
677 else { push( @send_now, $rcpt ) }
680 # Reset the relevant mail field.
681 $RT::Logger->debug( "Removing deferred recipients from $mailfield: line");
683 $self->SetHeader( $mailfield, join( ', ', @send_now ) );
684 } else { # No recipients! Remove the header.
685 $self->TemplateObj->MIMEObj->head->delete($mailfield);
688 # Push the deferred addresses into the appropriate field in
689 # our attribute hash, with the appropriate mail header.
691 "Setting deferred recipients for attribute creation");
692 $digest_hash->{'daily'}->{$_} = {'header' => $mailfield , _sent => 0} for (@daily_digest);
693 $digest_hash->{'weekly'}->{$_} ={'header' => $mailfield, _sent => 0} for (@weekly_digest);
694 $digest_hash->{'susp'}->{$_} = {'header' => $mailfield, _sent =>0 } for (@suspended);
697 if ( scalar keys %$digest_hash ) {
699 # Save the hash so that we can add it as an attribute to the
700 # outgoing email transaction.
701 $self->{'Deferred'} = $digest_hash;
703 $RT::Logger->debug( "No recipients found for deferred delivery on "
705 . $self->TransactionObj->id );
711 sub RecordDeferredRecipients {
713 return unless exists $self->{'Deferred'};
715 my $txn_id = $self->{'OutgoingMailTransaction'};
716 return unless $txn_id;
718 my $txn_obj = RT::Transaction->new( $self->CurrentUser );
719 $txn_obj->Load( $txn_id );
720 my( $ret, $msg ) = $txn_obj->AddAttribute(
721 Name => 'DeferredRecipients',
722 Content => $self->{'Deferred'}
724 $RT::Logger->warning( "Unable to add deferred recipients to outgoing transaction: $msg" )
730 =head2 SquelchMailTo [@ADDRESSES]
732 Mark ADDRESSES to be removed from list of the recipients. Returns list of the addresses.
733 To empty list pass undefined argument.
735 B<Note> that this method can be called as class method and works globaly. Don't forget to
736 clean this list when blocking is not required anymore, pass undef to do this.
746 $squelch = [ grep defined, @_ ];
752 =head2 RemoveInappropriateRecipients
754 Remove addresses that are RT addresses or that are on this transaction's blacklist
758 sub RemoveInappropriateRecipients {
763 # If there are no recipients, don't try to send the message.
764 # If the transaction has content and has the header RT-Squelch-Replies-To
766 my $msgid = $self->TemplateObj->MIMEObj->head->get('Message-Id');
767 if ( my $attachment = $self->TransactionObj->Attachments->First ) {
769 if ( $attachment->GetHeader('RT-DetectedAutoGenerated') ) {
771 # What do we want to do with this? It's probably (?) a bounce
772 # caused by one of the watcher addresses being broken.
773 # Default ("true") is to redistribute, for historical reasons.
775 if ( !RT->Config->Get('RedistributeAutoGeneratedMessages') ) {
777 # Don't send to any watchers.
778 @{ $self->{$_} } = () for (@EMAIL_RECIPIENT_HEADERS);
779 $RT::Logger->info( $msgid
780 . " The incoming message was autogenerated. "
781 . "Not redistributing this message based on site configuration."
783 } elsif ( RT->Config->Get('RedistributeAutoGeneratedMessages') eq
787 # Only send to "privileged" watchers.
788 foreach my $type (@EMAIL_RECIPIENT_HEADERS) {
789 foreach my $addr ( @{ $self->{$type} } ) {
790 my $user = RT::User->new($RT::SystemUser);
791 $user->LoadByEmail($addr);
792 push @blacklist, $addr if ( !$user->Privileged );
795 $RT::Logger->info( $msgid
796 . " The incoming message was autogenerated. "
797 . "Not redistributing this message to unprivileged users based on site configuration."
802 if ( my $squelch = $attachment->GetHeader('RT-Squelch-Replies-To') ) {
803 push @blacklist, split( /,/, $squelch );
807 # Let's grab the SquelchMailTo attribue and push those entries into the @blacklist
808 push @blacklist, map $_->Content, $self->TicketObj->SquelchMailTo;
809 push @blacklist, $self->SquelchMailTo;
811 # Cycle through the people we're sending to and pull out anyone on the
814 # Trim leading and trailing spaces.
815 @blacklist = map { RT::User->CanonicalizeEmailAddress( $_->address ) } Email::Address->parse(join(', ', grep {defined} @blacklist));
817 foreach my $type (@EMAIL_RECIPIENT_HEADERS) {
819 foreach my $addr ( @{ $self->{$type} } ) {
821 # Weed out any RT addresses. We really don't want to talk to ourselves!
822 # If we get a reply back, that means it's not an RT address
823 if ( !RT::EmailParser->CullRTAddresses($addr) ) {
824 $RT::Logger->info( $msgid . "$addr appears to point to this RT instance. Skipping" );
827 if ( grep /^\Q$addr\E$/, @blacklist ) {
828 $RT::Logger->info( $msgid . "$addr was blacklisted for outbound mail on this transaction. Skipping");
833 @{ $self->{$type} } = @addrs;
837 =head2 SetReturnAddress is_comment => BOOLEAN
839 Calculate and set From and Reply-To headers based on the is_comment flag.
843 sub SetReturnAddress {
848 friendly_name => undef,
853 # $args{is_comment} should be set if the comment address is to be used.
856 if ( $args{'is_comment'} ) {
857 $replyto = $self->TicketObj->QueueObj->CommentAddress
858 || RT->Config->Get('CommentAddress');
860 $replyto = $self->TicketObj->QueueObj->CorrespondAddress
861 || RT->Config->Get('CorrespondAddress');
864 unless ( $self->TemplateObj->MIMEObj->head->get('From') ) {
865 if ( RT->Config->Get('UseFriendlyFromLine') ) {
866 my $friendly_name = $args{friendly_name};
868 unless ( $friendly_name ) {
869 $friendly_name = $self->TransactionObj->CreatorObj->FriendlyName;
870 if ( $friendly_name =~ /^"(.*)"$/ ) { # a quoted string
875 $friendly_name =~ s/"/\\"/g;
879 RT->Config->Get('FriendlyFromLineFormat'),
880 $self->MIMEEncodeString(
881 $friendly_name, RT->Config->Get('EmailOutputEncoding')
887 $self->SetHeader( 'From', $replyto );
891 unless ( $self->TemplateObj->MIMEObj->head->get('Reply-To') ) {
892 $self->SetHeader( 'Reply-To', "$replyto" );
897 =head2 SetHeader FIELD, VALUE
899 Set the FIELD of the current MIME object into VALUE.
910 my $head = $self->TemplateObj->MIMEObj->head;
911 $head->fold_length( $field, 10000 );
912 $head->replace( $field, $val );
913 return $head->get($field);
918 This routine sets the subject. it does not add the rt tag. That gets done elsewhere
919 If subject is already defined via template, it uses that. otherwise, it tries to get
920 the transaction's subject.
928 if ( $self->TemplateObj->MIMEObj->head->get('Subject') ) {
932 my $message = $self->TransactionObj->Attachments;
933 $message->RowsPerPage(1);
934 if ( $self->{'Subject'} ) {
935 $subject = $self->{'Subject'};
936 } elsif ( my $first = $message->First ) {
937 my $tmp = $first->GetHeader('Subject');
938 $subject = defined $tmp ? $tmp : $self->TicketObj->Subject;
940 $subject = $self->TicketObj->Subject;
942 $subject = '' unless defined $subject;
945 $subject =~ s/(\r\n|\n|\s)/ /g;
947 $self->SetHeader( 'Subject', $subject );
951 =head2 SetSubjectToken
953 This routine fixes the RT tag in the subject. It's unlikely that you want to overwrite this.
957 sub SetSubjectToken {
960 my $head = $self->TemplateObj->MIMEObj->head;
962 Subject => RT::Interface::Email::AddSubjectTag(
963 Encode::decode_utf8( $head->get('Subject') ),
969 =head2 SetReferencesHeaders
971 Set References and In-Reply-To headers for this message.
975 sub SetReferencesHeaders {
977 my ( @in_reply_to, @references, @msgid );
979 if ( my $top = $self->TransactionObj->Message->First ) {
980 @in_reply_to = split( /\s+/m, $top->GetHeader('In-Reply-To') || '' );
981 @references = split( /\s+/m, $top->GetHeader('References') || '' );
982 @msgid = split( /\s+/m, $top->GetHeader('Message-ID') || '' );
987 # There are two main cases -- this transaction was created with
988 # the RT Web UI, and hence we want to *not* append its Message-ID
989 # to the References and In-Reply-To. OR it came from an outside
990 # source, and we should treat it as per the RFC
991 my $org = RT->Config->Get('Organization');
992 if ( "@msgid" =~ /<(rt-.*?-\d+-\d+)\.(\d+)-0-0\@\Q$org\E>/ ) {
994 # Make all references which are internal be to version which we
997 for ( @references, @in_reply_to ) {
998 s/<(rt-.*?-\d+-\d+)\.(\d+-0-0)\@\Q$org\E>$/
999 "<$1." . $self->TicketObj->id .
1000 "-" . $self->ScripObj->id .
1001 "-" . $self->ScripActionObj->{_Message_ID} .
1005 # In reply to whatever the internal message was in reply to
1006 $self->SetHeader( 'In-Reply-To', join( " ", (@in_reply_to) ) );
1008 # Default the references to whatever we're in reply to
1009 @references = @in_reply_to unless @references;
1011 # References are unchanged from internal
1014 # In reply to that message
1015 $self->SetHeader( 'In-Reply-To', join( " ", (@msgid) ) );
1017 # Default the references to whatever we're in reply to
1018 @references = @in_reply_to unless @references;
1020 # Push that message onto the end of the references
1021 push @references, @msgid;
1024 # Push pseudo-ref to the front
1025 my $pseudo_ref = $self->PseudoReference;
1026 @references = ( $pseudo_ref, grep { $_ ne $pseudo_ref } @references );
1028 # If there are more than 10 references headers, remove all but the
1029 # first four and the last six (Gotta keep this from growing
1031 splice( @references, 4, -6 ) if ( $#references >= 10 );
1033 # Add on the references
1034 $self->SetHeader( 'References', join( " ", @references ) );
1035 $self->TemplateObj->MIMEObj->head->fold_length( 'References', 80 );
1039 =head2 PseudoReference
1041 Returns a fake Message-ID: header for the ticket to allow a base level of threading
1045 sub PseudoReference {
1050 . $self->TicketObj->id . '@'
1051 . RT->Config->Get('Organization') . '>';
1055 =head2 SetHeaderAsEncoding($field_name, $charset_encoding)
1057 This routine converts the field into specified charset encoding.
1061 sub SetHeaderAsEncoding {
1063 my ( $field, $enc ) = ( shift, shift );
1065 my $head = $self->TemplateObj->MIMEObj->head;
1067 if ( lc($field) eq 'from' and RT->Config->Get('SMTPFrom') ) {
1068 $head->replace( $field, RT->Config->Get('SMTPFrom') );
1072 my $value = $head->get( $field );
1073 $value = $self->MIMEEncodeString( $value, $enc );
1074 $head->replace( $field, $value );
1078 =head2 MIMEEncodeString
1080 Takes a perl string and optional encoding pass it over
1081 L<RT::Interface::Email/EncodeToMIME>.
1083 Basicly encode a string using B encoding according to RFC2047.
1087 sub MIMEEncodeString {
1089 return RT::Interface::Email::EncodeToMIME( String => $_[0], Charset => $_[1] );
1092 eval "require RT::Action::SendEmail_Vendor";
1093 die $@ if ( $@ && $@ !~ qr{^Can't locate RT/Action/SendEmail_Vendor.pm} );
1094 eval "require RT::Action::SendEmail_Local";
1095 die $@ if ( $@ && $@ !~ qr{^Can't locate RT/Action/SendEmail_Local.pm} );