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
110 protected $codexFormData = [];
111
112 private NamespaceInfo $namespaceInfo;
113
125 public function __construct(
126 BlockUtils $blockUtils,
127 BlockPermissionCheckerFactory $blockPermissionCheckerFactory,
128 BlockUserFactory $blockUserFactory,
129 DatabaseBlockStore $blockStore,
130 UserNameUtils $userNameUtils,
131 UserNamePrefixSearch $userNamePrefixSearch,
132 BlockActionInfo $blockActionInfo,
133 TitleFormatter $titleFormatter,
134 NamespaceInfo $namespaceInfo
135 ) {
136 parent::__construct( 'Block', 'block' );
137
138 $this->blockUtils = $blockUtils;
139 $this->blockPermissionCheckerFactory = $blockPermissionCheckerFactory;
140 $this->blockUserFactory = $blockUserFactory;
141 $this->blockStore = $blockStore;
142 $this->userNameUtils = $userNameUtils;
143 $this->userNamePrefixSearch = $userNamePrefixSearch;
144 $this->blockActionInfo = $blockActionInfo;
145 $this->titleFormatter = $titleFormatter;
146 $this->namespaceInfo = $namespaceInfo;
147 }
148
149 public function execute( $par ) {
150 parent::execute( $par );
151
152 if ( $this->getConfig()->get( 'UseCodexSpecialBlock' ) ) {
153 $this->getOutput()->addModules( 'mediawiki.special.block.codex' );
154 $this->getOutput()->addJsConfigVars( $this->codexFormData );
155 }
156 }
157
158 public function doesWrites() {
159 return true;
160 }
161
168 protected function checkExecutePermissions( User $user ) {
169 parent::checkExecutePermissions( $user );
170 // T17810: blocked admins should have limited access here
171 $status = $this->blockPermissionCheckerFactory
172 ->newBlockPermissionChecker( $this->target, $user )
173 ->checkBlockPermissions();
174 if ( $status !== true ) {
175 throw new ErrorPageError( 'badaccess', $status );
176 }
177 }
178
184 public function requiresUnblock() {
185 return false;
186 }
187
193 protected function setParameter( $par ) {
194 // Extract variables from the request. Try not to get into a situation where we
195 // need to extract *every* variable from the form just for processing here, but
196 // there are legitimate uses for some variables
197 $request = $this->getRequest();
198 [ $this->target, $this->type ] = self::getTargetAndTypeInternal( $par, $request );
199 if ( $this->target instanceof UserIdentity ) {
200 // Set the 'relevant user' in the skin, so it displays links like Contributions,
201 // User logs, UserRights, etc.
202 $this->getSkin()->setRelevantUser( $this->target );
203 }
204
205 [ $this->previousTarget, /*...*/ ] = $this->blockUtils
206 ->parseBlockTarget( $request->getVal( 'wpPreviousTarget' ) );
207 $this->requestedHideUser = $request->getBool( 'wpHideUser' );
208 }
209
215 protected function alterForm( HTMLForm $form ) {
216 $form->setHeaderHtml( '' );
217 $form->setSubmitDestructive();
218
219 $msg = $this->alreadyBlocked ? 'ipb-change-block' : 'ipbsubmit';
220 $form->setSubmitTextMsg( $msg );
221
222 $this->addHelpLink( 'Help:Blocking users' );
223
224 // Don't need to do anything if the form has been posted
225 if ( !$this->getRequest()->wasPosted() && $this->preErrors ) {
226 // Mimic error messages normally generated by the form
227 $form->addHeaderHtml( (string)new FieldLayout(
228 new Widget( [] ),
229 [
230 'align' => 'top',
231 'errors' => array_map( function ( $errMsg ) {
232 // @phan-suppress-next-line PhanParamTooFewUnpack Should infer non-emptiness
233 return new HtmlSnippet( $this->msg( ...$errMsg )->parse() );
234 }, $this->preErrors ),
235 ]
236 ) );
237 }
238 }
239
240 protected function getDisplayFormat() {
241 return $this->getConfig()->get( 'UseCodexSpecialBlock' ) ? 'codex' : 'ooui';
242 }
243
248 protected function getFormFields() {
249 $conf = $this->getConfig();
250 $blockAllowsUTEdit = $conf->get( MainConfigNames::BlockAllowsUTEdit );
251
252 $this->getOutput()->enableOOUI();
253
254 $user = $this->getUser();
255
256 $suggestedDurations = $this->getLanguage()->getBlockDurations();
257
258 $a = [];
259
260 $a['Target'] = [
261 'type' => 'user',
262 'ipallowed' => true,
263 'iprange' => true,
264 'id' => 'mw-bi-target',
265 'size' => '45',
266 'autofocus' => true,
267 'required' => true,
268 'placeholder' => $this->msg( 'block-target-placeholder' )->text(),
269 'validation-callback' => function ( $value, $alldata, $form ) {
270 $status = $this->blockUtils->validateTarget( $value );
271 if ( !$status->isOK() ) {
272 $errors = $status->getErrorsArray();
273
274 return $form->msg( ...$errors[0] );
275 }
276 return true;
277 },
278 'section' => 'target',
279 ];
280
281 $editingRestrictionOptions = $this->getConfig()->get( 'UseCodexSpecialBlock' ) ?
282 // If we're using Codex, use the option-descriptions feature, which is only supported by Codex
283 [
284 'options-messages' => [
285 'ipb-sitewide' => 'sitewide',
286 'ipb-partial' => 'partial'
287 ],
288 'option-descriptions-messages' => [
289 'sitewide' => 'ipb-sitewide-help',
290 'partial' => 'ipb-partial-help'
291 ],
292 'option-descriptions-messages-parse' => true,
293 ] :
294 // Otherwise, if we're using OOUI, add the options' descriptions as part of their labels
295 [
296 'options' => [
297 $this->msg( 'ipb-sitewide' )->escaped() .
298 new LabelWidget( [
299 'classes' => [ 'oo-ui-inline-help' ],
300 'label' => new HtmlSnippet( $this->msg( 'ipb-sitewide-help' )->parse() ),
301 ] ) => 'sitewide',
302 $this->msg( 'ipb-partial' )->escaped() .
303 new LabelWidget( [
304 'classes' => [ 'oo-ui-inline-help' ],
305 'label' => new HtmlSnippet( $this->msg( 'ipb-partial-help' )->parse() ),
306 ] ) => 'partial',
307 ]
308 ];
309
310 $a['EditingRestriction'] = [
311 'type' => 'radio',
312 'cssclass' => 'mw-block-editing-restriction',
313 'default' => 'sitewide',
314 'section' => 'actions',
315 ] + $editingRestrictionOptions;
316
317 $a['PageRestrictions'] = [
318 'type' => 'titlesmultiselect',
319 'label' => $this->msg( 'ipb-pages-label' )->text(),
320 'exists' => true,
321 'max' => 10,
322 'cssclass' => 'mw-htmlform-checkradio-indent mw-block-partial-restriction',
323 'default' => '',
324 'showMissing' => false,
325 'excludeDynamicNamespaces' => true,
326 'input' => [
327 'autocomplete' => false
328 ],
329 'section' => 'actions',
330 ];
331
332 $a['NamespaceRestrictions'] = [
333 'type' => 'namespacesmultiselect',
334 'label' => $this->msg( 'ipb-namespaces-label' )->text(),
335 'exists' => true,
336 'cssclass' => 'mw-htmlform-checkradio-indent mw-block-partial-restriction',
337 'default' => '',
338 'input' => [
339 'autocomplete' => false
340 ],
341 'section' => 'actions',
342 ];
343
344 if ( $conf->get( MainConfigNames::EnablePartialActionBlocks ) ) {
345 $blockActions = $this->blockActionInfo->getAllBlockActions();
346 $a['ActionRestrictions'] = [
347 'type' => 'multiselect',
348 'cssclass' => 'mw-htmlform-checkradio-indent mw-block-partial-restriction mw-block-action-restriction',
349 'options-messages' => array_combine(
350 array_map( static function ( $action ) {
351 return "ipb-action-$action";
352 }, array_keys( $blockActions ) ),
353 $blockActions
354 ),
355 'section' => 'actions',
356 ];
357 }
358
359 $a['CreateAccount'] = [
360 'type' => 'check',
361 'cssclass' => 'mw-block-restriction',
362 'label-message' => 'ipbcreateaccount',
363 'default' => true,
364 'section' => 'details',
365 ];
366
367 if ( $this->blockPermissionCheckerFactory
368 ->newBlockPermissionChecker( null, $user )
369 ->checkEmailPermissions()
370 ) {
371 $a['DisableEmail'] = [
372 'type' => 'check',
373 'cssclass' => 'mw-block-restriction',
374 'label-message' => 'ipbemailban',
375 'section' => 'details',
376 ];
377
378 $this->codexFormData[ 'blockAllowsEmailBan'] = true;
379 }
380
381 if ( $blockAllowsUTEdit ) {
382 $a['DisableUTEdit'] = [
383 'type' => 'check',
384 'cssclass' => 'mw-block-restriction',
385 'label-message' => 'ipb-disableusertalk',
386 'default' => false,
387 'section' => 'details',
388 ];
389
390 $this->codexFormData[ 'blockAllowsUTEdit'] = true;
391 }
392
393 $defaultExpiry = $this->msg( 'ipb-default-expiry' )->inContentLanguage();
394 if ( $this->type === DatabaseBlock::TYPE_RANGE || $this->type === DatabaseBlock::TYPE_IP ) {
395 $defaultExpiryIP = $this->msg( 'ipb-default-expiry-ip' )->inContentLanguage();
396 if ( !$defaultExpiryIP->isDisabled() ) {
397 $defaultExpiry = $defaultExpiryIP;
398 }
399 }
400
401 $a['Expiry'] = [
402 'type' => 'expiry',
403 'required' => true,
404 'options' => $suggestedDurations,
405 'default' => $defaultExpiry->text(),
406 'section' => 'expiry',
407 ];
408
409 $a['Reason'] = [
410 'type' => 'selectandother',
411 // HTML maxlength uses "UTF-16 code units", which means that characters outside BMP
412 // (e.g. emojis) count for two each. This limit is overridden in JS to instead count
413 // Unicode codepoints.
414 'maxlength' => CommentStore::COMMENT_CHARACTER_LIMIT,
415 'maxlength-unit' => 'codepoints',
416 'options-message' => 'ipbreason-dropdown',
417 'section' => 'reason',
418 ];
419
420 $a['AutoBlock'] = [
421 'type' => 'check',
422 'label-message' => [
423 'ipbenableautoblock',
425 ],
426 'default' => true,
427 'section' => 'options',
428 ];
429
430 // Allow some users to hide name from block log, blocklist and listusers
431 if ( $this->getAuthority()->isAllowed( 'hideuser' ) ) {
432 $a['HideUser'] = [
433 'type' => 'check',
434 'label-message' => 'ipbhidename',
435 'cssclass' => 'mw-block-hideuser',
436 'section' => 'options',
437 ];
438 }
439
440 // Watchlist their user page? (Only if user is logged in)
441 if ( $user->isRegistered() ) {
442 $a['Watch'] = [
443 'type' => 'check',
444 'label-message' => 'ipbwatchuser',
445 'section' => 'options',
446 ];
447 }
448
449 $a['HardBlock'] = [
450 'type' => 'check',
451 'label-message' => 'ipb-hardblock',
452 'default' => false,
453 'section' => 'options',
454 ];
455
456 // This is basically a copy of the Target field, but the user can't change it, so we
457 // can see if the warnings we maybe showed to the user before still apply
458 $a['PreviousTarget'] = [
459 'type' => 'hidden',
460 'default' => false,
461 ];
462
463 // We'll turn this into a checkbox if we need to
464 $a['Confirm'] = [
465 'type' => 'hidden',
466 'default' => '',
467 'label-message' => 'ipb-confirm',
468 'cssclass' => 'mw-block-confirm',
469 ];
470
471 $this->maybeAlterFormDefaults( $a );
472
473 // Allow extensions to add more fields
474 $this->getHookRunner()->onSpecialBlockModifyFormFields( $this, $a );
475
476 return $a;
477 }
478
484 protected function maybeAlterFormDefaults( &$fields ) {
485 // This will be overwritten by request data
486 $fields['Target']['default'] = (string)$this->target;
487
488 if ( $this->target ) {
489 $status = $this->blockUtils->validateTarget( $this->target );
490 if ( !$status->isOK() ) {
491 $errors = $status->getErrorsArray();
492 $this->preErrors = array_merge( $this->preErrors, $errors );
493 }
494 }
495
496 // This won't be
497 $fields['PreviousTarget']['default'] = (string)$this->target;
498
499 $block = $this->blockStore->newFromTarget( $this->target );
500
501 // Populate fields if there is a block that is not an autoblock; if it is a range
502 // block, only populate the fields if the range is the same as $this->target
503 if ( $block instanceof DatabaseBlock && $block->getType() !== DatabaseBlock::TYPE_AUTO
504 && ( $this->type != DatabaseBlock::TYPE_RANGE
505 || ( $this->target && $block->isBlocking( $this->target ) ) )
506 ) {
507 $fields['HardBlock']['default'] = $block->isHardblock();
508 $fields['CreateAccount']['default'] = $block->isCreateAccountBlocked();
509 $fields['AutoBlock']['default'] = $block->isAutoblocking();
510
511 if ( isset( $fields['DisableEmail'] ) ) {
512 $fields['DisableEmail']['default'] = $block->isEmailBlocked();
513 }
514
515 if ( isset( $fields['HideUser'] ) ) {
516 $fields['HideUser']['default'] = $block->getHideName();
517 }
518
519 if ( isset( $fields['DisableUTEdit'] ) ) {
520 $fields['DisableUTEdit']['default'] = !$block->isUsertalkEditAllowed();
521 }
522
523 // If the username was hidden (bl_deleted == 1), don't show the reason
524 // unless this user also has rights to hideuser: T37839
525 if ( !$block->getHideName() || $this->getAuthority()->isAllowed( 'hideuser' ) ) {
526 $fields['Reason']['default'] = $block->getReasonComment()->text;
527 } else {
528 $fields['Reason']['default'] = '';
529 }
530
531 if ( $this->getRequest()->wasPosted() ) {
532 // Ok, so we got a POST submission asking us to reblock a user. So show the
533 // confirm checkbox; the user will only see it if they haven't previously
534 $fields['Confirm']['type'] = 'check';
535 } else {
536 // We got a target, but it wasn't a POST request, so the user must have gone
537 // to a link like [[Special:Block/User]]. We don't need to show the checkbox
538 // as long as they go ahead and block *that* user
539 $fields['Confirm']['default'] = 1;
540 }
541
542 if ( $block->getExpiry() == 'infinity' ) {
543 $fields['Expiry']['default'] = 'infinite';
544 } else {
545 $fields['Expiry']['default'] = wfTimestamp( TS_RFC2822, $block->getExpiry() );
546 }
547
548 if ( !$block->isSitewide() ) {
549 $fields['EditingRestriction']['default'] = 'partial';
550
551 $pageRestrictions = [];
552 $namespaceRestrictions = [];
553 foreach ( $block->getRestrictions() as $restriction ) {
554 if ( $restriction instanceof PageRestriction && $restriction->getTitle() ) {
555 $pageRestrictions[] = $restriction->getTitle()->getPrefixedText();
556 } elseif ( $restriction instanceof NamespaceRestriction &&
557 $this->namespaceInfo->exists( $restriction->getValue() )
558 ) {
559 $namespaceRestrictions[] = $restriction->getValue();
560 }
561 }
562
563 // Sort the restrictions so they are in alphabetical order.
564 sort( $pageRestrictions );
565 $fields['PageRestrictions']['default'] = implode( "\n", $pageRestrictions );
566 sort( $namespaceRestrictions );
567 $fields['NamespaceRestrictions']['default'] = implode( "\n", $namespaceRestrictions );
568
570 $actionRestrictions = [];
571 foreach ( $block->getRestrictions() as $restriction ) {
572 if ( $restriction instanceof ActionRestriction ) {
573 $actionRestrictions[] = $restriction->getValue();
574 }
575 }
576 $fields['ActionRestrictions']['default'] = $actionRestrictions;
577 }
578 }
579
580 $this->alreadyBlocked = true;
581 $this->preErrors[] = [ 'ipb-needreblock', wfEscapeWikiText( $block->getTargetName() ) ];
582 }
583
584 if ( $this->alreadyBlocked || $this->getRequest()->wasPosted()
585 || $this->getRequest()->getCheck( 'wpCreateAccount' )
586 ) {
587 $this->getOutput()->addJsConfigVars( 'wgCreateAccountDirty', true );
588 }
589
590 // We always need confirmation to do HideUser
591 if ( $this->requestedHideUser ) {
592 $fields['Confirm']['type'] = 'check';
593 unset( $fields['Confirm']['default'] );
594 $this->preErrors[] = [ 'ipb-confirmhideuser', 'ipb-confirmaction' ];
595 }
596
597 // Or if the user is trying to block themselves
598 if ( (string)$this->target === $this->getUser()->getName() ) {
599 $fields['Confirm']['type'] = 'check';
600 unset( $fields['Confirm']['default'] );
601 $this->preErrors[] = [ 'ipb-blockingself', 'ipb-confirmaction' ];
602 }
603 }
604
609 protected function preHtml() {
610 $this->getOutput()->addModuleStyles( [ 'mediawiki.special' ] );
611 $this->getOutput()->addModules( [ 'mediawiki.special.block' ] );
612
613 $blockCIDRLimit = $this->getConfig()->get( MainConfigNames::BlockCIDRLimit );
614 $text = $this->msg( 'blockiptext', $blockCIDRLimit['IPv4'], $blockCIDRLimit['IPv6'] )->parse();
615
616 $otherBlockMessages = [];
617 if ( $this->target !== null ) {
618 $targetName = $this->target;
619 if ( $this->target instanceof UserIdentity ) {
620 $targetName = $this->target->getName();
621 }
622 // Get other blocks, i.e. from GlobalBlocking or TorBlock extension
623 $this->getHookRunner()->onOtherBlockLogLink(
624 $otherBlockMessages, $targetName );
625
626 if ( count( $otherBlockMessages ) ) {
627 $s = Html::rawElement(
628 'h2',
629 [],
630 $this->msg( 'ipb-otherblocks-header', count( $otherBlockMessages ) )->parse()
631 ) . "\n";
632
633 $list = '';
634
635 foreach ( $otherBlockMessages as $link ) {
636 $list .= Html::rawElement( 'li', [], $link ) . "\n";
637 }
638
639 $s .= Html::rawElement(
640 'ul',
641 [ 'class' => 'mw-blockip-alreadyblocked' ],
642 $list
643 ) . "\n";
644
645 $text .= $s;
646 }
647 }
648
649 return $text;
650 }
651
656 protected function postHtml() {
657 $links = [];
658
659 $this->getOutput()->addModuleStyles( 'mediawiki.special' );
660
661 $linkRenderer = $this->getLinkRenderer();
662 // Link to the user's contributions, if applicable
663 if ( $this->target instanceof UserIdentity ) {
664 $contribsPage = SpecialPage::getTitleFor( 'Contributions', $this->target->getName() );
665 $links[] = $linkRenderer->makeLink(
666 $contribsPage,
667 $this->msg( 'ipb-blocklist-contribs', $this->target->getName() )->text()
668 );
669 }
670
671 // Link to unblock the specified user, or to a blank unblock form
672 if ( $this->target instanceof UserIdentity ) {
673 $message = $this->msg(
674 'ipb-unblock-addr',
675 wfEscapeWikiText( $this->target->getName() )
676 )->parse();
677 $list = SpecialPage::getTitleFor( 'Unblock', $this->target->getName() );
678 } else {
679 $message = $this->msg( 'ipb-unblock' )->parse();
680 $list = SpecialPage::getTitleFor( 'Unblock' );
681 }
682 $links[] = $linkRenderer->makeKnownLink(
683 $list,
684 new HtmlArmor( $message )
685 );
686
687 // Link to the block list
688 $links[] = $linkRenderer->makeKnownLink(
689 SpecialPage::getTitleFor( 'BlockList' ),
690 $this->msg( 'ipb-blocklist' )->text()
691 );
692
693 // Link to edit the block dropdown reasons, if applicable
694 if ( $this->getAuthority()->isAllowed( 'editinterface' ) ) {
695 $links[] = $linkRenderer->makeKnownLink(
696 $this->msg( 'ipbreason-dropdown' )->inContentLanguage()->getTitle(),
697 $this->msg( 'ipb-edit-dropdown' )->text(),
698 [],
699 [ 'action' => 'edit' ]
700 );
701 }
702
703 $text = Html::rawElement(
704 'p',
705 [ 'class' => 'mw-ipb-conveniencelinks' ],
706 $this->getLanguage()->pipeList( $links )
707 );
708
709 $userPage = self::getTargetUserTitle( $this->target );
710 if ( $userPage ) {
711 // Get relevant extracts from the block and suppression logs, if possible
712 $out = '';
713
714 LogEventsList::showLogExtract(
715 $out,
716 'block',
717 $userPage,
718 '',
719 [
720 'lim' => 10,
721 'msgKey' => [
722 'blocklog-showlog',
723 $this->titleFormatter->getText( $userPage ),
724 ],
725 'showIfEmpty' => false
726 ]
727 );
728 $text .= $out;
729
730 // Add suppression block entries if allowed
731 if ( $this->getAuthority()->isAllowed( 'suppressionlog' ) ) {
732 LogEventsList::showLogExtract(
733 $out,
734 'suppress',
735 $userPage,
736 '',
737 [
738 'lim' => 10,
739 'conds' => [ 'log_action' => [ 'block', 'reblock', 'unblock' ] ],
740 'msgKey' => [
741 'blocklog-showsuppresslog',
742 $this->titleFormatter->getText( $userPage ),
743 ],
744 'showIfEmpty' => false
745 ]
746 );
747
748 $text .= $out;
749 }
750 }
751
752 return $text;
753 }
754
761 protected static function getTargetUserTitle( $target ): ?PageReference {
762 if ( $target instanceof UserIdentity ) {
763 return PageReferenceValue::localReference( NS_USER, $target->getName() );
764 }
765
766 if ( is_string( $target ) && IPUtils::isIPAddress( $target ) ) {
767 return PageReferenceValue::localReference( NS_USER, $target );
768 }
769
770 return null;
771 }
772
787 public static function getTargetAndType( ?string $par, WebRequest $request = null ) {
788 wfDeprecated( __METHOD__, '1.36' );
789 return self::getTargetAndTypeInternal( $par, $request );
790 }
791
803 private static function getTargetAndTypeInternal( ?string $par, WebRequest $request = null ) {
804 if ( !$request instanceof WebRequest ) {
805 return MediaWikiServices::getInstance()->getBlockUtils()->parseBlockTarget( $par );
806 }
807
808 $possibleTargets = [
809 $request->getVal( 'wpTarget', null ),
810 $par,
811 $request->getVal( 'ip', null ),
812 // B/C @since 1.18
813 $request->getVal( 'wpBlockAddress', null ),
814 ];
815 foreach ( $possibleTargets as $possibleTarget ) {
816 $targetAndType = MediaWikiServices::getInstance()
817 ->getBlockUtils()
818 ->parseBlockTarget( $possibleTarget );
819 // If type is not null then target is valid
820 if ( $targetAndType[ 1 ] !== null ) {
821 break;
822 }
823 }
824 return $targetAndType;
825 }
826
835 public static function processForm( array $data, IContextSource $context ) {
836 $services = MediaWikiServices::getInstance();
837 return self::processFormInternal(
838 $data,
839 $context->getAuthority(),
840 $services->getBlockUserFactory(),
841 $services->getBlockUtils()
842 );
843 }
844
855 private static function processFormInternal(
856 array $data,
857 Authority $performer,
858 BlockUserFactory $blockUserFactory,
859 BlockUtils $blockUtils
860 ) {
861 // Temporarily access service container until the feature flag is removed: T280532
862 $enablePartialActionBlocks = MediaWikiServices::getInstance()
863 ->getMainConfig()->get( MainConfigNames::EnablePartialActionBlocks );
864
865 $isPartialBlock = isset( $data['EditingRestriction'] ) &&
866 $data['EditingRestriction'] === 'partial';
867
868 // This might have been a hidden field or a checkbox, so interesting data
869 // can come from it
870 $data['Confirm'] = !in_array( $data['Confirm'], [ '', '0', null, false ], true );
871
872 // If the user has done the form 'properly', they won't even have been given the
873 // option to suppress-block unless they have the 'hideuser' permission
874 if ( !isset( $data['HideUser'] ) ) {
875 $data['HideUser'] = false;
876 }
877
879 [ $target, $type ] = $blockUtils->parseBlockTarget( $data['Target'] );
880 if ( $type == DatabaseBlock::TYPE_USER ) {
881 $target = $target->getName();
882
883 // Give admins a heads-up before they go and block themselves. Much messier
884 // to do this for IPs, but it's pretty unlikely they'd ever get the 'block'
885 // permission anyway, although the code does allow for it.
886 // Note: Important to use $target instead of $data['Target']
887 // since both $data['PreviousTarget'] and $target are normalized
888 // but $data['target'] gets overridden by (non-normalized) request variable
889 // from previous request.
890 if ( $target === $performer->getUser()->getName() &&
891 ( $data['PreviousTarget'] !== $target || !$data['Confirm'] )
892 ) {
893 return [ 'ipb-blockingself', 'ipb-confirmaction' ];
894 }
895
896 if ( $data['HideUser'] && !$data['Confirm'] ) {
897 return [ 'ipb-confirmhideuser', 'ipb-confirmaction' ];
898 }
899 } elseif ( $type == DatabaseBlock::TYPE_IP ) {
900 $target = $target->getName();
901 } elseif ( $type != DatabaseBlock::TYPE_RANGE ) {
902 // This should have been caught in the form field validation
903 return [ 'badipaddress' ];
904 }
905
906 // Reason, to be passed to the block object. For default values of reason, see
907 // HTMLSelectAndOtherField::getDefault
908 $blockReason = $data['Reason'][0] ?? '';
909
910 $pageRestrictions = [];
911 $namespaceRestrictions = [];
912 $actionRestrictions = [];
913 if ( $isPartialBlock ) {
914 if ( isset( $data['PageRestrictions'] ) && $data['PageRestrictions'] !== '' ) {
915 $titles = explode( "\n", $data['PageRestrictions'] );
916 foreach ( $titles as $title ) {
917 $pageRestrictions[] = PageRestriction::newFromTitle( $title );
918 }
919 }
920 if ( isset( $data['NamespaceRestrictions'] ) && $data['NamespaceRestrictions'] !== '' ) {
921 $namespaceRestrictions = array_map( static function ( $id ) {
922 return new NamespaceRestriction( 0, (int)$id );
923 }, explode( "\n", $data['NamespaceRestrictions'] ) );
924 }
925 if (
926 $enablePartialActionBlocks &&
927 isset( $data['ActionRestrictions'] ) &&
928 $data['ActionRestrictions'] !== ''
929 ) {
930 $actionRestrictions = array_map( static function ( $id ) {
931 return new ActionRestriction( 0, $id );
932 }, $data['ActionRestrictions'] );
933 }
934 }
935 $restrictions = array_merge( $pageRestrictions, $namespaceRestrictions, $actionRestrictions );
936
937 if ( !isset( $data['Tags'] ) ) {
938 $data['Tags'] = [];
939 }
940
941 $blockOptions = [
942 'isCreateAccountBlocked' => $data['CreateAccount'],
943 'isHardBlock' => $data['HardBlock'],
944 'isAutoblocking' => $data['AutoBlock'],
945 'isHideUser' => $data['HideUser'],
946 'isPartial' => $isPartialBlock,
947 ];
948
949 if ( isset( $data['DisableUTEdit'] ) ) {
950 $blockOptions['isUserTalkEditBlocked'] = $data['DisableUTEdit'];
951 }
952 if ( isset( $data['DisableEmail'] ) ) {
953 $blockOptions['isEmailBlocked'] = $data['DisableEmail'];
954 }
955
956 $blockUser = $blockUserFactory->newBlockUser(
957 $target,
958 $performer,
959 $data['Expiry'],
960 $blockReason,
961 $blockOptions,
962 $restrictions,
963 $data['Tags']
964 );
965
966 // Indicates whether the user is confirming the block and is aware of
967 // the conflict (did not change the block target in the meantime)
968 $blockNotConfirmed = !$data['Confirm'] || ( array_key_exists( 'PreviousTarget', $data )
969 && $data['PreviousTarget'] !== $target );
970
971 // Special case for API - T34434
972 $reblockNotAllowed = ( array_key_exists( 'Reblock', $data ) && !$data['Reblock'] );
973
974 $doReblock = !$blockNotConfirmed && !$reblockNotAllowed;
975
976 $status = $blockUser->placeBlock( $doReblock );
977 if ( !$status->isOK() ) {
978 return $status;
979 }
980
981 if (
982 // Can't watch a range block
983 $type != DatabaseBlock::TYPE_RANGE
984
985 // Technically a wiki can be configured to allow anonymous users to place blocks,
986 // in which case the 'Watch' field isn't included in the form shown, and we should
987 // not try to access it.
988 && array_key_exists( 'Watch', $data )
989 && $data['Watch']
990 ) {
991 MediaWikiServices::getInstance()->getWatchlistManager()->addWatchIgnoringRights(
992 $performer->getUser(),
993 Title::makeTitle( NS_USER, $target )
994 );
995 }
996
997 return true;
998 }
999
1011 public static function getSuggestedDurations( Language $lang = null, $includeOther = true ) {
1012 $lang ??= MediaWikiServices::getInstance()->getContentLanguage();
1013 return $lang->getBlockDurations( $includeOther );
1014 }
1015
1025 public static function parseExpiryInput( $expiry ) {
1026 return BlockUser::parseExpiryInput( $expiry );
1027 }
1028
1036 public static function canBlockEmail( UserIdentity $user ) {
1038 ->getBlockPermissionCheckerFactory()
1039 ->newBlockPermissionChecker( null, User::newFromIdentity( $user ) )
1040 ->checkEmailPermissions();
1041 }
1042
1049 public function onSubmit( array $data, HTMLForm $form = null ) {
1050 return self::processFormInternal(
1051 $data,
1052 $this->getAuthority(),
1053 $this->blockUserFactory,
1054 $this->blockUtils
1055 );
1056 }
1057
1062 public function onSuccess() {
1063 $out = $this->getOutput();
1064 $out->setPageTitleMsg( $this->msg( 'blockipsuccesssub' ) );
1065 $out->addWikiMsg( 'blockipsuccesstext', wfEscapeWikiText( $this->target ) );
1066 }
1067
1076 public function prefixSearchSubpages( $search, $limit, $offset ) {
1077 $search = $this->userNameUtils->getCanonical( $search );
1078 if ( !$search ) {
1079 // No prefix suggestion for invalid user
1080 return [];
1081 }
1082 // Autocomplete subpage as user list - public to allow caching
1083 return $this->userNamePrefixSearch
1084 ->search( UserNamePrefixSearch::AUDIENCE_PUBLIC, $search, $limit, $offset );
1085 }
1086
1087 protected function getGroupName() {
1088 return 'users';
1089 }
1090}
1091
1093class_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: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 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:1217
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.
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.
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.
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.
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: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...