Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
51.15% |
223 / 436 |
|
63.64% |
7 / 11 |
CRAP | |
0.00% |
0 / 1 |
CheckUserGetUsersPager | |
51.15% |
223 / 436 |
|
63.64% |
7 / 11 |
768.29 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
19 / 19 |
|
100.00% |
1 / 1 |
4 | |||
formatRow | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getBody | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
42 | |||
formatUserRow | |
53.73% |
72 / 134 |
|
0.00% |
0 / 1 |
112.30 | |||
preprocessResults | |
100.00% |
42 / 42 |
|
100.00% |
1 / 1 |
11 | |||
getQueryInfo | |
100.00% |
18 / 18 |
|
100.00% |
1 / 1 |
6 | |||
getQueryInfoForCuChanges | |
100.00% |
25 / 25 |
|
100.00% |
1 / 1 |
3 | |||
getQueryInfoForCuLogEvent | |
100.00% |
23 / 23 |
|
100.00% |
1 / 1 |
2 | |||
getQueryInfoForCuPrivateEvent | |
100.00% |
23 / 23 |
|
100.00% |
1 / 1 |
2 | |||
getStartBody | |
0.00% |
0 / 26 |
|
0.00% |
0 / 1 |
20 | |||
getEndBody | |
0.00% |
0 / 108 |
|
0.00% |
0 / 1 |
90 |
1 | <?php |
2 | |
3 | namespace MediaWiki\CheckUser\CheckUser\Pagers; |
4 | |
5 | use ExtensionRegistry; |
6 | use IContextSource; |
7 | use LogicException; |
8 | use MediaWiki\Block\BlockPermissionCheckerFactory; |
9 | use MediaWiki\CheckUser\CheckUser\SpecialCheckUser; |
10 | use MediaWiki\CheckUser\CheckUser\Widgets\HTMLFieldsetCheckUser; |
11 | use MediaWiki\CheckUser\ClientHints\ClientHintsLookupResults; |
12 | use MediaWiki\CheckUser\ClientHints\ClientHintsReferenceIds; |
13 | use MediaWiki\CheckUser\Services\CheckUserLogService; |
14 | use MediaWiki\CheckUser\Services\CheckUserLookupUtils; |
15 | use MediaWiki\CheckUser\Services\CheckUserUtilityService; |
16 | use MediaWiki\CheckUser\Services\TokenQueryManager; |
17 | use MediaWiki\CheckUser\Services\UserAgentClientHintsFormatter; |
18 | use MediaWiki\CheckUser\Services\UserAgentClientHintsLookup; |
19 | use MediaWiki\CheckUser\Services\UserAgentClientHintsManager; |
20 | use MediaWiki\Config\ConfigException; |
21 | use MediaWiki\Extension\CentralAuth\User\CentralAuthUser; |
22 | use MediaWiki\Html\FormOptions; |
23 | use MediaWiki\Html\Html; |
24 | use MediaWiki\Html\ListToggle; |
25 | use MediaWiki\Linker\Linker; |
26 | use MediaWiki\Linker\LinkRenderer; |
27 | use MediaWiki\Permissions\PermissionManager; |
28 | use MediaWiki\SpecialPage\SpecialPageFactory; |
29 | use MediaWiki\User\CentralId\CentralIdLookup; |
30 | use MediaWiki\User\UserEditTracker; |
31 | use MediaWiki\User\UserFactory; |
32 | use MediaWiki\User\UserGroupManager; |
33 | use MediaWiki\User\UserIdentity; |
34 | use MediaWiki\User\UserIdentityLookup; |
35 | use MediaWiki\User\UserIdentityValue; |
36 | use MediaWiki\WikiMap\WikiMap; |
37 | use Wikimedia\IPUtils; |
38 | use Wikimedia\Rdbms\IConnectionProvider; |
39 | use Xml; |
40 | |
41 | class CheckUserGetUsersPager extends AbstractCheckUserPager { |
42 | /** @var bool Whether the user performing this check has the block right. */ |
43 | protected bool $canPerformBlocks; |
44 | |
45 | /** @var array[] */ |
46 | protected $userSets; |
47 | |
48 | /** @var string|false */ |
49 | private $centralAuthToollink; |
50 | |
51 | /** @var array|false */ |
52 | private $globalBlockingToollink; |
53 | |
54 | /** @var string[][] */ |
55 | private $aliases; |
56 | |
57 | private ClientHintsLookupResults $clientHintsLookupResults; |
58 | |
59 | private BlockPermissionCheckerFactory $blockPermissionCheckerFactory; |
60 | private PermissionManager $permissionManager; |
61 | private UserEditTracker $userEditTracker; |
62 | private CheckUserUtilityService $checkUserUtilityService; |
63 | private UserAgentClientHintsLookup $clientHintsLookup; |
64 | private UserAgentClientHintsFormatter $clientHintsFormatter; |
65 | |
66 | /** |
67 | * @param FormOptions $opts |
68 | * @param UserIdentity $target |
69 | * @param bool $xfor |
70 | * @param string $logType |
71 | * @param TokenQueryManager $tokenQueryManager |
72 | * @param PermissionManager $permissionManager |
73 | * @param BlockPermissionCheckerFactory $blockPermissionCheckerFactory |
74 | * @param UserGroupManager $userGroupManager |
75 | * @param CentralIdLookup $centralIdLookup |
76 | * @param IConnectionProvider $dbProvider |
77 | * @param SpecialPageFactory $specialPageFactory |
78 | * @param UserIdentityLookup $userIdentityLookup |
79 | * @param UserFactory $userFactory |
80 | * @param CheckUserLogService $checkUserLogService |
81 | * @param CheckUserLookupUtils $checkUserLookupUtils |
82 | * @param UserEditTracker $userEditTracker |
83 | * @param CheckUserUtilityService $checkUserUtilityService |
84 | * @param UserAgentClientHintsLookup $clientHintsLookup |
85 | * @param UserAgentClientHintsFormatter $clientHintsFormatter |
86 | * @param IContextSource|null $context |
87 | * @param LinkRenderer|null $linkRenderer |
88 | * @param ?int $limit |
89 | */ |
90 | public function __construct( |
91 | FormOptions $opts, |
92 | UserIdentity $target, |
93 | bool $xfor, |
94 | string $logType, |
95 | TokenQueryManager $tokenQueryManager, |
96 | PermissionManager $permissionManager, |
97 | BlockPermissionCheckerFactory $blockPermissionCheckerFactory, |
98 | UserGroupManager $userGroupManager, |
99 | CentralIdLookup $centralIdLookup, |
100 | IConnectionProvider $dbProvider, |
101 | SpecialPageFactory $specialPageFactory, |
102 | UserIdentityLookup $userIdentityLookup, |
103 | UserFactory $userFactory, |
104 | CheckUserLogService $checkUserLogService, |
105 | CheckUserLookupUtils $checkUserLookupUtils, |
106 | UserEditTracker $userEditTracker, |
107 | CheckUserUtilityService $checkUserUtilityService, |
108 | UserAgentClientHintsLookup $clientHintsLookup, |
109 | UserAgentClientHintsFormatter $clientHintsFormatter, |
110 | IContextSource $context = null, |
111 | LinkRenderer $linkRenderer = null, |
112 | ?int $limit = null |
113 | ) { |
114 | parent::__construct( $opts, $target, $logType, $tokenQueryManager, |
115 | $userGroupManager, $centralIdLookup, $dbProvider, $specialPageFactory, |
116 | $userIdentityLookup, $checkUserLogService, $userFactory, $checkUserLookupUtils, |
117 | $context, $linkRenderer, $limit ); |
118 | $this->checkType = SpecialCheckUser::SUBTYPE_GET_USERS; |
119 | $this->xfor = $xfor; |
120 | $this->canPerformBlocks = $permissionManager->userHasRight( $this->getUser(), 'block' ) |
121 | && !$this->getUser()->getBlock(); |
122 | $this->centralAuthToollink = ExtensionRegistry::getInstance()->isLoaded( 'CentralAuth' ) |
123 | ? $this->getConfig()->get( 'CheckUserCAtoollink' ) : false; |
124 | $this->globalBlockingToollink = ExtensionRegistry::getInstance()->isLoaded( 'GlobalBlocking' ) |
125 | ? $this->getConfig()->get( 'CheckUserGBtoollink' ) : false; |
126 | $this->aliases = $this->getLanguage()->getSpecialPageAliases(); |
127 | $this->blockPermissionCheckerFactory = $blockPermissionCheckerFactory; |
128 | $this->permissionManager = $permissionManager; |
129 | $this->userEditTracker = $userEditTracker; |
130 | $this->checkUserUtilityService = $checkUserUtilityService; |
131 | $this->clientHintsLookup = $clientHintsLookup; |
132 | $this->clientHintsFormatter = $clientHintsFormatter; |
133 | } |
134 | |
135 | /** |
136 | * Returns nothing as formatUserRow |
137 | * is instead used. |
138 | * |
139 | * @inheritDoc |
140 | */ |
141 | public function formatRow( $row ): string { |
142 | return ''; |
143 | } |
144 | |
145 | /** @inheritDoc */ |
146 | public function getBody() { |
147 | $this->getOutput()->addModuleStyles( $this->getModuleStyles() ); |
148 | if ( !$this->mQueryDone ) { |
149 | $this->doQuery(); |
150 | } |
151 | |
152 | if ( $this->mResult->numRows() ) { |
153 | # Do any special query batches before display |
154 | $this->doBatchLookups(); |
155 | } |
156 | |
157 | # Don't use any extra rows returned by the query |
158 | $numRows = count( $this->userSets['ids'] ); |
159 | |
160 | $s = $this->getStartBody(); |
161 | if ( $numRows ) { |
162 | $keys = array_keys( $this->userSets['ids'] ); |
163 | if ( $this->mIsBackwards ) { |
164 | $keys = array_reverse( $keys ); |
165 | } |
166 | foreach ( $keys as $user_text ) { |
167 | $s .= $this->formatUserRow( $user_text ); |
168 | } |
169 | $s .= $this->getFooter(); |
170 | } else { |
171 | $s .= $this->getEmptyBody(); |
172 | } |
173 | $s .= $this->getEndBody(); |
174 | return $s; |
175 | } |
176 | |
177 | /** |
178 | * Gets a row for the results for 'Get users' |
179 | * |
180 | * @param string $user_text the username for the current row. |
181 | * @return string |
182 | */ |
183 | public function formatUserRow( string $user_text ): string { |
184 | $templateParams = []; |
185 | |
186 | $userIsIP = IPUtils::isIPAddress( $user_text ); |
187 | |
188 | // Load user object |
189 | $user = new UserIdentityValue( |
190 | $this->userSets['ids'][$user_text], |
191 | $userIsIP ? IPUtils::prettifyIP( $user_text ) ?? $user_text : $user_text |
192 | ); |
193 | $hidden = $this->userFactory->newFromUserIdentity( $user )->isHidden() |
194 | && !$this->getAuthority()->isAllowed( 'hideuser' ); |
195 | if ( $hidden ) { |
196 | // User is hidden from the current authority, so the current authority cannot block this user either. |
197 | // As such, the checkbox (used for blocking the user) should not be shown. |
198 | $templateParams['canPerformBlocks'] = false; |
199 | $templateParams['userText'] = ''; |
200 | $templateParams['userLink'] = Html::element( |
201 | 'span', |
202 | [ 'class' => 'history-deleted' ], |
203 | $this->msg( 'rev-deleted-user' )->text() |
204 | ); |
205 | } else { |
206 | $templateParams['canPerformBlocks'] = $this->canPerformBlocks; |
207 | $templateParams['userText'] = $user->getName(); |
208 | $userNonExistent = !IPUtils::isIPAddress( $user ) && !$user->isRegistered(); |
209 | if ( $userNonExistent ) { |
210 | $templateParams['userLinkClass'] = 'mw-checkuser-nonexistent-user'; |
211 | } |
212 | $templateParams['userLink'] = Linker::userLink( $user->getId(), $user, $user ); |
213 | $templateParams['userToolLinks'] = Linker::userToolLinksRedContribs( |
214 | $user->getId(), |
215 | $user, |
216 | $this->userEditTracker->getUserEditCount( $user ), |
217 | // don't render parentheses in HTML markup (CSS will provide) |
218 | false |
219 | ); |
220 | if ( $userIsIP ) { |
221 | $templateParams['userLinks'] = $this->msg( 'checkuser-userlinks-ip', $user )->parse(); |
222 | } elseif ( !$userNonExistent ) { |
223 | if ( $this->msg( 'checkuser-userlinks' )->exists() ) { |
224 | $templateParams['userLinks'] = |
225 | $this->msg( 'checkuser-userlinks', htmlspecialchars( $user ) )->parse(); |
226 | } |
227 | } |
228 | // Add global user tools links |
229 | // Add CentralAuth link for real registered users |
230 | if ( $this->centralAuthToollink !== false |
231 | && !$userIsIP |
232 | && !$userNonExistent |
233 | ) { |
234 | // Get CentralAuth SpecialPage name in UserLang from the first Alias name |
235 | $spca = $this->aliases['CentralAuth'][0]; |
236 | $calinkAlias = str_replace( '_', ' ', $spca ); |
237 | $centralCAUrl = WikiMap::getForeignURL( |
238 | $this->centralAuthToollink, |
239 | 'Special:CentralAuth' |
240 | ); |
241 | if ( $centralCAUrl === false ) { |
242 | throw new ConfigException( |
243 | "Could not retrieve URL for CentralAuth: $this->centralAuthToollink" |
244 | ); |
245 | } |
246 | $linkCA = Html::element( 'a', |
247 | [ |
248 | 'href' => $centralCAUrl . "/" . $user, |
249 | 'title' => $this->msg( 'centralauth' )->text(), |
250 | ], |
251 | $calinkAlias |
252 | ); |
253 | $templateParams['centralAuthLink'] = $this->msg( 'parentheses' )->rawParams( $linkCA )->escaped(); |
254 | } |
255 | // Add GlobalBlocking link to CentralWiki |
256 | if ( $this->globalBlockingToollink !== false |
257 | && IPUtils::isIPAddress( $user ) |
258 | ) { |
259 | // Get GlobalBlock SpecialPage name in UserLang from the first Alias name |
260 | $centralGBUrl = WikiMap::getForeignURL( |
261 | $this->globalBlockingToollink['centralDB'], |
262 | 'Special:GlobalBlock' |
263 | ); |
264 | $spgb = $this->aliases['GlobalBlock'][0]; |
265 | $gblinkAlias = str_replace( '_', ' ', $spgb ); |
266 | if ( ExtensionRegistry::getInstance()->isLoaded( 'CentralAuth' ) ) { |
267 | $gbUserGroups = CentralAuthUser::getInstance( $this->getUser() )->getGlobalGroups(); |
268 | // Link to GB via WikiMap since CA require it |
269 | if ( $centralGBUrl === false ) { |
270 | throw new ConfigException( |
271 | 'Could not retrieve URL for global blocking toollink' |
272 | ); |
273 | } |
274 | $linkGB = Html::element( 'a', |
275 | [ |
276 | 'href' => $centralGBUrl . "/" . $user, |
277 | 'title' => $this->msg( 'globalblocking-block-submit' )->text(), |
278 | ], |
279 | $gblinkAlias |
280 | ); |
281 | } elseif ( $centralGBUrl !== false ) { |
282 | // Case wikimap configured without CentralAuth extension |
283 | // Get effective Local user groups since there is a wikimap but there is no CA |
284 | $gbUserGroups = $this->userGroupManager->getUserEffectiveGroups( $this->getUser() ); |
285 | $linkGB = Html::element( 'a', |
286 | [ |
287 | 'href' => $centralGBUrl . "/" . $user, |
288 | 'title' => $this->msg( 'globalblocking-block-submit' )->text(), |
289 | ], |
290 | $gblinkAlias |
291 | ); |
292 | } else { |
293 | // Load local user group instead |
294 | $gbUserGroups = [ '' ]; |
295 | $gbtitle = $this->getPageTitle( 'GlobalBlock' ); |
296 | $linkGB = $this->getLinkRenderer()->makeKnownLink( |
297 | $gbtitle, |
298 | $gblinkAlias, |
299 | [ 'title' => $this->msg( 'globalblocking-block-submit' ) ] |
300 | ); |
301 | $gbUserCanDo = $this->permissionManager->userHasRight( $this->getUser(), 'globalblock' ); |
302 | if ( $gbUserCanDo ) { |
303 | $this->globalBlockingToollink['groups'] = $gbUserGroups; |
304 | } |
305 | } |
306 | // Only load the script for users in the configured global(local) group(s) or |
307 | // for local user with globalblock permission if there is no WikiMap |
308 | if ( count( array_intersect( $this->globalBlockingToollink['groups'], $gbUserGroups ) ) ) { |
309 | $templateParams['globalBlockLink'] .= $this->msg( 'parentheses' )->rawParams( $linkGB )->escaped(); |
310 | } |
311 | } |
312 | // Check if this user or IP is blocked. If so, give a link to the block log... |
313 | $templateParams['flags'] = $this->userBlockFlags( $userIsIP ? $user : '', $user ); |
314 | } |
315 | // Show edit time range |
316 | $templateParams['timeRange'] = $this->getTimeRangeString( |
317 | $this->userSets['first'][$user_text], |
318 | $this->userSets['last'][$user_text] |
319 | ); |
320 | // Total edit count |
321 | $templateParams['editCount'] = $this->userSets['edits'][$user_text]; |
322 | // List out each IP/XFF combo for this username |
323 | $templateParams['infoSets'] = []; |
324 | for ( $i = ( count( $this->userSets['infosets'][$user_text] ) - 1 ); $i >= 0; $i-- ) { |
325 | // users_infosets[$name][$i] is array of [ $row->ip, XFF ]; |
326 | $row = []; |
327 | [ $clientIP, $xffString ] = $this->userSets['infosets'][$user_text][$i]; |
328 | // IP link |
329 | $row['ipLink'] = $this->getSelfLink( $clientIP, [ 'user' => $clientIP ] ); |
330 | // XFF string, link to /xff search |
331 | if ( $xffString ) { |
332 | // Flag our trusted proxies |
333 | [ $client ] = $this->checkUserUtilityService->getClientIPfromXFF( $xffString ); |
334 | // XFF was trusted if client came from it |
335 | $trusted = ( $client === $clientIP ); |
336 | $row['xffTrusted'] = $trusted; |
337 | $row['xff'] = $this->getSelfLink( $xffString, [ 'user' => $client . '/xff' ] ); |
338 | } |
339 | $templateParams['infoSets'][] = $row; |
340 | } |
341 | // List out each agent for this username |
342 | for ( $i = ( count( $this->userSets['agentsets'][$user_text] ) - 1 ); $i >= 0; $i-- ) { |
343 | $templateParams['agentsList'][] = $this->userSets['agentsets'][$user_text][$i]; |
344 | } |
345 | // Show Client Hints data if display is enabled. |
346 | $templateParams['displayClientHints'] = $this->displayClientHints; |
347 | if ( $this->displayClientHints ) { |
348 | $templateParams['clientHintsList'] = []; |
349 | [ $usagesOfClientHints, $clientHintsDataObjects ] = $this->clientHintsLookupResults |
350 | ->getGroupedClientHintsDataForReferenceIds( $this->userSets['clienthints'][$user_text] ); |
351 | // Sort the $usagesOfClientHints array such that the ClientHintsData object that is most used |
352 | // by the user referenced in $user_text is shown first and the ClientHintsData object least used is |
353 | // shown last. This is done to be consistent with the way that User-Agent strings are shown as well |
354 | // as ensuring that if there are more than 10 items the ClientHintsData objects used on the most reference |
355 | // IDs are shown. |
356 | arsort( $usagesOfClientHints, SORT_NUMERIC ); |
357 | // Limit the number displayed to at most 10 starting at the |
358 | // ClientHintsData object associated with the most rows |
359 | // in the results. This is to be consistent with User-Agent |
360 | // strings which are also limited to 10 strings. |
361 | $i = 0; |
362 | foreach ( array_keys( $usagesOfClientHints ) as $clientHintsDataIndex ) { |
363 | // If 10 Client Hints data objects have been displayed, |
364 | // then don't show any more (similar to User-Agent strings). |
365 | if ( $i === 10 ) { |
366 | break; |
367 | } |
368 | $clientHintsDataObject = $clientHintsDataObjects[$clientHintsDataIndex]; |
369 | if ( $clientHintsDataObject ) { |
370 | $formattedClientHintsData = $this->clientHintsFormatter |
371 | ->formatClientHintsDataObject( $clientHintsDataObject ); |
372 | if ( $formattedClientHintsData ) { |
373 | // If the Client Hints data object is valid and evaluates to a non-empty |
374 | // human readable string, then add it to the list to display. |
375 | $i++; |
376 | $templateParams['clientHintsList'][] = $formattedClientHintsData; |
377 | } |
378 | } |
379 | } |
380 | } |
381 | return $this->templateParser->processTemplate( 'GetUsersLine', $templateParams ); |
382 | } |
383 | |
384 | /** @inheritDoc */ |
385 | protected function preprocessResults( $result ) { |
386 | $this->userSets = [ |
387 | 'first' => [], |
388 | 'last' => [], |
389 | 'edits' => [], |
390 | 'ids' => [], |
391 | 'infosets' => [], |
392 | 'agentsets' => [], |
393 | 'clienthints' => [], |
394 | ]; |
395 | $referenceIdsForLookup = new ClientHintsReferenceIds(); |
396 | |
397 | foreach ( $result as $row ) { |
398 | // Use the IP as the user_text if the actor ID is NULL and the IP is not NULL (T353953). |
399 | if ( $row->actor === null && $row->ip ) { |
400 | $row->user_text = $row->ip; |
401 | } |
402 | |
403 | if ( !array_key_exists( $row->user_text, $this->userSets['edits'] ) ) { |
404 | $this->userSets['last'][$row->user_text] = $row->timestamp; |
405 | $this->userSets['edits'][$row->user_text] = 0; |
406 | $this->userSets['ids'][$row->user_text] = $row->user ?? 0; |
407 | $this->userSets['infosets'][$row->user_text] = []; |
408 | $this->userSets['agentsets'][$row->user_text] = []; |
409 | $this->userSets['clienthints'][$row->user_text] = new ClientHintsReferenceIds(); |
410 | } |
411 | if ( $this->displayClientHints ) { |
412 | $referenceIdsForLookup->addReferenceIds( |
413 | $row->client_hints_reference_id, |
414 | $row->client_hints_reference_type |
415 | ); |
416 | $this->userSets['clienthints'][$row->user_text]->addReferenceIds( |
417 | $row->client_hints_reference_id, |
418 | $row->client_hints_reference_type |
419 | ); |
420 | } |
421 | $this->userSets['edits'][$row->user_text]++; |
422 | $this->userSets['first'][$row->user_text] = $row->timestamp; |
423 | // Prettify IP |
424 | $formattedIP = IPUtils::prettifyIP( $row->ip ) ?? $row->ip; |
425 | // Treat blank or NULL xffs as empty strings |
426 | $xff = empty( $row->xff ) ? null : $row->xff; |
427 | $xff_ip_combo = [ $formattedIP, $xff ]; |
428 | // Add this IP/XFF combo for this username if it's not already there |
429 | if ( !in_array( $xff_ip_combo, $this->userSets['infosets'][$row->user_text] ) ) { |
430 | $this->userSets['infosets'][$row->user_text][] = $xff_ip_combo; |
431 | } |
432 | // Add this agent string if it's not already there; 10 max. |
433 | if ( count( $this->userSets['agentsets'][$row->user_text] ) < 10 ) { |
434 | if ( !in_array( $row->agent, $this->userSets['agentsets'][$row->user_text] ) ) { |
435 | $this->userSets['agentsets'][$row->user_text][] = $row->agent; |
436 | } |
437 | } |
438 | } |
439 | |
440 | // Lookup the Client Hints data objects from the DB |
441 | // and then batch format the ClientHintsData objects |
442 | // for display. |
443 | if ( $this->displayClientHints ) { |
444 | $this->clientHintsLookupResults = $this->clientHintsLookup |
445 | ->getClientHintsByReferenceIds( $referenceIdsForLookup ); |
446 | } |
447 | } |
448 | |
449 | /** @inheritDoc */ |
450 | public function getQueryInfo( ?string $table = null ): array { |
451 | if ( $table === null ) { |
452 | throw new LogicException( |
453 | "This ::getQueryInfo method must be provided with the table to generate " . |
454 | "the correct query info" |
455 | ); |
456 | } |
457 | |
458 | if ( $table === self::CHANGES_TABLE ) { |
459 | $queryInfo = $this->getQueryInfoForCuChanges(); |
460 | } elseif ( $table === self::LOG_EVENT_TABLE ) { |
461 | $queryInfo = $this->getQueryInfoForCuLogEvent(); |
462 | } elseif ( $table === self::PRIVATE_LOG_EVENT_TABLE ) { |
463 | $queryInfo = $this->getQueryInfoForCuPrivateEvent(); |
464 | } |
465 | |
466 | // Apply index and IP WHERE conditions. |
467 | $queryInfo['options']['USE INDEX'] = [ |
468 | $table => $this->checkUserLookupUtils->getIndexName( $this->xfor, $table ) |
469 | ]; |
470 | $ipExpr = $this->checkUserLookupUtils->getIPTargetExpr( $this->target->getName(), $this->xfor, $table ); |
471 | if ( $ipExpr !== null ) { |
472 | $queryInfo['conds'][] = $ipExpr; |
473 | } |
474 | |
475 | return $queryInfo; |
476 | } |
477 | |
478 | /** @inheritDoc */ |
479 | protected function getQueryInfoForCuChanges(): array { |
480 | $queryInfo = [ |
481 | 'fields' => [ |
482 | 'timestamp' => 'cuc_timestamp', |
483 | 'ip' => 'cuc_ip', |
484 | 'agent' => 'cuc_agent', |
485 | 'xff' => 'cuc_xff', |
486 | 'actor' => 'cuc_actor', |
487 | 'user' => 'actor_cuc_actor.actor_user', |
488 | 'user_text' => 'actor_cuc_actor.actor_name', |
489 | ], |
490 | 'tables' => [ 'cu_changes', 'actor_cuc_actor' => 'actor' ], |
491 | 'conds' => [], |
492 | 'join_conds' => [ 'actor_cuc_actor' => [ 'JOIN', 'actor_cuc_actor.actor_id=cuc_actor' ] ], |
493 | 'options' => [], |
494 | ]; |
495 | // When reading new, only select results from cu_changes that are |
496 | // for read new (defined as those with cuc_only_for_read_old set to 0). |
497 | if ( $this->eventTableReadNew ) { |
498 | $queryInfo['conds']['cuc_only_for_read_old'] = 0; |
499 | } |
500 | // When displaying Client Hints data, add the reference type and reference ID to each row. |
501 | if ( $this->displayClientHints ) { |
502 | $queryInfo['fields']['client_hints_reference_id'] = |
503 | UserAgentClientHintsManager::IDENTIFIER_TO_COLUMN_NAME_MAP[ |
504 | UserAgentClientHintsManager::IDENTIFIER_CU_CHANGES |
505 | ]; |
506 | $queryInfo['fields']['client_hints_reference_type'] = |
507 | UserAgentClientHintsManager::IDENTIFIER_CU_CHANGES; |
508 | } |
509 | return $queryInfo; |
510 | } |
511 | |
512 | /** @inheritDoc */ |
513 | protected function getQueryInfoForCuLogEvent(): array { |
514 | $queryInfo = [ |
515 | 'fields' => [ |
516 | 'timestamp' => 'cule_timestamp', |
517 | 'ip' => 'cule_ip', |
518 | 'agent' => 'cule_agent', |
519 | 'xff' => 'cule_xff', |
520 | 'actor' => 'cule_actor', |
521 | 'user' => 'actor_cule_actor.actor_user', |
522 | 'user_text' => 'actor_cule_actor.actor_name', |
523 | ], |
524 | 'tables' => [ 'cu_log_event', 'actor_cule_actor' => 'actor' ], |
525 | 'conds' => [], |
526 | 'join_conds' => [ 'actor_cule_actor' => [ 'JOIN', 'actor_cule_actor.actor_id=cule_actor' ] ], |
527 | 'options' => [], |
528 | ]; |
529 | // When displaying Client Hints data, add the reference type and reference ID to each row. |
530 | if ( $this->displayClientHints ) { |
531 | $queryInfo['fields']['client_hints_reference_id'] = |
532 | UserAgentClientHintsManager::IDENTIFIER_TO_COLUMN_NAME_MAP[ |
533 | UserAgentClientHintsManager::IDENTIFIER_CU_LOG_EVENT |
534 | ]; |
535 | $queryInfo['fields']['client_hints_reference_type'] = |
536 | UserAgentClientHintsManager::IDENTIFIER_CU_LOG_EVENT; |
537 | } |
538 | return $queryInfo; |
539 | } |
540 | |
541 | /** @inheritDoc */ |
542 | protected function getQueryInfoForCuPrivateEvent(): array { |
543 | $queryInfo = [ |
544 | 'fields' => [ |
545 | 'timestamp' => 'cupe_timestamp', |
546 | 'ip' => 'cupe_ip', |
547 | 'agent' => 'cupe_agent', |
548 | 'xff' => 'cupe_xff', |
549 | 'actor' => 'cupe_actor', |
550 | 'user' => 'actor_cupe_actor.actor_user', |
551 | 'user_text' => 'actor_cupe_actor.actor_name', |
552 | ], |
553 | 'tables' => [ 'cu_private_event', 'actor_cupe_actor' => 'actor' ], |
554 | 'conds' => [], |
555 | 'join_conds' => [ 'actor_cupe_actor' => [ 'LEFT JOIN', 'actor_cupe_actor.actor_id=cupe_actor' ] ], |
556 | 'options' => [], |
557 | ]; |
558 | // When displaying Client Hints data, add the reference type and reference ID to each row. |
559 | if ( $this->displayClientHints ) { |
560 | $queryInfo['fields']['client_hints_reference_id'] = |
561 | UserAgentClientHintsManager::IDENTIFIER_TO_COLUMN_NAME_MAP[ |
562 | UserAgentClientHintsManager::IDENTIFIER_CU_PRIVATE_EVENT |
563 | ]; |
564 | $queryInfo['fields']['client_hints_reference_type'] = |
565 | UserAgentClientHintsManager::IDENTIFIER_CU_PRIVATE_EVENT; |
566 | } |
567 | return $queryInfo; |
568 | } |
569 | |
570 | /** @inheritDoc */ |
571 | protected function getStartBody(): string { |
572 | $s = $this->getCheckUserHelperFieldsetHTML() . $this->getNavigationBar(); |
573 | if ( $this->mResult->numRows() ) { |
574 | $s .= ( new ListToggle( $this->getOutput() ) )->getHTML(); |
575 | } |
576 | if ( $this->canPerformBlocks ) { |
577 | $s .= Xml::openElement( |
578 | 'form', |
579 | [ |
580 | 'action' => $this->getPageTitle()->getLocalURL( 'action=block' ), |
581 | 'id' => 'checkuserblock', |
582 | 'name' => 'checkuserblock', |
583 | 'class' => 'mw-htmlform-ooui mw-htmlform', |
584 | 'method' => 'post', |
585 | ] |
586 | ); |
587 | } |
588 | |
589 | $divClasses = [ 'mw-checkuser-get-users-results' ]; |
590 | |
591 | if ( $this->displayClientHints ) { |
592 | // Class used to indicate whether Client Hints are enabled |
593 | // TODO: Remove this class and old CSS code once display |
594 | // is on all wikis (T341110). |
595 | $divClasses[] = 'mw-checkuser-clienthints-enabled-temporary-class'; |
596 | } |
597 | |
598 | $s .= Xml::openElement( |
599 | 'div', |
600 | [ |
601 | 'id' => 'checkuserresults', |
602 | 'class' => implode( ' ', $divClasses ) |
603 | ] |
604 | ); |
605 | |
606 | $s .= '<ul>'; |
607 | |
608 | return $s; |
609 | } |
610 | |
611 | /** @inheritDoc */ |
612 | protected function getEndBody(): string { |
613 | $fieldset = new HTMLFieldsetCheckUser( [], $this->getContext(), '' ); |
614 | $s = '</ul></div>'; |
615 | if ( $this->mResult->numRows() ) { |
616 | $s .= ( new ListToggle( $this->getOutput() ) )->getHTML(); |
617 | } |
618 | // T314217 - cannot have forms inside of forms. |
619 | // $s .= $this->getNavigationBar(); |
620 | if ( $this->canPerformBlocks ) { |
621 | $config = $this->getConfig(); |
622 | $checkUserCAMultiLock = $config->get( 'CheckUserCAMultiLock' ); |
623 | if ( $checkUserCAMultiLock !== false ) { |
624 | if ( !ExtensionRegistry::getInstance()->isLoaded( 'CentralAuth' ) ) { |
625 | // $wgCheckUserCAMultiLock shouldn't be enabled if CA is not loaded |
626 | throw new ConfigException( '$wgCheckUserCAMultiLock requires CentralAuth extension.' ); |
627 | } |
628 | |
629 | $caUserGroups = CentralAuthUser::getInstance( $this->getUser() )->getGlobalGroups(); |
630 | // Only load the script for users in the configured global group(s) |
631 | if ( count( array_intersect( $checkUserCAMultiLock['groups'], $caUserGroups ) ) ) { |
632 | $out = $this->getOutput(); |
633 | $centralMLUrl = WikiMap::getForeignURL( |
634 | $checkUserCAMultiLock['centralDB'], |
635 | // Use canonical name instead of local name so that it works |
636 | // even if the local language is different from central wiki |
637 | 'Special:MultiLock' |
638 | ); |
639 | if ( $centralMLUrl === false ) { |
640 | throw new ConfigException( |
641 | "Could not retrieve URL for {$checkUserCAMultiLock['centralDB']}" |
642 | ); |
643 | } |
644 | $out->addJsConfigVars( 'wgCUCAMultiLockCentral', $centralMLUrl ); |
645 | $out->addModules( 'ext.checkUser' ); |
646 | } |
647 | } |
648 | |
649 | $fields = [ |
650 | 'usetag' => [ |
651 | 'type' => 'check', |
652 | 'default' => false, |
653 | 'label-message' => 'checkuser-blocktag', |
654 | 'id' => 'usetag', |
655 | 'name' => 'usetag', |
656 | 'size' => 46, |
657 | ], |
658 | 'tag' => [ |
659 | 'type' => 'text', |
660 | 'id' => 'blocktag', |
661 | 'name' => 'blocktag', |
662 | 'minlength' => 3, |
663 | ], |
664 | 'talkusetag' => [ |
665 | 'type' => 'check', |
666 | 'default' => false, |
667 | 'label-message' => 'checkuser-blocktag-talk', |
668 | 'id' => 'usettag', |
669 | 'name' => 'usettag', |
670 | ], |
671 | 'talktag' => [ |
672 | 'type' => 'text', |
673 | 'id' => 'talktag', |
674 | 'name' => 'talktag', |
675 | 'size' => 46, |
676 | 'minlength' => 3, |
677 | ], |
678 | ]; |
679 | |
680 | $fieldset->addFields( $fields ) |
681 | ->setWrapperLegendMsg( 'checkuser-massblock' ) |
682 | ->setSubmitTextMsg( 'checkuser-massblock-commit' ) |
683 | ->setSubmitId( 'checkuserblocksubmit' ) |
684 | ->setSubmitName( 'checkuserblock' ) |
685 | ->setHeaderHtml( $this->msg( 'checkuser-massblock-text' )->escaped() ); |
686 | |
687 | if ( $config->get( 'BlockAllowsUTEdit' ) ) { |
688 | $fieldset->addFields( [ |
689 | 'blocktalk' => [ |
690 | 'type' => 'check', |
691 | 'default' => false, |
692 | 'label-message' => 'checkuser-blocktalk', |
693 | 'id' => 'blocktalk', |
694 | 'name' => 'blocktalk', |
695 | ] |
696 | ] ); |
697 | } |
698 | |
699 | if ( |
700 | $this->blockPermissionCheckerFactory |
701 | ->newBlockPermissionChecker( |
702 | null, |
703 | $this->getUser() |
704 | ) |
705 | ->checkEmailPermissions() |
706 | ) { |
707 | $fieldset->addFields( [ |
708 | 'blockemail' => [ |
709 | 'type' => 'check', |
710 | 'default' => false, |
711 | 'label-message' => 'checkuser-blockemail', |
712 | 'id' => 'blockemail', |
713 | 'name' => 'blockemail', |
714 | ] |
715 | ] ); |
716 | } |
717 | |
718 | $s .= $fieldset |
719 | ->addFields( [ |
720 | 'reblock' => [ |
721 | 'type' => 'check', |
722 | 'default' => false, |
723 | 'label-message' => 'checkuser-reblock', |
724 | 'id' => 'reblock', |
725 | 'name' => 'reblock', |
726 | ], |
727 | 'reason' => [ |
728 | 'type' => 'selectandother', |
729 | 'options-message' => 'checkuser-block-reason-dropdown', |
730 | 'label-message' => 'checkuser-reason', |
731 | 'size' => 46, |
732 | 'maxlength' => 150, |
733 | 'id' => 'blockreason', |
734 | 'name' => 'blockreason', |
735 | 'cssclass' => 'ext-checkuser-checkuserblock-block-reason' |
736 | ], |
737 | ] ) |
738 | ->prepareForm() |
739 | ->getHtml( false ); |
740 | $s .= '</form>'; |
741 | } |
742 | |
743 | return $s; |
744 | } |
745 | } |