MediaWiki  1.34.0
ApiQueryUsers.php
Go to the documentation of this file.
1 <?php
24 
30 class ApiQueryUsers extends ApiQueryBase {
32 
34 
40  protected static $publicProps = [
41  // everything except 'blockinfo' which might show hidden records if the user
42  // making the request has the appropriate permissions
43  'groups',
44  'groupmemberships',
45  'implicitgroups',
46  'rights',
47  'editcount',
48  'registration',
49  'emailable',
50  'gender',
51  'centralids',
52  'cancreate',
53  ];
54 
55  public function __construct( ApiQuery $query, $moduleName ) {
56  parent::__construct( $query, $moduleName, 'us' );
57  }
58 
66  protected function getTokenFunctions() {
67  // Don't call the hooks twice
68  if ( isset( $this->tokenFunctions ) ) {
69  return $this->tokenFunctions;
70  }
71 
72  // If we're in a mode that breaks the same-origin policy, no tokens can
73  // be obtained
74  if ( $this->lacksSameOriginSecurity() ) {
75  return [];
76  }
77 
78  $this->tokenFunctions = [
79  'userrights' => [ self::class, 'getUserrightsToken' ],
80  ];
81  Hooks::run( 'APIQueryUsersTokens', [ &$this->tokenFunctions ] );
82 
83  return $this->tokenFunctions;
84  }
85 
91  public static function getUserrightsToken( $user ) {
92  global $wgUser;
93 
94  // Since the permissions check for userrights is non-trivial,
95  // don't bother with it here
96  return $wgUser->getEditToken( $user->getName() );
97  }
98 
99  public function execute() {
100  $db = $this->getDB();
101  $commentStore = CommentStore::getStore();
102 
103  $params = $this->extractRequestParams();
104  $this->requireMaxOneParameter( $params, 'userids', 'users' );
105 
106  if ( !is_null( $params['prop'] ) ) {
107  $this->prop = array_flip( $params['prop'] );
108  } else {
109  $this->prop = [];
110  }
111  $useNames = !is_null( $params['users'] );
112 
113  $users = (array)$params['users'];
114  $userids = (array)$params['userids'];
115 
116  $goodNames = $done = [];
117  $result = $this->getResult();
118  // Canonicalize user names
119  foreach ( $users as $u ) {
120  $n = User::getCanonicalName( $u );
121  if ( $n === false || $n === '' ) {
122  $vals = [ 'name' => $u, 'invalid' => true ];
123  $fit = $result->addValue( [ 'query', $this->getModuleName() ],
124  null, $vals );
125  if ( !$fit ) {
126  $this->setContinueEnumParameter( 'users',
127  implode( '|', array_diff( $users, $done ) ) );
128  $goodNames = [];
129  break;
130  }
131  $done[] = $u;
132  } else {
133  $goodNames[] = $n;
134  }
135  }
136 
137  if ( $useNames ) {
138  $parameters = &$goodNames;
139  } else {
140  $parameters = &$userids;
141  }
142 
143  $result = $this->getResult();
144 
145  if ( count( $parameters ) ) {
146  $userQuery = User::getQueryInfo();
147  $this->addTables( $userQuery['tables'] );
148  $this->addFields( $userQuery['fields'] );
149  $this->addJoinConds( $userQuery['joins'] );
150  if ( $useNames ) {
151  $this->addWhereFld( 'user_name', $goodNames );
152  } else {
153  $this->addWhereFld( 'user_id', $userids );
154  }
155 
156  $this->addBlockInfoToQuery( isset( $this->prop['blockinfo'] ) );
157 
158  $data = [];
159  $res = $this->select( __METHOD__ );
160  $this->resetQueryParams();
161 
162  // get user groups if needed
163  if ( isset( $this->prop['groups'] ) || isset( $this->prop['rights'] ) ) {
164  $userGroups = [];
165 
166  $this->addTables( 'user' );
167  if ( $useNames ) {
168  $this->addWhereFld( 'user_name', $goodNames );
169  } else {
170  $this->addWhereFld( 'user_id', $userids );
171  }
172 
173  $this->addTables( 'user_groups' );
174  $this->addJoinConds( [ 'user_groups' => [ 'JOIN', 'ug_user=user_id' ] ] );
175  $this->addFields( [ 'user_name' ] );
177  $this->addWhere( 'ug_expiry IS NULL OR ug_expiry >= ' .
178  $db->addQuotes( $db->timestamp() ) );
179  $userGroupsRes = $this->select( __METHOD__ );
180 
181  foreach ( $userGroupsRes as $row ) {
182  $userGroups[$row->user_name][] = $row;
183  }
184  }
185 
186  foreach ( $res as $row ) {
187  // create user object and pass along $userGroups if set
188  // that reduces the number of database queries needed in User dramatically
189  if ( !isset( $userGroups ) ) {
190  $user = User::newFromRow( $row );
191  } else {
192  if ( !isset( $userGroups[$row->user_name] ) || !is_array( $userGroups[$row->user_name] ) ) {
193  $userGroups[$row->user_name] = [];
194  }
195  $user = User::newFromRow( $row, [ 'user_groups' => $userGroups[$row->user_name] ] );
196  }
197  if ( $useNames ) {
198  $key = $user->getName();
199  } else {
200  $key = $user->getId();
201  }
202  $data[$key]['userid'] = $user->getId();
203  $data[$key]['name'] = $user->getName();
204 
205  if ( isset( $this->prop['editcount'] ) ) {
206  $data[$key]['editcount'] = $user->getEditCount();
207  }
208 
209  if ( isset( $this->prop['registration'] ) ) {
210  $data[$key]['registration'] = wfTimestampOrNull( TS_ISO_8601, $user->getRegistration() );
211  }
212 
213  if ( isset( $this->prop['groups'] ) ) {
214  $data[$key]['groups'] = $user->getEffectiveGroups();
215  }
216 
217  if ( isset( $this->prop['groupmemberships'] ) ) {
218  $data[$key]['groupmemberships'] = array_map( function ( $ugm ) {
219  return [
220  'group' => $ugm->getGroup(),
221  'expiry' => ApiResult::formatExpiry( $ugm->getExpiry() ),
222  ];
223  }, $user->getGroupMemberships() );
224  }
225 
226  if ( isset( $this->prop['implicitgroups'] ) ) {
227  $data[$key]['implicitgroups'] = $user->getAutomaticGroups();
228  }
229 
230  if ( isset( $this->prop['rights'] ) ) {
231  $data[$key]['rights'] = $this->getPermissionManager()
232  ->getUserPermissions( $user );
233  }
234  if ( $row->ipb_deleted ) {
235  $data[$key]['hidden'] = true;
236  }
237  if ( isset( $this->prop['blockinfo'] ) && !is_null( $row->ipb_by_text ) ) {
238  $data[$key] += $this->getBlockDetails( DatabaseBlock::newFromRow( $row ) );
239  }
240 
241  if ( isset( $this->prop['emailable'] ) ) {
242  $data[$key]['emailable'] = $user->canReceiveEmail();
243  }
244 
245  if ( isset( $this->prop['gender'] ) ) {
246  $gender = $user->getOption( 'gender' );
247  if ( strval( $gender ) === '' ) {
248  $gender = 'unknown';
249  }
250  $data[$key]['gender'] = $gender;
251  }
252 
253  if ( isset( $this->prop['centralids'] ) ) {
255  $this->getConfig(), $user, $params['attachedwiki']
256  );
257  }
258 
259  if ( !is_null( $params['token'] ) ) {
261  foreach ( $params['token'] as $t ) {
262  $val = call_user_func( $tokenFunctions[$t], $user );
263  if ( $val === false ) {
264  $this->addWarning( [ 'apiwarn-tokennotallowed', $t ] );
265  } else {
266  $data[$key][$t . 'token'] = $val;
267  }
268  }
269  }
270  }
271  }
272 
273  $context = $this->getContext();
274  // Second pass: add result data to $retval
275  foreach ( $parameters as $u ) {
276  if ( !isset( $data[$u] ) ) {
277  if ( $useNames ) {
278  $data[$u] = [ 'name' => $u ];
279  $urPage = new UserrightsPage;
280  $urPage->setContext( $context );
281 
282  $iwUser = $urPage->fetchUser( $u );
283 
284  if ( $iwUser instanceof UserRightsProxy ) {
285  $data[$u]['interwiki'] = true;
286 
287  if ( !is_null( $params['token'] ) ) {
289 
290  foreach ( $params['token'] as $t ) {
291  $val = call_user_func( $tokenFunctions[$t], $iwUser );
292  if ( $val === false ) {
293  $this->addWarning( [ 'apiwarn-tokennotallowed', $t ] );
294  } else {
295  $data[$u][$t . 'token'] = $val;
296  }
297  }
298  }
299  } else {
300  $data[$u]['missing'] = true;
301  if ( isset( $this->prop['cancreate'] ) ) {
302  $status = MediaWiki\Auth\AuthManager::singleton()->canCreateAccount( $u );
303  $data[$u]['cancreate'] = $status->isGood();
304  if ( !$status->isGood() ) {
305  $data[$u]['cancreateerror'] = $this->getErrorFormatter()->arrayFromStatus( $status );
306  }
307  }
308  }
309  } else {
310  $data[$u] = [ 'userid' => $u, 'missing' => true ];
311  }
312 
313  } else {
314  if ( isset( $this->prop['groups'] ) && isset( $data[$u]['groups'] ) ) {
315  ApiResult::setArrayType( $data[$u]['groups'], 'array' );
316  ApiResult::setIndexedTagName( $data[$u]['groups'], 'g' );
317  }
318  if ( isset( $this->prop['groupmemberships'] ) && isset( $data[$u]['groupmemberships'] ) ) {
319  ApiResult::setArrayType( $data[$u]['groupmemberships'], 'array' );
320  ApiResult::setIndexedTagName( $data[$u]['groupmemberships'], 'groupmembership' );
321  }
322  if ( isset( $this->prop['implicitgroups'] ) && isset( $data[$u]['implicitgroups'] ) ) {
323  ApiResult::setArrayType( $data[$u]['implicitgroups'], 'array' );
324  ApiResult::setIndexedTagName( $data[$u]['implicitgroups'], 'g' );
325  }
326  if ( isset( $this->prop['rights'] ) && isset( $data[$u]['rights'] ) ) {
327  ApiResult::setArrayType( $data[$u]['rights'], 'array' );
328  ApiResult::setIndexedTagName( $data[$u]['rights'], 'r' );
329  }
330  }
331 
332  // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
333  $fit = $result->addValue( [ 'query', $this->getModuleName() ], null, $data[$u] );
334  if ( !$fit ) {
335  if ( $useNames ) {
336  $this->setContinueEnumParameter( 'users',
337  implode( '|', array_diff( $users, $done ) ) );
338  } else {
339  $this->setContinueEnumParameter( 'userids',
340  implode( '|', array_diff( $userids, $done ) ) );
341  }
342  break;
343  }
344  $done[] = $u;
345  }
346  $result->addIndexedTagName( [ 'query', $this->getModuleName() ], 'user' );
347  }
348 
349  public function getCacheMode( $params ) {
350  if ( isset( $params['token'] ) ) {
351  return 'private';
352  } elseif ( array_diff( (array)$params['prop'], static::$publicProps ) ) {
353  return 'anon-public-user-private';
354  } else {
355  return 'public';
356  }
357  }
358 
359  public function getAllowedParams() {
360  return [
361  'prop' => [
362  ApiBase::PARAM_ISMULTI => true,
364  'blockinfo',
365  'groups',
366  'groupmemberships',
367  'implicitgroups',
368  'rights',
369  'editcount',
370  'registration',
371  'emailable',
372  'gender',
373  'centralids',
374  'cancreate',
375  // When adding a prop, consider whether it should be added
376  // to self::$publicProps
377  ],
379  ],
380  'attachedwiki' => null,
381  'users' => [
383  ],
384  'userids' => [
385  ApiBase::PARAM_ISMULTI => true,
386  ApiBase::PARAM_TYPE => 'integer'
387  ],
388  'token' => [
390  ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() ),
392  ],
393  ];
394  }
395 
396  protected function getExamplesMessages() {
397  return [
398  'action=query&list=users&ususers=Example&usprop=groups|editcount|gender'
399  => 'apihelp-query+users-example-simple',
400  ];
401  }
402 
403  public function getHelpUrls() {
404  return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Users';
405  }
406 }
ContextSource\$context
IContextSource $context
Definition: ContextSource.php:33
ContextSource\getConfig
getConfig()
Definition: ContextSource.php:63
ApiQueryBase\addFields
addFields( $value)
Add a set of fields to select to the internal array.
Definition: ApiQueryBase.php:193
ContextSource\getContext
getContext()
Get the base IContextSource object.
Definition: ContextSource.php:40
ApiQuery
This is the main query class.
Definition: ApiQuery.php:37
ApiBase\addWarning
addWarning( $msg, $code=null, $data=null)
Add a warning for this module.
Definition: ApiBase.php:1933
ApiQueryUsers
Query module to get information about a list of users.
Definition: ApiQueryUsers.php:30
ApiQueryBase\resetQueryParams
resetQueryParams()
Blank the internal arrays with query parameters.
Definition: ApiQueryBase.php:146
true
return true
Definition: router.php:92
ApiBase\PARAM_TYPE
const PARAM_TYPE
(string|string[]) Either an array of allowed value strings, or a string type as described below.
Definition: ApiBase.php:94
ApiBase\getResult
getResult()
Get the result object.
Definition: ApiBase.php:640
UserRightsProxy
Cut-down copy of User interface for local-interwiki-database user rights manipulation.
Definition: UserRightsProxy.php:29
$res
$res
Definition: testCompression.php:52
User\newFromRow
static newFromRow( $row, $data=null)
Create a new user object from a user row.
Definition: User.php:696
ApiBase\lacksSameOriginSecurity
lacksSameOriginSecurity()
Returns true if the current request breaks the same-origin policy.
Definition: ApiBase.php:568
ApiQueryUsers\getUserrightsToken
static getUserrightsToken( $user)
Definition: ApiQueryUsers.php:91
ApiBase\PARAM_DEPRECATED
const PARAM_DEPRECATED
(boolean) Is the parameter deprecated (will show a warning)?
Definition: ApiBase.php:112
MediaWiki\Block\DatabaseBlock
A DatabaseBlock (unlike a SystemBlock) is stored in the database, may give rise to autoblocks and may...
Definition: DatabaseBlock.php:54
ApiQueryBlockInfoTrait
trait ApiQueryBlockInfoTrait
Definition: ApiQueryBlockInfoTrait.php:27
UserrightsPage
Special page to allow managing user group membership.
Definition: SpecialUserrights.php:31
ApiResult\setArrayType
static setArrayType(array &$arr, $type, $kvpKeyName=null)
Set the array data type.
Definition: ApiResult.php:728
ApiQueryBase
This is a base class for all Query modules.
Definition: ApiQueryBase.php:34
wfTimestampOrNull
wfTimestampOrNull( $outputtype=TS_UNIX, $ts=null)
Return a formatted timestamp, or null if input is null.
Definition: GlobalFunctions.php:1885
ApiQueryBase\getDB
getDB()
Get the Query database connection (read-only)
Definition: ApiQueryBase.php:107
ApiQueryBase\addTables
addTables( $tables, $alias=null)
Add a set of tables to the internal array.
Definition: ApiQueryBase.php:161
ApiQueryBase\select
select( $method, $extraQuery=[], array &$hookData=null)
Execute a SELECT query based on the values in the internal arrays.
Definition: ApiQueryBase.php:375
$t
$t
Definition: make-normalization-table.php:143
ApiBase\extractRequestParams
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition: ApiBase.php:761
UserGroupMembership\selectFields
static selectFields()
Returns the list of user_groups fields that should be selected to create a new user group membership.
Definition: UserGroupMembership.php:104
ApiQueryUsers\getHelpUrls
getHelpUrls()
Return links to more detailed help pages about the module.
Definition: ApiQueryUsers.php:403
ApiResult\setIndexedTagName
static setIndexedTagName(array &$arr, $tag)
Set the tag name for numeric-keyed values in XML format.
Definition: ApiResult.php:616
ApiBase\getPermissionManager
getPermissionManager()
Obtain a PermissionManager instance that subclasses may use in their authorization checks.
Definition: ApiBase.php:710
ApiQueryUsers\execute
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
Definition: ApiQueryUsers.php:99
ApiQueryBase\addJoinConds
addJoinConds( $join_conds)
Add a set of JOIN conditions to the internal array.
Definition: ApiQueryBase.php:182
ApiQueryUsers\__construct
__construct(ApiQuery $query, $moduleName)
Definition: ApiQueryUsers.php:55
ApiQueryBase\addWhereFld
addWhereFld( $field, $value)
Equivalent to addWhere( [ $field => $value ] )
Definition: ApiQueryBase.php:261
ApiQueryUsers\getTokenFunctions
getTokenFunctions()
Get an array mapping token names to their handler functions.
Definition: ApiQueryUsers.php:66
ApiBase\requireMaxOneParameter
requireMaxOneParameter( $params, $required)
Die if more than one of a certain set of parameters is set and not false.
Definition: ApiBase.php:931
User\getQueryInfo
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new user object.
Definition: User.php:5252
ApiQueryUsers\getCacheMode
getCacheMode( $params)
Get the cache mode for the data generated by this module.
Definition: ApiQueryUsers.php:349
ApiQueryUsers\getExamplesMessages
getExamplesMessages()
Returns usage examples for this module.
Definition: ApiQueryUsers.php:396
$status
return $status
Definition: SyntaxHighlight.php:347
MediaWiki\Auth\AuthManager\singleton
static singleton()
Get the global AuthManager.
Definition: AuthManager.php:155
ApiQueryUsers\$tokenFunctions
$tokenFunctions
Definition: ApiQueryUsers.php:33
User\getCanonicalName
static getCanonicalName( $name, $validate='valid')
Given unvalidated user input, return a canonical username, or false if the username is invalid.
Definition: User.php:1139
SpecialPage\setContext
setContext( $context)
Sets the context this SpecialPage is executed in.
Definition: SpecialPage.php:682
ApiBase\getModuleName
getModuleName()
Get the name of the module being executed by this instance.
Definition: ApiBase.php:520
ApiBase\PARAM_ISMULTI
const PARAM_ISMULTI
(boolean) Accept multiple pipe-separated values for this parameter (e.g.
Definition: ApiBase.php:58
ApiQueryUsers\getAllowedParams
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
Definition: ApiQueryUsers.php:359
ApiResult\formatExpiry
static formatExpiry( $expiry, $infinity='infinity')
Format an expiry timestamp for API output.
Definition: ApiResult.php:1205
ApiQueryUsers\$publicProps
static array $publicProps
Properties whose contents does not depend on who is looking at them.
Definition: ApiQueryUsers.php:40
ApiQueryBase\addWhere
addWhere( $value)
Add a set of WHERE clauses to the internal array.
Definition: ApiQueryBase.php:228
ApiQueryUserInfo\getCentralUserInfo
static getCentralUserInfo(Config $config, User $user, $attachedWiki=null)
Get central user info.
Definition: ApiQueryUserInfo.php:90
ApiQueryBase\setContinueEnumParameter
setContinueEnumParameter( $paramName, $paramValue)
Set a query-continue value.
Definition: ApiQueryBase.php:492
ApiBase\PARAM_HELP_MSG_PER_VALUE
const PARAM_HELP_MSG_PER_VALUE
((string|array|Message)[]) When PARAM_TYPE is an array, this is an array mapping those values to $msg...
Definition: ApiBase.php:164
CommentStore\getStore
static getStore()
Definition: CommentStore.php:139
ApiQueryUsers\$prop
$prop
Definition: ApiQueryUsers.php:33
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
ApiBase\getErrorFormatter
getErrorFormatter()
Get the error formatter.
Definition: ApiBase.php:654
addBlockInfoToQuery
addBlockInfoToQuery( $showBlockInfo)
Filters hidden users (where the user doesn't have the right to view them) Also adds relevant block in...
Definition: ApiQueryBlockInfoTrait.php:37