MediaWiki REL1_37
UsersPager.php
Go to the documentation of this file.
1<?php
33
42
46 protected $userGroupCache;
47
50
52 protected $editsOnly;
53
56
58 protected $creationSort;
59
61 protected $including;
62
64 protected $requestedUser;
65
68
70 private $hookRunner;
71
74
85 public function __construct(
86 ?IContextSource $context,
87 $par,
88 $including,
89 LinkBatchFactory $linkBatchFactory,
90 HookContainer $hookContainer,
91 ILoadBalancer $loadBalancer,
92 UserGroupManager $userGroupManager
93 ) {
94 if ( $context ) {
95 $this->setContext( $context );
96 }
97
98 $request = $this->getRequest();
99 $par = $par ?? '';
100 $parms = explode( '/', $par );
101 $symsForAll = [ '*', 'user' ];
102
103 if ( $parms[0] != '' &&
104 ( in_array( $par, $userGroupManager->listAllGroups() ) || in_array( $par, $symsForAll ) )
105 ) {
106 $this->requestedGroup = $par;
107 $un = $request->getText( 'username' );
108 } elseif ( count( $parms ) == 2 ) {
109 $this->requestedGroup = $parms[0];
110 $un = $parms[1];
111 } else {
112 $this->requestedGroup = $request->getVal( 'group' );
113 $un = ( $par != '' ) ? $par : $request->getText( 'username' );
114 }
115
116 if ( in_array( $this->requestedGroup, $symsForAll ) ) {
117 $this->requestedGroup = '';
118 }
119 $this->editsOnly = $request->getBool( 'editsOnly' );
120 $this->temporaryGroupsOnly = $request->getBool( 'temporaryGroupsOnly' );
121 $this->creationSort = $request->getBool( 'creationSort' );
122 $this->including = $including;
123 $this->mDefaultDirection = $request->getBool( 'desc' )
126
127 $this->requestedUser = '';
128
129 if ( $un != '' ) {
130 $username = Title::makeTitleSafe( NS_USER, $un );
131
132 if ( $username !== null ) {
133 $this->requestedUser = $username->getText();
134 }
135 }
136
137 // Set database before parent constructor to avoid setting it there with wfGetDB
138 $this->mDb = $loadBalancer->getConnectionRef( ILoadBalancer::DB_REPLICA );
139 parent::__construct();
140 $this->linkBatchFactory = $linkBatchFactory;
141 $this->hookRunner = new HookRunner( $hookContainer );
142 $this->userGroupManager = $userGroupManager;
143 }
144
148 public function getIndexField() {
149 return $this->creationSort ? 'user_id' : 'user_name';
150 }
151
155 public function getQueryInfo() {
156 $dbr = $this->getDatabase();
157 $conds = [];
158
159 // Don't show hidden names
160 if ( !$this->canSeeHideuser() ) {
161 $conds[] = 'ipb_deleted IS NULL OR ipb_deleted = 0';
162 }
163
164 $options = [];
165
166 if ( $this->requestedGroup != '' || $this->temporaryGroupsOnly ) {
167 $conds[] = 'ug_expiry >= ' . $dbr->addQuotes( $dbr->timestamp() ) .
168 ( !$this->temporaryGroupsOnly ? ' OR ug_expiry IS NULL' : '' );
169 }
170
171 if ( $this->requestedGroup != '' ) {
172 $conds['ug_group'] = $this->requestedGroup;
173 }
174
175 if ( $this->requestedUser != '' ) {
176 # Sorted either by account creation or name
177 if ( $this->creationSort ) {
178 $conds[] = 'user_id >= ' . intval( User::idFromName( $this->requestedUser ) );
179 } else {
180 $conds[] = 'user_name >= ' . $dbr->addQuotes( $this->requestedUser );
181 }
182 }
183
184 if ( $this->editsOnly ) {
185 $conds[] = 'user_editcount > 0';
186 }
187
188 $options['GROUP BY'] = $this->creationSort ? 'user_id' : 'user_name';
189
190 $query = [
191 'tables' => [ 'user', 'user_groups', 'ipblocks' ],
192 'fields' => [
193 'user_name' => $this->creationSort ? 'MAX(user_name)' : 'user_name',
194 'user_id' => $this->creationSort ? 'user_id' : 'MAX(user_id)',
195 'edits' => 'MAX(user_editcount)',
196 'creation' => 'MIN(user_registration)',
197 'ipb_deleted' => 'MAX(ipb_deleted)', // block/hide status
198 'ipb_sitewide' => 'MAX(ipb_sitewide)'
199 ],
200 'options' => $options,
201 'join_conds' => [
202 'user_groups' => [ 'LEFT JOIN', 'user_id=ug_user' ],
203 'ipblocks' => [
204 'LEFT JOIN', [
205 'user_id=ipb_user',
206 'ipb_auto' => 0
207 ]
208 ],
209 ],
210 'conds' => $conds
211 ];
212
213 $this->hookRunner->onSpecialListusersQueryInfo( $this, $query );
214
215 return $query;
216 }
217
222 public function formatRow( $row ) {
223 if ( $row->user_id == 0 ) { # T18487
224 return '';
225 }
226
227 $userName = $row->user_name;
228
229 $ulinks = Linker::userLink( $row->user_id, $userName );
231 $row->user_id,
232 $userName,
233 (int)$row->edits,
234 // don't render parentheses in HTML markup (CSS will provide)
235 false
236 );
237
238 $lang = $this->getLanguage();
239
240 $groups = '';
241 $userIdentity = new UserIdentityValue( intval( $row->user_id ), $userName );
242 $ugms = $this->getGroupMemberships( $userIdentity );
243
244 if ( !$this->including && count( $ugms ) > 0 ) {
245 $list = [];
246 foreach ( $ugms as $ugm ) {
247 $list[] = $this->buildGroupLink( $ugm, $userName );
248 }
249 $groups = $lang->commaList( $list );
250 }
251
252 $item = $lang->specialList( $ulinks, $groups );
253
254 if ( $row->ipb_deleted ) {
255 $item = "<span class=\"deleted\">$item</span>";
256 }
257
258 $edits = '';
259 if ( !$this->including && $this->getConfig()->get( 'Edititis' ) ) {
260 $count = $this->msg( 'usereditcount' )->numParams( $row->edits )->escaped();
261 $edits = $this->msg( 'word-separator' )->escaped() . $this->msg( 'brackets', $count )->escaped();
262 }
263
264 $created = '';
265 # Some rows may be null
266 if ( !$this->including && $row->creation ) {
267 $user = $this->getUser();
268 $d = $lang->userDate( $row->creation, $user );
269 $t = $lang->userTime( $row->creation, $user );
270 $created = $this->msg( 'usercreated', $d, $t, $row->user_name )->escaped();
271 $created = ' ' . $this->msg( 'parentheses' )->rawParams( $created )->escaped();
272 }
273
274 $blocked = $row->ipb_deleted !== null && $row->ipb_sitewide === '1' ?
275 ' ' . $this->msg( 'listusers-blocked', $userName )->escaped() :
276 '';
277
278 $this->hookRunner->onSpecialListusersFormatRow( $item, $row );
279
280 return Html::rawElement( 'li', [], "{$item}{$edits}{$created}{$blocked}" );
281 }
282
283 protected function doBatchLookups() {
284 $batch = $this->linkBatchFactory->newLinkBatch();
285 $userIds = [];
286 # Give some pointers to make user links
287 foreach ( $this->mResult as $row ) {
288 $batch->add( NS_USER, $row->user_name );
289 $batch->add( NS_USER_TALK, $row->user_name );
290 $userIds[] = $row->user_id;
291 }
292
293 // Lookup groups for all the users
294 $dbr = $this->getDatabase();
295 $groupsQueryInfo = $this->userGroupManager->getQueryInfo();
296 $groupRes = $dbr->select(
297 $groupsQueryInfo['tables'],
298 $groupsQueryInfo['fields'],
299 [ 'ug_user' => $userIds ],
300 __METHOD__,
301 $groupsQueryInfo['joins']
302 );
303 $cache = [];
304 $groups = [];
305 foreach ( $groupRes as $row ) {
306 $ugm = $this->userGroupManager->newGroupMembershipFromRow( $row );
307 if ( !$ugm->isExpired() ) {
308 $cache[$row->ug_user][$row->ug_group] = $ugm;
309 $groups[$row->ug_group] = true;
310 }
311 }
312
313 // Give extensions a chance to add things like global user group data
314 // into the cache array to ensure proper output later on
315 $this->hookRunner->onUsersPagerDoBatchLookups( $dbr, $userIds, $cache, $groups );
316
317 $this->userGroupCache = $cache;
318
319 // Add page of groups to link batch
320 foreach ( $groups as $group => $unused ) {
321 $groupPage = UserGroupMembership::getGroupPage( $group );
322 if ( $groupPage ) {
323 $batch->addObj( $groupPage );
324 }
325 }
326
327 $batch->execute();
328 $this->mResult->rewind();
329 }
330
334 public function getPageHeader() {
335 $self = explode( '/', $this->getTitle()->getPrefixedDBkey(), 2 )[0];
336
337 $groupOptions = [ $this->msg( 'group-all' )->text() => '' ];
338 foreach ( $this->getAllGroups() as $group => $groupText ) {
339 $groupOptions[ $groupText ] = $group;
340 }
341
342 $formDescriptor = [
343 'user' => [
344 'class' => HTMLUserTextField::class,
345 'label' => $this->msg( 'listusersfrom' )->text(),
346 'name' => 'username',
347 'default' => $this->requestedUser,
348 ],
349 'dropdown' => [
350 'label' => $this->msg( 'group' )->text(),
351 'name' => 'group',
352 'default' => $this->requestedGroup,
353 'class' => HTMLSelectField::class,
354 'options' => $groupOptions,
355 ],
356 'editsOnly' => [
357 'type' => 'check',
358 'label' => $this->msg( 'listusers-editsonly' )->text(),
359 'name' => 'editsOnly',
360 'id' => 'editsOnly',
361 'default' => $this->editsOnly
362 ],
363 'temporaryGroupsOnly' => [
364 'type' => 'check',
365 'label' => $this->msg( 'listusers-temporarygroupsonly' )->text(),
366 'name' => 'temporaryGroupsOnly',
367 'id' => 'temporaryGroupsOnly',
368 'default' => $this->temporaryGroupsOnly
369 ],
370 'creationSort' => [
371 'type' => 'check',
372 'label' => $this->msg( 'listusers-creationsort' )->text(),
373 'name' => 'creationSort',
374 'id' => 'creationSort',
375 'default' => $this->creationSort
376 ],
377 'desc' => [
378 'type' => 'check',
379 'label' => $this->msg( 'listusers-desc' )->text(),
380 'name' => 'desc',
381 'id' => 'desc',
382 'default' => $this->mDefaultDirection
383 ],
384 'limithiddenfield' => [
385 'class' => HTMLHiddenField::class,
386 'name' => 'limit',
387 'default' => $this->mLimit
388 ]
389 ];
390
391 $beforeSubmitButtonHookOut = '';
392 $this->hookRunner->onSpecialListusersHeaderForm( $this, $beforeSubmitButtonHookOut );
393
394 if ( $beforeSubmitButtonHookOut !== '' ) {
395 $formDescriptor[ 'beforeSubmitButtonHookOut' ] = [
396 'class' => HTMLInfoField::class,
397 'raw' => true,
398 'default' => $beforeSubmitButtonHookOut
399 ];
400 }
401
402 $formDescriptor[ 'submit' ] = [
403 'class' => HTMLSubmitField::class,
404 'buttonlabel-message' => 'listusers-submit',
405 ];
406
407 $beforeClosingFieldsetHookOut = '';
408 $this->hookRunner->onSpecialListusersHeader( $this, $beforeClosingFieldsetHookOut );
409
410 if ( $beforeClosingFieldsetHookOut !== '' ) {
411 $formDescriptor[ 'beforeClosingFieldsetHookOut' ] = [
412 'class' => HTMLInfoField::class,
413 'raw' => true,
414 'default' => $beforeClosingFieldsetHookOut
415 ];
416 }
417
418 $htmlForm = HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() );
419 $htmlForm
420 ->setMethod( 'get' )
421 ->setAction( Title::newFromText( $self )->getLocalURL() )
422 ->setId( 'mw-listusers-form' )
423 ->setFormIdentifier( 'mw-listusers-form' )
424 ->suppressDefaultSubmit()
425 ->setWrapperLegendMsg( 'listusers' );
426 return $htmlForm->prepareForm()->getHTML( true );
427 }
428
429 protected function canSeeHideuser() {
430 return $this->getAuthority()->isAllowed( 'hideuser' );
431 }
432
437 private function getAllGroups() {
438 $result = [];
439 foreach ( $this->userGroupManager->listAllGroups() as $group ) {
440 $result[$group] = UserGroupMembership::getGroupName( $group );
441 }
442 asort( $result );
443
444 return $result;
445 }
446
451 public function getDefaultQuery() {
452 $query = parent::getDefaultQuery();
453 if ( $this->requestedGroup != '' ) {
454 $query['group'] = $this->requestedGroup;
455 }
456 if ( $this->requestedUser != '' ) {
457 $query['username'] = $this->requestedUser;
458 }
459 $this->hookRunner->onSpecialListusersDefaultQuery( $this, $query );
460
461 return $query;
462 }
463
471 protected function getGroupMemberships( $user ) {
472 if ( $this->userGroupCache === null ) {
473 return $this->userGroupManager->getUserGroupMemberships( $user );
474 } else {
475 return $this->userGroupCache[$user->getId()] ?? [];
476 }
477 }
478
486 protected function buildGroupLink( $group, $username ) {
487 return UserGroupMembership::getLink( $group, $this->getContext(), 'html', $username );
488 }
489}
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)
static userLink( $userId, $userName, $altUserName=false)
Make user link (or user contributions for unregistered users)
Definition Linker.php:1064
static userToolLinksRedContribs( $userId, $userText, $edits=null, $useParentheses=true)
Alias for userToolLinks( $userId, $userText, true );.
Definition Linker.php:1190
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
listAllGroups()
Return the set of defined explicit groups.
Value object representing a user's identity.
static idFromName( $name, $flags=self::READ_NORMAL)
Get database id given a user name.
Definition User.php:938
This class is used to get a list of user.
__construct(?IContextSource $context, $par, $including, LinkBatchFactory $linkBatchFactory, HookContainer $hookContainer, ILoadBalancer $loadBalancer, UserGroupManager $userGroupManager)
getAllGroups()
Get a list of all explicit groups.
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.
HookRunner $hookRunner
formatRow( $row)
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.
LinkBatchFactory $linkBatchFactory
UserGroupManager $userGroupManager
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.
Database cluster connection, tracking, load balancing, and transaction manager interface.
getConnectionRef( $i, $groups=[], $domain=false, $flags=0)
Get a live database handle reference for a server index.
$cache
Definition mcc.php:33
if(!isset( $args[0])) $lang