1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC
6 # <sales@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 return abs $self->SendMessage( $self->TemplateObj->MIMEObj )
104 unless RT->Config->Get('RecordOutgoingEmail');
106 $self->DeferDigestRecipients();
107 my $message = $self->TemplateObj->MIMEObj;
110 $orig_message = $message->dup if RT::Interface::Email::WillSignEncrypt(
111 Attachment => $self->TransactionObj->Attachments->First,
112 Ticket => $self->TicketObj,
115 my ($ret) = $self->SendMessage($message);
116 return abs( $ret ) if $ret <= 0;
120 Type => 'application/x-rt-original-message',
121 Disposition => 'inline',
122 Data => $orig_message->as_string,
125 $self->RecordOutgoingMailTransaction($message);
126 $self->RecordDeferredRecipients();
132 Builds an outgoing email we're going to send using scrip's template.
139 my ( $result, $message ) = $self->TemplateObj->Parse(
140 Argument => $self->Argument,
141 TicketObj => $self->TicketObj,
142 TransactionObj => $self->TransactionObj
148 my $MIMEObj = $self->TemplateObj->MIMEObj;
151 $self->SetRTSpecialHeaders();
153 $self->RemoveInappropriateRecipients();
156 foreach my $type (@EMAIL_RECIPIENT_HEADERS) {
158 = grep defined && length && !$seen{ lc $_ }++,
162 # Go add all the Tos, Ccs and Bccs that we need to to the message to
163 # make it happy, but only if we actually have values in those arrays.
165 # TODO: We should be pulling the recipients out of the template and shove them into To, Cc and Bcc
167 for my $header (@EMAIL_RECIPIENT_HEADERS) {
169 $self->SetHeader( $header, join( ', ', @{ $self->{$header} } ) )
170 if (!$MIMEObj->head->get($header)
172 && @{ $self->{$header} } );
174 # PseudoTo (fake to headers) shouldn't get matched for message recipients.
175 # If we don't have any 'To' header (but do have other recipients), drop in
176 # the pseudo-to header.
177 $self->SetHeader( 'To', join( ', ', @{ $self->{'PseudoTo'} } ) )
178 if $self->{'PseudoTo'}
179 && @{ $self->{'PseudoTo'} }
180 && !$MIMEObj->head->get('To')
181 && ( $MIMEObj->head->get('Cc') or $MIMEObj->head->get('Bcc') );
183 # We should never have to set the MIME-Version header
184 $self->SetHeader( 'MIME-Version', '1.0' );
186 # fsck.com #5959: Since RT sends 8bit mail, we should say so.
187 $self->SetHeader( 'Content-Transfer-Encoding', '8bit' );
189 # For security reasons, we only send out textual mails.
190 foreach my $part ( grep !$_->is_multipart, $MIMEObj->parts_DFS ) {
191 my $type = $part->mime_type || 'text/plain';
192 $type = 'text/plain' unless RT::I18N::IsTextualContentType($type);
193 $part->head->mime_attr( "Content-Type" => $type );
194 # utf-8 here is for _FindOrGuessCharset in I18N.pm
195 # it's not the final charset/encoding sent
196 $part->head->mime_attr( "Content-Type.charset" => 'utf-8' );
199 RT::I18N::SetMIMEEntityToEncoding( $MIMEObj,
200 RT->Config->Get('EmailOutputEncoding'),
203 # Build up a MIME::Entity that looks like the original message.
204 $self->AddAttachments if ( $MIMEObj->head->get('RT-Attach-Message')
205 && ( $MIMEObj->head->get('RT-Attach-Message') !~ /^(n|no|0|off|false)$/i ) );
209 my $attachment = $self->TransactionObj->Attachments->First;
212 $attachment->GetHeader('X-RT-Encrypt')
213 || $self->TicketObj->QueueObj->Encrypt
217 $attachment->SetHeader( 'X-RT-Encrypt' => 1 )
218 if ( $attachment->GetHeader("X-RT-Incoming-Encryption") || '' ) eq
227 Returns an array of L<Email::Address> objects containing all the To: recipients for this notification
233 return ( $self->AddressesFromHeader('To') );
238 Returns an array of L<Email::Address> objects containing all the Cc: recipients for this notification
244 return ( $self->AddressesFromHeader('Cc') );
249 Returns an array of L<Email::Address> objects containing all the Bcc: recipients for this notification
255 return ( $self->AddressesFromHeader('Bcc') );
259 sub AddressesFromHeader {
262 my $header = $self->TemplateObj->MIMEObj->head->get($field);
263 my @addresses = Email::Address->parse($header);
268 =head2 SendMessage MIMEObj
270 sends the message using RT's preferred API.
271 TODO: Break this out to a separate module
277 # DO NOT SHIFT @_ in this subroutine. It breaks Hook::LexWrap's
278 # ability to pass @_ to a 'post' routine.
279 my ( $self, $MIMEObj ) = @_;
281 my $msgid = $MIMEObj->head->get('Message-ID');
284 $self->ScripActionObj->{_Message_ID}++;
286 $RT::Logger->info( $msgid . " #"
287 . $self->TicketObj->id . "/"
288 . $self->TransactionObj->id
290 . ($self->ScripObj->id || '#rule'). " "
291 . ( $self->ScripObj->Description || '' ) );
293 my $status = RT::Interface::Email::SendEmail(
295 Ticket => $self->TicketObj,
296 Transaction => $self->TransactionObj,
300 return $status unless ($status > 0 || exists $self->{'Deferred'});
302 my $success = $msgid . " sent ";
303 foreach (@EMAIL_RECIPIENT_HEADERS) {
304 my $recipients = $MIMEObj->head->get($_);
305 $success .= " $_: " . $recipients if $recipients;
308 if( exists $self->{'Deferred'} ) {
309 for (qw(daily weekly susp)) {
310 $success .= "\nBatched email $_ for: ". join(", ", keys %{ $self->{'Deferred'}{ $_ } } )
311 if exists $self->{'Deferred'}{ $_ };
317 $RT::Logger->info($success);
322 =head2 AddAttachments
324 Takes any attachments to this transaction and attaches them to the message
332 my $MIMEObj = $self->TemplateObj->MIMEObj;
334 $MIMEObj->head->delete('RT-Attach-Message');
336 my $attachments = RT::Attachments->new($RT::SystemUser);
338 FIELD => 'TransactionId',
339 VALUE => $self->TransactionObj->Id
342 # Don't attach anything blank
343 $attachments->LimitNotEmpty;
344 $attachments->OrderBy( FIELD => 'id' );
346 # We want to make sure that we don't include the attachment that's
347 # being used as the "Content" of this message" unless that attachment's
348 # content type is not like text/...
349 my $transaction_content_obj = $self->TransactionObj->ContentObj;
351 if ( $transaction_content_obj
352 && $transaction_content_obj->ContentType =~ m{text/}i )
354 # If this was part of a multipart/alternative, skip all of the kids
355 my $parent = $transaction_content_obj->ParentObj;
356 if ($parent and $parent->Id and $parent->ContentType eq "multipart/alternative") {
358 ENTRYAGGREGATOR => 'AND',
361 VALUE => $parent->Id,
365 ENTRYAGGREGATOR => 'AND',
368 VALUE => $transaction_content_obj->Id,
373 # attach any of this transaction's attachments
374 my $seen_attachment = 0;
375 while ( my $attach = $attachments->Next ) {
376 if ( !$seen_attachment ) {
377 $MIMEObj->make_multipart( 'mixed', Force => 1 );
378 $seen_attachment = 1;
380 $self->AddAttachment($attach);
384 =head2 AddAttachment $attachment
386 Takes one attachment object of L<RT::Attachmment> class and attaches it to the message
394 my $MIMEObj = shift || $self->TemplateObj->MIMEObj;
396 # $attach->TransactionObj may not always be $self->TransactionObj
397 return unless $attach->Id
398 and $attach->TransactionObj->CurrentUserCanSee;
401 Type => $attach->ContentType,
402 Charset => $attach->OriginalEncoding,
403 Data => $attach->OriginalContent,
404 Filename => $self->MIMEEncodeString( $attach->Filename ),
405 'RT-Attachment:' => $self->TicketObj->Id . "/"
406 . $self->TransactionObj->Id . "/"
408 Encoding => '-SUGGEST',
412 =head2 AttachTickets [@IDs]
414 Returns or set list of ticket's IDs that should be attached to an outgoing message.
416 B<Note> this method works as a class method and setup things global, so you have to
417 clean list by passing undef as argument.
426 $list = [ grep defined, @_ ] if @_;
433 Attaches tickets to the current message, list of tickets' ids get from
434 L</AttachTickets> method.
440 $self->AddTicket($_) foreach $self->AttachTickets;
446 Attaches a ticket with ID to the message.
448 Each ticket is attached as multipart entity and all its messages and attachments
449 are attached as sub entities in order of creation, but only if transaction type
450 is Create or Correspond.
458 my $attachs = RT::Attachments->new( RT::CurrentUser->new($self->TransactionObj->Creator) );
459 my $txn_alias = $attachs->TransactionAlias;
460 $attachs->Limit( ALIAS => $txn_alias, FIELD => 'Type', VALUE => 'Create' );
464 VALUE => 'Correspond'
466 $attachs->LimitByTicket($tid);
467 $attachs->LimitNotEmpty;
468 $attachs->OrderBy( FIELD => 'Created' );
470 my $ticket_mime = MIME::Entity->build(
471 Type => 'multipart/mixed',
473 Description => "ticket #$tid",
475 while ( my $attachment = $attachs->Next ) {
476 $self->AddAttachment( $attachment, $ticket_mime );
478 if ( $ticket_mime->parts ) {
479 my $email_mime = $self->TemplateObj->MIMEObj;
480 $email_mime->make_multipart;
481 $email_mime->add_part($ticket_mime);
486 =head2 RecordOutgoingMailTransaction MIMEObj
488 Record a transaction in RT with this outgoing message for future record-keeping purposes
492 sub RecordOutgoingMailTransaction {
496 my @parts = $MIMEObj->parts;
499 foreach my $part (@parts) {
500 my $attach = $part->head->get('RT-Attachment');
503 "We found an attachment. we want to not record it.");
504 push @attachments, $attach;
506 $RT::Logger->debug("We found a part. we want to record it.");
510 $MIMEObj->parts( \@keep );
511 foreach my $attachment (@attachments) {
512 $MIMEObj->head->add( 'RT-Attachment', $attachment );
515 RT::I18N::SetMIMEEntityToEncoding( $MIMEObj, 'utf-8', 'mime_words_ok' );
518 = RT::Transaction->new( $self->TransactionObj->CurrentUser );
520 # XXX: TODO -> Record attachments as references to things in the attachments table, maybe.
523 if ( $self->TransactionObj->Type eq 'Comment' ) {
524 $type = 'CommentEmailRecord';
526 $type = 'EmailRecord';
529 my $msgid = $MIMEObj->head->get('Message-ID');
532 my ( $id, $msg ) = $transaction->Create(
533 Ticket => $self->TicketObj->Id,
541 $self->{'OutgoingMailTransaction'} = $id;
543 $RT::Logger->warning(
544 "Could not record outgoing message transaction: $msg");
549 =head2 SetRTSpecialHeaders
551 This routine adds all the random headers that RT wants in a mail message
552 that don't matter much to anybody else.
556 sub SetRTSpecialHeaders {
560 $self->SetSubjectToken();
561 $self->SetHeaderAsEncoding( 'Subject',
562 RT->Config->Get('EmailOutputEncoding') )
563 if ( RT->Config->Get('EmailOutputEncoding') );
564 $self->SetReturnAddress();
565 $self->SetReferencesHeaders();
567 unless ( $self->TemplateObj->MIMEObj->head->get('Message-ID') ) {
569 # Get Message-ID for this txn
571 if ( my $msg = $self->TransactionObj->Message->First ) {
572 $msgid = $msg->GetHeader("RT-Message-ID")
573 || $msg->GetHeader("Message-ID");
576 # If there is one, and we can parse it, then base our Message-ID on it
579 =~ s/<(rt-.*?-\d+-\d+)\.(\d+)-\d+-\d+\@\QRT->Config->Get('Organization')\E>$/
580 "<$1." . $self->TicketObj->id
581 . "-" . $self->ScripObj->id
582 . "-" . $self->ScripActionObj->{_Message_ID}
583 . "@" . RT->Config->Get('Organization') . ">"/eg
584 and $2 == $self->TicketObj->id
587 $self->SetHeader( "Message-ID" => $msgid );
590 'Message-ID' => RT::Interface::Email::GenMessageId(
591 Ticket => $self->TicketObj,
592 Scrip => $self->ScripObj,
593 ScripAction => $self->ScripActionObj
599 if (my $precedence = RT->Config->Get('DefaultMailPrecedence')
600 and !$self->TemplateObj->MIMEObj->head->get("Precedence")
602 $self->SetHeader( 'Precedence', $precedence );
605 $self->SetHeader( 'X-RT-Loop-Prevention', RT->Config->Get('rtname') );
606 $self->SetHeader( 'RT-Ticket',
607 RT->Config->Get('rtname') . " #" . $self->TicketObj->id() );
608 $self->SetHeader( 'Managed-by',
609 "RT $RT::VERSION (http://www.bestpractical.com/rt/)" );
611 # XXX, TODO: use /ShowUser/ShowUserEntry(or something like that) when it would be
612 # refactored into user's method.
613 if ( my $email = $self->TransactionObj->CreatorObj->EmailAddress
614 and RT->Config->Get('UseOriginatorHeader')
616 $self->SetHeader( 'RT-Originator', $email );
622 sub DeferDigestRecipients {
624 $RT::Logger->debug( "Calling SetRecipientDigests for transaction " . $self->TransactionObj . ", id " . $self->TransactionObj->id );
626 # The digest attribute will be an array of notifications that need to
627 # be sent for this transaction. The array will have the following
628 # format for its objects.
629 # $digest_hash -> {daily|weekly|susp} -> address -> {To|Cc|Bcc}
630 # -> sent -> {true|false}
631 # The "sent" flag will be used by the cron job to indicate that it has
632 # run on this transaction.
633 # In a perfect world we might move this hash construction to the
634 # extension module itself.
635 my $digest_hash = {};
637 foreach my $mailfield (@EMAIL_RECIPIENT_HEADERS) {
638 # If we have a "PseudoTo", the "To" contains it, so we don't need to access it
639 next if ( ( $self->{'PseudoTo'} && @{ $self->{'PseudoTo'} } ) && ( $mailfield eq 'To' ) );
640 $RT::Logger->debug( "Working on mailfield $mailfield; recipients are " . join( ',', @{ $self->{$mailfield} } ) );
642 # Store the 'daily digest' folk in an array.
643 my ( @send_now, @daily_digest, @weekly_digest, @suspended );
645 # Have to get the list of addresses directly from the MIME header
647 $RT::Logger->debug( $self->TemplateObj->MIMEObj->head->as_string );
648 foreach my $rcpt ( map { $_->address } $self->AddressesFromHeader($mailfield) ) {
650 my $user_obj = RT::User->new($RT::SystemUser);
651 $user_obj->LoadByEmail($rcpt);
652 if ( ! $user_obj->id ) {
653 # If there's an email address in here without an associated
654 # RT user, pass it on through.
655 $RT::Logger->debug( "User $rcpt is not associated with an RT user object. Send mail.");
656 push( @send_now, $rcpt );
660 my $mailpref = RT->Config->Get( 'EmailFrequency', $user_obj ) || '';
661 $RT::Logger->debug( "Got user mail preference '$mailpref' for user $rcpt");
663 if ( $mailpref =~ /daily/i ) { push( @daily_digest, $rcpt ) }
664 elsif ( $mailpref =~ /weekly/i ) { push( @weekly_digest, $rcpt ) }
665 elsif ( $mailpref =~ /suspend/i ) { push( @suspended, $rcpt ) }
666 else { push( @send_now, $rcpt ) }
669 # Reset the relevant mail field.
670 $RT::Logger->debug( "Removing deferred recipients from $mailfield: line");
672 $self->SetHeader( $mailfield, join( ', ', @send_now ) );
673 } else { # No recipients! Remove the header.
674 $self->TemplateObj->MIMEObj->head->delete($mailfield);
677 # Push the deferred addresses into the appropriate field in
678 # our attribute hash, with the appropriate mail header.
680 "Setting deferred recipients for attribute creation");
681 $digest_hash->{'daily'}->{$_} = {'header' => $mailfield , _sent => 0} for (@daily_digest);
682 $digest_hash->{'weekly'}->{$_} ={'header' => $mailfield, _sent => 0} for (@weekly_digest);
683 $digest_hash->{'susp'}->{$_} = {'header' => $mailfield, _sent =>0 } for (@suspended);
686 if ( scalar keys %$digest_hash ) {
688 # Save the hash so that we can add it as an attribute to the
689 # outgoing email transaction.
690 $self->{'Deferred'} = $digest_hash;
692 $RT::Logger->debug( "No recipients found for deferred delivery on "
694 . $self->TransactionObj->id );
700 sub RecordDeferredRecipients {
702 return unless exists $self->{'Deferred'};
704 my $txn_id = $self->{'OutgoingMailTransaction'};
705 return unless $txn_id;
707 my $txn_obj = RT::Transaction->new( $self->CurrentUser );
708 $txn_obj->Load( $txn_id );
709 my( $ret, $msg ) = $txn_obj->AddAttribute(
710 Name => 'DeferredRecipients',
711 Content => $self->{'Deferred'}
713 $RT::Logger->warning( "Unable to add deferred recipients to outgoing transaction: $msg" )
719 =head2 SquelchMailTo [@ADDRESSES]
721 Mark ADDRESSES to be removed from list of the recipients. Returns list of the addresses.
722 To empty list pass undefined argument.
724 B<Note> that this method can be called as class method and works globaly. Don't forget to
725 clean this list when blocking is not required anymore, pass undef to do this.
735 $squelch = [ grep defined, @_ ];
741 =head2 RemoveInappropriateRecipients
743 Remove addresses that are RT addresses or that are on this transaction's blacklist
747 sub RemoveInappropriateRecipients {
752 # If there are no recipients, don't try to send the message.
753 # If the transaction has content and has the header RT-Squelch-Replies-To
755 my $msgid = $self->TemplateObj->MIMEObj->head->get('Message-Id');
756 if ( my $attachment = $self->TransactionObj->Attachments->First ) {
758 if ( $attachment->GetHeader('RT-DetectedAutoGenerated') ) {
760 # What do we want to do with this? It's probably (?) a bounce
761 # caused by one of the watcher addresses being broken.
762 # Default ("true") is to redistribute, for historical reasons.
764 if ( !RT->Config->Get('RedistributeAutoGeneratedMessages') ) {
766 # Don't send to any watchers.
767 @{ $self->{$_} } = () for (@EMAIL_RECIPIENT_HEADERS);
768 $RT::Logger->info( $msgid
769 . " The incoming message was autogenerated. "
770 . "Not redistributing this message based on site configuration."
772 } elsif ( RT->Config->Get('RedistributeAutoGeneratedMessages') eq
776 # Only send to "privileged" watchers.
777 foreach my $type (@EMAIL_RECIPIENT_HEADERS) {
778 foreach my $addr ( @{ $self->{$type} } ) {
779 my $user = RT::User->new($RT::SystemUser);
780 $user->LoadByEmail($addr);
781 push @blacklist, $addr if ( !$user->Privileged );
784 $RT::Logger->info( $msgid
785 . " The incoming message was autogenerated. "
786 . "Not redistributing this message to unprivileged users based on site configuration."
791 if ( my $squelch = $attachment->GetHeader('RT-Squelch-Replies-To') ) {
792 push @blacklist, split( /,/, $squelch );
796 # Let's grab the SquelchMailTo attribue and push those entries into the @blacklist
797 push @blacklist, map $_->Content, $self->TicketObj->SquelchMailTo;
798 push @blacklist, $self->SquelchMailTo;
800 # Cycle through the people we're sending to and pull out anyone on the
803 # Trim leading and trailing spaces.
804 @blacklist = map { RT::User->CanonicalizeEmailAddress( $_->address ) } Email::Address->parse(join(', ', grep {defined} @blacklist));
806 foreach my $type (@EMAIL_RECIPIENT_HEADERS) {
808 foreach my $addr ( @{ $self->{$type} } ) {
810 # Weed out any RT addresses. We really don't want to talk to ourselves!
811 # If we get a reply back, that means it's not an RT address
812 if ( !RT::EmailParser->CullRTAddresses($addr) ) {
813 $RT::Logger->info( $msgid . "$addr appears to point to this RT instance. Skipping" );
816 if ( grep /^\Q$addr\E$/, @blacklist ) {
817 $RT::Logger->info( $msgid . "$addr was blacklisted for outbound mail on this transaction. Skipping");
822 @{ $self->{$type} } = @addrs;
826 =head2 SetReturnAddress is_comment => BOOLEAN
828 Calculate and set From and Reply-To headers based on the is_comment flag.
832 sub SetReturnAddress {
837 friendly_name => undef,
842 # $args{is_comment} should be set if the comment address is to be used.
845 if ( $args{'is_comment'} ) {
846 $replyto = $self->TicketObj->QueueObj->CommentAddress
847 || RT->Config->Get('CommentAddress');
849 $replyto = $self->TicketObj->QueueObj->CorrespondAddress
850 || RT->Config->Get('CorrespondAddress');
853 unless ( $self->TemplateObj->MIMEObj->head->get('From') ) {
854 if ( RT->Config->Get('UseFriendlyFromLine') ) {
855 my $friendly_name = $args{friendly_name};
857 unless ( $friendly_name ) {
858 $friendly_name = $self->TransactionObj->CreatorObj->FriendlyName;
859 if ( $friendly_name =~ /^"(.*)"$/ ) { # a quoted string
864 $friendly_name =~ s/"/\\"/g;
868 RT->Config->Get('FriendlyFromLineFormat'),
869 $self->MIMEEncodeString(
870 $friendly_name, RT->Config->Get('EmailOutputEncoding')
876 $self->SetHeader( 'From', $replyto );
880 unless ( $self->TemplateObj->MIMEObj->head->get('Reply-To') ) {
881 $self->SetHeader( 'Reply-To', "$replyto" );
886 =head2 SetHeader FIELD, VALUE
888 Set the FIELD of the current MIME object into VALUE.
899 my $head = $self->TemplateObj->MIMEObj->head;
900 $head->fold_length( $field, 10000 );
901 $head->replace( $field, $val );
902 return $head->get($field);
907 This routine sets the subject. it does not add the rt tag. That gets done elsewhere
908 If subject is already defined via template, it uses that. otherwise, it tries to get
909 the transaction's subject.
917 if ( $self->TemplateObj->MIMEObj->head->get('Subject') ) {
921 # don't use Transaction->Attachments because it caches
922 # and anything which later calls ->Attachments will be hurt
923 # by our RowsPerPage() call. caching is hard.
924 my $message = RT::Attachments->new( $self->CurrentUser );
925 $message->Limit( FIELD => 'TransactionId', VALUE => $self->TransactionObj->id);
926 $message->OrderBy( FIELD => 'id', ORDER => 'ASC' );
927 $message->RowsPerPage(1);
929 if ( $self->{'Subject'} ) {
930 $subject = $self->{'Subject'};
931 } elsif ( my $first = $message->First ) {
932 my $tmp = $first->GetHeader('Subject');
933 $subject = defined $tmp ? $tmp : $self->TicketObj->Subject;
935 $subject = $self->TicketObj->Subject;
937 $subject = '' unless defined $subject;
940 $subject =~ s/(\r\n|\n|\s)/ /g;
942 $self->SetHeader( 'Subject', $subject );
946 =head2 SetSubjectToken
948 This routine fixes the RT tag in the subject. It's unlikely that you want to overwrite this.
952 sub SetSubjectToken {
955 my $head = $self->TemplateObj->MIMEObj->head;
957 Subject => RT::Interface::Email::AddSubjectTag(
958 Encode::decode_utf8( $head->get('Subject') ),
964 =head2 SetReferencesHeaders
966 Set References and In-Reply-To headers for this message.
970 sub SetReferencesHeaders {
972 my ( @in_reply_to, @references, @msgid );
974 if ( my $top = $self->TransactionObj->Message->First ) {
975 @in_reply_to = split( /\s+/m, $top->GetHeader('In-Reply-To') || '' );
976 @references = split( /\s+/m, $top->GetHeader('References') || '' );
977 @msgid = split( /\s+/m, $top->GetHeader('Message-ID') || '' );
982 # There are two main cases -- this transaction was created with
983 # the RT Web UI, and hence we want to *not* append its Message-ID
984 # to the References and In-Reply-To. OR it came from an outside
985 # source, and we should treat it as per the RFC
986 my $org = RT->Config->Get('Organization');
987 if ( "@msgid" =~ /<(rt-.*?-\d+-\d+)\.(\d+)-0-0\@\Q$org\E>/ ) {
989 # Make all references which are internal be to version which we
992 for ( @references, @in_reply_to ) {
993 s/<(rt-.*?-\d+-\d+)\.(\d+-0-0)\@\Q$org\E>$/
994 "<$1." . $self->TicketObj->id .
995 "-" . $self->ScripObj->id .
996 "-" . $self->ScripActionObj->{_Message_ID} .
1000 # In reply to whatever the internal message was in reply to
1001 $self->SetHeader( 'In-Reply-To', join( " ", (@in_reply_to) ) );
1003 # Default the references to whatever we're in reply to
1004 @references = @in_reply_to unless @references;
1006 # References are unchanged from internal
1009 # In reply to that message
1010 $self->SetHeader( 'In-Reply-To', join( " ", (@msgid) ) );
1012 # Default the references to whatever we're in reply to
1013 @references = @in_reply_to unless @references;
1015 # Push that message onto the end of the references
1016 push @references, @msgid;
1019 # Push pseudo-ref to the front
1020 my $pseudo_ref = $self->PseudoReference;
1021 @references = ( $pseudo_ref, grep { $_ ne $pseudo_ref } @references );
1023 # If there are more than 10 references headers, remove all but the
1024 # first four and the last six (Gotta keep this from growing
1026 splice( @references, 4, -6 ) if ( $#references >= 10 );
1028 # Add on the references
1029 $self->SetHeader( 'References', join( " ", @references ) );
1030 $self->TemplateObj->MIMEObj->head->fold_length( 'References', 80 );
1034 =head2 PseudoReference
1036 Returns a fake Message-ID: header for the ticket to allow a base level of threading
1040 sub PseudoReference {
1045 . $self->TicketObj->id . '@'
1046 . RT->Config->Get('Organization') . '>';
1050 =head2 SetHeaderAsEncoding($field_name, $charset_encoding)
1052 This routine converts the field into specified charset encoding.
1056 sub SetHeaderAsEncoding {
1058 my ( $field, $enc ) = ( shift, shift );
1060 my $head = $self->TemplateObj->MIMEObj->head;
1062 if ( lc($field) eq 'from' and RT->Config->Get('SMTPFrom') ) {
1063 $head->replace( $field, RT->Config->Get('SMTPFrom') );
1067 my $value = $head->get( $field );
1068 $value = $self->MIMEEncodeString( $value, $enc );
1069 $head->replace( $field, $value );
1073 =head2 MIMEEncodeString
1075 Takes a perl string and optional encoding pass it over
1076 L<RT::Interface::Email/EncodeToMIME>.
1078 Basicly encode a string using B encoding according to RFC2047.
1082 sub MIMEEncodeString {
1084 return RT::Interface::Email::EncodeToMIME( String => $_[0], Charset => $_[1] );
1087 RT::Base->_ImportOverlays();