r44960 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r44959‎ | r44960 | r44961 >
Date:18:08, 23 December 2008
Author:brion
Status:ok
Tags:
Comment:
Revert r44702, r44703, r44704 (wfInvoke and UserMailer refactor based on it) and r44715, r44721 (cleanup thereof)
As Tim notes, the weird callback setup in $wgHooks isn't really something we want to replicate or ever rely on ever again, as PHP's native callback syntax already handles things fine and is more consistent (and used extensively in the rest of MediaWiki).
May be other remaining issues with the refactor on top of bugs already discovered, but if it's going to be refactored to use callbacks it should be done using regular callbacks.
Modified paths:
  • /trunk/phase3/includes/AutoLoader.php (modified) (history)
  • /trunk/phase3/includes/EnotifNotifyJob.php (modified) (history)
  • /trunk/phase3/includes/Hooks.php (modified) (history)
  • /trunk/phase3/includes/RecentChange.php (modified) (history)
  • /trunk/phase3/includes/UserMailer.php (modified) (history)

Diff [purge]

Index: trunk/phase3/includes/RecentChange.php
@@ -184,8 +184,10 @@
185185 $editor = ($wgUser->getName() == $this->mAttribs['rc_user_text']) ?
186186 $wgUser : User::newFromName( $this->mAttribs['rc_user_text'], false );
187187 }
 188+ # FIXME: this would be better as an extension hook
 189+ $enotif = new EmailNotification();
