MediaWiki  master
SpecialBlock.php
Go to the documentation of this file.
1 <?php
24 namespace MediaWiki\Specials;
25 
26 use ErrorPageError;
27 use HtmlArmor;
28 use HTMLForm;
29 use IContextSource;
30 use Language;
31 use LogEventsList;
59 use Message;
60 use OOUI\FieldLayout;
61 use OOUI\HtmlSnippet;
62 use OOUI\LabelWidget;
63 use OOUI\Widget;
64 use Wikimedia\IPUtils;
65 use XmlSelect;
66 
74 
75  private BlockUtils $blockUtils;
76  private BlockPermissionCheckerFactory $blockPermissionCheckerFactory;
77  private BlockUserFactory $blockUserFactory;
78  private UserNameUtils $userNameUtils;
79  private UserNamePrefixSearch $userNamePrefixSearch;
80  private BlockActionInfo $blockActionInfo;
81  private TitleFormatter $titleFormatter;
82 
86  protected $target;
87 
89  protected $type;
90 
92  protected $previousTarget;
93 
95  protected $requestedHideUser;
96 
98  protected $alreadyBlocked;
99 
104  protected $preErrors = [];
105 
106  private NamespaceInfo $namespaceInfo;
107 
118  public function __construct(
119  BlockUtils $blockUtils,
120  BlockPermissionCheckerFactory $blockPermissionCheckerFactory,
121  BlockUserFactory $blockUserFactory,
122  UserNameUtils $userNameUtils,
123  UserNamePrefixSearch $userNamePrefixSearch,
124  BlockActionInfo $blockActionInfo,
125  TitleFormatter $titleFormatter,
126  NamespaceInfo $namespaceInfo
127  ) {
128  parent::__construct( 'Block', 'block' );
129 
130  $this->blockUtils = $blockUtils;
131  $this->blockPermissionCheckerFactory = $blockPermissionCheckerFactory;
132  $this->blockUserFactory = $blockUserFactory;
133  $this->userNameUtils = $userNameUtils;
134  $this->userNamePrefixSearch = $userNamePrefixSearch;
135  $this->blockActionInfo = $blockActionInfo;
136  $this->titleFormatter = $titleFormatter;
137  $this->namespaceInfo = $namespaceInfo;
138  }
139 
140  public function doesWrites() {
141  return true;
142  }
143 
150  protected function checkExecutePermissions( User $user ) {
151  parent::checkExecutePermissions( $user );
152  // T17810: blocked admins should have limited access here
153  $status = $this->blockPermissionCheckerFactory
154  ->newBlockPermissionChecker( $this->target, $user )
155  ->checkBlockPermissions();
156  if ( $status !== true ) {
157  throw new ErrorPageError( 'badaccess', $status );
158  }
159  }
160 
166  public function requiresUnblock() {
167  return false;
168  }
169 
175  protected function setParameter( $par ) {
176  // Extract variables from the request. Try not to get into a situation where we
177  // need to extract *every* variable from the form just for processing here, but
178  // there are legitimate uses for some variables
179  $request = $this->getRequest();
180  [ $this->target, $this->type ] = self::getTargetAndTypeInternal( $par, $request );
181  if ( $this->target instanceof UserIdentity ) {
182  // Set the 'relevant user' in the skin, so it displays links like Contributions,
183  // User logs, UserRights, etc.
184  $this->getSkin()->setRelevantUser( $this->target );
185  }
186 
187  [ $this->previousTarget, /*...*/ ] = $this->blockUtils
188  ->parseBlockTarget( $request->getVal( 'wpPreviousTarget' ) );
189  $this->requestedHideUser = $request->getBool( 'wpHideUser' );
190  }
191 
197  protected function alterForm( HTMLForm $form ) {
198  $form->setHeaderHtml( '' );
199  $form->setSubmitDestructive();
200 
201  $msg = $this->alreadyBlocked ? 'ipb-change-block' : 'ipbsubmit';
202  $form->setSubmitTextMsg( $msg );
203 
204  $this->addHelpLink( 'Help:Blocking users' );
205 
206  // Don't need to do anything if the form has been posted
207  if ( !$this->getRequest()->wasPosted() && $this->preErrors ) {
208  // Mimic error messages normally generated by the form
209  $form->addHeaderHtml( (string)new FieldLayout(
210  new Widget( [] ),
211  [
212  'align' => 'top',
213  'errors' => array_map( function ( $errMsg ) {
214  // @phan-suppress-next-line PhanParamTooFewUnpack Should infer non-emptiness
215  return new HtmlSnippet( $this->msg( ...$errMsg )->parse() );
216  }, $this->preErrors ),
217  ]
218  ) );
219  }
220  }
221 
222  protected function getDisplayFormat() {
223  return 'ooui';
224  }
225 
230  protected function getFormFields() {
231  $conf = $this->getConfig();
232  $blockAllowsUTEdit = $conf->get( MainConfigNames::BlockAllowsUTEdit );
233 
234  $this->getOutput()->enableOOUI();
235 
236  $user = $this->getUser();
237 
238  $suggestedDurations = self::getSuggestedDurations();
239 
240  $a = [];
241 
242  $a['Target'] = [
243  'type' => 'user',
244  'ipallowed' => true,
245  'iprange' => true,
246  'id' => 'mw-bi-target',
247  'size' => '45',
248  'autofocus' => true,
249  'required' => true,
250  'placeholder' => $this->msg( 'block-target-placeholder' )->text(),
251  'validation-callback' => function ( $value, $alldata, $form ) {
252  $status = $this->blockUtils->validateTarget( $value );
253  if ( !$status->isOK() ) {
254  $errors = $status->getErrorsArray();
255 
256  return $form->msg( ...$errors[0] );
257  }
258  return true;
259  },
260  'section' => 'target',
261  ];
262 
263  $a['EditingRestriction'] = [
264  'type' => 'radio',
265  'cssclass' => 'mw-block-editing-restriction',
266  'default' => 'sitewide',
267  'options' => [
268  $this->msg( 'ipb-sitewide' )->escaped() .
269  new LabelWidget( [
270  'classes' => [ 'oo-ui-inline-help' ],
271  'label' => new HtmlSnippet( $this->msg( 'ipb-sitewide-help' )->parse() ),
272  ] ) => 'sitewide',
273  $this->msg( 'ipb-partial' )->escaped() .
274  new LabelWidget( [
275  'classes' => [ 'oo-ui-inline-help' ],
276  'label' => $this->msg( 'ipb-partial-help' )->text(),
277  ] ) => 'partial',
278  ],
279  'section' => 'actions',
280  ];
281 
282  $a['PageRestrictions'] = [
283  'type' => 'titlesmultiselect',
284  'label' => $this->msg( 'ipb-pages-label' )->text(),
285  'exists' => true,
286  'max' => 10,
287  'cssclass' => 'mw-htmlform-checkradio-indent mw-block-partial-restriction',
288  'default' => '',
289  'showMissing' => false,
290  'excludeDynamicNamespaces' => true,
291  'input' => [
292  'autocomplete' => false
293  ],
294  'section' => 'actions',
295  ];
296 
297  $a['NamespaceRestrictions'] = [
298  'type' => 'namespacesmultiselect',
299  'label' => $this->msg( 'ipb-namespaces-label' )->text(),
300  'exists' => true,
301  'cssclass' => 'mw-htmlform-checkradio-indent mw-block-partial-restriction',
302  'default' => '',
303  'input' => [
304  'autocomplete' => false
305  ],
306  'section' => 'actions',
307  ];
308 
309  if ( $conf->get( MainConfigNames::EnablePartialActionBlocks ) ) {
310  $blockActions = $this->blockActionInfo->getAllBlockActions();
311  $a['ActionRestrictions'] = [
312  'type' => 'multiselect',
313  'cssclass' => 'mw-htmlform-checkradio-indent mw-block-partial-restriction mw-block-action-restriction',
314  'options-messages' => array_combine(
315  array_map( static function ( $action ) {
316  return "ipb-action-$action";
317  }, array_keys( $blockActions ) ),
318  $blockActions
319  ),
320  'section' => 'actions',
321  ];
322  }
323 
324  $a['CreateAccount'] = [
325  'type' => 'check',
326  'cssclass' => 'mw-block-restriction',
327  'label-message' => 'ipbcreateaccount',
328  'default' => true,
329  'section' => 'details',
330  ];
331 
332  if ( $this->blockPermissionCheckerFactory
333  ->newBlockPermissionChecker( null, $user )
334  ->checkEmailPermissions()
335  ) {
336  $a['DisableEmail'] = [
337  'type' => 'check',
338  'cssclass' => 'mw-block-restriction',
339  'label-message' => 'ipbemailban',
340  'section' => 'details',
341  ];
342  }
343 
344  if ( $blockAllowsUTEdit ) {
345  $a['DisableUTEdit'] = [
346  'type' => 'check',
347  'cssclass' => 'mw-block-restriction',
348  'label-message' => 'ipb-disableusertalk',
349  'default' => false,
350  'section' => 'details',
351  ];
352  }
353 
354  $defaultExpiry = $this->msg( 'ipb-default-expiry' )->inContentLanguage();
355  if ( $this->type === DatabaseBlock::TYPE_RANGE || $this->type === DatabaseBlock::TYPE_IP ) {
356  $defaultExpiryIP = $this->msg( 'ipb-default-expiry-ip' )->inContentLanguage();
357  if ( !$defaultExpiryIP->isDisabled() ) {
358  $defaultExpiry = $defaultExpiryIP;
359  }
360  }
361 
362  $a['Expiry'] = [
363  'type' => 'expiry',
364  'required' => true,
365  'options' => $suggestedDurations,
366  'default' => $defaultExpiry->text(),
367  'section' => 'expiry',
368  ];
369 
370  $a['Reason'] = [
371  'type' => 'selectandother',
372  // HTML maxlength uses "UTF-16 code units", which means that characters outside BMP
373  // (e.g. emojis) count for two each. This limit is overridden in JS to instead count
374  // Unicode codepoints.
376  'maxlength-unit' => 'codepoints',
377  'options-message' => 'ipbreason-dropdown',
378  'section' => 'reason',
379  ];
380 
381  $a['AutoBlock'] = [
382  'type' => 'check',
383  'label-message' => [
384  'ipbenableautoblock',
386  ],
387  'default' => true,
388  'section' => 'options',
389  ];
390 
391  // Allow some users to hide name from block log, blocklist and listusers
392  if ( $this->getAuthority()->isAllowed( 'hideuser' ) ) {
393  $a['HideUser'] = [
394  'type' => 'check',
395  'label-message' => 'ipbhidename',
396  'cssclass' => 'mw-block-hideuser',
397  'section' => 'options',
398  ];
399  }
400 
401  // Watchlist their user page? (Only if user is logged in)
402  if ( $user->isRegistered() ) {
403  $a['Watch'] = [
404  'type' => 'check',
405  'label-message' => 'ipbwatchuser',
406  'section' => 'options',
407  ];
408  }
409 
410  $a['HardBlock'] = [
411  'type' => 'check',
412  'label-message' => 'ipb-hardblock',
413  'default' => false,
414  'section' => 'options',
415  ];
416 
417  // This is basically a copy of the Target field, but the user can't change it, so we
418  // can see if the warnings we maybe showed to the user before still apply
419  $a['PreviousTarget'] = [
420  'type' => 'hidden',
421  'default' => false,
422  ];
423 
424  // We'll turn this into a checkbox if we need to
425  $a['Confirm'] = [
426  'type' => 'hidden',
427  'default' => '',
428  'label-message' => 'ipb-confirm',
429  'cssclass' => 'mw-block-confirm',
430  ];
431 
432  $this->maybeAlterFormDefaults( $a );
433 
434  // Allow extensions to add more fields
435  $this->getHookRunner()->onSpecialBlockModifyFormFields( $this, $a );
436 
437  return $a;
438  }
439 
445  protected function maybeAlterFormDefaults( &$fields ) {
446  // This will be overwritten by request data
447  $fields['Target']['default'] = (string)$this->target;
448 
449  if ( $this->target ) {
450  $status = $this->blockUtils->validateTarget( $this->target );
451  if ( !$status->isOK() ) {
452  $errors = $status->getErrorsArray();
453  $this->preErrors = array_merge( $this->preErrors, $errors );
454  }
455  }
456 
457  // This won't be
458  $fields['PreviousTarget']['default'] = (string)$this->target;
459 
460  $block = DatabaseBlock::newFromTarget( $this->target );
461 
462  // Populate fields if there is a block that is not an autoblock; if it is a range
463  // block, only populate the fields if the range is the same as $this->target
464  if ( $block instanceof DatabaseBlock && $block->getType() !== DatabaseBlock::TYPE_AUTO
465  && ( $this->type != DatabaseBlock::TYPE_RANGE
466  || ( $this->target && $block->isBlocking( $this->target ) ) )
467  ) {
468  $fields['HardBlock']['default'] = $block->isHardblock();
469  $fields['CreateAccount']['default'] = $block->isCreateAccountBlocked();
470  $fields['AutoBlock']['default'] = $block->isAutoblocking();
471 
472  if ( isset( $fields['DisableEmail'] ) ) {
473  $fields['DisableEmail']['default'] = $block->isEmailBlocked();
474  }
475 
476  if ( isset( $fields['HideUser'] ) ) {
477  $fields['HideUser']['default'] = $block->getHideName();
478  }
479 
480  if ( isset( $fields['DisableUTEdit'] ) ) {
481  $fields['DisableUTEdit']['default'] = !$block->isUsertalkEditAllowed();
482  }
483 
484  // If the username was hidden (ipb_deleted == 1), don't show the reason
485  // unless this user also has rights to hideuser: T37839
486  if ( !$block->getHideName() || $this->getAuthority()->isAllowed( 'hideuser' ) ) {
487  $fields['Reason']['default'] = $block->getReasonComment()->text;
488  } else {
489  $fields['Reason']['default'] = '';
490  }
491 
492  if ( $this->getRequest()->wasPosted() ) {
493  // Ok, so we got a POST submission asking us to reblock a user. So show the
494  // confirm checkbox; the user will only see it if they haven't previously
495  $fields['Confirm']['type'] = 'check';
496  } else {
497  // We got a target, but it wasn't a POST request, so the user must have gone
498  // to a link like [[Special:Block/User]]. We don't need to show the checkbox
499  // as long as they go ahead and block *that* user
500  $fields['Confirm']['default'] = 1;
501  }
502 
503  if ( $block->getExpiry() == 'infinity' ) {
504  $fields['Expiry']['default'] = 'infinite';
505  } else {
506  $fields['Expiry']['default'] = wfTimestamp( TS_RFC2822, $block->getExpiry() );
507  }
508 
509  if ( !$block->isSitewide() ) {
510  $fields['EditingRestriction']['default'] = 'partial';
511 
512  $pageRestrictions = [];
513  $namespaceRestrictions = [];
514  foreach ( $block->getRestrictions() as $restriction ) {
515  if ( $restriction instanceof PageRestriction && $restriction->getTitle() ) {
516  $pageRestrictions[] = $restriction->getTitle()->getPrefixedText();
517  } elseif ( $restriction instanceof NamespaceRestriction &&
518  $this->namespaceInfo->exists( $restriction->getValue() )
519  ) {
520  $namespaceRestrictions[] = $restriction->getValue();
521  }
522  }
523 
524  // Sort the restrictions so they are in alphabetical order.
525  sort( $pageRestrictions );
526  $fields['PageRestrictions']['default'] = implode( "\n", $pageRestrictions );
527  sort( $namespaceRestrictions );
528  $fields['NamespaceRestrictions']['default'] = implode( "\n", $namespaceRestrictions );
529 
530  if ( $this->getConfig()->get( MainConfigNames::EnablePartialActionBlocks ) ) {
531  $actionRestrictions = [];
532  foreach ( $block->getRestrictions() as $restriction ) {
533  if ( $restriction instanceof ActionRestriction ) {
534  $actionRestrictions[] = $restriction->getValue();
535  }
536  }
537  $fields['ActionRestrictions']['default'] = $actionRestrictions;
538  }
539  }
540 
541  $this->alreadyBlocked = true;
542  $this->preErrors[] = [ 'ipb-needreblock', wfEscapeWikiText( $block->getTargetName() ) ];
543  }
544 
545  if ( $this->alreadyBlocked || $this->getRequest()->wasPosted()
546  || $this->getRequest()->getCheck( 'wpCreateAccount' )
547  ) {
548  $this->getOutput()->addJsConfigVars( 'wgCreateAccountDirty', true );
549  }
550 
551  // We always need confirmation to do HideUser
552  if ( $this->requestedHideUser ) {
553  $fields['Confirm']['type'] = 'check';
554  unset( $fields['Confirm']['default'] );
555  $this->preErrors[] = [ 'ipb-confirmhideuser', 'ipb-confirmaction' ];
556  }
557 
558  // Or if the user is trying to block themselves
559  if ( (string)$this->target === $this->getUser()->getName() ) {
560  $fields['Confirm']['type'] = 'check';
561  unset( $fields['Confirm']['default'] );
562  $this->preErrors[] = [ 'ipb-blockingself', 'ipb-confirmaction' ];
563  }
564  }
565 
570  protected function preHtml() {
571  $this->getOutput()->addModuleStyles( [ 'mediawiki.special' ] );
572  $this->getOutput()->addModules( [ 'mediawiki.special.block' ] );
573 
574  $blockCIDRLimit = $this->getConfig()->get( MainConfigNames::BlockCIDRLimit );
575  $text = $this->msg( 'blockiptext', $blockCIDRLimit['IPv4'], $blockCIDRLimit['IPv6'] )->parse();
576 
577  $otherBlockMessages = [];
578  if ( $this->target !== null ) {
579  $targetName = $this->target;
580  if ( $this->target instanceof UserIdentity ) {
581  $targetName = $this->target->getName();
582  }
583  // Get other blocks, i.e. from GlobalBlocking or TorBlock extension
584  $this->getHookRunner()->onOtherBlockLogLink(
585  $otherBlockMessages, $targetName );
586 
587  if ( count( $otherBlockMessages ) ) {
588  $s = Html::rawElement(
589  'h2',
590  [],
591  $this->msg( 'ipb-otherblocks-header', count( $otherBlockMessages ) )->parse()
592  ) . "\n";
593 
594  $list = '';
595 
596  foreach ( $otherBlockMessages as $link ) {
597  $list .= Html::rawElement( 'li', [], $link ) . "\n";
598  }
599 
600  $s .= Html::rawElement(
601  'ul',
602  [ 'class' => 'mw-blockip-alreadyblocked' ],
603  $list
604  ) . "\n";
605 
606  $text .= $s;
607  }
608  }
609 
610  return $text;
611  }
612 
617  protected function postHtml() {
618  $links = [];
619 
620  $this->getOutput()->addModuleStyles( 'mediawiki.special' );
621 
622  $linkRenderer = $this->getLinkRenderer();
623  // Link to the user's contributions, if applicable
624  if ( $this->target instanceof UserIdentity ) {
625  $contribsPage = SpecialPage::getTitleFor( 'Contributions', $this->target->getName() );
626  $links[] = $linkRenderer->makeLink(
627  $contribsPage,
628  $this->msg( 'ipb-blocklist-contribs', $this->target->getName() )->text()
629  );
630  }
631 
632  // Link to unblock the specified user, or to a blank unblock form
633  if ( $this->target instanceof UserIdentity ) {
634  $message = $this->msg(
635  'ipb-unblock-addr',
636  wfEscapeWikiText( $this->target->getName() )
637  )->parse();
638  $list = SpecialPage::getTitleFor( 'Unblock', $this->target->getName() );
639  } else {
640  $message = $this->msg( 'ipb-unblock' )->parse();
641  $list = SpecialPage::getTitleFor( 'Unblock' );
642  }
643  $links[] = $linkRenderer->makeKnownLink(
644  $list,
645  new HtmlArmor( $message )
646  );
647 
648  // Link to the block list
649  $links[] = $linkRenderer->makeKnownLink(
650  SpecialPage::getTitleFor( 'BlockList' ),
651  $this->msg( 'ipb-blocklist' )->text()
652  );
653 
654  // Link to edit the block dropdown reasons, if applicable
655  if ( $this->getAuthority()->isAllowed( 'editinterface' ) ) {
656  $links[] = $linkRenderer->makeKnownLink(
657  $this->msg( 'ipbreason-dropdown' )->inContentLanguage()->getTitle(),
658  $this->msg( 'ipb-edit-dropdown' )->text(),
659  [],
660  [ 'action' => 'edit' ]
661  );
662  }
663 
664  $text = Html::rawElement(
665  'p',
666  [ 'class' => 'mw-ipb-conveniencelinks' ],
667  $this->getLanguage()->pipeList( $links )
668  );
669 
670  $userPage = self::getTargetUserTitle( $this->target );
671  if ( $userPage ) {
672  // Get relevant extracts from the block and suppression logs, if possible
673  $out = '';
674 
676  $out,
677  'block',
678  $userPage,
679  '',
680  [
681  'lim' => 10,
682  'msgKey' => [
683  'blocklog-showlog',
684  $this->titleFormatter->getText( $userPage ),
685  ],
686  'showIfEmpty' => false
687  ]
688  );
689  $text .= $out;
690 
691  // Add suppression block entries if allowed
692  if ( $this->getAuthority()->isAllowed( 'suppressionlog' ) ) {
694  $out,
695  'suppress',
696  $userPage,
697  '',
698  [
699  'lim' => 10,
700  'conds' => [ 'log_action' => [ 'block', 'reblock', 'unblock' ] ],
701  'msgKey' => [
702  'blocklog-showsuppresslog',
703  $this->titleFormatter->getText( $userPage ),
704  ],
705  'showIfEmpty' => false
706  ]
707  );
708 
709  $text .= $out;
710  }
711  }
712 
713  return $text;
714  }
715 
722  protected static function getTargetUserTitle( $target ): ?PageReference {
723  if ( $target instanceof UserIdentity ) {
725  }
726 
727  if ( is_string( $target ) && IPUtils::isIPAddress( $target ) ) {
729  }
730 
731  return null;
732  }
733 
748  public static function getTargetAndType( ?string $par, WebRequest $request = null ) {
749  wfDeprecated( __METHOD__, '1.36' );
750  return self::getTargetAndTypeInternal( $par, $request );
751  }
752 
764  private static function getTargetAndTypeInternal( ?string $par, WebRequest $request = null ) {
765  if ( !$request instanceof WebRequest ) {
766  return MediaWikiServices::getInstance()->getBlockUtils()->parseBlockTarget( $par );
767  }
768 
769  $possibleTargets = [
770  $request->getVal( 'wpTarget', null ),
771  $par,
772  $request->getVal( 'ip', null ),
773  // B/C @since 1.18
774  $request->getVal( 'wpBlockAddress', null ),
775  ];
776  foreach ( $possibleTargets as $possibleTarget ) {
777  $targetAndType = MediaWikiServices::getInstance()
778  ->getBlockUtils()
779  ->parseBlockTarget( $possibleTarget );
780  // If type is not null then target is valid
781  if ( $targetAndType[ 1 ] !== null ) {
782  break;
783  }
784  }
785  return $targetAndType;
786  }
787 
796  public static function processForm( array $data, IContextSource $context ) {
797  $services = MediaWikiServices::getInstance();
798  return self::processFormInternal(
799  $data,
800  $context->getAuthority(),
801  $services->getBlockUserFactory(),
802  $services->getBlockUtils()
803  );
804  }
805 
816  private static function processFormInternal(
817  array $data,
818  Authority $performer,
819  BlockUserFactory $blockUserFactory,
820  BlockUtils $blockUtils
821  ) {
822  // Temporarily access service container until the feature flag is removed: T280532
823  $enablePartialActionBlocks = MediaWikiServices::getInstance()
824  ->getMainConfig()->get( MainConfigNames::EnablePartialActionBlocks );
825 
826  $isPartialBlock = isset( $data['EditingRestriction'] ) &&
827  $data['EditingRestriction'] === 'partial';
828 
829  // This might have been a hidden field or a checkbox, so interesting data
830  // can come from it
831  $data['Confirm'] = !in_array( $data['Confirm'], [ '', '0', null, false ], true );
832 
833  // If the user has done the form 'properly', they won't even have been given the
834  // option to suppress-block unless they have the 'hideuser' permission
835  if ( !isset( $data['HideUser'] ) ) {
836  $data['HideUser'] = false;
837  }
838 
840  [ $target, $type ] = $blockUtils->parseBlockTarget( $data['Target'] );
841  if ( $type == DatabaseBlock::TYPE_USER ) {
842  $target = $target->getName();
843 
844  // Give admins a heads-up before they go and block themselves. Much messier
845  // to do this for IPs, but it's pretty unlikely they'd ever get the 'block'
846  // permission anyway, although the code does allow for it.
847  // Note: Important to use $target instead of $data['Target']
848  // since both $data['PreviousTarget'] and $target are normalized
849  // but $data['target'] gets overridden by (non-normalized) request variable
850  // from previous request.
851  if ( $target === $performer->getUser()->getName() &&
852  ( $data['PreviousTarget'] !== $target || !$data['Confirm'] )
853  ) {
854  return [ 'ipb-blockingself', 'ipb-confirmaction' ];
855  }
856 
857  if ( $data['HideUser'] && !$data['Confirm'] ) {
858  return [ 'ipb-confirmhideuser', 'ipb-confirmaction' ];
859  }
860  } elseif ( $type == DatabaseBlock::TYPE_IP ) {
861  $target = $target->getName();
862  } elseif ( $type != DatabaseBlock::TYPE_RANGE ) {
863  // This should have been caught in the form field validation
864  return [ 'badipaddress' ];
865  }
866 
867  // Reason, to be passed to the block object. For default values of reason, see
868  // HTMLSelectAndOtherField::getDefault
869  $blockReason = $data['Reason'][0] ?? '';
870 
871  $pageRestrictions = [];
872  $namespaceRestrictions = [];
873  $actionRestrictions = [];
874  if ( $isPartialBlock ) {
875  if ( isset( $data['PageRestrictions'] ) && $data['PageRestrictions'] !== '' ) {
876  $titles = explode( "\n", $data['PageRestrictions'] );
877  foreach ( $titles as $title ) {
878  $pageRestrictions[] = PageRestriction::newFromTitle( $title );
879  }
880  }
881  if ( isset( $data['NamespaceRestrictions'] ) && $data['NamespaceRestrictions'] !== '' ) {
882  $namespaceRestrictions = array_map( static function ( $id ) {
883  return new NamespaceRestriction( 0, (int)$id );
884  }, explode( "\n", $data['NamespaceRestrictions'] ) );
885  }
886  if (
887  $enablePartialActionBlocks &&
888  isset( $data['ActionRestrictions'] ) &&
889  $data['ActionRestrictions'] !== ''
890  ) {
891  $actionRestrictions = array_map( static function ( $id ) {
892  return new ActionRestriction( 0, $id );
893  }, $data['ActionRestrictions'] );
894  }
895  }
896  $restrictions = array_merge( $pageRestrictions, $namespaceRestrictions, $actionRestrictions );
897 
898  if ( !isset( $data['Tags'] ) ) {
899  $data['Tags'] = [];
900  }
901 
902  $blockOptions = [
903  'isCreateAccountBlocked' => $data['CreateAccount'],
904  'isHardBlock' => $data['HardBlock'],
905  'isAutoblocking' => $data['AutoBlock'],
906  'isHideUser' => $data['HideUser'],
907  'isPartial' => $isPartialBlock,
908  ];
909 
910  if ( isset( $data['DisableUTEdit'] ) ) {
911  $blockOptions['isUserTalkEditBlocked'] = $data['DisableUTEdit'];
912  }
913  if ( isset( $data['DisableEmail'] ) ) {
914  $blockOptions['isEmailBlocked'] = $data['DisableEmail'];
915  }
916 
917  $blockUser = $blockUserFactory->newBlockUser(
918  $target,
919  $performer,
920  $data['Expiry'],
921  $blockReason,
922  $blockOptions,
923  $restrictions,
924  $data['Tags']
925  );
926 
927  // Indicates whether the user is confirming the block and is aware of
928  // the conflict (did not change the block target in the meantime)
929  $blockNotConfirmed = !$data['Confirm'] || ( array_key_exists( 'PreviousTarget', $data )
930  && $data['PreviousTarget'] !== $target );
931 
932  // Special case for API - T34434
933  $reblockNotAllowed = ( array_key_exists( 'Reblock', $data ) && !$data['Reblock'] );
934 
935  $doReblock = !$blockNotConfirmed && !$reblockNotAllowed;
936 
937  $status = $blockUser->placeBlock( $doReblock );
938  if ( !$status->isOK() ) {
939  return $status;
940  }
941 
942  if (
943  // Can't watch a range block
945 
946  // Technically a wiki can be configured to allow anonymous users to place blocks,
947  // in which case the 'Watch' field isn't included in the form shown, and we should
948  // not try to access it.
949  && array_key_exists( 'Watch', $data )
950  && $data['Watch']
951  ) {
952  MediaWikiServices::getInstance()->getWatchlistManager()->addWatchIgnoringRights(
953  $performer->getUser(),
954  Title::makeTitle( NS_USER, $target )
955  );
956  }
957 
958  return true;
959  }
960 
971  public static function getSuggestedDurations( Language $lang = null, $includeOther = true ) {
972  $msg = $lang === null
973  ? wfMessage( 'ipboptions' )->inContentLanguage()->text()
974  : wfMessage( 'ipboptions' )->inLanguage( $lang )->text();
975 
976  if ( $msg == '-' ) {
977  return [];
978  }
979 
980  $a = XmlSelect::parseOptionsMessage( $msg );
981 
982  if ( $a && $includeOther ) {
983  // If options exist, add other to the end instead of the beginning (which
984  // is what happens by default).
985  $a[ wfMessage( 'ipbother' )->text() ] = 'other';
986  }
987 
988  return $a;
989  }
990 
1000  public static function parseExpiryInput( $expiry ) {
1001  return BlockUser::parseExpiryInput( $expiry );
1002  }
1003 
1011  public static function canBlockEmail( UserIdentity $user ) {
1013  ->getBlockPermissionCheckerFactory()
1014  ->newBlockPermissionChecker( null, User::newFromIdentity( $user ) )
1015  ->checkEmailPermissions();
1016  }
1017 
1032  public static function checkUnblockSelf( $target, Authority $performer ) {
1033  wfDeprecated( __METHOD__, '1.36' );
1035  ->getBlockPermissionCheckerFactory()
1036  ->newBlockPermissionChecker( $target, $performer )
1037  ->checkBlockPermissions();
1038  }
1039 
1046  public function onSubmit( array $data, HTMLForm $form = null ) {
1047  return self::processFormInternal(
1048  $data,
1049  $this->getAuthority(),
1050  $this->blockUserFactory,
1051  $this->blockUtils
1052  );
1053  }
1054 
1059  public function onSuccess() {
1060  $out = $this->getOutput();
1061  $out->setPageTitleMsg( $this->msg( 'blockipsuccesssub' ) );
1062  $out->addWikiMsg( 'blockipsuccesstext', wfEscapeWikiText( $this->target ) );
1063  }
1064 
1073  public function prefixSearchSubpages( $search, $limit, $offset ) {
1074  $search = $this->userNameUtils->getCanonical( $search );
1075  if ( !$search ) {
1076  // No prefix suggestion for invalid user
1077  return [];
1078  }
1079  // Autocomplete subpage as user list - public to allow caching
1080  return $this->userNamePrefixSearch
1081  ->search( UserNamePrefixSearch::AUDIENCE_PUBLIC, $search, $limit, $offset );
1082  }
1083 
1084  protected function getGroupName() {
1085  return 'users';
1086  }
1087 }
1088 
1092 class_alias( SpecialBlock::class, 'SpecialBlock' );
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'))
Definition: WebStart.php:88
An error page which can definitely be safely rendered using the OutputPage.
Object handling generic submission, CSRF protection, layout and other logic for UI forms in a reusabl...
Definition: HTMLForm.php:158
addHeaderHtml( $html, $section=null)
Add HTML to the header, inside the form.
Definition: HTMLForm.php:902
setSubmitTextMsg( $msg)
Set the text for the submit button to a message.
Definition: HTMLForm.php:1625
setSubmitDestructive()
Identify that the submit button in the form has a destructive action.
Definition: HTMLForm.php:1611
setHeaderHtml( $html, $section=null)
Set header HTML, inside the form.
Definition: HTMLForm.php:922
Marks HTML that shouldn't be escaped.
Definition: HtmlArmor.php:30
Base class for language-specific code.
Definition: Language.php:61
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:53
static parseExpiryInput(string $expiry)
Convert a submitted expiry time, which may be relative ("2 weeks", etc) or absolute ("24 May 2034",...
Definition: BlockUser.php:337
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...
static newFromTarget( $specificTarget, $vagueTarget=null, $fromPrimary=false)
Given a target and the target's type, get an existing block object if possible.
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.
const COMMENT_CHARACTER_LIMIT
Maximum length of a comment in UTF-8 characters.
This class is a collection of static functions that serve two purposes:
Definition: Html.php:57
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:239
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.
Immutable value object representing a page reference.
static localReference(int $namespace, string $dbKey)
Create PageReference for a local page.
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
Definition: WebRequest.php:50
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.
Definition: SpecialPage.php:66
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 name of this Special Page.
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...
__construct(BlockUtils $blockUtils, BlockPermissionCheckerFactory $blockPermissionCheckerFactory, BlockUserFactory $blockUserFactory, UserNameUtils $userNameUtils, UserNamePrefixSearch $userNamePrefixSearch, BlockActionInfo $blockActionInfo, TitleFormatter $titleFormatter, NamespaceInfo $namespaceInfo)
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.
static checkUnblockSelf( $target, Authority $performer)
T17810: Site-wide blocked admins should not be able to block/unblock others with one exception; they ...
alterForm(HTMLForm $form)
Customizes the HTMLForm a bit.
postHtml()
Add footer elements to the form.
requiresUnblock()
We allow certain special cases where user is blocked.
UserIdentity string null $target
User to be blocked, as passed either by parameter (url?wpTarget=Foo) or as subpage (Special:Block/Foo...
getDisplayFormat()
Get display format for the form.
setParameter( $par)
Handle some magic here.
maybeAlterFormDefaults(&$fields)
If the user has already been blocked with similar settings, load that block and change the defaults f...
checkExecutePermissions(User $user)
Check that the user can unblock themselves if they are trying to do so.
doesWrites()
Indicates whether this special page may perform database writes.
static getTargetUserTitle( $target)
Get a user page target for things like logs.
static getTargetAndType(?string $par, WebRequest $request=null)
Get the target and type, given the request and the subpage parameter.
bool $requestedHideUser
Whether the previous submission of the form asked for HideUser.
static processForm(array $data, IContextSource $context)
Given the form data, actually implement a block.
getFormFields()
Get the HTMLForm descriptor array for the block form.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:58
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:76
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:624
Handles searching prefixes of user names.
UserNameUtils service.
internal since 1.36
Definition: User.php:98
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
Definition: User.php:670
getTitle()
Get the Title object that we'll be acting on, as specified in the WebRequest.
Definition: MediaWiki.php:186
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition: Message.php:144
static durationParam( $duration)
Definition: Message.php:1165
Class for generating HTML <select> or <datalist> elements.
Definition: XmlSelect.php:28
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.
A title formatter service for MediaWiki.
Interface for objects representing user identity.