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 $wgErrorString = '';
00192 $html_errors = ini_get( 'html_errors' );
00193 ini_set( 'html_errors', '0' );
00194 set_error_handler( array( 'UserMailer', 'errorHandler' ) );
00195 wfDebug( "Sending mail via internal mail() function\n" );
00196
00197 if (function_exists('mail')) {
00198 if (is_array($to)) {
00199 foreach ($to as $recip) {
00200 $sent = mail( $recip->toString(), wfQuotedPrintable( $subject ), $body, $headers );
00201 }
00202 } else {
00203 $sent = mail( $to->toString(), wfQuotedPrintable( $subject ), $body, $headers );
00204 }
00205 } else {
00206 $wgErrorString = 'PHP is not configured to send mail';
00207 }
00208
00209 restore_error_handler();
00210 ini_set( 'html_errors', $html_errors );
00211
00212 if ( $wgErrorString ) {
00213 wfDebug( "Error sending mail: $wgErrorString\n" );
00214 return new WikiError( $wgErrorString );
00215 } elseif (! $sent) {
00216
00217 wfDebug( "Error sending mail\n" );
00218 return new WikiError( 'mailer error' );
00219 } else {
00220 return true;
00221 }
00222 }
00223 }
00224
00231 static function errorHandler( $code, $string ) {
00232 global $wgErrorString;
00233 $wgErrorString = preg_replace( '/^mail\(\)(\s*\[.*?\])?: /', '', $string );
00234 }
00235
00239 static function rfc822Phrase( $phrase ) {
00240 $phrase = strtr( $phrase, array( "\r" => '', "\n" => '', '"' => '' ) );
00241 return '"' . $phrase . '"';
00242 }
00243 }
00244
00265 class EmailNotification {
00266 private $to, $subject, $body, $replyto, $from;
00267 private $user, $title, $timestamp, $summary, $minorEdit, $oldid, $composed_common, $editor;
00268 private $mailTargets = array();
00269
00283 function notifyOnPageChange($editor, $title, $timestamp, $summary, $minorEdit, $oldid = false) {
00284 global $wgEnotifUseJobQ, $wgEnotifWatchlist, $wgShowUpdatedMarker;
00285
00286 if ($title->getNamespace() < 0)
00287 return;
00288
00289
00290 $watchers = array();
00291 if ($wgEnotifWatchlist || $wgShowUpdatedMarker) {
00292 $dbw = wfGetDB( DB_MASTER );
00293 $res = $dbw->select( array( 'watchlist' ),
00294 array( 'wl_user' ),
00295 array(
00296 'wl_title' => $title->getDBkey(),
00297 'wl_namespace' => $title->getNamespace(),
00298 'wl_user != ' . intval( $editor->getID() ),
00299 'wl_notificationtimestamp IS NULL',
00300 ), __METHOD__
00301 );
00302 while ($row = $dbw->fetchObject( $res ) ) {
00303 $watchers[] = intval( $row->wl_user );
00304 }
00305 if ($watchers) {
00306
00307
00308 $dbw->begin();
00309 $dbw->update( 'watchlist',
00310 array(
00311 'wl_notificationtimestamp' => $dbw->timestamp( $timestamp )
00312 ), array(
00313 'wl_title' => $title->getDBkey(),
00314 'wl_namespace' => $title->getNamespace(),
00315 'wl_user' => $watchers
00316 ), __METHOD__
00317 );
00318 $dbw->commit();
00319 }
00320 }
00321
00322 if ($wgEnotifUseJobQ) {
00323 $params = array(
00324 "editor" => $editor->getName(),
00325 "editorID" => $editor->getID(),
00326 "timestamp" => $timestamp,
00327 "summary" => $summary,
00328 "minorEdit" => $minorEdit,
00329 "oldid" => $oldid,
00330 "watchers" => $watchers);
00331 $job = new EnotifNotifyJob( $title, $params );
00332 $job->insert();
00333 } else {
00334 $this->actuallyNotifyOnPageChange( $editor, $title, $timestamp, $summary, $minorEdit, $oldid, $watchers );
00335 }
00336
00337 }
00338
00339
00340
00341
00342
00343
00344
00345
00346
00347
00348
00349
00350
00351
00352
00353 function actuallyNotifyOnPageChange($editor, $title, $timestamp, $summary, $minorEdit, $oldid, $watchers) {
00354 # we use $wgPasswordSender as sender's address
00355 global $wgEnotifWatchlist;
00356 global $wgEnotifMinorEdits, $wgEnotifUserTalk;
00357 global $wgEnotifImpersonal;
00358
00359 wfProfileIn( __METHOD__ );
00360
00361 # The following code is only run, if several conditions are met:
00362 # 1. EmailNotification for pages (other than user_talk pages) must be enabled
00363 # 2. minor edits (changes) are only regarded if the global flag indicates so
00364
00365 $isUserTalkPage = ($title->getNamespace() == NS_USER_TALK);
00366 $enotifusertalkpage = ($isUserTalkPage && $wgEnotifUserTalk);
00367 $enotifwatchlistpage = $wgEnotifWatchlist;
00368
00369 $this->title = $title;
00370 $this->timestamp = $timestamp;
00371 $this->summary = $summary;
00372 $this->minorEdit = $minorEdit;
00373 $this->oldid = $oldid;
00374 $this->editor = $editor;
00375 $this->composed_common = false;
00376
00377 $userTalkId = false;
00378
00379 if ( !$minorEdit || ($wgEnotifMinorEdits && !$editor->isAllowed('nominornewtalk') ) ) {
00380 if ( $wgEnotifUserTalk && $isUserTalkPage ) {
00381 $targetUser = User::newFromName( $title->getText() );
00382 if ( !$targetUser || $targetUser->isAnon() ) {
00383 wfDebug( __METHOD__.": user talk page edited, but user does not exist\n" );
00384 } elseif ( $targetUser->getId() == $editor->getId() ) {
00385 wfDebug( __METHOD__.": user edited their own talk page, no notification sent\n" );
00386 } elseif( $targetUser->getOption( 'enotifusertalkpages' ) ) {
00387 if( $targetUser->isEmailConfirmed() ) {
00388 wfDebug( __METHOD__.": sending talk page update notification\n" );
00389 $this->compose( $targetUser );
00390 $userTalkId = $targetUser->getId();
00391 } else {
00392 wfDebug( __METHOD__.": talk page owner doesn't have validated email\n" );
00393 }
00394 } else {
00395 wfDebug( __METHOD__.": talk page owner doesn't want notifications\n" );
00396 }
00397 }
00398
00399 if ( $wgEnotifWatchlist ) {
00400
00401 $userArray = UserArray::newFromIDs( $watchers );
00402 foreach ( $userArray as $watchingUser ) {
00403 if ( $watchingUser->getOption( 'enotifwatchlistpages' ) &&
00404 ( !$minorEdit || $watchingUser->getOption('enotifminoredits') ) &&
00405 $watchingUser->isEmailConfirmed() &&
00406 $watchingUser->getID() != $userTalkId )
00407 {
00408 $this->compose( $watchingUser );
00409 }
00410 }
00411 }
00412 }
00413
00414 global $wgUsersNotifiedOnAllChanges;
00415 foreach ( $wgUsersNotifiedOnAllChanges as $name ) {
00416 $user = User::newFromName( $name );
00417 $this->compose( $user );
00418 }
00419
00420 $this->sendMails();
00421 wfProfileOut( __METHOD__ );
00422 }
00423
00427 function composeCommonMailtext() {
00428 global $wgPasswordSender, $wgNoReplyAddress;
00429 global $wgEnotifFromEditor, $wgEnotifRevealEditorAddress;
00430 global $wgEnotifImpersonal, $wgEnotifUseRealName;
00431
00432 $this->composed_common = true;
00433
00434 $summary = ($this->summary == '') ? ' - ' : $this->summary;
00435 $medit = ($this->minorEdit) ? wfMsg( 'minoredit' ) : '';
00436
00437 # You as the WikiAdmin and Sysops can make use of plenty of
00438 # named variables when composing your notification emails while
00439 # simply editing the Meta pages
00440
00441 $subject = wfMsgForContent( 'enotif_subject' );
00442 $body = wfMsgForContent( 'enotif_body' );
00443 $from = '';
00444 $replyto = '';
00445 $keys = array();
00446
00447 if( $this->oldid ) {
00448 $difflink = $this->title->getFullUrl( 'diff=0&oldid=' . $this->oldid );
00449 $keys['$NEWPAGE'] = wfMsgForContent( 'enotif_lastvisited', $difflink );
00450 $keys['$OLDID'] = $this->oldid;
00451 $keys['$CHANGEDORCREATED'] = wfMsgForContent( 'changed' );
00452 } else {
00453 $keys['$NEWPAGE'] = wfMsgForContent( 'enotif_newpagetext' );
00454 # clear $OLDID placeholder in the message template
00455 $keys['$OLDID'] = '';
00456 $keys['$CHANGEDORCREATED'] = wfMsgForContent( 'created' );
00457 }
00458
00459 if ($wgEnotifImpersonal && $this->oldid)
00460
00461
00462
00463
00464 $keys['$NEWPAGE'] = wfMsgForContent('enotif_lastdiff',
00465 $this->title->getFullURL("oldid={$this->oldid}&diff=prev"));
00466
00467 $body = strtr( $body, $keys );
00468 $pagetitle = $this->title->getPrefixedText();
00469 $keys['$PAGETITLE'] = $pagetitle;
00470 $keys['$PAGETITLE_URL'] = $this->title->getFullUrl();
00471
00472 $keys['$PAGEMINOREDIT'] = $medit;
00473 $keys['$PAGESUMMARY'] = $summary;
00474
00475 $subject = strtr( $subject, $keys );
00476
00477 # Reveal the page editor's address as REPLY-TO address only if
00478 # the user has not opted-out and the option is enabled at the
00479 # global configuration level.
00480 $editor = $this->editor;
00481 $name = $wgEnotifUseRealName ? $editor->getRealName() : $editor->getName();
00482 $adminAddress = new MailAddress( $wgPasswordSender, 'WikiAdmin' );
00483 $editorAddress = new MailAddress( $editor );
00484 if( $wgEnotifRevealEditorAddress
00485 && ( $editor->getEmail() != '' )
00486 && $editor->getOption( 'enotifrevealaddr' ) ) {
00487 if( $wgEnotifFromEditor ) {
00488 $from = $editorAddress;
00489 } else {
00490 $from = $adminAddress;
00491 $replyto = $editorAddress;
00492 }
00493 } else {
00494 $from = $adminAddress;
00495 $replyto = new MailAddress( $wgNoReplyAddress );
00496 }
00497
00498 if( $editor->isIP( $name ) ) {
00499 #real anon (user:xxx.xxx.xxx.xxx)
00500 $utext = wfMsgForContent('enotif_anon_editor', $name);
00501 $subject = str_replace('$PAGEEDITOR', $utext, $subject);
00502 $keys['$PAGEEDITOR'] = $utext;
00503 $keys['$PAGEEDITOR_EMAIL'] = wfMsgForContent( 'noemailtitle' );
00504 } else {
00505 $subject = str_replace('$PAGEEDITOR', $name, $subject);
00506 $keys['$PAGEEDITOR'] = $name;
00507 $emailPage = SpecialPage::getSafeTitleFor( 'Emailuser', $name );
00508 $keys['$PAGEEDITOR_EMAIL'] = $emailPage->getFullUrl();
00509 }
00510 $userPage = $editor->getUserPage();
00511 $keys['$PAGEEDITOR_WIKI'] = $userPage->getFullUrl();
00512 $body = strtr( $body, $keys );
00513 $body = wordwrap( $body, 72 );
00514
00515 # now save this as the constant user-independent part of the message
00516 $this->from = $from;
00517 $this->replyto = $replyto;
00518 $this->subject = $subject;
00519 $this->body = $body;
00520 }
00521
00528 function compose( $user ) {
00529 global $wgEnotifImpersonal;
00530
00531 if ( !$this->composed_common )
00532 $this->composeCommonMailtext();
00533
00534 if ( $wgEnotifImpersonal ) {
00535 $this->mailTargets[] = new MailAddress( $user );
00536 } else {
00537 $this->sendPersonalised( $user );
00538 }
00539 }
00540
00544 function sendMails() {
00545 global $wgEnotifImpersonal;
00546 if ( $wgEnotifImpersonal ) {
00547 $this->sendImpersonal( $this->mailTargets );
00548 }
00549 }
00550
00561 function sendPersonalised( $watchingUser ) {
00562 global $wgContLang, $wgEnotifUseRealName;
00563
00564
00565
00566 $to = new MailAddress( $watchingUser );
00567 $name = $wgEnotifUseRealName ? $watchingUser->getRealName() : $watchingUser->getName();
00568 $body = str_replace( '$WATCHINGUSERNAME', $name , $this->body );
00569
00570 $timecorrection = $watchingUser->getOption( 'timecorrection' );
00571
00572 # $PAGEEDITDATE is the time and date of the page change
00573 # expressed in terms of individual local time of the notification
00574 # recipient, i.e. watching user
00575 $body = str_replace('$PAGEEDITDATE',
00576 $wgContLang->timeanddate( $this->timestamp, true, false, $timecorrection ),
00577 $body);
00578
00579 return UserMailer::send($to, $this->from, $this->subject, $body, $this->replyto);
00580 }
00581
00586 function sendImpersonal( $addresses ) {
00587 global $wgContLang;
00588
00589 if (empty($addresses))
00590 return;
00591
00592 $body = str_replace(
00593 array( '$WATCHINGUSERNAME',
00594 '$PAGEEDITDATE'),
00595 array( wfMsgForContent('enotif_impersonal_salutation'),
00596 $wgContLang->timeanddate($this->timestamp, true, false, false)),
00597 $this->body);
00598
00599 return UserMailer::send($addresses, $this->from, $this->subject, $body, $this->replyto);
00600 }
00601
00602 } # end of class EmailNotification
00603
00607 function wfRFC822Phrase( $s ) {
00608 return UserMailer::rfc822Phrase( $s );
00609 }
00610
00611 function userMailer( $to, $from, $subject, $body, $replyto=null ) {
00612 return UserMailer::send( $to, $from, $subject, $body, $replyto );
00613 }