MediaWiki master
SpecialEmailUser.php
Go to the documentation of this file.
1<?php
21namespace MediaWiki\Specials;
22
36use StatusValue;
37
47
48 private UserNameUtils $userNameUtils;
49 private UserNamePrefixSearch $userNamePrefixSearch;
50 private UserOptionsLookup $userOptionsLookup;
51 private EmailUserFactory $emailUserFactory;
52 private UserFactory $userFactory;
53
61 public function __construct(
62 UserNameUtils $userNameUtils,
63 UserNamePrefixSearch $userNamePrefixSearch,
64 UserOptionsLookup $userOptionsLookup,
65 EmailUserFactory $emailUserFactory,
66 UserFactory $userFactory
67 ) {
68 parent::__construct( 'Emailuser' );
69 $this->userNameUtils = $userNameUtils;
70 $this->userNamePrefixSearch = $userNamePrefixSearch;
71 $this->userOptionsLookup = $userOptionsLookup;
72 $this->emailUserFactory = $emailUserFactory;
73 $this->userFactory = $userFactory;
74 }
75
76 public function doesWrites() {
77 return true;
78 }
79
80 public function getDescription() {
81 return $this->msg( 'emailuser-title-notarget' );
82 }
83
84 protected function getFormFields( User $target ) {
85 $linkRenderer = $this->getLinkRenderer();
86 $user = $this->getUser();
87 return [
88 'From' => [
89 'type' => 'info',
90 'raw' => 1,
91 'default' => $linkRenderer->makeLink(
92 $user->getUserPage(),
93 $user->getName()
94 ),
95 'label-message' => 'emailfrom',
96 'id' => 'mw-emailuser-sender',
97 ],
98 'To' => [
99 'type' => 'info',
100 'raw' => 1,
101 'default' => $linkRenderer->makeLink(
102 $target->getUserPage(),
103 $target->getName()
104 ),
105 'label-message' => 'emailto',
106 'id' => 'mw-emailuser-recipient',
107 ],
108 'Target' => [
109 'type' => 'hidden',
110 'default' => $target->getName(),
111 ],
112 'Subject' => [
113 'type' => 'text',
114 'default' => $this->msg( 'defemailsubject', $user->getName() )->inContentLanguage()->text(),
115 'label-message' => 'emailsubject',
116 'maxlength' => 200,
117 'size' => 60,
118 'required' => true,
119 ],
120 'Text' => [
121 'type' => 'textarea',
122 'rows' => 20,
123 'label-message' => 'emailmessage',
124 'required' => true,
125 ],
126 'CCMe' => [
127 'type' => 'check',
128 'label-message' => 'emailccme',
129 'default' => $this->userOptionsLookup->getBoolOption( $user, 'ccmeonemails' ),
130 ],
131 ];
132 }
133
134 public function execute( $par ) {
135 $this->setHeaders();
136 $this->outputHeader();
137
138 $out = $this->getOutput();
139 $request = $this->getRequest();
140 $out->addModuleStyles( 'mediawiki.special' );
141
142 // Error out if sending user cannot do this. Don't authorize yet.
143 $emailUser = $this->emailUserFactory->newEmailUserBC(
144 $this->getUser(),
145 $this->getConfig()
146 );
147 $emailUser->setEditToken( (string)$request->getVal( 'wpEditToken' ) );
148 $status = $emailUser->canSend();
149
150 if ( !$status->isGood() ) {
151 if ( $status instanceof PermissionStatus ) {
152 $status->throwErrorPageError();
153 } elseif ( $status->hasMessage( 'mailnologin' ) ) {
154 throw new ErrorPageError( 'mailnologin', 'mailnologintext' );
155 } elseif ( $status->hasMessage( 'usermaildisabled' ) ) {
156 throw new ErrorPageError( 'usermaildisabled', 'usermaildisabledtext' );
157 } elseif ( $status->getValue() !== null ) {
158 // BC for deprecated hook errors
159 // (to be removed when UserCanSendEmail and EmailUserPermissionsErrors are removed)
160 $msg = $status->getMessages()[0];
161 throw new ErrorPageError( $status->getValue(), $msg );
162 } else {
163 // Fallback in case new error types are added in EmailUser
164 throw new ErrorPageError( $this->getDescription(), Status::wrap( $status )->getMessage() );
165 }
166 }
167
168 // Always go through the userform, it will do validations on the target
169 // and display the emailform for us.
170 $target = $par ?? $request->getVal( 'wpTarget', $request->getVal( 'target', '' ) );
171 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable Defaults to empty string
172 $this->userForm( $target );
173 }
174
183 public static function getTarget( $target, User $sender ) {
184 $targetObject = MediaWikiServices::getInstance()->getUserFactory()->newFromName( $target );
185 if ( !$targetObject instanceof User ) {
186 return 'notarget';
187 }
188
189 $status = MediaWikiServices::getInstance()->getEmailUserFactory()
190 ->newEmailUser( $sender )
191 ->validateTarget( $targetObject );
192 if ( !$status->isGood() ) {
193 $msg = $status->getMessages()[0]->getKey();
194 $ret = $msg === 'emailnotarget' ? 'notarget' : preg_replace( '/text$/', '', $msg );
195 } else {
196 $ret = $targetObject;
197 }
198 return $ret;
199 }
200
206 protected function userForm( $name ) {
207 $htmlForm = HTMLForm::factory( 'ooui', [
208 'Target' => [
209 'type' => 'user',
210 'exists' => true,
211 'required' => true,
212 'label-message' => 'emailusername',
213 'id' => 'emailusertarget',
214 'autofocus' => true,
215 // Exclude temporary accounts from the autocomplete, as they cannot have email addresses.
216 'excludetemp' => true,
217 // Skip validation when visit directly without subpage (T347854)
218 'default' => '',
219 // Prefill for subpage syntax and old target param.
220 'filter-callback' => static function ( $value ) use ( $name ) {
221 return str_replace( '_', ' ',
222 ( $value !== '' && $value !== false && $value !== null ) ? $value : $name );
223 },
224 'validation-callback' => function ( $value ) {
225 // HTMLForm checked that this is a valid user name
226 $target = $this->userFactory->newFromName( $value );
227 $statusValue = $this->emailUserFactory
228 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
229 ->newEmailUser( $this->getUser() )->validateTarget( $target );
230 if ( !$statusValue->isGood() ) {
231 // TODO: Return Status instead of StatusValue from validateTarget() method?
232 return Status::wrap( $statusValue )->getMessage();
233 }
234 return true;
235 }
236 ]
237 ], $this->getContext() );
238
239 $htmlForm
240 ->setMethod( 'GET' )
241 ->setTitle( $this->getPageTitle() ) // Remove subpage
242 ->setSubmitCallback( [ $this, 'sendEmailForm' ] )
243 ->setId( 'askusername' )
244 ->setWrapperLegendMsg( 'emailtarget' )
245 ->setSubmitTextMsg( 'emailusernamesubmit' )
246 ->show();
247 }
248
253 public function sendEmailForm( array $data ) {
254 $out = $this->getOutput();
255
256 // HTMLForm checked that this is a valid user name, the return value can never be null.
257 $target = $this->userFactory->newFromName( $data['Target'] );
258 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
259 $htmlForm = HTMLForm::factory( 'ooui', $this->getFormFields( $target ), $this->getContext() );
260 $htmlForm
261 ->setTitle( $this->getPageTitle() ) // Remove subpage
262 ->addPreHtml( $this->msg( 'emailpagetext', $target->getName() )->parse() )
263 ->setSubmitTextMsg( 'emailsend' )
264 ->setSubmitCallback( [ $this, 'onFormSubmit' ] )
265 ->setWrapperLegendMsg( 'email-legend' )
266 ->prepareForm();
267
268 if ( !$this->getHookRunner()->onEmailUserForm( $htmlForm ) ) {
269 return false;
270 }
271
272 $result = $htmlForm->show();
273
274 if ( $result === true || ( $result instanceof Status && $result->isGood() ) ) {
275 $out->setPageTitleMsg( $this->msg( 'emailsent' ) );
276 $out->addWikiMsg( 'emailsenttext', $target->getName() );
277 $out->returnToMain( false, $target->getUserPage() );
278 } else {
279 $out->setPageTitleMsg( $this->msg( 'emailuser-title-target', $target->getName() ) );
280 }
281 return true;
282 }
283
289 public function onFormSubmit( array $data ) {
290 // HTMLForm checked that this is a valid user name, the return value can never be null.
291 $target = $this->userFactory->newFromName( $data['Target'] );
292
293 $emailUser = $this->emailUserFactory->newEmailUser( $this->getAuthority() );
294 $emailUser->setEditToken( $this->getRequest()->getVal( 'wpEditToken' ) );
295
296 // Fully authorize on sending emails.
297 $status = $emailUser->authorizeSend();
298
299 if ( !$status->isOK() ) {
300 return $status;
301 }
302
303 // @phan-suppress-next-next-line PhanTypeMismatchArgumentNullable
304 $res = $emailUser->sendEmailUnsafe(
305 $target,
306 $data['Subject'],
307 $data['Text'],
308 $data['CCMe'],
309 $this->getLanguage()->getCode()
310 );
311 if ( $res->hasMessage( 'hookaborted' ) ) {
312 // BC: The method could previously return false if the EmailUser hook set the error to false. Preserve
313 // that behaviour until we replace the hook.
314 $res = false;
315 } else {
316 $res = Status::wrap( $res );
317 }
318 return $res;
319 }
320
329 public function prefixSearchSubpages( $search, $limit, $offset ) {
330 $search = $this->userNameUtils->getCanonical( $search );
331 if ( !$search ) {
332 // No prefix suggestion for invalid user
333 return [];
334 }
335 // Autocomplete subpage as user list - public to allow caching
336 return $this->userNamePrefixSearch
337 ->search( UserNamePrefixSearch::AUDIENCE_PUBLIC, $search, $limit, $offset );
338 }
339
343 public function isListed() {
344 return $this->getConfig()->get( MainConfigNames::EnableUserEmail );
345 }
346
347 protected function getGroupName() {
348 return 'users';
349 }
350}
351
353class_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:209
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.
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 By default the message key is the canonical name of...
Send an e-mail from one user to another.
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...
userForm( $name)
Form to ask for target user name.
doesWrites()
Indicates whether POST requests to this special page require write access to the wiki.
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.
__construct(UserNameUtils $userNameUtils, UserNamePrefixSearch $userNamePrefixSearch, UserOptionsLookup $userOptionsLookup, EmailUserFactory $emailUserFactory, UserFactory $userFactory)
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:54
Provides access to user options.
Create User objects.
Handles searching prefixes of user names.
UserNameUtils service.
User class for the MediaWiki software.
Definition User.php:119
getUserPage()
Get this user's personal page title.
Definition User.php:2740
getName()
Get the user name, or the IP of an anonymous user.
Definition User.php:1591
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.