aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--includes/specialpage/SpecialPageFactory.php1
-rw-r--r--includes/specials/SpecialBlockList.php54
-rw-r--r--languages/i18n/en.json2
-rw-r--r--languages/i18n/qqq.json2
-rw-r--r--tests/phpunit/integration/includes/specials/SpecialBlockListTest.php172
5 files changed, 221 insertions, 10 deletions
diff --git a/includes/specialpage/SpecialPageFactory.php b/includes/specialpage/SpecialPageFactory.php
index e91d174049e9..aed9cb398a3a 100644
--- a/includes/specialpage/SpecialPageFactory.php
+++ b/includes/specialpage/SpecialPageFactory.php
@@ -552,6 +552,7 @@ class SpecialPageFactory {
'HideUserUtils',
'BlockActionInfo',
'RowCommentFormatter',
+ 'TempUserConfig',
],
],
'AutoblockList' => [
diff --git a/includes/specials/SpecialBlockList.php b/includes/specials/SpecialBlockList.php
index 1af7b9c3f32c..1f6f3e6189e7 100644
--- a/includes/specials/SpecialBlockList.php
+++ b/includes/specials/SpecialBlockList.php
@@ -33,8 +33,10 @@ use MediaWiki\Html\Html;
use MediaWiki\HTMLForm\HTMLForm;
use MediaWiki\Pager\BlockListPager;
use MediaWiki\SpecialPage\SpecialPage;
+use MediaWiki\User\TempUser\TempUserConfig;
use Wikimedia\IPUtils;
use Wikimedia\Rdbms\IConnectionProvider;
+use Wikimedia\Rdbms\IExpression;
use Wikimedia\Rdbms\IReadableDatabase;
/**
@@ -63,6 +65,7 @@ class SpecialBlockList extends SpecialPage {
private HideUserUtils $hideUserUtils;
private BlockActionInfo $blockActionInfo;
private RowCommentFormatter $rowCommentFormatter;
+ private TempUserConfig $tempUserConfig;
public function __construct(
LinkBatchFactory $linkBatchFactory,
@@ -73,7 +76,8 @@ class SpecialBlockList extends SpecialPage {
BlockUtils $blockUtils,
HideUserUtils $hideUserUtils,
BlockActionInfo $blockActionInfo,
- RowCommentFormatter $rowCommentFormatter
+ RowCommentFormatter $rowCommentFormatter,
+ TempUserConfig $tempUserConfig
) {
parent::__construct( 'BlockList' );
@@ -86,6 +90,7 @@ class SpecialBlockList extends SpecialPage {
$this->hideUserUtils = $hideUserUtils;
$this->blockActionInfo = $blockActionInfo;
$this->rowCommentFormatter = $rowCommentFormatter;
+ $this->tempUserConfig = $tempUserConfig;
}
/**
@@ -119,6 +124,22 @@ class SpecialBlockList extends SpecialPage {
// Setup BlockListPager here to get the actual default Limit
$pager = $this->getBlockListPager();
+ $blockFilterOptions = [
+ 'blocklist-tempblocks' => 'tempblocks',
+ 'blocklist-indefblocks' => 'indefblocks',
+ 'blocklist-autoblocks' => 'autoblocks',
+ 'blocklist-addressblocks' => 'addressblocks',
+ 'blocklist-rangeblocks' => 'rangeblocks',
+ ];
+
+ if ( $this->tempUserConfig->isKnown() ) {
+ // Clarify that "userblocks" excludes named users only if temporary accounts are known (T380266)
+ $blockFilterOptions['blocklist-nameduserblocks'] = 'userblocks';
+ $blockFilterOptions['blocklist-tempuserblocks'] = 'tempuserblocks';
+ } else {
+ $blockFilterOptions['blocklist-userblocks'] = 'userblocks';
+ }
+
// Just show the block list
$fields = [
'Target' => [
@@ -130,14 +151,7 @@ class SpecialBlockList extends SpecialPage {
],
'Options' => [
'type' => 'multiselect',
- 'options-messages' => [
- 'blocklist-tempblocks' => 'tempblocks',
- 'blocklist-indefblocks' => 'indefblocks',
- 'blocklist-autoblocks' => 'autoblocks',
- 'blocklist-userblocks' => 'userblocks',
- 'blocklist-addressblocks' => 'addressblocks',
- 'blocklist-rangeblocks' => 'rangeblocks',
- ],
+ 'options-messages' => $blockFilterOptions,
'flatlist' => true,
],
];
@@ -214,7 +228,17 @@ class SpecialBlockList extends SpecialPage {
// Apply filters
if ( in_array( 'userblocks', $this->options ) ) {
- $conds['bt_user'] = null;
+ $namedUserConds = $db->expr( 'bt_user', '=', null );
+
+ // If temporary accounts are a known concept on this wiki,
+ // have the "Hide account blocks" filter exclude only named users (T380266).
+ if ( $this->tempUserConfig->isKnown() ) {
+ $namedUserConds = $namedUserConds->orExpr(
+ $this->tempUserConfig->getMatchCondition( $db, 'bt_user_text', IExpression::LIKE )
+ );
+ }
+
+ $conds[] = $namedUserConds;
}
if ( in_array( 'autoblocks', $this->options ) ) {
$conds['bl_parent_block_id'] = null;
@@ -230,6 +254,16 @@ class SpecialBlockList extends SpecialPage {
$conds['bt_range_start'] = null;
}
+ if (
+ in_array( 'tempuserblocks', $this->options ) &&
+ $this->tempUserConfig->isKnown()
+ ) {
+ $conds[] = $db->expr( 'bt_user', '=', null )
+ ->orExpr(
+ $this->tempUserConfig->getMatchCondition( $db, 'bt_user_text', IExpression::NOT_LIKE )
+ );
+ }
+
$hideTemp = in_array( 'tempblocks', $this->options );
$hideIndef = in_array( 'indefblocks', $this->options );
if ( $hideTemp && $hideIndef ) {
diff --git a/languages/i18n/en.json b/languages/i18n/en.json
index 4806fa0088bb..f6ff4344c0ec 100644
--- a/languages/i18n/en.json
+++ b/languages/i18n/en.json
@@ -2735,9 +2735,11 @@
"ipblocklist-legend": "Find a blocked user",
"blocklist-autoblocks": "Hide autoblocks",
"blocklist-userblocks": "Hide account blocks",
+ "blocklist-nameduserblocks": "Hide named user blocks",
"blocklist-tempblocks": "Hide temporary blocks",
"blocklist-indefblocks": "Hide indefinite blocks",
"blocklist-addressblocks": "Hide single IP blocks",
+ "blocklist-tempuserblocks": "Hide temporary account blocks",
"blocklist-type": "Type:",
"blocklist-type-header": "Type",
"blocklist-type-opt-all": "All",
diff --git a/languages/i18n/qqq.json b/languages/i18n/qqq.json
index 01396ed37d1c..dd2464e90cd3 100644
--- a/languages/i18n/qqq.json
+++ b/languages/i18n/qqq.json
@@ -3002,6 +3002,8 @@
"blocklist-tempblocks": "Used as the label for the multi-select checkbox in the form on [[Special:BlockList]].\n{{Related|Blocklist-blocks}}",
"blocklist-indefblocks": "Used as the label for the multi-select checkbox in the form on [[Special:BlockList]].\n{{Related|Blocklist-blocks}}",
"blocklist-addressblocks": "Used as the label for the multi-select checkbox in the form on [[Special:BlockList]].\n{{Related|Blocklist-blocks}}",
+ "blocklist-nameduserblocks": "Used as the label for the multi-select checkbox in the form on [[Special:BlockList]].\n{{Related|Blocklist-blocks}}",
+ "blocklist-tempuserblocks": "Used as the label for the multi-select checkbox in the form on [[Special:BlockList]].\n{{Related|Blocklist-blocks}}",
"blocklist-type": "Used as label for dropdown box in [[Special:BlockList]].\n\n{{Identical|Type}}",
"blocklist-type-header": "Used as the header for the block log type column in the table on the page [[Special:Block]].",
"blocklist-type-opt-all": "Used as option for dropdown box in [[Special:BlockList]]. This is the default option and indicates that \"all\" blocks will be listed\n{{Identical|All}}",
diff --git a/tests/phpunit/integration/includes/specials/SpecialBlockListTest.php b/tests/phpunit/integration/includes/specials/SpecialBlockListTest.php
new file mode 100644
index 000000000000..f597bbd01681
--- /dev/null
+++ b/tests/phpunit/integration/includes/specials/SpecialBlockListTest.php
@@ -0,0 +1,172 @@
+<?php
+namespace MediaWiki\Tests\Specials;
+
+use HtmlFormatter\HtmlFormatter;
+use MediaWiki\Request\FauxRequest;
+use MediaWiki\Tests\User\TempUser\TempUserTestTrait;
+use MediaWiki\User\UserIdentity;
+use SpecialPageTestBase;
+use Wikimedia\Parsoid\Utils\DOMCompat;
+
+/**
+ * @covers \MediaWiki\Specials\SpecialBlockList
+ * @group Database
+ */
+class SpecialBlockListTest extends SpecialPageTestBase {
+ use TempUserTestTrait;
+
+ private const TEST_BLOCKED_IP = '127.0.0.1';
+
+ private static string $blockedTempUserName;
+ private static string $blockedUserName;
+
+ protected function newSpecialPage() {
+ return $this->getServiceContainer()->getSpecialPageFactory()->getPage( 'BlockList' );
+ }
+
+ private function makeTestBlock( UserIdentity $target ): void {
+ $status = $this->getServiceContainer()
+ ->getBlockUserFactory()
+ ->newBlockUser(
+ $target,
+ $this->getTestSysop()->getAuthority(),
+ 'infinity'
+ )
+ ->placeBlock();
+
+ $this->assertStatusGood( $status, "Failed to place block for {$target->getName()}" );
+ }
+
+ public function addDBDataOnce() {
+ $this->enableAutoCreateTempUser();
+
+ $req = new FauxRequest();
+ $tempUser = $this->getServiceContainer()
+ ->getTempUserCreator()
+ ->create( null, $req )
+ ->getUser();
+
+ $namedUser = $this->getTestUser()->getUser();
+
+ self::$blockedTempUserName = $tempUser->getName();
+ self::$blockedUserName = $namedUser->getName();
+
+ $this->makeTestBlock( $tempUser );
+
+ $this->makeTestBlock( $this->getTestUser()->getUser() );
+
+ $this->makeTestBlock(
+ $this->getServiceContainer()->getUserFactory()->newAnonymous( self::TEST_BLOCKED_IP )
+ );
+ }
+
+ /**
+ * @dataProvider provideTargetTypes
+ */
+ public function testShouldAllowFilteringBlocksByTargetType(
+ ?string $targetType,
+ callable $expectedBlockTargetNamesProvider,
+ bool $tempAccountsKnown = true
+ ): void {
+ $this->disableAutoCreateTempUser( [
+ 'known' => $tempAccountsKnown,
+ ] );
+
+ $queryParams = $targetType ? [ 'wpOptions' => [ $targetType ] ] : [];
+ $req = new FauxRequest( $queryParams );
+
+ [ $html ] = $this->executeSpecialPage( '', $req );
+
+ $doc = ( new HtmlFormatter( HtmlFormatter::wrapHTML( $html ) ) )->getDoc();
+ $targetUserNames = [];
+ foreach ( DOMCompat::querySelectorAll( $doc, '.TablePager_col_target > .mw-userlink' ) as $targetUser ) {
+ $targetUserNames[] = $targetUser->textContent;
+ }
+
+ $this->assertSame(
+ $expectedBlockTargetNamesProvider(),
+ $targetUserNames
+ );
+ }
+
+ public static function provideTargetTypes(): iterable {
+ yield 'no target type, temp accounts known' => [
+ null,
+ static fn () => [ self::TEST_BLOCKED_IP, self::$blockedUserName, self::$blockedTempUserName ]
+ ];
+ yield 'named user blocks excluded, temp accounts known' => [
+ 'userblocks',
+ static fn () => [ self::TEST_BLOCKED_IP, self::$blockedTempUserName ]
+ ];
+ yield 'IP blocks excluded, temp accounts known' => [
+ 'addressblocks',
+ static fn () => [ self::$blockedUserName, self::$blockedTempUserName ]
+ ];
+ yield 'temp user blocks excluded, temp accounts known' => [
+ 'tempuserblocks',
+ static fn () => [ self::TEST_BLOCKED_IP, self::$blockedUserName ]
+ ];
+
+ yield 'no target type, temp accounts not known' => [
+ null,
+ static fn () => [ self::TEST_BLOCKED_IP, self::$blockedUserName, self::$blockedTempUserName ],
+ false
+ ];
+ yield 'user blocks excluded, temp accounts not known' => [
+ 'userblocks',
+ static fn () => [ self::TEST_BLOCKED_IP ],
+ false
+ ];
+ yield 'IP blocks excluded, temp accounts not known' => [
+ 'addressblocks',
+ static fn () => [ self::$blockedUserName, self::$blockedTempUserName ],
+ false
+ ];
+ }
+
+ public function testShouldAdaptFilterOptionsWhenTemporaryAccountsAreKnown(): void {
+ $this->disableAutoCreateTempUser( [
+ 'known' => true,
+ ] );
+
+ [ $html ] = $this->executeSpecialPage();
+
+ $doc = ( new HtmlFormatter( HtmlFormatter::wrapHTML( $html ) ) )->getDoc();
+
+ $accountBlocksFilter = DOMCompat::querySelector( $doc, 'input[name="wpOptions[]"][value="userblocks"]' );
+ $accountBlocksLabel = DOMCompat::querySelector(
+ $doc, "label[for=\"{$accountBlocksFilter->getAttribute( 'id' )}\"]"
+ );
+
+ $this->assertSame(
+ '(blocklist-nameduserblocks)',
+ $accountBlocksLabel->textContent
+ );
+ $this->assertNotNull(
+ DOMCompat::querySelector( $doc, 'input[name="wpOptions[]"][value="tempuserblocks"]' ),
+ 'Temporary accounts filter checkbox should be present'
+ );
+ }
+
+ public function testShouldNotShowTemporaryAccountsFilterCheckboxWhenTemporaryAccountsAreNotKnown(): void {
+ $this->disableAutoCreateTempUser();
+
+ [ $html ] = $this->executeSpecialPage();
+
+ $doc = ( new HtmlFormatter( HtmlFormatter::wrapHTML( $html ) ) )->getDoc();
+
+ $accountBlocksFilter = DOMCompat::querySelector( $doc, 'input[name="wpOptions[]"][value="userblocks"]' );
+ $accountBlocksLabel = DOMCompat::querySelector(
+ $doc, "label[for=\"{$accountBlocksFilter->getAttribute( 'id' )}\"]"
+ );
+
+ $this->assertSame(
+ '(blocklist-userblocks)',
+ $accountBlocksLabel->textContent
+ );
+ $this->assertNull(
+ DOMCompat::querySelector( $doc, 'input[name="wpOptions[]"][value="tempuserblocks"]' ),
+ 'Temporary accounts filter checkbox should not be present'
+ );
+ }
+}