MediaWiki master
RecentChangeNotifier.php
Go to the documentation of this file.
1<?php
12
23use UnexpectedValueException;
24
43
53 public function notifyOnPageChange(
54 RecentChange $recentChange
55 ): bool {
56 // Never send an RC notification email about categorization changes
57 if ( $recentChange->getAttribute( 'rc_source' ) === RecentChange::SRC_CATEGORIZE ) {
58 return false;
59 }
60 $mwServices = MediaWikiServices::getInstance();
61 $config = $mwServices->getMainConfig();
62
63 $minorEdit = $recentChange->getAttribute( 'rc_minor' );
64 $editor = $mwServices->getUserFactory()
65 ->newFromUserIdentity( $recentChange->getPerformerIdentity() );
66
67 $title = Title::castFromPageReference( $recentChange->getPage() );
68 if ( $title === null || $title->getNamespace() < 0 ) {
69 return false;
70 }
71
72 // update wl_notificationtimestamp for watchers
73 $watchers = [];
74 if ( $config->get( MainConfigNames::EnotifWatchlist ) || $config->get( MainConfigNames::ShowUpdatedMarker ) ) {
75 $watchers = $mwServices->getWatchedItemStore()->updateNotificationTimestamp(
76 $editor,
77 $title,
78 $recentChange->getAttribute( 'rc_timestamp' )
79 );
80 }
81
82 $sendNotification = $this->shouldSendNotification(
83 $editor, $title, $watchers, $minorEdit, $config
84 );
85
86 if ( $sendNotification ) {
87 $mwServices->getJobQueueGroup()->lazyPush( new RecentChangeNotifyJob(
88 $title,
89 [
90 'editor' => $editor->getName(),
91 'editorID' => $editor->getId(),
92 'watchers' => $watchers,
93 'pageStatus' => $recentChange->mExtra['pageStatus'] ?? 'changed',
94 'rc_id' => $recentChange->getAttribute( 'rc_id' ),
95 ],
96 $mwServices->getRecentChangeLookup()
97 ) );
98 }
99
100 return $sendNotification;
101 }
102
103 private function shouldSendNotification(
104 User $editor,
105 Title $title,
106 array $watchers,
107 bool $minorEdit,
108 Config $config
109 ): bool {
110 // Don't send notifications for bots
111 if ( $editor->isBot() ) {
112 return false;
113 }
114
115 // If someone is watching the page or there are users notified on all changes
116 if ( count( $watchers ) ||
117 count( $config->get( MainConfigNames::UsersNotifiedOnAllChanges ) ) ) {
118 return true;
119 }
120
121 // if it's a talk page with an applicable notification.
122 if ( !$minorEdit ||
123 ( $config->get( MainConfigNames::EnotifMinorEdits ) &&
124 !$editor->isAllowed( 'nominornewtalk' ) )
125 ) {
126 return $this->canSendUserTalkEmail( $editor, $title, $minorEdit );
127 }
128
129 return false;
130 }
131
147 Authority $editor,
148 $title,
149 RecentChange $recentChange,
150 array $watchers,
151 $pageStatus = 'changed'
152 ) {
153 # we use $wgPasswordSender as sender's address
154 $mwServices = MediaWikiServices::getInstance();
155 $config = $mwServices->getMainConfig();
156 $notifService = $mwServices->getNotificationService();
157 $userFactory = $mwServices->getUserFactory();
158 $hookRunner = new HookRunner( $mwServices->getHookContainer() );
159
160 $minorEdit = $recentChange->getAttribute( 'rc_minor' );
161 # The following code is only run, if several conditions are met:
162 # 1. RecentChangeNotifier for pages (other than user_talk pages) must be enabled
163 # 2. minor edits (changes) are only regarded if the global flag indicates so
164 $formattedPageStatus = [ 'deleted', 'created', 'moved', 'restored', 'changed' ];
165 if ( !in_array( $pageStatus, $formattedPageStatus ) ) {
166 throw new UnexpectedValueException( 'Not a valid page status!' );
167 }
168 $agent = $mwServices->getUserFactory()->newFromAuthority( $editor );
169
170 $userTalkId = false;
171 if ( !$minorEdit ||
172 ( $config->get( MainConfigNames::EnotifMinorEdits ) &&
173 !$editor->isAllowed( 'nominornewtalk' ) )
174 ) {
175 if ( $config->get( MainConfigNames::EnotifUserTalk )
176 && $title->getNamespace() === NS_USER_TALK
177 && $this->canSendUserTalkEmail( $editor->getUser(), $title, $minorEdit )
178 ) {
179 $targetUser = $userFactory->newFromName( $title->getText() );
180 if ( $targetUser ) {
181 $talkNotification = new RecentChangeNotification(
182 $agent,
183 $title,
184 $recentChange,
185 $pageStatus,
186 RecentChangeNotification::TALK_NOTIFICATION
187 );
188 $notifService->notify( $talkNotification, new RecipientSet( [ $targetUser ] ) );
189 $userTalkId = $targetUser->getId();
190 }
191 }
192
193 if ( $config->get( MainConfigNames::EnotifWatchlist ) ) {
194 $userOptionsLookup = $mwServices->getUserOptionsLookup();
195 // Send updates to watchers other than the current editor
196 // and don't send to watchers who are blocked and cannot login
197 $userArray = UserArray::newFromIDs( $watchers );
198 $recipients = new RecipientSet( [] );
199 foreach ( $userArray as $watchingUser ) {
200 if ( $userOptionsLookup->getOption( $watchingUser, 'enotifwatchlistpages' )
201 && ( !$minorEdit || $userOptionsLookup->getOption( $watchingUser, 'enotifminoredits' ) )
202 && $watchingUser->getId() != $userTalkId
203 && !in_array( $watchingUser->getName(),
204 $config->get( MainConfigNames::UsersNotifiedOnAllChanges ) )
205 // @TODO Partial blocks should not prevent the user from logging in.
206 // see: https://phabricator.wikimedia.org/T208895
207 && !( $config->get( MainConfigNames::BlockDisablesLogin ) &&
208 $watchingUser->getBlock() )
209 ) {
210 $recipients->addRecipient( $watchingUser );
211 }
212 }
213 if ( count( $recipients ) !== 0 ) {
214 $talkNotification = new RecentChangeNotification(
215 $agent,
216 $title,
217 $recentChange,
218 $pageStatus,
219 RecentChangeNotification::WATCHLIST_NOTIFICATION
220 );
221 $notifService->notify( $talkNotification, $recipients );
222 }
223 }
224 }
225
226 foreach ( $config->get( MainConfigNames::UsersNotifiedOnAllChanges ) as $name ) {
227 $admins = [];
228 if ( $editor->getUser()->getName() == $name ) {
229 // No point notifying the user that actually made the change!
230 continue;
231 }
232 $user = $userFactory->newFromName( $name );
233 if ( $user instanceof User ) {
234 $admins[] = $user;
235 }
236 $notifService->notify(
238 $agent,
239 $title,
240 $recentChange,
241 $pageStatus,
242 RecentChangeNotification::ADMIN_NOTIFICATION
243 ),
244 new RecipientSet( $admins )
245 );
246
247 }
248 }
249
256 private function canSendUserTalkEmail( UserIdentity $editor, $title, $minorEdit ) {
257 $services = MediaWikiServices::getInstance();
258 $config = $services->getMainConfig();
259
260 if ( !$config->get( MainConfigNames::EnotifUserTalk ) || $title->getNamespace() !== NS_USER_TALK ) {
261 return false;
262 }
263
264 $userOptionsLookup = $services->getUserOptionsLookup();
265 $targetUser = User::newFromName( $title->getText() );
266
267 if ( !$targetUser || $targetUser->isAnon() ) {
268 wfDebug( __METHOD__ . ": user talk page edited, but user does not exist" );
269 } elseif ( $targetUser->getId() == $editor->getId() ) {
270 wfDebug( __METHOD__ . ": user edited their own talk page, no notification sent" );
271 } elseif ( $targetUser->isTemp() ) {
272 wfDebug( __METHOD__ . ": talk page owner is a temporary user so doesn't have email" );
273 } elseif ( $config->get( MainConfigNames::BlockDisablesLogin ) &&
274 $targetUser->getBlock()
275 ) {
276 // @TODO Partial blocks should not prevent the user from logging in.
277 // see: https://phabricator.wikimedia.org/T208895
278 wfDebug( __METHOD__ . ": talk page owner is blocked and cannot login, no notification sent" );
279 } elseif ( $userOptionsLookup->getOption( $targetUser, 'enotifusertalkpages' )
280 && ( !$minorEdit || $userOptionsLookup->getOption( $targetUser, 'enotifminoredits' ) )
281 ) {
282 wfDebug( __METHOD__ . ": sending talk page update notification" );
283 return true;
284 } else {
285 wfDebug( __METHOD__ . ": talk page owner doesn't want notifications" );
286 }
287 return false;
288 }
289
290}
291
293class_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.
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:71
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.
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:130
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)