MediaWiki master
SpecialEmailUser.php
Go to the documentation of this file.
1<?php
24namespace MediaWiki\Specials;
25
42use StatusValue;
43
50
51 private UserNameUtils $userNameUtils;
52 private UserNamePrefixSearch $userNamePrefixSearch;
53 private UserOptionsLookup $userOptionsLookup;
54 private EmailUserFactory $emailUserFactory;
55 private UserFactory $userFactory;
56
64 public function __construct(
65 UserNameUtils $userNameUtils,
66 UserNamePrefixSearch $userNamePrefixSearch,
67 UserOptionsLookup $userOptionsLookup,
68 EmailUserFactory $emailUserFactory,
69 UserFactory $userFactory
70 ) {
71 parent::__construct( 'Emailuser' );
72 $this->userNameUtils = $userNameUtils;
73 $this->userNamePrefixSearch = $userNamePrefixSearch;
74 $this->userOptionsLookup = $userOptionsLookup;
75 $this->emailUserFactory = $emailUserFactory;
76 $this->userFactory = $userFactory;
77 }
78
79 public function doesWrites() {
80 return true;
81 }
82
83 public function getDescription() {
84 return $this->msg( 'emailuser-title-notarget' );
85 }
86
87 protected function getFormFields( User $target ) {
88 $linkRenderer = $this->getLinkRenderer();
89 $user = $this->getUser();
90 return [
91 'From' => [
92 'type' => 'info',
93 'raw' => 1,
94 'default' => $linkRenderer->makeLink(
95 $user->getUserPage(),
96 $user->getName()
97 ),
98 'label-message' => 'emailfrom',
99 'id' => 'mw-emailuser-sender',
100 ],
101 'To' => [
102 'type' => 'info',
103 'raw' => 1,
104 'default' => $linkRenderer->makeLink(
105 $target->getUserPage(),
106 $target->getName()
107 ),
108 'label-message' => 'emailto',
109 'id' => 'mw-emailuser-recipient',
110 ],
111 'Target' => [
112 'type' => 'hidden',
113 'default' => $target->getName(),
114 ],
115 'Subject' => [
116 'type' => 'text',
117 'default' => $this->msg( 'defemailsubject', $user->getName() )->inContentLanguage()->text(),
118 'label-message' => 'emailsubject',
119 'maxlength' => 200,
120 'size' => 60,
121 'required' => true,
122 ],
123 'Text' => [
124 'type' => 'textarea',
125 'rows' => 20,
126 'label-message' => 'emailmessage',
127 'required' => true,
128 ],
129 'CCMe' => [
130 'type' => 'check',
131 'label-message' => 'emailccme',
132 'default' => $this->userOptionsLookup->getBoolOption( $user, 'ccmeonemails' ),
133 ],
134 ];
135 }
136
137 public function execute( $par ) {
138 $this->setHeaders();
139 $this->outputHeader();
140
141 $out = $this->getOutput();
142 $request = $this->getRequest();
143 $out->addModuleStyles( 'mediawiki.special' );
144
145 // Error out if sending user cannot do this. Don't authorize yet.
146 $emailUser = $this->emailUserFactory->newEmailUserBC(
147 $this->getUser(),
148 $this->getConfig()
149 );
150 $emailUser->setEditToken( (string)$request->getVal( 'wpEditToken' ) );
151 $status = $emailUser->canSend();
152
153 if ( !$status->isGood() ) {
154 if ( $status instanceof PermissionStatus ) {
155 $status->throwErrorPageError();
156 } elseif ( $status->hasMessage( 'mailnologin' ) ) {
157 throw new ErrorPageError( 'mailnologin', 'mailnologintext' );
158 } elseif ( $status->hasMessage( 'usermaildisabled' ) ) {
159 throw new ErrorPageError( 'usermaildisabled', 'usermaildisabledtext' );
160 } elseif ( $status->getValue() !== null ) {
161 // BC for deprecated hook errors
162 // (to be removed when UserCanSendEmail and EmailUserPermissionsErrors are removed)
163 $error = $status->getErrors()[0];
164 throw new ErrorPageError( $status->getValue(), $error['message'], $error['params'] );
165 } else {
166 // Fallback in case new error types are added in EmailUser
167 throw new ErrorPageError( $this->getDescription(), Status::wrap( $status )->getMessage() );
168 }
169 }
170
171 // Always go through the userform, it will do validations on the target
172 // and display the emailform for us.
173 $target = $par ?? $request->getVal( 'wpTarget', $request->getVal( 'target', '' ) );
174 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable Defaults to empty string
175 $this->userForm( $target );
176 }
177
186 public static function getTarget( $target, User $sender ) {
187 $targetObject = MediaWikiServices::getInstance()->getUserFactory()->newFromName( $target );
188 if ( !$targetObject instanceof User ) {
189 return 'notarget';
190 }
191
192 $status = MediaWikiServices::getInstance()->getEmailUserFactory()
193 ->newEmailUser( $sender )
194 ->validateTarget( $targetObject );
195 if ( !$status->isGood() ) {
196 $msg = $status->getErrors()[0]['message'];
197 $ret = $msg === 'emailnotarget' ? 'notarget' : preg_replace( '/text$/', '', $msg );
198 } else {
199 $ret = $targetObject;
200 }
201 return $ret;
202 }
203
213 public static function validateTarget( $target, User $sender ) {
214 if ( !$target instanceof User ) {
215 return 'notarget';
216 }
217 $status = MediaWikiServices::getInstance()->getEmailUserFactory()
218 ->newEmailUser( $sender )
219 ->validateTarget( $target );
220 if ( $status->isGood() ) {
221 $ret = '';
222 } else {
223 $msg = $status->getErrors()[0]['message'];
224 $ret = $msg === 'emailnotarget' ? 'notarget' : preg_replace( '/text$/', '', $msg );
225 }
226 return $ret;
227 }
228
242 public static function getPermissionsError( $user, $editToken, Config $config = null, $authorize = false ) {
243 $emailUser = MediaWikiServices::getInstance()->getEmailUserFactory()->newEmailUserBC( $user, $config );
244 $emailUser->setEditToken( (string)$editToken );
245 $status = $authorize ? $emailUser->authorizeSend() : $emailUser->canSend();
246
247 if ( $status->isGood() ) {
248 return null;
249 }
250 foreach ( $status->getErrors() as $err ) {
251 $errKey = $err['message'] instanceof Message ? $err['message']->getKey() : $err['message'];
252 if ( strpos( $errKey, 'blockedtext' ) !== false ) {
253 // BC for block messages
254 return "blockedemailuser";
255 }
256 }
257 $error = $status->getErrors()[0];
258 if ( $status->getValue() !== null ) {
259 // BC for hook errors intended to be used with ErrorPageError
260 return [ $status->getValue(), $error['message'], $error['params'] ];
261 }
262 return $error['message'];
263 }
264
270 protected function userForm( $name ) {
271 $htmlForm = HTMLForm::factory( 'ooui', [
272 'Target' => [
273 'type' => 'user',
274 'exists' => true,
275 'required' => true,
276 'label-message' => 'emailusername',
277 'id' => 'emailusertarget',
278 'autofocus' => true,
279 // Skip validation when visit directly without subpage (T347854)
280 'default' => '',
281 // Prefill for subpage syntax and old target param.
282 'filter-callback' => static function ( $value ) use ( $name ) {
283 return str_replace( '_', ' ',
284 ( $value !== '' && $value !== false && $value !== null ) ? $value : $name );
285 },
286 'validation-callback' => function ( $value ) {
287 // HTMLForm checked that this is a valid user name
288 $target = $this->userFactory->newFromName( $value );
289 $statusValue = $this->emailUserFactory
290 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
291 ->newEmailUser( $this->getUser() )->validateTarget( $target );
292 if ( !$statusValue->isGood() ) {
293 // TODO: Return Status instead of StatusValue from validateTarget() method?
294 return Status::wrap( $statusValue )->getMessage();
295 }
296 return true;
297 }
298 ]
299 ], $this->getContext() );
300
301 $htmlForm
302 ->setMethod( 'GET' )
303 ->setTitle( $this->getPageTitle() ) // Remove subpage
304 ->setSubmitCallback( [ $this, 'sendEmailForm' ] )
305 ->setId( 'askusername' )
306 ->setWrapperLegendMsg( 'emailtarget' )
307 ->setSubmitTextMsg( 'emailusernamesubmit' )
308 ->show();
309 }
310
315 public function sendEmailForm( array $data ) {
316 $out = $this->getOutput();
317
318 // HTMLForm checked that this is a valid user name, the return value can never be null.
319 $target = $this->userFactory->newFromName( $data['Target'] );
320 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
321 $htmlForm = HTMLForm::factory( 'ooui', $this->getFormFields( $target ), $this->getContext() );
322 $htmlForm
323 ->setTitle( $this->getPageTitle() ) // Remove subpage
324 ->addPreHtml( $this->msg( 'emailpagetext', $target->getName() )->parse() )
325 ->setSubmitTextMsg( 'emailsend' )
326 ->setSubmitCallback( [ $this, 'onFormSubmit' ] )
327 ->setWrapperLegendMsg( 'email-legend' )
328 ->prepareForm();
329
330 if ( !$this->getHookRunner()->onEmailUserForm( $htmlForm ) ) {
331 return false;
332 }
333
334 $result = $htmlForm->show();
335
336 if ( $result === true || ( $result instanceof Status && $result->isGood() ) ) {
337 $out->setPageTitleMsg( $this->msg( 'emailsent' ) );
338 $out->addWikiMsg( 'emailsenttext', $target->getName() );
339 $out->returnToMain( false, $target->getUserPage() );
340 } else {
341 $out->setPageTitleMsg( $this->msg( 'emailuser-title-target', $target->getName() ) );
342 }
343 return true;
344 }
345
351 public function onFormSubmit( array $data ) {
352 // HTMLForm checked that this is a valid user name, the return value can never be null.
353 $target = $this->userFactory->newFromName( $data['Target'] );
354
355 $emailUser = $this->emailUserFactory->newEmailUser( $this->getAuthority() );
356 $emailUser->setEditToken( $this->getRequest()->getVal( 'wpEditToken' ) );
357
358 // Fully authorize on sending emails.
359 $status = $emailUser->authorizeSend();
360
361 if ( !$status->isOK() ) {
362 return $status;
363 }
364
365 // @phan-suppress-next-next-line PhanTypeMismatchArgumentNullable
366 $res = $emailUser->sendEmailUnsafe(
367 $target,
368 $data['Subject'],
369 $data['Text'],
370 $data['CCMe'],
371 $this->getLanguage()->getCode()
372 );
373 if ( $res->hasMessage( 'hookaborted' ) ) {
374 // BC: The method could previously return false if the EmailUser hook set the error to false. Preserve
375 // that behaviour until we replace the hook.
376 $res = false;
377 } else {
378 $res = Status::wrap( $res );
379 }
380 return $res;
381 }
382
393 public static function submit( array $data, IContextSource $context ) {
394 $target = MediaWikiServices::getInstance()->getUserFactory()->newFromName( (string)$data['Target'] );
395 if ( !$target instanceof User ) {
396 return Status::newFatal( 'emailnotarget' );
397 }
398
399 $emailUser = MediaWikiServices::getInstance()->getEmailUserFactory()
400 ->newEmailUserBC( $context->getAuthority(), $context->getConfig() );
401
402 $ret = $emailUser->sendEmailUnsafe(
403 $target,
404 (string)$data['Subject'],
405 (string)$data['Text'],
406 (bool)$data['CCMe'],
407 $context->getLanguage()->getCode()
408 );
409 if ( $ret->hasMessage( 'hookaborted' ) ) {
410 // BC: The method could previously return false if the EmailUser hook set the error to false.
411 $ret = false;
412 } elseif ( $ret->hasMessage( 'noemailtarget' ) ) {
413 // BC: The previous implementation would use notargettext even if noemailtarget would be the right
414 // message to use here.
415 return Status::newFatal( 'notargettext' );
416 } else {
417 $ret = Status::wrap( $ret );
418 }
419 return $ret;
420 }
421
430 public function prefixSearchSubpages( $search, $limit, $offset ) {
431 $search = $this->userNameUtils->getCanonical( $search );
432 if ( !$search ) {
433 // No prefix suggestion for invalid user
434 return [];
435 }
436 // Autocomplete subpage as user list - public to allow caching
437 return $this->userNamePrefixSearch
438 ->search( UserNamePrefixSearch::AUDIENCE_PUBLIC, $search, $limit, $offset );
439 }
440
444 public function isListed() {
445 return $this->getConfig()->get( MainConfigNames::EnableUserEmail );
446 }
447
448 protected function getGroupName() {
449 return 'users';
450 }
451}
452
454class_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:158
getKey()
Returns the message key.
Definition Message.php:386
A StatusValue for permission errors.
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
getUserPage()
Get this user's personal page title.
Definition User.php:2850
getName()
Get the user name, or the IP of an anonymous user.
Definition User.php:1594
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.
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...