MediaWiki REL1_34
SpecialEmailUser.php
Go to the documentation of this file.
1<?php
25
32 protected $mTarget;
33
37 protected $mTargetObj;
38
39 public function __construct() {
40 parent::__construct( 'Emailuser' );
41 }
42
43 public function doesWrites() {
44 return true;
45 }
46
47 public function getDescription() {
48 $target = self::getTarget( $this->mTarget, $this->getUser() );
49 if ( !$target instanceof User ) {
50 return $this->msg( 'emailuser-title-notarget' )->text();
51 }
52
53 return $this->msg( 'emailuser-title-target', $target->getName() )->text();
54 }
55
56 protected function getFormFields() {
58 return [
59 'From' => [
60 'type' => 'info',
61 'raw' => 1,
62 'default' => $linkRenderer->makeLink(
63 $this->getUser()->getUserPage(),
64 $this->getUser()->getName()
65 ),
66 'label-message' => 'emailfrom',
67 'id' => 'mw-emailuser-sender',
68 ],
69 'To' => [
70 'type' => 'info',
71 'raw' => 1,
72 'default' => $linkRenderer->makeLink(
73 $this->mTargetObj->getUserPage(),
74 $this->mTargetObj->getName()
75 ),
76 'label-message' => 'emailto',
77 'id' => 'mw-emailuser-recipient',
78 ],
79 'Target' => [
80 'type' => 'hidden',
81 'default' => $this->mTargetObj->getName(),
82 ],
83 'Subject' => [
84 'type' => 'text',
85 'default' => $this->msg( 'defemailsubject',
86 $this->getUser()->getName() )->inContentLanguage()->text(),
87 'label-message' => 'emailsubject',
88 'maxlength' => 200,
89 'size' => 60,
90 'required' => true,
91 ],
92 'Text' => [
93 'type' => 'textarea',
94 'rows' => 20,
95 'label-message' => 'emailmessage',
96 'required' => true,
97 ],
98 'CCMe' => [
99 'type' => 'check',
100 'label-message' => 'emailccme',
101 'default' => $this->getUser()->getBoolOption( 'ccmeonemails' ),
102 ],
103 ];
104 }
105
106 public function execute( $par ) {
107 $out = $this->getOutput();
108 $request = $this->getRequest();
109 $out->addModuleStyles( 'mediawiki.special' );
110
111 $this->mTarget = $par ?? $request->getVal( 'wpTarget', $request->getVal( 'target', '' ) );
112
113 // Make sure, that HTMLForm uses the correct target.
114 $request->setVal( 'wpTarget', $this->mTarget );
115
116 // This needs to be below assignment of $this->mTarget because
117 // getDescription() needs it to determine the correct page title.
118 $this->setHeaders();
119 $this->outputHeader();
120
121 // error out if sending user cannot do this
123 $this->getUser(),
124 $this->getRequest()->getVal( 'wpEditToken' ),
125 $this->getConfig()
126 );
127
128 switch ( $error ) {
129 case null:
130 # Wahey!
131 break;
132 case 'badaccess':
133 throw new PermissionsError( 'sendemail' );
134 case 'blockedemailuser':
135 throw $this->getBlockedEmailError();
136 case 'actionthrottledtext':
137 throw new ThrottledError;
138 case 'mailnologin':
139 case 'usermaildisabled':
140 throw new ErrorPageError( $error, "{$error}text" );
141 default:
142 # It's a hook error
143 list( $title, $msg, $params ) = $error;
144 throw new ErrorPageError( $title, $msg, $params );
145 }
146
147 // Make sure, that a submitted form isn't submitted to a subpage (which could be
148 // a non-existing username)
149 $context = new DerivativeContext( $this->getContext() );
150 $context->setTitle( $this->getPageTitle() ); // Remove subpage
151 $this->setContext( $context );
152
153 // A little hack: HTMLForm will check $this->mTarget only, if the form was posted, not
154 // if the user opens Special:EmailUser/Florian (e.g.). So check, if the user did that
155 // and show the "Send email to user" form directly, if so. Show the "enter username"
156 // form, otherwise.
157 $this->mTargetObj = self::getTarget( $this->mTarget, $this->getUser() );
158 if ( !$this->mTargetObj instanceof User ) {
159 $this->userForm( $this->mTarget );
160 } else {
161 $this->sendEmailForm();
162 }
163 }
164
172 public static function getTarget( $target, User $sender ) {
173 if ( $target == '' ) {
174 wfDebug( "Target is empty.\n" );
175
176 return 'notarget';
177 }
178
179 $nu = User::newFromName( $target );
180 $error = self::validateTarget( $nu, $sender );
181
182 return $error ?: $nu;
183 }
184
193 public static function validateTarget( $target, User $sender ) {
194 if ( !$target instanceof User || !$target->getId() ) {
195 wfDebug( "Target is invalid user.\n" );
196
197 return 'notarget';
198 }
199
200 if ( !$target->isEmailConfirmed() ) {
201 wfDebug( "User has no valid email.\n" );
202
203 return 'noemail';
204 }
205
206 if ( !$target->canReceiveEmail() ) {
207 wfDebug( "User does not allow user emails.\n" );
208
209 return 'nowikiemail';
210 }
211
212 if ( !$target->getOption( 'email-allow-new-users' ) && $sender->isNewbie() ) {
213 wfDebug( "User does not allow user emails from new users.\n" );
214
215 return 'nowikiemail';
216 }
217
218 $blacklist = $target->getOption( 'email-blacklist', '' );
219 if ( $blacklist ) {
220 $blacklist = MultiUsernameFilter::splitIds( $blacklist );
221 $lookup = CentralIdLookup::factory();
222 $senderId = $lookup->centralIdFromLocalUser( $sender );
223 if ( $senderId !== 0 && in_array( $senderId, $blacklist ) ) {
224 wfDebug( "User does not allow user emails from this user.\n" );
225
226 return 'nowikiemail';
227 }
228 }
229
230 return '';
231 }
232
242 public static function getPermissionsError( $user, $editToken, Config $config = null ) {
243 if ( $config === null ) {
244 wfDebug( __METHOD__ . ' called without a Config instance passed to it' );
245 $config = MediaWikiServices::getInstance()->getMainConfig();
246 }
247 if ( !$config->get( 'EnableEmail' ) || !$config->get( 'EnableUserEmail' ) ) {
248 return 'usermaildisabled';
249 }
250
251 // Run this before $user->isAllowed, to show appropriate message to anons (T160309)
252 if ( !$user->isEmailConfirmed() ) {
253 return 'mailnologin';
254 }
255
256 if ( !MediaWikiServices::getInstance()
258 ->userHasRight( $user, 'sendemail' )
259 ) {
260 return 'badaccess';
261 }
262
263 if ( $user->isBlockedFromEmailuser() ) {
264 wfDebug( "User is blocked from sending e-mail.\n" );
265
266 return "blockedemailuser";
267 }
268
269 // Check the ping limiter without incrementing it - we'll check it
270 // again later and increment it on a successful send
271 if ( $user->pingLimiter( 'emailuser', 0 ) ) {
272 wfDebug( "Ping limiter triggered.\n" );
273
274 return 'actionthrottledtext';
275 }
276
277 $hookErr = false;
278
279 Hooks::run( 'UserCanSendEmail', [ &$user, &$hookErr ] );
280 Hooks::run( 'EmailUserPermissionsErrors', [ $user, $editToken, &$hookErr ] );
281
282 if ( $hookErr ) {
283 return $hookErr;
284 }
285
286 return null;
287 }
288
294 protected function userForm( $name ) {
295 $htmlForm = HTMLForm::factory( 'ooui', [
296 'Target' => [
297 'type' => 'user',
298 'exists' => true,
299 'label' => $this->msg( 'emailusername' )->text(),
300 'id' => 'emailusertarget',
301 'autofocus' => true,
302 'value' => $name,
303 ]
304 ], $this->getContext() );
305
306 $htmlForm
307 ->setMethod( 'post' )
308 ->setSubmitCallback( [ $this, 'sendEmailForm' ] )
309 ->setFormIdentifier( 'userForm' )
310 ->setId( 'askusername' )
311 ->setWrapperLegendMsg( 'emailtarget' )
312 ->setSubmitTextMsg( 'emailusernamesubmit' )
313 ->show();
314 }
315
316 public function sendEmailForm() {
317 $out = $this->getOutput();
318
319 $ret = $this->mTargetObj;
320 if ( !$ret instanceof User ) {
321 if ( $this->mTarget != '' ) {
322 // Messages used here: notargettext, noemailtext, nowikiemailtext
323 $ret = ( $ret == 'notarget' ) ? 'emailnotarget' : ( $ret . 'text' );
324 return Status::newFatal( $ret );
325 }
326 return false;
327 }
328
329 $htmlForm = HTMLForm::factory( 'ooui', $this->getFormFields(), $this->getContext() );
330 // By now we are supposed to be sure that $this->mTarget is a user name
331 $htmlForm
332 ->addPreText( $this->msg( 'emailpagetext', $this->mTarget )->parse() )
333 ->setSubmitTextMsg( 'emailsend' )
334 ->setSubmitCallback( [ __CLASS__, 'submit' ] )
335 ->setFormIdentifier( 'sendEmailForm' )
336 ->setWrapperLegendMsg( 'email-legend' )
337 ->loadData();
338
339 if ( !Hooks::run( 'EmailUserForm', [ &$htmlForm ] ) ) {
340 return false;
341 }
342
343 $result = $htmlForm->show();
344
345 if ( $result === true || ( $result instanceof Status && $result->isGood() ) ) {
346 $out->setPageTitle( $this->msg( 'emailsent' ) );
347 $out->addWikiMsg( 'emailsenttext', $this->mTarget );
348 $out->returnToMain( false, $ret->getUserPage() );
349 }
350 return true;
351 }
352
362 public static function submit( array $data, IContextSource $context ) {
363 $config = $context->getConfig();
364
365 $target = self::getTarget( $data['Target'], $context->getUser() );
366 if ( !$target instanceof User ) {
367 // Messages used here: notargettext, noemailtext, nowikiemailtext
368 return Status::newFatal( $target . 'text' );
369 }
370
371 $to = MailAddress::newFromUser( $target );
372 $from = MailAddress::newFromUser( $context->getUser() );
373 $subject = $data['Subject'];
374 $text = $data['Text'];
375
376 // Add a standard footer and trim up trailing newlines
377 $text = rtrim( $text ) . "\n\n-- \n";
378 $text .= $context->msg( 'emailuserfooter',
379 $from->name, $to->name )->inContentLanguage()->text();
380
381 if ( $config->get( 'EnableSpecialMute' ) ) {
382 $specialMutePage = SpecialPage::getTitleFor( 'Mute', $context->getUser()->getName() );
383 $text .= "\n" . $context->msg(
384 'specialmute-email-footer',
385 $specialMutePage->getCanonicalURL(),
386 $context->getUser()->getName()
387 )->inContentLanguage()->text();
388 }
389
390 // Check and increment the rate limits
391 if ( $context->getUser()->pingLimiter( 'emailuser' ) ) {
392 throw new ThrottledError();
393 }
394
395 $error = false;
396 if ( !Hooks::run( 'EmailUser', [ &$to, &$from, &$subject, &$text, &$error ] ) ) {
397 if ( $error instanceof Status ) {
398 return $error;
399 } elseif ( $error === false || $error === '' || $error === [] ) {
400 // Possibly to tell HTMLForm to pretend there was no submission?
401 return false;
402 } elseif ( $error === true ) {
403 // Hook sent the mail itself and indicates success?
404 return Status::newGood();
405 } elseif ( is_array( $error ) ) {
406 $status = Status::newGood();
407 foreach ( $error as $e ) {
408 $status->fatal( $e );
409 }
410 return $status;
411 } elseif ( $error instanceof MessageSpecifier ) {
412 return Status::newFatal( $error );
413 } else {
414 // Ugh. Either a raw HTML string, or something that's supposed
415 // to be treated like one.
416 $type = is_object( $error ) ? get_class( $error ) : gettype( $error );
417 wfDeprecated( "EmailUser hook returning a $type as \$error", '1.29' );
418 return Status::newFatal( new ApiRawMessage(
419 [ '$1', Message::rawParam( (string)$error ) ], 'hookaborted'
420 ) );
421 }
422 }
423
424 if ( $config->get( 'UserEmailUseReplyTo' ) ) {
433 $mailFrom = new MailAddress( $config->get( 'PasswordSender' ),
434 $context->msg( 'emailsender' )->inContentLanguage()->text() );
435 $replyTo = $from;
436 } else {
452 $mailFrom = $from;
453 $replyTo = null;
454 }
455
456 $status = UserMailer::send( $to, $mailFrom, $subject, $text, [
457 'replyTo' => $replyTo,
458 ] );
459
460 if ( !$status->isGood() ) {
461 return $status;
462 } else {
463 // if the user requested a copy of this mail, do this now,
464 // unless they are emailing themselves, in which case one
465 // copy of the message is sufficient.
466 if ( $data['CCMe'] && $to != $from ) {
467 $ccTo = $from;
468 $ccFrom = $from;
469 $ccSubject = $context->msg( 'emailccsubject' )->plaintextParams(
470 $target->getName(), $subject )->text();
471 $ccText = $text;
472
473 Hooks::run( 'EmailUserCC', [ &$ccTo, &$ccFrom, &$ccSubject, &$ccText ] );
474
475 if ( $config->get( 'UserEmailUseReplyTo' ) ) {
476 $mailFrom = new MailAddress(
477 $config->get( 'PasswordSender' ),
478 $context->msg( 'emailsender' )->inContentLanguage()->text()
479 );
480 $replyTo = $ccFrom;
481 } else {
482 $mailFrom = $ccFrom;
483 $replyTo = null;
484 }
485
486 $ccStatus = UserMailer::send(
487 $ccTo, $mailFrom, $ccSubject, $ccText, [
488 'replyTo' => $replyTo,
489 ] );
490 $status->merge( $ccStatus );
491 }
492
493 Hooks::run( 'EmailUserComplete', [ $to, $from, $subject, $text ] );
494
495 return $status;
496 }
497 }
498
507 public function prefixSearchSubpages( $search, $limit, $offset ) {
508 $user = User::newFromName( $search );
509 if ( !$user ) {
510 // No prefix suggestion for invalid user
511 return [];
512 }
513 // Autocomplete subpage as user list - public to allow caching
514 return UserNamePrefixSearch::search( 'public', $search, $limit, $offset );
515 }
516
517 protected function getGroupName() {
518 return 'users';
519 }
520
526 private function getBlockedEmailError() {
527 $block = $this->getUser()->mBlock;
528 $params = $block->getBlockErrorParams( $this->getContext() );
529
530 $msg = $block->isSitewide() ? 'blockedtext' : 'blocked-email-user';
531 return new ErrorPageError( 'blockedtitle', $msg, $params );
532 }
533}
getPermissionManager()
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)
Throws a warning that $function is deprecated.
Extension of RawMessage implementing IApiMessage.
An IContextSource implementation which will inherit context from another source but allow individual ...
An error page which can definitely be safely rendered using the OutputPage.
Stores a single person's name and email address.
static newFromUser(User $user)
Create a new MailAddress object for the given user.
MediaWikiServices is the service locator for the application scope of MediaWiki.
static rawParam( $raw)
Definition Message.php:1027
Show an error when a user tries to do something they do not have the necessary permissions for.
A special page that allows users to send e-mails to other users.
static getTarget( $target, User $sender)
Validate target User.
userForm( $name)
Form to ask for target user name.
static getPermissionsError( $user, $editToken, Config $config=null)
Check whether a user is allowed to send email.
static validateTarget( $target, User $sender)
Validate target User.
static submit(array $data, IContextSource $context)
Really send a mail.
prefixSearchSubpages( $search, $limit, $offset)
Return an array of subpages beginning with $search that this special page will accept.
doesWrites()
Indicates whether this special page may perform database writes.
getBlockedEmailError()
Builds an error message based on the block params.
getDescription()
Returns the name that goes in the <h1> in the special page itself, and also the name that will be l...
execute( $par)
Default execute method Checks user permissions.
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
outputHeader( $summaryMessageKey='')
Outputs a summary message on top of special pages Per default the message key is the canonical name o...
setContext( $context)
Sets the context this SpecialPage is executed in.
getName()
Get the name of this Special Page.
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
getOutput()
Get the OutputPage being used for this instance.
getUser()
Shortcut to get the User executing this instance.
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,...
getContext()
Gets the context this SpecialPage is executed in.
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
getConfig()
Shortcut to get main config object.
getRequest()
Get the WebRequest being used for this instance.
getPageTitle( $subpage=false)
Get a self-referential title object.
MediaWiki Linker LinkRenderer null $linkRenderer
isGood()
Returns whether the operation completed and didn't have any error or warnings.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:40
Show an error when the user hits a rate limit.
Shortcut to construct a special page which is unlisted by default.
static send( $to, $from, $subject, $body, $options=[])
This function will perform a direct (authenticated) login to a SMTP Server to use for mail relaying i...
static search( $audience, $search, $limit, $offset=0)
Do a prefix search of user names and return a list of matching user names.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:51
getId()
Get the user's ID.
Definition User.php:2335
isNewbie()
Determine whether the user is a newbie.
Definition User.php:4400
Interface for configuration instances.
Definition Config.php:28
Interface for objects which can provide a MediaWiki context on request.
$context
Definition load.php:45