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