MediaWiki master
RecentChangeNotifier.php
Go to the documentation of this file.
1<?php
12
23use UnexpectedValueException;
24
42
43 protected string $pageStatus = '';
44
56 public function getPageStatus() {
57 wfDeprecated( __METHOD__, '1.45' );
58 return $this->pageStatus;
59 }
60
70 public function notifyOnPageChange(
71 RecentChange $recentChange
72 ): bool {
73 // Never send an RC notification email about categorization changes
74 if ( $recentChange->getAttribute( 'rc_source' ) === RecentChange::SRC_CATEGORIZE ) {
75 return false;
76 }
77 $mwServices = MediaWikiServices::getInstance();
78 $config = $mwServices->getMainConfig();
79
80 $minorEdit = $recentChange->getAttribute( 'rc_minor' );
81 $editor = $mwServices->getUserFactory()
82 ->newFromUserIdentity( $recentChange->getPerformerIdentity() );
83
84 $title = Title::castFromPageReference( $recentChange->getPage() );
85 if ( $title === null || $title->getNamespace() < 0 ) {
86 return false;
87 }
88
89 // update wl_notificationtimestamp for watchers
90 $watchers = [];
91 if ( $config->get( MainConfigNames::EnotifWatchlist ) || $config->get( MainConfigNames::ShowUpdatedMarker ) ) {
92 $watchers = $mwServices->getWatchedItemStore()->updateNotificationTimestamp(
93 $editor,
94 $title,
95 $recentChange->getAttribute( 'rc_timestamp' )
96 );
97 }
98
99 $sendNotification = $this->shouldSendNotification(
100 $editor, $title, $watchers, $minorEdit, $config
101 );
102
103 if ( $sendNotification ) {
104 $mwServices->getJobQueueGroup()->lazyPush( new RecentChangeNotifyJob(
105 $title,
106 [
107 'editor' => $editor->getName(),
108 'editorID' => $editor->getId(),
109 'watchers' => $watchers,
110 'pageStatus' => $recentChange->mExtra['pageStatus'] ?? 'changed',
111 'rc_id' => $recentChange->getAttribute( 'rc_id' ),
112 ],
113 $mwServices->getRecentChangeLookup()
114 ) );
115 }
116
117 return $sendNotification;
118 }
119
120 private function shouldSendNotification(
121 User $editor,
122 Title $title,
123 array $watchers,
124 bool $minorEdit,
125 Config $config
126 ): bool {
127 // Don't send notifications for bots
128 if ( $editor->isBot() ) {
129 return false;
130 }
131
132 // If someone is watching the page or there are users notified on all changes
133 if ( count( $watchers ) ||
134 count( $config->get( MainConfigNames::UsersNotifiedOnAllChanges ) ) ) {
135 return true;
136 }
137
138 // if it's a talk page with an applicable notification.
139 if ( !$minorEdit ||
140 ( $config->get( MainConfigNames::EnotifMinorEdits ) &&
141 !$editor->isAllowed( 'nominornewtalk' ) )
142 ) {
143 return $this->canSendUserTalkEmail( $editor, $title, $minorEdit );
144 }
145
146 return false;
147 }
148
164 Authority $editor,
165 $title,
166 RecentChange $recentChange,
167 array $watchers,
168 $pageStatus = 'changed'
169 ) {
170 # we use $wgPasswordSender as sender's address
171 $mwServices = MediaWikiServices::getInstance();
172 $config = $mwServices->getMainConfig();
173 $notifService = $mwServices->getNotificationService();
174 $userFactory = $mwServices->getUserFactory();
175 $hookRunner = new HookRunner( $mwServices->getHookContainer() );
176
177 $minorEdit = $recentChange->getAttribute( 'rc_minor' );
178 # The following code is only run, if several conditions are met:
179 # 1. RecentChangeNotifier for pages (other than user_talk pages) must be enabled
180 # 2. minor edits (changes) are only regarded if the global flag indicates so
181 $this->pageStatus = $pageStatus;
182 $formattedPageStatus = [ 'deleted', 'created', 'moved', 'restored', 'changed' ];
183
184 $hookRunner->onUpdateUserMailerFormattedPageStatus( $formattedPageStatus );
185 if ( !in_array( $this->pageStatus, $formattedPageStatus ) ) {
186 throw new UnexpectedValueException( 'Not a valid page status!' );
187 }
188 $agent = $mwServices->getUserFactory()->newFromAuthority( $editor );
189
190 $userTalkId = false;
191 if ( !$minorEdit ||
192 ( $config->get( MainConfigNames::EnotifMinorEdits ) &&
193 !$editor->isAllowed( 'nominornewtalk' ) )
194 ) {
195 if ( $config->get( MainConfigNames::EnotifUserTalk )
196 && $title->getNamespace() === NS_USER_TALK
197 && $this->canSendUserTalkEmail( $editor->getUser(), $title, $minorEdit )
198 ) {
199 $targetUser = $userFactory->newFromName( $title->getText() );
200 if ( $targetUser ) {
201 $talkNotification = new RecentChangeNotification(
202 $agent,
203 $title,
204 $recentChange,
205 $pageStatus,
206 RecentChangeNotification::TALK_NOTIFICATION
207 );
208 $notifService->notify( $talkNotification, new RecipientSet( [ $targetUser ] ) );
209 $userTalkId = $targetUser->getId();
210 }
211 }
212
213 if ( $config->get( MainConfigNames::EnotifWatchlist ) ) {
214 $userOptionsLookup = $mwServices->getUserOptionsLookup();
215 // Send updates to watchers other than the current editor
216 // and don't send to watchers who are blocked and cannot login
217 $userArray = UserArray::newFromIDs( $watchers );
218 $recipients = new RecipientSet( [] );
219 foreach ( $userArray as $watchingUser ) {
220 if ( $userOptionsLookup->getOption( $watchingUser, 'enotifwatchlistpages' )
221 && ( !$minorEdit || $userOptionsLookup->getOption( $watchingUser, 'enotifminoredits' ) )
222 && $watchingUser->getId() != $userTalkId
223 && !in_array( $watchingUser->getName(),
224 $config->get( MainConfigNames::UsersNotifiedOnAllChanges ) )
225 // @TODO Partial blocks should not prevent the user from logging in.
226 // see: https://phabricator.wikimedia.org/T208895
227 && !( $config->get( MainConfigNames::BlockDisablesLogin ) &&
228 $watchingUser->getBlock() )
229 && $hookRunner->onSendWatchlistEmailNotification( $watchingUser, $title, $this )
230 ) {
231 $recipients->addRecipient( $watchingUser );
232 }
233 }
234 if ( count( $recipients ) !== 0 ) {
235 $talkNotification = new RecentChangeNotification(
236 $agent,
237 $title,
238 $recentChange,
239 $pageStatus,
240 RecentChangeNotification::WATCHLIST_NOTIFICATION
241 );
242 $notifService->notify( $talkNotification, $recipients );
243 }
244 }
245 }
246
247 foreach ( $config->get( MainConfigNames::UsersNotifiedOnAllChanges ) as $name ) {
248 $admins = [];
249 if ( $editor->getUser()->getName() == $name ) {
250 // No point notifying the user that actually made the change!
251 continue;
252 }
253 $user = $userFactory->newFromName( $name );
254 if ( $user instanceof User ) {
255 $admins[] = $user;
256 }
257 $notifService->notify(
259 $agent,
260 $title,
261 $recentChange,
262 $pageStatus,
263 RecentChangeNotification::ADMIN_NOTIFICATION
264 ),
265 new RecipientSet( $admins )
266 );
267
268 }
269 }
270
277 private function canSendUserTalkEmail( UserIdentity $editor, $title, $minorEdit ) {
278 $services = MediaWikiServices::getInstance();
279 $config = $services->getMainConfig();
280
281 if ( !$config->get( MainConfigNames::EnotifUserTalk ) || $title->getNamespace() !== NS_USER_TALK ) {
282 return false;
283 }
284
285 $userOptionsLookup = $services->getUserOptionsLookup();
286 $targetUser = User::newFromName( $title->getText() );
287
288 if ( !$targetUser || $targetUser->isAnon() ) {
289 wfDebug( __METHOD__ . ": user talk page edited, but user does not exist" );
290 } elseif ( $targetUser->getId() == $editor->getId() ) {
291 wfDebug( __METHOD__ . ": user edited their own talk page, no notification sent" );
292 } elseif ( $targetUser->isTemp() ) {
293 wfDebug( __METHOD__ . ": talk page owner is a temporary user so doesn't have email" );
294 } elseif ( $config->get( MainConfigNames::BlockDisablesLogin ) &&
295 $targetUser->getBlock()
296 ) {
297 // @TODO Partial blocks should not prevent the user from logging in.
298 // see: https://phabricator.wikimedia.org/T208895
299 wfDebug( __METHOD__ . ": talk page owner is blocked and cannot login, no notification sent" );
300 } elseif ( $userOptionsLookup->getOption( $targetUser, 'enotifusertalkpages' )
301 && ( !$minorEdit || $userOptionsLookup->getOption( $targetUser, 'enotifminoredits' ) )
302 ) {
303 if ( !( new HookRunner( $services->getHookContainer() ) )
304 ->onAbortTalkPageEmailNotification( $targetUser, $title )
305 ) {
306 wfDebug( __METHOD__ . ": talk page update notification is aborted for this user" );
307 } else {
308 wfDebug( __METHOD__ . ": sending talk page update notification" );
309 return true;
310 }
311 } else {
312 wfDebug( __METHOD__ . ": talk page owner doesn't want notifications" );
313 }
314 return false;
315 }
316
317}
318
320class_alias( RecentChangeNotifier::class, 'EmailNotification' );
const NS_USER_TALK
Definition Defines.php:54
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:68
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.
const EnotifWatchlist
Name constant for the EnotifWatchlist setting, for use with Config::get()
const EnotifMinorEdits
Name constant for the EnotifMinorEdits setting, for use with Config::get()
const UsersNotifiedOnAllChanges
Name constant for the UsersNotifiedOnAllChanges setting, for use with Config::get()
const ShowUpdatedMarker
Name constant for the ShowUpdatedMarker setting, for use with Config::get()
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
Notification representing a Recent change.
Find watchers and create notifications after a page is changed.
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 ...
actuallyNotifyOnPageChange(Authority $editor, $title, RecentChange $recentChange, array $watchers, $pageStatus='changed')
Immediate version of notifyOnPageChange().
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:69
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:19
User class for the MediaWiki software.
Definition User.php:108
Interface for configuration instances.
Definition Config.php:18
This interface represents the authority associated with the current execution context,...
Definition Authority.php:23
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)