MediaWiki master
SpecialBlock.php
Go to the documentation of this file.
1<?php
24namespace MediaWiki\Specials;
25
27use HtmlArmor;
28use Language;
61use OOUI\FieldLayout;
62use OOUI\HtmlSnippet;
63use OOUI\LabelWidget;
64use OOUI\Widget;
65use Wikimedia\IPUtils;
66
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
87 protected $target;
88
90 protected $type;
91
93 protected $previousTarget;
94
97
99 protected $alreadyBlocked;
100
105 protected $preErrors = [];
106
107 private NamespaceInfo $namespaceInfo;
108
120 public function __construct(
121 BlockUtils $blockUtils,
122 BlockPermissionCheckerFactory $blockPermissionCheckerFactory,
123 BlockUserFactory $blockUserFactory,
124 DatabaseBlockStore $blockStore,
125 UserNameUtils $userNameUtils,
126 UserNamePrefixSearch $userNamePrefixSearch,
127 BlockActionInfo $blockActionInfo,
128 TitleFormatter $titleFormatter,
129 NamespaceInfo $namespaceInfo
130 ) {
131 parent::__construct( 'Block', 'block' );
132
133 $this->blockUtils = $blockUtils;
134 $this->blockPermissionCheckerFactory = $blockPermissionCheckerFactory;
135 $this->blockUserFactory = $blockUserFactory;
136 $this->blockStore = $blockStore;
137 $this->userNameUtils = $userNameUtils;
138 $this->userNamePrefixSearch = $userNamePrefixSearch;
139 $this->blockActionInfo = $blockActionInfo;
140 $this->titleFormatter = $titleFormatter;
141 $this->namespaceInfo = $namespaceInfo;
142 }
143
144 public function doesWrites() {
145 return true;
146 }
147
154 protected function checkExecutePermissions( User $user ) {
155 parent::checkExecutePermissions( $user );
156 // T17810: blocked admins should have limited access here
157 $status = $this->blockPermissionCheckerFactory
158 ->newBlockPermissionChecker( $this->target, $user )
159 ->checkBlockPermissions();
160 if ( $status !== true ) {
161 throw new ErrorPageError( 'badaccess', $status );
162 }
163 }
164
170 public function requiresUnblock() {
171 return false;
172 }
173
179 protected function setParameter( $par ) {
180 // Extract variables from the request. Try not to get into a situation where we
181 // need to extract *every* variable from the form just for processing here, but
182 // there are legitimate uses for some variables
183 $request = $this->getRequest();
184 [ $this->target, $this->type ] = self::getTargetAndTypeInternal( $par, $request );
185 if ( $this->target instanceof UserIdentity ) {
186 // Set the 'relevant user' in the skin, so it displays links like Contributions,
187 // User logs, UserRights, etc.
188 $this->getSkin()->setRelevantUser( $this->target );
189 }
190
191 [ $this->previousTarget, /*...*/ ] = $this->blockUtils
192 ->parseBlockTarget( $request->getVal( 'wpPreviousTarget' ) );
193 $this->requestedHideUser = $request->getBool( 'wpHideUser' );
194 }
195
201 protected function alterForm( HTMLForm $form ) {
202 $form->setHeaderHtml( '' );
203 $form->setSubmitDestructive();
204
205 $msg = $this->alreadyBlocked ? 'ipb-change-block' : 'ipbsubmit';
206 $form->setSubmitTextMsg( $msg );
207
208 $this->addHelpLink( 'Help:Blocking users' );
209
210 // Don't need to do anything if the form has been posted
211 if ( !$this->getRequest()->wasPosted() && $this->preErrors ) {
212 // Mimic error messages normally generated by the form
213 $form->addHeaderHtml( (string)new FieldLayout(
214 new Widget( [] ),
215 [
216 'align' => 'top',
217 'errors' => array_map( function ( $errMsg ) {
218 // @phan-suppress-next-line PhanParamTooFewUnpack Should infer non-emptiness
219 return new HtmlSnippet( $this->msg( ...$errMsg )->parse() );
220 }, $this->preErrors ),
221 ]
222 ) );
223 }
224 }
225
226 protected function getDisplayFormat() {
227 return 'ooui';
228 }
229
234 protected function getFormFields() {
235 $conf = $this->getConfig();
236 $blockAllowsUTEdit = $conf->get( MainConfigNames::BlockAllowsUTEdit );
237
238 $this->getOutput()->enableOOUI();
239
240 $user = $this->getUser();
241
242 $suggestedDurations = $this->getLanguage()->getBlockDurations();
243
244 $a = [];
245
246 $a['Target'] = [
247 'type' => 'user',
248 'ipallowed' => true,
249 'iprange' => true,
250 'id' => 'mw-bi-target',
251 'size' => '45',
252 'autofocus' => true,
253 'required' => true,
254 'placeholder' => $this->msg( 'block-target-placeholder' )->text(),
255 'validation-callback' => function ( $value, $alldata, $form ) {
256 $status = $this->blockUtils->validateTarget( $value );
257 if ( !$status->isOK() ) {
258 $errors = $status->getErrorsArray();
259
260 return $form->msg( ...$errors[0] );
261 }
262 return true;
263 },
264 'section' => 'target',
265 ];
266
267 $a['EditingRestriction'] = [
268 'type' => 'radio',
269 'cssclass' => 'mw-block-editing-restriction',
270 'default' => 'sitewide',
271 'options' => [
272 $this->msg( 'ipb-sitewide' )->escaped() .
273 new LabelWidget( [
274 'classes' => [ 'oo-ui-inline-help' ],
275 'label' => new HtmlSnippet( $this->msg( 'ipb-sitewide-help' )->parse() ),
276 ] ) => 'sitewide',
277 $this->msg( 'ipb-partial' )->escaped() .
278 new LabelWidget( [
279 'classes' => [ 'oo-ui-inline-help' ],
280 'label' => new HtmlSnippet( $this->msg( 'ipb-partial-help' )->parse() ),
281 ] ) => 'partial',
282 ],
283 'section' => 'actions',
284 ];
285
286 $a['PageRestrictions'] = [
287 'type' => 'titlesmultiselect',
288 'label' => $this->msg( 'ipb-pages-label' )->text(),
289 'exists' => true,
290 'max' => 10,
291 'cssclass' => 'mw-htmlform-checkradio-indent mw-block-partial-restriction',
292 'default' => '',
293 'showMissing' => false,
294 'excludeDynamicNamespaces' => true,
295 'input' => [
296 'autocomplete' => false
297 ],
298 'section' => 'actions',
299 ];
300
301 $a['NamespaceRestrictions'] = [
302 'type' => 'namespacesmultiselect',
303 'label' => $this->msg( 'ipb-namespaces-label' )->text(),
304 'exists' => true,
305 'cssclass' => 'mw-htmlform-checkradio-indent mw-block-partial-restriction',
306 'default' => '',
307 'input' => [
308 'autocomplete' => false
309 ],
310 'section' => 'actions',
311 ];
312
313 if ( $conf->get( MainConfigNames::EnablePartialActionBlocks ) ) {
314 $blockActions = $this->blockActionInfo->getAllBlockActions();
315 $a['ActionRestrictions'] = [
316 'type' => 'multiselect',
317 'cssclass' => 'mw-htmlform-checkradio-indent mw-block-partial-restriction mw-block-action-restriction',
318 'options-messages' => array_combine(
319 array_map( static function ( $action ) {
320 return "ipb-action-$action";
321 }, array_keys( $blockActions ) ),
322 $blockActions
323 ),
324 'section' => 'actions',
325 ];
326 }
327
328 $a['CreateAccount'] = [
329 'type' => 'check',
330 'cssclass' => 'mw-block-restriction',
331 'label-message' => 'ipbcreateaccount',
332 'default' => true,
333 'section' => 'details',
334 ];
335
336 if ( $this->blockPermissionCheckerFactory
337 ->newBlockPermissionChecker( null, $user )
338 ->checkEmailPermissions()
339 ) {
340 $a['DisableEmail'] = [
341 'type' => 'check',
342 'cssclass' => 'mw-block-restriction',
343 'label-message' => 'ipbemailban',
344 'section' => 'details',
345 ];
346 }
347
348 if ( $blockAllowsUTEdit ) {
349 $a['DisableUTEdit'] = [
350 'type' => 'check',
351 'cssclass' => 'mw-block-restriction',
352 'label-message' => 'ipb-disableusertalk',
353 'default' => false,
354 'section' => 'details',
355 ];
356 }
357
358 $defaultExpiry = $this->msg( 'ipb-default-expiry' )->inContentLanguage();
359 if ( $this->type === DatabaseBlock::TYPE_RANGE || $this->type === DatabaseBlock::TYPE_IP ) {
360 $defaultExpiryIP = $this->msg( 'ipb-default-expiry-ip' )->inContentLanguage();
361 if ( !$defaultExpiryIP->isDisabled() ) {
362 $defaultExpiry = $defaultExpiryIP;
363 }
364 }
365
366 $a['Expiry'] = [
367 'type' => 'expiry',
368 'required' => true,
369 'options' => $suggestedDurations,
370 'default' => $defaultExpiry->text(),
371 'section' => 'expiry',
372 ];
373
374 $a['Reason'] = [
375 'type' => 'selectandother',
376 // HTML maxlength uses "UTF-16 code units", which means that characters outside BMP
377 // (e.g. emojis) count for two each. This limit is overridden in JS to instead count
378 // Unicode codepoints.
379 'maxlength' => CommentStore::COMMENT_CHARACTER_LIMIT,
380 'maxlength-unit' => 'codepoints',
381 'options-message' => 'ipbreason-dropdown',
382 'section' => 'reason',
383 ];
384
385 $a['AutoBlock'] = [
386 'type' => 'check',
387 'label-message' => [
388 'ipbenableautoblock',
390 ],
391 'default' => true,
392 'section' => 'options',
393 ];
394
395 // Allow some users to hide name from block log, blocklist and listusers
396 if ( $this->getAuthority()->isAllowed( 'hideuser' ) ) {
397 $a['HideUser'] = [
398 'type' => 'check',
399 'label-message' => 'ipbhidename',
400 'cssclass' => 'mw-block-hideuser',
401 'section' => 'options',
402 ];
403 }
404
405 // Watchlist their user page? (Only if user is logged in)
406 if ( $user->isRegistered() ) {
407 $a['Watch'] = [
408 'type' => 'check',
409 'label-message' => 'ipbwatchuser',
410 'section' => 'options',
411 ];
412 }
413
414 $a['HardBlock'] = [
415 'type' => 'check',
416 'label-message' => 'ipb-hardblock',
417 'default' => false,
418 'section' => 'options',
419 ];
420
421 // This is basically a copy of the Target field, but the user can't change it, so we
422 // can see if the warnings we maybe showed to the user before still apply
423 $a['PreviousTarget'] = [
424 'type' => 'hidden',
425 'default' => false,
426 ];
427
428 // We'll turn this into a checkbox if we need to
429 $a['Confirm'] = [
430 'type' => 'hidden',
431 'default' => '',
432 'label-message' => 'ipb-confirm',
433 'cssclass' => 'mw-block-confirm',
434 ];
435
436 $this->maybeAlterFormDefaults( $a );
437
438 // Allow extensions to add more fields
439 $this->getHookRunner()->onSpecialBlockModifyFormFields( $this, $a );
440
441 return $a;
442 }
443
449 protected function maybeAlterFormDefaults( &$fields ) {
450 // This will be overwritten by request data
451 $fields['Target']['default'] = (string)$this->target;
452
453 if ( $this->target ) {
454 $status = $this->blockUtils->validateTarget( $this->target );
455 if ( !$status->isOK() ) {
456 $errors = $status->getErrorsArray();
457 $this->preErrors = array_merge( $this->preErrors, $errors );
458 }
459 }
460
461 // This won't be
462 $fields['PreviousTarget']['default'] = (string)$this->target;
463
464 $block = $this->blockStore->newFromTarget( $this->target );
465
466 // Populate fields if there is a block that is not an autoblock; if it is a range
467 // block, only populate the fields if the range is the same as $this->target
468 if ( $block instanceof DatabaseBlock && $block->getType() !== DatabaseBlock::TYPE_AUTO
469 && ( $this->type != DatabaseBlock::TYPE_RANGE
470 || ( $this->target && $block->isBlocking( $this->target ) ) )
471 ) {
472 $fields['HardBlock']['default'] = $block->isHardblock();
473 $fields['CreateAccount']['default'] = $block->isCreateAccountBlocked();
474 $fields['AutoBlock']['default'] = $block->isAutoblocking();
475
476 if ( isset( $fields['DisableEmail'] ) ) {
477 $fields['DisableEmail']['default'] = $block->isEmailBlocked();
478 }
479
480 if ( isset( $fields['HideUser'] ) ) {
481 $fields['HideUser']['default'] = $block->getHideName();
482 }
483
484 if ( isset( $fields['DisableUTEdit'] ) ) {
485 $fields['DisableUTEdit']['default'] = !$block->isUsertalkEditAllowed();
486 }
487
488 // If the username was hidden (bl_deleted == 1), don't show the reason
489 // unless this user also has rights to hideuser: T37839
490 if ( !$block->getHideName() || $this->getAuthority()->isAllowed( 'hideuser' ) ) {
491 $fields['Reason']['default'] = $block->getReasonComment()->text;
492 } else {
493 $fields['Reason']['default'] = '';
494 }
495
496 if ( $this->getRequest()->wasPosted() ) {
497 // Ok, so we got a POST submission asking us to reblock a user. So show the
498 // confirm checkbox; the user will only see it if they haven't previously
499 $fields['Confirm']['type'] = 'check';
500 } else {
501 // We got a target, but it wasn't a POST request, so the user must have gone
502 // to a link like [[Special:Block/User]]. We don't need to show the checkbox
503 // as long as they go ahead and block *that* user
504 $fields['Confirm']['default'] = 1;
505 }
506
507 if ( $block->getExpiry() == 'infinity' ) {
508 $fields['Expiry']['default'] = 'infinite';
509 } else {
510 $fields['Expiry']['default'] = wfTimestamp( TS_RFC2822, $block->getExpiry() );
511 }
512
513 if ( !$block->isSitewide() ) {
514 $fields['EditingRestriction']['default'] = 'partial';
515
516 $pageRestrictions = [];
517 $namespaceRestrictions = [];
518 foreach ( $block->getRestrictions() as $restriction ) {
519 if ( $restriction instanceof PageRestriction && $restriction->getTitle() ) {
520 $pageRestrictions[] = $restriction->getTitle()->getPrefixedText();
521 } elseif ( $restriction instanceof NamespaceRestriction &&
522 $this->namespaceInfo->exists( $restriction->getValue() )
523 ) {
524 $namespaceRestrictions[] = $restriction->getValue();
525 }
526 }
527
528 // Sort the restrictions so they are in alphabetical order.
529 sort( $pageRestrictions );
530 $fields['PageRestrictions']['default'] = implode( "\n", $pageRestrictions );
531 sort( $namespaceRestrictions );
532 $fields['NamespaceRestrictions']['default'] = implode( "\n", $namespaceRestrictions );
533
535 $actionRestrictions = [];
536 foreach ( $block->getRestrictions() as $restriction ) {
537 if ( $restriction instanceof ActionRestriction ) {
538 $actionRestrictions[] = $restriction->getValue();
539 }
540 }
541 $fields['ActionRestrictions']['default'] = $actionRestrictions;
542 }
543 }
544
545 $this->alreadyBlocked = true;
546 $this->preErrors[] = [ 'ipb-needreblock', wfEscapeWikiText( $block->getTargetName() ) ];
547 }
548
549 if ( $this->alreadyBlocked || $this->getRequest()->wasPosted()
550 || $this->getRequest()->getCheck( 'wpCreateAccount' )
551 ) {
552 $this->getOutput()->addJsConfigVars( 'wgCreateAccountDirty', true );
553 }
554
555 // We always need confirmation to do HideUser
556 if ( $this->requestedHideUser ) {
557 $fields['Confirm']['type'] = 'check';
558 unset( $fields['Confirm']['default'] );
559 $this->preErrors[] = [ 'ipb-confirmhideuser', 'ipb-confirmaction' ];
560 }
561
562 // Or if the user is trying to block themselves
563 if ( (string)$this->target === $this->getUser()->getName() ) {
564 $fields['Confirm']['type'] = 'check';
565 unset( $fields['Confirm']['default'] );
566 $this->preErrors[] = [ 'ipb-blockingself', 'ipb-confirmaction' ];
567 }
568 }
569
574 protected function preHtml() {
575 $this->getOutput()->addModuleStyles( [ 'mediawiki.special' ] );
576 $this->getOutput()->addModules( [ 'mediawiki.special.block' ] );
577
578 $blockCIDRLimit = $this->getConfig()->get( MainConfigNames::BlockCIDRLimit );
579 $text = $this->msg( 'blockiptext', $blockCIDRLimit['IPv4'], $blockCIDRLimit['IPv6'] )->parse();
580
581 $otherBlockMessages = [];
582 if ( $this->target !== null ) {
583 $targetName = $this->target;
584 if ( $this->target instanceof UserIdentity ) {
585 $targetName = $this->target->getName();
586 }
587 // Get other blocks, i.e. from GlobalBlocking or TorBlock extension
588 $this->getHookRunner()->onOtherBlockLogLink(
589 $otherBlockMessages, $targetName );
590
591 if ( count( $otherBlockMessages ) ) {
592 $s = Html::rawElement(
593 'h2',
594 [],
595 $this->msg( 'ipb-otherblocks-header', count( $otherBlockMessages ) )->parse()
596 ) . "\n";
597
598 $list = '';
599
600 foreach ( $otherBlockMessages as $link ) {
601 $list .= Html::rawElement( 'li', [], $link ) . "\n";
602 }
603
604 $s .= Html::rawElement(
605 'ul',
606 [ 'class' => 'mw-blockip-alreadyblocked' ],
607 $list
608 ) . "\n";
609
610 $text .= $s;
611 }
612 }
613
614 return $text;
615 }
616
621 protected function postHtml() {
622 $links = [];
623
624 $this->getOutput()->addModuleStyles( 'mediawiki.special' );
625
626 $linkRenderer = $this->getLinkRenderer();
627 // Link to the user's contributions, if applicable
628 if ( $this->target instanceof UserIdentity ) {
629 $contribsPage = SpecialPage::getTitleFor( 'Contributions', $this->target->getName() );
630 $links[] = $linkRenderer->makeLink(
631 $contribsPage,
632 $this->msg( 'ipb-blocklist-contribs', $this->target->getName() )->text()
633 );
634 }
635
636 // Link to unblock the specified user, or to a blank unblock form
637 if ( $this->target instanceof UserIdentity ) {
638 $message = $this->msg(
639 'ipb-unblock-addr',
640 wfEscapeWikiText( $this->target->getName() )
641 )->parse();
642 $list = SpecialPage::getTitleFor( 'Unblock', $this->target->getName() );
643 } else {
644 $message = $this->msg( 'ipb-unblock' )->parse();
645 $list = SpecialPage::getTitleFor( 'Unblock' );
646 }
647 $links[] = $linkRenderer->makeKnownLink(
648 $list,
649 new HtmlArmor( $message )
650 );
651
652 // Link to the block list
653 $links[] = $linkRenderer->makeKnownLink(
654 SpecialPage::getTitleFor( 'BlockList' ),
655 $this->msg( 'ipb-blocklist' )->text()
656 );
657
658 // Link to edit the block dropdown reasons, if applicable
659 if ( $this->getAuthority()->isAllowed( 'editinterface' ) ) {
660 $links[] = $linkRenderer->makeKnownLink(
661 $this->msg( 'ipbreason-dropdown' )->inContentLanguage()->getTitle(),
662 $this->msg( 'ipb-edit-dropdown' )->text(),
663 [],
664 [ 'action' => 'edit' ]
665 );
666 }
667
668 $text = Html::rawElement(
669 'p',
670 [ 'class' => 'mw-ipb-conveniencelinks' ],
671 $this->getLanguage()->pipeList( $links )
672 );
673
674 $userPage = self::getTargetUserTitle( $this->target );
675 if ( $userPage ) {
676 // Get relevant extracts from the block and suppression logs, if possible
677 $out = '';
678
679 LogEventsList::showLogExtract(
680 $out,
681 'block',
682 $userPage,
683 '',
684 [
685 'lim' => 10,
686 'msgKey' => [
687 'blocklog-showlog',
688 $this->titleFormatter->getText( $userPage ),
689 ],
690 'showIfEmpty' => false
691 ]
692 );
693 $text .= $out;
694
695 // Add suppression block entries if allowed
696 if ( $this->getAuthority()->isAllowed( 'suppressionlog' ) ) {
697 LogEventsList::showLogExtract(
698 $out,
699 'suppress',
700 $userPage,
701 '',
702 [
703 'lim' => 10,
704 'conds' => [ 'log_action' => [ 'block', 'reblock', 'unblock' ] ],
705 'msgKey' => [
706 'blocklog-showsuppresslog',
707 $this->titleFormatter->getText( $userPage ),
708 ],
709 'showIfEmpty' => false
710 ]
711 );
712
713 $text .= $out;
714 }
715 }
716
717 return $text;
718 }
719
726 protected static function getTargetUserTitle( $target ): ?PageReference {
727 if ( $target instanceof UserIdentity ) {
728 return PageReferenceValue::localReference( NS_USER, $target->getName() );
729 }
730
731 if ( is_string( $target ) && IPUtils::isIPAddress( $target ) ) {
732 return PageReferenceValue::localReference( NS_USER, $target );
733 }
734
735 return null;
736 }
737
752 public static function getTargetAndType( ?string $par, WebRequest $request = null ) {
753 wfDeprecated( __METHOD__, '1.36' );
754 return self::getTargetAndTypeInternal( $par, $request );
755 }
756
768 private static function getTargetAndTypeInternal( ?string $par, WebRequest $request = null ) {
769 if ( !$request instanceof WebRequest ) {
770 return MediaWikiServices::getInstance()->getBlockUtils()->parseBlockTarget( $par );
771 }
772
773 $possibleTargets = [
774 $request->getVal( 'wpTarget', null ),
775 $par,
776 $request->getVal( 'ip', null ),
777 // B/C @since 1.18
778 $request->getVal( 'wpBlockAddress', null ),
779 ];
780 foreach ( $possibleTargets as $possibleTarget ) {
781 $targetAndType = MediaWikiServices::getInstance()
782 ->getBlockUtils()
783 ->parseBlockTarget( $possibleTarget );
784 // If type is not null then target is valid
785 if ( $targetAndType[ 1 ] !== null ) {
786 break;
787 }
788 }
789 return $targetAndType;
790 }
791
800 public static function processForm( array $data, IContextSource $context ) {
801 $services = MediaWikiServices::getInstance();
802 return self::processFormInternal(
803 $data,
804 $context->getAuthority(),
805 $services->getBlockUserFactory(),
806 $services->getBlockUtils()
807 );
808 }
809
820 private static function processFormInternal(
821 array $data,
822 Authority $performer,
823 BlockUserFactory $blockUserFactory,
824 BlockUtils $blockUtils
825 ) {
826 // Temporarily access service container until the feature flag is removed: T280532
827 $enablePartialActionBlocks = MediaWikiServices::getInstance()
828 ->getMainConfig()->get( MainConfigNames::EnablePartialActionBlocks );
829
830 $isPartialBlock = isset( $data['EditingRestriction'] ) &&
831 $data['EditingRestriction'] === 'partial';
832
833 // This might have been a hidden field or a checkbox, so interesting data
834 // can come from it
835 $data['Confirm'] = !in_array( $data['Confirm'], [ '', '0', null, false ], true );
836
837 // If the user has done the form 'properly', they won't even have been given the
838 // option to suppress-block unless they have the 'hideuser' permission
839 if ( !isset( $data['HideUser'] ) ) {
840 $data['HideUser'] = false;
841 }
842
844 [ $target, $type ] = $blockUtils->parseBlockTarget( $data['Target'] );
845 if ( $type == DatabaseBlock::TYPE_USER ) {
846 $target = $target->getName();
847
848 // Give admins a heads-up before they go and block themselves. Much messier
849 // to do this for IPs, but it's pretty unlikely they'd ever get the 'block'
850 // permission anyway, although the code does allow for it.
851 // Note: Important to use $target instead of $data['Target']
852 // since both $data['PreviousTarget'] and $target are normalized
853 // but $data['target'] gets overridden by (non-normalized) request variable
854 // from previous request.
855 if ( $target === $performer->getUser()->getName() &&
856 ( $data['PreviousTarget'] !== $target || !$data['Confirm'] )
857 ) {
858 return [ 'ipb-blockingself', 'ipb-confirmaction' ];
859 }
860
861 if ( $data['HideUser'] && !$data['Confirm'] ) {
862 return [ 'ipb-confirmhideuser', 'ipb-confirmaction' ];
863 }
864 } elseif ( $type == DatabaseBlock::TYPE_IP ) {
865 $target = $target->getName();
866 } elseif ( $type != DatabaseBlock::TYPE_RANGE ) {
867 // This should have been caught in the form field validation
868 return [ 'badipaddress' ];
869 }
870
871 // Reason, to be passed to the block object. For default values of reason, see
872 // HTMLSelectAndOtherField::getDefault
873 $blockReason = $data['Reason'][0] ?? '';
874
875 $pageRestrictions = [];
876 $namespaceRestrictions = [];
877 $actionRestrictions = [];
878 if ( $isPartialBlock ) {
879 if ( isset( $data['PageRestrictions'] ) && $data['PageRestrictions'] !== '' ) {
880 $titles = explode( "\n", $data['PageRestrictions'] );
881 foreach ( $titles as $title ) {
882 $pageRestrictions[] = PageRestriction::newFromTitle( $title );
883 }
884 }
885 if ( isset( $data['NamespaceRestrictions'] ) && $data['NamespaceRestrictions'] !== '' ) {
886 $namespaceRestrictions = array_map( static function ( $id ) {
887 return new NamespaceRestriction( 0, (int)$id );
888 }, explode( "\n", $data['NamespaceRestrictions'] ) );
889 }
890 if (
891 $enablePartialActionBlocks &&
892 isset( $data['ActionRestrictions'] ) &&
893 $data['ActionRestrictions'] !== ''
894 ) {
895 $actionRestrictions = array_map( static function ( $id ) {
896 return new ActionRestriction( 0, $id );
897 }, $data['ActionRestrictions'] );
898 }
899 }
900 $restrictions = array_merge( $pageRestrictions, $namespaceRestrictions, $actionRestrictions );
901
902 if ( !isset( $data['Tags'] ) ) {
903 $data['Tags'] = [];
904 }
905
906 $blockOptions = [
907 'isCreateAccountBlocked' => $data['CreateAccount'],
908 'isHardBlock' => $data['HardBlock'],
909 'isAutoblocking' => $data['AutoBlock'],
910 'isHideUser' => $data['HideUser'],
911 'isPartial' => $isPartialBlock,
912 ];
913
914 if ( isset( $data['DisableUTEdit'] ) ) {
915 $blockOptions['isUserTalkEditBlocked'] = $data['DisableUTEdit'];
916 }
917 if ( isset( $data['DisableEmail'] ) ) {
918 $blockOptions['isEmailBlocked'] = $data['DisableEmail'];
919 }
920
921 $blockUser = $blockUserFactory->newBlockUser(
922 $target,
923 $performer,
924 $data['Expiry'],
925 $blockReason,
926 $blockOptions,
927 $restrictions,
928 $data['Tags']
929 );
930
931 // Indicates whether the user is confirming the block and is aware of
932 // the conflict (did not change the block target in the meantime)
933 $blockNotConfirmed = !$data['Confirm'] || ( array_key_exists( 'PreviousTarget', $data )
934 && $data['PreviousTarget'] !== $target );
935
936 // Special case for API - T34434
937 $reblockNotAllowed = ( array_key_exists( 'Reblock', $data ) && !$data['Reblock'] );
938
939 $doReblock = !$blockNotConfirmed && !$reblockNotAllowed;
940
941 $status = $blockUser->placeBlock( $doReblock );
942 if ( !$status->isOK() ) {
943 return $status;
944 }
945
946 if (
947 // Can't watch a range block
948 $type != DatabaseBlock::TYPE_RANGE
949
950 // Technically a wiki can be configured to allow anonymous users to place blocks,
951 // in which case the 'Watch' field isn't included in the form shown, and we should
952 // not try to access it.
953 && array_key_exists( 'Watch', $data )
954 && $data['Watch']
955 ) {
956 MediaWikiServices::getInstance()->getWatchlistManager()->addWatchIgnoringRights(
957 $performer->getUser(),
958 Title::makeTitle( NS_USER, $target )
959 );
960 }
961
962 return true;
963 }
964
976 public static function getSuggestedDurations( Language $lang = null, $includeOther = true ) {
977 $lang ??= MediaWikiServices::getInstance()->getContentLanguage();
978 return $lang->getBlockDurations( $includeOther );
979 }
980
990 public static function parseExpiryInput( $expiry ) {
991 return BlockUser::parseExpiryInput( $expiry );
992 }
993
1001 public static function canBlockEmail( UserIdentity $user ) {
1003 ->getBlockPermissionCheckerFactory()
1004 ->newBlockPermissionChecker( null, User::newFromIdentity( $user ) )
1005 ->checkEmailPermissions();
1006 }
1007
1014 public function onSubmit( array $data, HTMLForm $form = null ) {
1015 return self::processFormInternal(
1016 $data,
1017 $this->getAuthority(),
1018 $this->blockUserFactory,
1019 $this->blockUtils
1020 );
1021 }
1022
1027 public function onSuccess() {
1028 $out = $this->getOutput();
1029 $out->setPageTitleMsg( $this->msg( 'blockipsuccesssub' ) );
1030 $out->addWikiMsg( 'blockipsuccesstext', wfEscapeWikiText( $this->target ) );
1031 }
1032
1041 public function prefixSearchSubpages( $search, $limit, $offset ) {
1042 $search = $this->userNameUtils->getCanonical( $search );
1043 if ( !$search ) {
1044 // No prefix suggestion for invalid user
1045 return [];
1046 }
1047 // Autocomplete subpage as user list - public to allow caching
1048 return $this->userNamePrefixSearch
1049 ->search( UserNamePrefixSearch::AUDIENCE_PUBLIC, $search, $limit, $offset );
1050 }
1051
1052 protected function getGroupName() {
1053 return 'users';
1054 }
1055}
1056
1060class_alias( SpecialBlock::class, 'SpecialBlock' );
getAuthority()
const NS_USER
Definition Defines.php:66
wfEscapeWikiText( $input)
Escapes the given text so that it may be output using addWikiText() without any linking,...
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
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:81
An error page which can definitely be safely rendered using the OutputPage.
Marks HTML that shouldn't be escaped.
Definition HtmlArmor.php:30
Base class for language-specific code.
Definition Language.php:63
Defines the actions that can be blocked by a partial block.
Handles the backend logic of blocking users.
Definition BlockUser.php:54
static parseExpiryInput(string $expiry)
Convert a submitted expiry time, which may be relative ("2 weeks", etc) or absolute ("24 May 2034",...
Backend class for blocking utils.
parseBlockTarget( $target)
From string specification or UserIdentity, get the block target and the type of target.
A DatabaseBlock (unlike a SystemBlock) is stored in the database, may give rise to autoblocks and may...
getType()
Get the type of target for this particular block.int|null AbstractBlock::TYPE_ constant,...
Restriction for partial blocks of actions.
Handle database storage of comments such as edit summaries and log reasons.
Object handling generic submission, CSRF protection, layout and other logic for UI forms in a reusabl...
Definition HTMLForm.php:206
setHeaderHtml( $html, $section=null)
Set header HTML, inside the form.
Definition HTMLForm.php:965
setSubmitTextMsg( $msg)
Set the text for the submit button to a message.
setSubmitDestructive()
Identify that the submit button in the form has a destructive action.
addHeaderHtml( $html, $section=null)
Add HTML to the header, inside the form.
Definition HTMLForm.php:945
This class is a collection of static functions that serve two purposes:
Definition Html.php:56
A class containing constants representing the names of configuration variables.
const AutoblockExpiry
Name constant for the AutoblockExpiry setting, for use with Config::get()
const BlockAllowsUTEdit
Name constant for the BlockAllowsUTEdit setting, for use with Config::get()
const BlockCIDRLimit
Name constant for the BlockCIDRLimit setting, for use with Config::get()
const EnablePartialActionBlocks
Name constant for the EnablePartialActionBlocks setting, for use with Config::get()
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition Message.php:157
static durationParam( $duration)
Definition Message.php:1179
Immutable value object representing a page reference.
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
Special page which uses an HTMLForm to handle processing.
string null $par
The sub-page of the special page.
Parent class for all special pages.
getSkin()
Shortcut to get the skin being used for this instance.
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
getUser()
Shortcut to get the User executing this instance.
getConfig()
Shortcut to get main config object.
getRequest()
Get the WebRequest being used for this instance.
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
getOutput()
Get the OutputPage being used for this instance.
getAuthority()
Shortcut to get the Authority executing this instance.
getLanguage()
Shortcut to get user's language.
getName()
Get the canonical, unlocalized name of this special page without namespace.
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
A special page that allows users with 'block' right to block users from editing pages and other actio...
static parseExpiryInput( $expiry)
Convert a submitted expiry time, which may be relative ("2 weeks", etc) or absolute ("24 May 2034",...
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
onSuccess()
Do something exciting on successful processing of the form, most likely to show a confirmation messag...
onSubmit(array $data, HTMLForm $form=null)
Process the form on POST submission.
preHtml()
Add header elements like block log entries, etc.
static canBlockEmail(UserIdentity $user)
Can we do an email block?
int $type
DatabaseBlock::TYPE_ constant.
static getSuggestedDurations(Language $lang=null, $includeOther=true)
Get an array of suggested block durations from MediaWiki:Ipboptions.
prefixSearchSubpages( $search, $limit, $offset)
Return an array of subpages beginning with $search that this special page will accept.
User string $previousTarget
The previous block target.
alterForm(HTMLForm $form)
Customizes the HTMLForm a bit.
postHtml()
Add footer elements to the form.
requiresUnblock()
We allow certain special cases where user is blocked.
UserIdentity string null $target
User to be blocked, as passed either by parameter (url?wpTarget=Foo) or as subpage (Special:Block/Foo...
getDisplayFormat()
Get display format for the form.
setParameter( $par)
Handle some magic here.
maybeAlterFormDefaults(&$fields)
If the user has already been blocked with similar settings, load that block and change the defaults f...
checkExecutePermissions(User $user)
Check that the user can unblock themselves if they are trying to do so.
doesWrites()
Indicates whether this special page may perform database writes.
static getTargetUserTitle( $target)
Get a user page target for things like logs.
static getTargetAndType(?string $par, WebRequest $request=null)
Get the target and type, given the request and the subpage parameter.
bool $requestedHideUser
Whether the previous submission of the form asked for HideUser.
static processForm(array $data, IContextSource $context)
Given the form data, actually implement a block.
__construct(BlockUtils $blockUtils, BlockPermissionCheckerFactory $blockPermissionCheckerFactory, BlockUserFactory $blockUserFactory, DatabaseBlockStore $blockStore, UserNameUtils $userNameUtils, UserNamePrefixSearch $userNamePrefixSearch, BlockActionInfo $blockActionInfo, TitleFormatter $titleFormatter, NamespaceInfo $namespaceInfo)
getFormFields()
Get the HTMLForm descriptor array for the block form.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:54
This is a utility class for dealing with namespaces that encodes all the "magic" behaviors of them ba...
Represents a title within MediaWiki.
Definition Title.php:78
Handles searching prefixes of user names.
UserNameUtils service.
internal since 1.36
Definition User.php:93
newBlockUser( $target, Authority $performer, string $expiry, string $reason='', array $blockOptions=[], array $blockRestrictions=[], $tags=[])
Create BlockUser.
Interface for objects which can provide a MediaWiki context on request.
Interface for objects (potentially) representing a page that can be viewable and linked to on a wiki.
This interface represents the authority associated with the current execution context,...
Definition Authority.php:37
getUser()
Returns the performer of the actions associated with this authority.
A title formatter service for MediaWiki.
Interface for objects representing user identity.
This program is free software; you can redistribute it and/or modify it under the terms of the GNU Ge...