MediaWiki master
EmailNotification.php
Go to the documentation of this file.
1<?php
36
54
55 protected string $pageStatus = '';
56
67 public function getPageStatus() {
68 return $this->pageStatus;
69 }
70
80 public function notifyOnPageChange(
81 RecentChange $recentChange
82 ): bool {
83 // Never send an RC notification email about categorization changes
84 if ( $recentChange->getAttribute( 'rc_type' ) === RC_CATEGORIZE ) {
85 return false;
86 }
87 $mwServices = MediaWikiServices::getInstance();
88 $config = $mwServices->getMainConfig();
89
90 $minorEdit = $recentChange->getAttribute( 'rc_minor' );
91 $editor = $mwServices->getUserFactory()
92 ->newFromUserIdentity( $recentChange->getPerformerIdentity() );
93
94 $title = Title::castFromPageReference( $recentChange->getPage() );
95 if ( $title === null || $title->getNamespace() < 0 ) {
96 return false;
97 }
98
99 // update wl_notificationtimestamp for watchers
100 $watchers = [];
101 if ( $config->get( MainConfigNames::EnotifWatchlist ) || $config->get( MainConfigNames::ShowUpdatedMarker ) ) {
102 $watchers = $mwServices->getWatchedItemStore()->updateNotificationTimestamp(
103 $editor,
104 $title,
105 $recentChange->getAttribute( 'rc_timestamp' )
106 );
107 }
108
109 // Don't send email for bots
110 if ( $editor->isBot() ) {
111 return false;
112 }
113
114 $sendNotification = true;
115 // $watchers deals with $wgEnotifWatchlist.
116 // If nobody is watching the page, and there are no users notified on all changes
117 // don't bother creating a job/trying to send emails, unless it's a
118 // talk page with an applicable notification.
119 if ( $watchers === [] &&
120 !count( $config->get( MainConfigNames::UsersNotifiedOnAllChanges ) )
121 ) {
122 $sendNotification = false;
123 // Only send notification for non minor edits, unless $wgEnotifMinorEdits
124 if ( !$minorEdit ||
125 ( $config->get( MainConfigNames::EnotifMinorEdits ) &&
126 !$editor->isAllowed( 'nominornewtalk' ) )
127 ) {
128 if ( $config->get( MainConfigNames::EnotifUserTalk )
129 && $title->getNamespace() === NS_USER_TALK
130 && $this->canSendUserTalkEmail( $editor, $title, $minorEdit )
131 ) {
132 $sendNotification = true;
133 }
134 }
135 }
136
137 if ( $sendNotification ) {
138 $mwServices->getJobQueueGroup()->lazyPush( new EnotifNotifyJob(
139 $title,
140 [
141 'editor' => $editor->getName(),
142 'editorID' => $editor->getId(),
143 'watchers' => $watchers,
144 'pageStatus' => $recentChange->mExtra['pageStatus'] ?? 'changed',
145 'rc_id' => $recentChange->getAttribute( 'rc_id' ),
146 ]
147 ) );
148 }
149
150 return $sendNotification;
151 }
152
168 Authority $editor,
169 $title,
170 RecentChange $recentChange,
171 array $watchers,
172 $pageStatus = 'changed'
173 ) {
174 # we use $wgPasswordSender as sender's address
175 $mwServices = MediaWikiServices::getInstance();
176 $config = $mwServices->getMainConfig();
177 $notifService = $mwServices->getNotificationService();
178 $userFactory = $mwServices->getUserFactory();
179 $hookRunner = new HookRunner( $mwServices->getHookContainer() );
180
181 $minorEdit = $recentChange->getAttribute( 'rc_minor' );
182 # The following code is only run, if several conditions are met:
183 # 1. EmailNotification for pages (other than user_talk pages) must be enabled
184 # 2. minor edits (changes) are only regarded if the global flag indicates so
185 $this->pageStatus = $pageStatus;
186 $formattedPageStatus = [ 'deleted', 'created', 'moved', 'restored', 'changed' ];
187
188 $hookRunner->onUpdateUserMailerFormattedPageStatus( $formattedPageStatus );
189 if ( !in_array( $this->pageStatus, $formattedPageStatus ) ) {
190 throw new UnexpectedValueException( 'Not a valid page status!' );
191 }
192 $agent = $mwServices->getUserFactory()->newFromAuthority( $editor );
193
194 $userTalkId = false;
195 if ( !$minorEdit ||
196 ( $config->get( MainConfigNames::EnotifMinorEdits ) &&
197 !$editor->isAllowed( 'nominornewtalk' ) )
198 ) {
199 if ( $config->get( MainConfigNames::EnotifUserTalk )
200 && $title->getNamespace() === NS_USER_TALK
201 && $this->canSendUserTalkEmail( $editor->getUser(), $title, $minorEdit )
202 ) {
203 $targetUser = $userFactory->newFromName( $title->getText() );
204 if ( $targetUser ) {
205 $talkNotification = new RecentChangeNotification(
206 $agent,
207 $title,
208 $recentChange,
209 $pageStatus,
210 RecentChangeNotification::TALK_NOTIFICATION
211 );
212 $notifService->notify( $talkNotification, new RecipientSet( [ $targetUser ] ) );
213 $userTalkId = $targetUser->getId();
214 }
215 }
216
217 if ( $config->get( MainConfigNames::EnotifWatchlist ) ) {
218 $userOptionsLookup = $mwServices->getUserOptionsLookup();
219 // Send updates to watchers other than the current editor
220 // and don't send to watchers who are blocked and cannot login
221 $userArray = UserArray::newFromIDs( $watchers );
222 $recipients = new RecipientSet( [] );
223 foreach ( $userArray as $watchingUser ) {
224 if ( $userOptionsLookup->getOption( $watchingUser, 'enotifwatchlistpages' )
225 && ( !$minorEdit || $userOptionsLookup->getOption( $watchingUser, 'enotifminoredits' ) )
226 && $watchingUser->getId() != $userTalkId
227 && !in_array( $watchingUser->getName(),
228 $config->get( MainConfigNames::UsersNotifiedOnAllChanges ) )
229 // @TODO Partial blocks should not prevent the user from logging in.
230 // see: https://phabricator.wikimedia.org/T208895
231 && !( $config->get( MainConfigNames::BlockDisablesLogin ) &&
232 $watchingUser->getBlock() )
233 && $hookRunner->onSendWatchlistEmailNotification( $watchingUser, $title, $this )
234 ) {
235 $recipients->addRecipient( $watchingUser );
236 }
237 }
238 if ( count( $recipients ) !== 0 ) {
239 $talkNotification = new RecentChangeNotification(
240 $agent,
241 $title,
242 $recentChange,
243 $pageStatus,
244 RecentChangeNotification::WATCHLIST_NOTIFICATION
245 );
246 $notifService->notify( $talkNotification, $recipients );
247 }
248 }
249 }
250
251 foreach ( $config->get( MainConfigNames::UsersNotifiedOnAllChanges ) as $name ) {
252 $admins = [];
253 if ( $editor->getUser()->getName() == $name ) {
254 // No point notifying the user that actually made the change!
255 continue;
256 }
257 $user = $userFactory->newFromName( $name );
258 if ( $user instanceof User ) {
259 $admins[] = $user;
260 }
261 $notifService->notify(
263 $agent,
264 $title,
265 $recentChange,
266 $pageStatus,
267 RecentChangeNotification::ADMIN_NOTIFICATION
268 ),
269 new RecipientSet( $admins )
270 );
271
272 }
273 }
274
281 private function canSendUserTalkEmail( UserIdentity $editor, $title, $minorEdit ) {
282 $services = MediaWikiServices::getInstance();
283 $config = $services->getMainConfig();
284
285 if ( !$config->get( MainConfigNames::EnotifUserTalk ) || $title->getNamespace() !== NS_USER_TALK ) {
286 return false;
287 }
288
289 $userOptionsLookup = $services->getUserOptionsLookup();
290 $targetUser = User::newFromName( $title->getText() );
291
292 if ( !$targetUser || $targetUser->isAnon() ) {
293 wfDebug( __METHOD__ . ": user talk page edited, but user does not exist" );
294 } elseif ( $targetUser->getId() == $editor->getId() ) {
295 wfDebug( __METHOD__ . ": user edited their own talk page, no notification sent" );
296 } elseif ( $targetUser->isTemp() ) {
297 wfDebug( __METHOD__ . ": talk page owner is a temporary user so doesn't have email" );
298 } elseif ( $config->get( MainConfigNames::BlockDisablesLogin ) &&
299 $targetUser->getBlock()
300 ) {
301 // @TODO Partial blocks should not prevent the user from logging in.
302 // see: https://phabricator.wikimedia.org/T208895
303 wfDebug( __METHOD__ . ": talk page owner is blocked and cannot login, no notification sent" );
304 } elseif ( $userOptionsLookup->getOption( $targetUser, 'enotifusertalkpages' )
305 && ( !$minorEdit || $userOptionsLookup->getOption( $targetUser, 'enotifminoredits' ) )
306 ) {
307 if ( !( new HookRunner( $services->getHookContainer() ) )
308 ->onAbortTalkPageEmailNotification( $targetUser, $title )
309 ) {
310 wfDebug( __METHOD__ . ": talk page update notification is aborted for this user" );
311 } else {
312 wfDebug( __METHOD__ . ": sending talk page update notification" );
313 return true;
314 }
315 } else {
316 wfDebug( __METHOD__ . ": talk page owner doesn't want notifications" );
317 }
318 return false;
319 }
320
321}
const NS_USER_TALK
Definition Defines.php:68
const RC_CATEGORIZE
Definition Defines.php:121
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:82
Find watchers and create notifications after a page is changed.
actuallyNotifyOnPageChange(Authority $editor, $title, RecentChange $recentChange, array $watchers, $pageStatus='changed')
Immediate version of notifyOnPageChange().
notifyOnPageChange(RecentChange $recentChange)
Send emails corresponding to the user $editor editing the page $title.
getPageStatus()
Extensions that have hooks for UpdateUserMailerFormattedPageStatus (to provide additional pageStatus ...
Send an email notification.
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
Utility class for creating and reading rows in the recentchanges table.
getAttribute( $name)
Get an attribute value.
Represents a title within MediaWiki.
Definition Title.php:78
getOption(UserIdentity $user, string $oname, $defaultOverride=null, bool $ignoreHidden=false, int $queryFlags=IDBAccessObject::READ_NORMAL)
Get the user's current setting for a given option.
Class to walk into a list of User objects.
Definition UserArray.php:33
User class for the MediaWiki software.
Definition User.php:123
Notification representing a Recent change.
This interface represents the authority associated with the current execution context,...
Definition Authority.php:37
isAllowed(string $permission, ?PermissionStatus $status=null)
Checks whether this authority has the given permission in general.
getUser()
Returns the performer of the actions associated with this authority.
Interface for objects representing user identity.
getId( $wikiId=self::LOCAL)