73 private $formattedComments = [];
76 private $messages = [];
109 parent::__construct( $context, $linkRenderer );
111 $this->blockActionInfo = $blockActionInfo;
112 $this->blockRestrictionStore = $blockRestrictionStore;
113 $this->blockUtils = $blockUtils;
114 $this->hideUserUtils = $hideUserUtils;
115 $this->commentStore = $commentStore;
116 $this->linkBatchFactory = $linkBatchFactory;
117 $this->rowCommentFormatter = $rowCommentFormatter;
118 $this->specialPageFactory = $specialPageFactory;
124 static $headers =
null;
126 if ( $headers ===
null ) {
128 'bl_timestamp' =>
'blocklist-timestamp',
129 'target' =>
'blocklist-target',
130 'bl_expiry' =>
'blocklist-expiry',
131 'by' =>
'blocklist-by',
132 'params' =>
'blocklist-params',
133 'bl_reason' =>
'blocklist-reason',
135 foreach ( $headers as $key => $val ) {
136 $headers[$key] = $this->
msg( $val )->text();
149 if ( $this->messages === [] ) {
153 'createaccountblock',
156 'blocklist-nousertalk',
161 'blocklist-editing-sitewide',
162 'blocklist-hidden-param',
163 'blocklist-hidden-placeholder',
166 foreach ( $keys as $key ) {
167 $this->messages[$key] = $this->
msg( $key )->text();
182 $formatted = $linkRenderer->makeKnownLink(
183 $this->specialPageFactory->getTitleForAlias(
'BlockList' ),
184 $language->userTimeAndDate( $value, $this->getUser() ),
186 [
'wpTarget' =>
"#{$row->bl_id}" ],
191 $formatted = $this->formatTarget( $row );
195 $formatted = htmlspecialchars( $language->formatExpiry(
202 $links = $this->getBlockChangeLinks( $row );
203 $formatted .=
' ' . Html::rawElement(
205 [
'class' =>
'mw-blocklist-actions' ],
206 $this->
msg(
'parentheses' )->rawParams(
207 $language->pipeList( $links ) )->escaped()
210 if ( $value !==
'infinity' ) {
212 $formatted .=
'<br />' . $this->
msg(
213 'ipb-blocklist-duration-left',
214 $language->formatDurationBetweenTimestamps(
215 (
int)$timestamp->getTimestamp( TS_UNIX ),
224 $formatted = Linker::userLink( (
int)$value, $row->bl_by_text );
225 $formatted .= Linker::userToolLinks( (
int)$value, $row->bl_by_text );
235 if ( $row->bl_deleted ) {
236 $properties[] = htmlspecialchars( $this->messages[
'blocklist-hidden-param' ] );
238 if ( $row->bl_sitewide ) {
239 $properties[] = htmlspecialchars( $this->messages[
'blocklist-editing-sitewide'] );
242 if ( !$row->bl_sitewide && $this->restrictions ) {
243 $list = $this->getRestrictionListHTML( $row );
245 $properties[] = htmlspecialchars( $this->messages[
'blocklist-editing'] ) . $list;
249 if ( $row->bl_anon_only ) {
250 $properties[] = htmlspecialchars( $this->messages[
'anononlyblock'] );
252 if ( $row->bl_create_account ) {
253 $properties[] = htmlspecialchars( $this->messages[
'createaccountblock'] );
255 if ( $row->bt_user && !$row->bl_enable_autoblock ) {
256 $properties[] = htmlspecialchars( $this->messages[
'noautoblockblock'] );
259 if ( $row->bl_block_email ) {
260 $properties[] = htmlspecialchars( $this->messages[
'emailblock'] );
263 if ( !$row->bl_allow_usertalk ) {
264 $properties[] = htmlspecialchars( $this->messages[
'blocklist-nousertalk'] );
267 $formatted = Html::rawElement(
270 implode(
'', array_map(
static function ( $prop ) {
271 return Html::rawElement(
281 $formatted =
"Unable to format $name";
293 private function formatTarget( $row ) {
294 if ( $row->bt_auto ) {
295 return $this->
msg(
'autoblockid', $row->bl_id )->parse();
298 [ $target, $type ] = $this->blockUtils->parseBlockTargetRow( $row );
300 if ( $type === Block::TYPE_RANGE ) {
303 } elseif ( ( $row->hu_deleted ??
null )
304 && !$this->getAuthority()->isAllowed(
'hideuser' )
308 [
'class' =>
'mw-blocklist-hidden' ],
309 $this->messages[
'blocklist-hidden-placeholder']
311 } elseif ( $target instanceof UserIdentity ) {
312 $userId = $target->getId();
313 $userName = $target->getName();
314 } elseif ( is_string( $target ) ) {
315 return htmlspecialchars( $target );
317 return $this->
msg(
'empty-username' )->escaped();
319 return Linker::userLink( $userId, $userName ) .
320 Linker::userToolLinks(
324 Linker::TOOL_LINKS_NOBLOCK
334 private function getBlockChangeLinks( $row ): array {
337 if ( $row->bt_auto ) {
338 $target =
"#{$row->bl_id}";
340 $target = $row->bt_address ?? $row->bt_user_text;
343 $query = [
'id' => $row->bl_id ];
344 if ( $row->bt_auto ) {
345 $links[] = $linkRenderer->makeKnownLink(
346 $this->specialPageFactory->getTitleForAlias(
'Block' ),
347 $this->messages[
'remove-blocklink'],
350 'wpTarget' => $target,
355 $specialBlock = $this->specialPageFactory->getTitleForAlias(
"Block/$target" );
356 $links[] = $linkRenderer->makeKnownLink(
358 $this->messages[
'remove-blocklink'],
360 $query + [
'remove' =>
'1' ]
362 $links[] = $linkRenderer->makeKnownLink(
364 $this->messages[
'change-blocklink'],
370 if ( $row->bt_auto ) {
371 $links[] = $linkRenderer->makeKnownLink(
372 $this->specialPageFactory->getTitleForAlias(
'Unblock' ),
373 $this->messages[
'unblocklink'],
375 [
'wpTarget' =>
"#{$row->bl_id}" ]
378 $links[] = $linkRenderer->makeKnownLink(
379 $this->specialPageFactory->getTitleForAlias(
"Unblock/$target" ),
380 $this->messages[
'unblocklink']
382 $links[] = $linkRenderer->makeKnownLink(
383 $this->specialPageFactory->getTitleForAlias(
"Block/$target" ),
384 $this->messages[
'change-blocklink']
398 private function getRestrictionListHTML( stdClass $row ) {
400 $linkRenderer = $this->getLinkRenderer();
402 foreach ( $this->restrictions as $restriction ) {
403 if ( $restriction->getBlockId() !== (
int)$row->bl_id ) {
407 switch ( $restriction->getType() ) {
408 case PageRestriction::TYPE:
409 '@phan-var PageRestriction $restriction';
410 if ( $restriction->getTitle() ) {
411 $items[$restriction->getType()][] = Html::rawElement(
414 $linkRenderer->makeLink( $restriction->getTitle() )
418 case NamespaceRestriction::TYPE:
419 $text = $restriction->getValue() ===
NS_MAIN
420 ? $this->messages[
'blanknamespace']
421 : $this->getLanguage()->getFormattedNsText(
422 $restriction->getValue()
425 $items[$restriction->getType()][] = Html::rawElement(
428 $linkRenderer->makeLink(
429 $this->specialPageFactory->getTitleForAlias(
'Allpages' ),
433 'namespace' => $restriction->getValue()
439 case ActionRestriction::TYPE:
440 $actionName = $this->blockActionInfo->getActionFromId( $restriction->getValue() );
441 $enablePartialActionBlocks =
443 if ( $actionName && $enablePartialActionBlocks ) {
444 $items[$restriction->getType()][] = Html::rawElement(
451 $this->msg(
'ipb-action-' .
452 $this->blockActionInfo->getActionFromId( $restriction->getValue() ) )->escaped()
464 foreach ( $items as $key => $value ) {
465 $sets[] = Html::rawElement(
473 $this->msg(
'blocklist-editing-' . $key ) . Html::rawElement(
476 implode(
'', $value )
481 return Html::rawElement(
489 $db = $this->getDatabase();
490 $commentQuery = $this->commentStore->getJoin(
'bl_reason' );
492 'tables' => array_merge(
495 'block_by_actor' =>
'actor',
498 $commentQuery[
'tables']
510 'bl_by' =>
'block_by_actor.actor_user',
511 'bl_by_text' =>
'block_by_actor.actor_name',
515 'bl_enable_autoblock',
521 ] + $commentQuery[
'fields'],
522 'conds' => $this->conds,
524 'block_by_actor' => [
'JOIN',
'actor_id=bl_by_actor' ],
525 'block_target' => [
'JOIN',
'bt_id=bl_target' ],
526 ] + $commentQuery[
'joins']
529 # Filter out any expired blocks
530 $info[
'conds'][] = $db->expr(
'bl_expiry',
'>', $db->timestamp() );
532 # Filter out blocks with the deleted option if the user doesn't
533 # have permission to see hidden users
534 # TODO: consider removing this -- we could just redact them instead.
535 # The mere fact that an admin has deleted a user does not need to
536 # be private and could be included in block lists and logs for
537 # transparency purposes. Previously, filtering out deleted blocks
538 # was a convenient way to avoid showing the target name.
539 if ( !$this->getAuthority()->isAllowed(
'hideuser' ) ) {
540 $info[
'conds'][
'bl_deleted'] = 0;
543 # Determine if the user is hidden
544 # With multiblocks we can't just rely on bl_deleted in the row being formatted
545 $info[
'fields'][
'hu_deleted'] = $this->hideUserUtils->getExpression(
547 'block_target.bt_user',
548 HideUserUtils::HIDDEN_USERS );
553 return parent::getTableClass() .
' mw-blocklist';
557 return [ [
'bl_timestamp',
'bl_id' ] ];
574 $lb = $this->linkBatchFactory->newLinkBatch();
575 $lb->setCaller( __METHOD__ );
578 foreach ( $result as $row ) {
579 $target = $row->bt_address ?? $row->bt_user_text;
580 if ( $target !==
null ) {
585 if ( isset( $row->bl_by_text ) ) {
586 $lb->add(
NS_USER, $row->bl_by_text );
590 if ( !$row->bl_sitewide ) {
591 $partialBlocks[] = (int)$row->bl_id;
595 if ( $partialBlocks ) {
598 $this->restrictions = $this->blockRestrictionStore->loadByBlockId( $partialBlocks );
600 foreach ( $this->restrictions as $restriction ) {
601 if ( $restriction->getType() === PageRestriction::TYPE ) {
602 '@phan-var PageRestriction $restriction';
603 $title = $restriction->getTitle();
605 $lb->addObj( $title );
615 $this->formattedComments = $this->rowCommentFormatter->formatRows( $result,
'bl_reason' );