Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
13.79% |
36 / 261 |
|
0.00% |
0 / 9 |
CRAP | |
0.00% |
0 / 1 |
EmailNotification | |
13.79% |
36 / 261 |
|
0.00% |
0 / 9 |
3678.70 | |
0.00% |
0 / 1 |
getPageStatus | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
notifyOnPageChange | |
92.31% |
36 / 39 |
|
0.00% |
0 / 1 |
14.09 | |||
actuallyNotifyOnPageChange | |
0.00% |
0 / 47 |
|
0.00% |
0 / 1 |
506 | |||
canSendUserTalkEmail | |
0.00% |
0 / 26 |
|
0.00% |
0 / 1 |
210 | |||
composeCommonMailtext | |
0.00% |
0 / 76 |
|
0.00% |
0 / 1 |
182 | |||
compose | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
sendMails | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
sendPersonalised | |
0.00% |
0 / 36 |
|
0.00% |
0 / 1 |
20 | |||
sendImpersonal | |
0.00% |
0 / 29 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | /** |
3 | * Classes used to send e-mails |
4 | * |
5 | * This program is free software; you can redistribute it and/or modify |
6 | * it under the terms of the GNU General Public License as published by |
7 | * the Free Software Foundation; either version 2 of the License, or |
8 | * (at your option) any later version. |
9 | * |
10 | * This program is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | * GNU General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU General Public License along |
16 | * with this program; if not, write to the Free Software Foundation, Inc., |
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
18 | * http://www.gnu.org/copyleft/gpl.html |
19 | * |
20 | * @file |
21 | * @author Brooke Vibber |
22 | * @author <mail@tgries.de> |
23 | * @author Tim Starling |
24 | * @author Luke Welling lwelling@wikimedia.org |
25 | */ |
26 | |
27 | use MediaWiki\HookContainer\HookRunner; |
28 | use MediaWiki\Mail\UserEmailContact; |
29 | use MediaWiki\MainConfigNames; |
30 | use MediaWiki\MediaWikiServices; |
31 | use MediaWiki\Permissions\Authority; |
32 | use MediaWiki\SpecialPage\SpecialPage; |
33 | use MediaWiki\Title\Title; |
34 | use MediaWiki\User\User; |
35 | use MediaWiki\User\UserArray; |
36 | use MediaWiki\User\UserIdentity; |
37 | |
38 | /** |
39 | * This module processes the email notifications when the current page is |
40 | * changed. It queries the `watchlist` table to find which users are watching |
41 | * that page. |
42 | * |
43 | * The current implementation sends independent emails to each watching user for |
44 | * the following reason: |
45 | * |
46 | * - Each watching user will be notified about the page edit time expressed in |
47 | * his/her local time (UTC is shown additionally). To achieve this, we need to |
48 | * find the individual timeoffset of each watching user from the preferences. |
49 | * |
50 | * Suggested improvement to reduce the number of sent emails: We could think |
51 | * of sending out bulk mails (bcc:user1,user2...) for all these users having the |
52 | * same timeoffset in their preferences. |
53 | * |
54 | * Visit the documentation pages under |
55 | * https://www.mediawiki.org/wiki/Help:Watching_pages |
56 | * |
57 | * TODO use UserOptionsLookup and other services, consider converting this to a service |
58 | */ |
59 | class EmailNotification { |
60 | |
61 | /** |
62 | * Notification is due to user's user talk being edited |
63 | */ |
64 | private const USER_TALK = 'user_talk'; |
65 | /** |
66 | * Notification is due to a watchlisted page being edited |
67 | */ |
68 | private const WATCHLIST = 'watchlist'; |
69 | /** |
70 | * Notification because user is notified for all changes |
71 | */ |
72 | private const ALL_CHANGES = 'all_changes'; |
73 | |
74 | protected string $subject = ''; |
75 | |
76 | protected string $body = ''; |
77 | |
78 | protected ?MailAddress $replyto; |
79 | |
80 | protected ?MailAddress $from; |
81 | |
82 | protected ?string $timestamp; |
83 | |
84 | protected string $summary = ''; |
85 | |
86 | protected ?bool $minorEdit; |
87 | |
88 | /** @var int|null|bool */ |
89 | protected $oldid; |
90 | |
91 | protected bool $composed_common = false; |
92 | |
93 | protected string $pageStatus = ''; |
94 | |
95 | /** @var MailAddress[] */ |
96 | protected array $mailTargets = []; |
97 | |
98 | protected Title $title; |
99 | |
100 | protected User $editor; |
101 | |
102 | /** |
103 | * Extensions that have hooks for |
104 | * UpdateUserMailerFormattedPageStatus (to provide additional |
105 | * pageStatus indicators) need a way to make sure that, when their |
106 | * hook is called in SendWatchlistemailNotification, they only |
107 | * handle notifications using their pageStatus indicator. |
108 | * |
109 | * @since 1.33 |
110 | * @return string |
111 | */ |
112 | public function getPageStatus() { |
113 | return $this->pageStatus; |
114 | } |
115 | |
116 | /** |
117 | * Send emails corresponding to the user $editor editing the page $title. |
118 | * |
119 | * May be deferred via the job queue. |
120 | * |
121 | * @param Authority $editor |
122 | * @param Title $title |
123 | * @param string $timestamp |
124 | * @param string $summary |
125 | * @param bool $minorEdit |
126 | * @param bool $oldid (default: false) |
127 | * @param string $pageStatus (default: 'changed') |
128 | * @return bool Whether an email job was created or not. |
129 | * @since 1.35 returns a boolean indicating whether an email job was created. |
130 | */ |
131 | public function notifyOnPageChange( |
132 | Authority $editor, |
133 | $title, |
134 | $timestamp, |
135 | $summary, |
136 | $minorEdit, |
137 | $oldid = false, |
138 | $pageStatus = 'changed' |
139 | ): bool { |
140 | if ( $title->getNamespace() < 0 ) { |
141 | return false; |
142 | } |
143 | |
144 | $mwServices = MediaWikiServices::getInstance(); |
145 | $config = $mwServices->getMainConfig(); |
146 | |
147 | // update wl_notificationtimestamp for watchers |
148 | $watchers = []; |
149 | if ( $config->get( MainConfigNames::EnotifWatchlist ) || $config->get( MainConfigNames::ShowUpdatedMarker ) ) { |
150 | $watchers = $mwServices->getWatchedItemStore()->updateNotificationTimestamp( |
151 | $editor->getUser(), |
152 | $title, |
153 | $timestamp |
154 | ); |
155 | } |
156 | |
157 | // Don't send email for bots |
158 | if ( $mwServices->getUserFactory()->newFromAuthority( $editor )->isBot() ) { |
159 | return false; |
160 | } |
161 | |
162 | $sendEmail = true; |
163 | // $watchers deals with $wgEnotifWatchlist. |
164 | // If nobody is watching the page, and there are no users notified on all changes |
165 | // don't bother creating a job/trying to send emails, unless it's a |
166 | // talk page with an applicable notification. |
167 | if ( $watchers === [] && |
168 | !count( $config->get( MainConfigNames::UsersNotifiedOnAllChanges ) ) |
169 | ) { |
170 | $sendEmail = false; |
171 | // Only send notification for non minor edits, unless $wgEnotifMinorEdits |
172 | if ( !$minorEdit || |
173 | ( $config->get( MainConfigNames::EnotifMinorEdits ) && |
174 | !$editor->isAllowed( 'nominornewtalk' ) ) |
175 | ) { |
176 | if ( $config->get( MainConfigNames::EnotifUserTalk ) |
177 | && $title->getNamespace() === NS_USER_TALK |
178 | && $this->canSendUserTalkEmail( $editor->getUser(), $title, $minorEdit ) |
179 | ) { |
180 | $sendEmail = true; |
181 | } |
182 | } |
183 | } |
184 | |
185 | if ( $sendEmail ) { |
186 | $mwServices->getJobQueueGroup()->lazyPush( new EnotifNotifyJob( |
187 | $title, |
188 | [ |
189 | 'editor' => $editor->getUser()->getName(), |
190 | 'editorID' => $editor->getUser()->getId(), |
191 | 'timestamp' => $timestamp, |
192 | 'summary' => $summary, |
193 | 'minorEdit' => $minorEdit, |
194 | 'oldid' => $oldid, |
195 | 'watchers' => $watchers, |
196 | 'pageStatus' => $pageStatus |
197 | ] |
198 | ) ); |
199 | } |
200 | |
201 | return $sendEmail; |
202 | } |
203 | |
204 | /** |
205 | * Immediate version of notifyOnPageChange(). |
206 | * |
207 | * Send emails corresponding to the user $editor editing the page $title. |
208 | * |
209 | * @note Do not call directly. Use notifyOnPageChange so that wl_notificationtimestamp is updated. |
210 | * @param Authority $editor |
211 | * @param Title $title |
212 | * @param string $timestamp Edit timestamp |
213 | * @param string $summary Edit summary |
214 | * @param bool $minorEdit |
215 | * @param int $oldid Revision ID |
216 | * @param array $watchers Array of user IDs |
217 | * @param string $pageStatus |
218 | */ |
219 | public function actuallyNotifyOnPageChange( |
220 | Authority $editor, |
221 | $title, |
222 | $timestamp, |
223 | $summary, |
224 | $minorEdit, |
225 | $oldid, |
226 | $watchers, |
227 | $pageStatus = 'changed' |
228 | ) { |
229 | # we use $wgPasswordSender as sender's address |
230 | |
231 | $mwServices = MediaWikiServices::getInstance(); |
232 | $messageCache = $mwServices->getMessageCache(); |
233 | $config = $mwServices->getMainConfig(); |
234 | |
235 | # The following code is only run, if several conditions are met: |
236 | # 1. EmailNotification for pages (other than user_talk pages) must be enabled |
237 | # 2. minor edits (changes) are only regarded if the global flag indicates so |
238 | |
239 | $this->title = $title; |
240 | $this->timestamp = $timestamp; |
241 | $this->summary = $summary; |
242 | $this->minorEdit = $minorEdit; |
243 | $this->oldid = $oldid; |
244 | $this->editor = MediaWikiServices::getInstance()->getUserFactory()->newFromAuthority( $editor ); |
245 | $this->composed_common = false; |
246 | $this->pageStatus = $pageStatus; |
247 | |
248 | $formattedPageStatus = [ 'deleted', 'created', 'moved', 'restored', 'changed' ]; |
249 | |
250 | $hookRunner = new HookRunner( $mwServices->getHookContainer() ); |
251 | $hookRunner->onUpdateUserMailerFormattedPageStatus( $formattedPageStatus ); |
252 | if ( !in_array( $this->pageStatus, $formattedPageStatus ) ) { |
253 | throw new UnexpectedValueException( 'Not a valid page status!' ); |
254 | } |
255 | |
256 | $userTalkId = false; |
257 | |
258 | if ( !$minorEdit || |
259 | ( $config->get( MainConfigNames::EnotifMinorEdits ) && |
260 | !$editor->isAllowed( 'nominornewtalk' ) ) |
261 | ) { |
262 | if ( $config->get( MainConfigNames::EnotifUserTalk ) |
263 | && $title->getNamespace() === NS_USER_TALK |
264 | && $this->canSendUserTalkEmail( $editor->getUser(), $title, $minorEdit ) |
265 | ) { |
266 | $targetUser = User::newFromName( $title->getText() ); |
267 | $this->compose( $targetUser, self::USER_TALK, $messageCache ); |
268 | $userTalkId = $targetUser->getId(); |
269 | } |
270 | |
271 | if ( $config->get( MainConfigNames::EnotifWatchlist ) ) { |
272 | $userOptionsLookup = $mwServices->getUserOptionsLookup(); |
273 | // Send updates to watchers other than the current editor |
274 | // and don't send to watchers who are blocked and cannot login |
275 | $userArray = UserArray::newFromIDs( $watchers ); |
276 | foreach ( $userArray as $watchingUser ) { |
277 | if ( $userOptionsLookup->getOption( $watchingUser, 'enotifwatchlistpages' ) |
278 | && ( !$minorEdit || $userOptionsLookup->getOption( $watchingUser, 'enotifminoredits' ) ) |
279 | && $watchingUser->isEmailConfirmed() |
280 | && $watchingUser->getId() != $userTalkId |
281 | && !in_array( $watchingUser->getName(), |
282 | $config->get( MainConfigNames::UsersNotifiedOnAllChanges ) ) |
283 | // @TODO Partial blocks should not prevent the user from logging in. |
284 | // see: https://phabricator.wikimedia.org/T208895 |
285 | && !( $config->get( MainConfigNames::BlockDisablesLogin ) && |
286 | $watchingUser->getBlock() ) |
287 | && $hookRunner->onSendWatchlistEmailNotification( $watchingUser, $title, $this ) |
288 | ) { |
289 | $this->compose( $watchingUser, self::WATCHLIST, $messageCache ); |
290 | } |
291 | } |
292 | } |
293 | } |
294 | |
295 | foreach ( $config->get( MainConfigNames::UsersNotifiedOnAllChanges ) as $name ) { |
296 | if ( $editor->getUser()->getName() == $name ) { |
297 | // No point notifying the user that actually made the change! |
298 | continue; |
299 | } |
300 | $user = User::newFromName( $name ); |
301 | if ( $user instanceof User ) { |
302 | $this->compose( $user, self::ALL_CHANGES, $messageCache ); |
303 | } |
304 | } |
305 | $this->sendMails(); |
306 | } |
307 | |
308 | /** |
309 | * @param UserIdentity $editor |
310 | * @param Title $title |
311 | * @param bool $minorEdit |
312 | * @return bool |
313 | */ |
314 | private function canSendUserTalkEmail( UserIdentity $editor, $title, $minorEdit ) { |
315 | $services = MediaWikiServices::getInstance(); |
316 | $config = $services->getMainConfig(); |
317 | |
318 | if ( !$config->get( MainConfigNames::EnotifUserTalk ) || $title->getNamespace() !== NS_USER_TALK ) { |
319 | return false; |
320 | } |
321 | |
322 | $userOptionsLookup = $services->getUserOptionsLookup(); |
323 | $targetUser = User::newFromName( $title->getText() ); |
324 | |
325 | if ( !$targetUser || $targetUser->isAnon() ) { |
326 | wfDebug( __METHOD__ . ": user talk page edited, but user does not exist" ); |
327 | } elseif ( $targetUser->getId() == $editor->getId() ) { |
328 | wfDebug( __METHOD__ . ": user edited their own talk page, no notification sent" ); |
329 | } elseif ( $targetUser->isTemp() ) { |
330 | wfDebug( __METHOD__ . ": talk page owner is a temporary user so doesn't have email" ); |
331 | } elseif ( $config->get( MainConfigNames::BlockDisablesLogin ) && |
332 | $targetUser->getBlock() |
333 | ) { |
334 | // @TODO Partial blocks should not prevent the user from logging in. |
335 | // see: https://phabricator.wikimedia.org/T208895 |
336 | wfDebug( __METHOD__ . ": talk page owner is blocked and cannot login, no notification sent" ); |
337 | } elseif ( $userOptionsLookup->getOption( $targetUser, 'enotifusertalkpages' ) |
338 | && ( !$minorEdit || $userOptionsLookup->getOption( $targetUser, 'enotifminoredits' ) ) |
339 | ) { |
340 | if ( !$targetUser->isEmailConfirmed() ) { |
341 | wfDebug( __METHOD__ . ": talk page owner doesn't have validated email" ); |
342 | } elseif ( !( new HookRunner( $services->getHookContainer() ) ) |
343 | ->onAbortTalkPageEmailNotification( $targetUser, $title ) |
344 | ) { |
345 | wfDebug( __METHOD__ . ": talk page update notification is aborted for this user" ); |
346 | } else { |
347 | wfDebug( __METHOD__ . ": sending talk page update notification" ); |
348 | return true; |
349 | } |
350 | } else { |
351 | wfDebug( __METHOD__ . ": talk page owner doesn't want notifications" ); |
352 | } |
353 | return false; |
354 | } |
355 | |
356 | /** |
357 | * Generate the generic "this page has been changed" e-mail text. |
358 | * @param MessageCache $messageCache |
359 | */ |
360 | private function composeCommonMailtext( MessageCache $messageCache ) { |
361 | $services = MediaWikiServices::getInstance(); |
362 | $config = $services->getMainConfig(); |
363 | $userOptionsLookup = $services->getUserOptionsLookup(); |
364 | |
365 | $this->composed_common = true; |
366 | |
367 | # You as the WikiAdmin and Sysops can make use of plenty of |
368 | # named variables when composing your notification emails while |
369 | # simply editing the Meta pages |
370 | |
371 | $keys = []; |
372 | $postTransformKeys = []; |
373 | $pageTitleUrl = $this->title->getCanonicalURL(); |
374 | $pageTitle = $this->title->getPrefixedText(); |
375 | |
376 | if ( $this->oldid ) { |
377 | // Always show a link to the diff which triggered the mail. See T34210. |
378 | $keys['$NEWPAGE'] = "\n\n" . wfMessage( |
379 | 'enotif_lastdiff', |
380 | $this->title->getCanonicalURL( [ 'diff' => 'next', 'oldid' => $this->oldid ] ) |
381 | )->inContentLanguage()->text(); |
382 | |
383 | if ( !$config->get( MainConfigNames::EnotifImpersonal ) ) { |
384 | // For personal mail, also show a link to the diff of all changes |
385 | // since last visited. |
386 | $keys['$NEWPAGE'] .= "\n\n" . wfMessage( |
387 | 'enotif_lastvisited', |
388 | $this->title->getCanonicalURL( [ 'diff' => '0', 'oldid' => $this->oldid ] ) |
389 | )->inContentLanguage()->text(); |
390 | } |
391 | $keys['$OLDID'] = $this->oldid; |
392 | $keys['$PAGELOG'] = ''; |
393 | } else { |
394 | // If there is no revision to link to, link to the page log, which should have details. See T115183. |
395 | $keys['$OLDID'] = ''; |
396 | $keys['$NEWPAGE'] = ''; |
397 | $keys['$PAGELOG'] = "\n\n" . wfMessage( |
398 | 'enotif_pagelog', |
399 | SpecialPage::getTitleFor( 'Log' )->getCanonicalURL( [ 'page' => $this->title->getPrefixedDBkey() ] ) |
400 | )->inContentLanguage()->text(); |
401 | |
402 | } |
403 | |
404 | $keys['$PAGETITLE'] = $this->title->getPrefixedText(); |
405 | $keys['$PAGETITLE_URL'] = $this->title->getCanonicalURL(); |
406 | $keys['$PAGEMINOREDIT'] = $this->minorEdit ? |
407 | "\n\n" . wfMessage( 'enotif_minoredit' )->inContentLanguage()->text() : |
408 | ''; |
409 | $keys['$UNWATCHURL'] = $this->title->getCanonicalURL( 'action=unwatch' ); |
410 | |
411 | if ( $this->editor->isAnon() ) { |
412 | # real anon (user:xxx.xxx.xxx.xxx) |
413 | $keys['$PAGEEDITOR'] = wfMessage( 'enotif_anon_editor', $this->editor->getName() ) |
414 | ->inContentLanguage()->text(); |
415 | $keys['$PAGEEDITOR_EMAIL'] = wfMessage( 'noemailtitle' )->inContentLanguage()->text(); |
416 | } elseif ( $this->editor->isTemp() ) { |
417 | $keys['$PAGEEDITOR'] = wfMessage( 'enotif_temp_editor', $this->editor->getName() ) |
418 | ->inContentLanguage()->text(); |
419 | $keys['$PAGEEDITOR_EMAIL'] = wfMessage( 'noemailtitle' )->inContentLanguage()->text(); |
420 | } else { |
421 | $keys['$PAGEEDITOR'] = $config->get( MainConfigNames::EnotifUseRealName ) && |
422 | $this->editor->getRealName() !== '' |
423 | ? $this->editor->getRealName() : $this->editor->getName(); |
424 | $emailPage = SpecialPage::getSafeTitleFor( 'Emailuser', $this->editor->getName() ); |
425 | $keys['$PAGEEDITOR_EMAIL'] = $emailPage->getCanonicalURL(); |
426 | } |
427 | |
428 | $keys['$PAGEEDITOR_WIKI'] = $this->editor->getUserPage()->getCanonicalURL(); |
429 | $keys['$HELPPAGE'] = wfExpandUrl( |
430 | Skin::makeInternalOrExternalUrl( wfMessage( 'helppage' )->inContentLanguage()->text() ) |
431 | ); |
432 | |
433 | # Replace this after transforming the message, T37019 |
434 | $postTransformKeys['$PAGESUMMARY'] = $this->summary == '' ? ' - ' : $this->summary; |
435 | |
436 | // Now build message's subject and body |
437 | |
438 | // Messages: |
439 | // enotif_subject_deleted, enotif_subject_created, enotif_subject_moved, |
440 | // enotif_subject_restored, enotif_subject_changed |
441 | $this->subject = wfMessage( 'enotif_subject_' . $this->pageStatus )->inContentLanguage() |
442 | ->params( $pageTitle, $keys['$PAGEEDITOR'] )->text(); |
443 | |
444 | // Messages: |
445 | // enotif_body_intro_deleted, enotif_body_intro_created, enotif_body_intro_moved, |
446 | // enotif_body_intro_restored, enotif_body_intro_changed |
447 | $keys['$PAGEINTRO'] = wfMessage( 'enotif_body_intro_' . $this->pageStatus ) |
448 | ->inContentLanguage() |
449 | ->params( $pageTitle, $keys['$PAGEEDITOR'], $pageTitleUrl ) |
450 | ->text(); |
451 | |
452 | $body = wfMessage( 'enotif_body' )->inContentLanguage()->plain(); |
453 | $body = strtr( $body, $keys ); |
454 | $body = $messageCache->transform( $body, false, null, $this->title ); |
455 | $this->body = wordwrap( strtr( $body, $postTransformKeys ), 72 ); |
456 | |
457 | # Reveal the page editor's address as REPLY-TO address only if |
458 | # the user has not opted-out and the option is enabled at the |
459 | # global configuration level. |
460 | $adminAddress = new MailAddress( |
461 | $config->get( MainConfigNames::PasswordSender ), |
462 | wfMessage( 'emailsender' )->inContentLanguage()->text() |
463 | ); |
464 | if ( $config->get( MainConfigNames::EnotifRevealEditorAddress ) |
465 | && ( $this->editor->getEmail() != '' ) |
466 | && $userOptionsLookup->getOption( $this->editor, 'enotifrevealaddr' ) |
467 | ) { |
468 | $editorAddress = MailAddress::newFromUser( $this->editor ); |
469 | if ( $config->get( MainConfigNames::EnotifFromEditor ) ) { |
470 | $this->from = $editorAddress; |
471 | } else { |
472 | $this->from = $adminAddress; |
473 | $this->replyto = $editorAddress; |
474 | } |
475 | } else { |
476 | $this->from = $adminAddress; |
477 | $this->replyto = new MailAddress( |
478 | $config->get( MainConfigNames::NoReplyAddress ) |
479 | ); |
480 | } |
481 | } |
482 | |
483 | /** |
484 | * Compose a mail to a given user and either queue it for sending, or send it now, |
485 | * depending on settings. |
486 | * |
487 | * Call sendMails() to send any mails that were queued. |
488 | * @param UserEmailContact $user |
489 | * @param string $source |
490 | * @param MessageCache $messageCache |
491 | */ |
492 | private function compose( UserEmailContact $user, $source, MessageCache $messageCache ) { |
493 | if ( !$this->composed_common ) { |
494 | $this->composeCommonMailtext( $messageCache ); |
495 | } |
496 | |
497 | if ( MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::EnotifImpersonal ) ) { |
498 | $this->mailTargets[] = MailAddress::newFromUser( $user ); |
499 | } else { |
500 | $this->sendPersonalised( $user, $source ); |
501 | } |
502 | } |
503 | |
504 | /** |
505 | * Send any queued mails |
506 | */ |
507 | private function sendMails() { |
508 | if ( MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::EnotifImpersonal ) ) { |
509 | $this->sendImpersonal( $this->mailTargets ); |
510 | } |
511 | } |
512 | |
513 | /** |
514 | * Does the per-user customizations to a notification e-mail (name, |
515 | * timestamp in proper timezone, etc) and sends it out. |
516 | * Returns Status if email was sent successfully or not (Status::newGood() |
517 | * or Status::newFatal() respectively). |
518 | * |
519 | * @param UserEmailContact $watchingUser |
520 | * @param string $source |
521 | * @return StatusValue |
522 | */ |
523 | private function sendPersonalised( UserEmailContact $watchingUser, $source ): StatusValue { |
524 | // From the PHP manual: |
525 | // Note: The to parameter cannot be an address in the form of |
526 | // "Something <someone@example.com>". The mail command will not parse |
527 | // this properly while talking with the MTA. |
528 | $to = MailAddress::newFromUser( $watchingUser ); |
529 | |
530 | # $PAGEEDITDATE is the time and date of the page change |
531 | # expressed in terms of individual local time of the notification |
532 | # recipient, i.e. watching user |
533 | $mwServices = MediaWikiServices::getInstance(); |
534 | $contLang = $mwServices->getContentLanguage(); |
535 | $watchingUserName = ( |
536 | $mwServices->getMainConfig()->get( MainConfigNames::EnotifUseRealName ) && |
537 | $watchingUser->getRealName() !== '' |
538 | ) ? $watchingUser->getRealName() : $watchingUser->getUser()->getName(); |
539 | $body = str_replace( |
540 | [ |
541 | '$WATCHINGUSERNAME', |
542 | '$PAGEEDITDATE', |
543 | '$PAGEEDITTIME' |
544 | ], |
545 | [ |
546 | $watchingUserName, |
547 | $contLang->userDate( $this->timestamp, $watchingUser->getUser() ), |
548 | $contLang->userTime( $this->timestamp, $watchingUser->getUser() ) |
549 | ], |
550 | $this->body |
551 | ); |
552 | |
553 | $headers = []; |
554 | if ( $source === self::WATCHLIST ) { |
555 | $headers['List-Help'] = 'https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Watchlist'; |
556 | } |
557 | |
558 | return $mwServices |
559 | ->getEmailer() |
560 | ->send( |
561 | [ $to ], |
562 | $this->from, |
563 | $this->subject, |
564 | $body, |
565 | null, |
566 | [ |
567 | 'replyTo' => $this->replyto, |
568 | 'headers' => $headers, |
569 | ] |
570 | ); |
571 | } |
572 | |
573 | /** |
574 | * Same as sendPersonalised but does impersonal mail suitable for bulk |
575 | * mailing. Takes an array of MailAddress objects. |
576 | * @param MailAddress[] $addresses |
577 | * @return ?StatusValue |
578 | */ |
579 | private function sendImpersonal( array $addresses ): ?StatusValue { |
580 | if ( count( $addresses ) === 0 ) { |
581 | return null; |
582 | } |
583 | $services = MediaWikiServices::getInstance(); |
584 | $contLang = $services->getContentLanguage(); |
585 | $body = str_replace( |
586 | [ |
587 | '$WATCHINGUSERNAME', |
588 | '$PAGEEDITDATE', |
589 | '$PAGEEDITTIME' |
590 | ], |
591 | [ |
592 | wfMessage( 'enotif_impersonal_salutation' )->inContentLanguage()->text(), |
593 | $contLang->date( $this->timestamp, false, false ), |
594 | $contLang->time( $this->timestamp, false, false ) |
595 | ], |
596 | $this->body |
597 | ); |
598 | |
599 | return $services |
600 | ->getEmailer() |
601 | ->send( |
602 | $addresses, |
603 | $this->from, |
604 | $this->subject, |
605 | $body, |
606 | null, |
607 | [ |
608 | 'replyTo' => $this->replyto, |
609 | ] |
610 | ); |
611 | } |
612 | |
613 | } |