3use Wikimedia\TestingAccessWrapper;
21 'wgStructuredChangeFiltersShowPreference' =>
true,
26 $mock = $this->getMockBuilder( ChangesListSpecialPage::class )
29 'ChangesListSpecialPage',
33 ->setMethods( [
'getPageTitle' ] )
34 ->getMockForAbstractClass();
36 $mock->method(
'getPageTitle' )->willReturn(
37 Title::makeTitle(
NS_SPECIAL,
'ChangesListSpecialPage' )
40 $mock = TestingAccessWrapper::newFromObject(
48 $requestOptions =
null,
57 $this->changesListSpecialPage->setContext(
$context );
58 $this->changesListSpecialPage->filterGroups = [];
59 $formOptions = $this->changesListSpecialPage->setup(
null );
61 # Filter out rc_timestamp conditions which depends on the test runtime
62 # This condition is not needed as of march 2, 2011 -- hashar
63 # @todo FIXME: Find a way to generate the correct rc_timestamp
67 $queryConditions = [];
72 [ $this->changesListSpecialPage,
'buildQuery' ],
83 $queryConditions = array_filter(
85 'ChangesListSpecialPageTest::filterOutRcTimestampCondition'
88 return $queryConditions;
94 $requestOptions =
null,
98 $queryConditions = $this->
buildQuery( $requestOptions, $user );
101 self::normalizeCondition( $expected ),
102 self::normalizeCondition( $queryConditions ),
109 $normalized = array_map(
110 function ( $k, $v ) use (
$dbr ) {
111 if ( is_array( $v ) ) {
115 return $dbr->makeList( [ $k => $v ], Database::LIST_AND );
117 array_keys( $conds ),
126 return ( is_array( $var ) ||
false === strpos( $var,
'rc_timestamp ' ) );
132 "rc_namespace = '0'",
137 "rc conditions with one namespace"
144 "rc_namespace != '0'",
150 "rc conditions with namespace inverted"
157 "rc_namespace IN ('1','2','3')",
160 'namespace' =>
'1;2;3',
162 "rc conditions with multiple namespaces"
169 "rc_namespace IN ('0','1','4','5','6','7')",
172 'namespace' =>
'1;4;7',
175 "rc conditions with multiple namespaces and associated"
182 "rc_namespace NOT IN ('2','3','8','9')",
185 'namespace' =>
'2;3;9',
189 "rc conditions with multiple namespaces, associated and inverted"
196 "rc_namespace NOT IN ('1','2','3')",
199 'namespace' =>
'1;2;3',
202 "rc conditions with multiple namespaces inverted"
214 "NOT((rc_actor = '{$user->getActorId()}') OR "
215 .
"(rc_actor = '0' AND rc_user = '{$user->getId()}'))",
220 "rc conditions: hidemyself=1 (logged in)",
228 "NOT((rc_actor = '$id') OR (rc_actor = '0' AND rc_user_text = '10.11.12.13'))",
233 "rc conditions: hidemyself=1 (anon)",
246 "(rc_actor = '{$user->getActorId()}') OR "
247 .
"(rc_actor = '0' AND rc_user_text = '{$user->getName()}')",
252 "rc conditions: hidebyothers=1 (logged in)",
260 "(rc_actor = '$id') OR (rc_actor = '0' AND rc_user_text = '10.11.12.13')",
265 "rc conditions: hidebyothers=1 (anon)",
276 'hidepageedits' => 1,
278 "rc conditions: hidepageedits=1"
290 "rc conditions: hidenewpages=1"
302 "rc conditions: hidelog=1"
315 "rc conditions: hidebots=0 hidehumans=1"
326 'hidepatrolled' => 1,
328 "rc conditions: hidepatrolled=1 (user not allowed)",
340 'hideunpatrolled' => 1,
342 "rc conditions: hideunpatrolled=1 (user not allowed)",
353 'hidepatrolled' => 1,
355 "rc conditions: hidepatrolled=1",
364 'rc_patrolled' => [ 1, 2 ],
367 'hideunpatrolled' => 1,
369 "rc conditions: hideunpatrolled=1",
381 'reviewStatus' =>
'manual'
383 "rc conditions: reviewStatus=manual",
388 'rc_patrolled' => [ 0, 2 ],
391 'reviewStatus' =>
'unpatrolled;auto'
393 "rc conditions: reviewStatus=unpatrolled;auto",
406 "rc conditions: hideminor=1"
418 "rc conditions: hidemajor=1"
429 'hidecategorization' => 1
431 "rc conditions: hidecategorization=1"
441 'userExpLevel' =>
'registered;unregistered;newcomer;learner;experienced',
443 "rc conditions: userExpLevel=registered;unregistered;newcomer;learner;experienced"
453 'userExpLevel' =>
'registered;unregistered',
455 "rc conditions: userExpLevel=registered;unregistered"
465 'userExpLevel' =>
'registered;unregistered;learner',
467 "rc conditions: userExpLevel=registered;unregistered;learner"
478 'COALESCE( actor_rc_user.actor_user, rc_user ) != 0',
481 'userExpLevel' =>
'newcomer;learner;experienced',
483 "rc conditions: userExpLevel=newcomer;learner;experienced"
494 'COALESCE( actor_rc_user.actor_user, rc_user ) != 0',
497 'userExpLevel' =>
'registered',
499 "rc conditions: userExpLevel=registered"
510 'COALESCE( actor_rc_user.actor_user, rc_user ) = 0',
513 'userExpLevel' =>
'unregistered',
515 "rc conditions: userExpLevel=unregistered"
526 'COALESCE( actor_rc_user.actor_user, rc_user ) != 0',
529 'userExpLevel' =>
'registered;learner',
531 "rc conditions: userExpLevel=registered;learner"
539 $conds = $this->
buildQuery( [
'userExpLevel' =>
'unregistered;experienced' ] );
542 '/\(COALESCE\( actor_rc_user.actor_user, rc_user \) = 0\) OR '
543 .
'\(\(user_editcount >= 500\) AND \(user_registration <= \'[^\']+\'\)\)/',
545 "rc conditions: userExpLevel=unregistered;experienced"
552 'wgLearnerEdits' => 10,
553 'wgLearnerMemberSince' => 4,
554 'wgExperiencedUserEdits' => 500,
555 'wgExperiencedUserMemberSince' => 30,
559 'Newcomer1' => [
'edits' => 2,
'days' => 2 ],
560 'Newcomer2' => [
'edits' => 12,
'days' => 3 ],
561 'Newcomer3' => [
'edits' => 8,
'days' => 5 ],
562 'Learner1' => [
'edits' => 15,
'days' => 10 ],
563 'Learner2' => [
'edits' => 450,
'days' => 20 ],
564 'Learner3' => [
'edits' => 460,
'days' => 33 ],
565 'Learner4' => [
'edits' => 525,
'days' => 28 ],
566 'Experienced1' => [
'edits' => 538,
'days' => 33 ],
571 [
'Newcomer1',
'Newcomer2',
'Newcomer3' ],
578 'Newcomer1',
'Newcomer2',
'Newcomer3',
579 'Learner1',
'Learner2',
'Learner3',
'Learner4',
581 $this->
fetchUsers( [
'newcomer',
'learner' ], $now )
587 'Newcomer1',
'Newcomer2',
'Newcomer3',
590 $this->
fetchUsers( [
'newcomer',
'experienced' ], $now )
595 [
'Learner1',
'Learner2',
'Learner3',
'Learner4' ],
608 'Learner1',
'Learner2',
'Learner3',
'Learner4',
611 $this->
fetchUsers( [
'learner',
'experienced' ], $now ),
612 'Learner and more experienced'
618 foreach ( $specs as $name => $spec ) {
622 'editcount' => $spec[
'edits'],
623 'registration' => $dbw->timestamp( $this->daysAgo( $spec[
'days'], $now ) ),
639 call_user_func_array(
640 [ $this->changesListSpecialPage,
'filterOnUserExperienceLevel' ],
642 get_class( $this->changesListSpecialPage ),
643 $this->changesListSpecialPage->getContext(),
644 $this->changesListSpecialPage->getDB(),
660 array_filter( $conds ) + [
'user_email' =>
'ut' ]
664 foreach ( $result as $row ) {
665 $usernames[] = $row->user_name;
672 $secondsPerDay = 86400;
673 return $now - $days * $secondsPerDay;
679 'msg' =>
'showhidefoo',
684 'msg' =>
'showhidebar',
691 'name' =>
'unstructured',
692 'class' => ChangesListBooleanFilterGroup::class,
697 'showHide' =>
'showhidefoo',
702 'showHide' =>
'showhidebar',
707 $this->changesListSpecialPage->getFilterGroupDefinitionFromLegacyCustomFilters(
714 $this->changesListSpecialPage->filterGroups = [];
718 'name' =>
'gub-group',
719 'title' =>
'gub-group-title',
720 'class' => ChangesListBooleanFilterGroup::class,
724 'label' =>
'foo-label',
725 'description' =>
'foo-description',
727 'showHide' =>
'showhidefoo',
732 'label' =>
'bar-label',
733 'description' =>
'bar-description',
741 'name' =>
'des-group',
742 'title' =>
'des-group-title',
743 'class' => ChangesListStringOptionsFilterGroup::class,
744 'isFullCoverage' =>
true,
748 'label' =>
'grault-label',
749 'description' =>
'grault-description',
753 'label' =>
'garply-label',
754 'description' =>
'garply-description',
757 'queryCallable' =>
function () {
763 'name' =>
'unstructured',
764 'class' => ChangesListBooleanFilterGroup::class,
767 'name' =>
'hidethud',
768 'showHide' =>
'showhidethud',
774 'showHide' =>
'showhidemos',
782 $this->changesListSpecialPage->registerFiltersFromDefinitions( $definition );
791 'name' =>
'gub-group',
792 'title' =>
'gub-group-title',
798 'label' =>
'bar-label',
799 'description' =>
'bar-description',
805 'defaultHighlightColor' =>
null
809 'label' =>
'foo-label',
810 'description' =>
'foo-description',
816 'defaultHighlightColor' =>
null
819 'fullCoverage' =>
true,
824 'name' =>
'des-group',
825 'title' =>
'des-group-title',
828 'fullCoverage' =>
true,
832 'label' =>
'grault-label',
833 'description' =>
'grault-description',
838 'defaultHighlightColor' =>
null
842 'label' =>
'garply-label',
843 'description' =>
'garply-description',
848 'defaultHighlightColor' =>
null
864 'grault-description',
866 'garply-description',
869 $this->changesListSpecialPage->getStructuredFilterJsData(),
877 [
'hidebots', [
'hidebots' =>
true ] ],
879 [
'bots', [
'hidebots' =>
false ] ],
881 [
'hideminor', [
'hideminor' =>
true ] ],
883 [
'minor', [
'hideminor' =>
false ] ],
885 [
'hidemajor', [
'hidemajor' =>
true ] ],
887 [
'hideliu', [
'hideliu' =>
true ] ],
889 [
'hidepatrolled', [
'hidepatrolled' =>
true ] ],
891 [
'hideunpatrolled', [
'hideunpatrolled' =>
true ] ],
893 [
'hideanons', [
'hideanons' =>
true ] ],
895 [
'hidemyself', [
'hidemyself' =>
true ] ],
897 [
'hidebyothers', [
'hidebyothers' =>
true ] ],
899 [
'hidehumans', [
'hidehumans' =>
true ] ],
901 [
'hidepageedits', [
'hidepageedits' =>
true ] ],
903 [
'pagedits', [
'hidepageedits' =>
false ] ],
905 [
'hidenewpages', [
'hidenewpages' =>
true ] ],
907 [
'hidecategorization', [
'hidecategorization' =>
true ] ],
909 [
'hidelog', [
'hidelog' =>
true ] ],
912 'userExpLevel=learner;experienced',
914 'userExpLevel' =>
'learner;experienced'
920 'bots,hideliu,hidemyself',
924 'hidemyself' =>
true,
929 'minor,hideanons,categorization',
931 'hideminor' =>
false,
933 'hidecategorization' =>
false,
938 'hidehumans,bots,hidecategorization',
940 'hidehumans' =>
true,
942 'hidecategorization' =>
true,
947 'hidemyself,userExpLevel=newcomer;learner,hideminor',
949 'hidemyself' =>
true,
951 'userExpLevel' =>
'newcomer;learner',
961 "expectedConflicts" =>
false,
966 "userExpLevel" =>
"newcomer",
968 "expectedConflicts" =>
false,
973 "userExpLevel" =>
"learner",
975 "expectedConflicts" =>
false,
980 "hidenewpages" =>
true,
981 "hidepageedits" =>
true,
982 "hidecategorization" =>
false,
984 "hideWikidata" =>
true,
986 "expectedConflicts" =>
true,
991 "hidenewpages" =>
false,
992 "hidepageedits" =>
true,
993 "hidecategorization" =>
false,
995 "hideWikidata" =>
true,
997 "expectedConflicts" =>
true,
1001 "hidemajor" =>
true,
1002 "hidenewpages" =>
false,
1003 "hidepageedits" =>
false,
1004 "hidecategorization" =>
true,
1006 "hideWikidata" =>
true,
1008 "expectedConflicts" =>
false,
1012 "hideminor" =>
true,
1013 "hidenewpages" =>
true,
1014 "hidepageedits" =>
true,
1015 "hidecategorization" =>
false,
1017 "hideWikidata" =>
true,
1019 "expectedConflicts" =>
false,
1030 $this->changesListSpecialPage->setContext(
$context );
1032 $this->assertEquals(
1034 $this->changesListSpecialPage->areFiltersInConflict()
1041 [
'hideanons' => 1,
'hideliu' => 1,
'hidebots' => 1 ],
1043 [
'userExpLevel' =>
'unregistered',
'hidebots' => 1, ],
1046 [
'hideanons' => 1,
'hideliu' => 1,
'hidebots' => 0 ],
1048 [
'hidebots' => 0,
'hidehumans' => 1 ],
1051 [
'hideanons' => 1 ],
1053 [
'userExpLevel' =>
'registered' ]
1058 [
'userExpLevel' =>
'unregistered' ]
1061 [
'hideanons' => 1,
'hidebots' => 1 ],
1063 [
'userExpLevel' =>
'registered',
'hidebots' => 1 ]
1066 [
'hideliu' => 1,
'hidebots' => 0 ],
1068 [
'userExpLevel' =>
'unregistered',
'hidebots' => 0 ]
1071 [
'hidemyself' => 1,
'hidebyothers' => 1 ],
1076 [
'hidebots' => 1,
'hidehumans' => 1 ],
1081 [
'hidepatrolled' => 1,
'hideunpatrolled' => 1 ],
1086 [
'hideminor' => 1,
'hidemajor' => 1 ],
1092 [
'hidepageedits' => 1,
'hidenewpages' => 1,
'hidecategorization' => 1,
'hidelog' => 1, ],
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Abstract base class for shared logic when testing ChangesListSpecialPage and subclasses.
const TYPE
Type marker, used by JavaScript.
Test class for ChangesListSpecialPage class.
testRcHidepatrolledDisabledFilter()
testRcHidepatrolledFilter()
testFilterUserExpLevelRegisteredUnregistered()
testFilterUserExpLevelUnregistrered()
testFilterUserExpLevelAll()
validateOptionsProvider()
testRcHideunpatrolledDisabledFilter()
testRcHidebyothersFilter()
createUsers( $specs, $now)
testGetStructuredFilterJsData()
testFilterUserExpLevelAllExperienceLevels()
testRcNsFilterMultipleAssociated()
fetchUsers( $filters, $now)
testRcNsFilterMultipleInvert()
testRcHideunpatrolledFilter()
static normalizeCondition( $conds)
provideGetFilterConflicts()
testGetFilterConflicts( $parameters, $expectedConflicts)
provideGetFilterConflicts
static filterOutRcTimestampCondition( $var)
return false if condition begins with 'rc_timestamp '
testFilterUserExpLevelRegistrered()
buildQuery( $requestOptions=null, $user=null)
testGetFilterGroupDefinitionFromLegacyCustomFilters()
testFilterUserExpLevelRegistreredOrLearner()
testRcNsFilterInversion()
testFilterUserExpLevelRegisteredUnregisteredLearner()
testRcNsFilterMultipleAssociatedInvert()
testFilterUserExpLevelUnregistreredOrExperienced()
testRcReviewStatusFilter()
assertConditions( $expected, $requestOptions=null, $message='', $user=null)
helper to test SpecialRecentchanges::buildQuery()
const TYPE
Type marker, used by JavaScript.
const NONE
Signifies that no options in the group are selected, meaning the group has no effect.
WebRequest clone which takes values from a provided array.
Group all the pieces relevant to the context of a request into one instance.
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
static createNew( $name, $params=[])
Add a user to the database, return the user object.
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist & $tables
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on and they can depend only on the ResourceLoaderContext $context
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return true
processing should stop and the error should be shown to the user * false
const MIGRATION_WRITE_BOTH