Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
41.55% |
214 / 515 |
|
8.33% |
2 / 24 |
CRAP | |
0.00% |
0 / 1 |
SpecialBlock | |
41.63% |
214 / 514 |
|
8.33% |
2 / 24 |
2744.48 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
doesWrites | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
checkExecutePermissions | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
requiresUnblock | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setParameter | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 | |||
alterForm | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
20 | |||
getDisplayFormat | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
getFormFields | |
79.44% |
143 / 180 |
|
0.00% |
0 / 1 |
12.05 | |||
maybeAlterFormDefaults | |
79.03% |
49 / 62 |
|
0.00% |
0 / 1 |
36.75 | |||
preHtml | |
0.00% |
0 / 27 |
|
0.00% |
0 / 1 |
30 | |||
postHtml | |
0.00% |
0 / 73 |
|
0.00% |
0 / 1 |
42 | |||
getTargetUserTitle | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
20 | |||
getTargetAndType | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getTargetAndTypeInternal | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
4 | |||
processForm | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
processFormInternal | |
0.00% |
0 / 76 |
|
0.00% |
0 / 1 |
992 | |||
getSuggestedDurations | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
parseExpiryInput | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
canBlockEmail | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
onSubmit | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
onSuccess | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
prefixSearchSubpages | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
getGroupName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * Implements Special:Block |
4 | * |
5 | * This program is free software; you can redistribute it and/or modify |
6 | * it under the terms of the GNU General Public License as published by |
7 | * the Free Software Foundation; either version 2 of the License, or |
8 | * (at your option) any later version. |
9 | * |
10 | * This program is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | * GNU General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU General Public License along |
16 | * with this program; if not, write to the Free Software Foundation, Inc., |
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
18 | * http://www.gnu.org/copyleft/gpl.html |
19 | * |
20 | * @file |
21 | * @ingroup SpecialPage |
22 | */ |
23 | |
24 | namespace MediaWiki\Specials; |
25 | |
26 | use ErrorPageError; |
27 | use HtmlArmor; |
28 | use Language; |
29 | use LogEventsList; |
30 | use MediaWiki\Block\BlockActionInfo; |
31 | use MediaWiki\Block\BlockPermissionCheckerFactory; |
32 | use MediaWiki\Block\BlockUser; |
33 | use MediaWiki\Block\BlockUserFactory; |
34 | use MediaWiki\Block\BlockUtils; |
35 | use MediaWiki\Block\DatabaseBlock; |
36 | use MediaWiki\Block\DatabaseBlockStore; |
37 | use MediaWiki\Block\Restriction\ActionRestriction; |
38 | use MediaWiki\Block\Restriction\NamespaceRestriction; |
39 | use MediaWiki\Block\Restriction\PageRestriction; |
40 | use MediaWiki\CommentStore\CommentStore; |
41 | use MediaWiki\Context\IContextSource; |
42 | use MediaWiki\Html\Html; |
43 | use MediaWiki\HTMLForm\HTMLForm; |
44 | use MediaWiki\MainConfigNames; |
45 | use MediaWiki\MediaWikiServices; |
46 | use MediaWiki\Message\Message; |
47 | use MediaWiki\Page\PageReference; |
48 | use MediaWiki\Page\PageReferenceValue; |
49 | use MediaWiki\Permissions\Authority; |
50 | use MediaWiki\Request\WebRequest; |
51 | use MediaWiki\SpecialPage\FormSpecialPage; |
52 | use MediaWiki\SpecialPage\SpecialPage; |
53 | use MediaWiki\Status\Status; |
54 | use MediaWiki\Title\NamespaceInfo; |
55 | use MediaWiki\Title\Title; |
56 | use MediaWiki\Title\TitleFormatter; |
57 | use MediaWiki\User\User; |
58 | use MediaWiki\User\UserIdentity; |
59 | use MediaWiki\User\UserNamePrefixSearch; |
60 | use MediaWiki\User\UserNameUtils; |
61 | use OOUI\FieldLayout; |
62 | use OOUI\HtmlSnippet; |
63 | use OOUI\LabelWidget; |
64 | use OOUI\Widget; |
65 | use Wikimedia\IPUtils; |
66 | |
67 | /** |
68 | * A special page that allows users with 'block' right to block users from |
69 | * editing pages and other actions |
70 | * |
71 | * @ingroup SpecialPage |
72 | */ |
73 | class SpecialBlock extends FormSpecialPage { |
74 | |
75 | private BlockUtils $blockUtils; |
76 | private BlockPermissionCheckerFactory $blockPermissionCheckerFactory; |
77 | private BlockUserFactory $blockUserFactory; |
78 | private DatabaseBlockStore $blockStore; |
79 | private UserNameUtils $userNameUtils; |
80 | private UserNamePrefixSearch $userNamePrefixSearch; |
81 | private BlockActionInfo $blockActionInfo; |
82 | private TitleFormatter $titleFormatter; |
83 | |
84 | /** @var UserIdentity|string|null User to be blocked, as passed either by parameter |
85 | * (url?wpTarget=Foo) or as subpage (Special:Block/Foo) |
86 | */ |
87 | protected $target; |
88 | |
89 | /** @var int DatabaseBlock::TYPE_ constant */ |
90 | protected $type; |
91 | |
92 | /** @var User|string The previous block target */ |
93 | protected $previousTarget; |
94 | |
95 | /** @var bool Whether the previous submission of the form asked for HideUser */ |
96 | protected $requestedHideUser; |
97 | |
98 | /** @var bool */ |
99 | protected $alreadyBlocked; |
100 | |
101 | /** |
102 | * @var array[] |
103 | * @phan-var non-empty-array[] |
104 | */ |
105 | protected $preErrors = []; |
106 | |
107 | /** |
108 | * @var array <mixed,mixed> An associative array used to pass vars to Codex form |
109 | */ |
110 | protected $codexFormData = []; |
111 | |
112 | private NamespaceInfo $namespaceInfo; |
113 | |
114 | /** |
115 | * @param BlockUtils $blockUtils |
116 | * @param BlockPermissionCheckerFactory $blockPermissionCheckerFactory |
117 | * @param BlockUserFactory $blockUserFactory |
118 | * @param DatabaseBlockStore $blockStore |
119 | * @param UserNameUtils $userNameUtils |
120 | * @param UserNamePrefixSearch $userNamePrefixSearch |
121 | * @param BlockActionInfo $blockActionInfo |
122 | * @param TitleFormatter $titleFormatter |
123 | * @param NamespaceInfo $namespaceInfo |
124 | */ |
125 | public function __construct( |
126 | BlockUtils $blockUtils, |
127 | BlockPermissionCheckerFactory $blockPermissionCheckerFactory, |
128 | BlockUserFactory $blockUserFactory, |
129 | DatabaseBlockStore $blockStore, |
130 | UserNameUtils $userNameUtils, |
131 | UserNamePrefixSearch $userNamePrefixSearch, |
132 | BlockActionInfo $blockActionInfo, |
133 | TitleFormatter $titleFormatter, |
134 | NamespaceInfo $namespaceInfo |
135 | ) { |
136 | parent::__construct( 'Block', 'block' ); |
137 | |
138 | $this->blockUtils = $blockUtils; |
139 | $this->blockPermissionCheckerFactory = $blockPermissionCheckerFactory; |
140 | $this->blockUserFactory = $blockUserFactory; |
141 | $this->blockStore = $blockStore; |
142 | $this->userNameUtils = $userNameUtils; |
143 | $this->userNamePrefixSearch = $userNamePrefixSearch; |
144 | $this->blockActionInfo = $blockActionInfo; |
145 | $this->titleFormatter = $titleFormatter; |
146 | $this->namespaceInfo = $namespaceInfo; |
147 | } |
148 | |
149 | public function execute( $par ) { |
150 | parent::execute( $par ); |
151 | |
152 | if ( $this->getConfig()->get( 'UseCodexSpecialBlock' ) ) { |
153 | $this->getOutput()->addModules( 'mediawiki.special.block.codex' ); |
154 | $this->getOutput()->addJsConfigVars( $this->codexFormData ); |
155 | } |
156 | } |
157 | |
158 | public function doesWrites() { |
159 | return true; |
160 | } |
161 | |
162 | /** |
163 | * Check that the user can unblock themselves if they are trying to do so |
164 | * |
165 | * @param User $user |
166 | * @throws ErrorPageError |
167 | */ |
168 | protected function checkExecutePermissions( User $user ) { |
169 | parent::checkExecutePermissions( $user ); |
170 | // T17810: blocked admins should have limited access here |
171 | $status = $this->blockPermissionCheckerFactory |
172 | ->newBlockPermissionChecker( $this->target, $user ) |
173 | ->checkBlockPermissions(); |
174 | if ( $status !== true ) { |
175 | throw new ErrorPageError( 'badaccess', $status ); |
176 | } |
177 | } |
178 | |
179 | /** |
180 | * We allow certain special cases where user is blocked |
181 | * |
182 | * @return bool |
183 | */ |
184 | public function requiresUnblock() { |
185 | return false; |
186 | } |
187 | |
188 | /** |
189 | * Handle some magic here |
190 | * |
191 | * @param string $par |
192 | */ |
193 | protected function setParameter( $par ) { |
194 | // Extract variables from the request. Try not to get into a situation where we |
195 | // need to extract *every* variable from the form just for processing here, but |
196 | // there are legitimate uses for some variables |
197 | $request = $this->getRequest(); |
198 | [ $this->target, $this->type ] = self::getTargetAndTypeInternal( $par, $request ); |
199 | if ( $this->target instanceof UserIdentity ) { |
200 | // Set the 'relevant user' in the skin, so it displays links like Contributions, |
201 | // User logs, UserRights, etc. |
202 | $this->getSkin()->setRelevantUser( $this->target ); |
203 | } |
204 | |
205 | [ $this->previousTarget, /*...*/ ] = $this->blockUtils |
206 | ->parseBlockTarget( $request->getVal( 'wpPreviousTarget' ) ); |
207 | $this->requestedHideUser = $request->getBool( 'wpHideUser' ); |
208 | } |
209 | |
210 | /** |
211 | * Customizes the HTMLForm a bit |
212 | * |
213 | * @param HTMLForm $form |
214 | */ |
215 | protected function alterForm( HTMLForm $form ) { |
216 | $form->setHeaderHtml( '' ); |
217 | $form->setSubmitDestructive(); |
218 | |
219 | $msg = $this->alreadyBlocked ? 'ipb-change-block' : 'ipbsubmit'; |
220 | $form->setSubmitTextMsg( $msg ); |
221 | |
222 | $this->addHelpLink( 'Help:Blocking users' ); |
223 | |
224 | // Don't need to do anything if the form has been posted |
225 | if ( !$this->getRequest()->wasPosted() && $this->preErrors ) { |
226 | // Mimic error messages normally generated by the form |
227 | $form->addHeaderHtml( (string)new FieldLayout( |
228 | new Widget( [] ), |
229 | [ |
230 | 'align' => 'top', |
231 | 'errors' => array_map( function ( $errMsg ) { |
232 | // @phan-suppress-next-line PhanParamTooFewUnpack Should infer non-emptiness |
233 | return new HtmlSnippet( $this->msg( ...$errMsg )->parse() ); |
234 | }, $this->preErrors ), |
235 | ] |
236 | ) ); |
237 | } |
238 | } |
239 | |
240 | protected function getDisplayFormat() { |
241 | return $this->getConfig()->get( 'UseCodexSpecialBlock' ) ? 'codex' : 'ooui'; |
242 | } |
243 | |
244 | /** |
245 | * Get the HTMLForm descriptor array for the block form |
246 | * @return array |
247 | */ |
248 | protected function getFormFields() { |
249 | $conf = $this->getConfig(); |
250 | $blockAllowsUTEdit = $conf->get( MainConfigNames::BlockAllowsUTEdit ); |
251 | |
252 | $this->getOutput()->enableOOUI(); |
253 | |
254 | $user = $this->getUser(); |
255 | |
256 | $suggestedDurations = $this->getLanguage()->getBlockDurations(); |
257 | |
258 | $a = []; |
259 | |
260 | $a['Target'] = [ |
261 | 'type' => 'user', |
262 | 'ipallowed' => true, |
263 | 'iprange' => true, |
264 | 'id' => 'mw-bi-target', |
265 | 'size' => '45', |
266 | 'autofocus' => true, |
267 | 'required' => true, |
268 | 'placeholder' => $this->msg( 'block-target-placeholder' )->text(), |
269 | 'validation-callback' => function ( $value, $alldata, $form ) { |
270 | $status = $this->blockUtils->validateTarget( $value ); |
271 | if ( !$status->isOK() ) { |
272 | $errors = $status->getErrorsArray(); |
273 | |
274 | return $form->msg( ...$errors[0] ); |
275 | } |
276 | return true; |
277 | }, |
278 | 'section' => 'target', |
279 | ]; |
280 | |
281 | $editingRestrictionOptions = $this->getConfig()->get( 'UseCodexSpecialBlock' ) ? |
282 | // If we're using Codex, use the option-descriptions feature, which is only supported by Codex |
283 | [ |
284 | 'options-messages' => [ |
285 | 'ipb-sitewide' => 'sitewide', |
286 | 'ipb-partial' => 'partial' |
287 | ], |
288 | 'option-descriptions-messages' => [ |
289 | 'sitewide' => 'ipb-sitewide-help', |
290 | 'partial' => 'ipb-partial-help' |
291 | ], |
292 | 'option-descriptions-messages-parse' => true, |
293 | ] : |
294 | // Otherwise, if we're using OOUI, add the options' descriptions as part of their labels |
295 | [ |
296 | 'options' => [ |
297 | $this->msg( 'ipb-sitewide' )->escaped() . |
298 | new LabelWidget( [ |
299 | 'classes' => [ 'oo-ui-inline-help' ], |
300 | 'label' => new HtmlSnippet( $this->msg( 'ipb-sitewide-help' )->parse() ), |
301 | ] ) => 'sitewide', |
302 | $this->msg( 'ipb-partial' )->escaped() . |
303 | new LabelWidget( [ |
304 | 'classes' => [ 'oo-ui-inline-help' ], |
305 | 'label' => new HtmlSnippet( $this->msg( 'ipb-partial-help' )->parse() ), |
306 | ] ) => 'partial', |
307 | ] |
308 | ]; |
309 | |
310 | $a['EditingRestriction'] = [ |
311 | 'type' => 'radio', |
312 | 'cssclass' => 'mw-block-editing-restriction', |
313 | 'default' => 'sitewide', |
314 | 'section' => 'actions', |
315 | ] + $editingRestrictionOptions; |
316 | |
317 | $a['PageRestrictions'] = [ |
318 | 'type' => 'titlesmultiselect', |
319 | 'label' => $this->msg( 'ipb-pages-label' )->text(), |
320 | 'exists' => true, |
321 | 'max' => 10, |
322 | 'cssclass' => 'mw-htmlform-checkradio-indent mw-block-partial-restriction', |
323 | 'default' => '', |
324 | 'showMissing' => false, |
325 | 'excludeDynamicNamespaces' => true, |
326 | 'input' => [ |
327 | 'autocomplete' => false |
328 | ], |
329 | 'section' => 'actions', |
330 | ]; |
331 | |
332 | $a['NamespaceRestrictions'] = [ |
333 | 'type' => 'namespacesmultiselect', |
334 | 'label' => $this->msg( 'ipb-namespaces-label' )->text(), |
335 | 'exists' => true, |
336 | 'cssclass' => 'mw-htmlform-checkradio-indent mw-block-partial-restriction', |
337 | 'default' => '', |
338 | 'input' => [ |
339 | 'autocomplete' => false |
340 | ], |
341 | 'section' => 'actions', |
342 | ]; |
343 | |
344 | if ( $conf->get( MainConfigNames::EnablePartialActionBlocks ) ) { |
345 | $blockActions = $this->blockActionInfo->getAllBlockActions(); |
346 | $a['ActionRestrictions'] = [ |
347 | 'type' => 'multiselect', |
348 | 'cssclass' => 'mw-htmlform-checkradio-indent mw-block-partial-restriction mw-block-action-restriction', |
349 | 'options-messages' => array_combine( |
350 | array_map( static function ( $action ) { |
351 | return "ipb-action-$action"; |
352 | }, array_keys( $blockActions ) ), |
353 | $blockActions |
354 | ), |
355 | 'section' => 'actions', |
356 | ]; |
357 | } |
358 | |
359 | $a['CreateAccount'] = [ |
360 | 'type' => 'check', |
361 | 'cssclass' => 'mw-block-restriction', |
362 | 'label-message' => 'ipbcreateaccount', |
363 | 'default' => true, |
364 | 'section' => 'details', |
365 | ]; |
366 | |
367 | if ( $this->blockPermissionCheckerFactory |
368 | ->newBlockPermissionChecker( null, $user ) |
369 | ->checkEmailPermissions() |
370 | ) { |
371 | $a['DisableEmail'] = [ |
372 | 'type' => 'check', |
373 | 'cssclass' => 'mw-block-restriction', |
374 | 'label-message' => 'ipbemailban', |
375 | 'section' => 'details', |
376 | ]; |
377 | |
378 | $this->codexFormData[ 'blockAllowsEmailBan'] = true; |
379 | } |
380 | |
381 | if ( $blockAllowsUTEdit ) { |
382 | $a['DisableUTEdit'] = [ |
383 | 'type' => 'check', |
384 | 'cssclass' => 'mw-block-restriction', |
385 | 'label-message' => 'ipb-disableusertalk', |
386 | 'default' => false, |
387 | 'section' => 'details', |
388 | ]; |
389 | |
390 | $this->codexFormData[ 'blockAllowsUTEdit'] = true; |
391 | } |
392 | |
393 | $defaultExpiry = $this->msg( 'ipb-default-expiry' )->inContentLanguage(); |
394 | if ( $this->type === DatabaseBlock::TYPE_RANGE || $this->type === DatabaseBlock::TYPE_IP ) { |
395 | $defaultExpiryIP = $this->msg( 'ipb-default-expiry-ip' )->inContentLanguage(); |
396 | if ( !$defaultExpiryIP->isDisabled() ) { |
397 | $defaultExpiry = $defaultExpiryIP; |
398 | } |
399 | } |
400 | |
401 | $a['Expiry'] = [ |
402 | 'type' => 'expiry', |
403 | 'required' => true, |
404 | 'options' => $suggestedDurations, |
405 | 'default' => $defaultExpiry->text(), |
406 | 'section' => 'expiry', |
407 | ]; |
408 | |
409 | $a['Reason'] = [ |
410 | 'type' => 'selectandother', |
411 | // HTML maxlength uses "UTF-16 code units", which means that characters outside BMP |
412 | // (e.g. emojis) count for two each. This limit is overridden in JS to instead count |
413 | // Unicode codepoints. |
414 | 'maxlength' => CommentStore::COMMENT_CHARACTER_LIMIT, |
415 | 'maxlength-unit' => 'codepoints', |
416 | 'options-message' => 'ipbreason-dropdown', |
417 | 'section' => 'reason', |
418 | ]; |
419 | |
420 | $a['AutoBlock'] = [ |
421 | 'type' => 'check', |
422 | 'label-message' => [ |
423 | 'ipbenableautoblock', |
424 | Message::durationParam( $conf->get( MainConfigNames::AutoblockExpiry ) ) |
425 | ], |
426 | 'default' => true, |
427 | 'section' => 'options', |
428 | ]; |
429 | |
430 | // Allow some users to hide name from block log, blocklist and listusers |
431 | if ( $this->getAuthority()->isAllowed( 'hideuser' ) ) { |
432 | $a['HideUser'] = [ |
433 | 'type' => 'check', |
434 | 'label-message' => 'ipbhidename', |
435 | 'cssclass' => 'mw-block-hideuser', |
436 | 'section' => 'options', |
437 | ]; |
438 | } |
439 | |
440 | // Watchlist their user page? (Only if user is logged in) |
441 | if ( $user->isRegistered() ) { |
442 | $a['Watch'] = [ |
443 | 'type' => 'check', |
444 | 'label-message' => 'ipbwatchuser', |
445 | 'section' => 'options', |
446 | ]; |
447 | } |
448 | |
449 | $a['HardBlock'] = [ |
450 | 'type' => 'check', |
451 | 'label-message' => 'ipb-hardblock', |
452 | 'default' => false, |
453 | 'section' => 'options', |
454 | ]; |
455 | |
456 | // This is basically a copy of the Target field, but the user can't change it, so we |
457 | // can see if the warnings we maybe showed to the user before still apply |
458 | $a['PreviousTarget'] = [ |
459 | 'type' => 'hidden', |
460 | 'default' => false, |
461 | ]; |
462 | |
463 | // We'll turn this into a checkbox if we need to |
464 | $a['Confirm'] = [ |
465 | 'type' => 'hidden', |
466 | 'default' => '', |
467 | 'label-message' => 'ipb-confirm', |
468 | 'cssclass' => 'mw-block-confirm', |
469 | ]; |
470 | |
471 | $this->maybeAlterFormDefaults( $a ); |
472 | |
473 | // Allow extensions to add more fields |
474 | $this->getHookRunner()->onSpecialBlockModifyFormFields( $this, $a ); |
475 | |
476 | return $a; |
477 | } |
478 | |
479 | /** |
480 | * If the user has already been blocked with similar settings, load that block |
481 | * and change the defaults for the form fields to match the existing settings. |
482 | * @param array &$fields HTMLForm descriptor array |
483 | */ |
484 | protected function maybeAlterFormDefaults( &$fields ) { |
485 | // This will be overwritten by request data |
486 | $fields['Target']['default'] = (string)$this->target; |
487 | |
488 | if ( $this->target ) { |
489 | $status = $this->blockUtils->validateTarget( $this->target ); |
490 | if ( !$status->isOK() ) { |
491 | $errors = $status->getErrorsArray(); |
492 | $this->preErrors = array_merge( $this->preErrors, $errors ); |
493 | } |
494 | } |
495 | |
496 | // This won't be |
497 | $fields['PreviousTarget']['default'] = (string)$this->target; |
498 | |
499 | $block = $this->blockStore->newFromTarget( $this->target ); |
500 | |
501 | // Populate fields if there is a block that is not an autoblock; if it is a range |
502 | // block, only populate the fields if the range is the same as $this->target |
503 | if ( $block instanceof DatabaseBlock && $block->getType() !== DatabaseBlock::TYPE_AUTO |
504 | && ( $this->type != DatabaseBlock::TYPE_RANGE |
505 | || ( $this->target && $block->isBlocking( $this->target ) ) ) |
506 | ) { |
507 | $fields['HardBlock']['default'] = $block->isHardblock(); |
508 | $fields['CreateAccount']['default'] = $block->isCreateAccountBlocked(); |
509 | $fields['AutoBlock']['default'] = $block->isAutoblocking(); |
510 | |
511 | if ( isset( $fields['DisableEmail'] ) ) { |
512 | $fields['DisableEmail']['default'] = $block->isEmailBlocked(); |
513 | } |
514 | |
515 | if ( isset( $fields['HideUser'] ) ) { |
516 | $fields['HideUser']['default'] = $block->getHideName(); |
517 | } |
518 | |
519 | if ( isset( $fields['DisableUTEdit'] ) ) { |
520 | $fields['DisableUTEdit']['default'] = !$block->isUsertalkEditAllowed(); |
521 | } |
522 | |
523 | // If the username was hidden (bl_deleted == 1), don't show the reason |
524 | // unless this user also has rights to hideuser: T37839 |
525 | if ( !$block->getHideName() || $this->getAuthority()->isAllowed( 'hideuser' ) ) { |
526 | $fields['Reason']['default'] = $block->getReasonComment()->text; |
527 | } else { |
528 | $fields['Reason']['default'] = ''; |
529 | } |
530 | |
531 | if ( $this->getRequest()->wasPosted() ) { |
532 | // Ok, so we got a POST submission asking us to reblock a user. So show the |
533 | // confirm checkbox; the user will only see it if they haven't previously |
534 | $fields['Confirm']['type'] = 'check'; |
535 | } else { |
536 | // We got a target, but it wasn't a POST request, so the user must have gone |
537 | // to a link like [[Special:Block/User]]. We don't need to show the checkbox |
538 | // as long as they go ahead and block *that* user |
539 | $fields['Confirm']['default'] = 1; |
540 | } |
541 | |
542 | if ( $block->getExpiry() == 'infinity' ) { |
543 | $fields['Expiry']['default'] = 'infinite'; |
544 | } else { |
545 | $fields['Expiry']['default'] = wfTimestamp( TS_RFC2822, $block->getExpiry() ); |
546 | } |
547 | |
548 | if ( !$block->isSitewide() ) { |
549 | $fields['EditingRestriction']['default'] = 'partial'; |
550 | |
551 | $pageRestrictions = []; |
552 | $namespaceRestrictions = []; |
553 | foreach ( $block->getRestrictions() as $restriction ) { |
554 | if ( $restriction instanceof PageRestriction && $restriction->getTitle() ) { |
555 | $pageRestrictions[] = $restriction->getTitle()->getPrefixedText(); |
556 | } elseif ( $restriction instanceof NamespaceRestriction && |
557 | $this->namespaceInfo->exists( $restriction->getValue() ) |
558 | ) { |
559 | $namespaceRestrictions[] = $restriction->getValue(); |
560 | } |
561 | } |
562 | |
563 | // Sort the restrictions so they are in alphabetical order. |
564 | sort( $pageRestrictions ); |
565 | $fields['PageRestrictions']['default'] = implode( "\n", $pageRestrictions ); |
566 | sort( $namespaceRestrictions ); |
567 | $fields['NamespaceRestrictions']['default'] = implode( "\n", $namespaceRestrictions ); |
568 | |
569 | if ( $this->getConfig()->get( MainConfigNames::EnablePartialActionBlocks ) ) { |
570 | $actionRestrictions = []; |
571 | foreach ( $block->getRestrictions() as $restriction ) { |
572 | if ( $restriction instanceof ActionRestriction ) { |
573 | $actionRestrictions[] = $restriction->getValue(); |
574 | } |
575 | } |
576 | $fields['ActionRestrictions']['default'] = $actionRestrictions; |
577 | } |
578 | } |
579 | |
580 | $this->alreadyBlocked = true; |
581 | $this->preErrors[] = [ 'ipb-needreblock', wfEscapeWikiText( $block->getTargetName() ) ]; |
582 | } |
583 | |
584 | if ( $this->alreadyBlocked || $this->getRequest()->wasPosted() |
585 | || $this->getRequest()->getCheck( 'wpCreateAccount' ) |
586 | ) { |
587 | $this->getOutput()->addJsConfigVars( 'wgCreateAccountDirty', true ); |
588 | } |
589 | |
590 | // We always need confirmation to do HideUser |
591 | if ( $this->requestedHideUser ) { |
592 | $fields['Confirm']['type'] = 'check'; |
593 | unset( $fields['Confirm']['default'] ); |
594 | $this->preErrors[] = [ 'ipb-confirmhideuser', 'ipb-confirmaction' ]; |
595 | } |
596 | |
597 | // Or if the user is trying to block themselves |
598 | if ( (string)$this->target === $this->getUser()->getName() ) { |
599 | $fields['Confirm']['type'] = 'check'; |
600 | unset( $fields['Confirm']['default'] ); |
601 | $this->preErrors[] = [ 'ipb-blockingself', 'ipb-confirmaction' ]; |
602 | } |
603 | } |
604 | |
605 | /** |
606 | * Add header elements like block log entries, etc. |
607 | * @return string |
608 | */ |
609 | protected function preHtml() { |
610 | $this->getOutput()->addModuleStyles( [ 'mediawiki.special' ] ); |
611 | $this->getOutput()->addModules( [ 'mediawiki.special.block' ] ); |
612 | |
613 | $blockCIDRLimit = $this->getConfig()->get( MainConfigNames::BlockCIDRLimit ); |
614 | $text = $this->msg( 'blockiptext', $blockCIDRLimit['IPv4'], $blockCIDRLimit['IPv6'] )->parse(); |
615 | |
616 | $otherBlockMessages = []; |
617 | if ( $this->target !== null ) { |
618 | $targetName = $this->target; |
619 | if ( $this->target instanceof UserIdentity ) { |
620 | $targetName = $this->target->getName(); |
621 | } |
622 | // Get other blocks, i.e. from GlobalBlocking or TorBlock extension |
623 | $this->getHookRunner()->onOtherBlockLogLink( |
624 | $otherBlockMessages, $targetName ); |
625 | |
626 | if ( count( $otherBlockMessages ) ) { |
627 | $s = Html::rawElement( |
628 | 'h2', |
629 | [], |
630 | $this->msg( 'ipb-otherblocks-header', count( $otherBlockMessages ) )->parse() |
631 | ) . "\n"; |
632 | |
633 | $list = ''; |
634 | |
635 | foreach ( $otherBlockMessages as $link ) { |
636 | $list .= Html::rawElement( 'li', [], $link ) . "\n"; |
637 | } |
638 | |
639 | $s .= Html::rawElement( |
640 | 'ul', |
641 | [ 'class' => 'mw-blockip-alreadyblocked' ], |
642 | $list |
643 | ) . "\n"; |
644 | |
645 | $text .= $s; |
646 | } |
647 | } |
648 | |
649 | return $text; |
650 | } |
651 | |
652 | /** |
653 | * Add footer elements to the form |
654 | * @return string |
655 | */ |
656 | protected function postHtml() { |
657 | $links = []; |
658 | |
659 | $this->getOutput()->addModuleStyles( 'mediawiki.special' ); |
660 | |
661 | $linkRenderer = $this->getLinkRenderer(); |
662 | // Link to the user's contributions, if applicable |
663 | if ( $this->target instanceof UserIdentity ) { |
664 | $contribsPage = SpecialPage::getTitleFor( 'Contributions', $this->target->getName() ); |
665 | $links[] = $linkRenderer->makeLink( |
666 | $contribsPage, |
667 | $this->msg( 'ipb-blocklist-contribs', $this->target->getName() )->text() |
668 | ); |
669 | } |
670 | |
671 | // Link to unblock the specified user, or to a blank unblock form |
672 | if ( $this->target instanceof UserIdentity ) { |
673 | $message = $this->msg( |
674 | 'ipb-unblock-addr', |
675 | wfEscapeWikiText( $this->target->getName() ) |
676 | )->parse(); |
677 | $list = SpecialPage::getTitleFor( 'Unblock', $this->target->getName() ); |
678 | } else { |
679 | $message = $this->msg( 'ipb-unblock' )->parse(); |
680 | $list = SpecialPage::getTitleFor( 'Unblock' ); |
681 | } |
682 | $links[] = $linkRenderer->makeKnownLink( |
683 | $list, |
684 | new HtmlArmor( $message ) |
685 | ); |
686 | |
687 | // Link to the block list |
688 | $links[] = $linkRenderer->makeKnownLink( |
689 | SpecialPage::getTitleFor( 'BlockList' ), |
690 | $this->msg( 'ipb-blocklist' )->text() |
691 | ); |
692 | |
693 | // Link to edit the block dropdown reasons, if applicable |
694 | if ( $this->getAuthority()->isAllowed( 'editinterface' ) ) { |
695 | $links[] = $linkRenderer->makeKnownLink( |
696 | $this->msg( 'ipbreason-dropdown' )->inContentLanguage()->getTitle(), |
697 | $this->msg( 'ipb-edit-dropdown' )->text(), |
698 | [], |
699 | [ 'action' => 'edit' ] |
700 | ); |
701 | } |
702 | |
703 | $text = Html::rawElement( |
704 | 'p', |
705 | [ 'class' => 'mw-ipb-conveniencelinks' ], |
706 | $this->getLanguage()->pipeList( $links ) |
707 | ); |
708 | |
709 | $userPage = self::getTargetUserTitle( $this->target ); |
710 | if ( $userPage ) { |
711 | // Get relevant extracts from the block and suppression logs, if possible |
712 | $out = ''; |
713 | |
714 | LogEventsList::showLogExtract( |
715 | $out, |
716 | 'block', |
717 | $userPage, |
718 | '', |
719 | [ |
720 | 'lim' => 10, |
721 | 'msgKey' => [ |
722 | 'blocklog-showlog', |
723 | $this->titleFormatter->getText( $userPage ), |
724 | ], |
725 | 'showIfEmpty' => false |
726 | ] |
727 | ); |
728 | $text .= $out; |
729 | |
730 | // Add suppression block entries if allowed |
731 | if ( $this->getAuthority()->isAllowed( 'suppressionlog' ) ) { |
732 | LogEventsList::showLogExtract( |
733 | $out, |
734 | 'suppress', |
735 | $userPage, |
736 | '', |
737 | [ |
738 | 'lim' => 10, |
739 | 'conds' => [ 'log_action' => [ 'block', 'reblock', 'unblock' ] ], |
740 | 'msgKey' => [ |
741 | 'blocklog-showsuppresslog', |
742 | $this->titleFormatter->getText( $userPage ), |
743 | ], |
744 | 'showIfEmpty' => false |
745 | ] |
746 | ); |
747 | |
748 | $text .= $out; |
749 | } |
750 | } |
751 | |
752 | return $text; |
753 | } |
754 | |
755 | /** |
756 | * Get a user page target for things like logs. |
757 | * This handles account and IP range targets. |
758 | * @param UserIdentity|string|null $target |
759 | * @return PageReference|null |
760 | */ |
761 | protected static function getTargetUserTitle( $target ): ?PageReference { |
762 | if ( $target instanceof UserIdentity ) { |
763 | return PageReferenceValue::localReference( NS_USER, $target->getName() ); |
764 | } |
765 | |
766 | if ( is_string( $target ) && IPUtils::isIPAddress( $target ) ) { |
767 | return PageReferenceValue::localReference( NS_USER, $target ); |
768 | } |
769 | |
770 | return null; |
771 | } |
772 | |
773 | /** |
774 | * Get the target and type, given the request and the subpage parameter. |
775 | * Several parameters are handled for backwards compatability. 'wpTarget' is |
776 | * prioritized, since it matches the HTML form. |
777 | * |
778 | * @deprecated since 1.36. Use BlockUtils::parseBlockTarget directly instead. |
779 | * Hard-deprecated since 1.41. |
780 | * |
781 | * @param string|null $par Subpage parameter passed to setup, or data value from |
782 | * the HTMLForm |
783 | * @param WebRequest|null $request Optionally try and get data from a request too |
784 | * @return array [ UserIdentity|string|null, DatabaseBlock::TYPE_ constant|null ] |
785 | * @phan-return array{0:UserIdentity|string|null,1:int|null} |
786 | */ |
787 | public static function getTargetAndType( ?string $par, WebRequest $request = null ) { |
788 | wfDeprecated( __METHOD__, '1.36' ); |
789 | return self::getTargetAndTypeInternal( $par, $request ); |
790 | } |
791 | |
792 | /** |
793 | * Get the target and type, given the request and the subpage parameter. |
794 | * Several parameters are handled for backwards compatability. 'wpTarget' is |
795 | * prioritized, since it matches the HTML form. |
796 | * |
797 | * @param string|null $par Subpage parameter passed to setup, or data value from |
798 | * the HTMLForm |
799 | * @param WebRequest|null $request Optionally try and get data from a request too |
800 | * @return array [ UserIdentity|string|null, DatabaseBlock::TYPE_ constant|null ] |
801 | * @phan-return array{0:UserIdentity|string|null,1:int|null} |
802 | */ |
803 | private static function getTargetAndTypeInternal( ?string $par, WebRequest $request = null ) { |
804 | if ( !$request instanceof WebRequest ) { |
805 | return MediaWikiServices::getInstance()->getBlockUtils()->parseBlockTarget( $par ); |
806 | } |
807 | |
808 | $possibleTargets = [ |
809 | $request->getVal( 'wpTarget', null ), |
810 | $par, |
811 | $request->getVal( 'ip', null ), |
812 | // B/C @since 1.18 |
813 | $request->getVal( 'wpBlockAddress', null ), |
814 | ]; |
815 | foreach ( $possibleTargets as $possibleTarget ) { |
816 | $targetAndType = MediaWikiServices::getInstance() |
817 | ->getBlockUtils() |
818 | ->parseBlockTarget( $possibleTarget ); |
819 | // If type is not null then target is valid |
820 | if ( $targetAndType[ 1 ] !== null ) { |
821 | break; |
822 | } |
823 | } |
824 | return $targetAndType; |
825 | } |
826 | |
827 | /** |
828 | * Given the form data, actually implement a block. |
829 | * |
830 | * @deprecated since 1.36, use BlockUserFactory service instead |
831 | * @param array $data |
832 | * @param IContextSource $context |
833 | * @return bool|string|array|Status |
834 | */ |
835 | public static function processForm( array $data, IContextSource $context ) { |
836 | $services = MediaWikiServices::getInstance(); |
837 | return self::processFormInternal( |
838 | $data, |
839 | $context->getAuthority(), |
840 | $services->getBlockUserFactory(), |
841 | $services->getBlockUtils() |
842 | ); |
843 | } |
844 | |
845 | /** |
846 | * Implementation details for processForm |
847 | * Own function to allow sharing the deprecated code with non-deprecated and service code |
848 | * |
849 | * @param array $data |
850 | * @param Authority $performer |
851 | * @param BlockUserFactory $blockUserFactory |
852 | * @param BlockUtils $blockUtils |
853 | * @return bool|string|array|Status |
854 | */ |
855 | private static function processFormInternal( |
856 | array $data, |
857 | Authority $performer, |
858 | BlockUserFactory $blockUserFactory, |
859 | BlockUtils $blockUtils |
860 | ) { |
861 | // Temporarily access service container until the feature flag is removed: T280532 |
862 | $enablePartialActionBlocks = MediaWikiServices::getInstance() |
863 | ->getMainConfig()->get( MainConfigNames::EnablePartialActionBlocks ); |
864 | |
865 | $isPartialBlock = isset( $data['EditingRestriction'] ) && |
866 | $data['EditingRestriction'] === 'partial'; |
867 | |
868 | // This might have been a hidden field or a checkbox, so interesting data |
869 | // can come from it |
870 | $data['Confirm'] = !in_array( $data['Confirm'], [ '', '0', null, false ], true ); |
871 | |
872 | // If the user has done the form 'properly', they won't even have been given the |
873 | // option to suppress-block unless they have the 'hideuser' permission |
874 | if ( !isset( $data['HideUser'] ) ) { |
875 | $data['HideUser'] = false; |
876 | } |
877 | |
878 | /** @var User $target */ |
879 | [ $target, $type ] = $blockUtils->parseBlockTarget( $data['Target'] ); |
880 | if ( $type == DatabaseBlock::TYPE_USER ) { |
881 | $target = $target->getName(); |
882 | |
883 | // Give admins a heads-up before they go and block themselves. Much messier |
884 | // to do this for IPs, but it's pretty unlikely they'd ever get the 'block' |
885 | // permission anyway, although the code does allow for it. |
886 | // Note: Important to use $target instead of $data['Target'] |
887 | // since both $data['PreviousTarget'] and $target are normalized |
888 | // but $data['target'] gets overridden by (non-normalized) request variable |
889 | // from previous request. |
890 | if ( $target === $performer->getUser()->getName() && |
891 | ( $data['PreviousTarget'] !== $target || !$data['Confirm'] ) |
892 | ) { |
893 | return [ 'ipb-blockingself', 'ipb-confirmaction' ]; |
894 | } |
895 | |
896 | if ( $data['HideUser'] && !$data['Confirm'] ) { |
897 | return [ 'ipb-confirmhideuser', 'ipb-confirmaction' ]; |
898 | } |
899 | } elseif ( $type == DatabaseBlock::TYPE_IP ) { |
900 | $target = $target->getName(); |
901 | } elseif ( $type != DatabaseBlock::TYPE_RANGE ) { |
902 | // This should have been caught in the form field validation |
903 | return [ 'badipaddress' ]; |
904 | } |
905 | |
906 | // Reason, to be passed to the block object. For default values of reason, see |
907 | // HTMLSelectAndOtherField::getDefault |
908 | $blockReason = $data['Reason'][0] ?? ''; |
909 | |
910 | $pageRestrictions = []; |
911 | $namespaceRestrictions = []; |
912 | $actionRestrictions = []; |
913 | if ( $isPartialBlock ) { |
914 | if ( isset( $data['PageRestrictions'] ) && $data['PageRestrictions'] !== '' ) { |
915 | $titles = explode( "\n", $data['PageRestrictions'] ); |
916 | foreach ( $titles as $title ) { |
917 | $pageRestrictions[] = PageRestriction::newFromTitle( $title ); |
918 | } |
919 | } |
920 | if ( isset( $data['NamespaceRestrictions'] ) && $data['NamespaceRestrictions'] !== '' ) { |
921 | $namespaceRestrictions = array_map( static function ( $id ) { |
922 | return new NamespaceRestriction( 0, (int)$id ); |
923 | }, explode( "\n", $data['NamespaceRestrictions'] ) ); |
924 | } |
925 | if ( |
926 | $enablePartialActionBlocks && |
927 | isset( $data['ActionRestrictions'] ) && |
928 | $data['ActionRestrictions'] !== '' |
929 | ) { |
930 | $actionRestrictions = array_map( static function ( $id ) { |
931 | return new ActionRestriction( 0, $id ); |
932 | }, $data['ActionRestrictions'] ); |
933 | } |
934 | } |
935 | $restrictions = array_merge( $pageRestrictions, $namespaceRestrictions, $actionRestrictions ); |
936 | |
937 | if ( !isset( $data['Tags'] ) ) { |
938 | $data['Tags'] = []; |
939 | } |
940 | |
941 | $blockOptions = [ |
942 | 'isCreateAccountBlocked' => $data['CreateAccount'], |
943 | 'isHardBlock' => $data['HardBlock'], |
944 | 'isAutoblocking' => $data['AutoBlock'], |
945 | 'isHideUser' => $data['HideUser'], |
946 | 'isPartial' => $isPartialBlock, |
947 | ]; |
948 | |
949 | if ( isset( $data['DisableUTEdit'] ) ) { |
950 | $blockOptions['isUserTalkEditBlocked'] = $data['DisableUTEdit']; |
951 | } |
952 | if ( isset( $data['DisableEmail'] ) ) { |
953 | $blockOptions['isEmailBlocked'] = $data['DisableEmail']; |
954 | } |
955 | |
956 | $blockUser = $blockUserFactory->newBlockUser( |
957 | $target, |
958 | $performer, |
959 | $data['Expiry'], |
960 | $blockReason, |
961 | $blockOptions, |
962 | $restrictions, |
963 | $data['Tags'] |
964 | ); |
965 | |
966 | // Indicates whether the user is confirming the block and is aware of |
967 | // the conflict (did not change the block target in the meantime) |
968 | $blockNotConfirmed = !$data['Confirm'] || ( array_key_exists( 'PreviousTarget', $data ) |
969 | && $data['PreviousTarget'] !== $target ); |
970 | |
971 | // Special case for API - T34434 |
972 | $reblockNotAllowed = ( array_key_exists( 'Reblock', $data ) && !$data['Reblock'] ); |
973 | |
974 | $doReblock = !$blockNotConfirmed && !$reblockNotAllowed; |
975 | |
976 | $status = $blockUser->placeBlock( $doReblock ); |
977 | if ( !$status->isOK() ) { |
978 | return $status; |
979 | } |
980 | |
981 | if ( |
982 | // Can't watch a range block |
983 | $type != DatabaseBlock::TYPE_RANGE |
984 | |
985 | // Technically a wiki can be configured to allow anonymous users to place blocks, |
986 | // in which case the 'Watch' field isn't included in the form shown, and we should |
987 | // not try to access it. |
988 | && array_key_exists( 'Watch', $data ) |
989 | && $data['Watch'] |
990 | ) { |
991 | MediaWikiServices::getInstance()->getWatchlistManager()->addWatchIgnoringRights( |
992 | $performer->getUser(), |
993 | Title::makeTitle( NS_USER, $target ) |
994 | ); |
995 | } |
996 | |
997 | return true; |
998 | } |
999 | |
1000 | /** |
1001 | * Get an array of suggested block durations from MediaWiki:Ipboptions |
1002 | * @todo FIXME: This uses a rather odd syntax for the options, should it be converted |
1003 | * to the standard "**<duration>|<displayname>" format? |
1004 | * @deprecated since 1.42, use Language::getBlockDurations() instead. |
1005 | * @param Language|null $lang The language to get the durations in, or null to use |
1006 | * the wiki's content language |
1007 | * @param bool $includeOther Whether to include the 'other' option in the list of |
1008 | * suggestions |
1009 | * @return string[] |
1010 | */ |
1011 | public static function getSuggestedDurations( Language $lang = null, $includeOther = true ) { |
1012 | $lang ??= MediaWikiServices::getInstance()->getContentLanguage(); |
1013 | return $lang->getBlockDurations( $includeOther ); |
1014 | } |
1015 | |
1016 | /** |
1017 | * Convert a submitted expiry time, which may be relative ("2 weeks", etc) or absolute |
1018 | * ("24 May 2034", etc), into an absolute timestamp we can put into the database. |
1019 | * |
1020 | * @deprecated since 1.36, use BlockUser::parseExpiryInput instead |
1021 | * |
1022 | * @param string $expiry Whatever was typed into the form |
1023 | * @return string|bool Timestamp or 'infinity' or false on error. |
1024 | */ |
1025 | public static function parseExpiryInput( $expiry ) { |
1026 | return BlockUser::parseExpiryInput( $expiry ); |
1027 | } |
1028 | |
1029 | /** |
1030 | * Can we do an email block? |
1031 | * |
1032 | * @deprecated since 1.36, use BlockPermissionChecker service instead |
1033 | * @param UserIdentity $user The sysop wanting to make a block |
1034 | * @return bool |
1035 | */ |
1036 | public static function canBlockEmail( UserIdentity $user ) { |
1037 | return MediaWikiServices::getInstance() |
1038 | ->getBlockPermissionCheckerFactory() |
1039 | ->newBlockPermissionChecker( null, User::newFromIdentity( $user ) ) |
1040 | ->checkEmailPermissions(); |
1041 | } |
1042 | |
1043 | /** |
1044 | * Process the form on POST submission. |
1045 | * @param array $data |
1046 | * @param HTMLForm|null $form |
1047 | * @return bool|string|array|Status As documented for HTMLForm::trySubmit. |
1048 | */ |
1049 | public function onSubmit( array $data, HTMLForm $form = null ) { |
1050 | return self::processFormInternal( |
1051 | $data, |
1052 | $this->getAuthority(), |
1053 | $this->blockUserFactory, |
1054 | $this->blockUtils |
1055 | ); |
1056 | } |
1057 | |
1058 | /** |
1059 | * Do something exciting on successful processing of the form, most likely to show a |
1060 | * confirmation message |
1061 | */ |
1062 | public function onSuccess() { |
1063 | $out = $this->getOutput(); |
1064 | $out->setPageTitleMsg( $this->msg( 'blockipsuccesssub' ) ); |
1065 | $out->addWikiMsg( 'blockipsuccesstext', wfEscapeWikiText( $this->target ) ); |
1066 | } |
1067 | |
1068 | /** |
1069 | * Return an array of subpages beginning with $search that this special page will accept. |
1070 | * |
1071 | * @param string $search Prefix to search for |
1072 | * @param int $limit Maximum number of results to return (usually 10) |
1073 | * @param int $offset Number of results to skip (usually 0) |
1074 | * @return string[] Matching subpages |
1075 | */ |
1076 | public function prefixSearchSubpages( $search, $limit, $offset ) { |
1077 | $search = $this->userNameUtils->getCanonical( $search ); |
1078 | if ( !$search ) { |
1079 | // No prefix suggestion for invalid user |
1080 | return []; |
1081 | } |
1082 | // Autocomplete subpage as user list - public to allow caching |
1083 | return $this->userNamePrefixSearch |
1084 | ->search( UserNamePrefixSearch::AUDIENCE_PUBLIC, $search, $limit, $offset ); |
1085 | } |
1086 | |
1087 | protected function getGroupName() { |
1088 | return 'users'; |
1089 | } |
1090 | } |
1091 | |
1092 | /** @deprecated class alias since 1.41 */ |
1093 | class_alias( SpecialBlock::class, 'SpecialBlock' ); |