wiki.techinc.nl/tests/phpunit/includes/specialpage/ChangesListSpecialPageTest.php
James D. Forrester b5ce7632c7 hooks: Drop Special{Watchlist|RecentChanges}Filters, deprecated in 1.23
Neither SpecialWatchlistFilters nor SpecialRecentChangesFilters are used in any
code known to Wikimedia code search. The related code to use these hooks was
only ever used within the MediaWiki repo.

Change-Id: Ib631d49d7b5835c665171dbad3e8a646b80827ef
2018-09-12 12:43:01 -07:00

1061 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 {
public function setUp() {
parent::setUp();
$this->setMwGlobals( [
'wgStructuredChangeFiltersShowPreference' => true,
] );
}
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', MIGRATION_WRITE_BOTH );
$this->overrideMwServices();
$user = $this->getTestUser()->getUser();
$user->getActorId( wfGetDB( DB_MASTER ) );
$this->assertConditions(
[ # expected
"NOT((rc_actor = '{$user->getActorId()}') OR "
. "(rc_actor = '0' AND 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_actor = '$id') OR (rc_actor = '0' AND rc_user_text = '10.11.12.13'))",
],
[
'hidemyself' => 1,
],
"rc conditions: hidemyself=1 (anon)",
$user
);
}
public function testRcHidebyothersFilter() {
$this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
$this->overrideMwServices();
$user = $this->getTestUser()->getUser();
$user->getActorId( wfGetDB( DB_MASTER ) );
$this->assertConditions(
[ # expected
"(rc_actor = '{$user->getActorId()}') OR "
. "(rc_actor = '0' AND 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_actor = '$id') OR (rc_actor = '0' AND 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', MIGRATION_WRITE_BOTH );
$this->overrideMwServices();
$this->assertConditions(
[
# expected
'COALESCE( actor_rc_user.actor_user, rc_user ) != 0',
],
[
'userExpLevel' => 'newcomer;learner;experienced',
],
"rc conditions: userExpLevel=newcomer;learner;experienced"
);
}
public function testFilterUserExpLevelRegistrered() {
$this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
$this->overrideMwServices();
$this->assertConditions(
[
# expected
'COALESCE( actor_rc_user.actor_user, rc_user ) != 0',
],
[
'userExpLevel' => 'registered',
],
"rc conditions: userExpLevel=registered"
);
}
public function testFilterUserExpLevelUnregistrered() {
$this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
$this->overrideMwServices();
$this->assertConditions(
[
# expected
'COALESCE( actor_rc_user.actor_user, rc_user ) = 0',
],
[
'userExpLevel' => 'unregistered',
],
"rc conditions: userExpLevel=unregistered"
);
}
public function testFilterUserExpLevelRegistreredOrLearner() {
$this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
$this->overrideMwServices();
$this->assertConditions(
[
# expected
'COALESCE( actor_rc_user.actor_user, rc_user ) != 0',
],
[
'userExpLevel' => 'registered;learner',
],
"rc conditions: userExpLevel=registered;learner"
);
}
public function testFilterUserExpLevelUnregistreredOrExperienced() {
$this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
$this->overrideMwServices();
$conds = $this->buildQuery( [ 'userExpLevel' => 'unregistered;experienced' ] );
$this->assertRegExp(
'/\(COALESCE\( actor_rc_user.actor_user, 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, ],
],
[
[ 'hideanons' => 1, 'hideliu' => 1, 'hidebots' => 0 ],
true,
[ 'hidebots' => 0, 'hidehumans' => 1 ],
],
[
[ 'hideanons' => 1 ],
true,
[ 'userExpLevel' => 'registered' ]
],
[
[ 'hideliu' => 1 ],
true,
[ 'userExpLevel' => 'unregistered' ]
],
[
[ 'hideanons' => 1, 'hidebots' => 1 ],
true,
[ 'userExpLevel' => 'registered', 'hidebots' => 1 ]
],
[
[ 'hideliu' => 1, 'hidebots' => 0 ],
true,
[ 'userExpLevel' => 'unregistered', 'hidebots' => 0 ]
],
[
[ 'hidemyself' => 1, 'hidebyothers' => 1 ],
true,
[],
],
[
[ 'hidebots' => 1, 'hidehumans' => 1 ],
true,
[],
],
[
[ 'hidepatrolled' => 1, 'hideunpatrolled' => 1 ],
true,
[],
],
[
[ 'hideminor' => 1, 'hidemajor' => 1 ],
true,
[],
],
[
// changeType
[ 'hidepageedits' => 1, 'hidenewpages' => 1, 'hidecategorization' => 1, 'hidelog' => 1, ],
true,
[],
],
];
}
}