MediaWiki  master
UsersPager.php
Go to the documentation of this file.
1 <?php
38 
46 class UsersPager extends AlphabeticPager {
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 }
const NS_USER
Definition: Defines.php:66
const NS_USER_TALK
Definition: Defines.php:67
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()
getContext()
Get the base IContextSource object.
setContext(IContextSource $context)
static factory( $displayFormat, $descriptor, IContextSource $context, $messagePrefix='')
Construct a HTMLForm object for given display type.
Definition: HTMLForm.php:352
int $mLimit
The maximum number of entries to show.
Definition: IndexPager.php:96
bool $mDefaultDirection
$mDefaultDirection gives the direction to use when sorting results: DIR_ASCENDING or DIR_DESCENDING.
Definition: IndexPager.php:133
const DIR_ASCENDING
Backwards-compatible constant for $mDefaultDirection field (do not change)
Definition: IndexPager.php:78
getDatabase()
Get the Database object in use.
Definition: IndexPager.php:248
const DIR_DESCENDING
Backwards-compatible constant for $mDefaultDirection field (do not change)
Definition: IndexPager.php:80
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Definition: HookRunner.php:568
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:65
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.
static getGroupPage( $group)
Gets the title of a page describing a particular user group.
static getLink( $ugm, IContextSource $context, $format, $userName=null)
Gets a link for a user group, possibly including the expiry date if relevant.
This class is used to get a list of user.
Definition: UsersPager.php:46
bool $temporaryGroupsOnly
Definition: UsersPager.php:60
bool null $including
Definition: UsersPager.php:66
string $requestedUser
Definition: UsersPager.php:69
array[] $userGroupCache
A array with user ids as key and a array of groups as value.
Definition: UsersPager.php:51
bool $editsOnly
Definition: UsersPager.php:57
buildGroupLink( $group, $username)
Format a link to a group description page.
Definition: UsersPager.php:494
formatRow( $row)
Definition: UsersPager.php:234
__construct(IContextSource $context, HookContainer $hookContainer, LinkBatchFactory $linkBatchFactory, ILoadBalancer $loadBalancer, UserGroupManager $userGroupManager, UserIdentityLookup $userIdentityLookup, $par, $including)
Definition: UsersPager.php:94
getGroupMemberships( $user)
Get an associative array containing groups the specified user belongs to, and the relevant UserGroupM...
Definition: UsersPager.php:479
bool $creationSort
Definition: UsersPager.php:63
doBatchLookups()
Called from getBody(), before getStartBody() is called and after doQuery() was called.
Definition: UsersPager.php:295
getDefaultQuery()
Preserve group and username offset parameters when paging.
Definition: UsersPager.php:459
string $requestedGroup
Definition: UsersPager.php:54
$self
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)
const DB_REPLICA
Definition: defines.php:26
if(!isset( $args[0])) $lang