MediaWiki master
SpecialEmailUser.php
Go to the documentation of this file.
1<?php
24namespace MediaWiki\Specials;
25
42use StatusValue;
45
52 protected $mTarget;
53
57 protected $mTargetObj;
58
59 private UserNameUtils $userNameUtils;
60 private UserNamePrefixSearch $userNamePrefixSearch;
61 private UserOptionsLookup $userOptionsLookup;
62 private EmailUserFactory $emailUserFactory;
63 private UserFactory $userFactory;
64
72 public function __construct(
73 UserNameUtils $userNameUtils,
74 UserNamePrefixSearch $userNamePrefixSearch,
75 UserOptionsLookup $userOptionsLookup,
76 EmailUserFactory $emailUserFactory,
77 UserFactory $userFactory
78 ) {
79 parent::__construct( 'Emailuser' );
80 $this->userNameUtils = $userNameUtils;
81 $this->userNamePrefixSearch = $userNamePrefixSearch;
82 $this->userOptionsLookup = $userOptionsLookup;
83 $this->emailUserFactory = $emailUserFactory;
84 $this->userFactory = $userFactory;
85 }
86
87 public function doesWrites() {
88 return true;
89 }
90
91 public function getDescription() {
92 $target = self::getTarget( $this->mTarget ?? '', $this->getUser() );
93 if ( !$target instanceof User ) {
94 return $this->msg( 'emailuser-title-notarget' );
95 }
96
97 return $this->msg( 'emailuser-title-target', $target->getName() );
98 }
99
100 protected function getFormFields() {
101 $linkRenderer = $this->getLinkRenderer();
102 $user = $this->getUser();
103 return [
104 'From' => [
105 'type' => 'info',
106 'raw' => 1,
107 'default' => $linkRenderer->makeLink(
108 $user->getUserPage(),
109 $user->getName()
110 ),
111 'label-message' => 'emailfrom',
112 'id' => 'mw-emailuser-sender',
113 ],
114 'To' => [
115 'type' => 'info',
116 'raw' => 1,
117 'default' => $linkRenderer->makeLink(
118 $this->mTargetObj->getUserPage(),
119 $this->mTargetObj->getName()
120 ),
121 'label-message' => 'emailto',
122 'id' => 'mw-emailuser-recipient',
123 ],
124 'Target' => [
125 'type' => 'hidden',
126 'default' => $this->mTargetObj->getName(),
127 ],
128 'Subject' => [
129 'type' => 'text',
130 'default' => $this->msg( 'defemailsubject', $user->getName() )->inContentLanguage()->text(),
131 'label-message' => 'emailsubject',
132 'maxlength' => 200,
133 'size' => 60,
134 'required' => true,
135 ],
136 'Text' => [
137 'type' => 'textarea',
138 'rows' => 20,
139 'label-message' => 'emailmessage',
140 'required' => true,
141 ],
142 'CCMe' => [
143 'type' => 'check',
144 'label-message' => 'emailccme',
145 'default' => $this->userOptionsLookup->getBoolOption( $user, 'ccmeonemails' ),
146 ],
147 ];
148 }
149
150 public function execute( $par ) {
151 $out = $this->getOutput();
152 $request = $this->getRequest();
153 $out->addModuleStyles( 'mediawiki.special' );
154
155 $this->mTarget = $par ?? $request->getVal( 'wpTarget', $request->getVal( 'target', '' ) );
156
157 // This needs to be below assignment of $this->mTarget because
158 // getDescription() needs it to determine the correct page title.
159 $this->setHeaders();
160 $this->outputHeader();
161
162 // Error out if sending user cannot do this. Don't authorize yet.
164 $this->getUser(),
165 $this->getRequest()->getVal( 'wpEditToken' ),
166 $this->getConfig()
167 );
168
169 switch ( $error ) {
170 case null:
171 # Wahey!
172 break;
173 case 'badaccess':
174 throw new PermissionsError( 'sendemail' );
175 case 'blockedemailuser':
176 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable Block is checked and not null
177 throw new UserBlockedError( $this->getUser()->getBlock() );
178 case 'actionthrottledtext':
179 throw new ThrottledError;
180 case 'mailnologin':
181 case 'usermaildisabled':
182 throw new ErrorPageError( $error, "{$error}text" );
183 default:
184 # It's a hook error
185 [ $title, $msg, $params ] = $error;
186 throw new ErrorPageError( $title, $msg, $params );
187 }
188
189 // A little hack: HTMLForm will check wpTarget only, if the form was posted, not
190 // if the user opens Special:EmailUser/Florian (e.g.). So check, if the user did that
191 // and show the "Send email to user" form directly, if so. Show the "enter username"
192 // form, otherwise.
193 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable target is set
194 $this->mTargetObj = self::getTarget( $this->mTarget, $this->getUser() );
195 if ( !$this->mTargetObj instanceof User ) {
196 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable target is set
197 $this->userForm( $this->mTarget );
198 } else {
199 $this->sendEmailForm();
200 }
201 }
202
211 public static function getTarget( $target, User $sender ) {
212 $targetObject = MediaWikiServices::getInstance()->getUserFactory()->newFromName( $target );
213 if ( !$targetObject instanceof User ) {
214 return 'notarget';
215 }
216
217 $status = MediaWikiServices::getInstance()->getEmailUserFactory()
218 ->newEmailUser( $sender )
219 ->validateTarget( $targetObject );
220 if ( !$status->isGood() ) {
221 $msg = $status->getErrors()[0]['message'];
222 $ret = $msg === 'emailnotarget' ? 'notarget' : preg_replace( '/text$/', '', $msg );
223 } else {
224 $ret = $targetObject;
225 }
226 return $ret;
227 }
228
238 public static function validateTarget( $target, User $sender ) {
239 if ( !$target instanceof User ) {
240 return 'notarget';
241 }
242 $status = MediaWikiServices::getInstance()->getEmailUserFactory()
243 ->newEmailUser( $sender )
244 ->validateTarget( $target );
245 if ( $status->isGood() ) {
246 $ret = '';
247 } else {
248 $msg = $status->getErrors()[0]['message'];
249 $ret = $msg === 'emailnotarget' ? 'notarget' : preg_replace( '/text$/', '', $msg );
250 }
251 return $ret;
252 }
253
267 public static function getPermissionsError( $user, $editToken, Config $config = null, $authorize = false ) {
268 $emailUser = MediaWikiServices::getInstance()->getEmailUserFactory()->newEmailUserBC( $user, $config );
269 $emailUser->setEditToken( (string)$editToken );
270 $status = $authorize ? $emailUser->authorizeSend() : $emailUser->canSend();
271
272 if ( $status->isGood() ) {
273 return null;
274 }
275 foreach ( $status->getErrors() as $err ) {
276 $errKey = $err['message'] instanceof Message ? $err['message']->getKey() : $err['message'];
277 if ( strpos( $errKey, 'blockedtext' ) !== false ) {
278 // BC for block messages
279 return "blockedemailuser";
280 }
281 }
282 $error = $status->getErrors()[0];
283 if ( $status->getValue() !== null ) {
284 // BC for hook errors intended to be used with ErrorPageError
285 return [ $status->getValue(), $error['message'], $error['params'] ];
286 }
287 return $error['message'];
288 }
289
295 protected function userForm( $name ) {
296 $htmlForm = HTMLForm::factory( 'ooui', [
297 'Target' => [
298 'type' => 'user',
299 'exists' => true,
300 'required' => true,
301 'label-message' => 'emailusername',
302 'id' => 'emailusertarget',
303 'autofocus' => true,
304 // Skip validation when visit directly without subpage (T347854)
305 'default' => '',
306 // Prefill for subpage syntax and old target param.
307 'filter-callback' => static function ( $value ) use ( $name ) {
308 return str_replace( '_', ' ',
309 ( $value !== '' && $value !== false && $value !== null ) ? $value : $name );
310 }
311 ]
312 ], $this->getContext() );
313
314 $htmlForm
315 ->setMethod( 'GET' )
316 ->setTitle( $this->getPageTitle() ) // Remove subpage
317 ->setSubmitCallback( [ $this, 'sendEmailForm' ] )
318 ->setId( 'askusername' )
319 ->setWrapperLegendMsg( 'emailtarget' )
320 ->setSubmitTextMsg( 'emailusernamesubmit' )
321 ->show();
322 }
323
324 public function sendEmailForm() {
325 $out = $this->getOutput();
326
327 if ( !$this->mTargetObj instanceof User ) {
328 if ( $this->mTarget != '' ) {
329 // Messages used here: noemailtext, nowikiemailtext
330 $msg = ( $this->mTargetObj === 'notarget' ) ? 'emailnotarget' : ( $this->mTargetObj . 'text' );
331 return Status::newFatal( $msg );
332 }
333 return false;
334 }
335
336 $htmlForm = HTMLForm::factory( 'ooui', $this->getFormFields(), $this->getContext() );
337 // By now we are supposed to be sure that $this->mTarget is a user name
338 $htmlForm
339 ->setTitle( $this->getPageTitle() ) // Remove subpage
340 ->addPreHtml( $this->msg( 'emailpagetext', $this->mTarget )->parse() )
341 ->setSubmitTextMsg( 'emailsend' )
342 ->setSubmitCallback( [ $this, 'onFormSubmit' ] )
343 ->setWrapperLegendMsg( 'email-legend' )
344 ->prepareForm();
345
346 if ( !$this->getHookRunner()->onEmailUserForm( $htmlForm ) ) {
347 return false;
348 }
349
350 $result = $htmlForm->show();
351
352 if ( $result === true || ( $result instanceof Status && $result->isGood() ) ) {
353 $out->setPageTitleMsg( $this->msg( 'emailsent' ) );
354 $out->addWikiMsg( 'emailsenttext', $this->mTarget );
355 $out->returnToMain( false, $this->mTargetObj->getUserPage() );
356 }
357 return true;
358 }
359
365 public function onFormSubmit( array $data ) {
366 $target = $this->userFactory->newFromName( $data['Target'] );
367 if ( !$target instanceof User ) {
368 return StatusValue::newFatal( 'emailnotarget' );
369 }
370
371 $emailUser = $this->emailUserFactory->newEmailUser( $this->getAuthority() );
372 $emailUser->setEditToken( $this->getRequest()->getVal( 'wpEditToken' ) );
373
374 // Fully authorize on sending emails.
375 $status = $emailUser->authorizeSend();
376
377 if ( !$status->isOK() ) {
378 return $status;
379 }
380
381 $res = $emailUser->sendEmailUnsafe(
382 $target,
383 $data['Subject'],
384 $data['Text'],
385 $data['CCMe'],
386 $this->getLanguage()->getCode()
387 );
388 if ( $res->hasMessage( 'hookaborted' ) ) {
389 // BC: The method could previously return false if the EmailUser hook set the error to false. Preserve
390 // that behaviour until we replace the hook.
391 $res = false;
392 } else {
393 $res = Status::wrap( $res );
394 }
395 return $res;
396 }
397
408 public static function submit( array $data, IContextSource $context ) {
409 $target = MediaWikiServices::getInstance()->getUserFactory()->newFromName( (string)$data['Target'] );
410 if ( !$target instanceof User ) {
411 return Status::newFatal( 'emailnotarget' );
412 }
413
414 $emailUser = MediaWikiServices::getInstance()->getEmailUserFactory()
415 ->newEmailUserBC( $context->getAuthority(), $context->getConfig() );
416
417 $ret = $emailUser->sendEmailUnsafe(
418 $target,
419 (string)$data['Subject'],
420 (string)$data['Text'],
421 (bool)$data['CCMe'],
422 $context->getLanguage()->getCode()
423 );
424 if ( $ret->hasMessage( 'hookaborted' ) ) {
425 // BC: The method could previously return false if the EmailUser hook set the error to false.
426 $ret = false;
427 } elseif ( $ret->hasMessage( 'noemailtarget' ) ) {
428 // BC: The previous implementation would use notargettext even if noemailtarget would be the right
429 // message to use here.
430 return Status::newFatal( 'notargettext' );
431 } else {
432 $ret = Status::wrap( $ret );
433 }
434 return $ret;
435 }
436
445 public function prefixSearchSubpages( $search, $limit, $offset ) {
446 $search = $this->userNameUtils->getCanonical( $search );
447 if ( !$search ) {
448 // No prefix suggestion for invalid user
449 return [];
450 }
451 // Autocomplete subpage as user list - public to allow caching
452 return $this->userNamePrefixSearch
453 ->search( UserNamePrefixSearch::AUDIENCE_PUBLIC, $search, $limit, $offset );
454 }
455
459 public function isListed() {
460 return $this->getConfig()->get( MainConfigNames::EnableUserEmail );
461 }
462
463 protected function getGroupName() {
464 return 'users';
465 }
466}
467
471class_alias( SpecialEmailUser::class, 'SpecialEmailUser' );
An error page which can definitely be safely rendered using the OutputPage.
Object handling generic submission, CSRF protection, layout and other logic for UI forms in a reusabl...
Definition HTMLForm.php:206
Factory for EmailUser objects.
A class containing constants representing the names of configuration variables.
const EnableUserEmail
Name constant for the EnableUserEmail 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.
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition Message.php:157
getKey()
Returns the message key.
Definition Message.php:379
Parent class for all special pages.
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
getUser()
Shortcut to get the User executing this instance.
getPageTitle( $subpage=false)
Get a self-referential title object.
getConfig()
Shortcut to get main config object.
getContext()
Gets the context this SpecialPage is executed in.
getRequest()
Get the WebRequest being used for this instance.
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
getOutput()
Get the OutputPage being used for this instance.
getAuthority()
Shortcut to get the Authority executing this instance.
getLanguage()
Shortcut to get user's language.
outputHeader( $summaryMessageKey='')
Outputs a summary message on top of special pages Per default the message key is the canonical name o...
A special page that allows users to send e-mails to other users.
prefixSearchSubpages( $search, $limit, $offset)
Return an array of subpages beginning with $search that this special page will accept.
getDescription()
Returns the name that goes in the <h1> in the special page itself, and also the name that will be l...
static validateTarget( $target, User $sender)
Validate target User.
userForm( $name)
Form to ask for target user name.
doesWrites()
Indicates whether this special page may perform database writes.
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
static getTarget( $target, User $sender)
Validate target User.
execute( $par)
Default execute method Checks user permissions.
static getPermissionsError( $user, $editToken, Config $config=null, $authorize=false)
Check whether a user is allowed to send email.
__construct(UserNameUtils $userNameUtils, UserNamePrefixSearch $userNamePrefixSearch, UserOptionsLookup $userOptionsLookup, EmailUserFactory $emailUserFactory, UserFactory $userFactory)
static submit(array $data, IContextSource $context)
Really send a mail.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:54
Provides access to user options.
Creates User objects.
Handles searching prefixes of user names.
UserNameUtils service.
internal since 1.36
Definition User.php:93
Show an error when a user tries to do something they do not have the necessary permissions for.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
isGood()
Returns whether the operation completed and didn't have any error or warnings.
Show an error when the user hits a rate limit.
Show an error when the user tries to do something whilst blocked.
Interface for configuration instances.
Definition Config.php:32
Interface for objects which can provide a MediaWiki context on request.
getConfig()
Get the site configuration.
This program is free software; you can redistribute it and/or modify it under the terms of the GNU Ge...