X-Git-Url: http://git.cyclocoop.org/?a=blobdiff_plain;f=includes%2FUserMailer.php;h=d302e89a770f37ba9c279ac9b298ce8143989fa5;hb=4dcc7961dfe37cd3fe4671a21fed30990cc6f959;hp=b8b6aa86a07adec3a6842e95d730fe16359e41fe;hpb=83783cd93399fd7526aacf629e448ad3fa80e23c;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/UserMailer.php b/includes/UserMailer.php index b8b6aa86a0..d302e89a77 100644 --- a/includes/UserMailer.php +++ b/includes/UserMailer.php @@ -21,9 +21,9 @@ * @author * @author * @author Tim Starling + * @author Luke Welling lwelling@wikimedia.org */ - /** * Stores a single person's name and email address. * These are passed in via the constructor, and will be returned in SMTP @@ -31,9 +31,9 @@ */ class MailAddress { /** - * @param $address string|User string with an email address, or a User object - * @param $name String: human-readable name if a string address is given - * @param $realName String: human-readable real name if a string address is given + * @param string|User $address string with an email address, or a User object + * @param string $name human-readable name if a string address is given + * @param string $realName human-readable real name if a string address is given */ function __construct( $address, $name = null, $realName = null ) { if ( is_object( $address ) && $address instanceof User ) { @@ -77,7 +77,6 @@ class MailAddress { } } - /** * Collection of static functions for sending mail */ @@ -109,9 +108,13 @@ class UserMailer { /** * Creates a single string from an associative array * - * @param $headers array Associative Array: keys are header field names, + * @param array $headers Associative Array: keys are header field names, * values are ... values. - * @param $endl String: The end of line character. Defaults to "\n" + * @param string $endl 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" ) { @@ -131,10 +134,10 @@ class UserMailer { global $wgSMTP, $wgServer; $msgid = uniqid( wfWikiID() . ".", true ); /* true required for cygwin */ - if ( is_array($wgSMTP) && isset($wgSMTP['IDHost']) && $wgSMTP['IDHost'] ) { + if ( is_array( $wgSMTP ) && isset( $wgSMTP['IDHost'] ) && $wgSMTP['IDHost'] ) { $domain = $wgSMTP['IDHost']; } else { - $url = wfParseUrl($wgServer); + $url = wfParseUrl( $wgServer ); $domain = $url['host']; } return "<$msgid@$domain>"; @@ -148,20 +151,48 @@ 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 string $subject email's subject. + * @param string $body 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) + * @param string $contentType 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 +242,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 +297,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,17 +338,7 @@ 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 ) { + if( count( $to ) > 1 ) { $headers['To'] = 'undisclosed-recipients:;'; } $headers = self::arrayToHeaderString( $headers, $endl ); @@ -295,6 +351,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 ); @@ -323,7 +380,7 @@ class UserMailer { * Set the mail error message in self::$mErrorString * * @param $code Integer: error number - * @param $string String: error message + * @param string $string error message */ static function errorHandler( $code, $string ) { self::$mErrorString = preg_replace( '/^mail\(\)(\s*\[.*?\])?: /', '', $string ); @@ -342,6 +399,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 = '' ) { @@ -452,9 +515,9 @@ class EmailNotification { array( /* SET */ 'wl_notificationtimestamp' => $dbw->timestamp( $timestamp ) ), array( /* WHERE */ - 'wl_user' => $watchers, + 'wl_user' => $watchers, 'wl_namespace' => $title->getNamespace(), - 'wl_title' => $title->getDBkey(), + 'wl_title' => $title->getDBkey(), ), $fname ); $dbw->commit( $fname ); @@ -507,11 +570,11 @@ class EmailNotification { * * @param $editor User object * @param $title Title object - * @param $timestamp string Edit timestamp - * @param $summary string Edit summary + * @param string $timestamp Edit timestamp + * @param string $summary Edit summary * @param $minorEdit bool - * @param $oldid int Revision ID - * @param $watchers array of user IDs + * @param int $oldid Revision ID + * @param array $watchers of user IDs * @param string $pageStatus * @throws MWException */ @@ -645,15 +708,19 @@ class EmailNotification { if ( !$wgEnotifImpersonal ) { // For personal mail, also show a link to the diff of all changes // since last visited. - $keys['$NEWPAGE'] .= "\n\n" . wfMessage( 'enotif_lastvisited', + $keys['$NEWPAGE'] .= "\n\n" . wfMessage( 'enotif_lastvisited', $this->title->getCanonicalUrl( 'diff=0&oldid=' . $this->oldid ) ) ->inContentLanguage()->text(); } - $keys['$OLDID'] = $this->oldid; + $keys['$OLDID'] = $this->oldid; + // @deprecated Remove in MediaWiki 1.23. + $keys['$CHANGEDORCREATED'] = wfMessage( 'changed' )->inContentLanguage()->text(); } else { # clear $OLDID placeholder in the message template - $keys['$OLDID'] = ''; + $keys['$OLDID'] = ''; $keys['$NEWPAGE'] = ''; + // @deprecated Remove in MediaWiki 1.23. + $keys['$CHANGEDORCREATED'] = wfMessage( 'created' )->inContentLanguage()->text(); } $keys['$PAGETITLE'] = $this->title->getPrefixedText(); @@ -681,11 +748,11 @@ class EmailNotification { # Now build message's subject and body $this->subject = wfMessage( 'enotif_subject_' . $this->pageStatus )->inContentLanguage() - ->params( $pageTitle, $keys['$PAGEEDITOR'] )->escaped(); + ->params( $pageTitle, $keys['$PAGEEDITOR'] )->text(); $keys['$PAGEINTRO'] = wfMessage( 'enotif_body_intro_' . $this->pageStatus ) ->inContentLanguage()->params( $pageTitle, $keys['$PAGEEDITOR'], $pageTitleUrl ) - ->escaped(); + ->text(); $body = wfMessage( 'enotif_body' )->inContentLanguage()->plain(); $body = strtr( $body, $keys ); @@ -702,13 +769,13 @@ class EmailNotification { { $editorAddress = new MailAddress( $this->editor ); if ( $wgEnotifFromEditor ) { - $this->from = $editorAddress; + $this->from = $editorAddress; } else { - $this->from = $adminAddress; + $this->from = $adminAddress; $this->replyto = $editorAddress; } } else { - $this->from = $adminAddress; + $this->from = $adminAddress; $this->replyto = new MailAddress( $wgNoReplyAddress ); } }