MediaWiki  master
SpecialBlock.php
Go to the documentation of this file.
1 <?php
29 
40  protected $target;
41 
43  protected $type;
44 
46  protected $previousTarget;
47 
49  protected $requestedHideUser;
50 
52  protected $alreadyBlocked;
53 
55  protected $preErrors = [];
56 
57  public function __construct() {
58  parent::__construct( 'Block', 'block' );
59  }
60 
61  public function doesWrites() {
62  return true;
63  }
64 
71  protected function checkExecutePermissions( User $user ) {
72  parent::checkExecutePermissions( $user );
73  # T17810: blocked admins should have limited access here
74  $status = self::checkUnblockSelf( $this->target, $user );
75  if ( $status !== true ) {
76  throw new ErrorPageError( 'badaccess', $status );
77  }
78  }
79 
85  public function requiresUnblock() {
86  return false;
87  }
88 
94  protected function setParameter( $par ) {
95  # Extract variables from the request. Try not to get into a situation where we
96  # need to extract *every* variable from the form just for processing here, but
97  # there are legitimate uses for some variables
98  $request = $this->getRequest();
99  list( $this->target, $this->type ) = self::getTargetAndType( $par, $request );
100  if ( $this->target instanceof User ) {
101  # Set the 'relevant user' in the skin, so it displays links like Contributions,
102  # User logs, UserRights, etc.
103  $this->getSkin()->setRelevantUser( $this->target );
104  }
105 
106  list( $this->previousTarget, /*...*/ ) =
107  DatabaseBlock::parseTarget( $request->getVal( 'wpPreviousTarget' ) );
108  $this->requestedHideUser = $request->getBool( 'wpHideUser' );
109  }
110 
116  protected function alterForm( HTMLForm $form ) {
117  $form->setHeaderText( '' );
118  $form->setSubmitDestructive();
119 
120  $msg = $this->alreadyBlocked ? 'ipb-change-block' : 'ipbsubmit';
121  $form->setSubmitTextMsg( $msg );
122 
123  $this->addHelpLink( 'Help:Blocking users' );
124 
125  # Don't need to do anything if the form has been posted
126  if ( !$this->getRequest()->wasPosted() && $this->preErrors ) {
127  $s = $form->formatErrors( $this->preErrors );
128  if ( $s ) {
130  'div',
131  [ 'class' => 'error' ],
132  $s
133  ) );
134  }
135  }
136  }
137 
138  protected function getDisplayFormat() {
139  return 'ooui';
140  }
141 
146  protected function getFormFields() {
147  $conf = $this->getConfig();
148  $enablePartialBlocks = $conf->get( 'EnablePartialBlocks' );
149  $blockAllowsUTEdit = $conf->get( 'BlockAllowsUTEdit' );
150 
151  $this->getOutput()->enableOOUI();
152 
153  $user = $this->getUser();
154 
155  $suggestedDurations = self::getSuggestedDurations();
156 
157  $a = [];
158 
159  $a['Target'] = [
160  'type' => 'user',
161  'ipallowed' => true,
162  'iprange' => true,
163  'id' => 'mw-bi-target',
164  'size' => '45',
165  'autofocus' => true,
166  'required' => true,
167  'validation-callback' => [ __CLASS__, 'validateTargetField' ],
168  'section' => 'target',
169  ];
170 
171  $a['Editing'] = [
172  'type' => 'check',
173  'label-message' => 'block-prevent-edit',
174  'default' => true,
175  'section' => 'actions',
176  'disabled' => $enablePartialBlocks ? false : true,
177  ];
178 
179  if ( $enablePartialBlocks ) {
180  $a['EditingRestriction'] = [
181  'type' => 'radio',
182  'cssclass' => 'mw-block-editing-restriction',
183  'options' => [
184  $this->msg( 'ipb-sitewide' )->escaped() .
185  new \OOUI\LabelWidget( [
186  'classes' => [ 'oo-ui-inline-help' ],
187  'label' => $this->msg( 'ipb-sitewide-help' )->text(),
188  ] ) => 'sitewide',
189  $this->msg( 'ipb-partial' )->escaped() .
190  new \OOUI\LabelWidget( [
191  'classes' => [ 'oo-ui-inline-help' ],
192  'label' => $this->msg( 'ipb-partial-help' )->text(),
193  ] ) => 'partial',
194  ],
195  'section' => 'actions',
196  ];
197  $a['PageRestrictions'] = [
198  'type' => 'titlesmultiselect',
199  'label' => $this->msg( 'ipb-pages-label' )->text(),
200  'exists' => true,
201  'max' => 10,
202  'cssclass' => 'mw-block-restriction',
203  'showMissing' => false,
204  'excludeDynamicNamespaces' => true,
205  'input' => [
206  'autocomplete' => false
207  ],
208  'section' => 'actions',
209  ];
210  $a['NamespaceRestrictions'] = [
211  'type' => 'namespacesmultiselect',
212  'label' => $this->msg( 'ipb-namespaces-label' )->text(),
213  'exists' => true,
214  'cssclass' => 'mw-block-restriction',
215  'input' => [
216  'autocomplete' => false
217  ],
218  'section' => 'actions',
219  ];
220  }
221 
222  $a['CreateAccount'] = [
223  'type' => 'check',
224  'label-message' => 'ipbcreateaccount',
225  'default' => true,
226  'section' => 'actions',
227  ];
228 
229  if ( self::canBlockEmail( $user ) ) {
230  $a['DisableEmail'] = [
231  'type' => 'check',
232  'label-message' => 'ipbemailban',
233  'section' => 'actions',
234  ];
235  }
236 
237  if ( $blockAllowsUTEdit ) {
238  $a['DisableUTEdit'] = [
239  'type' => 'check',
240  'label-message' => 'ipb-disableusertalk',
241  'default' => false,
242  'section' => 'actions',
243  ];
244  }
245 
246  $a['Expiry'] = [
247  'type' => 'expiry',
248  'required' => true,
249  'options' => $suggestedDurations,
250  'default' => $this->msg( 'ipb-default-expiry' )->inContentLanguage()->text(),
251  'section' => 'expiry',
252  ];
253 
254  $a['Reason'] = [
255  'type' => 'selectandother',
256  // HTML maxlength uses "UTF-16 code units", which means that characters outside BMP
257  // (e.g. emojis) count for two each. This limit is overridden in JS to instead count
258  // Unicode codepoints.
260  'maxlength-unit' => 'codepoints',
261  'options-message' => 'ipbreason-dropdown',
262  'section' => 'reason',
263  ];
264 
265  $a['AutoBlock'] = [
266  'type' => 'check',
267  'label-message' => 'ipbenableautoblock',
268  'default' => true,
269  'section' => 'options',
270  ];
271 
272  # Allow some users to hide name from block log, blocklist and listusers
273  if ( MediaWikiServices::getInstance()
275  ->userHasRight( $user, 'hideuser' )
276  ) {
277  $a['HideUser'] = [
278  'type' => 'check',
279  'label-message' => 'ipbhidename',
280  'cssclass' => 'mw-block-hideuser',
281  'section' => 'options',
282  ];
283  }
284 
285  # Watchlist their user page? (Only if user is logged in)
286  if ( $user->isLoggedIn() ) {
287  $a['Watch'] = [
288  'type' => 'check',
289  'label-message' => 'ipbwatchuser',
290  'section' => 'options',
291  ];
292  }
293 
294  $a['HardBlock'] = [
295  'type' => 'check',
296  'label-message' => 'ipb-hardblock',
297  'default' => false,
298  'section' => 'options',
299  ];
300 
301  # This is basically a copy of the Target field, but the user can't change it, so we
302  # can see if the warnings we maybe showed to the user before still apply
303  $a['PreviousTarget'] = [
304  'type' => 'hidden',
305  'default' => false,
306  ];
307 
308  # We'll turn this into a checkbox if we need to
309  $a['Confirm'] = [
310  'type' => 'hidden',
311  'default' => '',
312  'label-message' => 'ipb-confirm',
313  'cssclass' => 'mw-block-confirm',
314  ];
315 
316  $this->maybeAlterFormDefaults( $a );
317 
318  // Allow extensions to add more fields
319  Hooks::run( 'SpecialBlockModifyFormFields', [ $this, &$a ] );
320 
321  return $a;
322  }
323 
329  protected function maybeAlterFormDefaults( &$fields ) {
330  # This will be overwritten by request data
331  $fields['Target']['default'] = (string)$this->target;
332 
333  if ( $this->target ) {
334  $status = self::validateTarget( $this->target, $this->getUser() );
335  if ( !$status->isOK() ) {
336  $errors = $status->getErrorsArray();
337  $this->preErrors = array_merge( $this->preErrors, $errors );
338  }
339  }
340 
341  # This won't be
342  $fields['PreviousTarget']['default'] = (string)$this->target;
343 
344  $block = DatabaseBlock::newFromTarget( $this->target );
345 
346  // Populate fields if there is a block that is not an autoblock; if it is a range
347  // block, only populate the fields if the range is the same as $this->target
348  if ( $block instanceof DatabaseBlock && $block->getType() !== DatabaseBlock::TYPE_AUTO
349  && ( $this->type != DatabaseBlock::TYPE_RANGE
350  || $block->getTarget() == $this->target )
351  ) {
352  $fields['HardBlock']['default'] = $block->isHardblock();
353  $fields['CreateAccount']['default'] = $block->isCreateAccountBlocked();
354  $fields['AutoBlock']['default'] = $block->isAutoblocking();
355 
356  if ( isset( $fields['DisableEmail'] ) ) {
357  $fields['DisableEmail']['default'] = $block->isEmailBlocked();
358  }
359 
360  if ( isset( $fields['HideUser'] ) ) {
361  $fields['HideUser']['default'] = $block->getHideName();
362  }
363 
364  if ( isset( $fields['DisableUTEdit'] ) ) {
365  $fields['DisableUTEdit']['default'] = !$block->isUsertalkEditAllowed();
366  }
367 
368  // If the username was hidden (ipb_deleted == 1), don't show the reason
369  // unless this user also has rights to hideuser: T37839
370  if ( !$block->getHideName() || MediaWikiServices::getInstance()
371  ->getPermissionManager()
372  ->userHasRight( $this->getUser(), 'hideuser' )
373  ) {
374  $fields['Reason']['default'] = $block->getReasonComment()->text;
375  } else {
376  $fields['Reason']['default'] = '';
377  }
378 
379  if ( $this->getRequest()->wasPosted() ) {
380  # Ok, so we got a POST submission asking us to reblock a user. So show the
381  # confirm checkbox; the user will only see it if they haven't previously
382  $fields['Confirm']['type'] = 'check';
383  } else {
384  # We got a target, but it wasn't a POST request, so the user must have gone
385  # to a link like [[Special:Block/User]]. We don't need to show the checkbox
386  # as long as they go ahead and block *that* user
387  $fields['Confirm']['default'] = 1;
388  }
389 
390  if ( $block->getExpiry() == 'infinity' ) {
391  $fields['Expiry']['default'] = 'infinite';
392  } else {
393  $fields['Expiry']['default'] = wfTimestamp( TS_RFC2822, $block->getExpiry() );
394  }
395 
396  $this->alreadyBlocked = true;
397  $this->preErrors[] = [ 'ipb-needreblock', wfEscapeWikiText( (string)$block->getTarget() ) ];
398  }
399 
400  if ( $this->alreadyBlocked || $this->getRequest()->wasPosted()
401  || $this->getRequest()->getCheck( 'wpCreateAccount' )
402  ) {
403  $this->getOutput()->addJsConfigVars( 'wgCreateAccountDirty', true );
404  }
405 
406  # We always need confirmation to do HideUser
407  if ( $this->requestedHideUser ) {
408  $fields['Confirm']['type'] = 'check';
409  unset( $fields['Confirm']['default'] );
410  $this->preErrors[] = [ 'ipb-confirmhideuser', 'ipb-confirmaction' ];
411  }
412 
413  # Or if the user is trying to block themselves
414  if ( (string)$this->target === $this->getUser()->getName() ) {
415  $fields['Confirm']['type'] = 'check';
416  unset( $fields['Confirm']['default'] );
417  $this->preErrors[] = [ 'ipb-blockingself', 'ipb-confirmaction' ];
418  }
419 
420  if ( $this->getConfig()->get( 'EnablePartialBlocks' ) ) {
421  if ( $block instanceof DatabaseBlock && !$block->isSitewide() ) {
422  $fields['EditingRestriction']['default'] = 'partial';
423  } else {
424  $fields['EditingRestriction']['default'] = 'sitewide';
425  }
426 
427  if ( $block instanceof DatabaseBlock ) {
428  $pageRestrictions = [];
429  $namespaceRestrictions = [];
430  foreach ( $block->getRestrictions() as $restriction ) {
431  switch ( $restriction->getType() ) {
432  case PageRestriction::TYPE:
434  '@phan-var PageRestriction $restriction';
435  if ( $restriction->getTitle() ) {
436  $pageRestrictions[] = $restriction->getTitle()->getPrefixedText();
437  }
438  break;
439  case NamespaceRestriction::TYPE:
440  $namespaceRestrictions[] = $restriction->getValue();
441  break;
442  }
443  }
444 
445  if (
446  !$block->isSitewide() &&
447  empty( $pageRestrictions ) &&
448  empty( $namespaceRestrictions )
449  ) {
450  $fields['Editing']['default'] = false;
451  }
452 
453  // Sort the restrictions so they are in alphabetical order.
454  sort( $pageRestrictions );
455  $fields['PageRestrictions']['default'] = implode( "\n", $pageRestrictions );
456  sort( $namespaceRestrictions );
457  $fields['NamespaceRestrictions']['default'] = implode( "\n", $namespaceRestrictions );
458  }
459  }
460  }
461 
466  protected function preText() {
467  $this->getOutput()->addModuleStyles( [
468  'mediawiki.widgets.TagMultiselectWidget.styles',
469  'mediawiki.special',
470  ] );
471  $this->getOutput()->addModules( [ 'mediawiki.special.block' ] );
472 
473  $blockCIDRLimit = $this->getConfig()->get( 'BlockCIDRLimit' );
474  $text = $this->msg( 'blockiptext', $blockCIDRLimit['IPv4'], $blockCIDRLimit['IPv6'] )->parse();
475 
476  $otherBlockMessages = [];
477  if ( $this->target !== null ) {
478  $targetName = $this->target;
479  if ( $this->target instanceof User ) {
480  $targetName = $this->target->getName();
481  }
482  # Get other blocks, i.e. from GlobalBlocking or TorBlock extension
483  Hooks::run( 'OtherBlockLogLink', [ &$otherBlockMessages, $targetName ] );
484 
485  if ( count( $otherBlockMessages ) ) {
487  'h2',
488  [],
489  $this->msg( 'ipb-otherblocks-header', count( $otherBlockMessages ) )->parse()
490  ) . "\n";
491 
492  $list = '';
493 
494  // @phan-suppress-next-line PhanEmptyForeach False positive
495  foreach ( $otherBlockMessages as $link ) {
496  $list .= Html::rawElement( 'li', [], $link ) . "\n";
497  }
498 
499  $s .= Html::rawElement(
500  'ul',
501  [ 'class' => 'mw-blockip-alreadyblocked' ],
502  $list
503  ) . "\n";
504 
505  $text .= $s;
506  }
507  }
508 
509  return $text;
510  }
511 
516  protected function postText() {
517  $links = [];
518 
519  $this->getOutput()->addModuleStyles( 'mediawiki.special' );
520 
521  $linkRenderer = $this->getLinkRenderer();
522  # Link to the user's contributions, if applicable
523  if ( $this->target instanceof User ) {
524  $contribsPage = SpecialPage::getTitleFor( 'Contributions', $this->target->getName() );
525  $links[] = $linkRenderer->makeLink(
526  $contribsPage,
527  $this->msg( 'ipb-blocklist-contribs', $this->target->getName() )->text()
528  );
529  }
530 
531  # Link to unblock the specified user, or to a blank unblock form
532  if ( $this->target instanceof User ) {
533  $message = $this->msg(
534  'ipb-unblock-addr',
535  wfEscapeWikiText( $this->target->getName() )
536  )->parse();
537  $list = SpecialPage::getTitleFor( 'Unblock', $this->target->getName() );
538  } else {
539  $message = $this->msg( 'ipb-unblock' )->parse();
540  $list = SpecialPage::getTitleFor( 'Unblock' );
541  }
542  $links[] = $linkRenderer->makeKnownLink(
543  $list,
544  new HtmlArmor( $message )
545  );
546 
547  # Link to the block list
548  $links[] = $linkRenderer->makeKnownLink(
549  SpecialPage::getTitleFor( 'BlockList' ),
550  $this->msg( 'ipb-blocklist' )->text()
551  );
552 
553  $user = $this->getUser();
554 
555  # Link to edit the block dropdown reasons, if applicable
556  $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
557  if ( $permissionManager->userHasRight( $user, 'editinterface' ) ) {
558  $links[] = $linkRenderer->makeKnownLink(
559  $this->msg( 'ipbreason-dropdown' )->inContentLanguage()->getTitle(),
560  $this->msg( 'ipb-edit-dropdown' )->text(),
561  [],
562  [ 'action' => 'edit' ]
563  );
564  }
565 
566  $text = Html::rawElement(
567  'p',
568  [ 'class' => 'mw-ipb-conveniencelinks' ],
569  $this->getLanguage()->pipeList( $links )
570  );
571 
572  $userTitle = self::getTargetUserTitle( $this->target );
573  if ( $userTitle ) {
574  # Get relevant extracts from the block and suppression logs, if possible
575  $out = '';
576 
578  $out,
579  'block',
580  $userTitle,
581  '',
582  [
583  'lim' => 10,
584  'msgKey' => [ 'blocklog-showlog', $userTitle->getText() ],
585  'showIfEmpty' => false
586  ]
587  );
588  $text .= $out;
589 
590  # Add suppression block entries if allowed
591  if ( $permissionManager->userHasRight( $user, 'suppressionlog' ) ) {
593  $out,
594  'suppress',
595  $userTitle,
596  '',
597  [
598  'lim' => 10,
599  'conds' => [ 'log_action' => [ 'block', 'reblock', 'unblock' ] ],
600  'msgKey' => [ 'blocklog-showsuppresslog', $userTitle->getText() ],
601  'showIfEmpty' => false
602  ]
603  );
604 
605  $text .= $out;
606  }
607  }
608 
609  return $text;
610  }
611 
618  protected static function getTargetUserTitle( $target ) {
619  if ( $target instanceof User ) {
620  return $target->getUserPage();
621  } elseif ( IP::isIPAddress( $target ) ) {
623  }
624 
625  return null;
626  }
627 
637  public static function getTargetAndType( $par, WebRequest $request = null ) {
638  $i = 0;
639  $target = null;
640 
641  while ( true ) {
642  switch ( $i++ ) {
643  case 0:
644  # The HTMLForm will check wpTarget first and only if it doesn't get
645  # a value use the default, which will be generated from the options
646  # below; so this has to have a higher precedence here than $par, or
647  # we could end up with different values in $this->target and the HTMLForm!
648  if ( $request instanceof WebRequest ) {
649  $target = $request->getText( 'wpTarget', null );
650  }
651  break;
652  case 1:
653  $target = $par;
654  break;
655  case 2:
656  if ( $request instanceof WebRequest ) {
657  $target = $request->getText( 'ip', null );
658  }
659  break;
660  case 3:
661  # B/C @since 1.18
662  if ( $request instanceof WebRequest ) {
663  $target = $request->getText( 'wpBlockAddress', null );
664  }
665  break;
666  case 4:
667  break 2;
668  }
669 
670  list( $target, $type ) = DatabaseBlock::parseTarget( $target );
671 
672  if ( $type !== null ) {
673  return [ $target, $type ];
674  }
675  }
676 
677  return [ null, null ];
678  }
679 
688  public static function validateTargetField( $value, $alldata, $form ) {
689  $status = self::validateTarget( $value, $form->getUser() );
690  if ( !$status->isOK() ) {
691  $errors = $status->getErrorsArray();
692 
693  return $form->msg( ...$errors[0] );
694  } else {
695  return true;
696  }
697  }
698 
707  public static function validateTarget( $value, User $user ) {
708  global $wgBlockCIDRLimit;
709 
711  list( $target, $type ) = self::getTargetAndType( $value );
712  $status = Status::newGood( $target );
713 
714  if ( $type == DatabaseBlock::TYPE_USER ) {
715  if ( $target->isAnon() ) {
716  $status->fatal(
717  'nosuchusershort',
719  );
720  }
721 
722  $unblockStatus = self::checkUnblockSelf( $target, $user );
723  if ( $unblockStatus !== true ) {
724  $status->fatal( 'badaccess', $unblockStatus );
725  }
726  } elseif ( $type == DatabaseBlock::TYPE_RANGE ) {
727  list( $ip, $range ) = explode( '/', $target, 2 );
728 
729  if (
730  ( IP::isIPv4( $ip ) && $wgBlockCIDRLimit['IPv4'] == 32 ) ||
731  ( IP::isIPv6( $ip ) && $wgBlockCIDRLimit['IPv6'] == 128 )
732  ) {
733  // Range block effectively disabled
734  $status->fatal( 'range_block_disabled' );
735  }
736 
737  if (
738  ( IP::isIPv4( $ip ) && $range > 32 ) ||
739  ( IP::isIPv6( $ip ) && $range > 128 )
740  ) {
741  // Dodgy range
742  $status->fatal( 'ip_range_invalid' );
743  }
744 
745  if ( IP::isIPv4( $ip ) && $range < $wgBlockCIDRLimit['IPv4'] ) {
746  $status->fatal( 'ip_range_toolarge', $wgBlockCIDRLimit['IPv4'] );
747  }
748 
749  if ( IP::isIPv6( $ip ) && $range < $wgBlockCIDRLimit['IPv6'] ) {
750  $status->fatal( 'ip_range_toolarge', $wgBlockCIDRLimit['IPv6'] );
751  }
752  } elseif ( $type == DatabaseBlock::TYPE_IP ) {
753  # All is well
754  } else {
755  $status->fatal( 'badipaddress' );
756  }
757 
758  return $status;
759  }
760 
768  public static function processForm( array $data, IContextSource $context ) {
769  $performer = $context->getUser();
770  $enablePartialBlocks = $context->getConfig()->get( 'EnablePartialBlocks' );
771  $isPartialBlock = $enablePartialBlocks &&
772  isset( $data['EditingRestriction'] ) &&
773  $data['EditingRestriction'] === 'partial';
774 
775  # This might have been a hidden field or a checkbox, so interesting data
776  # can come from it
777  $data['Confirm'] = !in_array( $data['Confirm'], [ '', '0', null, false ], true );
778 
780  list( $target, $type ) = self::getTargetAndType( $data['Target'] );
781  if ( $type == DatabaseBlock::TYPE_USER ) {
782  $user = $target;
783  $target = $user->getName();
784  $userId = $user->getId();
785 
786  # Give admins a heads-up before they go and block themselves. Much messier
787  # to do this for IPs, but it's pretty unlikely they'd ever get the 'block'
788  # permission anyway, although the code does allow for it.
789  # Note: Important to use $target instead of $data['Target']
790  # since both $data['PreviousTarget'] and $target are normalized
791  # but $data['target'] gets overridden by (non-normalized) request variable
792  # from previous request.
793  if ( $target === $performer->getName() &&
794  ( $data['PreviousTarget'] !== $target || !$data['Confirm'] )
795  ) {
796  return [ 'ipb-blockingself', 'ipb-confirmaction' ];
797  }
798  } elseif ( $type == DatabaseBlock::TYPE_RANGE ) {
799  $user = null;
800  $userId = 0;
801  } elseif ( $type == DatabaseBlock::TYPE_IP ) {
802  $user = null;
803  $target = $target->getName();
804  $userId = 0;
805  } else {
806  # This should have been caught in the form field validation
807  return [ 'badipaddress' ];
808  }
809 
810  $expiryTime = self::parseExpiryInput( $data['Expiry'] );
811 
812  if (
813  // an expiry time is needed
814  ( strlen( $data['Expiry'] ) == 0 ) ||
815  // can't be a larger string as 50 (it should be a time format in any way)
816  ( strlen( $data['Expiry'] ) > 50 ) ||
817  // check, if the time could be parsed
818  !$expiryTime
819  ) {
820  return [ 'ipb_expiry_invalid' ];
821  }
822 
823  // an expiry time should be in the future, not in the
824  // past (wouldn't make any sense) - bug T123069
825  if ( $expiryTime < wfTimestampNow() ) {
826  return [ 'ipb_expiry_old' ];
827  }
828 
829  if ( !isset( $data['DisableEmail'] ) ) {
830  $data['DisableEmail'] = false;
831  }
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 
839  if ( $data['HideUser'] ) {
840  if ( !MediaWikiServices::getInstance()
842  ->userHasRight( $performer, 'hideuser' )
843  ) {
844  # this codepath is unreachable except by a malicious user spoofing forms,
845  # or by race conditions (user has hideuser and block rights, loads block form,
846  # and loses hideuser rights before submission); so need to fail completely
847  # rather than just silently disable hiding
848  return [ 'badaccess-group0' ];
849  }
850 
851  if ( $isPartialBlock ) {
852  return [ 'ipb_hide_partial' ];
853  }
854 
855  # Recheck params here...
856  $hideUserContribLimit = $context->getConfig()->get( 'HideUserContribLimit' );
857  if ( $type != DatabaseBlock::TYPE_USER ) {
858  $data['HideUser'] = false; # IP users should not be hidden
859  } elseif ( !wfIsInfinity( $data['Expiry'] ) ) {
860  # Bad expiry.
861  return [ 'ipb_expiry_temp' ];
862  } elseif ( $hideUserContribLimit !== false
863  && $user->getEditCount() > $hideUserContribLimit
864  ) {
865  # Typically, the user should have a handful of edits.
866  # Disallow hiding users with many edits for performance.
867  return [ [ 'ipb_hide_invalid',
868  Message::numParam( $hideUserContribLimit ) ] ];
869  } elseif ( !$data['Confirm'] ) {
870  return [ 'ipb-confirmhideuser', 'ipb-confirmaction' ];
871  }
872  }
873 
874  $blockAllowsUTEdit = $context->getConfig()->get( 'BlockAllowsUTEdit' );
875  $userTalkEditAllowed = !$blockAllowsUTEdit || !$data['DisableUTEdit'];
876  if ( !$userTalkEditAllowed &&
877  $isPartialBlock &&
878  !in_array( NS_USER_TALK, explode( "\n", $data['NamespaceRestrictions'] ) )
879  ) {
880  return [ 'ipb-prevent-user-talk-edit' ];
881  }
882 
883  # Create block object.
884  $block = new DatabaseBlock();
885  $block->setTarget( $target );
886  $block->setBlocker( $performer );
887  $block->setReason( $data['Reason'][0] );
888  $block->setExpiry( $expiryTime );
889  $block->isCreateAccountBlocked( $data['CreateAccount'] );
890  $block->isUsertalkEditAllowed( $userTalkEditAllowed );
891  $block->isEmailBlocked( $data['DisableEmail'] );
892  $block->isHardblock( $data['HardBlock'] );
893  $block->isAutoblocking( $data['AutoBlock'] );
894  $block->setHideName( $data['HideUser'] );
895 
896  if ( $isPartialBlock ) {
897  $block->isSitewide( false );
898  }
899 
900  $reason = [ 'hookaborted' ];
901  if ( !Hooks::run( 'BlockIp', [ &$block, &$performer, &$reason ] ) ) {
902  return $reason;
903  }
904 
905  $pageRestrictions = [];
906  $namespaceRestrictions = [];
907  if ( $enablePartialBlocks ) {
908  if ( $data['PageRestrictions'] !== '' ) {
909  $pageRestrictions = array_map( function ( $text ) {
910  $title = Title::newFromText( $text );
911  // Use the link cache since the title has already been loaded when
912  // the field was validated.
913  $restriction = new PageRestriction( 0, $title->getArticleID() );
914  $restriction->setTitle( $title );
915  return $restriction;
916  }, explode( "\n", $data['PageRestrictions'] ) );
917  }
918  if ( $data['NamespaceRestrictions'] !== '' ) {
919  $namespaceRestrictions = array_map( function ( $id ) {
920  return new NamespaceRestriction( 0, $id );
921  }, explode( "\n", $data['NamespaceRestrictions'] ) );
922  }
923 
924  $restrictions = ( array_merge( $pageRestrictions, $namespaceRestrictions ) );
925  $block->setRestrictions( $restrictions );
926  }
927 
928  $priorBlock = null;
929  # Try to insert block. Is there a conflicting block?
930  $status = $block->insert();
931  if ( !$status ) {
932  # Indicates whether the user is confirming the block and is aware of
933  # the conflict (did not change the block target in the meantime)
934  $blockNotConfirmed = !$data['Confirm'] || ( array_key_exists( 'PreviousTarget', $data )
935  && $data['PreviousTarget'] !== $target );
936 
937  # Special case for API - T34434
938  $reblockNotAllowed = ( array_key_exists( 'Reblock', $data ) && !$data['Reblock'] );
939 
940  # Show form unless the user is already aware of this...
941  if ( $blockNotConfirmed || $reblockNotAllowed ) {
942  return [ [ 'ipb_already_blocked', $block->getTarget() ] ];
943  # Otherwise, try to update the block...
944  } else {
945  # This returns direct blocks before autoblocks/rangeblocks, since we should
946  # be sure the user is blocked by now it should work for our purposes
947  $currentBlock = DatabaseBlock::newFromTarget( $target );
948  if ( $block->equals( $currentBlock ) ) {
949  return [ [ 'ipb_already_blocked', $block->getTarget() ] ];
950  }
951  # If the name was hidden and the blocking user cannot hide
952  # names, then don't allow any block changes...
953  if ( $currentBlock->getHideName() && !MediaWikiServices::getInstance()
954  ->getPermissionManager()
955  ->userHasRight( $performer, 'hideuser' )
956  ) {
957  return [ 'cant-see-hidden-user' ];
958  }
959 
960  $priorBlock = clone $currentBlock;
961  $currentBlock->isHardblock( $block->isHardblock() );
962  $currentBlock->isCreateAccountBlocked( $block->isCreateAccountBlocked() );
963  $currentBlock->setExpiry( $block->getExpiry() );
964  $currentBlock->isAutoblocking( $block->isAutoblocking() );
965  $currentBlock->setHideName( $block->getHideName() );
966  $currentBlock->isEmailBlocked( $block->isEmailBlocked() );
967  $currentBlock->isUsertalkEditAllowed( $block->isUsertalkEditAllowed() );
968  $currentBlock->setReason( $block->getReasonComment() );
969 
970  if ( $enablePartialBlocks ) {
971  // Maintain the sitewide status. If partial blocks is not enabled,
972  // saving the block will result in a sitewide block.
973  $currentBlock->isSitewide( $block->isSitewide() );
974 
975  // Set the block id of the restrictions.
976  $blockRestrictionStore = MediaWikiServices::getInstance()->getBlockRestrictionStore();
977  $currentBlock->setRestrictions(
978  $blockRestrictionStore->setBlockId( $currentBlock->getId(), $restrictions )
979  );
980  }
981 
982  $status = $currentBlock->update();
983  // TODO handle failure
984 
985  $logaction = 'reblock';
986 
987  # Unset _deleted fields if requested
988  if ( $currentBlock->getHideName() && !$data['HideUser'] ) {
990  }
991 
992  # If hiding/unhiding a name, this should go in the private logs
993  if ( (bool)$currentBlock->getHideName() ) {
994  $data['HideUser'] = true;
995  }
996 
997  $block = $currentBlock;
998  }
999  } else {
1000  $logaction = 'block';
1001  }
1002 
1003  Hooks::run( 'BlockIpComplete', [ $block, $performer, $priorBlock ] );
1004 
1005  # Set *_deleted fields if requested
1006  if ( $data['HideUser'] ) {
1008  }
1009 
1010  # Can't watch a rangeblock
1011  if ( $type != DatabaseBlock::TYPE_RANGE && $data['Watch'] ) {
1014  $performer,
1016  );
1017  }
1018 
1019  # DatabaseBlock constructor sanitizes certain block options on insert
1020  $data['BlockEmail'] = $block->isEmailBlocked();
1021  $data['AutoBlock'] = $block->isAutoblocking();
1022 
1023  # Prepare log parameters
1024  $logParams = [];
1025  $logParams['5::duration'] = $data['Expiry'];
1026  $logParams['6::flags'] = self::blockLogFlags( $data, $type );
1027  $logParams['sitewide'] = $block->isSitewide();
1028 
1029  if ( $enablePartialBlocks && !$block->isSitewide() ) {
1030  if ( $data['PageRestrictions'] !== '' ) {
1031  $logParams['7::restrictions']['pages'] = explode( "\n", $data['PageRestrictions'] );
1032  }
1033 
1034  if ( $data['NamespaceRestrictions'] !== '' ) {
1035  $logParams['7::restrictions']['namespaces'] = explode( "\n", $data['NamespaceRestrictions'] );
1036  }
1037  }
1038 
1039  # Make log entry, if the name is hidden, put it in the suppression log
1040  $log_type = $data['HideUser'] ? 'suppress' : 'block';
1041  $logEntry = new ManualLogEntry( $log_type, $logaction );
1042  $logEntry->setTarget( Title::makeTitle( NS_USER, $target ) );
1043  $logEntry->setComment( $data['Reason'][0] );
1044  $logEntry->setPerformer( $performer );
1045  $logEntry->setParameters( $logParams );
1046  # Relate log ID to block ID (T27763)
1047  $logEntry->setRelations( [ 'ipb_id' => $block->getId() ] );
1048  $logId = $logEntry->insert();
1049 
1050  if ( !empty( $data['Tags'] ) ) {
1051  $logEntry->addTags( $data['Tags'] );
1052  }
1053 
1054  $logEntry->publish( $logId );
1055 
1056  return true;
1057  }
1058 
1069  public static function getSuggestedDurations( Language $lang = null, $includeOther = true ) {
1070  $a = [];
1071  $msg = $lang === null
1072  ? wfMessage( 'ipboptions' )->inContentLanguage()->text()
1073  : wfMessage( 'ipboptions' )->inLanguage( $lang )->text();
1074 
1075  if ( $msg == '-' ) {
1076  return [];
1077  }
1078 
1079  foreach ( explode( ',', $msg ) as $option ) {
1080  if ( strpos( $option, ':' ) === false ) {
1081  $option = "$option:$option";
1082  }
1083 
1084  list( $show, $value ) = explode( ':', $option );
1085  $a[$show] = $value;
1086  }
1087 
1088  if ( $a && $includeOther ) {
1089  // if options exist, add other to the end instead of the begining (which
1090  // is what happens by default).
1091  $a[ wfMessage( 'ipbother' )->text() ] = 'other';
1092  }
1093 
1094  return $a;
1095  }
1096 
1108  public static function parseExpiryInput( $expiry ) {
1109  if ( wfIsInfinity( $expiry ) ) {
1110  return 'infinity';
1111  }
1112 
1113  $expiry = strtotime( $expiry );
1114 
1115  if ( $expiry < 0 || $expiry === false ) {
1116  return false;
1117  }
1118 
1119  return wfTimestamp( TS_MW, $expiry );
1120  }
1121 
1127  public static function canBlockEmail( UserIdentity $user ) {
1128  global $wgEnableUserEmail;
1129 
1130  return ( $wgEnableUserEmail && MediaWikiServices::getInstance()
1132  ->userHasRight( $user, 'blockemail' ) );
1133  }
1134 
1149  public static function checkUnblockSelf( $target, User $performer ) {
1150  if ( is_int( $target ) ) {
1152  } elseif ( is_string( $target ) ) {
1154  }
1155  if ( $performer->getBlock() ) {
1156  if ( $target instanceof User && $target->getId() == $performer->getId() ) {
1157  # User is trying to unblock themselves
1158  if ( MediaWikiServices::getInstance()
1159  ->getPermissionManager()
1160  ->userHasRight( $performer, 'unblockself' )
1161  ) {
1162  return true;
1163  # User blocked themselves and is now trying to reverse it
1164  } elseif ( $performer->blockedBy() === $performer->getName() ) {
1165  return true;
1166  } else {
1167  return 'ipbnounblockself';
1168  }
1169  } elseif (
1170  $target instanceof User &&
1171  $performer->getBlock() instanceof DatabaseBlock &&
1172  $performer->getBlock()->getBy() &&
1173  $performer->getBlock()->getBy() === $target->getId()
1174  ) {
1175  // Allow users to block the user that blocked them.
1176  // This is to prevent a situation where a malicious user
1177  // blocks all other users. This way, the non-malicious
1178  // user can block the malicious user back, resulting
1179  // in a stalemate.
1180  return true;
1181 
1182  } else {
1183  # User is trying to block/unblock someone else
1184  return 'ipbblocked';
1185  }
1186  } else {
1187  return true;
1188  }
1189  }
1190 
1198  protected static function blockLogFlags( array $data, $type ) {
1199  $config = RequestContext::getMain()->getConfig();
1200 
1201  $blockAllowsUTEdit = $config->get( 'BlockAllowsUTEdit' );
1202 
1203  $flags = [];
1204 
1205  # when blocking a user the option 'anononly' is not available/has no effect
1206  # -> do not write this into log
1207  if ( !$data['HardBlock'] && $type != DatabaseBlock::TYPE_USER ) {
1208  // For grepping: message block-log-flags-anononly
1209  $flags[] = 'anononly';
1210  }
1211 
1212  if ( $data['CreateAccount'] ) {
1213  // For grepping: message block-log-flags-nocreate
1214  $flags[] = 'nocreate';
1215  }
1216 
1217  # Same as anononly, this is not displayed when blocking an IP address
1218  if ( !$data['AutoBlock'] && $type == DatabaseBlock::TYPE_USER ) {
1219  // For grepping: message block-log-flags-noautoblock
1220  $flags[] = 'noautoblock';
1221  }
1222 
1223  if ( $data['DisableEmail'] ) {
1224  // For grepping: message block-log-flags-noemail
1225  $flags[] = 'noemail';
1226  }
1227 
1228  if ( $blockAllowsUTEdit && $data['DisableUTEdit'] ) {
1229  // For grepping: message block-log-flags-nousertalk
1230  $flags[] = 'nousertalk';
1231  }
1232 
1233  if ( $data['HideUser'] ) {
1234  // For grepping: message block-log-flags-hiddenname
1235  $flags[] = 'hiddenname';
1236  }
1237 
1238  return implode( ',', $flags );
1239  }
1240 
1247  public function onSubmit( array $data, HTMLForm $form = null ) {
1248  // If "Editing" checkbox is unchecked, the block must be a partial block affecting
1249  // actions other than editing, and there must be no restrictions.
1250  if ( isset( $data['Editing'] ) && $data['Editing'] === false ) {
1251  $data['EditingRestriction'] = 'partial';
1252  $data['PageRestrictions'] = '';
1253  $data['NamespaceRestrictions'] = '';
1254  }
1255  return self::processForm( $data, $form->getContext() );
1256  }
1257 
1262  public function onSuccess() {
1263  $out = $this->getOutput();
1264  $out->setPageTitle( $this->msg( 'blockipsuccesssub' ) );
1265  $out->addWikiMsg( 'blockipsuccesstext', wfEscapeWikiText( $this->target ) );
1266  }
1267 
1276  public function prefixSearchSubpages( $search, $limit, $offset ) {
1277  $user = User::newFromName( $search );
1278  if ( !$user ) {
1279  // No prefix suggestion for invalid user
1280  return [];
1281  }
1282  // Autocomplete subpage as user list - public to allow caching
1283  return UserNamePrefixSearch::search( 'public', $search, $limit, $offset );
1284  }
1285 
1286  protected function getGroupName() {
1287  return 'users';
1288  }
1289 }
static getSuggestedDurations(Language $lang=null, $includeOther=true)
Get an array of suggested block durations from MediaWiki:Ipboptions.
A special page that allows users with &#39;block&#39; right to block users from editing pages and other actio...
static checkUnblockSelf( $target, User $performer)
T17810: blocked admins should not be able to block/unblock others, and probably shouldn&#39;t be able to ...
static isIPAddress( $ip)
Determine if a string is as valid IP address or network (CIDR prefix).
Definition: IP.php:77
static processForm(array $data, IContextSource $context)
Given the form data, actually implement a block.
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking, formatting, etc.
static validateTarget( $value, User $user)
Validate a block target.
$context
Definition: load.php:45
static validateTargetField( $value, $alldata, $form)
HTMLForm field validation-callback for Target field.
setTitle(\Title $title)
Set the title.
if(!isset( $args[0])) $lang
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:209
requiresUnblock()
We allow certain special cases where user is blocked.
blockedBy()
If user is blocked, return the name of the user who placed the block.
Definition: User.php:2089
getBlock( $fromReplica=true)
Get the block affecting the user, or null if the user is not blocked.
Definition: User.php:2064
wfIsInfinity( $str)
Determine input string is represents as infinity.
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
bool $requestedHideUser
Whether the previous submission of the form asked for HideUser.
static getTargetUserTitle( $target)
Get a user page target for things like logs.
getOutput()
Get the OutputPage being used for this instance.
A DatabaseBlock (unlike a SystemBlock) is stored in the database, may give rise to autoblocks and may...
const COMMENT_CHARACTER_LIMIT
Maximum length of a comment in UTF-8 characters.
preText()
Add header elements like block log entries, etc.
static numParam( $num)
Definition: Message.php:1038
Special page which uses an HTMLForm to handle processing.
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:2230
static search( $audience, $search, $limit, $offset=0)
Do a prefix search of user names and return a list of matching user names.
Interface for objects representing user identity.
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:51
getPermissionManager()
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
$wgBlockCIDRLimit
Limits on the possible sizes of range blocks.
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
getConfig()
Get the site configuration.
setSubmitDestructive()
Identify that the submit button in the form has a destructive action.
Definition: HTMLForm.php:1378
static getMain()
Get the RequestContext object associated with the main request.
int $type
DatabaseBlock::TYPE_ constant.
onSuccess()
Do something exciting on successful processing of the form, most likely to show a confirmation messag...
An error page which can definitely be safely rendered using the OutputPage.
isAnon()
Get whether the user is anonymous.
Definition: User.php:3531
isSitewide( $x=null)
Indicates that the block is a sitewide block.
static getTargetAndType( $par, WebRequest $request=null)
Determine the target of the block, and the type of target.
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:81
postText()
Add footer elements to the form.
setHeaderText( $msg, $section=null)
Set header text, inside the form.
Definition: HTMLForm.php:828
formatErrors( $errors)
Format a stack of error messages into a single HTML string.
Definition: HTMLForm.php:1343
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
setParameter( $par)
Handle some magic here.
const IGNORE_USER_RIGHTS
Definition: User.php:83
static doWatch(Title $title, User $user, $checkRights=User::CHECK_USER_RIGHTS)
Watch a page.
getSkin()
Shortcut to get the skin being used for this instance.
bool $alreadyBlocked
static unsuppressUserName( $name, $userId, IDatabase $dbw=null)
static canBlockEmail(UserIdentity $user)
Can we do an email block?
$wgEnableUserEmail
Set to true to enable user-to-user e-mail.
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don&#39;t need a full Title object...
Definition: SpecialPage.php:83
User string $previousTarget
The previous block target.
prefixSearchSubpages( $search, $limit, $offset)
Return an array of subpages beginning with $search that this special page will accept.
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:612
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:586
$expiryTime
string null $par
The sub-page of the special page.
static isIPv6( $ip)
Given a string, determine if it as valid IP in IPv6 only.
Definition: IP.php:88
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition: User.php:543
getName()
Get the name of this Special Page.
getFormFields()
Get the HTMLForm descriptor array for the block form.
getId()
Get the user&#39;s ID.
Definition: User.php:2201
static suppressUserName( $name, $userId, IDatabase $dbw=null)
static blockLogFlags(array $data, $type)
Return a comma-delimited list of "flags" to be passed to the log reader for this block, to provide more information in the logs.
getUser()
Shortcut to get the User executing this instance.
checkExecutePermissions(User $user)
Checks that the user can unblock themselves if they are trying to do so.
getConfig()
Shortcut to get main config object.
getUserPage()
Get this user&#39;s personal page title.
Definition: User.php:4274
alterForm(HTMLForm $form)
Customizes the HTMLForm a bit.
static isIPv4( $ip)
Given a string, determine if it as valid IP in IPv4 only.
Definition: IP.php:99
static parseExpiryInput( $expiry)
Convert a submitted expiry time, which may be relative ("2 weeks", etc) or absolute ("24 May 2034"...
getLanguage()
Shortcut to get user&#39;s language.
setSubmitTextMsg( $msg)
Set the text for the submit button to a message.
Definition: HTMLForm.php:1392
User string null $target
User to be blocked, as passed either by parameter (url?wpTarget=Foo) or as subpage (Special:Block/Foo...
array $preErrors
onSubmit(array $data, HTMLForm $form=null)
Process the form on POST submission.
maybeAlterFormDefaults(&$fields)
If the user has already been blocked with similar settings, load that block and change the defaults f...
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
getRequest()
Get the WebRequest being used for this instance.
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:519
addHeaderText( $msg, $section=null)
Add HTML to the header, inside the form.
Definition: HTMLForm.php:806
const NS_USER_TALK
Definition: Defines.php:63
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
static showLogExtract(&$out, $types=[], $page='', $user='', $param=[])
Show log extract.
MediaWiki Linker LinkRenderer null $linkRenderer
Definition: SpecialPage.php:67
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:319