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 }}}
48 # Portions Copyright 2000 Tobias Brox <tobix@cpan.org>
50 package RT::Action::SendEmail;
51 require RT::Action::Generic;
55 @ISA = qw(RT::Action::Generic);
57 use MIME::Words qw(encode_mimeword);
61 use Date::Format qw(strftime);
65 RT::Action::SendEmail - An Action which users can use to send mail
66 or can subclassed for more specialized mail sending behavior.
67 RT::Action::AutoReply is a good example subclass.
71 require RT::Action::SendEmail;
72 @ISA = qw(RT::Action::SendEmail);
77 Basically, you create another module RT::Action::YourAction which ISA
78 RT::Action::SendEmail.
82 ok (require RT::Action::SendEmail);
89 Jesse Vincent <jesse@bestpractical.com> and Tobias Brox <tobix@cpan.org>
97 # {{{ Scrip methods (_Init, Commit, Prepare, IsApplicable)
103 # DO NOT SHIFT @_ in this subroutine. It breaks Hook::LexWrap's
104 # ability to pass @_ to a 'post' routine.
107 my ($ret) = $self->SendMessage( $self->TemplateObj->MIMEObj );
109 $self->RecordOutgoingMailTransaction( $self->TemplateObj->MIMEObj )
110 if ($RT::RecordOutgoingEmail);
122 my ( $result, $message ) = $self->TemplateObj->Parse(
123 Argument => $self->Argument,
124 TicketObj => $self->TicketObj,
125 TransactionObj => $self->TransactionObj
131 my $MIMEObj = $self->TemplateObj->MIMEObj;
134 $self->SetRTSpecialHeaders();
136 $self->RemoveInappropriateRecipients();
139 foreach my $type qw(To Cc Bcc) {
140 @{ $self->{ $type } } =
141 grep defined && length && !$seen{ lc $_ }++,
142 @{ $self->{ $type } };
145 # Go add all the Tos, Ccs and Bccs that we need to to the message to
146 # make it happy, but only if we actually have values in those arrays.
148 # TODO: We should be pulling the recipients out of the template and shove them into To, Cc and Bcc
150 $self->SetHeader( 'To', join ( ', ', @{ $self->{'To'} } ) )
151 if ( ! $MIMEObj->head->get('To') && $self->{'To'} && @{ $self->{'To'} } );
152 $self->SetHeader( 'Cc', join ( ', ', @{ $self->{'Cc'} } ) )
153 if ( !$MIMEObj->head->get('Cc') && $self->{'Cc'} && @{ $self->{'Cc'} } );
154 $self->SetHeader( 'Bcc', join ( ', ', @{ $self->{'Bcc'} } ) )
155 if ( !$MIMEObj->head->get('Bcc') && $self->{'Bcc'} && @{ $self->{'Bcc'} } );
157 # PseudoTo (fake to headers) shouldn't get matched for message recipients.
158 # If we don't have any 'To' header (but do have other recipients), drop in
159 # the pseudo-to header.
160 $self->SetHeader( 'To', join ( ', ', @{ $self->{'PseudoTo'} } ) )
161 if ( $self->{'PseudoTo'} && ( @{ $self->{'PseudoTo'} } )
162 and ( !$MIMEObj->head->get('To') ) ) and ( $MIMEObj->head->get('Cc') or $MIMEObj->head->get('Bcc'));
164 # We should never have to set the MIME-Version header
165 $self->SetHeader( 'MIME-Version', '1.0' );
167 # fsck.com #5959: Since RT sends 8bit mail, we should say so.
168 $self->SetHeader( 'Content-Transfer-Encoding','8bit');
170 # For security reasons, we only send out textual mails.
171 my @parts = $MIMEObj;
172 while (my $part = shift @parts) {
173 if ($part->is_multipart) {
174 push @parts, $part->parts;
177 if ( RT::I18N::IsTextualContentType( $part->mime_type ) ) {
178 $part->head->mime_attr( "Content-Type" => $part->mime_type )
180 $part->head->mime_attr( "Content-Type" => 'text/plain' );
182 $part->head->mime_attr( "Content-Type.charset" => 'utf-8' );
187 RT::I18N::SetMIMEEntityToEncoding( $MIMEObj, $RT::EmailOutputEncoding, 'mime_words_ok' );
189 # Build up a MIME::Entity that looks like the original message.
190 $self->AddAttachments() if ( $MIMEObj->head->get('RT-Attach-Message') );
204 Returns an array of Mail::Address objects containing all the To: recipients for this notification
210 return ($self->_AddressesFromHeader('To'));
215 Returns an array of Mail::Address objects containing all the Cc: recipients for this notification
221 return ($self->_AddressesFromHeader('Cc'));
226 Returns an array of Mail::Address objects containing all the Bcc: recipients for this notification
233 return ($self->_AddressesFromHeader('Bcc'));
237 sub _AddressesFromHeader {
240 my $header = $self->TemplateObj->MIMEObj->head->get($field);
241 my @addresses = Mail::Address->parse($header);
249 =head2 SendMessage MIMEObj
251 sends the message using RT's preferred API.
252 TODO: Break this out to a separate module
257 # DO NOT SHIFT @_ in this subroutine. It breaks Hook::LexWrap's
258 # ability to pass @_ to a 'post' routine.
259 my ( $self, $MIMEObj ) = @_;
261 my $msgid = $MIMEObj->head->get('Message-ID');
264 $self->ScripActionObj->{_Message_ID}++;
266 $RT::Logger->info( $msgid . " #"
267 . $self->TicketObj->id . "/"
268 . $self->TransactionObj->id
270 . $self->ScripObj->id . " "
271 . $self->ScripObj->Description );
273 #If we don't have any recipients to send to, don't send a message;
274 unless ( $MIMEObj->head->get('To')
275 || $MIMEObj->head->get('Cc')
276 || $MIMEObj->head->get('Bcc') )
278 $RT::Logger->info( $msgid . " No recipients found. Not sending.\n" );
282 unless ($MIMEObj->head->get('Date')) {
283 # We coerce localtime into an array since strftime has a flawed prototype that only accepts
285 $MIMEObj->head->replace(Date => strftime('%a, %d %b %Y %H:%M:%S %z', @{[localtime()]}));
288 return (0) unless ($self->OutputMIMEObject($MIMEObj));
290 my $success = $msgid . " sent ";
291 foreach( qw(To Cc Bcc) ) {
292 my $recipients = $MIMEObj->head->get($_);
293 $success .= " $_: ". $recipients if $recipients;
297 $RT::Logger->info($success);
303 =head2 OutputMIMEObject MIME::Entity
305 Sends C<MIME::Entity> as an email message according to RT's mailer configuration.
311 sub OutputMIMEObject {
315 my $msgid = $MIMEObj->head->get('Message-ID');
318 my $SendmailArguments = $RT::SendmailArguments;
319 if (defined $RT::VERPPrefix && defined $RT::VERPDomain) {
320 my $EnvelopeFrom = $self->TransactionObj->CreatorObj->EmailAddress;
321 $EnvelopeFrom =~ s/@/=/g;
322 $EnvelopeFrom =~ s/\s//g;
323 $SendmailArguments .= " -f ${RT::VERPPrefix}${EnvelopeFrom}\@${RT::VERPDomain}";
327 if ( $RT::MailCommand eq 'sendmailpipe' ) {
329 # don't ignore CHLD signal to get proper exit code
330 local $SIG{'CHLD'} = 'DEFAULT';
333 unless( open $mail, "|$RT::SendmailPath $SendmailArguments" ) {
334 die "Couldn't run $RT::SendmailPath: $!";
337 # if something wrong with $mail->print we will get PIPE signal, handle it
338 local $SIG{'PIPE'} = sub { die "$RT::SendmailPath closed pipe" };
339 $MIMEObj->print($mail);
341 unless ( close $mail ) {
342 die "Close failed: $!" if $!; # system error
343 # sendmail exit statuses mostly errors with data not software
344 # TODO: status parsing: core dump, exit on signal or EX_*
345 $RT::Logger->warning( "$RT::SendmailPath exitted with status $?" );
349 $RT::Logger->crit( $msgid . "Could not send mail: " . $@ );
354 my @mailer_args = ($RT::MailCommand);
357 local $ENV{MAILADDRESS};
359 if ( $RT::MailCommand eq 'sendmail' ) {
360 push @mailer_args, split(/\s+/, $SendmailArguments);
362 elsif ( $RT::MailCommand eq 'smtp' ) {
363 $ENV{MAILADDRESS} = $RT::SMTPFrom || $MIMEObj->head->get('From');
364 push @mailer_args, ( Host => $RT::SMTPServer );
365 push @mailer_args, ( Debug => $RT::SMTPDebug );
366 $method = 'smtpsend';
369 push @mailer_args, $RT::MailParams;
372 unless ( $MIMEObj->$method(@mailer_args) ) {
373 $RT::Logger->crit( $msgid . "Could not send mail." );
384 =head2 AddAttachments
386 Takes any attachments to this transaction and attaches them to the message
395 my $MIMEObj = $self->TemplateObj->MIMEObj;
397 $MIMEObj->head->delete('RT-Attach-Message');
399 my $attachments = RT::Attachments->new($RT::SystemUser);
401 FIELD => 'TransactionId',
402 VALUE => $self->TransactionObj->Id
404 $attachments->OrderBy( FIELD => 'id');
406 my $transaction_content_obj = $self->TransactionObj->ContentObj;
408 # attach any of this transaction's attachments
409 while ( my $attach = $attachments->Next ) {
411 # Don't attach anything blank
412 next unless ( $attach->ContentLength );
414 # We want to make sure that we don't include the attachment that's being used as the "Content" of this message.
416 if ( $transaction_content_obj
417 && $transaction_content_obj->Id == $attach->Id
418 && $transaction_content_obj->ContentType =~ qr{text/plain}i );
419 $MIMEObj->make_multipart('mixed');
421 Type => $attach->ContentType,
422 Charset => $attach->OriginalEncoding,
423 Data => $attach->OriginalContent,
424 Filename => $self->MIMEEncodeString( $attach->Filename,
425 $RT::EmailOutputEncoding ),
426 'RT-Attachment:' => $self->TicketObj->Id."/".$self->TransactionObj->Id."/".$attach->id,
427 Encoding => '-SUGGEST'
435 # {{{ RecordOutgoingMailTransaction
437 =head2 RecordOutgoingMailTransaction MIMEObj
439 Record a transaction in RT with this outgoing message for future record-keeping purposes
445 sub RecordOutgoingMailTransaction {
450 my @parts = $MIMEObj->parts;
453 foreach my $part (@parts) {
454 my $attach = $part->head->get('RT-Attachment');
456 $RT::Logger->debug("We found an attachment. we want to not record it.");
457 push @attachments, $attach;
459 $RT::Logger->debug("We found a part. we want to record it.");
463 $MIMEObj->parts(\@keep);
464 foreach my $attachment (@attachments) {
465 $MIMEObj->head->add('RT-Attachment', $attachment);
468 RT::I18N::SetMIMEEntityToEncoding( $MIMEObj, 'utf-8', 'mime_words_ok' );
470 my $transaction = RT::Transaction->new($self->TransactionObj->CurrentUser);
472 # XXX: TODO -> Record attachments as references to things in the attachments table, maybe.
475 if ($self->TransactionObj->Type eq 'Comment') {
476 $type = 'CommentEmailRecord';
478 $type = 'EmailRecord';
481 my $msgid = $MIMEObj->head->get('Message-ID');
484 my ( $id, $msg ) = $transaction->Create(
485 Ticket => $self->TicketObj->Id,
493 $self->{'OutgoingMailTransaction'} = $id;
495 $RT::Logger->warning( "Could not record outgoing message transaction: $msg" );
503 # {{{ sub SetRTSpecialHeaders
505 =head2 SetRTSpecialHeaders
507 This routine adds all the random headers that RT wants in a mail message
508 that don't matter much to anybody else.
512 sub SetRTSpecialHeaders {
516 $self->SetSubjectToken();
517 $self->SetHeaderAsEncoding( 'Subject', $RT::EmailOutputEncoding )
518 if ($RT::EmailOutputEncoding);
519 $self->SetReturnAddress();
520 $self->SetReferencesHeaders();
522 unless ($self->TemplateObj->MIMEObj->head->get('Message-ID')) {
523 # Get Message-ID for this txn
525 $msgid = $self->TransactionObj->Message->First->GetHeader("RT-Message-ID")
526 || $self->TransactionObj->Message->First->GetHeader("Message-ID")
527 if $self->TransactionObj->Message && $self->TransactionObj->Message->First;
529 # If there is one, and we can parse it, then base our Message-ID on it
531 and $msgid =~ s/<(rt-.*?-\d+-\d+)\.(\d+)-\d+-\d+\@\Q$RT::Organization\E>$/
532 "<$1." . $self->TicketObj->id
533 . "-" . $self->ScripObj->id
534 . "-" . $self->ScripActionObj->{_Message_ID}
535 . "@" . $RT::Organization . ">"/eg
536 and $2 == $self->TicketObj->id) {
537 $self->SetHeader( "Message-ID" => $msgid );
539 $self->SetHeader( 'Message-ID',
544 . int(rand(2000)) . '.'
545 . $self->TicketObj->id . "-"
546 . $self->ScripObj->id . "-" # Scrip
547 . $self->ScripActionObj->{_Message_ID} . "@" # Email sent
553 $self->SetHeader( 'Precedence', "bulk" )
554 unless ( $self->TemplateObj->MIMEObj->head->get("Precedence") );
556 $self->SetHeader( 'X-RT-Loop-Prevention', $RT::rtname );
557 $self->SetHeader( 'RT-Ticket',
558 $RT::rtname . " #" . $self->TicketObj->id() );
559 $self->SetHeader( 'Managed-by',
560 "RT $RT::VERSION (http://www.bestpractical.com/rt/)" );
562 $self->SetHeader( 'RT-Originator',
563 $self->TransactionObj->CreatorObj->EmailAddress );
572 # {{{ RemoveInappropriateRecipients
574 =head2 RemoveInappropriateRecipients
576 Remove addresses that are RT addresses or that are on this transaction's blacklist
580 sub RemoveInappropriateRecipients {
583 my $msgid = $self->TemplateObj->MIMEObj->head->get ('Message-Id');
589 my @types = qw/To Cc Bcc/;
591 # Weed out any RT addresses. We really don't want to talk to ourselves!
592 foreach my $type (@types) {
593 @{ $self->{$type} } =
594 RT::EmailParser::CullRTAddresses( "", @{ $self->{$type} } );
597 # If there are no recipients, don't try to send the message.
598 # If the transaction has content and has the header RT-Squelch-Replies-To
600 if ( $self->TransactionObj->Attachments->First() ) {
602 $self->TransactionObj->Attachments->First->GetHeader(
603 'RT-DetectedAutoGenerated')
607 # What do we want to do with this? It's probably (?) a bounce
608 # caused by one of the watcher addresses being broken.
609 # Default ("true") is to redistribute, for historical reasons.
611 if ( !$RT::RedistributeAutoGeneratedMessages ) {
613 # Don't send to any watchers.
614 @{ $self->{'To'} } = ();
615 @{ $self->{'Cc'} } = ();
616 @{ $self->{'Bcc'} } = ();
618 $RT::Logger->info( $msgid . " The incoming message was autogenerated. Not redistributing this message based on site configuration.\n");
620 elsif ( $RT::RedistributeAutoGeneratedMessages eq 'privileged' ) {
622 # Only send to "privileged" watchers.
625 foreach my $type (@types) {
627 foreach my $addr ( @{ $self->{$type} } ) {
628 my $user = RT::User->new($RT::SystemUser);
629 $user->LoadByEmail($addr);
630 @{ $self->{$type} } =
631 grep ( !/^\Q$addr\E$/, @{ $self->{$type} } )
632 if ( !$user->Privileged );
636 $RT::Logger->info( $msgid . " The incoming message was autogenerated. Not redistributing this message to unprivileged users based on site configuration.\n");
643 $self->TransactionObj->Attachments->First->GetHeader(
644 'RT-Squelch-Replies-To');
647 @blacklist = split( /,/, $squelch );
651 # Let's grab the SquelchMailTo attribue and push those entries into the @blacklist
652 my @non_recipients = $self->TicketObj->SquelchMailTo;
653 foreach my $attribute (@non_recipients) {
654 push @blacklist, $attribute->Content;
657 # Cycle through the people we're sending to and pull out anyone on the
660 foreach my $person_to_yank (@blacklist) {
661 $person_to_yank =~ s/\s//g;
662 foreach my $type (@types) {
663 @{ $self->{$type} } =
664 grep ( !/^\Q$person_to_yank\E$/, @{ $self->{$type} } );
670 # {{{ sub SetReturnAddress
672 =head2 SetReturnAddress is_comment => BOOLEAN
674 Calculate and set From and Reply-To headers based on the is_comment flag.
678 sub SetReturnAddress {
687 # $args{is_comment} should be set if the comment address is to be used.
690 if ( $args{'is_comment'} ) {
691 $replyto = $self->TicketObj->QueueObj->CommentAddress
692 || $RT::CommentAddress;
695 $replyto = $self->TicketObj->QueueObj->CorrespondAddress
696 || $RT::CorrespondAddress;
699 unless ( $self->TemplateObj->MIMEObj->head->get('From') ) {
700 if ($RT::UseFriendlyFromLine) {
701 my $friendly_name = $self->TransactionObj->CreatorObj->RealName
702 || $self->TransactionObj->CreatorObj->Name;
703 if ( $friendly_name =~ /^"(.*)"$/ ) { # a quoted string
707 $friendly_name =~ s/"/\\"/g;
711 $RT::FriendlyFromLineFormat,
712 $self->MIMEEncodeString( $friendly_name,
713 $RT::EmailOutputEncoding ),
719 $self->SetHeader( 'From', $replyto );
723 unless ( $self->TemplateObj->MIMEObj->head->get('Reply-To') ) {
724 $self->SetHeader( 'Reply-To', "$replyto" );
733 =head2 SetHeader FIELD, VALUE
735 Set the FIELD of the current MIME object into VALUE.
746 $self->TemplateObj->MIMEObj->head->fold_length( $field, 10000 );
747 $self->TemplateObj->MIMEObj->head->replace( $field, $val );
748 return $self->TemplateObj->MIMEObj->head->get($field);
758 This routine sets the subject. it does not add the rt tag. that gets done elsewhere
759 If $self->{'Subject'} is already defined, it uses that. otherwise, it tries to get
760 the transaction's subject.
768 my $message = $self->TransactionObj->Attachments;
769 if ( $self->TemplateObj->MIMEObj->head->get('Subject') ) {
772 if ( $self->{'Subject'} ) {
773 $subject = $self->{'Subject'};
775 elsif ( ( $message->First() ) && ( $message->First->Headers ) ) {
776 my $header = $message->First->Headers();
777 $header =~ s/\n\s+/ /g;
778 if ( $header =~ /^Subject: (.*?)$/m ) {
782 $subject = $self->TicketObj->Subject();
787 $subject = $self->TicketObj->Subject();
790 $subject =~ s/(\r\n|\n|\s)/ /gi;
793 $self->SetHeader( 'Subject', $subject );
799 # {{{ sub SetSubjectToken
801 =head2 SetSubjectToken
803 This routine fixes the RT tag in the subject. It's unlikely that you want to overwrite this.
807 sub SetSubjectToken {
809 my $sub = $self->TemplateObj->MIMEObj->head->get('Subject');
810 my $id = $self->TicketObj->id;
812 my $token_re = $RT::EmailSubjectTagRegex;
813 $token_re = qr/\Q$RT::rtname\E/o unless $token_re;
814 return if $sub =~ /\[$token_re\s+#$id\]/;
816 $sub =~ s/(\r\n|\n|\s)/ /gi;
818 $self->TemplateObj->MIMEObj->head->replace(
819 Subject => "[$RT::rtname #$id] $sub",
825 =head2 SetReferencesHeaders
827 Set References and In-Reply-To headers for this message.
831 sub SetReferencesHeaders {
834 my ( @in_reply_to, @references, @msgid );
836 my $attachments = $self->TransactionObj->Message;
838 if ( my $top = $attachments->First() ) {
839 @in_reply_to = split(/\s+/m, $top->GetHeader('In-Reply-To') || '');
840 @references = split(/\s+/m, $top->GetHeader('References') || '' );
841 @msgid = split(/\s+/m, $top->GetHeader('Message-ID') || '');
847 # There are two main cases -- this transaction was created with
848 # the RT Web UI, and hence we want to *not* append its Message-ID
849 # to the References and In-Reply-To. OR it came from an outside
850 # source, and we should treat it as per the RFC
851 if ( "@msgid" =~ /<(rt-.*?-\d+-\d+)\.(\d+-0-0)\@$RT::Organization>/) {
853 # Make all references which are internal be to version which we
855 for (@references, @in_reply_to) {
856 s/<(rt-.*?-\d+-\d+)\.(\d+-0-0)\@$RT::Organization>$/
857 "<$1." . $self->TicketObj->id .
858 "-" . $self->ScripObj->id .
859 "-" . $self->ScripActionObj->{_Message_ID} .
860 "@" . $RT::Organization . ">"/eg
863 # In reply to whatever the internal message was in reply to
864 $self->SetHeader( 'In-Reply-To', join( " ", ( @in_reply_to )));
866 # Default the references to whatever we're in reply to
867 @references = @in_reply_to unless @references;
869 # References are unchanged from internal
871 # In reply to that message
872 $self->SetHeader( 'In-Reply-To', join( " ", ( @msgid )));
874 # Default the references to whatever we're in reply to
875 @references = @in_reply_to unless @references;
877 # Push that message onto the end of the references
878 push @references, @msgid;
881 # Push pseudo-ref to the front
882 my $pseudo_ref = $self->PseudoReference;
883 @references = ($pseudo_ref, grep { $_ ne $pseudo_ref } @references);
885 # If there are more than 10 references headers, remove all but the
886 # first four and the last six (Gotta keep this from growing
888 splice(@references, 4, -6) if ($#references >= 10);
890 # Add on the references
891 $self->SetHeader( 'References', join( " ", @references) );
892 $self->TemplateObj->MIMEObj->head->fold_length( 'References', 80 );
898 =head2 PseudoReference
900 Returns a fake Message-ID: header for the ticket to allow a base level of threading
904 sub PseudoReference {
907 my $pseudo_ref = '<RT-Ticket-'.$self->TicketObj->id .'@'.$RT::Organization .'>';
912 # {{{ SetHeadingAsEncoding
914 =head2 SetHeaderAsEncoding($field_name, $charset_encoding)
916 This routine converts the field into specified charset encoding.
920 sub SetHeaderAsEncoding {
922 my ( $field, $enc ) = ( shift, shift );
924 if ($field eq 'From' and $RT::SMTPFrom) {
925 $self->TemplateObj->MIMEObj->head->replace( $field, $RT::SMTPFrom );
929 my $value = $self->TemplateObj->MIMEObj->head->get($field);
931 $value = $self->MIMEEncodeString($value, $enc);
933 $self->TemplateObj->MIMEObj->head->replace( $field, $value );
939 # {{{ MIMEEncodeString
941 =head2 MIMEEncodeString STRING ENCODING
943 Takes a string and a possible encoding and returns the string wrapped in MIME goo.
947 sub MIMEEncodeString {
950 # using RFC2047 notation, sec 2.
951 # encoded-word = "=?" charset "?" encoding "?" encoded-text "?="
954 # An 'encoded-word' may not be more than 75 characters long
956 # MIME encoding increases 4/3*(number of bytes), and always in multiples
957 # of 4. Thus we have to find the best available value of bytes available
960 # First we get the integer max which max*4/3 would fit on space.
961 # Then we find the greater multiple of 3 lower or equal than $max.
962 my $max = int(((75-length('=?'.$charset.'?'.$encoding.'?'.'?='))*3)/4);
963 $max = int($max/3)*3;
969 $RT::Logger->crit("Can't encode! Charset or encoding too big.\n");
973 return ($value) unless $value =~ /[^\x20-\x7e]/;
977 # we need perl string to split thing char by char
978 Encode::_utf8_on($value) unless Encode::is_utf8( $value );
980 my ($tmp, @chunks) = ('', ());
981 while ( length $value ) {
982 my $char = substr($value, 0, 1, '');
983 my $octets = Encode::encode( $charset, $char );
984 if ( length($tmp) + length($octets) > $max ) {
990 push @chunks, $tmp if length $tmp;
992 # encode an join chuncks
994 map encode_mimeword( $_, $encoding, $charset ), @chunks ;
1000 eval "require RT::Action::SendEmail_Vendor";
1001 die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/SendEmail_Vendor.pm});
1002 eval "require RT::Action::SendEmail_Local";
1003 die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/SendEmail_Local.pm});