From 169fa47cdcbb2bbae2fe772b6440b137140c5839 Mon Sep 17 00:00:00 2001 From: Colin Kuskie Date: Tue, 15 Jun 2010 12:26:02 -0700 Subject: [PATCH] Insert mail footers into the first attachment of the email, trying to match its type (text/html). Convert email messages with only 1 part to singlepart, instead of multipart. Fixes bug #11560. --- docs/changelog/7.x.x.txt | 1 + lib/WebGUI/Mail/Send.pm | 59 +++++++++++++++++++-- t/Mail/Send.t | 108 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 162 insertions(+), 6 deletions(-) diff --git a/docs/changelog/7.x.x.txt b/docs/changelog/7.x.x.txt index c92ba6634..8f649a2e8 100644 --- a/docs/changelog/7.x.x.txt +++ b/docs/changelog/7.x.x.txt @@ -18,6 +18,7 @@ - fixed #11623: Navigation CSS-id - fixed #11629: WebGUI Collateral Manager = Error - fixed #11622: Archived CSS entries displayable. + - fixed #11560: Email footer hidden from Outlook users 7.9.6 - new checkbox in the asset manager for clearing the package flag on import diff --git a/lib/WebGUI/Mail/Send.pm b/lib/WebGUI/Mail/Send.pm index 2bf1cb1ab..4f5aafe7d 100644 --- a/lib/WebGUI/Mail/Send.pm +++ b/lib/WebGUI/Mail/Send.pm @@ -22,6 +22,7 @@ use Net::SMTP; use WebGUI::Group; use WebGUI::Macro; use WebGUI::User; +use WebGUI::HTML; use Encode qw(encode); =head1 NAME @@ -84,15 +85,57 @@ sub addAttachment { =head2 addFooter ( ) -Adds the mail footer as set by the site admin to the end of this message. +Adds the mail footer as set by the site admin to the end of the first +part of this message. If the first part of the message has an HTML MIME-type, +then it will translate the footer to HTML. + +If the message is empty, it will create a MIME entity part to hold it. + +Macros in the footer will be evaluated. =cut sub addFooter { my $self = shift; + return if $self->{_footerAdded}; my $text = "\n\n".$self->session->setting->get("mailFooter"); WebGUI::Macro::process($self->session, \$text); - $self->addText($text); + $self->{_footerAdded} = 1; + my @parts = $self->getMimeEntity->parts(); + ##No parts yet, add one with the footer content. + if (! $parts[0]) { + $self->addText($text); + return; + } + ##Get the content of the first part, drop it from the set of parts + my $mime_body = $parts[0]->bodyhandle; + my $body_content = join '', $mime_body->as_lines; + my $mime_type; + if ($parts[0]->effective_type eq 'text/plain') { + $body_content .= $text; + my $new_part = MIME::Entity->build( + Charset => "UTF-8", + Encoding => "quoted-printable", + Type => 'text/plain', + Data => encode('utf8', $body_content), + ); + shift @parts; + unshift @parts, $new_part; + $self->getMimeEntity->parts(\@parts); + } + elsif ($parts[0]->effective_type eq 'text/html') { + $text = WebGUI::HTML::format($text, 'mixed'); + $body_content =~ s{(?=)}{$text}; + my $new_part = MIME::Entity->build( + Charset => "UTF-8", + Encoding => "quoted-printable", + Type => 'text/html', + Data => encode('utf8', $body_content), + ); + shift @parts; + unshift @parts, $new_part; + $self->getMimeEntity->parts(\@parts); + } } #------------------------------------------------------------------- @@ -339,7 +382,13 @@ sub create { delete $headers->{toGroup}; $message->attach(Data=>"This message was intended for ".$to." but was overridden in the config file.\n\n"); } - bless {_message=>$message, _session=>$session, _toGroup=>$headers->{toGroup}, _isInbox => $isInbox }, $class; + return bless { + _message => $message, + _session => $session, + _toGroup => $headers->{toGroup}, + _isInbox => $isInbox, + _footerAdded => 0, + }, $class; } #------------------------------------------------------------------- @@ -462,6 +511,10 @@ sub send { my $smtpServer = $session->setting->get("smtpServer"); my $status = 1; + if ($mail->parts <= 1) { + warn "making singlepart"; + $mail->make_singlepart; + } if ($mail->head->get("To")) { if ($session->config->get("emailToLog")){ my $message = $mail->stringify; diff --git a/t/Mail/Send.t b/t/Mail/Send.t index c3736a00f..344c15973 100644 --- a/t/Mail/Send.t +++ b/t/Mail/Send.t @@ -42,7 +42,7 @@ if ( $@ ) { diag( "Can't prepare mail server: $@" ) } #---------------------------------------------------------------------------- # Tests -plan tests => 17; # Increment this number for each test you create +plan tests => 33; # Increment this number for each test you create WebGUI::Test->addToCleanup(SQL => 'delete from mailQueue'); @@ -78,6 +78,8 @@ is( $mime->parts(0)->as_string =~ m/\n/, $newlines, "addText should add newlines after 78 characters", ); +is ( $mime->parts(0)->effective_type, 'text/plain', '... sets the correct MIME type' ); + #---------------------------------------------------------------------------- # Test addHtml $mail = WebGUI::Mail::Send->create( $session ); @@ -89,13 +91,13 @@ $mail->addHtml($text); $mime = $mail->getMimeEntity; # TODO: Test that addHtml creates an HTML wrapper if no html or body tag exists -# TODO: Test that addHtml creates a body with the right content type # addHtml should add newlines after 78 characters $newlines = length $text / 78; is( $mime->parts(0)->as_string =~ m/\n/, $newlines, "addHtml should add newlines after 78 characters", ); +is ( $mime->parts(0)->effective_type, 'text/html', '... sets the correct MIME type' ); # TODO: Test that addHtml does not create an HTML wrapper if html or body tag exist @@ -127,7 +129,53 @@ my $messageId = $mail->queue; my $dbMail = WebGUI::Mail::Send->retrieve($session, $messageId); is($dbMail->getMimeEntity->head->get('List-ID'), "=?UTF-8?Q?H=C3=84ufige=20Fragen?=\n", 'addHeaderField: handles utf-8 correctly'); -# TODO: Test that addHtml creates a body with the right content type +{ + my $mail = WebGUI::Mail::Send->create( $session ); + ok ! $mail->{_footerAdded}, 'footerAdded flag set to false by default'; + $mail->addFooter; + ok $mail->{_footerAdded}, '... flag set after calling addFooter'; + my $number_of_parts; + $number_of_parts = $mail->getMimeEntity->parts; + is $number_of_parts, 1, '... added 1 part for a footer'; + $mail->addFooter; + ok $mail->{_footerAdded}, '... flag still set after calling addFooter again'; + $number_of_parts = $mail->getMimeEntity->parts; + is $number_of_parts, 1, '... 2nd footer not added'; + +} + +{ + my $mail = WebGUI::Mail::Send->create( $session ); + $mail->addText('some text'); + $mail->addFooter; + my $number_of_parts; + $number_of_parts = $mail->getMimeEntity->parts; + is $number_of_parts, 1, 'addFooter did not add any other parts'; + my $body = $mail->getMimeEntity->parts(0)->as_string; + $body =~ s/\A.+?(?=some text)//s; + is $body, "some text\n\nMy Company\ninfo\@mycompany.com\nhttp://www.mycompany.com\n", '... footer appended to the first part as text'; +} + +{ + my $mail = WebGUI::Mail::Send->create( $session ); + $mail->addHtml('some markup'); + $mail->addFooter; + my $number_of_parts; + $number_of_parts = $mail->getMimeEntity->parts; + is $number_of_parts, 1, 'addFooter did not add any other parts'; + my $body = $mail->getMimeEntity->parts(0)->as_string; + $body =~ s/\A.+?\n//sm; + $body =~ s!.+\Z!!sm; + is $body, "some markup\n
\n
\nMy Company
\ninfo\@mycompany.com
\nhttp://www.mycompany.com
\n", '... footer appended to the first part as text'; +} + +{ + my $mail = WebGUI::Mail::Send->create( $session ); + $mail->addText('This is a textual email'); + my $result = $mail->getMimeEntity->is_multipart; + ok(defined $result && $result, 'by default, we make multipart messages'); +} + my $smtpServerOk = 0; #---------------------------------------------------------------------------- @@ -354,4 +402,58 @@ cmp_bag( 'send: when the original is sent, new messages are created for each user in the group, following their user profile settings' ); +SKIP: { + my $numtests = 2; # Number of tests in this block + + skip "Cannot test making emails single part", $numtests unless $smtpServerOk; + + # Send the mail + my $mail + = WebGUI::Mail::Send->create( $session, { + to => 'norton@localhost', + } ); + $mail->addText("They say it has no memory. That's where I want to live the rest of my life. A warm place with no memory."); + + ok ($mail->getMimeEntity->is_multipart, 'starting with a multipart message'); + $mail->send; + my $received = WebGUI::Test->getMail; + + if (!$received) { + skip "Cannot making single part: No response received from smtpd", $numtests; + } + + # Test the mail + my $parser = MIME::Parser->new(); + $parser->output_to_core(1); + my $parsed_message = $parser->parse_data($received->{contents}); + ok (!$parsed_message->is_multipart, 'converted to singlepart since it only has 1 part.'); +} + +SKIP: { + my $numtests = 2; # Number of tests in this block + + skip "Cannot test making emails single part", $numtests unless $smtpServerOk; + + # Send the mail + my $mail + = WebGUI::Mail::Send->create( $session, { + to => 'norton@localhost', + } ); + $mail->addText("You know what the Mexicans say about the Pacific?"); + $mail->addText("They say it has no memory. That's where I want to live the rest of my life. A warm place with no memory."); + + ok ($mail->getMimeEntity->is_multipart, 'starting with a multipart message'); + $mail->send; + my $received = WebGUI::Test->getMail; + + if (!$received) { + skip "Cannot making single part: No response received from smtpd", $numtests; + } + + # Test the mail + my $parser = MIME::Parser->new(); + $parser->output_to_core(1); + my $parsed_message = $parser->parse_data($received->{contents}); + ok ( $parsed_message->is_multipart, 'left as multipart since it has more than 1 part'); +} # TODO: Test the emailToLog config setting