00001 <?php
00030 class MailAddress {
00035 function __construct( $address, $name = null, $realName = null ) {
00036 if( is_object( $address ) && $address instanceof User ) {
00037 $this->address = $address->getEmail();
00038 $this->name = $address->getName();
00039 $this->realName = $address->getRealName();
00040 } else {
00041 $this->address = strval( $address );
00042 $this->name = strval( $name );
00043 $this->realName = strval( $realName );
00044 }
00045 }
00046
00051 function toString() {
00052 # PHP's mail() implementation under Windows is somewhat shite, and
00053 # can't handle "Joe Bloggs <joe@bloggs.com>" format email addresses,
00054 # so don't bother generating them
00055 if( $this->name != '' && !wfIsWindows() ) {
00056 global $wgEnotifUseRealName;
00057 $name = ( $wgEnotifUseRealName && $this->realName ) ? $this->realName : $this->name;
00058 $quoted = wfQuotedPrintable( $name );
00059 if( strpos( $quoted, '.' ) !== false || strpos( $quoted, ',' ) !== false ) {
00060 $quoted = '"' . $quoted . '"';
00061 }
00062 return "$quoted <{$this->address}>";
00063 } else {
00064 return $this->address;
00065 }
00066 }
00067
00068 function __toString() {
00069 return $this->toString();
00070 }
00071 }
00072
00073
00077 class UserMailer {
00081 protected static function sendWithPear($mailer, $dest, $headers, $body)
00082 {
00083 $mailResult = $mailer->send($dest, $headers, $body);
00084
00085 # Based on the result return an error string,
00086 if( PEAR::isError( $mailResult ) ) {
00087 wfDebug( "PEAR::Mail failed: " . $mailResult->getMessage() . "\n" );
00088 return new WikiError( $mailResult->getMessage() );
00089 } else {
00090 return true;
00091 }
00092 }
00093
00108 static function send( $to, $from, $subject, $body, $replyto=null, $contentType=null ) {
00109 global $wgSMTP, $wgOutputEncoding, $wgErrorString, $wgEnotifImpersonal;
00110 global $wgEnotifMaxRecips;
00111
00112 if ( is_array( $to ) ) {
00113 wfDebug( __METHOD__.': sending mail to ' . implode( ',', $to ) . "\n" );
00114 } else {
00115 wfDebug( __METHOD__.': sending mail to ' . implode( ',', array( $to->toString() ) ) . "\n" );
00116 }
00117
00118 if (is_array( $wgSMTP )) {
00119 require_once( 'Mail.php' );
00120
00121 $msgid = str_replace(" ", "_", microtime());
00122 if (function_exists('posix_getpid'))
00123 $msgid .= '.' . posix_getpid();
00124
00125 if (is_array($to)) {
00126 $dest = array();
00127 foreach ($to as $u)
00128 $dest[] = $u->address;
00129 } else
00130 $dest = $to->address;
00131
00132 $headers['From'] = $from->toString();
00133
00134 if ($wgEnotifImpersonal) {
00135 $headers['To'] = 'undisclosed-recipients:;';
00136 }
00137 else {
00138 $headers['To'] = implode( ", ", (array )$dest );
00139 }
00140
00141 if ( $replyto ) {
00142 $headers['Reply-To'] = $replyto->toString();
00143 }
00144 $headers['Subject'] = wfQuotedPrintable( $subject );
00145 $headers['Date'] = date( 'r' );
00146 $headers['MIME-Version'] = '1.0';
00147 $headers['Content-type'] = (is_null($contentType) ?
00148 'text/plain; charset='.$wgOutputEncoding : $contentType);
00149 $headers['Content-transfer-encoding'] = '8bit';
00150 $headers['Message-ID'] = "<$msgid@" . $wgSMTP['IDHost'] . '>';
00151 $headers['X-Mailer'] = 'MediaWiki mailer';
00152
00153
00154 $mail_object =& Mail::factory('smtp', $wgSMTP);
00155 if( PEAR::isError( $mail_object ) ) {
00156 wfDebug( "PEAR::Mail factory failed: " . $mail_object->getMessage() . "\n" );
00157 return new WikiError( $mail_object->getMessage() );
00158 }
00159
00160 wfDebug( "Sending mail via PEAR::Mail to $dest\n" );
00161 $chunks = array_chunk( (array)$dest, $wgEnotifMaxRecips );
00162 foreach ($chunks as $chunk) {
00163 $e = self::sendWithPear($mail_object, $chunk, $headers, $body);
00164 if( WikiError::isError( $e ) )
00165 return $e;
00166 }
00167 } else {
00168 # In the following $headers = expression we removed "Reply-To: {$from}\r\n" , because it is treated differently
00169 # (fifth parameter of the PHP mail function, see some lines below)
00170
00171 # Line endings need to be different on Unix and Windows due to
00172 # the bug described at http://trac.wordpress.org/ticket/2603
00173 if ( wfIsWindows() ) {
00174 $body = str_replace( "\n", "\r\n", $body );
00175 $endl = "\r\n";
00176 } else {
00177 $endl = "\n";
00178 }
00179 $ctype = (is_null($contentType) ?
00180 'text/plain; charset='.$wgOutputEncoding : $contentType);
00181 $headers =
00182 "MIME-Version: 1.0$endl" .
00183 "Content-type: $ctype$endl" .
00184 "Content-Transfer-Encoding: 8bit$endl" .
00185 "X-Mailer: MediaWiki mailer$endl".
00186 'From: ' . $from->toString();
00187 if ($replyto) {
00188 $headers .= "{$endl}Reply-To: " . $replyto->toString();
00189 }
00190
00191 wfDebug( "Sending mail via internal mail() function\n" );
00192
00193 $wgErrorString = '';
00194 $html_errors = ini_get( 'html_errors' );
00195 ini_set( 'html_errors', '0' );
00196 set_error_handler( array( 'UserMailer', 'errorHandler' ) );
00197
00198 if (function_exists('mail')) {
00199 if (is_array($to)) {
00200 foreach ($to as $recip) {
00201 $sent = mail( $recip->toString(), wfQuotedPrintable( $subject ), $body, $headers );
00202 }
00203 } else {
00204 $sent = mail( $to->toString(), wfQuotedPrintable( $subject ), $body, $headers );
00205 }
00206 } else {
00207 $wgErrorString = 'PHP is not configured to send mail';
00208 }
00209
00210 restore_error_handler();
00211 ini_set( 'html_errors', $html_errors );
00212
00213 if ( $wgErrorString ) {
00214 wfDebug( "Error sending mail: $wgErrorString\n" );
00215 return new WikiError( $wgErrorString );
00216 } elseif (! $sent) {
00217
00218 wfDebug( "Error sending mail\n" );
00219 return new WikiError( 'mailer error' );
00220 } else {
00221 return true;
00222 }
00223 }
00224 }
00225
00232 static function errorHandler( $code, $string ) {
00233 global $wgErrorString;
00234 $wgErrorString = preg_replace( '/^mail\(\)(\s*\[.*?\])?: /', '', $string );
00235 }
00236
00240 static function rfc822Phrase( $phrase ) {
00241 $phrase = strtr( $phrase, array( "\r" => '', "\n" => '', '"' => '' ) );
00242 return '"' . $phrase . '"';
00243 }
00244 }
00245
00266 class EmailNotification {
00267 protected $to, $subject, $body, $replyto, $from;
00268 protected $user, $title, $timestamp, $summary, $minorEdit, $oldid, $composed_common, $editor;
00269 protected $mailTargets = array();
00270
00284 function notifyOnPageChange($editor, $title, $timestamp, $summary, $minorEdit, $oldid = false) {
00285 global $wgEnotifUseJobQ, $wgEnotifWatchlist, $wgShowUpdatedMarker;
00286
00287 if ($title->getNamespace() < 0)
00288 return;
00289
00290
00291 $watchers = array();
00292 if ($wgEnotifWatchlist || $wgShowUpdatedMarker) {
00293 $dbw = wfGetDB( DB_MASTER );
00294 $res = $dbw->select( array( 'watchlist' ),
00295 array( 'wl_user' ),
00296 array(
00297 'wl_title' => $title->getDBkey(),
00298 'wl_namespace' => $title->getNamespace(),
00299 'wl_user != ' . intval( $editor->getID() ),
00300 'wl_notificationtimestamp IS NULL',
00301 ), __METHOD__
00302 );
00303 while ($row = $dbw->fetchObject( $res ) ) {
00304 $watchers[] = intval( $row->wl_user );
00305 }
00306 if ($watchers) {
00307
00308
00309 $dbw->begin();
00310 $dbw->update( 'watchlist',
00311 array(
00312 'wl_notificationtimestamp' => $dbw->timestamp( $timestamp )
00313 ), array(
00314 'wl_title' => $title->getDBkey(),
00315 'wl_namespace' => $title->getNamespace(),
00316 'wl_user' => $watchers
00317 ), __METHOD__
00318 );
00319 $dbw->commit();
00320 }
00321 }
00322
00323 if ($wgEnotifUseJobQ) {
00324 $params = array(
00325 "editor" => $editor->getName(),
00326 "editorID" => $editor->getID(),
00327 "timestamp" => $timestamp,
00328 "summary" => $summary,
00329 "minorEdit" => $minorEdit,
00330 "oldid" => $oldid,
00331 "watchers" => $watchers);
00332 $job = new EnotifNotifyJob( $title, $params );
00333 $job->insert();
00334 } else {
00335 $this->actuallyNotifyOnPageChange( $editor, $title, $timestamp, $summary, $minorEdit, $oldid, $watchers );
00336 }
00337
00338 }
00339
00340
00341
00342
00343
00344
00345
00346
00347
00348
00349
00350
00351
00352
00353
00354 function actuallyNotifyOnPageChange($editor, $title, $timestamp, $summary, $minorEdit, $oldid, $watchers) {
00355 # we use $wgPasswordSender as sender's address
00356 global $wgEnotifWatchlist;
00357 global $wgEnotifMinorEdits, $wgEnotifUserTalk;
00358 global $wgEnotifImpersonal;
00359
00360 wfProfileIn( __METHOD__ );
00361
00362 # The following code is only run, if several conditions are met:
00363 # 1. EmailNotification for pages (other than user_talk pages) must be enabled
00364 # 2. minor edits (changes) are only regarded if the global flag indicates so
00365
00366 $isUserTalkPage = ($title->getNamespace() == NS_USER_TALK);
00367 $enotifusertalkpage = ($isUserTalkPage && $wgEnotifUserTalk);
00368 $enotifwatchlistpage = $wgEnotifWatchlist;
00369
00370 $this->title = $title;
00371 $this->timestamp = $timestamp;
00372 $this->summary = $summary;
00373 $this->minorEdit = $minorEdit;
00374 $this->oldid = $oldid;
00375 $this->editor = $editor;
00376 $this->composed_common = false;
00377
00378 $userTalkId = false;
00379
00380 if ( !$minorEdit || ($wgEnotifMinorEdits && !$editor->isAllowed('nominornewtalk') ) ) {
00381 if ( $wgEnotifUserTalk && $isUserTalkPage ) {
00382 $targetUser = User::newFromName( $title->getText() );
00383 if ( !$targetUser || $targetUser->isAnon() ) {
00384 wfDebug( __METHOD__.": user talk page edited, but user does not exist\n" );
00385 } elseif ( $targetUser->getId() == $editor->getId() ) {
00386 wfDebug( __METHOD__.": user edited their own talk page, no notification sent\n" );
00387 } elseif( $targetUser->getOption( 'enotifusertalkpages' ) ) {
00388 if( $targetUser->isEmailConfirmed() ) {
00389 wfDebug( __METHOD__.": sending talk page update notification\n" );
00390 $this->compose( $targetUser );
00391 $userTalkId = $targetUser->getId();
00392 } else {
00393 wfDebug( __METHOD__.": talk page owner doesn't have validated email\n" );
00394 }
00395 } else {
00396 wfDebug( __METHOD__.": talk page owner doesn't want notifications\n" );
00397 }
00398 }
00399
00400 if ( $wgEnotifWatchlist ) {
00401
00402 $userArray = UserArray::newFromIDs( $watchers );
00403 foreach ( $userArray as $watchingUser ) {
00404 if ( $watchingUser->getOption( 'enotifwatchlistpages' ) &&
00405 ( !$minorEdit || $watchingUser->getOption('enotifminoredits') ) &&
00406 $watchingUser->isEmailConfirmed() &&
00407 $watchingUser->getID() != $userTalkId )
00408 {
00409 $this->compose( $watchingUser );
00410 }
00411 }
00412 }
00413 }
00414
00415 global $wgUsersNotifiedOnAllChanges;
00416 foreach ( $wgUsersNotifiedOnAllChanges as $name ) {
00417 $user = User::newFromName( $name );
00418 $this->compose( $user );
00419 }
00420
00421 $this->sendMails();
00422 wfProfileOut( __METHOD__ );
00423 }
00424
00428 function composeCommonMailtext() {
00429 global $wgPasswordSender, $wgNoReplyAddress;
00430 global $wgEnotifFromEditor, $wgEnotifRevealEditorAddress;
00431 global $wgEnotifImpersonal, $wgEnotifUseRealName;
00432
00433 $this->composed_common = true;
00434
00435 $summary = ($this->summary == '') ? ' - ' : $this->summary;
00436 $medit = ($this->minorEdit) ? wfMsg( 'minoredit' ) : '';
00437
00438 # You as the WikiAdmin and Sysops can make use of plenty of
00439 # named variables when composing your notification emails while
00440 # simply editing the Meta pages
00441
00442 $subject = wfMsgForContent( 'enotif_subject' );
00443 $body = wfMsgForContent( 'enotif_body' );
00444 $from = '';
00445 $replyto = '';
00446 $keys = array();
00447
00448 if( $this->oldid ) {
00449 $difflink = $this->title->getFullUrl( 'diff=0&oldid=' . $this->oldid );
00450 $keys['$NEWPAGE'] = wfMsgForContent( 'enotif_lastvisited', $difflink );
00451 $keys['$OLDID'] = $this->oldid;
00452 $keys['$CHANGEDORCREATED'] = wfMsgForContent( 'changed' );
00453 } else {
00454 $keys['$NEWPAGE'] = wfMsgForContent( 'enotif_newpagetext' );
00455 # clear $OLDID placeholder in the message template
00456 $keys['$OLDID'] = '';
00457 $keys['$CHANGEDORCREATED'] = wfMsgForContent( 'created' );
00458 }
00459
00460 if ($wgEnotifImpersonal && $this->oldid)
00461
00462
00463
00464
00465 $keys['$NEWPAGE'] = wfMsgForContent('enotif_lastdiff',
00466 $this->title->getFullURL("oldid={$this->oldid}&diff=prev"));
00467
00468 $body = strtr( $body, $keys );
00469 $pagetitle = $this->title->getPrefixedText();
00470 $keys['$PAGETITLE'] = $pagetitle;
00471 $keys['$PAGETITLE_URL'] = $this->title->getFullUrl();
00472
00473 $keys['$PAGEMINOREDIT'] = $medit;
00474 $keys['$PAGESUMMARY'] = $summary;
00475 $keys['$UNWATCHURL'] = $this->title->getFullUrl( 'action=unwatch' );
00476
00477 $subject = strtr( $subject, $keys );
00478
00479 # Reveal the page editor's address as REPLY-TO address only if
00480 # the user has not opted-out and the option is enabled at the
00481 # global configuration level.
00482 $editor = $this->editor;
00483 $name = $wgEnotifUseRealName ? $editor->getRealName() : $editor->getName();
00484 $adminAddress = new MailAddress( $wgPasswordSender, 'WikiAdmin' );
00485 $editorAddress = new MailAddress( $editor );
00486 if( $wgEnotifRevealEditorAddress
00487 && ( $editor->getEmail() != '' )
00488 && $editor->getOption( 'enotifrevealaddr' ) ) {
00489 if( $wgEnotifFromEditor ) {
00490 $from = $editorAddress;
00491 } else {
00492 $from = $adminAddress;
00493 $replyto = $editorAddress;
00494 }
00495 } else {
00496 $from = $adminAddress;
00497 $replyto = new MailAddress( $wgNoReplyAddress );
00498 }
00499
00500 if( $editor->isIP( $name ) ) {
00501 #real anon (user:xxx.xxx.xxx.xxx)
00502 $utext = wfMsgForContent('enotif_anon_editor', $name);
00503 $subject = str_replace('$PAGEEDITOR', $utext, $subject);
00504 $keys['$PAGEEDITOR'] = $utext;
00505 $keys['$PAGEEDITOR_EMAIL'] = wfMsgForContent( 'noemailtitle' );
00506 } else {
00507 $subject = str_replace('$PAGEEDITOR', $name, $subject);
00508 $keys['$PAGEEDITOR'] = $name;
00509 $emailPage = SpecialPage::getSafeTitleFor( 'Emailuser', $name );
00510 $keys['$PAGEEDITOR_EMAIL'] = $emailPage->getFullUrl();
00511 }
00512 $userPage = $editor->getUserPage();
00513 $keys['$PAGEEDITOR_WIKI'] = $userPage->getFullUrl();
00514 $body = strtr( $body, $keys );
00515 $body = wordwrap( $body, 72 );
00516
00517 # now save this as the constant user-independent part of the message
00518 $this->from = $from;
00519 $this->replyto = $replyto;
00520 $this->subject = $subject;
00521 $this->body = $body;
00522 }
00523
00530 function compose( $user ) {
00531 global $wgEnotifImpersonal;
00532
00533 if ( !$this->composed_common )
00534 $this->composeCommonMailtext();
00535
00536 if ( $wgEnotifImpersonal ) {
00537 $this->mailTargets[] = new MailAddress( $user );
00538 } else {
00539 $this->sendPersonalised( $user );
00540 }
00541 }
00542
00546 function sendMails() {
00547 global $wgEnotifImpersonal;
00548 if ( $wgEnotifImpersonal ) {
00549 $this->sendImpersonal( $this->mailTargets );
00550 }
00551 }
00552
00563 function sendPersonalised( $watchingUser ) {
00564 global $wgContLang, $wgEnotifUseRealName;
00565
00566
00567
00568 $to = new MailAddress( $watchingUser );
00569 $name = $wgEnotifUseRealName ? $watchingUser->getRealName() : $watchingUser->getName();
00570 $body = str_replace( '$WATCHINGUSERNAME', $name , $this->body );
00571
00572 $timecorrection = $watchingUser->getOption( 'timecorrection' );
00573
00574 # $PAGEEDITDATE is the time and date of the page change
00575 # expressed in terms of individual local time of the notification
00576 # recipient, i.e. watching user
00577 $body = str_replace(
00578 array( '$PAGEEDITDATEANDTIME',
00579 '$PAGEEDITDATE',
00580 '$PAGEEDITTIME' ),
00581 array( $wgContLang->timeanddate( $this->timestamp, true, false, $timecorrection ),
00582 $wgContLang->date( $this->timestamp, true, false, $timecorrection ),
00583 $wgContLang->time( $this->timestamp, true, false, $timecorrection ) ),
00584 $body);
00585
00586 return UserMailer::send($to, $this->from, $this->subject, $body, $this->replyto);
00587 }
00588
00593 function sendImpersonal( $addresses ) {
00594 global $wgContLang;
00595
00596 if (empty($addresses))
00597 return;
00598
00599 $body = str_replace(
00600 array( '$WATCHINGUSERNAME',
00601 '$PAGEEDITDATE'),
00602 array( wfMsgForContent('enotif_impersonal_salutation'),
00603 $wgContLang->timeanddate($this->timestamp, true, false, false)),
00604 $this->body);
00605
00606 return UserMailer::send($addresses, $this->from, $this->subject, $body, $this->replyto);
00607 }
00608
00609 } # end of class EmailNotification
00610
00614 function wfRFC822Phrase( $s ) {
00615 return UserMailer::rfc822Phrase( $s );
00616 }
00617
00618 function userMailer( $to, $from, $subject, $body, $replyto=null ) {
00619 return UserMailer::send( $to, $from, $subject, $body, $replyto );
00620 }