188190 $title = Title::makeTitle( $this->mAttribs['rc_namespace'], $this->mAttribs['rc_title'] );
189 - PageChangeNotification::notifyOnPageChange( $editor, $title,
 191+ $enotif->notifyOnPageChange( $editor, $title,
190192 $this->mAttribs['rc_timestamp'],
191193 $this->mAttribs['rc_comment'],
192194 $this->mAttribs['rc_minor'],
Index: trunk/phase3/includes/EnotifNotifyJob.php
@@ -12,6 +12,7 @@
1313 }
1414
1515 function run() {
 16+ $enotif = new EmailNotification();
1617 // Get the user from ID (rename safe). Anons are 0, so defer to name.
1718 if( isset($this->params['editorID']) && $this->params['editorID'] ) {
1819 $editor = User::newFromId( $this->params['editorID'] );
@@ -19,7 +20,7 @@
2021 } else {
2122 $editor = User::newFromName( $this->params['editor'], false );
2223 }
23 - PageChangeNotification::actuallyNotifyOnPageChange(
 24+ $enotif->actuallyNotifyOnPageChange(
2425 $editor,
2526 $this->title,
2627 $this->params['timestamp'],
Index: trunk/phase3/includes/UserMailer.php
@@ -242,111 +242,31 @@
243243 }
244244 }
245245
246 -
 246+/**
 247+ * This module processes the email notifications when the current page is
 248+ * changed. It looks up the table watchlist to find out which users are watching
 249+ * that page.
 250+ *
 251+ * The current implementation sends independent emails to each watching user for
 252+ * the following reason:
 253+ *
 254+ * - Each watching user will be notified about the page edit time expressed in
 255+ * his/her local time (UTC is shown additionally). To achieve this, we need to
 256+ * find the individual timeoffset of each watching user from the preferences..
 257+ *
 258+ * Suggested improvement to slack down the number of sent emails: We could think
 259+ * of sending out bulk mails (bcc:user1,user2...) for all these users having the
 260+ * same timeoffset in their preferences.
 261+ *
 262+ * Visit the documentation pages under http://meta.wikipedia.com/Enotif
 263+ *
 264+ *
 265+ */
247266 class EmailNotification {
248 -
249 - /*
250 - * Send users an email.
251 - *
252 - * @param $editor User whose action precipitated the notification.
253 - * @param $timestamp of the event.
254 - * @param Callback that returns an an array of Users who will recieve the notification.
255 - * @param Callback that returns an array($keys, $body, $subject) where
256 - * * $keys is a dictionary whose keys will be replaced with the corresponding
257 - * values within the subject and body of the message.
258 - * * $body is the name of the message that will be used for the email body.
259 - * * $subject is the name of the message that will be used for the subject.
260 - * Both messages are resolved using the content language.
261 - * The messageCompositionFunction is invoked for each recipient user;
262 - * The keys returned are merged with those given by EmailNotification::commonMessageKeys().
263 - * The recipient is appended to the arguments given to messageCompositionFunction.
264 - * Both callbacks are to be given in the same formats accepted by the hook system.
265 - */
266 - static function notify( $editor, $timestamp, $userListFunction, $messageCompositionFunction ) {
267 - global $wgEnotifUseRealName, $wgEnotifImpersonal;
268 - global $wgLang;
 267+ private $to, $subject, $body, $replyto, $from;
 268+ private $user, $title, $timestamp, $summary, $minorEdit, $oldid, $composed_common, $editor;
 269+ private $mailTargets = array();
269270
270 - $users = wfInvoke( 'userList', $userListFunction );
271 - if( !count( $users ) )
272 - return;
273 -
274 - $common_keys = self::commonMessageKeys( $editor );
275 - foreach( $users as $u ) {
276 - list( $user_keys, $body_msg_name, $subj_msg_name ) =
277 - wfInvoke( 'message', $messageCompositionFunction, array( $u ) );
278 - $keys = array_merge( $common_keys, $user_keys );
279 -
280 - if( $wgEnotifImpersonal ) {
281 - $keys['$WATCHINGUSERNAME'] = wfMsgForContent( 'enotif_impersonal_salutation' );
282 - $keys['$PAGEEDITDATE'] = $wgLang->timeanddate( $timestamp, true, false, false );
283 - } else {
284 - $keys['$WATCHINGUSERNAME'] = $wgEnotifUseRealName ? $u->getRealName() : $u->getName();
285 - $keys['$PAGEEDITDATE'] = $wgLang->timeAndDate( $timestamp, true, false,
286 - $u->getOption( 'timecorrection' ) );
287 - }
288 -
289 - $subject = strtr( wfMsgForContent( $subj_msg_name ), $keys );
290 - $body = wordwrap( strtr( wfMsgForContent( $body_msg_name ), $keys ), 72 );
291 -
292 - $to = new MailAddress( $u );
293 - $from = $keys['$FROM_HEADER'];
294 - $replyto = $keys['$REPLYTO_HEADER'];
295 - UserMailer::send( $to, $from, $subject, $body, $replyto );
296 - }
297 - }
298 -
299 -
300 - static function commonMessageKeys( $editor ) {
301 - global $wgEnotifUseRealName, $wgEnotifRevealEditorAddress;
302 - global $wgNoReplyAddress, $wgPasswordSender;
303 -
304 - $keys = array();
305 -
306 - $name = $wgEnotifUseRealName ? $editor->getRealName() : $editor->getName();
307 -
308 - $adminAddress = new MailAddress( $wgPasswordSender, 'WikiAdmin' );
309 - $editorAddress = new MailAddress( $editor );
310 - if( $wgEnotifRevealEditorAddress
311 - && $editor->getEmail() != ''
312 - && $editor->getOption( 'enotifrevealaddr' ) ) {
313 - if( $wgEnotifFromEditor ) {
314 - $from = $editorAddress;
315 - } else {
316 - $from = $adminAddress;
317 - $replyto = $editorAddress;
318 - }
319 - } else {
320 - $from = $adminAddress;
321 - $replyto = new MailAddress( $wgNoReplyAddress );
322 - }
323 - $keys['$FROM_HEADER'] = $from;
324 - $keys['$REPLYTO_HEADER'] = $replyto;
325 -
326 - if( $editor->isAnon() ) {
327 - $keys['$PAGEEDITOR'] = wfMsgForContent( 'enotif_anon_editor', $name );
328 - $keys['$PAGEEDITOR_EMAIL'] = wfMsgForContent( 'noemailtitle' );
329 - } else{
330 - $keys['$PAGEEDITOR'] = $name;
331 - $keys['$PAGEEDITOR_EMAIL'] = SpecialPage::getSafeTitleFor( 'Emailuser', $name )->getFullUrl();
332 - }
333 - $keys['$PAGEEDITOR_WIKI'] = $editor->getUserPage()->getFullUrl();
334 -
335 - return $keys;
336 - }
337 -
338 -
339 -
340 - /*
341 - * @deprecated
342 - * Use PageChangeNotification::notifyOnPageChange instead.
343 - */
344 - function notifyOnPageChange($editor, $title, $timestamp, $summary, $minorEdit, $oldid = false) {
345 - PageChangeNotification::notifyOnPageChange($editor, $title, $timestamp, $summary, $minorEdit, $oldid);
346 - }
347 -}
348 -
349 -class PageChangeNotification {
350 -
351271 /**
352272 * Send emails corresponding to the user $editor editing the page $title.
353273 * Also updates wl_notificationtimestamp.
@@ -360,13 +280,13 @@
361281 * @param $minorEdit
362282 * @param $oldid (default: false)
363283 */
364 - static function notifyOnPageChange( $editor, $title, $timestamp, $summary, $minorEdit, $oldid = false ) {
 284+ function notifyOnPageChange($editor, $title, $timestamp, $summary, $minorEdit, $oldid = false) {
365285 global $wgEnotifUseJobQ;
366286
367 - if ( $title->getNamespace() < 0 )
 287+ if( $title->getNamespace() < 0 )
368288 return;
369289
370 - if ( $wgEnotifUseJobQ ) {
 290+ if ($wgEnotifUseJobQ) {
371291 $params = array(
372292 "editor" => $editor->getName(),
373293 "editorID" => $editor->getID(),
@@ -377,7 +297,7 @@
378298 $job = new EnotifNotifyJob( $title, $params );
379299 $job->insert();
380300 } else {
381 - self::actuallyNotifyOnPageChange( $editor, $title, $timestamp, $summary, $minorEdit, $oldid );
 301+ $this->actuallyNotifyOnPageChange($editor, $title, $timestamp, $summary, $minorEdit, $oldid);
382302 }
383303
384304 }
@@ -395,16 +315,94 @@
396316 * @param $minorEdit
397317 * @param $oldid (default: false)
398318 */
399 - static function actuallyNotifyOnPageChange( $editor, $title, $timestamp,
400 - $summary, $minorEdit, $oldid = false ) {
401 - global $wgShowUpdatedMarker, $wgEnotifWatchlist;
402 -
 319+ function actuallyNotifyOnPageChange($editor, $title, $timestamp, $summary, $minorEdit, $oldid=false) {
 320+
 321+ # we use $wgPasswordSender as sender's address
 322+ global $wgEnotifWatchlist;
 323+ global $wgEnotifMinorEdits, $wgEnotifUserTalk, $wgShowUpdatedMarker;
 324+ global $wgEnotifImpersonal;
 325+
403326 wfProfileIn( __METHOD__ );
404 -
405 - EmailNotification::notify( $editor, $timestamp,
406 - array( 'PageChangeNotification::usersList', array( $editor, $title, $minorEdit ) ),
407 - array( 'PageChangeNotification::message', array( $oldid, $minorEdit, $summary, $title, $editor ) ) );
408 -
 327+
 328+ # The following code is only run, if several conditions are met:
 329+ # 1. EmailNotification for pages (other than user_talk pages) must be enabled
 330+ # 2. minor edits (changes) are only regarded if the global flag indicates so
 331+
 332+ $isUserTalkPage = ($title->getNamespace() == NS_USER_TALK);
 333+ $enotifusertalkpage = ($isUserTalkPage && $wgEnotifUserTalk);
 334+ $enotifwatchlistpage = $wgEnotifWatchlist;
 335+
 336+ $this->title = $title;
 337+ $this->timestamp = $timestamp;
 338+ $this->summary = $summary;
 339+ $this->minorEdit = $minorEdit;
 340+ $this->oldid = $oldid;
 341+ $this->editor = $editor;
 342+ $this->composed_common = false;
 343+
 344+ $userTalkId = false;
 345+
 346+ if ( (!$minorEdit || $wgEnotifMinorEdits) ) {
 347+ if ( $wgEnotifUserTalk && $isUserTalkPage ) {
 348+ $targetUser = User::newFromName( $title->getText() );
 349+ if ( !$targetUser || $targetUser->isAnon() ) {
 350+ wfDebug( __METHOD__.": user talk page edited, but user does not exist\n" );
 351+ } elseif ( $targetUser->getId() == $editor->getId() ) {
 352+ wfDebug( __METHOD__.": user edited their own talk page, no notification sent\n" );
 353+ } elseif( $targetUser->getOption( 'enotifusertalkpages' ) ) {
 354+ if( $targetUser->isEmailConfirmed() ) {
 355+ wfDebug( __METHOD__.": sending talk page update notification\n" );
 356+ $this->compose( $targetUser );
 357+ $userTalkId = $targetUser->getId();
 358+ } else {
 359+ wfDebug( __METHOD__.": talk page owner doesn't have validated email\n" );
 360+ }
 361+ } else {
 362+ wfDebug( __METHOD__.": talk page owner doesn't want notifications\n" );
 363+ }
 364+ }
 365+
 366+ if ( $wgEnotifWatchlist ) {
 367+ // Send updates to watchers other than the current editor
 368+ $userCondition = 'wl_user != ' . $editor->getID();
 369+ if ( $userTalkId !== false ) {
 370+ // Already sent an email to this person
 371+ $userCondition .= ' AND wl_user != ' . intval( $userTalkId );
 372+ }
 373+ $dbr = wfGetDB( DB_SLAVE );
 374+
 375+ list( $user ) = $dbr->tableNamesN( 'user' );
 376+
 377+ $res = $dbr->select( array( 'watchlist', 'user' ),
 378+ array( "$user.*" ),
 379+ array(
 380+ 'wl_user=user_id',
 381+ 'wl_title' => $title->getDBkey(),
 382+ 'wl_namespace' => $title->getNamespace(),
 383+ $userCondition,
 384+ 'wl_notificationtimestamp IS NULL',
 385+ ), __METHOD__ );
 386+ $userArray = UserArray::newFromResult( $res );
 387+
 388+ foreach ( $userArray as $watchingUser ) {
 389+ if ( $watchingUser->getOption( 'enotifwatchlistpages' ) &&
 390+ ( !$minorEdit || $watchingUser->getOption('enotifminoredits') ) &&
 391+ $watchingUser->isEmailConfirmed() )
 392+ {
 393+ $this->compose( $watchingUser );
 394+ }
 395+ }
 396+ }
 397+ }
 398+
 399+ global $wgUsersNotifiedOnAllChanges;
 400+ foreach ( $wgUsersNotifiedOnAllChanges as $name ) {
 401+ $user = User::newFromName( $name );
 402+ $this->compose( $user );
 403+ }
 404+
 405+ $this->sendMails();
 406+
409407 $latestTimestamp = Revision::getTimestampFromId( $title, $title->getLatestRevID() );
410408 // Do not update watchlists if something else already did.
411409 if ( $timestamp >= $latestTimestamp && ($wgShowUpdatedMarker || $wgEnotifWatchlist) ) {
@@ -425,22 +423,38 @@
426424 }
427425
428426 wfProfileOut( __METHOD__ );
429 - }
430 -
431 -
432 - static function message( $stuff ) {
433 - global $wgEnotifImpersonal;
 427+ } # function NotifyOnChange
434428
435 - list( $oldid, $medit, $summary, $title, $user ) = $stuff;
436 - $keys = array();
 429+ /**
 430+ * @private
 431+ */
 432+ function composeCommonMailtext() {
 433+ global $wgPasswordSender, $wgNoReplyAddress;
 434+ global $wgEnotifFromEditor, $wgEnotifRevealEditorAddress;
 435+ global $wgEnotifImpersonal, $wgEnotifUseRealName;
437436
 437+ $this->composed_common = true;
 438+
 439+ $summary = ($this->summary == '') ? ' - ' : $this->summary;
 440+ $medit = ($this->minorEdit) ? wfMsg( 'minoredit' ) : '';
 441+
 442+ # You as the WikiAdmin and Sysops can make use of plenty of
 443+ # named variables when composing your notification emails while
 444+ # simply editing the Meta pages
 445+
 446+ $subject = wfMsgForContent( 'enotif_subject' );
 447+ $body = wfMsgForContent( 'enotif_body' );
 448+ $from = ''; /* fail safe */
 449+ $replyto = ''; /* fail safe */
 450+ $keys = array();
 451+
438452 # regarding the use of oldid as an indicator for the last visited version, see also
439453 # http://bugzilla.wikipeda.org/show_bug.cgi?id=603 "Delete + undelete cycle doesn't preserve old_id"
440454 # However, in the case of a new page which is already watched, we have no previous version to compare
441 - if( $oldid ) {
442 - $difflink = $title->getFullUrl( 'diff=0&oldid=' . $oldid );
 455+ if( $this->oldid ) {
 456+ $difflink = $this->title->getFullUrl( 'diff=0&oldid=' . $this->oldid );
443457 $keys['$NEWPAGE'] = wfMsgForContent( 'enotif_lastvisited', $difflink );
444 - $keys['$OLDID'] = $oldid;
 458+ $keys['$OLDID'] = $this->oldid;
445459 $keys['$CHANGEDORCREATED'] = wfMsgForContent( 'changed' );
446460 } else {
447461 $keys['$NEWPAGE'] = wfMsgForContent( 'enotif_newpagetext' );
@@ -449,92 +463,151 @@
450464 $keys['$CHANGEDORCREATED'] = wfMsgForContent( 'created' );
451465 }
452466
453 - if ($wgEnotifImpersonal && $oldid) {
454 - # For impersonal mail, show a diff link to the last revision.
455 - $keys['$NEWPAGE'] = wfMsgForContent( 'enotif_lastdiff',
456 - $title->getFullURL( "oldid={$oldid}&diff=prev" ) );
457 - }
 467+ if ($wgEnotifImpersonal && $this->oldid)
 468+ /*
 469+ * For impersonal mail, show a diff link to the last
 470+ * revision.
 471+ */
 472+ $keys['$NEWPAGE'] = wfMsgForContent('enotif_lastdiff',
 473+ $this->title->getFullURL("oldid={$this->oldid}&diff=prev"));
458474
459 - $keys['$PAGETITLE'] = $title->getPrefixedText();
460 - $keys['$PAGETITLE_URL'] = $title->getFullUrl();
461 - $keys['$PAGEMINOREDIT'] = $medit ? wfMsg( 'minoredit' ) : '';
462 - $keys['$PAGESUMMARY'] = ( $summary == '' ) ? ' - ' : $summary;
 475+ $body = strtr( $body, $keys );
 476+ $pagetitle = $this->title->getPrefixedText();
 477+ $keys['$PAGETITLE'] = $pagetitle;
 478+ $keys['$PAGETITLE_URL'] = $this->title->getFullUrl();
463479
464 - return array( $keys, 'enotif_body', 'enotif_subject' );
465 - }
 480+ $keys['$PAGEMINOREDIT'] = $medit;
 481+ $keys['$PAGESUMMARY'] = $summary;
466482
467 - static function usersList( $stuff ) {
468 - global $wgEnotifWatchlist, $wgEnotifMinorEdits, $wgUsersNotifiedOnAllChanges;
 483+ $subject = strtr( $subject, $keys );
469484
470 - list( $editor, $title, $minorEdit ) = $stuff;
471 - $recipients = array();
 485+ # Reveal the page editor's address as REPLY-TO address only if
 486+ # the user has not opted-out and the option is enabled at the
 487+ # global configuration level.
 488+ $editor = $this->editor;
 489+ $name = $wgEnotifUseRealName ? $editor->getRealName() : $editor->getName();
 490+ $adminAddress = new MailAddress( $wgPasswordSender, 'WikiAdmin' );
 491+ $editorAddress = new MailAddress( $editor );
 492+ if( $wgEnotifRevealEditorAddress
 493+ && ( $editor->getEmail() != '' )
 494+ && $editor->getOption( 'enotifrevealaddr' ) ) {
 495+ if( $wgEnotifFromEditor ) {
 496+ $from = $editorAddress;
 497+ } else {
 498+ $from = $adminAddress;
 499+ $replyto = $editorAddress;
 500+ }
 501+ } else {
 502+ $from = $adminAddress;
 503+ $replyto = new MailAddress( $wgNoReplyAddress );
 504+ }
472505
473 - # User talk pages:
474 - $userTalkId = false;
475 - if( $title->getNamespace() == NS_USER_TALK && ( !$minorEdit || $wgEnotifMinorEdits ) ) {
476 - $targetUser = User::newFromName( $title->getText() );
 506+ if( $editor->isIP( $name ) ) {
 507+ #real anon (user:xxx.xxx.xxx.xxx)
 508+ $utext = wfMsgForContent('enotif_anon_editor', $name);
 509+ $subject = str_replace('$PAGEEDITOR', $utext, $subject);
 510+ $keys['$PAGEEDITOR'] = $utext;
 511+ $keys['$PAGEEDITOR_EMAIL'] = wfMsgForContent( 'noemailtitle' );
 512+ } else {
 513+ $subject = str_replace('$PAGEEDITOR', $name, $subject);
 514+ $keys['$PAGEEDITOR'] = $name;
 515+ $emailPage = SpecialPage::getSafeTitleFor( 'Emailuser', $name );
 516+ $keys['$PAGEEDITOR_EMAIL'] = $emailPage->getFullUrl();
 517+ }
 518+ $userPage = $editor->getUserPage();
 519+ $keys['$PAGEEDITOR_WIKI'] = $userPage->getFullUrl();
 520+ $body = strtr( $body, $keys );
 521+ $body = wordwrap( $body, 72 );
477522
478 - if ( !$targetUser || $targetUser->isAnon() )
479 - $msg = "user talk page edited, but user does not exist";
 523+ # now save this as the constant user-independent part of the message
 524+ $this->from = $from;
 525+ $this->replyto = $replyto;
 526+ $this->subject = $subject;
 527+ $this->body = $body;
 528+ }
480529
481 - else if ( $targetUser->getId() == $editor->getId() )
482 - $msg = "user edited their own talk page, no notification sent";
 530+ /**
 531+ * Compose a mail to a given user and either queue it for sending, or send it now,
 532+ * depending on settings.
 533+ *
 534+ * Call sendMails() to send any mails that were queued.
 535+ */
 536+ function compose( $user ) {
 537+ global $wgEnotifImpersonal;
483538
484 - else if ( !$targetUser->getOption( 'enotifusertalkpages' ) )
485 - $msg = "talk page owner doesn't want notifications";
 539+ if ( !$this->composed_common )
 540+ $this->composeCommonMailtext();
486541
487 - else if ( !$targetUser->isEmailConfirmed() )
488 - $msg = "talk page owner doesn't have validated email";
 542+ if ( $wgEnotifImpersonal ) {
 543+ $this->mailTargets[] = new MailAddress( $user );
 544+ } else {
 545+ $this->sendPersonalised( $user );
 546+ }
 547+ }
489548
490 - else {
491 - $msg = "sending talk page update notification";
492 - $recipients[] = $targetUser;
493 - $userTalkId = $targetUser->getId(); # won't be included in watchlist, below.
494 - }
495 - wfDebug( __METHOD__ . ': ' . $msg . "\n" );
 549+ /**
 550+ * Send any queued mails
 551+ */
 552+ function sendMails() {
 553+ global $wgEnotifImpersonal;
 554+ if ( $wgEnotifImpersonal ) {
 555+ $this->sendImpersonal( $this->mailTargets );
496556 }
497 - wfDebug( "Did not send a user-talk notification.\n" );
 557+ }
498558
499 - if( $wgEnotifWatchlist && ( !$minorEdit || $wgEnotifMinorEdits ) ) {
500 - // Send updates to watchers other than the current editor
501 - $userCondition = 'wl_user != ' . $editor->getID();
 559+ /**
 560+ * Does the per-user customizations to a notification e-mail (name,
 561+ * timestamp in proper timezone, etc) and sends it out.
 562+ * Returns true if the mail was sent successfully.
 563+ *
 564+ * @param User $watchingUser
 565+ * @param object $mail
 566+ * @return bool
 567+ * @private
 568+ */
 569+ function sendPersonalised( $watchingUser ) {
 570+ global $wgLang, $wgEnotifUseRealName;
 571+ // From the PHP manual:
 572+ // Note: The to parameter cannot be an address in the form of "Something <someone@example.com>".
 573+ // The mail command will not parse this properly while talking with the MTA.
 574+ $to = new MailAddress( $watchingUser );
 575+ $name = $wgEnotifUseRealName ? $watchingUser->getRealName() : $watchingUser->getName();
 576+ $body = str_replace( '$WATCHINGUSERNAME', $name , $this->body );
502577
503 - if ( $userTalkId !== false ) {
504 - // Already sent an email to this person
505 - $userCondition .= ' AND wl_user != ' . intval( $userTalkId );
506 - }
507 - $dbr = wfGetDB( DB_SLAVE );
 578+ $timecorrection = $watchingUser->getOption( 'timecorrection' );
508579
509 - list( $user ) = $dbr->tableNamesN( 'user' );
 580+ # $PAGEEDITDATE is the time and date of the page change
 581+ # expressed in terms of individual local time of the notification
 582+ # recipient, i.e. watching user
 583+ $body = str_replace('$PAGEEDITDATE',
 584+ $wgLang->timeanddate( $this->timestamp, true, false, $timecorrection ),
 585+ $body);
510586
511 - $res = $dbr->select( array( 'watchlist', 'user' ),
512 - array( "$user.*" ),
513 - array(
514 - 'wl_user=user_id',
515 - 'wl_title' => $title->getDBkey(),
516 - 'wl_namespace' => $title->getNamespace(),
517 - $userCondition,
518 - 'wl_notificationtimestamp IS NULL',
519 - ), __METHOD__ );
520 - $userArray = UserArray::newFromResult( $res );
 587+ return UserMailer::send($to, $this->from, $this->subject, $body, $this->replyto);
 588+ }
521589
522 - foreach ( $userArray as $watchingUser ) {
523 - if ( $watchingUser->getOption( 'enotifwatchlistpages' ) &&
524 - ( !$minorEdit || $watchingUser->getOption('enotifminoredits') ) &&
525 - $watchingUser->isEmailConfirmed() ) {
526 - $recipients[] = $watchingUser;
527 - }
528 - }
529 - }
 590+ /**
 591+ * Same as sendPersonalised but does impersonal mail suitable for bulk
 592+ * mailing. Takes an array of MailAddress objects.
 593+ */
 594+ function sendImpersonal( $addresses ) {
 595+ global $wgLang;
530596
531 - foreach ( $wgUsersNotifiedOnAllChanges as $name ) {
532 - $recipients[] = User::newFromName( $name );
533 - }
 597+ if (empty($addresses))
 598+ return;
534599
535 - return $recipients;
 600+ $body = str_replace(
 601+ array( '$WATCHINGUSERNAME',
 602+ '$PAGEEDITDATE'),
 603+ array( wfMsgForContent('enotif_impersonal_salutation'),
 604+ $wgLang->timeanddate($this->timestamp, true, false, false)),
 605+ $this->body);
 606+
 607+ return UserMailer::send($addresses, $this->from, $this->subject, $body, $this->replyto);
536608 }
537 -}
538609
 610+} # end of class EmailNotification
 611+
539612 /**
540613 * Backwards compatibility functions
541614 */
Index: trunk/phase3/includes/Hooks.php
@@ -22,146 +22,122 @@
2323 * @file
2424 */
2525
26 -/* Private */
27 -function _wfInvokeInternalGoop( $event, $hook ) {
28 - $object = NULL;
29 - $method = NULL;
30 - $func = NULL;
31 - $data = NULL;
32 - $have_data = false;
3326
34 - if ( is_array( $hook ) ) {
35 - if ( count( $hook ) < 1 ) {
36 - throw new MWException( "Empty array in hooks for " . $event . "\n" );
37 - } else if ( is_object( $hook[0] ) ) {
38 - $object = $hook[0];
39 - if ( count( $hook ) < 2 ) {
40 - $method = "on" . $event;
41 - } else {
42 - $method = $hook[1];
43 - if ( count( $hook ) > 2 ) {
44 - $data = $hook[2];
45 - $have_data = true;
46 - }
47 - }
48 - } else if ( is_string( $hook[0] ) ) {
49 - $func = $hook[0];
50 - if ( count( $hook ) > 1 ) {
51 - $data = $hook[1];
52 - $have_data = true;
53 - }
54 - } else {
55 - throw new MWException( "Unknown datatype in hooks for " . $event . "\n" );
56 - }
57 - } else if ( is_string( $hook ) ) { # functions look like strings, too
58 - $func = $hook;
59 - } else if ( is_object( $hook ) ) {
60 - $object = $hook;
61 - $method = "on" . $event;
62 - } else {
63 - throw new MWException("Unknown datatype in hooks for " . $event . "\n");
64 - }
65 -
66 - if ( isset( $object ) ) {
67 - $func = get_class( $object ) . '::' . $method;
68 - $callback = array( $object, $method );
69 - } elseif ( false !== ( $pos = strpos( $func, '::' ) ) ) {
70 - $callback = array( substr( $func, 0, $pos ), substr( $func, $pos + 2 ) );
71 - } else {
72 - $callback = $func;
73 - }
74 -
75 - return array($callback, $func, $data);
76 -}
77 -
78 -/* Return a string describing the hook for debugging purposes. */
79 -function wfFormatInvocation( $event, $hook, $args = array() ) {
80 - list( $callback, $func, $data ) = _wfInvokeInternalGoop( $event, $hook, $args );
81 -
82 - if( is_array( $callback ) ) {
83 - if( is_object( $callback[0] ) ) {
84 - $prettyClass = get_class( $callback[0] );
85 - } else {
86 - $prettyClass = strval( $callback[0] );
87 - }
88 - $prettyFunc = $prettyClass . '::' . strval( $callback[1] );
89 - } else {
90 - $prettyFunc = strval( $callback );
91 - }
92 - return $prettyFunc;
93 -}
94 -
95 -
96 -/*
97 - * Invoke a function dynamically.
98 - * $event is the name of the invocation; this is used if the hook specifies
99 - * an object but no method name; 'on$event' is then invoked on the object.
100 - * $hook can be: a function, an object, an array of $function and $data,
101 - * an array of just a function, an array of object and method, or an
102 - * array of object, method, and data.
103 - * If arguments are provided both as part of the hook itself, and when
104 - * calling wfCallFancyCallback, the two arrays are merged.
105 - */
106 -function wfInvoke( $event, $hook, $args = array() ) {
107 - list( $callback, $func, $data ) = _wfInvokeInternalGoop( $event, $hook, $args );
108 -
109 - /* We put the first data element on, if needed. */
110 - if ( $data ) {
111 - $hook_args = array_merge( array( $data ), $args );
112 - } else {
113 - $hook_args = $args;
114 - }
115 -
116 - // Run autoloader (workaround for call_user_func_array bug)
117 - is_callable( $callback );
118 -
119 - /* Call the hook. */
120 - wfProfileIn( $func );
121 - $retval = call_user_func_array( $callback, $hook_args );
122 - wfProfileOut( $func );
123 - return $retval;
124 -}
125 -
126 -
12727 /**
12828 * Because programmers assign to $wgHooks, we need to be very
12929 * careful about its contents. So, there's a lot more error-checking
13030 * in here than would normally be necessary.
13131 */
132 -function wfRunHooks( $event, $args = array() ) {
 32+function wfRunHooks($event, $args = array()) {
13333
13434 global $wgHooks;
13535
136 - if ( !is_array( $wgHooks ) ) {
137 - throw new MWException( "Global hooks array is not an array!\n" );
 36+ if (!is_array($wgHooks)) {
 37+ throw new MWException("Global hooks array is not an array!\n");
13838 return false;
13939 }
14040
141 - if (!array_key_exists( $event, $wgHooks ) ) {
 41+ if (!array_key_exists($event, $wgHooks)) {
14242 return true;
14343 }
14444
145 - if ( !is_array( $wgHooks[$event] ) ) {
146 - throw new MWException( "Hooks array for event '$event' is not an array!\n" );
 45+ if (!is_array($wgHooks[$event])) {
 46+ throw new MWException("Hooks array for event '$event' is not an array!\n");
14747 return false;
14848 }
14949
150 - foreach ( $wgHooks[$event] as $index => $hook ) {
 50+ foreach ($wgHooks[$event] as $index => $hook) {
15151
152 - $retval = wfInvoke( $event, $hook, $args );
 52+ $object = NULL;
 53+ $method = NULL;
 54+ $func = NULL;
 55+ $data = NULL;
 56+ $have_data = false;
15357
 58+ /* $hook can be: a function, an object, an array of $function and $data,
 59+ * an array of just a function, an array of object and method, or an
 60+ * array of object, method, and data.
 61+ */
 62+
 63+ if (is_array($hook)) {
 64+ if (count($hook) < 1) {
 65+ throw new MWException("Empty array in hooks for " . $event . "\n");
 66+ } else if (is_object($hook[0])) {
 67+ $object = $wgHooks[$event][$index][0];
 68+ if (count($hook) < 2) {
 69+ $method = "on" . $event;
 70+ } else {
 71+ $method = $hook[1];
 72+ if (count($hook) > 2) {
 73+ $data = $hook[2];
 74+ $have_data = true;
 75+ }
 76+ }
 77+ } else if (is_string($hook[0])) {
 78+ $func = $hook[0];
 79+ if (count($hook) > 1) {
 80+ $data = $hook[1];
 81+ $have_data = true;
 82+ }
 83+ } else {
 84+ var_dump( $wgHooks );
 85+ throw new MWException("Unknown datatype in hooks for " . $event . "\n");
 86+ }
 87+ } else if (is_string($hook)) { # functions look like strings, too
 88+ $func = $hook;
 89+ } else if (is_object($hook)) {
 90+ $object = $wgHooks[$event][$index];
 91+ $method = "on" . $event;
 92+ } else {
 93+ throw new MWException("Unknown datatype in hooks for " . $event . "\n");
 94+ }
 95+
 96+ /* We put the first data element on, if needed. */
 97+
 98+ if ($have_data) {
 99+ $hook_args = array_merge(array($data), $args);
 100+ } else {
 101+ $hook_args = $args;
 102+ }
 103+
 104+ if ( isset( $object ) ) {
 105+ $func = get_class( $object ) . '::' . $method;
 106+ $callback = array( $object, $method );
 107+ } elseif ( false !== ( $pos = strpos( $func, '::' ) ) ) {
 108+ $callback = array( substr( $func, 0, $pos ), substr( $func, $pos + 2 ) );
 109+ } else {
 110+ $callback = $func;
 111+ }
 112+
 113+ // Run autoloader (workaround for call_user_func_array bug)
 114+ is_callable( $callback );
 115+
 116+ /* Call the hook. */
 117+ wfProfileIn( $func );
 118+ $retval = call_user_func_array( $callback, $hook_args );
 119+ wfProfileOut( $func );
 120+
154121 /* String return is an error; false return means stop processing. */
155122
156 - if ( is_string( $retval ) ) {
 123+ if (is_string($retval)) {
157124 global $wgOut;
158 - $wgOut->showFatalError( $retval );
 125+ $wgOut->showFatalError($retval);
159126 return false;
160127 } elseif( $retval === null ) {
161 - $prettyFunc = wfFormatInvocation( $event, $hook, $args );
 128+ if( is_array( $callback ) ) {
 129+ if( is_object( $callback[0] ) ) {
 130+ $prettyClass = get_class( $callback[0] );
 131+ } else {
 132+ $prettyClass = strval( $callback[0] );
 133+ }
 134+ $prettyFunc = $prettyClass . '::' . strval( $callback[1] );
 135+ } else {
 136+ $prettyFunc = strval( $callback );
 137+ }
162138 throw new MWException( "Detected bug in an extension! " .
163139 "Hook $prettyFunc failed to return a value; " .
164140 "should return true to continue hook processing or false to abort." );
165 - } else if ( !$retval ) {
 141+ } else if (!$retval) {
166142 return false;
167143 }
168144 }
Index: trunk/phase3/includes/AutoLoader.php
@@ -55,7 +55,6 @@
5656 'EditPage' => 'includes/EditPage.php',
5757 'EmaillingJob' => 'includes/EmaillingJob.php',
5858 'EmailNotification' => 'includes/UserMailer.php',
59 - 'PageChangeNotification' => 'includes/UserMailer.php',
6059 'EnhancedChangesList' => 'includes/ChangesList.php',
6160 'EnotifNotifyJob' => 'includes/EnotifNotifyJob.php',
6261 'ErrorPageError' => 'includes/Exception.php',

Past revisions this follows-up on

RevisionCommit summaryAuthorDate
r44702Factored wfInvoke out of wfRunHooks. This time without breakage. (I think.)david07:00, 17 December 2008
r44703wfInvoke documentation fixdavid07:05, 17 December 2008
r44704UserMailer bigtime refactor. Please test.david07:08, 17 December 2008
r44715* Shut this upnikerabbit14:07, 17 December 2008
r44721* Short circuit EmailNotification::notify() to not call EmailNotification::co...ialex17:27, 17 December 2008

Status & tagging log