diff options
Diffstat (limited to 'includes/mail')
-rw-r--r-- | includes/mail/EmailNotification.php | 61 | ||||
-rw-r--r-- | includes/mail/UserMailer.php | 140 |
2 files changed, 128 insertions, 73 deletions
diff --git a/includes/mail/EmailNotification.php b/includes/mail/EmailNotification.php index 81c4e38d..01b6afab 100644 --- a/includes/mail/EmailNotification.php +++ b/includes/mail/EmailNotification.php @@ -43,6 +43,20 @@ * Visit the documentation pages under http://meta.wikipedia.com/Enotif */ class EmailNotification { + + /** + * Notification is due to user's user talk being edited + */ + const USER_TALK = 'user_talk'; + /** + * Notification is due to a watchlisted page being edited + */ + const WATCHLIST = 'watchlist'; + /** + * Notification because user is notified for all changes + */ + const ALL_CHANGES = 'all_changes'; + protected $subject, $body, $replyto, $from; protected $timestamp, $summary, $minorEdit, $oldid, $composed_common, $pageStatus; protected $mailTargets = array(); @@ -61,7 +75,7 @@ class EmailNotification { * @param User $editor The editor that triggered the update. Their notification * timestamp will not be updated(they have already seen it) * @param Title $title The title to update timestamps for - * @param string $timestamp Set the upate timestamp to this value + * @param string $timestamp Set the update timestamp to this value * @return int[] */ public static function updateWatchlistTimestamp( User $editor, Title $title, $timestamp ) { @@ -110,7 +124,6 @@ class EmailNotification { /** * Send emails corresponding to the user $editor editing the page $title. - * Also updates wl_notificationtimestamp. * * May be deferred via the job queue. * @@ -136,7 +149,9 @@ class EmailNotification { $sendEmail = true; // If nobody is watching the page, and there are no users notified on all changes - // don't bother creating a job/trying to send emails + // don't bother creating a job/trying to send emails, unless it's a + // talk page with an applicable notification. + // // $watchers deals with $wgEnotifWatchlist if ( !count( $watchers ) && !count( $wgUsersNotifiedOnAllChanges ) ) { $sendEmail = false; @@ -168,7 +183,7 @@ class EmailNotification { 'pageStatus' => $pageStatus ); $job = new EnotifNotifyJob( $title, $params ); - JobQueueGroup::singleton()->push( $job ); + JobQueueGroup::singleton()->lazyPush( $job ); } else { $this->actuallyNotifyOnPageChange( $editor, @@ -187,8 +202,8 @@ class EmailNotification { * Immediate version of notifyOnPageChange(). * * Send emails corresponding to the user $editor editing the page $title. - * Also updates wl_notificationtimestamp. * + * @note Do not call directly. Use notifyOnPageChange so that wl_notificationtimestamp is updated. * @param User $editor * @param Title $title * @param string $timestamp Edit timestamp @@ -202,7 +217,7 @@ class EmailNotification { public function actuallyNotifyOnPageChange( $editor, $title, $timestamp, $summary, $minorEdit, $oldid, $watchers, $pageStatus = 'changed' ) { # we use $wgPasswordSender as sender's address - global $wgEnotifWatchlist; + global $wgEnotifWatchlist, $wgBlockDisablesLogin; global $wgEnotifMinorEdits, $wgEnotifUserTalk; # The following code is only run, if several conditions are met: @@ -235,21 +250,23 @@ class EmailNotification { && $this->canSendUserTalkEmail( $editor, $title, $minorEdit ) ) { $targetUser = User::newFromName( $title->getText() ); - $this->compose( $targetUser ); + $this->compose( $targetUser, self::USER_TALK ); $userTalkId = $targetUser->getId(); } if ( $wgEnotifWatchlist ) { // Send updates to watchers other than the current editor + // and don't send to watchers who are blocked and cannot login $userArray = UserArray::newFromIDs( $watchers ); foreach ( $userArray as $watchingUser ) { if ( $watchingUser->getOption( 'enotifwatchlistpages' ) && ( !$minorEdit || $watchingUser->getOption( 'enotifminoredits' ) ) && $watchingUser->isEmailConfirmed() && $watchingUser->getID() != $userTalkId + && !( $wgBlockDisablesLogin && $watchingUser->isBlocked() ) ) { if ( Hooks::run( 'SendWatchlistEmailNotification', array( $watchingUser, $title, $this ) ) ) { - $this->compose( $watchingUser ); + $this->compose( $watchingUser, self::WATCHLIST ); } } } @@ -263,7 +280,7 @@ class EmailNotification { continue; } $user = User::newFromName( $name ); - $this->compose( $user ); + $this->compose( $user, self::ALL_CHANGES ); } $this->sendMails(); @@ -276,7 +293,7 @@ class EmailNotification { * @return bool */ private function canSendUserTalkEmail( $editor, $title, $minorEdit ) { - global $wgEnotifUserTalk; + global $wgEnotifUserTalk, $wgBlockDisablesLogin; $isUserTalkPage = ( $title->getNamespace() == NS_USER_TALK ); if ( $wgEnotifUserTalk && $isUserTalkPage ) { @@ -286,6 +303,8 @@ class EmailNotification { wfDebug( __METHOD__ . ": user talk page edited, but user does not exist\n" ); } elseif ( $targetUser->getId() == $editor->getId() ) { wfDebug( __METHOD__ . ": user edited their own talk page, no notification sent\n" ); + } elseif ( $wgBlockDisablesLogin && $targetUser->isBlocked() ) { + wfDebug( __METHOD__ . ": talk page owner is blocked and cannot login, no notification sent\n" ); } elseif ( $targetUser->getOption( 'enotifusertalkpages' ) && ( !$minorEdit || $targetUser->getOption( 'enotifminoredits' ) ) ) { @@ -422,8 +441,9 @@ class EmailNotification { * * Call sendMails() to send any mails that were queued. * @param User $user + * @param string $source */ - function compose( $user ) { + function compose( $user, $source ) { global $wgEnotifImpersonal; if ( !$this->composed_common ) { @@ -433,7 +453,7 @@ class EmailNotification { if ( $wgEnotifImpersonal ) { $this->mailTargets[] = MailAddress::newFromUser( $user ); } else { - $this->sendPersonalised( $user ); + $this->sendPersonalised( $user, $source ); } } @@ -453,10 +473,11 @@ class EmailNotification { * Returns true if the mail was sent successfully. * * @param User $watchingUser + * @param string $source * @return bool * @private */ - function sendPersonalised( $watchingUser ) { + function sendPersonalised( $watchingUser, $source ) { global $wgContLang, $wgEnotifUseRealName; // From the PHP manual: // Note: The to parameter cannot be an address in the form of @@ -477,7 +498,15 @@ class EmailNotification { $wgContLang->userTime( $this->timestamp, $watchingUser ) ), $this->body ); - return UserMailer::send( $to, $this->from, $this->subject, $body, $this->replyto ); + $headers = array(); + if ( $source === self::WATCHLIST ) { + $headers['List-Help'] = 'https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Watchlist'; + } + + return UserMailer::send( $to, $this->from, $this->subject, $body, array( + 'replyTo' => $this->replyto, + 'headers' => $headers, + ) ); } /** @@ -502,7 +531,9 @@ class EmailNotification { $wgContLang->time( $this->timestamp, false, false ) ), $this->body ); - return UserMailer::send( $addresses, $this->from, $this->subject, $body, $this->replyto ); + return UserMailer::send( $addresses, $this->from, $this->subject, $body, array( + 'replyTo' => $this->replyto, + ) ); } } diff --git a/includes/mail/UserMailer.php b/includes/mail/UserMailer.php index 3cabdaeb..7c5f18dd 100644 --- a/includes/mail/UserMailer.php +++ b/includes/mail/UserMailer.php @@ -43,7 +43,7 @@ class UserMailer { protected static function sendWithPear( $mailer, $dest, $headers, $body ) { $mailResult = $mailer->send( $dest, $headers, $body ); - # Based on the result return an error string, + // Based on the result return an error string, if ( PEAR::isError( $mailResult ) ) { wfDebug( "PEAR::Mail failed: " . $mailResult->getMessage() . "\n" ); return Status::newFatal( 'pear-mail-error', $mailResult->getMessage() ); @@ -102,16 +102,35 @@ class UserMailer { * @param MailAddress $from Sender's email * @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 MailAddress $replyto Optional reply-to email (default: null). - * @param string $contentType Optional custom Content-Type (default: text/plain; charset=UTF-8) + * @param array $options: + * 'replyTo' MailAddress + * 'contentType' string default 'text/plain; charset=UTF-8' + * 'headers' array Extra headers to set + * + * Previous versions of this function had $replyto as the 5th argument and $contentType + * as the 6th. These are still supported for backwards compatability, but deprecated. + * * @throws MWException * @throws Exception * @return Status */ - public static function send( $to, $from, $subject, $body, $replyto = null, - $contentType = 'text/plain; charset=UTF-8' - ) { + public static function send( $to, $from, $subject, $body, $options = array() ) { global $wgSMTP, $wgEnotifMaxRecips, $wgAdditionalMailParams, $wgAllowHTMLEmail; + $contentType = 'text/plain; charset=UTF-8'; + $headers = array(); + if ( is_array( $options ) ) { + $replyto = isset( $options['replyTo'] ) ? $options['replyTo'] : null; + $contentType = isset( $options['contentType'] ) ? $options['contentType'] : $contentType; + $headers = isset( $options['headers'] ) ? $options['headers'] : $headers; + } else { + // Old calling style + wfDeprecated( __METHOD__ . ' with $replyto as 5th parameter', '1.26' ); + $replyto = $options; + if ( func_num_args() === 6 ) { + $contentType = func_get_arg( 5 ); + } + } + $mime = null; if ( !is_array( $to ) ) { $to = array( $to ); @@ -147,7 +166,7 @@ class UserMailer { wfDebug( __METHOD__ . ': sending mail to ' . implode( ', ', $to ) . "\n" ); - # Make sure we have at least one address + // Make sure we have at least one address $has_address = false; foreach ( $to as $u ) { if ( $u->address ) { @@ -159,32 +178,32 @@ class UserMailer { return Status::newFatal( 'user-mail-no-addy' ); } - # Forge email headers - # ------------------- - # - # WARNING - # - # DO NOT add To: or Subject: headers at this step. They need to be - # handled differently depending upon the mailer we are going to use. - # - # To: - # PHP mail() first argument is the mail receiver. The argument is - # used as a recipient destination and as a To header. - # - # PEAR mailer has a recipient argument which is only used to - # send the mail. If no To header is given, PEAR will set it to - # to 'undisclosed-recipients:'. - # - # NOTE: To: is for presentation, the actual recipient is specified - # by the mailer using the Rcpt-To: header. - # - # Subject: - # PHP mail() second argument to pass the subject, passing a Subject - # as an additional header will result in a duplicate header. - # - # PEAR mailer should be passed a Subject header. - # - # -- hashar 20120218 + // Forge email headers + // ------------------- + // + // WARNING + // + // DO NOT add To: or Subject: headers at this step. They need to be + // handled differently depending upon the mailer we are going to use. + // + // To: + // PHP mail() first argument is the mail receiver. The argument is + // used as a recipient destination and as a To header. + // + // PEAR mailer has a recipient argument which is only used to + // send the mail. If no To header is given, PEAR will set it to + // to 'undisclosed-recipients:'. + // + // NOTE: To: is for presentation, the actual recipient is specified + // by the mailer using the Rcpt-To: header. + // + // Subject: + // PHP mail() second argument to pass the subject, passing a Subject + // as an additional header will result in a duplicate header. + // + // PEAR mailer should be passed a Subject header. + // + // -- hashar 20120218 $headers['From'] = $from->toString(); $returnPath = $from->address; @@ -192,9 +211,9 @@ class UserMailer { // Hook to generate custom VERP address for 'Return-Path' Hooks::run( 'UserMailerChangeReturnPath', array( $to, &$returnPath ) ); - # Add the envelope sender address using the -f command line option when PHP mail() is used. - # Will default to the $from->address when the UserMailerChangeReturnPath hook fails and the - # generated VERP address when the hook runs effectively. + // Add the envelope sender address using the -f command line option when PHP mail() is used. + // Will default to the $from->address when the UserMailerChangeReturnPath hook fails and the + // generated VERP address when the hook runs effectively. $extraParams .= ' -f ' . $returnPath; $headers['Return-Path'] = $returnPath; @@ -206,9 +225,11 @@ class UserMailer { $headers['Date'] = MWTimestamp::getLocalInstance()->format( 'r' ); $headers['Message-ID'] = self::makeMsgId(); $headers['X-Mailer'] = 'MediaWiki mailer'; + $headers['List-Unsubscribe'] = '<' . SpecialPage::getTitleFor( 'Preferences' ) + ->getFullURL( '', false, PROTO_CANONICAL ) . '>'; - # Line endings need to be different on Unix and Windows due to - # the bug described at http://trac.wordpress.org/ticket/2603 + // 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 { @@ -223,7 +244,10 @@ class UserMailer { // remove the html body for text email fall back $body = $body['text']; } else { - require_once 'Mail/mime.php'; + // Check if pear/mail_mime is already loaded (via composer) + if ( !class_exists( 'Mail_mime' ) ) { + 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'] ); @@ -260,22 +284,22 @@ class UserMailer { } if ( is_array( $wgSMTP ) ) { - # - # PEAR MAILER - # - - if ( !stream_resolve_include_path( 'Mail.php' ) ) { - throw new MWException( 'PEAR mail package is not installed' ); + // Check if pear/mail is already loaded (via composer) + if ( !class_exists( 'Mail' ) ) { + // PEAR MAILER + if ( !stream_resolve_include_path( 'Mail.php' ) ) { + throw new MWException( 'PEAR mail package is not installed' ); + } + require_once 'Mail.php'; } - require_once 'Mail.php'; - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); // Create the mail object using the Mail::factory method $mail_object =& Mail::factory( 'smtp', $wgSMTP ); if ( PEAR::isError( $mail_object ) ) { wfDebug( "PEAR::Mail factory failed: " . $mail_object->getMessage() . "\n" ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); return Status::newFatal( 'pear-mail-error', $mail_object->getMessage() ); } @@ -283,28 +307,28 @@ class UserMailer { $headers['Subject'] = self::quotedPrintable( $subject ); - # When sending only to one recipient, shows it its email using To: + // When sending only to one recipient, shows it its email using To: if ( count( $to ) == 1 ) { $headers['To'] = $to[0]->toString(); } - # Split jobs since SMTP servers tends to limit the maximum - # number of possible recipients. + // Split jobs since SMTP servers tends to limit the maximum + // number of possible recipients. $chunks = array_chunk( $to, $wgEnotifMaxRecips ); foreach ( $chunks as $chunk ) { $status = self::sendWithPear( $mail_object, $chunk, $headers, $body ); - # FIXME : some chunks might be sent while others are not! + // FIXME : some chunks might be sent while others are not! if ( !$status->isOK() ) { - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); return $status; } } - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); return Status::newGood(); } else { - # - # PHP mail() - # + // + // PHP mail() + // if ( count( $to ) > 1 ) { $headers['To'] = 'undisclosed-recipients:;'; } @@ -400,7 +424,7 @@ class UserMailer { * @return string */ public static function quotedPrintable( $string, $charset = '' ) { - # Probably incomplete; see RFC 2045 + // Probably incomplete; see RFC 2045 if ( empty( $charset ) ) { $charset = 'UTF-8'; } |