51 private array $formattedComments = [];
54 private array $messages = [];
68 protected array $conds,
73 parent::__construct( $context, $linkRenderer );
75 $this->mDefaultDirection = IndexPager::DIR_DESCENDING;
80 static $headers =
null;
82 if ( $headers ===
null ) {
84 'bl_timestamp' =>
'blocklist-timestamp',
85 'target' =>
'blocklist-target',
86 'bl_expiry' =>
'blocklist-expiry',
87 'bl_by' =>
'blocklist-by',
88 'params' =>
'blocklist-params',
89 'bl_reason' =>
'blocklist-reason',
91 foreach ( $headers as $key => $val ) {
92 $headers[$key] = $this->
msg( $val )->text();
105 if ( $this->messages === [] ) {
109 'createaccountblock',
112 'blocklist-nousertalk',
117 'blocklist-editing-sitewide',
118 'blocklist-hidden-param',
119 'blocklist-hidden-placeholder',
122 foreach ( $keys as $key ) {
123 $this->messages[$key] = $this->
msg( $key )->text();
138 $formatted = $linkRenderer->makeKnownLink(
139 $this->specialPageFactory->getTitleForAlias(
'BlockList' ),
140 $language->userTimeAndDate( $value, $this->getUser() ),
142 [
'wpTarget' =>
"#{$row->bl_id}" ],
147 $formatted = $this->formatTarget( $row );
151 $formatted = htmlspecialchars( $language->formatExpiry(
158 $links = $this->getBlockChangeLinks( $row );
159 $formatted .=
' ' . Html::rawElement(
161 [
'class' =>
'mw-blocklist-actions' ],
162 $this->
msg(
'parentheses' )->rawParams(
163 $language->pipeList( $links ) )->escaped()
166 if ( $value !==
'infinity' ) {
168 $formatted .=
'<br />' . $this->
msg(
169 'ipb-blocklist-duration-left',
170 $language->formatDurationBetweenTimestamps(
171 (
int)$timestamp->getTimestamp( TS::UNIX ),
180 $formatted = Linker::userLink( (
int)$value, $row->bl_by_text );
181 $formatted .= Linker::userToolLinks( (
int)$value, $row->bl_by_text );
191 if ( $row->bl_deleted ) {
192 $properties[] = htmlspecialchars( $this->messages[
'blocklist-hidden-param' ] );
194 if ( $row->bl_sitewide ) {
195 $properties[] = htmlspecialchars( $this->messages[
'blocklist-editing-sitewide'] );
198 if ( !$row->bl_sitewide && $this->restrictions ) {
199 $list = $this->getRestrictionListHTML( $row );
201 $properties[] = htmlspecialchars( $this->messages[
'blocklist-editing'] ) . $list;
205 if ( $row->bl_anon_only ) {
206 $properties[] = htmlspecialchars( $this->messages[
'anononlyblock'] );
208 if ( $row->bl_create_account ) {
209 $properties[] = htmlspecialchars( $this->messages[
'createaccountblock'] );
211 if ( $row->bt_user && !$row->bl_enable_autoblock ) {
212 $properties[] = htmlspecialchars( $this->messages[
'noautoblockblock'] );
215 if ( $row->bl_block_email ) {
216 $properties[] = htmlspecialchars( $this->messages[
'emailblock'] );
219 if ( !$row->bl_allow_usertalk ) {
220 $properties[] = htmlspecialchars( $this->messages[
'blocklist-nousertalk'] );
223 $formatted = Html::rawElement(
226 implode(
'', array_map(
static function ( $prop ) {
227 return Html::rawElement(
237 $formatted =
"Unable to format $name";
249 private function formatTarget( $row ) {
250 if ( $row->bt_auto ) {
251 return $this->
msg(
'autoblockid', $row->bl_id )->parse();
254 $target = $this->blockTargetFactory->newFromRowRedacted( $row );
258 $userName = $target->toString();
259 } elseif ( ( $row->hu_deleted ??
null )
260 && !$this->getAuthority()->isAllowed(
'hideuser' )
264 [
'class' =>
'mw-blocklist-hidden' ],
265 $this->messages[
'blocklist-hidden-placeholder']
267 } elseif ( $target instanceof BlockTargetWithUserPage ) {
268 $user = $target->getUserIdentity();
269 $userId = $user->getId();
270 $userName = $user->getName();
272 return $this->
msg(
'empty-username' )->escaped();
274 return Linker::userLink( $userId, $userName ) .
275 Linker::userToolLinks(
279 Linker::TOOL_LINKS_NOBLOCK
289 private function getBlockChangeLinks( $row ): array {
292 $target = $this->blockTargetFactory->newFromRowRedacted( $row )->toString();
294 $query = [
'id' => $row->bl_id ];
295 if ( $row->bt_auto ) {
296 $links[] = $linkRenderer->makeKnownLink(
297 $this->specialPageFactory->getTitleForAlias(
'Unblock' ),
298 $this->messages[
'remove-blocklink'],
300 [
'wpTarget' =>
"#{$row->bl_id}" ]
303 $specialBlock = $this->specialPageFactory->getTitleForAlias(
"Block/$target" );
304 $links[] = $linkRenderer->makeKnownLink(
306 $this->messages[
'remove-blocklink'],
308 $query + [
'remove' =>
'1' ]
310 $links[] = $linkRenderer->makeKnownLink(
312 $this->messages[
'change-blocklink'],
318 if ( $row->bt_auto ) {
319 $links[] = $linkRenderer->makeKnownLink(
320 $this->specialPageFactory->getTitleForAlias(
'Unblock' ),
321 $this->messages[
'unblocklink'],
323 [
'wpTarget' =>
"#{$row->bl_id}" ]
326 $links[] = $linkRenderer->makeKnownLink(
327 $this->specialPageFactory->getTitleForAlias(
"Unblock/$target" ),
328 $this->messages[
'unblocklink']
330 $links[] = $linkRenderer->makeKnownLink(
331 $this->specialPageFactory->getTitleForAlias(
"Block/$target" ),
332 $this->messages[
'change-blocklink']
346 private function getRestrictionListHTML( stdClass $row ) {
350 foreach ( $this->restrictions as $restriction ) {
351 if ( $restriction->getBlockId() !== (
int)$row->bl_id ) {
355 switch ( $restriction->getType() ) {
356 case PageRestriction::TYPE:
357 '@phan-var PageRestriction $restriction';
358 if ( $restriction->getTitle() ) {
359 $items[$restriction->getType()][] = Html::rawElement(
362 $linkRenderer->makeLink( $restriction->getTitle() )
366 case NamespaceRestriction::TYPE:
367 $text = $restriction->getValue() ===
NS_MAIN
368 ? $this->messages[
'blanknamespace']
370 $restriction->getValue()
373 $items[$restriction->getType()][] = Html::rawElement(
376 $linkRenderer->makeLink(
377 $this->specialPageFactory->getTitleForAlias(
'Allpages' ),
381 'namespace' => $restriction->getValue()
387 case ActionRestriction::TYPE:
388 $actionName = $this->blockActionInfo->getActionFromId( $restriction->getValue() );
397 $this->
msg(
'ipb-action-' .
398 $this->blockActionInfo->getActionFromId( $restriction->getValue() ) )->text()
410 foreach ( $items as $key => $value ) {
411 $sets[] = Html::rawElement(
419 $this->
msg(
'blocklist-editing-' . $key ) . Html::rawElement(
422 implode(
'', $value )
427 return Html::rawElement(
436 $db = $this->getDatabase();
437 $commentQuery = $this->commentStore->getJoin(
'bl_reason' );
441 'block_by_actor' =>
'actor',
442 'block_target' =>
'block_target',
443 ...$commentQuery[
'tables'],
455 'bl_by' =>
'block_by_actor.actor_user',
456 'bl_by_text' =>
'block_by_actor.actor_name',
460 'bl_enable_autoblock',
466 ] + $commentQuery[
'fields'],
467 'conds' => $this->conds,
469 'block_by_actor' => [
'JOIN',
'actor_id=bl_by_actor' ],
470 'block_target' => [
'JOIN',
'bt_id=bl_target' ],
471 ] + $commentQuery[
'joins']
474 # Filter out any expired blocks
475 $info[
'conds'][] = $db->expr(
'bl_expiry',
'>', $db->timestamp() );
477 # Filter out blocks with the deleted option if the user doesn't
478 # have permission to see hidden users
479 # TODO: consider removing this -- we could just redact them instead.
480 # The mere fact that an admin has deleted a user does not need to
481 # be private and could be included in block lists and logs for
482 # transparency purposes. Previously, filtering out deleted blocks
483 # was a convenient way to avoid showing the target name.
484 if ( $this->getAuthority()->isAllowed(
'hideuser' ) ) {
485 $info[
'fields'][
'hu_deleted'] = $this->hideUserUtils->getExpression(
487 'block_target.bt_user',
488 HideUserUtils::HIDDEN_USERS
491 $info[
'fields'][
'hu_deleted'] = 0;
492 $info[
'conds'][] = $this->hideUserUtils->getExpression(
494 'block_target.bt_user',
495 HideUserUtils::SHOWN_USERS
497 $info[
'conds'][
'bl_deleted'] = 0;
504 return parent::getTableClass() .
' mw-blocklist';
509 return [ [
'bl_timestamp',
'bl_id' ] ];
528 $lb = $this->linkBatchFactory->newLinkBatch();
529 $lb->setCaller( __METHOD__ );
532 foreach ( $result as $row ) {
533 $target = $row->bt_address ?? $row->bt_user_text;
534 if ( $target !==
null ) {
538 if ( isset( $row->bl_by_text ) ) {
539 $lb->add(
NS_USER, $row->bl_by_text );
543 if ( !$row->bl_sitewide ) {
544 $partialBlocks[] = (int)$row->bl_id;
548 if ( $partialBlocks ) {
551 $this->restrictions = $this->blockRestrictionStore->loadByBlockId( $partialBlocks );
553 foreach ( $this->restrictions as $restriction ) {
554 if ( $restriction->getType() === PageRestriction::TYPE ) {
555 '@phan-var PageRestriction $restriction';
556 $title = $restriction->getTitle();
558 $lb->addObj( $title );
568 $this->formattedComments = $this->rowCommentFormatter->formatRows( $result,
'bl_reason' );