MediaWiki master
SpecialBlock.php
Go to the documentation of this file.
1<?php
21namespace MediaWiki\Specials;
22
24use HtmlArmor;
25use Language;
58use OOUI\FieldLayout;
59use OOUI\HtmlSnippet;
60use OOUI\LabelWidget;
61use OOUI\Widget;
62use Wikimedia\IPUtils;
63
71
72 private BlockUtils $blockUtils;
73 private BlockPermissionCheckerFactory $blockPermissionCheckerFactory;
74 private BlockUserFactory $blockUserFactory;
75 private DatabaseBlockStore $blockStore;
76 private UserNameUtils $userNameUtils;
77 private UserNamePrefixSearch $userNamePrefixSearch;
78 private BlockActionInfo $blockActionInfo;
79 private TitleFormatter $titleFormatter;
80
84 protected $target;
85
87 protected $type;
88
90 protected $previousTarget;
91
94
96 protected $alreadyBlocked;
97
102 protected $preErrors = [];
103
107 protected $codexFormData = [];
108
109 private NamespaceInfo $namespaceInfo;
110
122 public function __construct(
123 BlockUtils $blockUtils,
124 BlockPermissionCheckerFactory $blockPermissionCheckerFactory,
125 BlockUserFactory $blockUserFactory,
126 DatabaseBlockStore $blockStore,
127 UserNameUtils $userNameUtils,
128 UserNamePrefixSearch $userNamePrefixSearch,
129 BlockActionInfo $blockActionInfo,
130 TitleFormatter $titleFormatter,
131 NamespaceInfo $namespaceInfo
132 ) {
133 parent::__construct( 'Block', 'block' );
134
135 $this->blockUtils = $blockUtils;
136 $this->blockPermissionCheckerFactory = $blockPermissionCheckerFactory;
137 $this->blockUserFactory = $blockUserFactory;
138 $this->blockStore = $blockStore;
139 $this->userNameUtils = $userNameUtils;
140 $this->userNamePrefixSearch = $userNamePrefixSearch;
141 $this->blockActionInfo = $blockActionInfo;
142 $this->titleFormatter = $titleFormatter;
143 $this->namespaceInfo = $namespaceInfo;
144 }
145
146 public function execute( $par ) {
147 parent::execute( $par );
148
149 if ( $this->getConfig()->get( MainConfigNames::UseCodexSpecialBlock ) ) {
150 $this->getOutput()->addModules( 'mediawiki.special.block.codex' );
151 $this->getOutput()->addJsConfigVars( $this->codexFormData );
152 }
153 }
154
155 public function doesWrites() {
156 return true;
157 }
158
165 protected function checkExecutePermissions( User $user ) {
166 parent::checkExecutePermissions( $user );
167 // T17810: blocked admins should have limited access here
168 $status = $this->blockPermissionCheckerFactory
169 ->newBlockPermissionChecker( $this->target, $user )
170 ->checkBlockPermissions();
171 if ( $status !== true ) {
172 throw new ErrorPageError( 'badaccess', $status );
173 }
174 }
175
181 public function requiresUnblock() {
182 return false;
183 }
184
190 protected function setParameter( $par ) {
191 // Extract variables from the request. Try not to get into a situation where we
192 // need to extract *every* variable from the form just for processing here, but
193 // there are legitimate uses for some variables
194 $request = $this->getRequest();
195 [ $this->target, $this->type ] = self::getTargetAndTypeInternal( $par, $request );
196 if ( $this->target instanceof UserIdentity ) {
197 // Set the 'relevant user' in the skin, so it displays links like Contributions,
198 // User logs, UserRights, etc.
199 $this->getSkin()->setRelevantUser( $this->target );
200 }
201
202 [ $this->previousTarget, /*...*/ ] = $this->blockUtils
203 ->parseBlockTarget( $request->getVal( 'wpPreviousTarget' ) );
204 $this->requestedHideUser = $request->getBool( 'wpHideUser' );
205 }
206
212 protected function alterForm( HTMLForm $form ) {
213 $form->setHeaderHtml( '' );
214 $form->setSubmitDestructive();
215
216 $msg = $this->alreadyBlocked ? 'ipb-change-block' : 'ipbsubmit';
217 $form->setSubmitTextMsg( $msg );
218
219 $this->addHelpLink( 'Help:Blocking users' );
220
221 // Don't need to do anything if the form has been posted
222 if ( !$this->getRequest()->wasPosted() && $this->preErrors ) {
223 // Mimic error messages normally generated by the form
224 $form->addHeaderHtml( (string)new FieldLayout(
225 new Widget( [] ),
226 [
227 'align' => 'top',
228 'errors' => array_map( function ( $errMsg ) {
229 // @phan-suppress-next-line PhanParamTooFewUnpack Should infer non-emptiness
230 return new HtmlSnippet( $this->msg( ...$errMsg )->parse() );
231 }, $this->preErrors ),
232 ]
233 ) );
234 }
235 }
236
237 protected function getDisplayFormat() {
238 return $this->getConfig()->get( MainConfigNames::UseCodexSpecialBlock ) ? 'codex' : 'ooui';
239 }
240
245 protected function getFormFields() {
246 $conf = $this->getConfig();
247 $blockAllowsUTEdit = $conf->get( MainConfigNames::BlockAllowsUTEdit );
248
249 $this->getOutput()->enableOOUI();
250
251 $user = $this->getUser();
252
253 $suggestedDurations = $this->getLanguage()->getBlockDurations();
254
255 $a = [];
256
257 $a['Target'] = [
258 'type' => 'user',
259 'ipallowed' => true,
260 'iprange' => true,
261 'id' => 'mw-bi-target',
262 'size' => '45',
263 'autofocus' => true,
264 'required' => true,
265 'placeholder' => $this->msg( 'block-target-placeholder' )->text(),
266 'validation-callback' => function ( $value, $alldata, $form ) {
267 $status = $this->blockUtils->validateTarget( $value );
268 if ( !$status->isOK() ) {
269 $errors = $status->getMessages();
270 return $form->msg( $errors[0] );
271 }
272 return true;
273 },
274 'section' => 'target',
275 ];
276
277 $editingRestrictionOptions = $this->getConfig()->get( MainConfigNames::UseCodexSpecialBlock ) ?
278 // If we're using Codex, use the option-descriptions feature, which is only supported by Codex
279 [
280 'options-messages' => [
281 'ipb-sitewide' => 'sitewide',
282 'ipb-partial' => 'partial'
283 ],
284 'option-descriptions-messages' => [
285 'sitewide' => 'ipb-sitewide-help',
286 'partial' => 'ipb-partial-help'
287 ],
288 'option-descriptions-messages-parse' => true,
289 ] :
290 // Otherwise, if we're using OOUI, add the options' descriptions as part of their labels
291 [
292 'options' => [
293 $this->msg( 'ipb-sitewide' )->escaped() .
294 new LabelWidget( [
295 'classes' => [ 'oo-ui-inline-help' ],
296 'label' => new HtmlSnippet( $this->msg( 'ipb-sitewide-help' )->parse() ),
297 ] ) => 'sitewide',
298 $this->msg( 'ipb-partial' )->escaped() .
299 new LabelWidget( [
300 'classes' => [ 'oo-ui-inline-help' ],
301 'label' => new HtmlSnippet( $this->msg( 'ipb-partial-help' )->parse() ),
302 ] ) => 'partial',
303 ]
304 ];
305
306 $a['EditingRestriction'] = [
307 'type' => 'radio',
308 'cssclass' => 'mw-block-editing-restriction',
309 'default' => 'sitewide',
310 'section' => 'actions',
311 ] + $editingRestrictionOptions;
312
313 $a['PageRestrictions'] = [
314 'type' => 'titlesmultiselect',
315 'label' => $this->msg( 'ipb-pages-label' )->text(),
316 'exists' => true,
317 'max' => 10,
318 'cssclass' => 'mw-htmlform-checkradio-indent mw-block-partial-restriction',
319 'default' => '',
320 'showMissing' => false,
321 'excludeDynamicNamespaces' => true,
322 'input' => [
323 'autocomplete' => false
324 ],
325 'section' => 'actions',
326 ];
327
328 $a['NamespaceRestrictions'] = [
329 'type' => 'namespacesmultiselect',
330 'label' => $this->msg( 'ipb-namespaces-label' )->text(),
331 'exists' => true,
332 'cssclass' => 'mw-htmlform-checkradio-indent mw-block-partial-restriction',
333 'default' => '',
334 'input' => [
335 'autocomplete' => false
336 ],
337 'section' => 'actions',
338 ];
339
340 if ( $conf->get( MainConfigNames::EnablePartialActionBlocks ) ) {
341 $blockActions = $this->blockActionInfo->getAllBlockActions();
342 $optionMessages = array_combine(
343 array_map( static function ( $action ) {
344 return "ipb-action-$action";
345 }, array_keys( $blockActions ) ),
346 $blockActions
347 );
348
349 $this->codexFormData[ 'partialBlockActionOptions'] = $optionMessages;
350
351 $a['ActionRestrictions'] = [
352 'type' => 'multiselect',
353 'cssclass' => 'mw-htmlform-checkradio-indent mw-block-partial-restriction mw-block-action-restriction',
354 'options-messages' => $optionMessages,
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',
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 $this->codexFormData[ 'hideUser'] = true;
440 }
441
442 // Watchlist their user page? (Only if user is logged in)
443 if ( $user->isRegistered() ) {
444 $a['Watch'] = [
445 'type' => 'check',
446 'label-message' => 'ipbwatchuser',
447 'section' => 'options',
448 ];
449 }
450
451 $a['HardBlock'] = [
452 'type' => 'check',
453 'label-message' => 'ipb-hardblock',
454 'default' => false,
455 'section' => 'options',
456 ];
457
458 // This is basically a copy of the Target field, but the user can't change it, so we
459 // can see if the warnings we maybe showed to the user before still apply
460 $a['PreviousTarget'] = [
461 'type' => 'hidden',
462 'default' => false,
463 ];
464
465 // We'll turn this into a checkbox if we need to
466 $a['Confirm'] = [
467 'type' => 'hidden',
468 'default' => '',
469 'label-message' => 'ipb-confirm',
470 'cssclass' => 'mw-block-confirm',
471 ];
472
473 $this->maybeAlterFormDefaults( $a );
474
475 // Allow extensions to add more fields
476 $this->getHookRunner()->onSpecialBlockModifyFormFields( $this, $a );
477
478 return $a;
479 }
480
486 protected function maybeAlterFormDefaults( &$fields ) {
487 // This will be overwritten by request data
488 $fields['Target']['default'] = (string)$this->target;
489
490 if ( $this->target ) {
491 $status = $this->blockUtils->validateTarget( $this->target );
492 if ( !$status->isOK() ) {
493 $errors = $status->getErrorsArray();
494 $this->preErrors = array_merge( $this->preErrors, $errors );
495 }
496 }
497
498 // This won't be
499 $fields['PreviousTarget']['default'] = (string)$this->target;
500
501 $block = $this->blockStore->newFromTarget( $this->target );
502
503 // Populate fields if there is a block that is not an autoblock; if it is a range
504 // block, only populate the fields if the range is the same as $this->target
505 if ( $block instanceof DatabaseBlock && $block->getType() !== DatabaseBlock::TYPE_AUTO
506 && ( $this->type != DatabaseBlock::TYPE_RANGE
507 || ( $this->target && $block->isBlocking( $this->target ) ) )
508 ) {
509 $fields['HardBlock']['default'] = $block->isHardblock();
510 $fields['CreateAccount']['default'] = $block->isCreateAccountBlocked();
511 $fields['AutoBlock']['default'] = $block->isAutoblocking();
512
513 if ( isset( $fields['DisableEmail'] ) ) {
514 $fields['DisableEmail']['default'] = $block->isEmailBlocked();
515 }
516
517 if ( isset( $fields['HideUser'] ) ) {
518 $fields['HideUser']['default'] = $block->getHideName();
519 }
520
521 if ( isset( $fields['DisableUTEdit'] ) ) {
522 $fields['DisableUTEdit']['default'] = !$block->isUsertalkEditAllowed();
523 }
524
525 // If the username was hidden (bl_deleted == 1), don't show the reason
526 // unless this user also has rights to hideuser: T37839
527 if ( !$block->getHideName() || $this->getAuthority()->isAllowed( 'hideuser' ) ) {
528 $fields['Reason']['default'] = $block->getReasonComment()->text;
529 } else {
530 $fields['Reason']['default'] = '';
531 }
532
533 if ( $this->getRequest()->wasPosted() ) {
534 // Ok, so we got a POST submission asking us to reblock a user. So show the
535 // confirm checkbox; the user will only see it if they haven't previously
536 $fields['Confirm']['type'] = 'check';
537 } else {
538 // We got a target, but it wasn't a POST request, so the user must have gone
539 // to a link like [[Special:Block/User]]. We don't need to show the checkbox
540 // as long as they go ahead and block *that* user
541 $fields['Confirm']['default'] = 1;
542 }
543
544 if ( $block->getExpiry() == 'infinity' ) {
545 $fields['Expiry']['default'] = 'infinite';
546 } else {
547 $fields['Expiry']['default'] = wfTimestamp( TS_RFC2822, $block->getExpiry() );
548 }
549
550 if ( !$block->isSitewide() ) {
551 $fields['EditingRestriction']['default'] = 'partial';
552
553 $pageRestrictions = [];
554 $namespaceRestrictions = [];
555 foreach ( $block->getRestrictions() as $restriction ) {
556 if ( $restriction instanceof PageRestriction && $restriction->getTitle() ) {
557 $pageRestrictions[] = $restriction->getTitle()->getPrefixedText();
558 } elseif ( $restriction instanceof NamespaceRestriction &&
559 $this->namespaceInfo->exists( $restriction->getValue() )
560 ) {
561 $namespaceRestrictions[] = $restriction->getValue();
562 }
563 }
564
565 // Sort the restrictions so they are in alphabetical order.
566 sort( $pageRestrictions );
567 $fields['PageRestrictions']['default'] = implode( "\n", $pageRestrictions );
568 sort( $namespaceRestrictions );
569 $fields['NamespaceRestrictions']['default'] = implode( "\n", $namespaceRestrictions );
570
572 $actionRestrictions = [];
573 foreach ( $block->getRestrictions() as $restriction ) {
574 if ( $restriction instanceof ActionRestriction ) {
575 $actionRestrictions[] = $restriction->getValue();
576 }
577 }
578 $fields['ActionRestrictions']['default'] = $actionRestrictions;
579 }
580 }
581
582 $this->alreadyBlocked = true;
583 $this->preErrors[] = [ 'ipb-needreblock', wfEscapeWikiText( $block->getTargetName() ) ];
584 }
585
586 if ( $this->alreadyBlocked || $this->getRequest()->wasPosted()
587 || $this->getRequest()->getCheck( 'wpCreateAccount' )
588 ) {
589 $this->getOutput()->addJsConfigVars( 'wgCreateAccountDirty', true );
590 }
591
592 // We always need confirmation to do HideUser
593 if ( $this->requestedHideUser ) {
594 $fields['Confirm']['type'] = 'check';
595 unset( $fields['Confirm']['default'] );
596 $this->preErrors[] = [ 'ipb-confirmhideuser', 'ipb-confirmaction' ];
597 }
598
599 // Or if the user is trying to block themselves
600 if ( (string)$this->target === $this->getUser()->getName() ) {
601 $fields['Confirm']['type'] = 'check';
602 unset( $fields['Confirm']['default'] );
603 $this->preErrors[] = [ 'ipb-blockingself', 'ipb-confirmaction' ];
604 }
605 }
606
611 protected function preHtml() {
612 $this->getOutput()->addModuleStyles( [ 'mediawiki.special' ] );
613 $this->getOutput()->addModules( [ 'mediawiki.special.block' ] );
614
615 $blockCIDRLimit = $this->getConfig()->get( MainConfigNames::BlockCIDRLimit );
616 $text = $this->msg( 'blockiptext', $blockCIDRLimit['IPv4'], $blockCIDRLimit['IPv6'] )->parse();
617
618 $otherBlockMessages = [];
619 if ( $this->target !== null ) {
620 $targetName = $this->target;
621 if ( $this->target instanceof UserIdentity ) {
622 $targetName = $this->target->getName();
623 }
624 // Get other blocks, i.e. from GlobalBlocking or TorBlock extension
625 $this->getHookRunner()->onOtherBlockLogLink(
626 $otherBlockMessages, $targetName );
627
628 if ( count( $otherBlockMessages ) ) {
629 $s = Html::rawElement(
630 'h2',
631 [],
632 $this->msg( 'ipb-otherblocks-header', count( $otherBlockMessages ) )->parse()
633 ) . "\n";
634
635 $list = '';
636
637 foreach ( $otherBlockMessages as $link ) {
638 $list .= Html::rawElement( 'li', [], $link ) . "\n";
639 }
640
641 $s .= Html::rawElement(
642 'ul',
643 [ 'class' => 'mw-blockip-alreadyblocked' ],
644 $list
645 ) . "\n";
646
647 $text .= $s;
648 }
649 }
650
651 return $text;
652 }
653
658 protected function postHtml() {
659 $links = [];
660
661 $this->getOutput()->addModuleStyles( 'mediawiki.special' );
662
663 $linkRenderer = $this->getLinkRenderer();
664 // Link to the user's contributions, if applicable
665 if ( $this->target instanceof UserIdentity ) {
666 $contribsPage = SpecialPage::getTitleFor( 'Contributions', $this->target->getName() );
667 $links[] = $linkRenderer->makeLink(
668 $contribsPage,
669 $this->msg( 'ipb-blocklist-contribs', $this->target->getName() )->text()
670 );
671 }
672
673 // Link to unblock the specified user, or to a blank unblock form
674 if ( $this->target instanceof UserIdentity ) {
675 $message = $this->msg(
676 'ipb-unblock-addr',
677 wfEscapeWikiText( $this->target->getName() )
678 )->parse();
679 $list = SpecialPage::getTitleFor( 'Unblock', $this->target->getName() );
680 } else {
681 $message = $this->msg( 'ipb-unblock' )->parse();
682 $list = SpecialPage::getTitleFor( 'Unblock' );
683 }
684 $links[] = $linkRenderer->makeKnownLink(
685 $list,
686 new HtmlArmor( $message )
687 );
688
689 // Link to the block list
690 $links[] = $linkRenderer->makeKnownLink(
691 SpecialPage::getTitleFor( 'BlockList' ),
692 $this->msg( 'ipb-blocklist' )->text()
693 );
694
695 // Link to edit the block dropdown reasons, if applicable
696 if ( $this->getAuthority()->isAllowed( 'editinterface' ) ) {
697 $links[] = $linkRenderer->makeKnownLink(
698 $this->msg( 'ipbreason-dropdown' )->inContentLanguage()->getTitle(),
699 $this->msg( 'ipb-edit-dropdown' )->text(),
700 [],
701 [ 'action' => 'edit' ]
702 );
703 }
704
705 $text = Html::rawElement(
706 'p',
707 [ 'class' => 'mw-ipb-conveniencelinks' ],
708 $this->getLanguage()->pipeList( $links )
709 );
710
711 $userPage = self::getTargetUserTitle( $this->target );
712 if ( $userPage ) {
713 // Get relevant extracts from the block and suppression logs, if possible
714 $out = '';
715
716 LogEventsList::showLogExtract(
717 $out,
718 'block',
719 $userPage,
720 '',
721 [
722 'lim' => 10,
723 'msgKey' => [
724 'blocklog-showlog',
725 $this->titleFormatter->getText( $userPage ),
726 ],
727 'showIfEmpty' => false
728 ]
729 );
730 $text .= $out;
731
732 // Add suppression block entries if allowed
733 if ( $this->getAuthority()->isAllowed( 'suppressionlog' ) ) {
734 LogEventsList::showLogExtract(
735 $out,
736 'suppress',
737 $userPage,
738 '',
739 [
740 'lim' => 10,
741 'conds' => [ 'log_action' => [ 'block', 'reblock', 'unblock' ] ],
742 'msgKey' => [
743 'blocklog-showsuppresslog',
744 $this->titleFormatter->getText( $userPage ),
745 ],
746 'showIfEmpty' => false
747 ]
748 );
749
750 $text .= $out;
751 }
752 }
753
754 return $text;
755 }
756
763 protected static function getTargetUserTitle( $target ): ?PageReference {
764 if ( $target instanceof UserIdentity ) {
765 return PageReferenceValue::localReference( NS_USER, $target->getName() );
766 }
767
768 if ( is_string( $target ) && IPUtils::isIPAddress( $target ) ) {
769 return PageReferenceValue::localReference( NS_USER, $target );
770 }
771
772 return null;
773 }
774
786 private static function getTargetAndTypeInternal( ?string $par, WebRequest $request = null ) {
787 if ( !$request instanceof WebRequest ) {
788 return MediaWikiServices::getInstance()->getBlockUtils()->parseBlockTarget( $par );
789 }
790
791 $possibleTargets = [
792 $request->getVal( 'wpTarget', null ),
793 $par,
794 $request->getVal( 'ip', null ),
795 // B/C @since 1.18
796 $request->getVal( 'wpBlockAddress', null ),
797 ];
798 foreach ( $possibleTargets as $possibleTarget ) {
799 $targetAndType = MediaWikiServices::getInstance()
800 ->getBlockUtils()
801 ->parseBlockTarget( $possibleTarget );
802 // If type is not null then target is valid
803 if ( $targetAndType[ 1 ] !== null ) {
804 break;
805 }
806 }
807 return $targetAndType;
808 }
809
818 public static function processForm( array $data, IContextSource $context ) {
819 $services = MediaWikiServices::getInstance();
820 return self::processFormInternal(
821 $data,
822 $context->getAuthority(),
823 $services->getBlockUserFactory(),
824 $services->getBlockUtils()
825 );
826 }
827
838 private static function processFormInternal(
839 array $data,
840 Authority $performer,
841 BlockUserFactory $blockUserFactory,
842 BlockUtils $blockUtils
843 ) {
844 // Temporarily access service container until the feature flag is removed: T280532
845 $enablePartialActionBlocks = MediaWikiServices::getInstance()
846 ->getMainConfig()->get( MainConfigNames::EnablePartialActionBlocks );
847
848 $isPartialBlock = isset( $data['EditingRestriction'] ) &&
849 $data['EditingRestriction'] === 'partial';
850
851 // This might have been a hidden field or a checkbox, so interesting data
852 // can come from it
853 $data['Confirm'] = !in_array( $data['Confirm'], [ '', '0', null, false ], true );
854
855 // If the user has done the form 'properly', they won't even have been given the
856 // option to suppress-block unless they have the 'hideuser' permission
857 if ( !isset( $data['HideUser'] ) ) {
858 $data['HideUser'] = false;
859 }
860
862 [ $target, $type ] = $blockUtils->parseBlockTarget( $data['Target'] );
863 if ( $type == DatabaseBlock::TYPE_USER ) {
864 $target = $target->getName();
865
866 // Give admins a heads-up before they go and block themselves. Much messier
867 // to do this for IPs, but it's pretty unlikely they'd ever get the 'block'
868 // permission anyway, although the code does allow for it.
869 // Note: Important to use $target instead of $data['Target']
870 // since both $data['PreviousTarget'] and $target are normalized
871 // but $data['target'] gets overridden by (non-normalized) request variable
872 // from previous request.
873 if ( $target === $performer->getUser()->getName() &&
874 ( $data['PreviousTarget'] !== $target || !$data['Confirm'] )
875 ) {
876 return [ 'ipb-blockingself', 'ipb-confirmaction' ];
877 }
878
879 if ( $data['HideUser'] && !$data['Confirm'] ) {
880 return [ 'ipb-confirmhideuser', 'ipb-confirmaction' ];
881 }
882 } elseif ( $type == DatabaseBlock::TYPE_IP ) {
883 $target = $target->getName();
884 } elseif ( $type != DatabaseBlock::TYPE_RANGE ) {
885 // This should have been caught in the form field validation
886 return [ 'badipaddress' ];
887 }
888
889 // Reason, to be passed to the block object. For default values of reason, see
890 // HTMLSelectAndOtherField::getDefault
891 $blockReason = $data['Reason'][0] ?? '';
892
893 $pageRestrictions = [];
894 $namespaceRestrictions = [];
895 $actionRestrictions = [];
896 if ( $isPartialBlock ) {
897 if ( isset( $data['PageRestrictions'] ) && $data['PageRestrictions'] !== '' ) {
898 $titles = explode( "\n", $data['PageRestrictions'] );
899 foreach ( $titles as $title ) {
900 $pageRestrictions[] = PageRestriction::newFromTitle( $title );
901 }
902 }
903 if ( isset( $data['NamespaceRestrictions'] ) && $data['NamespaceRestrictions'] !== '' ) {
904 $namespaceRestrictions = array_map( static function ( $id ) {
905 return new NamespaceRestriction( 0, (int)$id );
906 }, explode( "\n", $data['NamespaceRestrictions'] ) );
907 }
908 if (
909 $enablePartialActionBlocks &&
910 isset( $data['ActionRestrictions'] ) &&
911 $data['ActionRestrictions'] !== ''
912 ) {
913 $actionRestrictions = array_map( static function ( $id ) {
914 return new ActionRestriction( 0, $id );
915 }, $data['ActionRestrictions'] );
916 }
917 }
918 $restrictions = array_merge( $pageRestrictions, $namespaceRestrictions, $actionRestrictions );
919
920 if ( !isset( $data['Tags'] ) ) {
921 $data['Tags'] = [];
922 }
923
924 $blockOptions = [
925 'isCreateAccountBlocked' => $data['CreateAccount'],
926 'isHardBlock' => $data['HardBlock'],
927 'isAutoblocking' => $data['AutoBlock'],
928 'isHideUser' => $data['HideUser'],
929 'isPartial' => $isPartialBlock,
930 ];
931
932 if ( isset( $data['DisableUTEdit'] ) ) {
933 $blockOptions['isUserTalkEditBlocked'] = $data['DisableUTEdit'];
934 }
935 if ( isset( $data['DisableEmail'] ) ) {
936 $blockOptions['isEmailBlocked'] = $data['DisableEmail'];
937 }
938
939 $blockUser = $blockUserFactory->newBlockUser(
940 $target,
941 $performer,
942 $data['Expiry'],
943 $blockReason,
944 $blockOptions,
945 $restrictions,
946 $data['Tags']
947 );
948
949 // Indicates whether the user is confirming the block and is aware of
950 // the conflict (did not change the block target in the meantime)
951 $blockNotConfirmed = !$data['Confirm'] || ( array_key_exists( 'PreviousTarget', $data )
952 && $data['PreviousTarget'] !== $target );
953
954 // Special case for API - T34434
955 $reblockNotAllowed = ( array_key_exists( 'Reblock', $data ) && !$data['Reblock'] );
956
957 $doReblock = !$blockNotConfirmed && !$reblockNotAllowed;
958
959 $status = $blockUser->placeBlock( $doReblock );
960 if ( !$status->isOK() ) {
961 return $status;
962 }
963
964 if (
965 // Can't watch a range block
966 $type != DatabaseBlock::TYPE_RANGE
967
968 // Technically a wiki can be configured to allow anonymous users to place blocks,
969 // in which case the 'Watch' field isn't included in the form shown, and we should
970 // not try to access it.
971 && array_key_exists( 'Watch', $data )
972 && $data['Watch']
973 ) {
974 MediaWikiServices::getInstance()->getWatchlistManager()->addWatchIgnoringRights(
975 $performer->getUser(),
976 Title::makeTitle( NS_USER, $target )
977 );
978 }
979
980 return true;
981 }
982
994 public static function getSuggestedDurations( Language $lang = null, $includeOther = true ) {
995 $lang ??= MediaWikiServices::getInstance()->getContentLanguage();
996 return $lang->getBlockDurations( $includeOther );
997 }
998
1008 public static function parseExpiryInput( $expiry ) {
1009 return BlockUser::parseExpiryInput( $expiry );
1010 }
1011
1019 public static function canBlockEmail( UserIdentity $user ) {
1021 ->getBlockPermissionCheckerFactory()
1022 ->newBlockPermissionChecker( null, User::newFromIdentity( $user ) )
1023 ->checkEmailPermissions();
1024 }
1025
1032 public function onSubmit( array $data, HTMLForm $form = null ) {
1033 return self::processFormInternal(
1034 $data,
1035 $this->getAuthority(),
1036 $this->blockUserFactory,
1037 $this->blockUtils
1038 );
1039 }
1040
1045 public function onSuccess() {
1046 $out = $this->getOutput();
1047 $out->setPageTitleMsg( $this->msg( 'blockipsuccesssub' ) );
1048 $out->addWikiMsg( 'blockipsuccesstext', wfEscapeWikiText( $this->target ) );
1049 }
1050
1059 public function prefixSearchSubpages( $search, $limit, $offset ) {
1060 $search = $this->userNameUtils->getCanonical( $search );
1061 if ( !$search ) {
1062 // No prefix suggestion for invalid user
1063 return [];
1064 }
1065 // Autocomplete subpage as user list - public to allow caching
1066 return $this->userNamePrefixSearch
1067 ->search( UserNamePrefixSearch::AUDIENCE_PUBLIC, $search, $limit, $offset );
1068 }
1069
1070 protected function getGroupName() {
1071 return 'users';
1072 }
1073}
1074
1076class_alias( SpecialBlock::class, 'SpecialBlock' );
getAuthority()
const NS_USER
Definition Defines.php:67
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.
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:65
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:963
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:943
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 UseCodexSpecialBlock
Name constant for the UseCodexSpecialBlock setting, for use with Config::get()
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:158
static durationParam( $duration)
Definition Message.php:1231
Immutable value object representing a page reference.
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form,...
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.
Allow users with 'block' user right to block IPs and user accounts 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.
array $codexFormData
<mixed,mixed> An associative array used to pass vars to Codex form
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.
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.
execute( $par)
Basic SpecialPage workflow: get a form, send it to the user; get some data back,.
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:79
Handles searching prefixes of user names.
UserNameUtils service.
internal since 1.36
Definition User.php:94
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.