MediaWiki REL1_41
UsersPager.php
Go to the documentation of this file.
1<?php
26namespace MediaWiki\Pager;
27
28use HTMLForm;
47use stdClass;
49
58
62 protected $userGroupCache;
63
66
68 protected $editsOnly;
69
72
74 protected $creationSort;
75
77 protected $including;
78
80 protected $requestedUser;
81
82 private HookRunner $hookRunner;
83 private LinkBatchFactory $linkBatchFactory;
84 private UserGroupManager $userGroupManager;
85 private UserIdentityLookup $userIdentityLookup;
86
98 public function __construct(
99 IContextSource $context,
100 HookContainer $hookContainer,
101 LinkBatchFactory $linkBatchFactory,
102 IConnectionProvider $dbProvider,
103 UserGroupManager $userGroupManager,
104 UserIdentityLookup $userIdentityLookup,
105 $par,
107 ) {
108 $this->setContext( $context );
109
110 $request = $this->getRequest();
111 $par ??= '';
112 $parms = explode( '/', $par );
113 $symsForAll = [ '*', 'user' ];
114
115 if ( $parms[0] != '' &&
116 ( in_array( $par, $userGroupManager->listAllGroups() ) || in_array( $par, $symsForAll ) )
117 ) {
118 $this->requestedGroup = $par;
119 $un = $request->getText( 'username' );
120 } elseif ( count( $parms ) == 2 ) {
121 $this->requestedGroup = $parms[0];
122 $un = $parms[1];
123 } else {
124 $this->requestedGroup = $request->getVal( 'group' );
125 $un = ( $par != '' ) ? $par : $request->getText( 'username' );
126 }
127
128 if ( in_array( $this->requestedGroup, $symsForAll ) ) {
129 $this->requestedGroup = '';
130 }
131 $this->editsOnly = $request->getBool( 'editsOnly' );
132 $this->temporaryGroupsOnly = $request->getBool( 'temporaryGroupsOnly' );
133 $this->creationSort = $request->getBool( 'creationSort' );
134 $this->including = $including;
135 $this->mDefaultDirection = $request->getBool( 'desc' )
138
139 $this->requestedUser = '';
140
141 if ( $un != '' ) {
142 $username = Title::makeTitleSafe( NS_USER, $un );
143
144 if ( $username !== null ) {
145 $this->requestedUser = $username->getText();
146 }
147 }
148
149 // Set database before parent constructor to avoid setting it there with wfGetDB
150 $this->mDb = $dbProvider->getReplicaDatabase();
151 parent::__construct();
152 $this->userGroupManager = $userGroupManager;
153 $this->hookRunner = new HookRunner( $hookContainer );
154 $this->linkBatchFactory = $linkBatchFactory;
155 $this->userIdentityLookup = $userIdentityLookup;
156 }
157
161 public function getIndexField() {
162 return $this->creationSort ? 'user_id' : 'user_name';
163 }
164
168 public function getQueryInfo() {
169 $dbr = $this->getDatabase();
170 $conds = [];
171
172 // Don't show hidden names
173 if ( !$this->canSeeHideuser() ) {
174 $conds['ipb_deleted'] = [ null, 0 ];
175 }
176
177 $options = [];
178
179 if ( $this->requestedGroup != '' || $this->temporaryGroupsOnly ) {
180 $conds[] = 'ug_expiry >= ' . $dbr->addQuotes( $dbr->timestamp() ) .
181 ( !$this->temporaryGroupsOnly ? ' OR ug_expiry IS NULL' : '' );
182 }
183
184 if ( $this->requestedGroup != '' ) {
185 $conds['ug_group'] = $this->requestedGroup;
186 }
187
188 if ( $this->requestedUser != '' ) {
189 # Sorted either by account creation or name
190 if ( $this->creationSort ) {
191 $userIdentity = $this->userIdentityLookup->getUserIdentityByName( $this->requestedUser );
192 if ( $userIdentity && $userIdentity->isRegistered() ) {
193 $conds[] = 'user_id >= ' . $userIdentity->getId();
194 }
195 } else {
196 $conds[] = 'user_name >= ' . $dbr->addQuotes( $this->requestedUser );
197 }
198 }
199
200 if ( $this->editsOnly ) {
201 $conds[] = 'user_editcount > 0';
202 }
203
204 $options['GROUP BY'] = $this->creationSort ? 'user_id' : 'user_name';
205
206 $query = [
207 'tables' => [ 'user', 'user_groups', 'ipblocks' ],
208 'fields' => [
209 'user_name' => $this->creationSort ? 'MAX(user_name)' : 'user_name',
210 'user_id' => $this->creationSort ? 'user_id' : 'MAX(user_id)',
211 'edits' => 'MAX(user_editcount)',
212 'creation' => 'MIN(user_registration)',
213 'ipb_deleted' => 'MAX(ipb_deleted)', // block/hide status
214 'ipb_sitewide' => 'MAX(ipb_sitewide)'
215 ],
216 'options' => $options,
217 'join_conds' => [
218 'user_groups' => [ 'LEFT JOIN', 'user_id=ug_user' ],
219 'ipblocks' => [
220 'LEFT JOIN', [
221 'user_id=ipb_user',
222 'ipb_auto' => 0
223 ]
224 ],
225 ],
226 'conds' => $conds
227 ];
228
229 $this->hookRunner->onSpecialListusersQueryInfo( $this, $query );
230
231 return $query;
232 }
233
238 public function formatRow( $row ) {
239 if ( $row->user_id == 0 ) { # T18487
240 return '';
241 }
242
243 $userName = $row->user_name;
244
245 $ulinks = Linker::userLink( $row->user_id, $userName );
246 $ulinks .= Linker::userToolLinksRedContribs(
247 $row->user_id,
248 $userName,
249 (int)$row->edits,
250 // don't render parentheses in HTML markup (CSS will provide)
251 false
252 );
253
254 $lang = $this->getLanguage();
255
256 $groups = '';
257 $userIdentity = new UserIdentityValue( intval( $row->user_id ), $userName );
258 $ugms = $this->getGroupMemberships( $userIdentity );
259
260 if ( !$this->including && count( $ugms ) > 0 ) {
261 $list = [];
262 foreach ( $ugms as $ugm ) {
263 $list[] = $this->buildGroupLink( $ugm, $userName );
264 }
265 $groups = $lang->commaList( $list );
266 }
267
268 $item = $lang->specialList( $ulinks, $groups );
269
270 if ( $row->ipb_deleted ) {
271 $item = "<span class=\"deleted\">$item</span>";
272 }
273
274 $edits = '';
275 if ( !$this->including && $this->getConfig()->get( MainConfigNames::Edititis ) ) {
276 $count = $this->msg( 'usereditcount' )->numParams( $row->edits )->escaped();
277 $edits = $this->msg( 'word-separator' )->escaped() . $this->msg( 'brackets', $count )->escaped();
278 }
279
280 $created = '';
281 # Some rows may be null
282 if ( !$this->including && $row->creation ) {
283 $user = $this->getUser();
284 $d = $lang->userDate( $row->creation, $user );
285 $t = $lang->userTime( $row->creation, $user );
286 $created = $this->msg( 'usercreated', $d, $t, $row->user_name )->escaped();
287 $created = ' ' . $this->msg( 'parentheses' )->rawParams( $created )->escaped();
288 }
289
290 $blocked = $row->ipb_deleted !== null && $row->ipb_sitewide === '1' ?
291 ' ' . $this->msg( 'listusers-blocked', $userName )->escaped() :
292 '';
293
294 $this->hookRunner->onSpecialListusersFormatRow( $item, $row );
295
296 return Html::rawElement( 'li', [], "{$item}{$edits}{$created}{$blocked}" );
297 }
298
299 protected function doBatchLookups() {
300 $batch = $this->linkBatchFactory->newLinkBatch();
301 $userIds = [];
302 # Give some pointers to make user links
303 foreach ( $this->mResult as $row ) {
304 $batch->add( NS_USER, $row->user_name );
305 $batch->add( NS_USER_TALK, $row->user_name );
306 $userIds[] = (int)$row->user_id;
307 }
308
309 // Lookup groups for all the users
310 $queryBuilder = $this->userGroupManager->newQueryBuilder( $this->getDatabase() );
311 $groupRes = $queryBuilder->where( [ 'ug_user' => $userIds ] )
312 ->caller( __METHOD__ )
313 ->fetchResultSet();
314 $cache = [];
315 $groups = [];
316 foreach ( $groupRes as $row ) {
317 $ugm = $this->userGroupManager->newGroupMembershipFromRow( $row );
318 if ( !$ugm->isExpired() ) {
319 $cache[$row->ug_user][$row->ug_group] = $ugm;
320 $groups[$row->ug_group] = true;
321 }
322 }
323
324 // Give extensions a chance to add things like global user group data
325 // into the cache array to ensure proper output later on
326 $this->hookRunner->onUsersPagerDoBatchLookups( $this->getDatabase(), $userIds, $cache, $groups );
327
328 $this->userGroupCache = $cache;
329
330 // Add page of groups to link batch
331 foreach ( $groups as $group => $unused ) {
332 $groupPage = UserGroupMembership::getGroupPage( $group );
333 if ( $groupPage ) {
334 $batch->addObj( $groupPage );
335 }
336 }
337
338 $batch->execute();
339 $this->mResult->rewind();
340 }
341
345 public function getPageHeader() {
346 $self = explode( '/', $this->getTitle()->getPrefixedDBkey(), 2 )[0];
347
348 $groupOptions = [ $this->msg( 'group-all' )->text() => '' ];
349 foreach ( $this->getAllGroups() as $group => $groupText ) {
350 $groupOptions[ $groupText ] = $group;
351 }
352
353 $formDescriptor = [
354 'user' => [
355 'class' => HTMLUserTextField::class,
356 'label' => $this->msg( 'listusersfrom' )->text(),
357 'name' => 'username',
358 'default' => $this->requestedUser,
359 ],
360 'dropdown' => [
361 'label' => $this->msg( 'group' )->text(),
362 'name' => 'group',
363 'default' => $this->requestedGroup,
364 'class' => HTMLSelectField::class,
365 'options' => $groupOptions,
366 ],
367 'editsOnly' => [
368 'type' => 'check',
369 'label' => $this->msg( 'listusers-editsonly' )->text(),
370 'name' => 'editsOnly',
371 'id' => 'editsOnly',
372 'default' => $this->editsOnly
373 ],
374 'temporaryGroupsOnly' => [
375 'type' => 'check',
376 'label' => $this->msg( 'listusers-temporarygroupsonly' )->text(),
377 'name' => 'temporaryGroupsOnly',
378 'id' => 'temporaryGroupsOnly',
379 'default' => $this->temporaryGroupsOnly
380 ],
381 'creationSort' => [
382 'type' => 'check',
383 'label' => $this->msg( 'listusers-creationsort' )->text(),
384 'name' => 'creationSort',
385 'id' => 'creationSort',
386 'default' => $this->creationSort
387 ],
388 'desc' => [
389 'type' => 'check',
390 'label' => $this->msg( 'listusers-desc' )->text(),
391 'name' => 'desc',
392 'id' => 'desc',
393 'default' => $this->mDefaultDirection
394 ],
395 'limithiddenfield' => [
396 'class' => HTMLHiddenField::class,
397 'name' => 'limit',
398 'default' => $this->mLimit
399 ]
400 ];
401
402 $beforeSubmitButtonHookOut = '';
403 $this->hookRunner->onSpecialListusersHeaderForm( $this, $beforeSubmitButtonHookOut );
404
405 if ( $beforeSubmitButtonHookOut !== '' ) {
406 $formDescriptor[ 'beforeSubmitButtonHookOut' ] = [
407 'class' => HTMLInfoField::class,
408 'raw' => true,
409 'default' => $beforeSubmitButtonHookOut
410 ];
411 }
412
413 $formDescriptor[ 'submit' ] = [
414 'class' => HTMLSubmitField::class,
415 'buttonlabel-message' => 'listusers-submit',
416 ];
417
418 $beforeClosingFieldsetHookOut = '';
419 $this->hookRunner->onSpecialListusersHeader( $this, $beforeClosingFieldsetHookOut );
420
421 if ( $beforeClosingFieldsetHookOut !== '' ) {
422 $formDescriptor[ 'beforeClosingFieldsetHookOut' ] = [
423 'class' => HTMLInfoField::class,
424 'raw' => true,
425 'default' => $beforeClosingFieldsetHookOut
426 ];
427 }
428
429 $htmlForm = HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() );
430 $htmlForm
431 ->setMethod( 'get' )
432 ->setTitle( Title::newFromText( $self ) )
433 ->setId( 'mw-listusers-form' )
434 ->setFormIdentifier( 'mw-listusers-form' )
435 ->suppressDefaultSubmit()
436 ->setWrapperLegendMsg( 'listusers' );
437 return $htmlForm->prepareForm()->getHTML( true );
438 }
439
440 protected function canSeeHideuser() {
441 return $this->getAuthority()->isAllowed( 'hideuser' );
442 }
443
448 private function getAllGroups() {
449 $result = [];
450 $lang = $this->getLanguage();
451 foreach ( $this->userGroupManager->listAllGroups() as $group ) {
452 $result[$group] = $lang->getGroupName( $group );
453 }
454 asort( $result );
455
456 return $result;
457 }
458
463 public function getDefaultQuery() {
464 $query = parent::getDefaultQuery();
465 if ( $this->requestedGroup != '' ) {
466 $query['group'] = $this->requestedGroup;
467 }
468 if ( $this->requestedUser != '' ) {
469 $query['username'] = $this->requestedUser;
470 }
471 $this->hookRunner->onSpecialListusersDefaultQuery( $this, $query );
472
473 return $query;
474 }
475
483 protected function getGroupMemberships( $user ) {
484 if ( $this->userGroupCache === null ) {
485 return $this->userGroupManager->getUserGroupMemberships( $user );
486 } else {
487 return $this->userGroupCache[$user->getId()] ?? [];
488 }
489 }
490
498 protected function buildGroupLink( $group, $username ) {
499 return UserGroupMembership::getLinkHTML( $group, $this->getContext(), $username );
500 }
501}
502
507class_alias( UsersPager::class, 'UsersPager' );
const NS_USER
Definition Defines.php:66
const NS_USER_TALK
Definition Defines.php:67
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
getContext()
Get the base IContextSource object.
setContext(IContextSource $context)
Object handling generic submission, CSRF protection, layout and other logic for UI forms in a reusabl...
Definition HTMLForm.php:158
An information field (text blob), not a proper input.
A select dropdown field.
Add a submit button inline in the form (as opposed to HTMLForm::addButton(), which will add it at the...
Implements a text input field for user names.
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
This class is a collection of static functions that serve two purposes:
Definition Html.php:57
Some internal bits split of from Skin.php.
Definition Linker.php:65
A class containing constants representing the names of configuration variables.
const Edititis
Name constant for the Edititis setting, for use with Config::get()
IndexPager with an alphabetic list and a formatted navigation bar.
getDatabase()
Get the Database object in use.
int $mLimit
The maximum number of entries to show.
const DIR_ASCENDING
Backwards-compatible constant for $mDefaultDirection field (do not change)
const DIR_DESCENDING
Backwards-compatible constant for $mDefaultDirection field (do not change)
bool $mDefaultDirection
$mDefaultDirection gives the direction to use when sorting results: DIR_ASCENDING or DIR_DESCENDING.
This class is used to get a list of user.
__construct(IContextSource $context, HookContainer $hookContainer, LinkBatchFactory $linkBatchFactory, IConnectionProvider $dbProvider, UserGroupManager $userGroupManager, UserIdentityLookup $userIdentityLookup, $par, $including)
getGroupMemberships( $user)
Get an associative array containing groups the specified user belongs to, and the relevant UserGroupM...
buildGroupLink( $group, $username)
Format a link to a group description page.
getDefaultQuery()
Preserve group and username offset parameters when paging.
doBatchLookups()
Called from getBody(), before getStartBody() is called and after doQuery() was called.
array[] $userGroupCache
A array with user ids as key and a array of groups as value.
Represents a title within MediaWiki.
Definition Title.php:76
listAllGroups()
Return the set of defined explicit groups.
Represents a "user group membership" – a specific instance of a user belonging to a group.
Value object representing a user's identity.
Interface for objects which can provide a MediaWiki context on request.
Interface for objects representing user identity.
Provide primary and replica IDatabase connections.
getReplicaDatabase( $domain=false, $group=null)
Get connection to a replica database.