MediaWiki master
RecentChangeMailComposer.php
Go to the documentation of this file.
1<?php
21namespace MediaWiki\Mail;
22
23use MailAddress;
37use StatusValue;
38
48
52 public const USER_TALK = 'user_talk';
56 public const WATCHLIST = 'watchlist';
60 public const ALL_CHANGES = 'all_changes';
61
62 protected string $subject = '';
63
64 protected string $body = '';
65
66 protected string $summary = '';
67
69
70 protected ?MailAddress $from;
71
72 protected bool $composed_common = false;
73
75 protected array $mailTargets = [];
76
77 protected ?bool $minorEdit;
78
80 protected $oldid;
81
82 protected string $timestamp;
83
84 protected string $pageStatus = '';
85
86 protected Title $title;
87
88 protected User $editor;
89
90 private Config $mainConfig;
91 private UserOptionsLookup $userOptionsLookup;
92 private UrlUtils $urlUtils;
93 private MessageParser $messageParser;
94 private Language $contentLanguage;
95 private Emailer $emailer;
96
97 public function __construct(
100 RecentChange $recentChange,
101 string $pageStatus
102 ) {
103 $services = MediaWikiServices::getInstance();
104 $this->editor = $services->getUserFactory()->newFromAuthority( $editor );
105 $this->title = $title;
106 $this->oldid = $recentChange->getAttribute( 'rc_last_oldid' );
107 $this->minorEdit = $recentChange->getAttribute( 'rc_minor' );
108 $this->timestamp = $recentChange->getAttribute( 'rc_timestamp' );
109 $this->summary = $recentChange->getAttribute( 'rc_comment' );
110 $this->pageStatus = $pageStatus;
111
112 // Prepare for dependency injection
113 $this->mainConfig = $services->getMainConfig();
114 $this->userOptionsLookup = $services->getUserOptionsLookup();
115 $this->urlUtils = $services->getUrlUtils();
116 $this->messageParser = $services->getMessageParser();
117 $this->contentLanguage = $services->getContentLanguage();
118 $this->emailer = $services->getEmailer();
119 }
120
124 private function composeCommonMailtext() {
125 $this->composed_common = true;
126
127 # You as the WikiAdmin and Sysops can make use of plenty of
128 # named variables when composing your notification emails while
129 # simply editing the Meta pages
130
131 $keys = [];
132 $postTransformKeys = [];
133 $pageTitleUrl = $this->title->getCanonicalURL();
134 $pageTitle = $this->title->getPrefixedText();
135
136 if ( $this->oldid ) {
137 // Always show a link to the diff which triggered the mail. See T34210.
138 $keys['$NEWPAGE'] = "\n\n" . wfMessage(
139 'enotif_lastdiff',
140 $this->title->getCanonicalURL( [ 'diff' => 'next', 'oldid' => $this->oldid ] )
141 )->inContentLanguage()->text();
142
143 if ( !$this->mainConfig->get( MainConfigNames::EnotifImpersonal ) ) {
144 // For personal mail, also show a link to the diff of all changes
145 // since last visited.
146 $keys['$NEWPAGE'] .= "\n\n" . wfMessage(
147 'enotif_lastvisited',
148 $this->title->getCanonicalURL( [ 'diff' => '0', 'oldid' => $this->oldid ] )
149 )->inContentLanguage()->text();
150 }
151 $keys['$OLDID'] = $this->oldid;
152 $keys['$PAGELOG'] = '';
153 } else {
154 // If there is no revision to link to, link to the page log, which should have details. See T115183.
155 $keys['$OLDID'] = '';
156 $keys['$NEWPAGE'] = '';
157 $keys['$PAGELOG'] = "\n\n" . wfMessage(
158 'enotif_pagelog',
159 SpecialPage::getTitleFor( 'Log' )->getCanonicalURL( [ 'page' =>
160 $this->title->getPrefixedDBkey() ] )
161 )->inContentLanguage()->text();
162 }
163
164 $keys['$PAGETITLE'] = $this->title->getPrefixedText();
165 $keys['$PAGETITLE_URL'] = $this->title->getCanonicalURL();
166 $keys['$PAGEMINOREDIT'] = $this->minorEdit ?
167 "\n\n" . wfMessage( 'enotif_minoredit' )->inContentLanguage()->text() :
168 '';
169 $keys['$UNWATCHURL'] = $this->title->getCanonicalURL( 'action=unwatch' );
170
171 if ( $this->editor->isAnon() ) {
172 # real anon (user:xxx.xxx.xxx.xxx)
173 $keys['$PAGEEDITOR'] = wfMessage( 'enotif_anon_editor', $this->editor->getName() )
174 ->inContentLanguage()->text();
175 $keys['$PAGEEDITOR_EMAIL'] = wfMessage( 'noemailtitle' )->inContentLanguage()->text();
176 } elseif ( $this->editor->isTemp() ) {
177 $keys['$PAGEEDITOR'] = wfMessage( 'enotif_temp_editor', $this->editor->getName() )
178 ->inContentLanguage()->text();
179 $keys['$PAGEEDITOR_EMAIL'] = wfMessage( 'noemailtitle' )->inContentLanguage()->text();
180 } else {
181 $keys['$PAGEEDITOR'] = $this->mainConfig->get( MainConfigNames::EnotifUseRealName ) &&
182 $this->editor->getRealName() !== ''
183 ? $this->editor->getRealName() : $this->editor->getName();
184 $emailPage = SpecialPage::getSafeTitleFor( 'Emailuser', $this->editor->getName() );
185 $keys['$PAGEEDITOR_EMAIL'] = $emailPage->getCanonicalURL();
186 }
187
188 $keys['$PAGEEDITOR_WIKI'] = $this->editor->getTalkPage()->getCanonicalURL();
189 $keys['$HELPPAGE'] = $this->urlUtils->expand(
190 Skin::makeInternalOrExternalUrl( wfMessage( 'helppage' )->inContentLanguage()->text() ),
192 ) ?? false;
193
194 # Replace this after transforming the message, T37019
195 $postTransformKeys['$PAGESUMMARY'] = $this->summary == '' ? ' - ' : $this->summary;
196
197 // Now build message's subject and body
198
199 // Messages:
200 // enotif_subject_deleted, enotif_subject_created, enotif_subject_moved,
201 // enotif_subject_restored, enotif_subject_changed
202 $this->subject = wfMessage( 'enotif_subject_' . $this->pageStatus )->inContentLanguage()
203 ->params( $pageTitle, $keys['$PAGEEDITOR'] )->text();
204
205 // Messages:
206 // enotif_body_intro_deleted, enotif_body_intro_created, enotif_body_intro_moved,
207 // enotif_body_intro_restored, enotif_body_intro_changed
208 $keys['$PAGEINTRO'] = wfMessage( 'enotif_body_intro_' . $this->pageStatus )
209 ->inContentLanguage()
210 ->params( $pageTitle, $keys['$PAGEEDITOR'], "<{$pageTitleUrl}>" )
211 ->text();
212
213 $body = wfMessage( 'enotif_body' )->inContentLanguage()->plain();
214 $body = strtr( $body, $keys );
215 $body = $this->messageParser->transform( $body, false, null, $this->title );
216 $this->body = wordwrap( strtr( $body, $postTransformKeys ), 72 );
217
218 # Reveal the page editor's address as REPLY-TO address only if
219 # the user has not opted-out and the option is enabled at the
220 # global configuration level.
221 $adminAddress = new MailAddress(
222 $this->mainConfig->get( MainConfigNames::PasswordSender ),
223 wfMessage( 'emailsender' )->inContentLanguage()->text()
224 );
225 if ( $this->mainConfig->get( MainConfigNames::EnotifRevealEditorAddress )
226 && ( $this->editor->getEmail() != '' )
227 && $this->userOptionsLookup->getOption( $this->editor, 'enotifrevealaddr' )
228 ) {
229 $editorAddress = MailAddress::newFromUser( $this->editor );
230 if ( $this->mainConfig->get( MainConfigNames::EnotifFromEditor ) ) {
231 $this->from = $editorAddress;
232 } else {
233 $this->from = $adminAddress;
234 $this->replyto = $editorAddress;
235 }
236 } else {
237 $this->from = $adminAddress;
238 $this->replyto = new MailAddress(
239 $this->mainConfig->get( MainConfigNames::NoReplyAddress )
240 );
241 }
242 }
243
252 public function compose( UserEmailContact $user, $source ) {
253 if ( !$this->composed_common ) {
254 $this->composeCommonMailtext();
255 }
256
257 if ( $this->mainConfig->get( MainConfigNames::EnotifImpersonal ) ) {
258 wfDeprecated( 'EnotifImpersonal is now deprecated', '1.44' );
259
260 $this->mailTargets[] = MailAddress::newFromUser( $user );
261 } else {
262 $this->sendPersonalised( $user, $source );
263 }
264 }
265
269 public function sendMails() {
270 if ( $this->mainConfig->get( MainConfigNames::EnotifImpersonal ) ) {
271 wfDeprecated( 'EnotifImpersonal is now deprecated', '1.44' );
272 $this->sendImpersonal( $this->mailTargets );
273 }
274 }
275
286 private function sendPersonalised( UserEmailContact $watchingUser, $source ): StatusValue {
287 // From the PHP manual:
288 // Note: The to parameter cannot be an address in the form of
289 // "Something <someone@example.com>". The mail command will not parse
290 // this properly while talking with the MTA.
291 $to = MailAddress::newFromUser( $watchingUser );
292
293 # $PAGEEDITDATE is the time and date of the page change
294 # expressed in terms of individual local time of the notification
295 # recipient, i.e. watching user
296 $watchingUserName = (
297 $this->mainConfig->get( MainConfigNames::EnotifUseRealName ) &&
298 $watchingUser->getRealName() !== ''
299 ) ? $watchingUser->getRealName() : $watchingUser->getUser()->getName();
300 $body = str_replace(
301 [
302 '$WATCHINGUSERNAME',
303 '$PAGEEDITDATE',
304 '$PAGEEDITTIME'
305 ],
306 [
307 $watchingUserName,
308 $this->contentLanguage->userDate( $this->timestamp, $watchingUser->getUser() ),
309 $this->contentLanguage->userTime( $this->timestamp, $watchingUser->getUser() )
310 ],
311 $this->body
312 );
313
314 $headers = [];
315 if ( $source === self::WATCHLIST ) {
316 $headers['List-Help'] = 'https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Watchlist';
317 }
318
319 return $this->emailer->send(
320 [ $to ],
321 $this->from,
322 $this->subject,
323 $body,
324 null,
325 [
326 'replyTo' => $this->replyto,
327 'headers' => $headers,
328 ]
329 );
330 }
331
338 private function sendImpersonal( array $addresses ): ?StatusValue {
339 if ( count( $addresses ) === 0 ) {
340 return null;
341 }
342 $services = MediaWikiServices::getInstance();
343 $contLang = $services->getContentLanguage();
344 $body = str_replace(
345 [
346 '$WATCHINGUSERNAME',
347 '$PAGEEDITDATE',
348 '$PAGEEDITTIME'
349 ],
350 [
351 wfMessage( 'enotif_impersonal_salutation' )->inContentLanguage()->text(),
352 $contLang->date( $this->timestamp, false, false ),
353 $contLang->time( $this->timestamp, false, false )
354 ],
355 $this->body
356 );
357
358 return $services
359 ->getEmailer()
360 ->send(
361 $addresses,
362 $this->from,
363 $this->subject,
364 $body,
365 null,
366 [
367 'replyTo' => $this->replyto,
368 ]
369 );
370 }
371}
const PROTO_CURRENT
Definition Defines.php:236
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
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:81
Represent and format a single name and email address pair for SMTP.
static newFromUser(UserEmailContact $user)
Base class for language-specific code.
Definition Language.php:81
Service for transformation of interface message text.
Send arbitrary emails.
Definition Emailer.php:42
Component responsible for composing and sending emails triggered after a RecentChange.
__construct(Authority $editor, Title $title, RecentChange $recentChange, string $pageStatus)
const USER_TALK
Notification is due to user's user talk being edited.
const ALL_CHANGES
Notification because user is notified for all changes.
compose(UserEmailContact $user, $source)
Compose a mail to a given user and either queue it for sending, or send it now, depending on settings...
const WATCHLIST
Notification is due to a watchlisted page being edited.
A class containing constants representing the names of configuration variables.
const NoReplyAddress
Name constant for the NoReplyAddress setting, for use with Config::get()
const EnotifRevealEditorAddress
Name constant for the EnotifRevealEditorAddress setting, for use with Config::get()
const EnotifFromEditor
Name constant for the EnotifFromEditor setting, for use with Config::get()
const EnotifUseRealName
Name constant for the EnotifUseRealName setting, for use with Config::get()
const PasswordSender
Name constant for the PasswordSender setting, for use with Config::get()
const EnotifImpersonal
Name constant for the EnotifImpersonal 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.
Utility class for creating and reading rows in the recentchanges table.
getAttribute( $name)
Get an attribute value.
The base class for all skins.
Definition Skin.php:58
Parent class for all special pages.
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
static getSafeTitleFor( $name, $subpage=false)
Get a localised Title object for a page name with a possibly unvalidated subpage.
Represents a title within MediaWiki.
Definition Title.php:78
Provides access to user options.
User class for the MediaWiki software.
Definition User.php:121
A service to expand, parse, and otherwise manipulate URLs.
Definition UrlUtils.php:16
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Interface for configuration instances.
Definition Config.php:32
getRealName()
Get user real name or an empty string if unknown.
getUser()
Get the identity of the user this contact belongs to.
This interface represents the authority associated with the current execution context,...
Definition Authority.php:37
$source