wiki.techinc.nl/tests/phpunit/includes/specialpage/ChangesListSpecialPageTest.php
Brad Jorsch 993baa3493 ActorMigration: Remove possibility of read-both
When this was originally written, the plan was to read both the old and
new fields during the transition period, while stopping writes to them
midway through. It turns out that the WHERE conditions to do read-both
correctly are generally not handled well by the database and working
around that would require a lot of complicated code (see what's being
removed from ApiQueryUserContribs here, for example).

We can simplify things greatly by instead having it write both fields
during the transition period, reading from the old for the first part
and the new for the second part, as is being done for MCR.

Bug: T204669
Change-Id: I4764c1c7883dc1003cb12729455c8107319f70b1
Depends-On: I845f6ae462f2539ebd35cbb5f2ca8b5714e2c1fb
Depends-On: I88b31b977543fdbdf69f8c1158e77e448df94e11
2018-10-11 12:12:00 +11:00

1077 lines
23 KiB
PHP
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
use Wikimedia\TestingAccessWrapper;
/**
* Test class for ChangesListSpecialPage class
*
* Copyright © 2011-, Antoine Musso, Stephane Bisson, Matthew Flaschen
*
* @author Antoine Musso
* @author Stephane Bisson
* @author Matthew Flaschen
* @group Database
*
* @covers ChangesListSpecialPage
*/
class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase {
protected function getPage() {
$mock = $this->getMockBuilder( ChangesListSpecialPage::class )
->setConstructorArgs(
[
'ChangesListSpecialPage',
''
]
)
->setMethods( [ 'getPageTitle' ] )
->getMockForAbstractClass();
$mock->method( 'getPageTitle' )->willReturn(
Title::makeTitle( NS_SPECIAL, 'ChangesListSpecialPage' )
);
$mock = TestingAccessWrapper::newFromObject(
$mock
);
return $mock;
}
private function buildQuery(
$requestOptions = null,
$user = null
) {
$context = new RequestContext;
$context->setRequest( new FauxRequest( $requestOptions ) );
if ( $user ) {
$context->setUser( $user );
}
$this->changesListSpecialPage->setContext( $context );
$this->changesListSpecialPage->filterGroups = [];
$formOptions = $this->changesListSpecialPage->setup( null );
#  Filter out rc_timestamp conditions which depends on the test runtime
# This condition is not needed as of march 2, 2011 -- hashar
# @todo FIXME: Find a way to generate the correct rc_timestamp
$tables = [];
$fields = [];
$queryConditions = [];
$query_options = [];
$join_conds = [];
call_user_func_array(
[ $this->changesListSpecialPage, 'buildQuery' ],
[
&$tables,
&$fields,
&$queryConditions,
&$query_options,
&$join_conds,
$formOptions
]
);
$queryConditions = array_filter(
$queryConditions,
'ChangesListSpecialPageTest::filterOutRcTimestampCondition'
);
return $queryConditions;
}
/** helper to test SpecialRecentchanges::buildQuery() */
private function assertConditions(
$expected,
$requestOptions = null,
$message = '',
$user = null
) {
$queryConditions = $this->buildQuery( $requestOptions, $user );
$this->assertEquals(
self::normalizeCondition( $expected ),
self::normalizeCondition( $queryConditions ),
$message
);
}
private static function normalizeCondition( $conds ) {
$dbr = wfGetDB( DB_REPLICA );
$normalized = array_map(
function ( $k, $v ) use ( $dbr ) {
if ( is_array( $v ) ) {
sort( $v );
}
// (Ab)use makeList() to format only this entry
return $dbr->makeList( [ $k => $v ], Database::LIST_AND );
},
array_keys( $conds ),
$conds
);
sort( $normalized );
return $normalized;
}
/** return false if condition begins with 'rc_timestamp ' */
private static function filterOutRcTimestampCondition( $var ) {
return ( is_array( $var ) || false === strpos( $var, 'rc_timestamp ' ) );
}
public function testRcNsFilter() {
$this->assertConditions(
[ # expected
"rc_namespace = '0'",
],
[
'namespace' => NS_MAIN,
],
"rc conditions with one namespace"
);
}
public function testRcNsFilterInversion() {
$this->assertConditions(
[ # expected
"rc_namespace != '0'",
],
[
'namespace' => NS_MAIN,
'invert' => 1,
],
"rc conditions with namespace inverted"
);
}
public function testRcNsFilterMultiple() {
$this->assertConditions(
[ # expected
"rc_namespace IN ('1','2','3')",
],
[
'namespace' => '1;2;3',
],
"rc conditions with multiple namespaces"
);
}
public function testRcNsFilterMultipleAssociated() {
$this->assertConditions(
[ # expected
"rc_namespace IN ('0','1','4','5','6','7')",
],
[
'namespace' => '1;4;7',
'associated' => 1,
],
"rc conditions with multiple namespaces and associated"
);
}
public function testRcNsFilterMultipleAssociatedInvert() {
$this->assertConditions(
[ # expected
"rc_namespace NOT IN ('2','3','8','9')",
],
[
'namespace' => '2;3;9',
'associated' => 1,
'invert' => 1
],
"rc conditions with multiple namespaces, associated and inverted"
);
}
public function testRcNsFilterMultipleInvert() {
$this->assertConditions(
[ # expected
"rc_namespace NOT IN ('1','2','3')",
],
[
'namespace' => '1;2;3',
'invert' => 1,
],
"rc conditions with multiple namespaces inverted"
);
}
public function testRcHidemyselfFilter() {
$this->setMwGlobals(
'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
);
$this->overrideMwServices();
$user = $this->getTestUser()->getUser();
$user->getActorId( wfGetDB( DB_MASTER ) );
$this->assertConditions(
[ # expected
"NOT((rc_user = '{$user->getId()}'))",
],
[
'hidemyself' => 1,
],
"rc conditions: hidemyself=1 (logged in)",
$user
);
$user = User::newFromName( '10.11.12.13', false );
$id = $user->getActorId( wfGetDB( DB_MASTER ) );
$this->assertConditions(
[ # expected
"NOT((rc_user_text = '10.11.12.13'))",
],
[
'hidemyself' => 1,
],
"rc conditions: hidemyself=1 (anon)",
$user
);
}
public function testRcHidebyothersFilter() {
$this->setMwGlobals(
'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
);
$this->overrideMwServices();
$user = $this->getTestUser()->getUser();
$user->getActorId( wfGetDB( DB_MASTER ) );
$this->assertConditions(
[ # expected
"(rc_user_text = '{$user->getName()}')",
],
[
'hidebyothers' => 1,
],
"rc conditions: hidebyothers=1 (logged in)",
$user
);
$user = User::newFromName( '10.11.12.13', false );
$id = $user->getActorId( wfGetDB( DB_MASTER ) );
$this->assertConditions(
[ # expected
"(rc_user_text = '10.11.12.13')",
],
[
'hidebyothers' => 1,
],
"rc conditions: hidebyothers=1 (anon)",
$user
);
}
public function testRcHidepageedits() {
$this->assertConditions(
[ # expected
"rc_type != '0'",
],
[
'hidepageedits' => 1,
],
"rc conditions: hidepageedits=1"
);
}
public function testRcHidenewpages() {
$this->assertConditions(
[ # expected
"rc_type != '1'",
],
[
'hidenewpages' => 1,
],
"rc conditions: hidenewpages=1"
);
}
public function testRcHidelog() {
$this->assertConditions(
[ # expected
"rc_type != '3'",
],
[
'hidelog' => 1,
],
"rc conditions: hidelog=1"
);
}
public function testRcHidehumans() {
$this->assertConditions(
[ # expected
'rc_bot' => 1,
],
[
'hidebots' => 0,
'hidehumans' => 1,
],
"rc conditions: hidebots=0 hidehumans=1"
);
}
public function testRcHidepatrolledDisabledFilter() {
$this->setMwGlobals( 'wgUseRCPatrol', false );
$user = $this->getTestUser()->getUser();
$this->assertConditions(
[ # expected
],
[
'hidepatrolled' => 1,
],
"rc conditions: hidepatrolled=1 (user not allowed)",
$user
);
}
public function testRcHideunpatrolledDisabledFilter() {
$this->setMwGlobals( 'wgUseRCPatrol', false );
$user = $this->getTestUser()->getUser();
$this->assertConditions(
[ # expected
],
[
'hideunpatrolled' => 1,
],
"rc conditions: hideunpatrolled=1 (user not allowed)",
$user
);
}
public function testRcHidepatrolledFilter() {
$user = $this->getTestSysop()->getUser();
$this->assertConditions(
[ # expected
'rc_patrolled' => 0,
],
[
'hidepatrolled' => 1,
],
"rc conditions: hidepatrolled=1",
$user
);
}
public function testRcHideunpatrolledFilter() {
$user = $this->getTestSysop()->getUser();
$this->assertConditions(
[ # expected
'rc_patrolled' => [ 1, 2 ],
],
[
'hideunpatrolled' => 1,
],
"rc conditions: hideunpatrolled=1",
$user
);
}
public function testRcReviewStatusFilter() {
$user = $this->getTestSysop()->getUser();
$this->assertConditions(
[ #expected
'rc_patrolled' => 1,
],
[
'reviewStatus' => 'manual'
],
"rc conditions: reviewStatus=manual",
$user
);
$this->assertConditions(
[ #expected
'rc_patrolled' => [ 0, 2 ],
],
[
'reviewStatus' => 'unpatrolled;auto'
],
"rc conditions: reviewStatus=unpatrolled;auto",
$user
);
}
public function testRcHideminorFilter() {
$this->assertConditions(
[ # expected
"rc_minor = 0",
],
[
'hideminor' => 1,
],
"rc conditions: hideminor=1"
);
}
public function testRcHidemajorFilter() {
$this->assertConditions(
[ # expected
"rc_minor = 1",
],
[
'hidemajor' => 1,
],
"rc conditions: hidemajor=1"
);
}
public function testHideCategorization() {
$this->assertConditions(
[
# expected
"rc_type != '6'"
],
[
'hidecategorization' => 1
],
"rc conditions: hidecategorization=1"
);
}
public function testFilterUserExpLevelAll() {
$this->assertConditions(
[
# expected
],
[
'userExpLevel' => 'registered;unregistered;newcomer;learner;experienced',
],
"rc conditions: userExpLevel=registered;unregistered;newcomer;learner;experienced"
);
}
public function testFilterUserExpLevelRegisteredUnregistered() {
$this->assertConditions(
[
# expected
],
[
'userExpLevel' => 'registered;unregistered',
],
"rc conditions: userExpLevel=registered;unregistered"
);
}
public function testFilterUserExpLevelRegisteredUnregisteredLearner() {
$this->assertConditions(
[
# expected
],
[
'userExpLevel' => 'registered;unregistered;learner',
],
"rc conditions: userExpLevel=registered;unregistered;learner"
);
}
public function testFilterUserExpLevelAllExperienceLevels() {
$this->setMwGlobals(
'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
);
$this->overrideMwServices();
$this->assertConditions(
[
# expected
'rc_user != 0',
],
[
'userExpLevel' => 'newcomer;learner;experienced',
],
"rc conditions: userExpLevel=newcomer;learner;experienced"
);
}
public function testFilterUserExpLevelRegistrered() {
$this->setMwGlobals(
'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
);
$this->overrideMwServices();
$this->assertConditions(
[
# expected
'rc_user != 0',
],
[
'userExpLevel' => 'registered',
],
"rc conditions: userExpLevel=registered"
);
}
public function testFilterUserExpLevelUnregistrered() {
$this->setMwGlobals(
'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
);
$this->overrideMwServices();
$this->assertConditions(
[
# expected
'rc_user = 0',
],
[
'userExpLevel' => 'unregistered',
],
"rc conditions: userExpLevel=unregistered"
);
}
public function testFilterUserExpLevelRegistreredOrLearner() {
$this->setMwGlobals(
'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
);
$this->overrideMwServices();
$this->assertConditions(
[
# expected
'rc_user != 0',
],
[
'userExpLevel' => 'registered;learner',
],
"rc conditions: userExpLevel=registered;learner"
);
}
public function testFilterUserExpLevelUnregistreredOrExperienced() {
$this->setMwGlobals(
'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
);
$this->overrideMwServices();
$conds = $this->buildQuery( [ 'userExpLevel' => 'unregistered;experienced' ] );
$this->assertRegExp(
'/\(rc_user = 0\) OR '
. '\(\(user_editcount >= 500\) AND \(user_registration <= \'[^\']+\'\)\)/',
reset( $conds ),
"rc conditions: userExpLevel=unregistered;experienced"
);
}
public function testFilterUserExpLevel() {
$now = time();
$this->setMwGlobals( [
'wgLearnerEdits' => 10,
'wgLearnerMemberSince' => 4,
'wgExperiencedUserEdits' => 500,
'wgExperiencedUserMemberSince' => 30,
] );
$this->createUsers( [
'Newcomer1' => [ 'edits' => 2, 'days' => 2 ],
'Newcomer2' => [ 'edits' => 12, 'days' => 3 ],
'Newcomer3' => [ 'edits' => 8, 'days' => 5 ],
'Learner1' => [ 'edits' => 15, 'days' => 10 ],
'Learner2' => [ 'edits' => 450, 'days' => 20 ],
'Learner3' => [ 'edits' => 460, 'days' => 33 ],
'Learner4' => [ 'edits' => 525, 'days' => 28 ],
'Experienced1' => [ 'edits' => 538, 'days' => 33 ],
], $now );
// newcomers only
$this->assertArrayEquals(
[ 'Newcomer1', 'Newcomer2', 'Newcomer3' ],
$this->fetchUsers( [ 'newcomer' ], $now )
);
// newcomers and learner
$this->assertArrayEquals(
[
'Newcomer1', 'Newcomer2', 'Newcomer3',
'Learner1', 'Learner2', 'Learner3', 'Learner4',
],
$this->fetchUsers( [ 'newcomer', 'learner' ], $now )
);
// newcomers and more learner
$this->assertArrayEquals(
[
'Newcomer1', 'Newcomer2', 'Newcomer3',
'Experienced1',
],
$this->fetchUsers( [ 'newcomer', 'experienced' ], $now )
);
// learner only
$this->assertArrayEquals(
[ 'Learner1', 'Learner2', 'Learner3', 'Learner4' ],
$this->fetchUsers( [ 'learner' ], $now )
);
// more experienced only
$this->assertArrayEquals(
[ 'Experienced1' ],
$this->fetchUsers( [ 'experienced' ], $now )
);
// learner and more experienced
$this->assertArrayEquals(
[
'Learner1', 'Learner2', 'Learner3', 'Learner4',
'Experienced1',
],
$this->fetchUsers( [ 'learner', 'experienced' ], $now ),
'Learner and more experienced'
);
}
private function createUsers( $specs, $now ) {
$dbw = wfGetDB( DB_MASTER );
foreach ( $specs as $name => $spec ) {
User::createNew(
$name,
[
'editcount' => $spec['edits'],
'registration' => $dbw->timestamp( $this->daysAgo( $spec['days'], $now ) ),
'email' => 'ut',
]
);
}
}
private function fetchUsers( $filters, $now ) {
$tables = [];
$conds = [];
$fields = [];
$query_options = [];
$join_conds = [];
sort( $filters );
call_user_func_array(
[ $this->changesListSpecialPage, 'filterOnUserExperienceLevel' ],
[
get_class( $this->changesListSpecialPage ),
$this->changesListSpecialPage->getContext(),
$this->changesListSpecialPage->getDB(),
&$tables,
&$fields,
&$conds,
&$query_options,
&$join_conds,
$filters,
$now
]
);
// @todo: This is not at all safe or sane. It just blindly assumes
// nothing in $conds depends on any other tables.
$result = wfGetDB( DB_MASTER )->select(
'user',
'user_name',
array_filter( $conds ) + [ 'user_email' => 'ut' ]
);
$usernames = [];
foreach ( $result as $row ) {
$usernames[] = $row->user_name;
}
return $usernames;
}
private function daysAgo( $days, $now ) {
$secondsPerDay = 86400;
return $now - $days * $secondsPerDay;
}
public function testGetStructuredFilterJsData() {
$this->changesListSpecialPage->filterGroups = [];
$definition = [
[
'name' => 'gub-group',
'title' => 'gub-group-title',
'class' => ChangesListBooleanFilterGroup::class,
'filters' => [
[
'name' => 'hidefoo',
'label' => 'foo-label',
'description' => 'foo-description',
'default' => true,
'showHide' => 'showhidefoo',
'priority' => 2,
],
[
'name' => 'hidebar',
'label' => 'bar-label',
'description' => 'bar-description',
'default' => false,
'priority' => 4,
]
],
],
[
'name' => 'des-group',
'title' => 'des-group-title',
'class' => ChangesListStringOptionsFilterGroup::class,
'isFullCoverage' => true,
'filters' => [
[
'name' => 'grault',
'label' => 'grault-label',
'description' => 'grault-description',
],
[
'name' => 'garply',
'label' => 'garply-label',
'description' => 'garply-description',
],
],
'queryCallable' => function () {
},
'default' => ChangesListStringOptionsFilterGroup::NONE,
],
[
'name' => 'unstructured',
'class' => ChangesListBooleanFilterGroup::class,
'filters' => [
[
'name' => 'hidethud',
'showHide' => 'showhidethud',
'default' => true,
],
[
'name' => 'hidemos',
'showHide' => 'showhidemos',
'default' => false,
],
],
],
];
$this->changesListSpecialPage->registerFiltersFromDefinitions( $definition );
$this->assertArrayEquals(
[
// Filters that only display in the unstructured UI are
// are not included, and neither are groups that would
// be empty due to the above.
'groups' => [
[
'name' => 'gub-group',
'title' => 'gub-group-title',
'type' => ChangesListBooleanFilterGroup::TYPE,
'priority' => -1,
'filters' => [
[
'name' => 'hidebar',
'label' => 'bar-label',
'description' => 'bar-description',
'default' => false,
'priority' => 4,
'cssClass' => null,
'conflicts' => [],
'subset' => [],
'defaultHighlightColor' => null
],
[
'name' => 'hidefoo',
'label' => 'foo-label',
'description' => 'foo-description',
'default' => true,
'priority' => 2,
'cssClass' => null,
'conflicts' => [],
'subset' => [],
'defaultHighlightColor' => null
],
],
'fullCoverage' => true,
'conflicts' => [],
],
[
'name' => 'des-group',
'title' => 'des-group-title',
'type' => ChangesListStringOptionsFilterGroup::TYPE,
'priority' => -2,
'fullCoverage' => true,
'filters' => [
[
'name' => 'grault',
'label' => 'grault-label',
'description' => 'grault-description',
'cssClass' => null,
'priority' => -2,
'conflicts' => [],
'subset' => [],
'defaultHighlightColor' => null
],
[
'name' => 'garply',
'label' => 'garply-label',
'description' => 'garply-description',
'cssClass' => null,
'priority' => -3,
'conflicts' => [],
'subset' => [],
'defaultHighlightColor' => null
],
],
'conflicts' => [],
'separator' => ';',
'default' => ChangesListStringOptionsFilterGroup::NONE,
],
],
'messageKeys' => [
'gub-group-title',
'bar-label',
'bar-description',
'foo-label',
'foo-description',
'des-group-title',
'grault-label',
'grault-description',
'garply-label',
'garply-description',
],
],
$this->changesListSpecialPage->getStructuredFilterJsData(),
/** ordered= */ false,
/** named= */ true
);
}
public function provideParseParameters() {
return [
[ 'hidebots', [ 'hidebots' => true ] ],
[ 'bots', [ 'hidebots' => false ] ],
[ 'hideminor', [ 'hideminor' => true ] ],
[ 'minor', [ 'hideminor' => false ] ],
[ 'hidemajor', [ 'hidemajor' => true ] ],
[ 'hideliu', [ 'hideliu' => true ] ],
[ 'hidepatrolled', [ 'hidepatrolled' => true ] ],
[ 'hideunpatrolled', [ 'hideunpatrolled' => true ] ],
[ 'hideanons', [ 'hideanons' => true ] ],
[ 'hidemyself', [ 'hidemyself' => true ] ],
[ 'hidebyothers', [ 'hidebyothers' => true ] ],
[ 'hidehumans', [ 'hidehumans' => true ] ],
[ 'hidepageedits', [ 'hidepageedits' => true ] ],
[ 'pagedits', [ 'hidepageedits' => false ] ],
[ 'hidenewpages', [ 'hidenewpages' => true ] ],
[ 'hidecategorization', [ 'hidecategorization' => true ] ],
[ 'hidelog', [ 'hidelog' => true ] ],
[
'userExpLevel=learner;experienced',
[
'userExpLevel' => 'learner;experienced'
],
],
// A few random combos
[
'bots,hideliu,hidemyself',
[
'hidebots' => false,
'hideliu' => true,
'hidemyself' => true,
],
],
[
'minor,hideanons,categorization',
[
'hideminor' => false,
'hideanons' => true,
'hidecategorization' => false,
]
],
[
'hidehumans,bots,hidecategorization',
[
'hidehumans' => true,
'hidebots' => false,
'hidecategorization' => true,
],
],
[
'hidemyself,userExpLevel=newcomer;learner,hideminor',
[
'hidemyself' => true,
'hideminor' => true,
'userExpLevel' => 'newcomer;learner',
],
],
];
}
public function provideGetFilterConflicts() {
return [
[
"parameters" => [],
"expectedConflicts" => false,
],
[
"parameters" => [
"hideliu" => true,
"userExpLevel" => "newcomer",
],
"expectedConflicts" => false,
],
[
"parameters" => [
"hideanons" => true,
"userExpLevel" => "learner",
],
"expectedConflicts" => false,
],
[
"parameters" => [
"hidemajor" => true,
"hidenewpages" => true,
"hidepageedits" => true,
"hidecategorization" => false,
"hidelog" => true,
"hideWikidata" => true,
],
"expectedConflicts" => true,
],
[
"parameters" => [
"hidemajor" => true,
"hidenewpages" => false,
"hidepageedits" => true,
"hidecategorization" => false,
"hidelog" => false,
"hideWikidata" => true,
],
"expectedConflicts" => true,
],
[
"parameters" => [
"hidemajor" => true,
"hidenewpages" => false,
"hidepageedits" => false,
"hidecategorization" => true,
"hidelog" => true,
"hideWikidata" => true,
],
"expectedConflicts" => false,
],
[
"parameters" => [
"hideminor" => true,
"hidenewpages" => true,
"hidepageedits" => true,
"hidecategorization" => false,
"hidelog" => true,
"hideWikidata" => true,
],
"expectedConflicts" => false,
],
];
}
/**
* @dataProvider provideGetFilterConflicts
*/
public function testGetFilterConflicts( $parameters, $expectedConflicts ) {
$context = new RequestContext;
$context->setRequest( new FauxRequest( $parameters ) );
$this->changesListSpecialPage->setContext( $context );
$this->assertEquals(
$expectedConflicts,
$this->changesListSpecialPage->areFiltersInConflict()
);
}
public function validateOptionsProvider() {
return [
[
[ 'hideanons' => 1, 'hideliu' => 1, 'hidebots' => 1 ],
true,
[ 'userExpLevel' => 'unregistered', 'hidebots' => 1, ],
true,
],
[
[ 'hideanons' => 1, 'hideliu' => 1, 'hidebots' => 0 ],
true,
[ 'hidebots' => 0, 'hidehumans' => 1 ],
true,
],
[
[ 'hideanons' => 1 ],
true,
[ 'userExpLevel' => 'registered' ],
true,
],
[
[ 'hideliu' => 1 ],
true,
[ 'userExpLevel' => 'unregistered' ],
true,
],
[
[ 'hideanons' => 1, 'hidebots' => 1 ],
true,
[ 'userExpLevel' => 'registered', 'hidebots' => 1 ],
true,
],
[
[ 'hideliu' => 1, 'hidebots' => 0 ],
true,
[ 'userExpLevel' => 'unregistered', 'hidebots' => 0 ],
true,
],
[
[ 'hidemyself' => 1, 'hidebyothers' => 1 ],
true,
[],
true,
],
[
[ 'hidebots' => 1, 'hidehumans' => 1 ],
true,
[],
true,
],
[
[ 'hidepatrolled' => 1, 'hideunpatrolled' => 1 ],
true,
[],
true,
],
[
[ 'hideminor' => 1, 'hidemajor' => 1 ],
true,
[],
true,
],
[
// changeType
[ 'hidepageedits' => 1, 'hidenewpages' => 1, 'hidecategorization' => 1, 'hidelog' => 1, ],
true,
[],
true,
],
];
}
}