MediaWiki  master
SpecialEmailUser.php
Go to the documentation of this file.
1 <?php
29 
36  protected $mTarget;
37 
41  protected $mTargetObj;
42 
44  private $userNameUtils;
45 
48 
51 
57  public function __construct(
61  ) {
62  parent::__construct( 'Emailuser' );
63  $this->userNameUtils = $userNameUtils;
64  $this->userNamePrefixSearch = $userNamePrefixSearch;
65  $this->userOptionsLookup = $userOptionsLookup;
66  }
67 
68  public function doesWrites() {
69  return true;
70  }
71 
72  public function getDescription() {
73  $target = self::getTarget( $this->mTarget, $this->getUser() );
74  if ( !$target instanceof User ) {
75  return $this->msg( 'emailuser-title-notarget' )->text();
76  }
77 
78  return $this->msg( 'emailuser-title-target', $target->getName() )->text();
79  }
80 
81  protected function getFormFields() {
82  $linkRenderer = $this->getLinkRenderer();
83  $user = $this->getUser();
84  return [
85  'From' => [
86  'type' => 'info',
87  'raw' => 1,
88  'default' => $linkRenderer->makeLink(
89  $user->getUserPage(),
90  $user->getName()
91  ),
92  'label-message' => 'emailfrom',
93  'id' => 'mw-emailuser-sender',
94  ],
95  'To' => [
96  'type' => 'info',
97  'raw' => 1,
98  'default' => $linkRenderer->makeLink(
99  $this->mTargetObj->getUserPage(),
100  $this->mTargetObj->getName()
101  ),
102  'label-message' => 'emailto',
103  'id' => 'mw-emailuser-recipient',
104  ],
105  'Target' => [
106  'type' => 'hidden',
107  'default' => $this->mTargetObj->getName(),
108  ],
109  'Subject' => [
110  'type' => 'text',
111  'default' => $this->msg( 'defemailsubject', $user->getName() )->inContentLanguage()->text(),
112  'label-message' => 'emailsubject',
113  'maxlength' => 200,
114  'size' => 60,
115  'required' => true,
116  ],
117  'Text' => [
118  'type' => 'textarea',
119  'rows' => 20,
120  'label-message' => 'emailmessage',
121  'required' => true,
122  ],
123  'CCMe' => [
124  'type' => 'check',
125  'label-message' => 'emailccme',
126  'default' => $this->userOptionsLookup->getBoolOption( $user, 'ccmeonemails' ),
127  ],
128  ];
129  }
130 
131  public function execute( $par ) {
132  $out = $this->getOutput();
133  $request = $this->getRequest();
134  $out->addModuleStyles( 'mediawiki.special' );
135 
136  $this->mTarget = $par ?? $request->getVal( 'wpTarget', $request->getVal( 'target', '' ) );
137 
138  // Make sure, that HTMLForm uses the correct target.
139  $request->setVal( 'wpTarget', $this->mTarget );
140 
141  // This needs to be below assignment of $this->mTarget because
142  // getDescription() needs it to determine the correct page title.
143  $this->setHeaders();
144  $this->outputHeader();
145 
146  // error out if sending user cannot do this
147  $error = self::getPermissionsError(
148  $this->getUser(),
149  $this->getRequest()->getVal( 'wpEditToken' ),
150  $this->getConfig()
151  );
152 
153  switch ( $error ) {
154  case null:
155  # Wahey!
156  break;
157  case 'badaccess':
158  throw new PermissionsError( 'sendemail' );
159  case 'blockedemailuser':
160  throw new UserBlockedError( $this->getUser()->getBlock() );
161  case 'actionthrottledtext':
162  throw new ThrottledError;
163  case 'mailnologin':
164  case 'usermaildisabled':
165  throw new ErrorPageError( $error, "{$error}text" );
166  default:
167  # It's a hook error
168  list( $title, $msg, $params ) = $error;
169  throw new ErrorPageError( $title, $msg, $params );
170  }
171 
172  // Make sure, that a submitted form isn't submitted to a subpage (which could be
173  // a non-existing username)
174  $context = new DerivativeContext( $this->getContext() );
175  $context->setTitle( $this->getPageTitle() ); // Remove subpage
176  $this->setContext( $context );
177 
178  // A little hack: HTMLForm will check $this->mTarget only, if the form was posted, not
179  // if the user opens Special:EmailUser/Florian (e.g.). So check, if the user did that
180  // and show the "Send email to user" form directly, if so. Show the "enter username"
181  // form, otherwise.
182  $this->mTargetObj = self::getTarget( $this->mTarget, $this->getUser() );
183  if ( !$this->mTargetObj instanceof User ) {
184  $this->userForm( $this->mTarget );
185  } else {
186  $this->sendEmailForm();
187  }
188  }
189 
197  public static function getTarget( $target, User $sender ) {
198  if ( $target == '' ) {
199  wfDebug( "Target is empty." );
200 
201  return 'notarget';
202  }
203 
204  $nu = User::newFromName( $target );
205  $error = self::validateTarget( $nu, $sender );
206 
207  return $error ?: $nu;
208  }
209 
218  public static function validateTarget( $target, User $sender ) {
219  if ( !$target instanceof User || !$target->getId() ) {
220  wfDebug( "Target is invalid user." );
221 
222  return 'notarget';
223  }
224 
225  if ( !$target->isEmailConfirmed() ) {
226  wfDebug( "User has no valid email." );
227 
228  return 'noemail';
229  }
230 
231  if ( !$target->canReceiveEmail() ) {
232  wfDebug( "User does not allow user emails." );
233 
234  return 'nowikiemail';
235  }
236 
237  if ( !$target->getOption( 'email-allow-new-users' ) && $sender->isNewbie() ) {
238  wfDebug( "User does not allow user emails from new users." );
239 
240  return 'nowikiemail';
241  }
242 
243  $muteList = $target->getOption( 'email-blacklist', '' );
244  if ( $muteList ) {
245  $muteList = MultiUsernameFilter::splitIds( $muteList );
246  $senderId = MediaWikiServices::getInstance()
247  ->getCentralIdLookup()
248  ->centralIdFromLocalUser( $sender );
249  if ( $senderId !== 0 && in_array( $senderId, $muteList ) ) {
250  wfDebug( "User does not allow user emails from this user." );
251 
252  return 'nowikiemail';
253  }
254  }
255 
256  return '';
257  }
258 
268  public static function getPermissionsError( $user, $editToken, Config $config = null ) {
269  if ( $config === null ) {
270  wfDebug( __METHOD__ . ' called without a Config instance passed to it' );
271  $config = MediaWikiServices::getInstance()->getMainConfig();
272  }
273  if ( !$config->get( 'EnableEmail' ) || !$config->get( 'EnableUserEmail' ) ) {
274  return 'usermaildisabled';
275  }
276 
277  // Run this before checking 'sendemail' permission
278  // to show appropriate message to anons (T160309)
279  if ( !$user->isEmailConfirmed() ) {
280  return 'mailnologin';
281  }
282 
283  if ( !MediaWikiServices::getInstance()
284  ->getPermissionManager()
285  ->userHasRight( $user, 'sendemail' )
286  ) {
287  return 'badaccess';
288  }
289 
290  if ( $user->isBlockedFromEmailuser() ) {
291  wfDebug( "User is blocked from sending e-mail." );
292 
293  return "blockedemailuser";
294  }
295 
296  // Check the ping limiter without incrementing it - we'll check it
297  // again later and increment it on a successful send
298  if ( $user->pingLimiter( 'emailuser', 0 ) ) {
299  wfDebug( "Ping limiter triggered." );
300 
301  return 'actionthrottledtext';
302  }
303 
304  $hookErr = false;
305 
306  Hooks::runner()->onUserCanSendEmail( $user, $hookErr );
307  Hooks::runner()->onEmailUserPermissionsErrors( $user, $editToken, $hookErr );
308 
309  if ( $hookErr ) {
310  return $hookErr;
311  }
312 
313  return null;
314  }
315 
321  protected function userForm( $name ) {
322  $htmlForm = HTMLForm::factory( 'ooui', [
323  'Target' => [
324  'type' => 'user',
325  'exists' => true,
326  'required' => true,
327  'label' => $this->msg( 'emailusername' )->text(),
328  'id' => 'emailusertarget',
329  'autofocus' => true,
330  'value' => $name,
331  ]
332  ], $this->getContext() );
333 
334  $htmlForm
335  ->setSubmitCallback( [ $this, 'sendEmailForm' ] )
336  ->setFormIdentifier( 'userForm' )
337  ->setId( 'askusername' )
338  ->setWrapperLegendMsg( 'emailtarget' )
339  ->setSubmitTextMsg( 'emailusernamesubmit' )
340  ->show();
341  }
342 
343  public function sendEmailForm() {
344  $out = $this->getOutput();
345 
346  $ret = $this->mTargetObj;
347  if ( !$ret instanceof User ) {
348  if ( $this->mTarget != '' ) {
349  // Messages used here: notargettext, noemailtext, nowikiemailtext
350  $ret = ( $ret == 'notarget' ) ? 'emailnotarget' : ( $ret . 'text' );
351  return Status::newFatal( $ret );
352  }
353  return false;
354  }
355 
356  $htmlForm = HTMLForm::factory( 'ooui', $this->getFormFields(), $this->getContext() );
357  // By now we are supposed to be sure that $this->mTarget is a user name
358  $htmlForm
359  ->addPreText( $this->msg( 'emailpagetext', $this->mTarget )->parse() )
360  ->setSubmitTextMsg( 'emailsend' )
361  ->setSubmitCallback( [ __CLASS__, 'submit' ] )
362  ->setFormIdentifier( 'sendEmailForm' )
363  ->setWrapperLegendMsg( 'email-legend' )
364  ->loadData();
365 
366  if ( !$this->getHookRunner()->onEmailUserForm( $htmlForm ) ) {
367  return false;
368  }
369 
370  $result = $htmlForm->show();
371 
372  if ( $result === true || ( $result instanceof Status && $result->isGood() ) ) {
373  $out->setPageTitle( $this->msg( 'emailsent' ) );
374  $out->addWikiMsg( 'emailsenttext', $this->mTarget );
375  $out->returnToMain( false, $ret->getUserPage() );
376  }
377  return true;
378  }
379 
390  public static function submit( array $data, IContextSource $context ) {
391  $config = $context->getConfig();
392 
393  $sender = $context->getUser();
394  $target = self::getTarget( $data['Target'], $sender );
395  if ( !$target instanceof User ) {
396  // Messages used here: notargettext, noemailtext, nowikiemailtext
397  return Status::newFatal( $target . 'text' );
398  }
399 
400  $toAddress = MailAddress::newFromUser( $target );
401  $fromAddress = MailAddress::newFromUser( $sender );
402  $subject = $data['Subject'];
403  $text = $data['Text'];
404 
405  // Add a standard footer and trim up trailing newlines
406  $text = rtrim( $text ) . "\n\n-- \n";
407  $text .= $context->msg(
408  'emailuserfooter',
409  $fromAddress->name,
410  $toAddress->name
411  )->inContentLanguage()->text();
412 
413  if ( $config->get( 'EnableSpecialMute' ) ) {
414  $specialMutePage = SpecialPage::getTitleFor( 'Mute', $sender->getName() );
415  $text .= "\n" . $context->msg(
416  'specialmute-email-footer',
417  $specialMutePage->getCanonicalURL(),
418  $sender->getName()
419  )->inContentLanguage()->text();
420  }
421 
422  // Check and increment the rate limits
423  if ( $sender->pingLimiter( 'emailuser' ) ) {
424  throw new ThrottledError();
425  }
426 
427  // Services that are needed, will be injected once this is moved to EmailUserUtils
428  // service, see T265541
430  $emailer = MediaWikiServices::getInstance()->getEmailer();
431 
432  $error = false;
433  if ( !$hookRunner->onEmailUser( $toAddress, $fromAddress, $subject, $text, $error ) ) {
434  if ( $error instanceof Status ) {
435  return $error;
436  } elseif ( $error === false || $error === '' || $error === [] ) {
437  // Possibly to tell HTMLForm to pretend there was no submission?
438  return false;
439  } elseif ( $error === true ) {
440  // Hook sent the mail itself and indicates success?
441  return Status::newGood();
442  } elseif ( is_array( $error ) ) {
443  $status = Status::newGood();
444  foreach ( $error as $e ) {
445  $status->fatal( $e );
446  }
447  return $status;
448  } elseif ( $error instanceof MessageSpecifier ) {
449  return Status::newFatal( $error );
450  } else {
451  // Setting $error to something else was deprecated in 1.29 and
452  // removed in 1.36, and so an exception is now thrown
453  $type = is_object( $error ) ? get_class( $error ) : gettype( $error );
454  throw new MWException(
455  'EmailUser hook set $error to unsupported type ' . $type
456  );
457  }
458  }
459 
460  if ( $config->get( 'UserEmailUseReplyTo' ) ) {
469  $mailFrom = new MailAddress(
470  $config->get( 'PasswordSender' ),
471  $context->msg( 'emailsender' )->inContentLanguage()->text()
472  );
473  $replyTo = $fromAddress;
474  } else {
490  $mailFrom = $fromAddress;
491  $replyTo = null;
492  }
493 
494  $status = Status::wrap( $emailer->send(
495  $toAddress,
496  $mailFrom,
497  $subject,
498  $text,
499  null,
500  [ 'replyTo' => $replyTo ]
501  ) );
502 
503  if ( !$status->isGood() ) {
504  return $status;
505  }
506 
507  // if the user requested a copy of this mail, do this now,
508  // unless they are emailing themselves, in which case one
509  // copy of the message is sufficient.
510  if ( $data['CCMe'] && $toAddress != $fromAddress ) {
511  $ccTo = $fromAddress;
512  $ccFrom = $fromAddress;
513  $ccSubject = $context->msg( 'emailccsubject' )->plaintextParams(
514  $target->getName(),
515  $subject
516  )->text();
517  $ccText = $text;
518 
519  $hookRunner->onEmailUserCC( $ccTo, $ccFrom, $ccSubject, $ccText );
520 
521  if ( $config->get( 'UserEmailUseReplyTo' ) ) {
522  $mailFrom = new MailAddress(
523  $config->get( 'PasswordSender' ),
524  $context->msg( 'emailsender' )->inContentLanguage()->text()
525  );
526  $replyTo = $ccFrom;
527  } else {
528  $mailFrom = $ccFrom;
529  $replyTo = null;
530  }
531 
532  $ccStatus = $emailer->send(
533  $ccTo,
534  $mailFrom,
535  $ccSubject,
536  $ccText,
537  null,
538  [ 'replyTo' => $replyTo ]
539  );
540  $status->merge( $ccStatus );
541  }
542 
543  $hookRunner->onEmailUserComplete( $toAddress, $fromAddress, $subject, $text );
544 
545  return $status;
546  }
547 
556  public function prefixSearchSubpages( $search, $limit, $offset ) {
557  $search = $this->userNameUtils->getCanonical( $search );
558  if ( !$search ) {
559  // No prefix suggestion for invalid user
560  return [];
561  }
562  // Autocomplete subpage as user list - public to allow caching
563  return $this->userNamePrefixSearch
564  ->search( UserNamePrefixSearch::AUDIENCE_PUBLIC, $search, $limit, $offset );
565  }
566 
567  protected function getGroupName() {
568  return 'users';
569  }
570 }
SpecialPage\$linkRenderer
LinkRenderer null $linkRenderer
Definition: SpecialPage.php:80
SpecialPage\getPageTitle
getPageTitle( $subpage=false)
Get a self-referential title object.
Definition: SpecialPage.php:744
SpecialPage\msg
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
Definition: SpecialPage.php:912
StatusValue\newFatal
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:70
UserBlockedError
Show an error when the user tries to do something whilst blocked.
Definition: UserBlockedError.php:32
SpecialEmailUser\$userNameUtils
UserNameUtils $userNameUtils
Definition: SpecialEmailUser.php:44
SpecialPage\getOutput
getOutput()
Get the OutputPage being used for this instance.
Definition: SpecialPage.php:790
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:193
UnlistedSpecialPage
Shortcut to construct a special page which is unlisted by default.
Definition: UnlistedSpecialPage.php:31
SpecialEmailUser\userForm
userForm( $name)
Form to ask for target user name.
Definition: SpecialEmailUser.php:321
MessageSpecifier
Definition: MessageSpecifier.php:24
User\newFromName
static newFromName( $name, $validate='valid')
Definition: User.php:606
SpecialPage\getTitleFor
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,...
Definition: SpecialPage.php:107
PermissionsError
Show an error when a user tries to do something they do not have the necessary permissions for.
Definition: PermissionsError.php:32
SpecialEmailUser\getTarget
static getTarget( $target, User $sender)
Validate target User.
Definition: SpecialEmailUser.php:197
SpecialEmailUser\validateTarget
static validateTarget( $target, User $sender)
Validate target User.
Definition: SpecialEmailUser.php:218
SpecialEmailUser\execute
execute( $par)
Default execute method Checks user permissions.
Definition: SpecialEmailUser.php:131
Status
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:44
MailAddress
Stores a single person's name and email address.
Definition: MailAddress.php:36
Config
Interface for configuration instances.
Definition: Config.php:30
StatusValue\isGood
isGood()
Returns whether the operation completed and didn't have any error or warnings.
Definition: StatusValue.php:122
DerivativeContext
An IContextSource implementation which will inherit context from another source but allow individual ...
Definition: DerivativeContext.php:32
SpecialPage\getHookRunner
getHookRunner()
Definition: SpecialPage.php:1095
MWException
MediaWiki exception.
Definition: MWException.php:29
SpecialPage\getConfig
getConfig()
Shortcut to get main config object.
Definition: SpecialPage.php:878
MessageLocalizer\msg
msg( $key,... $params)
This is the method for getting translated interface messages.
Status\wrap
static wrap( $sv)
Succinct helper method to wrap a StatusValue.
Definition: Status.php:62
SpecialEmailUser\doesWrites
doesWrites()
Indicates whether this special page may perform database writes.
Definition: SpecialEmailUser.php:68
MailAddress\newFromUser
static newFromUser(UserEmailContact $user)
Create a new MailAddress object for the given user.
Definition: MailAddress.php:72
MediaWiki\User\UserNamePrefixSearch
Handles searching prefixes of user names.
Definition: UserNamePrefixSearch.php:39
ThrottledError
Show an error when the user hits a rate limit.
Definition: ThrottledError.php:28
SpecialEmailUser
A special page that allows users to send e-mails to other users.
Definition: SpecialEmailUser.php:35
User\isNewbie
isNewbie()
Determine whether the user is a newbie.
Definition: User.php:3673
$title
$title
Definition: testCompression.php:38
SpecialPage\setHeaders
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
Definition: SpecialPage.php:618
SpecialPage\getUser
getUser()
Shortcut to get the User executing this instance.
Definition: SpecialPage.php:800
User\getId
getId( $wikiId=self::LOCAL)
Get the user's ID.
Definition: User.php:2082
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:894
SpecialPage\getContext
getContext()
Gets the context this SpecialPage is executed in.
Definition: SpecialPage.php:764
StatusValue\newGood
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:82
Hooks\runner
static runner()
Get a HookRunner instance for calling hooks using the new interfaces.
Definition: Hooks.php:173
SpecialPage\getRequest
getRequest()
Get the WebRequest being used for this instance.
Definition: SpecialPage.php:780
SpecialEmailUser\$userNamePrefixSearch
UserNamePrefixSearch $userNamePrefixSearch
Definition: SpecialEmailUser.php:47
IContextSource\getUser
getUser()
SpecialEmailUser\$mTargetObj
User string $mTargetObj
Definition: SpecialEmailUser.php:41
MediaWiki\User\UserOptionsLookup
Provides access to user options.
Definition: UserOptionsLookup.php:29
SpecialEmailUser\getGroupName
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
Definition: SpecialEmailUser.php:567
IContextSource
Interface for objects which can provide a MediaWiki context on request.
Definition: IContextSource.php:58
SpecialEmailUser\getPermissionsError
static getPermissionsError( $user, $editToken, Config $config=null)
Check whether a user is allowed to send email.
Definition: SpecialEmailUser.php:268
MediaWiki\Preferences\MultiUsernameFilter
Definition: MultiUsernameFilter.php:27
SpecialPage\getLinkRenderer
getLinkRenderer()
Definition: SpecialPage.php:1028
SpecialEmailUser\$userOptionsLookup
UserOptionsLookup $userOptionsLookup
Definition: SpecialEmailUser.php:50
SpecialPage\$hookRunner
HookRunner null $hookRunner
Definition: SpecialPage.php:85
SpecialEmailUser\$mTarget
$mTarget
Definition: SpecialEmailUser.php:36
SpecialEmailUser\submit
static submit(array $data, IContextSource $context)
Really send a mail.
Definition: SpecialEmailUser.php:390
MediaWiki\User\UserNameUtils
UserNameUtils service.
Definition: UserNameUtils.php:42
IContextSource\getConfig
getConfig()
Get the site configuration.
SpecialEmailUser\getFormFields
getFormFields()
Definition: SpecialEmailUser.php:81
SpecialPage\setContext
setContext( $context)
Sets the context this SpecialPage is executed in.
Definition: SpecialPage.php:754
SpecialEmailUser\__construct
__construct(UserNameUtils $userNameUtils, UserNamePrefixSearch $userNamePrefixSearch, UserOptionsLookup $userOptionsLookup)
Definition: SpecialEmailUser.php:57
ErrorPageError
An error page which can definitely be safely rendered using the OutputPage.
Definition: ErrorPageError.php:30
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:68
SpecialEmailUser\sendEmailForm
sendEmailForm()
Definition: SpecialEmailUser.php:343
HTMLForm\factory
static factory( $displayFormat,... $arguments)
Construct a HTMLForm object for given display type.
Definition: HTMLForm.php:326
SpecialPage\outputHeader
outputHeader( $summaryMessageKey='')
Outputs a summary message on top of special pages Per default the message key is the canonical name o...
Definition: SpecialPage.php:709
SpecialEmailUser\getDescription
getDescription()
Returns the name that goes in the <h1> in the special page itself, and also the name that will be l...
Definition: SpecialEmailUser.php:72
$type
$type
Definition: testCompression.php:52
SpecialEmailUser\prefixSearchSubpages
prefixSearchSubpages( $search, $limit, $offset)
Return an array of subpages beginning with $search that this special page will accept.
Definition: SpecialEmailUser.php:556