62 private array $formattedComments = [];
65 private array $messages = [];
84 parent::__construct( $context, $linkRenderer );
86 $this->blockActionInfo = $blockActionInfo;
87 $this->blockRestrictionStore = $blockRestrictionStore;
88 $this->blockTargetFactory = $blockTargetFactory;
89 $this->hideUserUtils = $hideUserUtils;
90 $this->commentStore = $commentStore;
91 $this->linkBatchFactory = $linkBatchFactory;
92 $this->rowCommentFormatter = $rowCommentFormatter;
93 $this->specialPageFactory = $specialPageFactory;
95 $this->mDefaultDirection = IndexPager::DIR_DESCENDING;
100 static $headers =
null;
102 if ( $headers ===
null ) {
104 'bl_timestamp' =>
'blocklist-timestamp',
105 'target' =>
'blocklist-target',
106 'bl_expiry' =>
'blocklist-expiry',
107 'bl_by' =>
'blocklist-by',
108 'params' =>
'blocklist-params',
109 'bl_reason' =>
'blocklist-reason',
111 foreach ( $headers as $key => $val ) {
112 $headers[$key] = $this->
msg( $val )->text();
125 if ( $this->messages === [] ) {
129 'createaccountblock',
132 'blocklist-nousertalk',
137 'blocklist-editing-sitewide',
138 'blocklist-hidden-param',
139 'blocklist-hidden-placeholder',
142 foreach ( $keys as $key ) {
143 $this->messages[$key] = $this->
msg( $key )->text();
158 $formatted = $linkRenderer->makeKnownLink(
159 $this->specialPageFactory->getTitleForAlias(
'BlockList' ),
160 $language->userTimeAndDate( $value, $this->getUser() ),
162 [
'wpTarget' =>
"#{$row->bl_id}" ],
167 $formatted = $this->formatTarget( $row );
171 $formatted = htmlspecialchars( $language->formatExpiry(
178 $links = $this->getBlockChangeLinks( $row );
179 $formatted .=
' ' . Html::rawElement(
181 [
'class' =>
'mw-blocklist-actions' ],
182 $this->
msg(
'parentheses' )->rawParams(
183 $language->pipeList( $links ) )->escaped()
186 if ( $value !==
'infinity' ) {
188 $formatted .=
'<br />' . $this->
msg(
189 'ipb-blocklist-duration-left',
190 $language->formatDurationBetweenTimestamps(
191 (
int)$timestamp->getTimestamp( TS::UNIX ),
200 $formatted = Linker::userLink( (
int)$value, $row->bl_by_text );
201 $formatted .= Linker::userToolLinks( (
int)$value, $row->bl_by_text );
211 if ( $row->bl_deleted ) {
212 $properties[] = htmlspecialchars( $this->messages[
'blocklist-hidden-param' ] );
214 if ( $row->bl_sitewide ) {
215 $properties[] = htmlspecialchars( $this->messages[
'blocklist-editing-sitewide'] );
218 if ( !$row->bl_sitewide && $this->restrictions ) {
219 $list = $this->getRestrictionListHTML( $row );
221 $properties[] = htmlspecialchars( $this->messages[
'blocklist-editing'] ) . $list;
225 if ( $row->bl_anon_only ) {
226 $properties[] = htmlspecialchars( $this->messages[
'anononlyblock'] );
228 if ( $row->bl_create_account ) {
229 $properties[] = htmlspecialchars( $this->messages[
'createaccountblock'] );
231 if ( $row->bt_user && !$row->bl_enable_autoblock ) {
232 $properties[] = htmlspecialchars( $this->messages[
'noautoblockblock'] );
235 if ( $row->bl_block_email ) {
236 $properties[] = htmlspecialchars( $this->messages[
'emailblock'] );
239 if ( !$row->bl_allow_usertalk ) {
240 $properties[] = htmlspecialchars( $this->messages[
'blocklist-nousertalk'] );
243 $formatted = Html::rawElement(
246 implode(
'', array_map(
static function ( $prop ) {
247 return Html::rawElement(
257 $formatted =
"Unable to format $name";
269 private function formatTarget( $row ) {
270 if ( $row->bt_auto ) {
271 return $this->
msg(
'autoblockid', $row->bl_id )->parse();
274 $target = $this->blockTargetFactory->newFromRowRedacted( $row );
278 $userName = $target->toString();
279 } elseif ( ( $row->hu_deleted ??
null )
280 && !$this->getAuthority()->isAllowed(
'hideuser' )
284 [
'class' =>
'mw-blocklist-hidden' ],
285 $this->messages[
'blocklist-hidden-placeholder']
287 } elseif ( $target instanceof BlockTargetWithUserPage ) {
288 $user = $target->getUserIdentity();
289 $userId = $user->getId();
290 $userName = $user->getName();
292 return $this->
msg(
'empty-username' )->escaped();
294 return Linker::userLink( $userId, $userName ) .
295 Linker::userToolLinks(
299 Linker::TOOL_LINKS_NOBLOCK
309 private function getBlockChangeLinks( $row ): array {
312 $target = $this->blockTargetFactory->newFromRowRedacted( $row )->toString();
314 $query = [
'id' => $row->bl_id ];
315 if ( $row->bt_auto ) {
316 $links[] = $linkRenderer->makeKnownLink(
317 $this->specialPageFactory->getTitleForAlias(
'Unblock' ),
318 $this->messages[
'remove-blocklink'],
320 [
'wpTarget' =>
"#{$row->bl_id}" ]
323 $specialBlock = $this->specialPageFactory->getTitleForAlias(
"Block/$target" );
324 $links[] = $linkRenderer->makeKnownLink(
326 $this->messages[
'remove-blocklink'],
328 $query + [
'remove' =>
'1' ]
330 $links[] = $linkRenderer->makeKnownLink(
332 $this->messages[
'change-blocklink'],
338 if ( $row->bt_auto ) {
339 $links[] = $linkRenderer->makeKnownLink(
340 $this->specialPageFactory->getTitleForAlias(
'Unblock' ),
341 $this->messages[
'unblocklink'],
343 [
'wpTarget' =>
"#{$row->bl_id}" ]
346 $links[] = $linkRenderer->makeKnownLink(
347 $this->specialPageFactory->getTitleForAlias(
"Unblock/$target" ),
348 $this->messages[
'unblocklink']
350 $links[] = $linkRenderer->makeKnownLink(
351 $this->specialPageFactory->getTitleForAlias(
"Block/$target" ),
352 $this->messages[
'change-blocklink']
366 private function getRestrictionListHTML( stdClass $row ) {
370 foreach ( $this->restrictions as $restriction ) {
371 if ( $restriction->getBlockId() !== (
int)$row->bl_id ) {
375 switch ( $restriction->getType() ) {
376 case PageRestriction::TYPE:
377 '@phan-var PageRestriction $restriction';
378 if ( $restriction->getTitle() ) {
379 $items[$restriction->getType()][] = Html::rawElement(
382 $linkRenderer->makeLink( $restriction->getTitle() )
386 case NamespaceRestriction::TYPE:
387 $text = $restriction->getValue() ===
NS_MAIN
388 ? $this->messages[
'blanknamespace']
389 : $this->getLanguage()->getFormattedNsText(
390 $restriction->getValue()
393 $items[$restriction->getType()][] = Html::rawElement(
396 $linkRenderer->makeLink(
397 $this->specialPageFactory->getTitleForAlias(
'Allpages' ),
401 'namespace' => $restriction->getValue()
407 case ActionRestriction::TYPE:
408 $actionName = $this->blockActionInfo->getActionFromId( $restriction->getValue() );
417 $this->
msg(
'ipb-action-' .
418 $this->blockActionInfo->getActionFromId( $restriction->getValue() ) )->text()
430 foreach ( $items as $key => $value ) {
431 $sets[] = Html::rawElement(
439 $this->
msg(
'blocklist-editing-' . $key ) . Html::rawElement(
442 implode(
'', $value )
447 return Html::rawElement(
456 $db = $this->getDatabase();
457 $commentQuery = $this->commentStore->getJoin(
'bl_reason' );
461 'block_by_actor' =>
'actor',
463 ...$commentQuery[
'tables'],
475 'bl_by' =>
'block_by_actor.actor_user',
476 'bl_by_text' =>
'block_by_actor.actor_name',
480 'bl_enable_autoblock',
486 ] + $commentQuery[
'fields'],
487 'conds' => $this->conds,
489 'block_by_actor' => [
'JOIN',
'actor_id=bl_by_actor' ],
490 'block_target' => [
'JOIN',
'bt_id=bl_target' ],
491 ] + $commentQuery[
'joins']
494 # Filter out any expired blocks
495 $info[
'conds'][] = $db->expr(
'bl_expiry',
'>', $db->timestamp() );
497 # Filter out blocks with the deleted option if the user doesn't
498 # have permission to see hidden users
499 # TODO: consider removing this -- we could just redact them instead.
500 # The mere fact that an admin has deleted a user does not need to
501 # be private and could be included in block lists and logs for
502 # transparency purposes. Previously, filtering out deleted blocks
503 # was a convenient way to avoid showing the target name.
504 if ( $this->getAuthority()->isAllowed(
'hideuser' ) ) {
505 $info[
'fields'][
'hu_deleted'] = $this->hideUserUtils->getExpression(
507 'block_target.bt_user',
508 HideUserUtils::HIDDEN_USERS
511 $info[
'fields'][
'hu_deleted'] = 0;
512 $info[
'conds'][] = $this->hideUserUtils->getExpression(
514 'block_target.bt_user',
515 HideUserUtils::SHOWN_USERS
517 $info[
'conds'][
'bl_deleted'] = 0;
524 return parent::getTableClass() .
' mw-blocklist';
529 return [ [
'bl_timestamp',
'bl_id' ] ];
548 $lb = $this->linkBatchFactory->newLinkBatch();
549 $lb->setCaller( __METHOD__ );
552 foreach ( $result as $row ) {
553 $target = $row->bt_address ?? $row->bt_user_text;
554 if ( $target !==
null ) {
558 if ( isset( $row->bl_by_text ) ) {
559 $lb->add(
NS_USER, $row->bl_by_text );
563 if ( !$row->bl_sitewide ) {
564 $partialBlocks[] = (int)$row->bl_id;
568 if ( $partialBlocks ) {
571 $this->restrictions = $this->blockRestrictionStore->loadByBlockId( $partialBlocks );
573 foreach ( $this->restrictions as $restriction ) {
574 if ( $restriction->getType() === PageRestriction::TYPE ) {
575 '@phan-var PageRestriction $restriction';
576 $title = $restriction->getTitle();
578 $lb->addObj( $title );
588 $this->formattedComments = $this->rowCommentFormatter->formatRows( $result,
'bl_reason' );