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