Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
79.10% covered (warning)
79.10%
106 / 134
76.92% covered (warning)
76.92%
10 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
PreliminaryCheckPager
79.10% covered (warning)
79.10%
106 / 134
76.92% covered (warning)
76.92%
10 / 13
51.17
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
 getTableClass
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getCellAttrs
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
3
 formatValue
64.18% covered (warning)
64.18%
43 / 67
0.00% covered (danger)
0.00%
0 / 1
30.28
 getIndexField
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 getFieldNames
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
4
 getQueryInfo
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 preprocessResults
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 isGlobalCheck
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getCentralReplicaDB
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 isFieldSortable
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDefaultSort
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPagingQueries
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
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 * @ingroup Pager
20 */
21
22namespace MediaWiki\CheckUser\Investigate\Pagers;
23
24use ExtensionRegistry;
25use IContextSource;
26use MediaWiki\CheckUser\Investigate\Services\PreliminaryCheckService;
27use MediaWiki\CheckUser\Services\TokenQueryManager;
28use MediaWiki\Extension\CentralAuth\CentralAuthDatabaseManager;
29use MediaWiki\Extension\CentralAuth\CentralAuthServices;
30use MediaWiki\Html\Html;
31use MediaWiki\Linker\LinkRenderer;
32use MediaWiki\Pager\TablePager;
33use MediaWiki\SpecialPage\SpecialPage;
34use MediaWiki\Title\NamespaceInfo;
35use MediaWiki\User\User;
36use MediaWiki\User\UserFactory;
37use MediaWiki\WikiMap\WikiMap;
38use Wikimedia\Rdbms\FakeResultWrapper;
39use Wikimedia\Rdbms\IReadableDatabase;
40
41/**
42 * @ingroup Pager
43 */
44class PreliminaryCheckPager extends TablePager {
45    private NamespaceInfo $namespaceInfo;
46    private ExtensionRegistry $extensionRegistry;
47    private TokenQueryManager $tokenQueryManager;
48    private PreliminaryCheckService $preliminaryCheckService;
49    private UserFactory $userFactory;
50
51    /** @var array Data loaded from the token provided in the request. */
52    protected $tokenData;
53
54    /** @var string[] Array of column name to translated table header message */
55    private $fieldNames;
56
57    /**
58     * @param IContextSource $context
59     * @param LinkRenderer $linkRenderer
60     * @param NamespaceInfo $namespaceInfo
61     * @param TokenQueryManager $tokenQueryManager
62     * @param ExtensionRegistry $extensionRegistry
63     * @param PreliminaryCheckService $preliminaryCheckService
64     * @param UserFactory $userFactory
65     */
66    public function __construct(
67        IContextSource $context,
68        LinkRenderer $linkRenderer,
69        NamespaceInfo $namespaceInfo,
70        TokenQueryManager $tokenQueryManager,
71        ExtensionRegistry $extensionRegistry,
72        PreliminaryCheckService $preliminaryCheckService,
73        UserFactory $userFactory
74    ) {
75        // This must be done before getIndexField is called by the TablePager constructor
76        $this->extensionRegistry = $extensionRegistry;
77        if ( $this->isGlobalCheck() ) {
78            // @phan-suppress-next-line PhanPossiblyNullTypeMismatchProperty
79            $this->mDb = $this->getCentralReplicaDB();
80        }
81
82        parent::__construct( $context, $linkRenderer );
83        $this->namespaceInfo = $namespaceInfo;
84        $this->preliminaryCheckService = $preliminaryCheckService;
85        $this->tokenQueryManager = $tokenQueryManager;
86        $this->userFactory = $userFactory;
87
88        $this->tokenData = $tokenQueryManager->getDataFromRequest( $context->getRequest() );
89        $this->mOffset = $this->tokenData['offset'] ?? '';
90    }
91
92    /**
93     * @inheritDoc
94     */
95    protected function getTableClass() {
96        return parent::getTableClass() .
97            ' ext-checkuser-investigate-table' .
98            ' ext-checkuser-investigate-table-preliminary-check';
99    }
100
101    /**
102     * @inheritDoc
103     */
104    public function getCellAttrs( $field, $value ) {
105        $attributes = parent::getCellAttrs( $field, $value );
106        $attributes['class'] ??= '';
107
108        switch ( $field ) {
109            case 'wiki':
110                $attributes['class'] .= ' ext-checkuser-investigate-table-cell-interactive';
111                $attributes['class'] .= ' ext-checkuser-investigate-table-cell-pinnable';
112                $attributes['data-field'] = $field;
113                $attributes['data-value'] = $value;
114                break;
115            case 'registration':
116                $attributes['class'] .= ' ext-checkuser-investigate-table-cell-interactive';
117                $attributes['class'] .= ' ext-checkuser-investigate-table-cell-pinnable';
118                $date = $this->getLanguage()->userDate(
119                    $value,
120                    $this->getUser(),
121                    [ 'format' => 'ISO 8601' ]
122                );
123                $attributes['data-field'] = $field;
124                $attributes['data-value'] = $date;
125                break;
126        }
127
128        // Add each cell to the tab index.
129        $attributes['tabindex'] = 0;
130
131        return $attributes;
132    }
133
134    /**
135     * @param string $name
136     * @param mixed $value preprocessed by {@link PreliminaryCheckService::getAdditionalLocalData}
137     * @return string
138     */
139    public function formatValue( $name, $value ) {
140        $language = $this->getLanguage();
141        $row = $this->mCurrentRow;
142
143        $user = $this->userFactory->newFromName( $row->name );
144        $userIsHidden = $user !== null && $user->isHidden() && !$this->getAuthority()->isAllowed( 'hideuser' );
145
146        $formatted = '';
147        switch ( $name ) {
148            case 'name':
149                // Hide the username if it is hidden from the current authority.
150                if ( $userIsHidden ) {
151                    $formatted = $this->msg( 'rev-deleted-user' )->text();
152                } else {
153                    $formatted = htmlspecialchars( $value );
154                }
155                break;
156            case 'registration':
157                if ( !$userIsHidden ) {
158                    $formatted = htmlspecialchars(
159                        $language->userTimeAndDate( $value, $this->getUser() )
160                    );
161                }
162                break;
163            case 'wiki':
164                $wiki = WikiMap::getWiki( $row->wiki );
165                if ( $wiki ) {
166                    $formatted = Html::element(
167                        'a',
168                        [
169                            'href' => $wiki->getFullUrl(
170                                $this->namespaceInfo->getCanonicalName( NS_USER ) . ':' . $row->name
171                            ),
172                        ],
173                        $wiki->getDisplayName()
174                    );
175                } else {
176                    $formatted = $this->msg( 'checkuser-investigate-preliminary-table-cell-wiki-nowiki' )->text();
177                }
178                break;
179            case 'editcount':
180                if ( $userIsHidden ) {
181                    return '';
182                }
183                $wiki = WikiMap::getWiki( $row->wiki );
184                if ( $wiki ) {
185                    $formatted = Html::rawElement(
186                        'a',
187                        [
188                            'href' => $wiki->getFullUrl(
189                                $this->namespaceInfo->getCanonicalName( NS_SPECIAL ) . ':Contributions/' . $row->name
190                            ),
191                        ],
192                        $this->msg(
193                            'checkuser-investigate-preliminary-table-cell-edits',
194                            $value
195                        )->parse()
196                    );
197                } else {
198                    $formatted = $this->getLinkRenderer()->makeKnownLink(
199                        SpecialPage::getTitleFor( 'Contributions', $row->name ),
200                        $this->msg(
201                            'checkuser-investigate-preliminary-table-cell-edits',
202                            $value
203                        )->text()
204                    );
205                }
206                break;
207            case 'blocked':
208                if ( !$userIsHidden ) {
209                    $formatted = $this->msg( $value ?
210                        'checkuser-investigate-preliminary-table-cell-blocked' :
211                        'checkuser-investigate-preliminary-table-cell-unblocked'
212                    )->parse();
213                }
214                break;
215            case 'groups':
216                if ( !$userIsHidden ) {
217                    $formatted = htmlspecialchars( implode( ', ', $value ) );
218                }
219                break;
220        }
221
222        return $formatted;
223    }
224
225    /**
226     * @inheritDoc
227     */
228    public function getIndexField() {
229        return $this->isGlobalCheck() ? [ [ 'lu_name', 'lu_wiki' ] ] : 'user_name';
230    }
231
232    /**
233     * @inheritDoc
234     */
235    public function getFieldNames() {
236        if ( $this->fieldNames === null ) {
237            $this->fieldNames = [
238                'name' => 'checkuser-investigate-preliminary-table-header-name',
239                'registration' => 'checkuser-investigate-preliminary-table-header-registration',
240                'wiki' => 'checkuser-investigate-preliminary-table-header-wiki',
241                'editcount' => 'checkuser-investigate-preliminary-table-header-editcount',
242                'blocked' => 'checkuser-investigate-preliminary-table-header-blocked',
243                'groups' => 'checkuser-investigate-preliminary-table-header-groups',
244            ];
245
246            if ( !$this->isGlobalCheck() ) {
247                unset( $this->fieldNames['wiki'] );
248            }
249
250            foreach ( $this->fieldNames as &$val ) {
251                $val = $this->msg( $val )->text();
252            }
253        }
254        return $this->fieldNames;
255    }
256
257    /**
258     * @inheritDoc
259     */
260    public function getQueryInfo() {
261        $targets = $this->tokenData['targets'] ?? [];
262        $users = array_filter( array_map( [ User::class, 'newFromName' ], $targets ), static function ( $user ) {
263            return (bool)$user;
264        } );
265
266        return $this->preliminaryCheckService->getQueryInfo( $users );
267    }
268
269    /**
270     * @inheritDoc
271     */
272    public function preprocessResults( $result ) {
273        $this->mResult = new FakeResultWrapper(
274            $this->preliminaryCheckService->preprocessResults( $result )
275        );
276    }
277
278    /**
279     * @return bool
280     */
281    public function isGlobalCheck(): bool {
282        return $this->extensionRegistry->isLoaded( 'CentralAuth' )
283            && class_exists( CentralAuthDatabaseManager::class );
284    }
285
286    /**
287     * @return IReadableDatabase|null
288     */
289    protected function getCentralReplicaDB(): ?IReadableDatabase {
290        if ( class_exists( CentralAuthDatabaseManager::class ) ) {
291            return CentralAuthServices::getDatabaseManager()->getCentralReplicaDB();
292        }
293        return null;
294    }
295
296    /**
297     * @inheritDoc
298     */
299    public function isFieldSortable( $field ) {
300        return false;
301    }
302
303    /**
304     * @inheritDoc
305     */
306    public function getDefaultSort() {
307        return '';
308    }
309
310    /**
311     * @inheritDoc
312     *
313     * Conceal the offset which may reveal private data.
314     */
315    public function getPagingQueries() {
316        return $this->tokenQueryManager->getPagingQueries(
317            $this->getRequest(), parent::getPagingQueries()
318        );
319    }
320}