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