From fc8611d286a0d2a634fac61327b0635b24a7a17f Mon Sep 17 00:00:00 2001 From: lwelling Date: Fri, 7 Dec 2012 16:41:44 -0500 Subject: [PATCH] Add support for mulitpart mime email to email sending code The change is backwards compatible so various places that send email can be upgraded in their own time, or ignore it. The first user will be the Echo extension but it is likely to be used elsewhere This requires adding 'pear install Mime_Mail' to the server install To see a change you need to add $wgAllowHTMLEmail to your LocalSettings.php By default it is false in DefaultSettings.php as there is no core code that uses it yet (patch 11 notes fixed) Change-Id: I2096f50b789ffa0b8f4fa8c32fdaf43e9b0e4661 --- includes/DefaultSettings.php | 6 ++ includes/UserMailer.php | 103 ++++++++++++++++++++++++------ languages/messages/MessagesEn.php | 1 + 3 files changed, 91 insertions(+), 19 deletions(-) diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index e12b3be3fe..35f23c2912 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -1264,6 +1264,12 @@ $wgSMTP = false; */ $wgAdditionalMailParams = null; +/** + * For parts of the system that have been updated to provide HTML email content, send + * both text and HTML parts as the body of the email + */ +$wgAllowHTMLEmail = false; + /** * True: from page editor if s/he opted-in. False: Enotif mails appear to come * from $wgEmergencyContact diff --git a/includes/UserMailer.php b/includes/UserMailer.php index b8b6aa86a0..cb7afc0a12 100644 --- a/includes/UserMailer.php +++ b/includes/UserMailer.php @@ -21,6 +21,7 @@ * @author * @author * @author Tim Starling + * @author Luke Welling lwelling@wikimedia.org */ @@ -112,6 +113,10 @@ class UserMailer { * @param $headers array Associative Array: keys are header field names, * values are ... values. * @param $endl String: The end of line character. Defaults to "\n" + * + * Note RFC2822 says newlines must be CRLF (\r\n) + * but php mail naively "corrects" it and requires \n for the "correction" to work + * * @return String */ static function arrayToHeaderString( $headers, $endl = "\n" ) { @@ -149,19 +154,47 @@ class UserMailer { * @param $to MailAddress: recipient's email (or an array of them) * @param $from MailAddress: sender's email * @param $subject String: email's subject. - * @param $body String: email's text. + * @param $body String: email's text or Array of two strings to be the text and html bodies * @param $replyto MailAddress: optional reply-to email (default: null). * @param $contentType String: optional custom Content-Type (default: text/plain; charset=UTF-8) * @throws MWException * @return Status object */ public static function send( $to, $from, $subject, $body, $replyto = null, $contentType = 'text/plain; charset=UTF-8' ) { - global $wgSMTP, $wgEnotifMaxRecips, $wgAdditionalMailParams; - + global $wgSMTP, $wgEnotifMaxRecips, $wgAdditionalMailParams, $wgAllowHTMLEmail; + $mime = null; if ( !is_array( $to ) ) { $to = array( $to ); } + // mail body must have some content + $minBodyLen = 10; + // arbitrary but longer than Array or Object to detect casting error + + // body must either be a string or an array with text and body + if ( + !( + !is_array( $body ) && + strlen( $body ) >= $minBodyLen + ) + && + !( + is_array( $body ) && + isset( $body['text'] ) && + isset( $body['html'] ) && + strlen( $body['text'] ) >= $minBodyLen && + strlen( $body['html'] ) >= $minBodyLen + ) + ) { + // if it is neither we have a problem + return Status::newFatal( 'user-mail-no-body' ); + } + + if ( !$wgAllowHTMLEmail && is_array( $body ) ) { + // HTML not wanted. Dump it. + $body = $body['text']; + } + wfDebug( __METHOD__ . ': sending mail to ' . implode( ', ', $to ) . "\n" ); # Make sure we have at least one address @@ -211,18 +244,53 @@ class UserMailer { } $headers['Date'] = date( 'r' ); - $headers['MIME-Version'] = '1.0'; - $headers['Content-type'] = ( is_null( $contentType ) ? - 'text/plain; charset=UTF-8' : $contentType ); - $headers['Content-transfer-encoding'] = '8bit'; - $headers['Message-ID'] = self::makeMsgId(); $headers['X-Mailer'] = 'MediaWiki mailer'; + # Line endings need to be different on Unix and Windows due to + # the bug described at http://trac.wordpress.org/ticket/2603 + if ( wfIsWindows() ) { + $endl = "\r\n"; + } else { + $endl = "\n"; + } + + if ( is_array( $body ) ) { + // we are sending a multipart message + wfDebug( "Assembling mulitpart mime email\n" ); + if ( !stream_resolve_include_path( 'Mail/mime.php' ) ) { + wfDebug( "PEAR Mail_Mime package is not installed. Falling back to text email.\n" ); + } + else { + require_once( 'Mail/mime.php' ); + if ( wfIsWindows() ) { + $body['text'] = str_replace( "\n", "\r\n", $body['text'] ); + $body['html'] = str_replace( "\n", "\r\n", $body['html'] ); + } + $mime = new Mail_mime( array( 'eol' => $endl ) ); + $mime->setTXTBody( $body['text'] ); + $mime->setHTMLBody( $body['html'] ); + $body = $mime->get(); // must call get() before headers() + $headers = $mime->headers( $headers ); + } + } + if ( !isset( $mime ) ) { + // sending text only, either deliberately or as a fallback + if ( wfIsWindows() ) { + $body = str_replace( "\n", "\r\n", $body ); + } + $headers['MIME-Version'] = '1.0'; + $headers['Content-type'] = ( is_null( $contentType ) ? + 'text/plain; charset=UTF-8' : $contentType ); + $headers['Content-transfer-encoding'] = '8bit'; + } + $ret = wfRunHooks( 'AlternateUserMailer', array( $headers, $to, $from, $subject, $body ) ); if ( $ret === false ) { + // the hook implementation will return false to skip regular mail sending return Status::newGood(); } elseif ( $ret !== true ) { + // the hook implementation will return a string to pass an error message return Status::newFatal( 'php-mail-error', $ret ); } @@ -231,7 +299,7 @@ class UserMailer { # PEAR MAILER # - if ( ! stream_resolve_include_path( 'Mail.php' ) ) { + if ( !stream_resolve_include_path( 'Mail.php' ) ) { throw new MWException( 'PEAR mail package is not installed' ); } require_once( 'Mail.php' ); @@ -272,16 +340,6 @@ class UserMailer { # # PHP mail() # - - # Line endings need to be different on Unix and Windows due to - # the bug described at http://trac.wordpress.org/ticket/2603 - if ( wfIsWindows() ) { - $body = str_replace( "\n", "\r\n", $body ); - $endl = "\r\n"; - } else { - $endl = "\n"; - } - if( count($to) > 1 ) { $headers['To'] = 'undisclosed-recipients:;'; } @@ -295,6 +353,7 @@ class UserMailer { set_error_handler( 'UserMailer::errorHandler' ); $safeMode = wfIniGetBool( 'safe_mode' ); + foreach ( $to as $recip ) { if ( $safeMode ) { $sent = mail( $recip, self::quotedPrintable( $subject ), $body, $headers ); @@ -342,6 +401,12 @@ class UserMailer { /** * Converts a string into quoted-printable format * @since 1.17 + * + * From PHP5.3 there is a built in function quoted_printable_encode() + * This method does not duplicate that. + * This method is doing Q encoding inside encoded-words as defined by RFC 2047 + * This is for email headers. + * The built in quoted_printable_encode() is for email bodies * @return string */ public static function quotedPrintable( $string, $charset = '' ) { diff --git a/languages/messages/MessagesEn.php b/languages/messages/MessagesEn.php index cd3ef1abea..d62b49a72d 100644 --- a/languages/messages/MessagesEn.php +++ b/languages/messages/MessagesEn.php @@ -1192,6 +1192,7 @@ Please wait before trying again.', 'php-mail-error' => '$1', # do not translate or duplicate this message to other languages 'php-mail-error-unknown' => "Unknown error in PHP's mail() function.", 'user-mail-no-addy' => 'Tried to send e-mail without an e-mail address.', +'user-mail-no-body' => 'Tried to send e-mail with an empty or unreasonably short body.', # Change password dialog 'resetpass' => 'Change password', -- 2.20.1