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  $lookup = CentralIdLookup::factory();
247  $senderId = $lookup->centralIdFromLocalUser( $sender );
248  if ( $senderId !== 0 && in_array( $senderId, $muteList ) ) {
249  wfDebug( "User does not allow user emails from this user." );
250 
251  return 'nowikiemail';
252  }
253  }
254 
255  return '';
256  }
257 
267  public static function getPermissionsError( $user, $editToken, Config $config = null ) {
268  if ( $config === null ) {
269  wfDebug( __METHOD__ . ' called without a Config instance passed to it' );
270  $config = MediaWikiServices::getInstance()->getMainConfig();
271  }
272  if ( !$config->get( 'EnableEmail' ) || !$config->get( 'EnableUserEmail' ) ) {
273  return 'usermaildisabled';
274  }
275 
276  // Run this before checking 'sendemail' permission
277  // to show appropriate message to anons (T160309)
278  if ( !$user->isEmailConfirmed() ) {
279  return 'mailnologin';
280  }
281 
282  if ( !MediaWikiServices::getInstance()
283  ->getPermissionManager()
284  ->userHasRight( $user, 'sendemail' )
285  ) {
286  return 'badaccess';
287  }
288 
289  if ( $user->isBlockedFromEmailuser() ) {
290  wfDebug( "User is blocked from sending e-mail." );
291 
292  return "blockedemailuser";
293  }
294 
295  // Check the ping limiter without incrementing it - we'll check it
296  // again later and increment it on a successful send
297  if ( $user->pingLimiter( 'emailuser', 0 ) ) {
298  wfDebug( "Ping limiter triggered." );
299 
300  return 'actionthrottledtext';
301  }
302 
303  $hookErr = false;
304 
305  Hooks::runner()->onUserCanSendEmail( $user, $hookErr );
306  Hooks::runner()->onEmailUserPermissionsErrors( $user, $editToken, $hookErr );
307 
308  if ( $hookErr ) {
309  return $hookErr;
310  }
311 
312  return null;
313  }
314 
320  protected function userForm( $name ) {
321  $htmlForm = HTMLForm::factory( 'ooui', [
322  'Target' => [
323  'type' => 'user',
324  'exists' => true,
325  'required' => true,
326  'label' => $this->msg( 'emailusername' )->text(),
327  'id' => 'emailusertarget',
328  'autofocus' => true,
329  'value' => $name,
330  ]
331  ], $this->getContext() );
332 
333  $htmlForm
334  ->setSubmitCallback( [ $this, 'sendEmailForm' ] )
335  ->setFormIdentifier( 'userForm' )
336  ->setId( 'askusername' )
337  ->setWrapperLegendMsg( 'emailtarget' )
338  ->setSubmitTextMsg( 'emailusernamesubmit' )
339  ->show();
340  }
341 
342  public function sendEmailForm() {
343  $out = $this->getOutput();
344 
345  $ret = $this->mTargetObj;
346  if ( !$ret instanceof User ) {
347  if ( $this->mTarget != '' ) {
348  // Messages used here: notargettext, noemailtext, nowikiemailtext
349  $ret = ( $ret == 'notarget' ) ? 'emailnotarget' : ( $ret . 'text' );
350  return Status::newFatal( $ret );
351  }
352  return false;
353  }
354 
355  $htmlForm = HTMLForm::factory( 'ooui', $this->getFormFields(), $this->getContext() );
356  // By now we are supposed to be sure that $this->mTarget is a user name
357  $htmlForm
358  ->addPreText( $this->msg( 'emailpagetext', $this->mTarget )->parse() )
359  ->setSubmitTextMsg( 'emailsend' )
360  ->setSubmitCallback( [ __CLASS__, 'submit' ] )
361  ->setFormIdentifier( 'sendEmailForm' )
362  ->setWrapperLegendMsg( 'email-legend' )
363  ->loadData();
364 
365  if ( !$this->getHookRunner()->onEmailUserForm( $htmlForm ) ) {
366  return false;
367  }
368 
369  $result = $htmlForm->show();
370 
371  if ( $result === true || ( $result instanceof Status && $result->isGood() ) ) {
372  $out->setPageTitle( $this->msg( 'emailsent' ) );
373  $out->addWikiMsg( 'emailsenttext', $this->mTarget );
374  $out->returnToMain( false, $ret->getUserPage() );
375  }
376  return true;
377  }
378 
388  public static function submit( array $data, IContextSource $context ) {
389  $config = $context->getConfig();
390 
391  $target = self::getTarget( $data['Target'], $context->getUser() );
392  if ( !$target instanceof User ) {
393  // Messages used here: notargettext, noemailtext, nowikiemailtext
394  return Status::newFatal( $target . 'text' );
395  }
396 
397  $to = MailAddress::newFromUser( $target );
398  $from = MailAddress::newFromUser( $context->getUser() );
399  $subject = $data['Subject'];
400  $text = $data['Text'];
401 
402  // Add a standard footer and trim up trailing newlines
403  $text = rtrim( $text ) . "\n\n-- \n";
404  $text .= $context->msg( 'emailuserfooter',
405  $from->name, $to->name )->inContentLanguage()->text();
406 
407  if ( $config->get( 'EnableSpecialMute' ) ) {
408  $specialMutePage = SpecialPage::getTitleFor( 'Mute', $context->getUser()->getName() );
409  $text .= "\n" . $context->msg(
410  'specialmute-email-footer',
411  $specialMutePage->getCanonicalURL(),
412  $context->getUser()->getName()
413  )->inContentLanguage()->text();
414  }
415 
416  // Check and increment the rate limits
417  if ( $context->getUser()->pingLimiter( 'emailuser' ) ) {
418  throw new ThrottledError();
419  }
420 
421  $error = false;
422  if ( !Hooks::runner()->onEmailUser( $to, $from, $subject, $text, $error ) ) {
423  if ( $error instanceof Status ) {
424  return $error;
425  } elseif ( $error === false || $error === '' || $error === [] ) {
426  // Possibly to tell HTMLForm to pretend there was no submission?
427  return false;
428  } elseif ( $error === true ) {
429  // Hook sent the mail itself and indicates success?
430  return Status::newGood();
431  } elseif ( is_array( $error ) ) {
432  $status = Status::newGood();
433  foreach ( $error as $e ) {
434  $status->fatal( $e );
435  }
436  return $status;
437  } elseif ( $error instanceof MessageSpecifier ) {
438  return Status::newFatal( $error );
439  } else {
440  // Ugh. Either a raw HTML string, or something that's supposed
441  // to be treated like one.
442  $type = is_object( $error ) ? get_class( $error ) : gettype( $error );
443  wfDeprecatedMsg( "An EmailUser hook returned a $type as \$error, " .
444  "this is deprecated since MediaWiki 1.29",
445  '1.29', false, false );
446  return Status::newFatal( new ApiRawMessage(
447  [ '$1', Message::rawParam( (string)$error ) ], 'hookaborted'
448  ) );
449  }
450  }
451 
452  if ( $config->get( 'UserEmailUseReplyTo' ) ) {
461  $mailFrom = new MailAddress( $config->get( 'PasswordSender' ),
462  $context->msg( 'emailsender' )->inContentLanguage()->text() );
463  $replyTo = $from;
464  } else {
480  $mailFrom = $from;
481  $replyTo = null;
482  }
483 
484  $status = UserMailer::send( $to, $mailFrom, $subject, $text, [
485  'replyTo' => $replyTo,
486  ] );
487 
488  if ( !$status->isGood() ) {
489  return $status;
490  } else {
491  // if the user requested a copy of this mail, do this now,
492  // unless they are emailing themselves, in which case one
493  // copy of the message is sufficient.
494  if ( $data['CCMe'] && $to != $from ) {
495  $ccTo = $from;
496  $ccFrom = $from;
497  $ccSubject = $context->msg( 'emailccsubject' )->plaintextParams(
498  $target->getName(), $subject )->text();
499  $ccText = $text;
500 
501  Hooks::runner()->onEmailUserCC( $ccTo, $ccFrom, $ccSubject, $ccText );
502 
503  if ( $config->get( 'UserEmailUseReplyTo' ) ) {
504  $mailFrom = new MailAddress(
505  $config->get( 'PasswordSender' ),
506  $context->msg( 'emailsender' )->inContentLanguage()->text()
507  );
508  $replyTo = $ccFrom;
509  } else {
510  $mailFrom = $ccFrom;
511  $replyTo = null;
512  }
513 
514  $ccStatus = UserMailer::send(
515  $ccTo, $mailFrom, $ccSubject, $ccText, [
516  'replyTo' => $replyTo,
517  ] );
518  $status->merge( $ccStatus );
519  }
520 
521  Hooks::runner()->onEmailUserComplete( $to, $from, $subject, $text );
522 
523  return $status;
524  }
525  }
526 
535  public function prefixSearchSubpages( $search, $limit, $offset ) {
536  $search = $this->userNameUtils->getCanonical( $search );
537  if ( !$search ) {
538  // No prefix suggestion for invalid user
539  return [];
540  }
541  // Autocomplete subpage as user list - public to allow caching
542  return $this->userNamePrefixSearch
543  ->search( UserNamePrefixSearch::AUDIENCE_PUBLIC, $search, $limit, $offset );
544  }
545 
546  protected function getGroupName() {
547  return 'users';
548  }
549 }
SpecialPage\$linkRenderer
LinkRenderer null $linkRenderer
Definition: SpecialPage.php:79
SpecialPage\getPageTitle
getPageTitle( $subpage=false)
Get a self-referential title object.
Definition: SpecialPage.php:742
SpecialPage\msg
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
Definition: SpecialPage.php:900
StatusValue\newFatal
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:70
User\getId
getId()
Get the user's ID.
Definition: User.php:2067
UserBlockedError
Show an error when the user tries to do something whilst blocked.
Definition: UserBlockedError.php:31
SpecialEmailUser\$userNameUtils
UserNameUtils $userNameUtils
Definition: SpecialEmailUser.php:44
SpecialPage\getOutput
getOutput()
Get the OutputPage being used for this instance.
Definition: SpecialPage.php:788
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:172
UnlistedSpecialPage
Shortcut to construct a special page which is unlisted by default.
Definition: UnlistedSpecialPage.php:31
UserMailer\send
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...
Definition: UserMailer.php:115
SpecialEmailUser\userForm
userForm( $name)
Form to ask for target user name.
Definition: SpecialEmailUser.php:320
MessageSpecifier
Stable for implementing.
Definition: MessageSpecifier.php:24
User\newFromName
static newFromName( $name, $validate='valid')
Definition: User.php:584
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:106
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
MailAddress\newFromUser
static newFromUser(User $user)
Create a new MailAddress object for the given user.
Definition: MailAddress.php:70
SpecialEmailUser\execute
execute( $par)
Default execute method Checks user permissions.
Definition: SpecialEmailUser.php:131
ApiRawMessage
Extension of RawMessage implementing IApiMessage @newable.
Definition: ApiRawMessage.php:27
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:34
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
wfDeprecatedMsg
wfDeprecatedMsg( $msg, $version=false, $component=false, $callerOffset=2)
Log a deprecation warning with arbitrary message text.
Definition: GlobalFunctions.php:1066
SpecialPage\getHookRunner
getHookRunner()
Definition: SpecialPage.php:1083
SpecialPage\getConfig
getConfig()
Shortcut to get main config object.
Definition: SpecialPage.php:866
MessageLocalizer\msg
msg( $key,... $params)
This is the method for getting translated interface messages.
SpecialEmailUser\doesWrites
doesWrites()
Indicates whether this special page may perform database writes.
Definition: SpecialEmailUser.php:68
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:3793
$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:616
SpecialPage\getUser
getUser()
Shortcut to get the User executing this instance.
Definition: SpecialPage.php:798
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:915
SpecialPage\getContext
getContext()
Gets the context this SpecialPage is executed in.
Definition: SpecialPage.php:762
StatusValue\newGood
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:82
Message\rawParam
static rawParam( $raw)
Definition: Message.php:1027
Hooks\runner
static runner()
Get a HookRunner instance for calling hooks using the new interfaces.
Definition: Hooks.php:172
SpecialPage\getRequest
getRequest()
Get the WebRequest being used for this instance.
Definition: SpecialPage.php:778
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:546
IContextSource
Interface for objects which can provide a MediaWiki context on request.
Definition: IContextSource.php:57
SpecialEmailUser\getPermissionsError
static getPermissionsError( $user, $editToken, Config $config=null)
Check whether a user is allowed to send email.
Definition: SpecialEmailUser.php:267
MediaWiki\Preferences\MultiUsernameFilter
Definition: MultiUsernameFilter.php:25
SpecialPage\getLinkRenderer
getLinkRenderer()
Definition: SpecialPage.php:1016
SpecialEmailUser\$userOptionsLookup
UserOptionsLookup $userOptionsLookup
Definition: SpecialEmailUser.php:50
SpecialEmailUser\$mTarget
$mTarget
Definition: SpecialEmailUser.php:36
SpecialEmailUser\submit
static submit(array $data, IContextSource $context)
Really send a mail.
Definition: SpecialEmailUser.php:388
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:752
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
CentralIdLookup\factory
static factory( $providerId=null)
Fetch a CentralIdLookup.
Definition: CentralIdLookup.php:47
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:66
SpecialEmailUser\sendEmailForm
sendEmailForm()
Definition: SpecialEmailUser.php:342
HTMLForm\factory
static factory( $displayFormat,... $arguments)
Construct a HTMLForm object for given display type.
Definition: HTMLForm.php:322
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:707
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:535