MediaWiki master
SpecialEmailUser.php
Go to the documentation of this file.
1<?php
7namespace MediaWiki\Specials;
8
22use StatusValue;
23
33
34 public function __construct(
35 private readonly UserNameUtils $userNameUtils,
36 private readonly UserNamePrefixSearch $userNamePrefixSearch,
37 private readonly UserOptionsLookup $userOptionsLookup,
38 private readonly EmailUserFactory $emailUserFactory,
39 private readonly UserFactory $userFactory,
40 ) {
41 parent::__construct( 'Emailuser' );
42 }
43
45 public function doesWrites() {
46 return true;
47 }
48
50 public function getDescription() {
51 return $this->msg( 'emailuser-title-notarget' );
52 }
53
54 protected function getFormFields( User $target ): array {
55 $linkRenderer = $this->getLinkRenderer();
56 $user = $this->getUser();
57 return [
58 'From' => [
59 'type' => 'info',
60 'raw' => 1,
61 'default' => $linkRenderer->makeLink(
62 $user->getUserPage(),
63 $user->getName()
64 ),
65 'label-message' => 'emailfrom',
66 'id' => 'mw-emailuser-sender',
67 ],
68 'To' => [
69 'type' => 'info',
70 'raw' => 1,
71 'default' => $linkRenderer->makeLink(
72 $target->getUserPage(),
73 $target->getName()
74 ),
75 'label-message' => 'emailto',
76 'id' => 'mw-emailuser-recipient',
77 ],
78 'Target' => [
79 'type' => 'hidden',
80 'default' => $target->getName(),
81 ],
82 'Subject' => [
83 'type' => 'text',
84 'default' => $this->msg( 'defemailsubject', $user->getName() )->inContentLanguage()->text(),
85 'label-message' => 'emailsubject',
86 'maxlength' => 200,
87 'size' => 60,
88 'required' => true,
89 ],
90 'Text' => [
91 'type' => 'textarea',
92 'rows' => 20,
93 'label-message' => 'emailmessage',
94 'required' => true,
95 ],
96 'CCMe' => [
97 'type' => 'check',
98 'label-message' => 'emailccme',
99 'default' => $this->userOptionsLookup->getBoolOption( $user, 'ccmeonemails' ),
100 ],
101 ];
102 }
103
107 private function handleCanSendEmailStatus( StatusValue $status ): void {
108 if ( !$status->isGood() ) {
109 if ( $status instanceof PermissionStatus ) {
110 $status->throwErrorPageError();
111 } elseif ( $status->hasMessage( 'mailnologin' ) ) {
112 throw new ErrorPageError( 'mailnologin', 'mailnologintext' );
113 } elseif ( $status->hasMessage( 'usermaildisabled' ) ) {
114 throw new ErrorPageError( 'usermaildisabled', 'usermaildisabledtext' );
115 } elseif ( $status->getValue() !== null ) {
116 // BC for deprecated hook errors
117 // (to be removed when UserCanSendEmail and EmailUserPermissionsErrors are removed)
118 $msg = $status->getMessages()[0];
119 throw new ErrorPageError( $status->getValue(), $msg );
120 } else {
121 // Fallback in case new error types are added in EmailUser
122 throw new ErrorPageError( $this->getDescription(), Status::wrap( $status )->getMessage() );
123 }
124 }
125 }
126
128 public function execute( $par ) {
129 $this->setHeaders();
130 $this->outputHeader();
131
132 $out = $this->getOutput();
133 $request = $this->getRequest();
134 $out->addModuleStyles( 'mediawiki.special' );
135
136 // Check if the user can send emails without authorizing the action.
137 $emailUser = $this->emailUserFactory->newEmailUserBC(
138 $this->getUser(),
139 $this->getConfig()
140 );
141 $emailUser->setEditToken( (string)$request->getVal( 'wpEditToken' ) );
142 $status = $emailUser->canSend();
143
144 // If user emailing is disabled, then prioritise this error over anything else (including the
145 // ::requireNamedUser check). This is because a user would still be unable access the form if they were to
146 // log in or create account.
147 if ( !$status->isGood() && $status->hasMessage( 'usermaildisabled' ) ) {
148 $this->handleCanSendEmailStatus( $status );
149 }
150
151 // If the user is not logged into a named account, then display this error. This should redirect to
152 // Special:UserLogin or Special:CreateAccount to not interrupt the flow.
153 $this->requireNamedUser( 'mailnologintext', 'mailnologin' );
154
155 $this->handleCanSendEmailStatus( $status );
156
157 // Always go through the userform, it will do validations on the target
158 // and display the emailform for us.
159 $target = $par ?? $request->getVal( 'wpTarget', $request->getVal( 'target', '' ) );
160 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable Defaults to empty string
161 $this->userForm( $target );
162 }
163
173 public static function getTarget( $target, User $sender ) {
174 wfDeprecated( __METHOD__, '1.47' );
175
176 $targetObject = MediaWikiServices::getInstance()->getUserFactory()->newFromName( $target );
177 if ( !$targetObject instanceof User ) {
178 return 'notarget';
179 }
180
181 $status = MediaWikiServices::getInstance()->getEmailUserFactory()
182 ->newEmailUser( $sender )
183 ->validateTarget( $targetObject );
184 if ( !$status->isGood() ) {
185 $msg = $status->getMessages()[0]->getKey();
186 $ret = $msg === 'emailnotarget' ? 'notarget' : preg_replace( '/text$/', '', $msg );
187 } else {
188 $ret = $targetObject;
189 }
190 return $ret;
191 }
192
198 protected function userForm( $name ) {
199 $htmlForm = HTMLForm::factory( 'ooui', [
200 'Target' => [
201 'type' => 'user',
202 'exists' => true,
203 'required' => true,
204 'label-message' => 'emailusername',
205 'id' => 'emailusertarget',
206 'autofocus' => true,
207 // Exclude temporary accounts from the autocomplete, as they cannot have email addresses.
208 'excludetemp' => true,
209 // Skip validation when visit directly without subpage (T347854)
210 'default' => '',
211 // Prefill for subpage syntax and old target param.
212 'filter-callback' => static function ( $value ) use ( $name ) {
213 return str_replace( '_', ' ',
214 ( $value !== '' && $value !== false && $value !== null ) ? $value : $name );
215 },
216 'validation-callback' => function ( $value ) {
217 // HTMLForm checked that this is a valid user name
218 $target = $this->userFactory->newFromName( $value );
219 $statusValue = $this->emailUserFactory
220 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
221 ->newEmailUser( $this->getUser() )->validateTarget( $target );
222 if ( !$statusValue->isGood() ) {
223 // TODO: Return Status instead of StatusValue from validateTarget() method?
224 return Status::wrap( $statusValue )->getMessage();
225 }
226 return true;
227 }
228 ]
229 ], $this->getContext() );
230
231 $htmlForm
232 ->setMethod( 'GET' )
233 ->setTitle( $this->getPageTitle() ) // Remove subpage
234 ->setSubmitCallback( $this->sendEmailForm( ... ) )
235 ->setId( 'askusername' )
236 ->setWrapperLegendMsg( 'emailtarget' )
237 ->setSubmitTextMsg( 'emailusernamesubmit' )
238 ->show();
239 }
240
245 private function sendEmailForm( array $data ) {
246 $out = $this->getOutput();
247
248 // HTMLForm checked that this is a valid user name, the return value can never be null.
249 $target = $this->userFactory->newFromName( $data['Target'] );
250 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
251 $htmlForm = HTMLForm::factory( 'ooui', $this->getFormFields( $target ), $this->getContext() );
252 $htmlForm
253 ->setTitle( $this->getPageTitle() ) // Remove subpage
254 ->addPreHtml( $this->msg( 'emailpagetext', $target->getName() )->parse() )
255 ->setSubmitTextMsg( 'emailsend' )
256 ->setSubmitCallback( $this->onFormSubmit( ... ) )
257 ->setWrapperLegendMsg( 'email-legend' )
258 ->prepareForm();
259
260 if ( !$this->getHookRunner()->onEmailUserForm( $htmlForm ) ) {
261 return false;
262 }
263
264 $result = $htmlForm->show();
265
266 if ( $result === true || ( $result instanceof Status && $result->isGood() ) ) {
267 $out->setPageTitleMsg( $this->msg( 'emailsent' ) );
268 $out->addWikiMsg( 'emailsenttext', $target->getName() );
269 $out->returnToMain( false, $target->getUserPage() );
270 } else {
271 $out->setPageTitleMsg( $this->msg( 'emailuser-title-target', $target->getName() ) );
272 }
273 return true;
274 }
275
280 private function onFormSubmit( array $data ) {
281 // HTMLForm checked that this is a valid user name, the return value can never be null.
282 $target = $this->userFactory->newFromName( $data['Target'] );
283
284 $emailUser = $this->emailUserFactory->newEmailUser( $this->getAuthority() );
285 $emailUser->setEditToken( $this->getRequest()->getVal( 'wpEditToken' ) );
286
287 // Fully authorize on sending emails.
288 $status = $emailUser->authorizeSend();
289
290 if ( !$status->isOK() ) {
291 return $status;
292 }
293
294 // @phan-suppress-next-next-line PhanTypeMismatchArgumentNullable
295 $res = $emailUser->sendEmailUnsafe(
296 $target,
297 $data['Subject'],
298 $data['Text'],
299 $data['CCMe'],
300 $this->getLanguage()->getCode()
301 );
302 if ( $res->hasMessage( 'hookaborted' ) ) {
303 // BC: The method could previously return false if the EmailUser hook set the error to false. Preserve
304 // that behaviour until we replace the hook.
305 $res = false;
306 } else {
307 $res = Status::wrap( $res );
308 }
309 return $res;
310 }
311
320 public function prefixSearchSubpages( $search, $limit, $offset ) {
321 $search = $this->userNameUtils->getCanonical( $search );
322 if ( !$search ) {
323 // No prefix suggestion for invalid user
324 return [];
325 }
326 // Autocomplete subpage as user list - public to allow caching
327 return $this->userNamePrefixSearch
328 ->search( UserNamePrefixSearch::AUDIENCE_PUBLIC, $search, $limit, $offset );
329 }
330
334 public function isListed() {
335 return $this->getConfig()->get( MainConfigNames::EnableUserEmail );
336 }
337
339 protected function getGroupName() {
340 return 'users';
341 }
342}
343
344// @codeCoverageIgnoreStart
346class_alias( SpecialEmailUser::class, 'SpecialEmailUser' );
347// @codeCoverageIgnoreEnd
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:71
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:214
Factory for EmailUser objects.
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
A StatusValue for permission errors.
Parent class for all special pages.
getUser()
Shortcut to get the User executing this instance.
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
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.This must be overridden by subclasses; it will be made...
__construct(private readonly UserNameUtils $userNameUtils, private readonly UserNamePrefixSearch $userNamePrefixSearch, private readonly UserOptionsLookup $userOptionsLookup, private readonly EmailUserFactory $emailUserFactory, private readonly UserFactory $userFactory,)
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:44
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:129
getUserPage()
Get this user's personal page title.
Definition User.php:2696
getName()
Get the user name, or the IP of an anonymous user.
Definition User.php:1511
static newFromName( $name, $validate='valid')
Definition User.php:610
Generic operation result class Has warning/error list, boolean status and arbitrary value.
getMessages(?string $type=null)
Returns a list of error messages, optionally only those of the given type.
hasMessage(string $message)
Returns true if the specified message is present as a warning or error.
isOK()
Returns whether the operation completed.
isGood()
Returns whether the operation completed and didn't have any error or warnings.