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