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 $error = $status->getErrors()[0];
161 throw new ErrorPageError( $status->getValue(), $error['message'], $error['params'] );
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->getErrors()[0]['message'];
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 // Skip validation when visit directly without subpage (T347854)
216 'default' => '',
217 // Prefill for subpage syntax and old target param.
218 'filter-callback' => static function ( $value ) use ( $name ) {
219 return str_replace( '_', ' ',
220 ( $value !== '' && $value !== false && $value !== null ) ? $value : $name );
221 },
222 'validation-callback' => function ( $value ) {
223 // HTMLForm checked that this is a valid user name
224 $target = $this->userFactory->newFromName( $value );
225 $statusValue = $this->emailUserFactory
226 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
227 ->newEmailUser( $this->getUser() )->validateTarget( $target );
228 if ( !$statusValue->isGood() ) {
229 // TODO: Return Status instead of StatusValue from validateTarget() method?
230 return Status::wrap( $statusValue )->getMessage();
231 }
232 return true;
233 }
234 ]
235 ], $this->getContext() );
236
237 $htmlForm
238 ->setMethod( 'GET' )
239 ->setTitle( $this->getPageTitle() ) // Remove subpage
240 ->setSubmitCallback( [ $this, 'sendEmailForm' ] )
241 ->setId( 'askusername' )
242 ->setWrapperLegendMsg( 'emailtarget' )
243 ->setSubmitTextMsg( 'emailusernamesubmit' )
244 ->show();
245 }
246
251 public function sendEmailForm( array $data ) {
252 $out = $this->getOutput();
253
254 // HTMLForm checked that this is a valid user name, the return value can never be null.
255 $target = $this->userFactory->newFromName( $data['Target'] );
256 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
257 $htmlForm = HTMLForm::factory( 'ooui', $this->getFormFields( $target ), $this->getContext() );
258 $htmlForm
259 ->setTitle( $this->getPageTitle() ) // Remove subpage
260 ->addPreHtml( $this->msg( 'emailpagetext', $target->getName() )->parse() )
261 ->setSubmitTextMsg( 'emailsend' )
262 ->setSubmitCallback( [ $this, 'onFormSubmit' ] )
263 ->setWrapperLegendMsg( 'email-legend' )
264 ->prepareForm();
265
266 if ( !$this->getHookRunner()->onEmailUserForm( $htmlForm ) ) {
267 return false;
268 }
269
270 $result = $htmlForm->show();
271
272 if ( $result === true || ( $result instanceof Status && $result->isGood() ) ) {
273 $out->setPageTitleMsg( $this->msg( 'emailsent' ) );
274 $out->addWikiMsg( 'emailsenttext', $target->getName() );
275 $out->returnToMain( false, $target->getUserPage() );
276 } else {
277 $out->setPageTitleMsg( $this->msg( 'emailuser-title-target', $target->getName() ) );
278 }
279 return true;
280 }
281
287 public function onFormSubmit( array $data ) {
288 // HTMLForm checked that this is a valid user name, the return value can never be null.
289 $target = $this->userFactory->newFromName( $data['Target'] );
290
291 $emailUser = $this->emailUserFactory->newEmailUser( $this->getAuthority() );
292 $emailUser->setEditToken( $this->getRequest()->getVal( 'wpEditToken' ) );
293
294 // Fully authorize on sending emails.
295 $status = $emailUser->authorizeSend();
296
297 if ( !$status->isOK() ) {
298 return $status;
299 }
300
301 // @phan-suppress-next-next-line PhanTypeMismatchArgumentNullable
302 $res = $emailUser->sendEmailUnsafe(
303 $target,
304 $data['Subject'],
305 $data['Text'],
306 $data['CCMe'],
307 $this->getLanguage()->getCode()
308 );
309 if ( $res->hasMessage( 'hookaborted' ) ) {
310 // BC: The method could previously return false if the EmailUser hook set the error to false. Preserve
311 // that behaviour until we replace the hook.
312 $res = false;
313 } else {
314 $res = Status::wrap( $res );
315 }
316 return $res;
317 }
318
327 public function prefixSearchSubpages( $search, $limit, $offset ) {
328 $search = $this->userNameUtils->getCanonical( $search );
329 if ( !$search ) {
330 // No prefix suggestion for invalid user
331 return [];
332 }
333 // Autocomplete subpage as user list - public to allow caching
334 return $this->userNamePrefixSearch
335 ->search( UserNamePrefixSearch::AUDIENCE_PUBLIC, $search, $limit, $offset );
336 }
337
341 public function isListed() {
342 return $this->getConfig()->get( MainConfigNames::EnableUserEmail );
343 }
344
345 protected function getGroupName() {
346 return 'users';
347 }
348}
349
351class_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.
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 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.
__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.
Creates User objects.
Handles searching prefixes of user names.
UserNameUtils service.
internal since 1.36
Definition User.php:94
getUserPage()
Get this user's personal page title.
Definition User.php:2722
getName()
Get the user name, or the IP of an anonymous user.
Definition User.php:1564
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.