Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
85.84% covered (warning)
85.84%
897 / 1045
61.54% covered (warning)
61.54%
32 / 52
CRAP
0.00% covered (danger)
0.00%
0 / 1
ChangesListSpecialPage
85.92% covered (warning)
85.92%
897 / 1044
61.54% covered (warning)
61.54%
32 / 52
262.47
0.00% covered (danger)
0.00%
0 / 1
 __construct
96.17% covered (success)
96.17%
427 / 444
0.00% covered (danger)
0.00%
0 / 1
7
 areFiltersInConflict
62.50% covered (warning)
62.50%
10 / 16
0.00% covered (danger)
0.00%
0 / 1
13.27
 execute
51.02% covered (warning)
51.02%
25 / 49
0.00% covered (danger)
0.00%
0 / 1
46.08
 setTempUserConfig
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 considerActionsForDefaultSavedQuery
39.39% covered (danger)
39.39%
13 / 33
0.00% covered (danger)
0.00%
0 / 1
27.03
 getLinkDays
45.45% covered (danger)
45.45%
5 / 11
0.00% covered (danger)
0.00%
0 / 1
6.60
 includeRcFiltersApp
95.24% covered (success)
95.24%
40 / 42
0.00% covered (danger)
0.00%
0 / 1
5
 getRcFiltersConfigSummary
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 getRcFiltersConfigVars
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 outputNoResults
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 outputTimeout
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 getRows
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 getOptions
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 registerFilters
100.00% covered (success)
100.00%
51 / 51
100.00% covered (success)
100.00%
1 / 1
5
 transformFilterDefinition
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 registerFiltersFromDefinitions
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
4
 getLegacyShowHideFilters
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
5
 setup
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 getDefaultOptions
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
2
 registerFilterGroup
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getFilterGroups
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFilterGroup
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getStructuredFilterJsData
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
3
 fetchOptionsFromRequest
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 parseParameters
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
10
 validateOptions
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 fixContradictoryOptions
94.44% covered (success)
94.44%
17 / 18
0.00% covered (danger)
0.00%
0 / 1
7.01
 fixBackwardsCompatibilityOptions
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 replaceOldOptions
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
1 / 1
7
 convertParamsForLink
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
3.07
 buildQuery
96.43% covered (success)
96.43%
27 / 28
0.00% covered (danger)
0.00%
0 / 1
8
 doMainQuery
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
6
 runMainQueryHook
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getDB
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 webOutputHeader
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 webOutput
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 outputFeedLinks
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 outputChangesList
n/a
0 / 0
n/a
0 / 0
0
 doHeader
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setTopText
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setBottomText
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getExtraOptions
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 makeLegend
67.80% covered (warning)
67.80%
40 / 59
0.00% covered (danger)
0.00%
0 / 1
10.14
 addModules
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
3
 getGroupName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getRegisteredExpr
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 getExperienceExpr
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
3
 filterOnUserExperienceLevel
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
1 / 1
11
 isStructuredFilterUiEnabled
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 checkStructuredFilterUiEnabled
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getDefaultLimit
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getDefaultDays
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getLimitPreferenceName
n/a
0 / 0
n/a
0 / 0
0
 getSavedQueriesPreferenceName
n/a
0 / 0
n/a
0 / 0
0
 getDefaultDaysPreferenceName
n/a
0 / 0
n/a
0 / 0
0
 getCollapsedPreferenceName
