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