66 private const USER_TALK =
'user_talk';
70 private const WATCHLIST =
'watchlist';
74 private const ALL_CHANGES =
'all_changes';
76 protected string $subject =
'';
78 protected string $body =
'';
86 protected string $summary =
'';
93 protected bool $composed_common =
false;
95 protected string $pageStatus =
'';
98 protected array $mailTargets = [];
115 return $this->pageStatus;
141 $pageStatus =
'changed'
143 if ( $title->getNamespace() < 0 ) {
147 $mwServices = MediaWikiServices::getInstance();
148 $config = $mwServices->getMainConfig();
152 if ( $config->get( MainConfigNames::EnotifWatchlist ) || $config->get( MainConfigNames::ShowUpdatedMarker ) ) {
153 $watchers = $mwServices->getWatchedItemStore()->updateNotificationTimestamp(
161 if ( $mwServices->getUserFactory()->newFromAuthority( $editor )->isBot() ) {
170 if ( $watchers === [] &&
171 !count( $config->get( MainConfigNames::UsersNotifiedOnAllChanges ) )
176 ( $config->get( MainConfigNames::EnotifMinorEdits ) &&
177 !$editor->
isAllowed(
'nominornewtalk' ) )
179 if ( $config->get( MainConfigNames::EnotifUserTalk )
181 && $this->canSendUserTalkEmail( $editor->
getUser(), $title, $minorEdit )
192 'editor' => $editor->
getUser()->getName(),
193 'editorID' => $editor->
getUser()->getId(),
194 'timestamp' => $timestamp,
195 'summary' => $summary,
196 'minorEdit' => $minorEdit,
198 'watchers' => $watchers,
199 'pageStatus' => $pageStatus
232 $pageStatus =
'changed'
234 # we use $wgPasswordSender as sender's address
236 $mwServices = MediaWikiServices::getInstance();
237 $messageCache = $mwServices->getMessageCache();
238 $config = $mwServices->getMainConfig();
240 # The following code is only run, if several conditions are met:
241 # 1. EmailNotification for pages (other than user_talk pages) must be enabled
242 # 2. minor edits (changes) are only regarded if the global flag indicates so
244 $this->title = $title;
245 $this->timestamp = $timestamp;
246 $this->summary = $summary;
247 $this->minorEdit = $minorEdit;
248 $this->oldid = $oldid;
249 $this->editor = MediaWikiServices::getInstance()->getUserFactory()->newFromAuthority( $editor );
250 $this->composed_common =
false;
251 $this->pageStatus = $pageStatus;
253 $formattedPageStatus = [
'deleted',
'created',
'moved',
'restored',
'changed' ];
255 $hookRunner =
new HookRunner( $mwServices->getHookContainer() );
256 $hookRunner->onUpdateUserMailerFormattedPageStatus( $formattedPageStatus );
257 if ( !in_array( $this->pageStatus, $formattedPageStatus ) ) {
258 throw new UnexpectedValueException(
'Not a valid page status!' );
264 ( $config->get( MainConfigNames::EnotifMinorEdits ) &&
265 !$editor->
isAllowed(
'nominornewtalk' ) )
267 if ( $config->get( MainConfigNames::EnotifUserTalk )
269 && $this->canSendUserTalkEmail( $editor->
getUser(), $title, $minorEdit )
271 $targetUser = User::newFromName( $title->
getText() );
272 $this->compose( $targetUser, self::USER_TALK, $messageCache );
273 $userTalkId = $targetUser->getId();
276 if ( $config->get( MainConfigNames::EnotifWatchlist ) ) {
277 $userOptionsLookup = $mwServices->getUserOptionsLookup();
280 $userArray = UserArray::newFromIDs( $watchers );
281 foreach ( $userArray as $watchingUser ) {
282 if ( $userOptionsLookup->
getOption( $watchingUser,
'enotifwatchlistpages' )
283 && ( !$minorEdit || $userOptionsLookup->
getOption( $watchingUser,
'enotifminoredits' ) )
284 && $watchingUser->isEmailConfirmed()
285 && $watchingUser->getId() != $userTalkId
286 && !in_array( $watchingUser->getName(),
287 $config->get( MainConfigNames::UsersNotifiedOnAllChanges ) )
290 && !( $config->get( MainConfigNames::BlockDisablesLogin ) &&
291 $watchingUser->getBlock() )
292 && $hookRunner->onSendWatchlistEmailNotification( $watchingUser, $title, $this )
294 $this->compose( $watchingUser, self::WATCHLIST, $messageCache );
300 foreach ( $config->get( MainConfigNames::UsersNotifiedOnAllChanges ) as $name ) {
301 if ( $editor->
getUser()->getName() == $name ) {
305 $user = User::newFromName( $name );
306 if ( $user instanceof
User ) {
307 $this->compose( $user, self::ALL_CHANGES, $messageCache );
319 private function canSendUserTalkEmail(
UserIdentity $editor, $title, $minorEdit ) {
320 $services = MediaWikiServices::getInstance();
321 $config = $services->getMainConfig();
327 $userOptionsLookup = $services->getUserOptionsLookup();
328 $targetUser = User::newFromName( $title->
getText() );
330 if ( !$targetUser || $targetUser->isAnon() ) {
331 wfDebug( __METHOD__ .
": user talk page edited, but user does not exist" );
332 } elseif ( $targetUser->getId() == $editor->
getId() ) {
333 wfDebug( __METHOD__ .
": user edited their own talk page, no notification sent" );
334 } elseif ( $targetUser->isTemp() ) {
335 wfDebug( __METHOD__ .
": talk page owner is a temporary user so doesn't have email" );
336 } elseif ( $config->get( MainConfigNames::BlockDisablesLogin ) &&
337 $targetUser->getBlock()
341 wfDebug( __METHOD__ .
": talk page owner is blocked and cannot login, no notification sent" );
342 } elseif ( $userOptionsLookup->
getOption( $targetUser,
'enotifusertalkpages' )
343 && ( !$minorEdit || $userOptionsLookup->
getOption( $targetUser,
'enotifminoredits' ) )
345 if ( !$targetUser->isEmailConfirmed() ) {
346 wfDebug( __METHOD__ .
": talk page owner doesn't have validated email" );
347 } elseif ( !(
new HookRunner( $services->getHookContainer() ) )
348 ->onAbortTalkPageEmailNotification( $targetUser, $title )
350 wfDebug( __METHOD__ .
": talk page update notification is aborted for this user" );
352 wfDebug( __METHOD__ .
": sending talk page update notification" );
356 wfDebug( __METHOD__ .
": talk page owner doesn't want notifications" );
365 private function composeCommonMailtext(
MessageCache $messageCache ) {
366 $services = MediaWikiServices::getInstance();
367 $config = $services->getMainConfig();
368 $userOptionsLookup = $services->getUserOptionsLookup();
369 $urlUtils = $services->getUrlUtils();
371 $this->composed_common =
true;
373 # You as the WikiAdmin and Sysops can make use of plenty of
374 # named variables when composing your notification emails while
375 # simply editing the Meta pages
378 $postTransformKeys = [];
379 $pageTitleUrl = $this->title->getCanonicalURL();
380 $pageTitle = $this->title->getPrefixedText();
382 if ( $this->oldid ) {
386 $this->title->getCanonicalURL( [
'diff' =>
'next',
'oldid' => $this->oldid ] )
387 )->inContentLanguage()->text();
389 if ( !$config->get( MainConfigNames::EnotifImpersonal ) ) {
393 'enotif_lastvisited',
394 $this->title->getCanonicalURL( [
'diff' =>
'0',
'oldid' => $this->oldid ] )
395 )->inContentLanguage()->text();
397 $keys[
'$OLDID'] = $this->oldid;
398 $keys[
'$PAGELOG'] =
'';
401 $keys[
'$OLDID'] =
'';
402 $keys[
'$NEWPAGE'] =
'';
405 SpecialPage::getTitleFor(
'Log' )->getCanonicalURL( [
'page' => $this->title->getPrefixedDBkey() ] )
406 )->inContentLanguage()->text();
410 $keys[
'$PAGETITLE'] = $this->title->getPrefixedText();
411 $keys[
'$PAGETITLE_URL'] = $this->title->getCanonicalURL();
412 $keys[
'$PAGEMINOREDIT'] = $this->minorEdit ?
413 "\n\n" .
wfMessage(
'enotif_minoredit' )->inContentLanguage()->text() :
415 $keys[
'$UNWATCHURL'] = $this->title->getCanonicalURL(
'action=unwatch' );
417 if ( $this->editor->isAnon() ) {
418 # real anon (user:xxx.xxx.xxx.xxx)
419 $keys[
'$PAGEEDITOR'] =
wfMessage(
'enotif_anon_editor', $this->editor->getName() )
420 ->inContentLanguage()->text();
421 $keys[
'$PAGEEDITOR_EMAIL'] =
wfMessage(
'noemailtitle' )->inContentLanguage()->text();
422 } elseif ( $this->editor->isTemp() ) {
423 $keys[
'$PAGEEDITOR'] =
wfMessage(
'enotif_temp_editor', $this->editor->getName() )
424 ->inContentLanguage()->text();
425 $keys[
'$PAGEEDITOR_EMAIL'] =
wfMessage(
'noemailtitle' )->inContentLanguage()->text();
427 $keys[
'$PAGEEDITOR'] = $config->get( MainConfigNames::EnotifUseRealName ) &&
428 $this->editor->getRealName() !==
''
429 ? $this->editor->getRealName() : $this->editor->getName();
430 $emailPage = SpecialPage::getSafeTitleFor(
'Emailuser', $this->editor->getName() );
431 $keys[
'$PAGEEDITOR_EMAIL'] = $emailPage->getCanonicalURL();
434 $keys[
'$PAGEEDITOR_WIKI'] = $this->editor->getTalkPage()->getCanonicalURL();
435 $keys[
'$HELPPAGE'] = $urlUtils->expand(
440 # Replace this after transforming the message, T37019
441 $postTransformKeys[
'$PAGESUMMARY'] = $this->summary ==
'' ?
' - ' : $this->summary;
448 $this->subject =
wfMessage(
'enotif_subject_' . $this->pageStatus )->inContentLanguage()
449 ->params( $pageTitle, $keys[
'$PAGEEDITOR'] )->text();
454 $keys[
'$PAGEINTRO'] =
wfMessage(
'enotif_body_intro_' . $this->pageStatus )
455 ->inContentLanguage()
456 ->params( $pageTitle, $keys[
'$PAGEEDITOR'],
"<{$pageTitleUrl}>" )
459 $body =
wfMessage(
'enotif_body' )->inContentLanguage()->plain();
460 $body = strtr( $body, $keys );
461 $body = $messageCache->
transform( $body,
false,
null, $this->title );
462 $this->body = wordwrap( strtr( $body, $postTransformKeys ), 72 );
464 # Reveal the page editor's address as REPLY-TO address only if
465 # the user has not opted-out and the option is enabled at the
466 # global configuration level.
468 $config->get( MainConfigNames::PasswordSender ),
469 wfMessage(
'emailsender' )->inContentLanguage()->text()
471 if ( $config->get( MainConfigNames::EnotifRevealEditorAddress )
472 && ( $this->editor->getEmail() !=
'' )
473 && $userOptionsLookup->
getOption( $this->editor,
'enotifrevealaddr' )
476 if ( $config->get( MainConfigNames::EnotifFromEditor ) ) {
477 $this->from = $editorAddress;
479 $this->from = $adminAddress;
480 $this->replyto = $editorAddress;
483 $this->from = $adminAddress;
485 $config->get( MainConfigNames::NoReplyAddress )
500 if ( !$this->composed_common ) {
501 $this->composeCommonMailtext( $messageCache );
504 if ( MediaWikiServices::getInstance()->getMainConfig()->
get( MainConfigNames::EnotifImpersonal ) ) {
507 $this->sendPersonalised( $user,
$source );
514 private function sendMails() {
515 if ( MediaWikiServices::getInstance()->getMainConfig()->
get( MainConfigNames::EnotifImpersonal ) ) {
516 $this->sendImpersonal( $this->mailTargets );
537 # $PAGEEDITDATE is the time and date of the page change
538 # expressed in terms of individual local time of the notification
539 # recipient, i.e. watching user
540 $mwServices = MediaWikiServices::getInstance();
541 $contLang = $mwServices->getContentLanguage();
542 $watchingUserName = (
543 $mwServices->getMainConfig()->get( MainConfigNames::EnotifUseRealName ) &&
554 $contLang->userDate( $this->timestamp, $watchingUser->
getUser() ),
555 $contLang->userTime( $this->timestamp, $watchingUser->
getUser() )
561 if (
$source === self::WATCHLIST ) {
562 $headers[
'List-Help'] =
'https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Watchlist';
574 'replyTo' => $this->replyto,
575 'headers' => $headers,
586 private function sendImpersonal( array $addresses ): ?
StatusValue {
587 if ( count( $addresses ) === 0 ) {
590 $services = MediaWikiServices::getInstance();
591 $contLang = $services->getContentLanguage();
599 wfMessage(
'enotif_impersonal_salutation' )->inContentLanguage()->text(),
600 $contLang->date( $this->timestamp,
false,
false ),
601 $contLang->time( $this->timestamp,
false,
false )
615 'replyTo' => $this->replyto,