n/a
0 / 0
n/a
0 / 0
0
 expandSymbolicNamespaceFilters
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2/**
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
17 *
18 * @file
19 */
20
21namespace MediaWiki\SpecialPage;
22
23use ChangesListBooleanFilter;
24use ChangesListBooleanFilterGroup;
25use ChangesListFilterGroup;
26use ChangesListStringOptionsFilterGroup;
27use ChangeTags;
28use LogFormatter;
29use MediaWiki\Context\IContextSource;
30use MediaWiki\Html\FormOptions;
31use MediaWiki\Html\Html;
32use MediaWiki\Json\FormatJson;
33use MediaWiki\MainConfigNames;
34use MediaWiki\MediaWikiServices;
35use MediaWiki\Parser\Sanitizer;
36use MediaWiki\ResourceLoader as RL;
37use MediaWiki\User\TempUser\TempUserConfig;
38use MediaWiki\User\UserArray;
39use MediaWiki\User\UserIdentity;
40use MediaWiki\User\UserIdentityUtils;
41use MWExceptionHandler;
42use OOUI\IconWidget;
43use RecentChange;
44use Wikimedia\Rdbms\DBQueryTimeoutError;
45use Wikimedia\Rdbms\FakeResultWrapper;
46use Wikimedia\Rdbms\IExpression;
47use Wikimedia\Rdbms\IReadableDatabase;
48use Wikimedia\Rdbms\IResultWrapper;
49use Wikimedia\Rdbms\RawSQLValue;
50
51/**
52 * Special page which uses a ChangesList to show query results.
53 *
54 * @todo Most of the functions here should be protected instead of public.
55 *
56 * @ingroup SpecialPage
57 */
58abstract class ChangesListSpecialPage extends SpecialPage {
59
60    /** @var string */
61    protected $rcSubpage;
62
63    /** @var FormOptions */
64    protected $rcOptions;
65
66    protected UserIdentityUtils $userIdentityUtils;
67    protected TempUserConfig $tempUserConfig;
68
69    // Order of both groups and filters is significant; first is top-most priority,
70    // descending from there.
71    // 'showHideSuffix' is a shortcut to and avoid spelling out
72    // details specific to subclasses here.
73    /**
74     * Definition information for the filters and their groups
75     *
76     * The value is $groupDefinition, a parameter to the ChangesListFilterGroup constructor.
77     * However, priority is dynamically added for the core groups, to ease maintenance.
78     *
79     * Groups are displayed to the user in the structured UI.  However, if necessary,
80     * all of the filters in a group can be configured to only display on the
81     * unstuctured UI, in which case you don't need a group title.
82     *
83     * @var array
84     */
85    private $filterGroupDefinitions;
86
87    /**
88     * @var array Same format as filterGroupDefinitions, but for a single group (reviewStatus)
89     * that is registered conditionally.
90     */
91    private $legacyReviewStatusFilterGroupDefinition;
92
93    /** @var array Single filter group registered conditionally */
94    private $reviewStatusFilterGroupDefinition;
95
96    /** @var array Single filter group registered conditionally */
97    private $hideCategorizationFilterDefinition;
98
99    /**
100     * Filter groups, and their contained filters
101     * This is an associative array (with group name as key) of ChangesListFilterGroup objects.
102     *
103     * @var ChangesListFilterGroup[]
104     */
105    protected $filterGroups = [];
106
107    /**
108     * @param string $name
109     * @param string $restriction
110     * @param UserIdentityUtils $userIdentityUtils
111     * @param TempUserConfig $tempUserConfig
112     */
113    public function __construct(
114        $name,
115        $restriction,
116        UserIdentityUtils $userIdentityUtils,
117        TempUserConfig $tempUserConfig
118    ) {
119        parent::__construct( $name, $restriction );
120
121        $this->userIdentityUtils = $userIdentityUtils;
122        $this->tempUserConfig = $tempUserConfig;
123
124        $nonRevisionTypes = [ RC_LOG ];
125        $this->getHookRunner()->onSpecialWatchlistGetNonRevisionTypes( $nonRevisionTypes );
126
127        $this->filterGroupDefinitions = [
128            [
129                'name' => 'registration',
130                'title' => 'rcfilters-filtergroup-registration',
131                'class' => ChangesListBooleanFilterGroup::class,
132                'filters' => [
133                    [
134                        'name' => 'hideliu',
135                        // rcshowhideliu-show, rcshowhideliu-hide,
136                        // wlshowhideliu
137                        'showHideSuffix' => 'showhideliu',
138                        'default' => false,
139                        'queryCallable' => static function ( string $specialClassName, IContextSource $ctx,
140                            IReadableDatabase $dbr, &$tables, &$fields, &$conds, &$query_options, &$join_conds
141                        ) {
142                            $conds['actor_user'] = null;
143                            $join_conds['recentchanges_actor'] = [ 'JOIN', 'actor_id=rc_actor' ];
144                        },
145                        'isReplacedInStructuredUi' => true,
146
147                    ],
148                    [
149                        'name' => 'hideanons',
150                        // rcshowhideanons-show, rcshowhideanons-hide,
151                        // wlshowhideanons
152                        'showHideSuffix' => 'showhideanons',
153                        'default' => false,
154                        'queryCallable' => static function ( string $specialClassName, IContextSource $ctx,
155                            IReadableDatabase $dbr, &$tables, &$fields, &$conds, &$query_options, &$join_conds
156                        ) {
157                            $conds[] = 'actor_user IS NOT NULL';
158                            $join_conds['recentchanges_actor'] = [ 'JOIN', 'actor_id=rc_actor' ];
159                        },
160                        'isReplacedInStructuredUi' => true,
161                    ]
162                ],
163            ],
164
165            [
166                'name' => 'userExpLevel',
167                'title' => 'rcfilters-filtergroup-user-experience-level',
168                'class' => ChangesListStringOptionsFilterGroup::class,
169                'isFullCoverage' => true,
170                'filters' => [
171                    [
172                        'name' => 'unregistered',
173                        'label' => 'rcfilters-filter-user-experience-level-unregistered-label',
174                        'description' => $this->tempUserConfig->isKnown() ?
175                            'rcfilters-filter-user-experience-level-unregistered-description-temp' :
176                            'rcfilters-filter-user-experience-level-unregistered-description',
177                        'cssClassSuffix' => 'user-unregistered',
178                        'isRowApplicableCallable' => function ( IContextSource $ctx, RecentChange $rc ) {
179                            return !$this->userIdentityUtils->isNamed( $rc->getPerformerIdentity() );
180                        }
181                    ],
182                    [
183                        'name' => 'registered',
184                        'label' => 'rcfilters-filter-user-experience-level-registered-label',
185                        'description' => 'rcfilters-filter-user-experience-level-registered-description',
186                        'cssClassSuffix' => 'user-registered',
187                        'isRowApplicableCallable' => function ( IContextSource $ctx, RecentChange $rc ) {
188                            return $this->userIdentityUtils->isNamed( $rc->getPerformerIdentity() );
189                        }
190                    ],
191                    [
192                        'name' => 'newcomer',
193                        'label' => 'rcfilters-filter-user-experience-level-newcomer-label',
194                        'description' => 'rcfilters-filter-user-experience-level-newcomer-description',
195                        'cssClassSuffix' => 'user-newcomer',
196                        'isRowApplicableCallable' => static function ( IContextSource $ctx, RecentChange $rc ) {
197                            $performer = $rc->getPerformerIdentity();
198                            return $performer->isRegistered() &&
199                                MediaWikiServices::getInstance()
200                                    ->getUserFactory()
201                                    ->newFromUserIdentity( $performer )
202                                    ->getExperienceLevel() === 'newcomer';
203                        }
204                    ],
205                    [
206                        'name' => 'learner',