Index: trunk/phase3/includes/RecentChange.php |
— | — | @@ -184,8 +184,10 @@ |
185 | 185 | $editor = ($wgUser->getName() == $this->mAttribs['rc_user_text']) ? |
186 | 186 | $wgUser : User::newFromName( $this->mAttribs['rc_user_text'], false ); |
187 | 187 | } |
| 188 | + # FIXME: this would be better as an extension hook |
| 189 | + $enotif = new EmailNotification(); |
188 | 190 | $title = Title::makeTitle( $this->mAttribs['rc_namespace'], $this->mAttribs['rc_title'] ); |
189 | | - PageChangeNotification::notifyOnPageChange( $editor, $title, |
| 191 | + $enotif->notifyOnPageChange( $editor, $title, |
190 | 192 | $this->mAttribs['rc_timestamp'], |
191 | 193 | $this->mAttribs['rc_comment'], |
192 | 194 | $this->mAttribs['rc_minor'], |
Index: trunk/phase3/includes/EnotifNotifyJob.php |
— | — | @@ -12,6 +12,7 @@ |
13 | 13 | } |
14 | 14 | |
15 | 15 | function run() { |
| 16 | + $enotif = new EmailNotification(); |
16 | 17 | // Get the user from ID (rename safe). Anons are 0, so defer to name. |
17 | 18 | if( isset($this->params['editorID']) && $this->params['editorID'] ) { |
18 | 19 | $editor = User::newFromId( $this->params['editorID'] ); |
— | — | @@ -19,7 +20,7 @@ |
20 | 21 | } else { |
21 | 22 | $editor = User::newFromName( $this->params['editor'], false ); |
22 | 23 | } |
23 | | - PageChangeNotification::actuallyNotifyOnPageChange( |
| 24 | + $enotif->actuallyNotifyOnPageChange( |
24 | 25 | $editor, |
25 | 26 | $this->title, |
26 | 27 | $this->params['timestamp'], |
Index: trunk/phase3/includes/UserMailer.php |
— | — | @@ -242,111 +242,31 @@ |
243 | 243 | } |
244 | 244 | } |
245 | 245 | |
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 | + */ |
247 | 266 | 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(); |
269 | 270 | |
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 | | - |
351 | 271 | /** |
352 | 272 | * Send emails corresponding to the user $editor editing the page $title. |
353 | 273 | * Also updates wl_notificationtimestamp. |
— | — | @@ -360,13 +280,13 @@ |
361 | 281 | * @param $minorEdit |
362 | 282 | * @param $oldid (default: false) |
363 | 283 | */ |
364 | | - static function notifyOnPageChange( $editor, $title, $timestamp, $summary, $minorEdit, $oldid = false ) { |
| 284 | + function notifyOnPageChange($editor, $title, $timestamp, $summary, $minorEdit, $oldid = false) { |
365 | 285 | global $wgEnotifUseJobQ; |
366 | 286 | |
367 | | - if ( $title->getNamespace() < 0 ) |
| 287 | + if( $title->getNamespace() < 0 ) |
368 | 288 | return; |
369 | 289 | |
370 | | - if ( $wgEnotifUseJobQ ) { |
| 290 | + if ($wgEnotifUseJobQ) { |
371 | 291 | $params = array( |
372 | 292 | "editor" => $editor->getName(), |
373 | 293 | "editorID" => $editor->getID(), |
— | — | @@ -377,7 +297,7 @@ |
378 | 298 | $job = new EnotifNotifyJob( $title, $params ); |
379 | 299 | $job->insert(); |
380 | 300 | } else { |
381 | | - self::actuallyNotifyOnPageChange( $editor, $title, $timestamp, $summary, $minorEdit, $oldid ); |
| 301 | + $this->actuallyNotifyOnPageChange($editor, $title, $timestamp, $summary, $minorEdit, $oldid); |
382 | 302 | } |
383 | 303 | |
384 | 304 | } |
— | — | @@ -395,16 +315,94 @@ |
396 | 316 | * @param $minorEdit |
397 | 317 | * @param $oldid (default: false) |
398 | 318 | */ |
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 | + |
403 | 326 | 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 | + |
409 | 407 | $latestTimestamp = Revision::getTimestampFromId( $title, $title->getLatestRevID() ); |
410 | 408 | // Do not update watchlists if something else already did. |
411 | 409 | if ( $timestamp >= $latestTimestamp && ($wgShowUpdatedMarker || $wgEnotifWatchlist) ) { |
— | — | @@ -425,22 +423,38 @@ |
426 | 424 | } |
427 | 425 | |
428 | 426 | wfProfileOut( __METHOD__ ); |
429 | | - } |
430 | | - |
431 | | - |
432 | | - static function message( $stuff ) { |
433 | | - global $wgEnotifImpersonal; |
| 427 | + } # function NotifyOnChange |
434 | 428 | |
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; |
437 | 436 | |
| 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 | + |
438 | 452 | # regarding the use of oldid as an indicator for the last visited version, see also |
439 | 453 | # http://bugzilla.wikipeda.org/show_bug.cgi?id=603 "Delete + undelete cycle doesn't preserve old_id" |
440 | 454 | # 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 ); |
443 | 457 | $keys['$NEWPAGE'] = wfMsgForContent( 'enotif_lastvisited', $difflink ); |
444 | | - $keys['$OLDID'] = $oldid; |
| 458 | + $keys['$OLDID'] = $this->oldid; |
445 | 459 | $keys['$CHANGEDORCREATED'] = wfMsgForContent( 'changed' ); |
446 | 460 | } else { |
447 | 461 | $keys['$NEWPAGE'] = wfMsgForContent( 'enotif_newpagetext' ); |
— | — | @@ -449,92 +463,151 @@ |
450 | 464 | $keys['$CHANGEDORCREATED'] = wfMsgForContent( 'created' ); |
451 | 465 | } |
452 | 466 | |
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")); |
458 | 474 | |
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(); |
463 | 479 | |
464 | | - return array( $keys, 'enotif_body', 'enotif_subject' ); |
465 | | - } |
| 480 | + $keys['$PAGEMINOREDIT'] = $medit; |
| 481 | + $keys['$PAGESUMMARY'] = $summary; |
466 | 482 | |
467 | | - static function usersList( $stuff ) { |
468 | | - global $wgEnotifWatchlist, $wgEnotifMinorEdits, $wgUsersNotifiedOnAllChanges; |
| 483 | + $subject = strtr( $subject, $keys ); |
469 | 484 | |
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 | + } |
472 | 505 | |
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 ); |
477 | 522 | |
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 | + } |
480 | 529 | |
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; |
483 | 538 | |
484 | | - else if ( !$targetUser->getOption( 'enotifusertalkpages' ) ) |
485 | | - $msg = "talk page owner doesn't want notifications"; |
| 539 | + if ( !$this->composed_common ) |
| 540 | + $this->composeCommonMailtext(); |
486 | 541 | |
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 | + } |
489 | 548 | |
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 ); |
496 | 556 | } |
497 | | - wfDebug( "Did not send a user-talk notification.\n" ); |
| 557 | + } |
498 | 558 | |
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 ); |
502 | 577 | |
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' ); |
508 | 579 | |
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); |
510 | 586 | |
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 | + } |
521 | 589 | |
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; |
530 | 596 | |
531 | | - foreach ( $wgUsersNotifiedOnAllChanges as $name ) { |
532 | | - $recipients[] = User::newFromName( $name ); |
533 | | - } |
| 597 | + if (empty($addresses)) |
| 598 | + return; |
534 | 599 | |
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); |
536 | 608 | } |
537 | | -} |
538 | 609 | |
| 610 | +} # end of class EmailNotification |
| 611 | + |
539 | 612 | /** |
540 | 613 | * Backwards compatibility functions |
541 | 614 | */ |
Index: trunk/phase3/includes/Hooks.php |
— | — | @@ -22,146 +22,122 @@ |
23 | 23 | * @file |
24 | 24 | */ |
25 | 25 | |
26 | | -/* Private */ |
27 | | -function _wfInvokeInternalGoop( $event, $hook ) { |
28 | | - $object = NULL; |
29 | | - $method = NULL; |
30 | | - $func = NULL; |
31 | | - $data = NULL; |
32 | | - $have_data = false; |
33 | 26 | |
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 | | - |
127 | 27 | /** |
128 | 28 | * Because programmers assign to $wgHooks, we need to be very |
129 | 29 | * careful about its contents. So, there's a lot more error-checking |
130 | 30 | * in here than would normally be necessary. |
131 | 31 | */ |
132 | | -function wfRunHooks( $event, $args = array() ) { |
| 32 | +function wfRunHooks($event, $args = array()) { |
133 | 33 | |
134 | 34 | global $wgHooks; |
135 | 35 | |
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"); |
138 | 38 | return false; |
139 | 39 | } |
140 | 40 | |
141 | | - if (!array_key_exists( $event, $wgHooks ) ) { |
| 41 | + if (!array_key_exists($event, $wgHooks)) { |
142 | 42 | return true; |
143 | 43 | } |
144 | 44 | |
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"); |
147 | 47 | return false; |
148 | 48 | } |
149 | 49 | |
150 | | - foreach ( $wgHooks[$event] as $index => $hook ) { |
| 50 | + foreach ($wgHooks[$event] as $index => $hook) { |
151 | 51 | |
152 | | - $retval = wfInvoke( $event, $hook, $args ); |
| 52 | + $object = NULL; |
| 53 | + $method = NULL; |
| 54 | + $func = NULL; |
| 55 | + $data = NULL; |
| 56 | + $have_data = false; |
153 | 57 | |
| 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 | + |
154 | 121 | /* String return is an error; false return means stop processing. */ |
155 | 122 | |
156 | | - if ( is_string( $retval ) ) { |
| 123 | + if (is_string($retval)) { |
157 | 124 | global $wgOut; |
158 | | - $wgOut->showFatalError( $retval ); |
| 125 | + $wgOut->showFatalError($retval); |
159 | 126 | return false; |
160 | 127 | } 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 | + } |
162 | 138 | throw new MWException( "Detected bug in an extension! " . |
163 | 139 | "Hook $prettyFunc failed to return a value; " . |
164 | 140 | "should return true to continue hook processing or false to abort." ); |
165 | | - } else if ( !$retval ) { |
| 141 | + } else if (!$retval) { |
166 | 142 | return false; |
167 | 143 | } |
168 | 144 | } |
Index: trunk/phase3/includes/AutoLoader.php |
— | — | @@ -55,7 +55,6 @@ |
56 | 56 | 'EditPage' => 'includes/EditPage.php', |
57 | 57 | 'EmaillingJob' => 'includes/EmaillingJob.php', |
58 | 58 | 'EmailNotification' => 'includes/UserMailer.php', |
59 | | - 'PageChangeNotification' => 'includes/UserMailer.php', |
60 | 59 | 'EnhancedChangesList' => 'includes/ChangesList.php', |
61 | 60 | 'EnotifNotifyJob' => 'includes/EnotifNotifyJob.php', |
62 | 61 | 'ErrorPageError' => 'includes/Exception.php', |