73 private array $formattedComments = [];
76 private array $messages = [];
95 parent::__construct( $context, $linkRenderer );
97 $this->blockActionInfo = $blockActionInfo;
98 $this->blockRestrictionStore = $blockRestrictionStore;
99 $this->blockTargetFactory = $blockTargetFactory;
100 $this->hideUserUtils = $hideUserUtils;
101 $this->commentStore = $commentStore;
102 $this->linkBatchFactory = $linkBatchFactory;
103 $this->rowCommentFormatter = $rowCommentFormatter;
104 $this->specialPageFactory = $specialPageFactory;
110 static $headers =
null;
112 if ( $headers ===
null ) {
114 'bl_timestamp' =>
'blocklist-timestamp',
115 'target' =>
'blocklist-target',
116 'bl_expiry' =>
'blocklist-expiry',
117 'bl_by' =>
'blocklist-by',
118 'params' =>
'blocklist-params',
119 'bl_reason' =>
'blocklist-reason',
121 foreach ( $headers as $key => $val ) {
122 $headers[$key] = $this->
msg( $val )->text();
135 if ( $this->messages === [] ) {
139 'createaccountblock',
142 'blocklist-nousertalk',
147 'blocklist-editing-sitewide',
148 'blocklist-hidden-param',
149 'blocklist-hidden-placeholder',
152 foreach ( $keys as $key ) {
153 $this->messages[$key] = $this->
msg( $key )->text();
168 $formatted = $linkRenderer->makeKnownLink(
169 $this->specialPageFactory->getTitleForAlias(
'BlockList' ),
170 $language->userTimeAndDate( $value, $this->getUser() ),
172 [
'wpTarget' =>
"#{$row->bl_id}" ],
177 $formatted = $this->formatTarget( $row );
181 $formatted = htmlspecialchars( $language->formatExpiry(
188 $links = $this->getBlockChangeLinks( $row );
189 $formatted .=
' ' . Html::rawElement(
191 [
'class' =>
'mw-blocklist-actions' ],
192 $this->
msg(
'parentheses' )->rawParams(
193 $language->pipeList( $links ) )->escaped()
196 if ( $value !==
'infinity' ) {
198 $formatted .=
'<br />' . $this->
msg(
199 'ipb-blocklist-duration-left',
200 $language->formatDurationBetweenTimestamps(
201 (
int)$timestamp->getTimestamp( TS_UNIX ),
210 $formatted = Linker::userLink( (
int)$value, $row->bl_by_text );
211 $formatted .= Linker::userToolLinks( (
int)$value, $row->bl_by_text );
221 if ( $row->bl_deleted ) {
222 $properties[] = htmlspecialchars( $this->messages[
'blocklist-hidden-param' ] );
224 if ( $row->bl_sitewide ) {
225 $properties[] = htmlspecialchars( $this->messages[
'blocklist-editing-sitewide'] );
228 if ( !$row->bl_sitewide && $this->restrictions ) {
229 $list = $this->getRestrictionListHTML( $row );
231 $properties[] = htmlspecialchars( $this->messages[
'blocklist-editing'] ) . $list;
235 if ( $row->bl_anon_only ) {
236 $properties[] = htmlspecialchars( $this->messages[
'anononlyblock'] );
238 if ( $row->bl_create_account ) {
239 $properties[] = htmlspecialchars( $this->messages[
'createaccountblock'] );
241 if ( $row->bt_user && !$row->bl_enable_autoblock ) {
242 $properties[] = htmlspecialchars( $this->messages[
'noautoblockblock'] );
245 if ( $row->bl_block_email ) {
246 $properties[] = htmlspecialchars( $this->messages[
'emailblock'] );
249 if ( !$row->bl_allow_usertalk ) {
250 $properties[] = htmlspecialchars( $this->messages[
'blocklist-nousertalk'] );
253 $formatted = Html::rawElement(
256 implode(
'', array_map(
static function ( $prop ) {
257 return Html::rawElement(
267 $formatted =
"Unable to format $name";
279 private function formatTarget( $row ) {
280 if ( $row->bt_auto ) {
281 return $this->
msg(
'autoblockid', $row->bl_id )->parse();
284 $target = $this->blockTargetFactory->newFromRowRedacted( $row );
286 if ( $target instanceof RangeBlockTarget ) {
288 $userName = $target->toString();
289 } elseif ( ( $row->hu_deleted ??
null )
290 && !$this->getAuthority()->isAllowed(
'hideuser' )
294 [
'class' =>
'mw-blocklist-hidden' ],
295 $this->messages[
'blocklist-hidden-placeholder']
297 } elseif ( $target instanceof BlockTargetWithUserPage ) {
298 $user = $target->getUserIdentity();
299 $userId = $user->getId();
300 $userName = $user->getName();
302 return $this->
msg(
'empty-username' )->escaped();
304 return Linker::userLink( $userId, $userName ) .
305 Linker::userToolLinks(
309 Linker::TOOL_LINKS_NOBLOCK
319 private function getBlockChangeLinks( $row ): array {
322 $target = $this->blockTargetFactory->newFromRowRedacted( $row )->toString();
324 $query = [
'id' => $row->bl_id ];
325 if ( $row->bt_auto ) {
326 $links[] = $linkRenderer->makeKnownLink(
327 $this->specialPageFactory->getTitleForAlias(
'Unblock' ),
328 $this->messages[
'remove-blocklink'],
330 [
'wpTarget' =>
"#{$row->bl_id}" ]
333 $specialBlock = $this->specialPageFactory->getTitleForAlias(
"Block/$target" );
334 $links[] = $linkRenderer->makeKnownLink(
336 $this->messages[
'remove-blocklink'],
338 $query + [
'remove' =>
'1' ]
340 $links[] = $linkRenderer->makeKnownLink(
342 $this->messages[
'change-blocklink'],
348 if ( $row->bt_auto ) {
349 $links[] = $linkRenderer->makeKnownLink(
350 $this->specialPageFactory->getTitleForAlias(
'Unblock' ),
351 $this->messages[
'unblocklink'],
353 [
'wpTarget' =>
"#{$row->bl_id}" ]
356 $links[] = $linkRenderer->makeKnownLink(
357 $this->specialPageFactory->getTitleForAlias(
"Unblock/$target" ),
358 $this->messages[
'unblocklink']
360 $links[] = $linkRenderer->makeKnownLink(
361 $this->specialPageFactory->getTitleForAlias(
"Block/$target" ),
362 $this->messages[
'change-blocklink']
376 private function getRestrictionListHTML( stdClass $row ) {
378 $linkRenderer = $this->getLinkRenderer();
380 foreach ( $this->restrictions as $restriction ) {
381 if ( $restriction->getBlockId() !== (
int)$row->bl_id ) {
385 switch ( $restriction->getType() ) {
386 case PageRestriction::TYPE:
387 '@phan-var PageRestriction $restriction';
388 if ( $restriction->getTitle() ) {
389 $items[$restriction->getType()][] = Html::rawElement(
392 $linkRenderer->makeLink( $restriction->getTitle() )
396 case NamespaceRestriction::TYPE:
397 $text = $restriction->getValue() ===
NS_MAIN
398 ? $this->messages[
'blanknamespace']
399 : $this->getLanguage()->getFormattedNsText(
400 $restriction->getValue()
403 $items[$restriction->getType()][] = Html::rawElement(
406 $linkRenderer->makeLink(
407 $this->specialPageFactory->getTitleForAlias(
'Allpages' ),
411 'namespace' => $restriction->getValue()
417 case ActionRestriction::TYPE:
418 $actionName = $this->blockActionInfo->getActionFromId( $restriction->getValue() );
419 $enablePartialActionBlocks =
421 if ( $actionName && $enablePartialActionBlocks ) {
422 $items[$restriction->getType()][] = Html::rawElement(
429 $this->msg(
'ipb-action-' .
430 $this->blockActionInfo->getActionFromId( $restriction->getValue() ) )->escaped()
442 foreach ( $items as $key => $value ) {
443 $sets[] = Html::rawElement(
451 $this->msg(
'blocklist-editing-' . $key ) . Html::rawElement(
454 implode(
'', $value )
459 return Html::rawElement(
467 $db = $this->getDatabase();
468 $commentQuery = $this->commentStore->getJoin(
'bl_reason' );
470 'tables' => array_merge(
473 'block_by_actor' =>
'actor',
476 $commentQuery[
'tables']
488 'bl_by' =>
'block_by_actor.actor_user',
489 'bl_by_text' =>
'block_by_actor.actor_name',
493 'bl_enable_autoblock',
499 ] + $commentQuery[
'fields'],
500 'conds' => $this->conds,
502 'block_by_actor' => [
'JOIN',
'actor_id=bl_by_actor' ],
503 'block_target' => [
'JOIN',
'bt_id=bl_target' ],
504 ] + $commentQuery[
'joins']
507 # Filter out any expired blocks
508 $info[
'conds'][] = $db->expr(
'bl_expiry',
'>', $db->timestamp() );
510 # Filter out blocks with the deleted option if the user doesn't
511 # have permission to see hidden users
512 # TODO: consider removing this -- we could just redact them instead.
513 # The mere fact that an admin has deleted a user does not need to
514 # be private and could be included in block lists and logs for
515 # transparency purposes. Previously, filtering out deleted blocks
516 # was a convenient way to avoid showing the target name.
517 if ( !$this->getAuthority()->isAllowed(
'hideuser' ) ) {
518 $info[
'conds'][
'bl_deleted'] = 0;
521 # Determine if the user is hidden
522 # With multiblocks we can't just rely on bl_deleted in the row being formatted
523 $info[
'fields'][
'hu_deleted'] = $this->hideUserUtils->getExpression(
525 'block_target.bt_user',
526 HideUserUtils::HIDDEN_USERS );
531 return parent::getTableClass() .
' mw-blocklist';
535 return [ [
'bl_timestamp',
'bl_id' ] ];
552 $lb = $this->linkBatchFactory->newLinkBatch();
553 $lb->setCaller( __METHOD__ );
556 foreach ( $result as $row ) {
557 $target = $row->bt_address ?? $row->bt_user_text;
558 if ( $target !==
null ) {
562 if ( isset( $row->bl_by_text ) ) {
563 $lb->add(
NS_USER, $row->bl_by_text );
567 if ( !$row->bl_sitewide ) {
568 $partialBlocks[] = (int)$row->bl_id;
572 if ( $partialBlocks ) {
575 $this->restrictions = $this->blockRestrictionStore->loadByBlockId( $partialBlocks );
577 foreach ( $this->restrictions as $restriction ) {
578 if ( $restriction->getType() === PageRestriction::TYPE ) {
579 '@phan-var PageRestriction $restriction';
580 $title = $restriction->getTitle();
582 $lb->addObj( $title );
592 $this->formattedComments = $this->rowCommentFormatter->formatRows( $result,
'bl_reason' );