MediaWiki REL1_35
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 new UserBlockedError( $this->getUser()->getBlock() );
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." );
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." );
196
197 return 'notarget';
198 }
199
200 if ( !$target->isEmailConfirmed() ) {
201 wfDebug( "User has no valid email." );
202
203 return 'noemail';
204 }
205
206 if ( !$target->canReceiveEmail() ) {
207 wfDebug( "User does not allow user emails." );
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." );
214
215 return 'nowikiemail';
216 }
217
218 $muteList = $target->getOption( 'email-blacklist', '' );
219 if ( $muteList ) {
220 $muteList = MultiUsernameFilter::splitIds( $muteList );
221 $lookup = CentralIdLookup::factory();
222 $senderId = $lookup->centralIdFromLocalUser( $sender );
223 if ( $senderId !== 0 && in_array( $senderId, $muteList ) ) {
224 wfDebug( "User does not allow user emails from this user." );
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." );
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." );
273
274 return 'actionthrottledtext';
275 }
276
277 $hookErr = false;
278
279 Hooks::runner()->onUserCanSendEmail( $user, $hookErr );
280 Hooks::runner()->onEmailUserPermissionsErrors( $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 'required' => true,
300 'label' => $this->msg( 'emailusername' )->text(),
301 'id' => 'emailusertarget',
302 'autofocus' => true,
303 'value' => $name,
304 ]
305 ], $this->getContext() );
306
307 $htmlForm
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 ( !$this->getHookRunner()->onEmailUserForm( $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::runner()->onEmailUser( $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 wfDeprecatedMsg( "An EmailUser hook returned a $type as \$error, " .
418 "this is deprecated since MediaWiki 1.29",
419 '1.29', false, false );
420 return Status::newFatal( new ApiRawMessage(
421 [ '$1', Message::rawParam( (string)$error ) ], 'hookaborted'
422 ) );
423 }
424 }
425
426 if ( $config->get( 'UserEmailUseReplyTo' ) ) {
435 $mailFrom = new MailAddress( $config->get( 'PasswordSender' ),
436 $context->msg( 'emailsender' )->inContentLanguage()->text() );
437 $replyTo = $from;
438 } else {
454 $mailFrom = $from;
455 $replyTo = null;
456 }
457
458 $status = UserMailer::send( $to, $mailFrom, $subject, $text, [
459 'replyTo' => $replyTo,
460 ] );
461
462 if ( !$status->isGood() ) {
463 return $status;
464 } else {
465 // if the user requested a copy of this mail, do this now,
466 // unless they are emailing themselves, in which case one
467 // copy of the message is sufficient.
468 if ( $data['CCMe'] && $to != $from ) {
469 $ccTo = $from;
470 $ccFrom = $from;
471 $ccSubject = $context->msg( 'emailccsubject' )->plaintextParams(
472 $target->getName(), $subject )->text();
473 $ccText = $text;
474
475 Hooks::runner()->onEmailUserCC( $ccTo, $ccFrom, $ccSubject, $ccText );
476
477 if ( $config->get( 'UserEmailUseReplyTo' ) ) {
478 $mailFrom = new MailAddress(
479 $config->get( 'PasswordSender' ),
480 $context->msg( 'emailsender' )->inContentLanguage()->text()
481 );
482 $replyTo = $ccFrom;
483 } else {
484 $mailFrom = $ccFrom;
485 $replyTo = null;
486 }
487
488 $ccStatus = UserMailer::send(
489 $ccTo, $mailFrom, $ccSubject, $ccText, [
490 'replyTo' => $replyTo,
491 ] );
492 $status->merge( $ccStatus );
493 }
494
495 Hooks::runner()->onEmailUserComplete( $to, $from, $subject, $text );
496
497 return $status;
498 }
499 }
500
509 public function prefixSearchSubpages( $search, $limit, $offset ) {
510 $user = User::newFromName( $search );
511 if ( !$user ) {
512 // No prefix suggestion for invalid user
513 return [];
514 }
515 // Autocomplete subpage as user list - public to allow caching
516 return UserNamePrefixSearch::search( 'public', $search, $limit, $offset );
517 }
518
519 protected function getGroupName() {
520 return 'users';
521 }
522}
getPermissionManager()
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfDeprecatedMsg( $msg, $version=false, $component=false, $callerOffset=2)
Log a deprecation warning with arbitrary message text.
Extension of RawMessage implementing IApiMessage @newable.
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.
MediaWikiServices is the service locator for the application scope of MediaWiki.
static rawParam( $raw)
Definition Message.php:1053
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.
User string $mTargetObj
doesWrites()
Indicates whether this special page may perform database writes.
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:44
Show an error when the user hits a rate limit.
Shortcut to construct a special page which is unlisted by default.
Show an error when the user tries to do something whilst blocked.
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:60
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition User.php:541
getId()
Get the user's ID.
Definition User.php:2121
isNewbie()
Determine whether the user is a newbie.
Definition User.php:3821
Interface for configuration instances.
Definition Config.php:30
Interface for objects which can provide a MediaWiki context on request.
getConfig()
Get the site configuration.
msg( $key,... $params)
This is the method for getting translated interface messages.
Stable for implementing.