MediaWiki  master
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  $this->getHookRunner()->onAPIQueryUsersTokens( $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  $params = $this->extractRequestParams();
102  $this->requireMaxOneParameter( $params, 'userids', 'users' );
103 
104  if ( $params['prop'] !== null ) {
105  $this->prop = array_flip( $params['prop'] );
106  } else {
107  $this->prop = [];
108  }
109  $useNames = $params['users'] !== null;
110 
111  $users = (array)$params['users'];
112  $userids = (array)$params['userids'];
113 
114  $goodNames = $done = [];
115  $result = $this->getResult();
116  // Canonicalize user names
117  foreach ( $users as $u ) {
118  $n = User::getCanonicalName( $u );
119  if ( $n === false || $n === '' ) {
120  $vals = [ 'name' => $u, 'invalid' => true ];
121  $fit = $result->addValue( [ 'query', $this->getModuleName() ],
122  null, $vals );
123  if ( !$fit ) {
124  $this->setContinueEnumParameter( 'users',
125  implode( '|', array_diff( $users, $done ) ) );
126  $goodNames = [];
127  break;
128  }
129  $done[] = $u;
130  } else {
131  $goodNames[] = $n;
132  }
133  }
134 
135  if ( $useNames ) {
136  $parameters = &$goodNames;
137  } else {
138  $parameters = &$userids;
139  }
140 
141  $result = $this->getResult();
142 
143  if ( count( $parameters ) ) {
144  $userQuery = User::getQueryInfo();
145  $this->addTables( $userQuery['tables'] );
146  $this->addFields( $userQuery['fields'] );
147  $this->addJoinConds( $userQuery['joins'] );
148  if ( $useNames ) {
149  $this->addWhereFld( 'user_name', $goodNames );
150  } else {
151  $this->addWhereFld( 'user_id', $userids );
152  }
153 
154  $this->addBlockInfoToQuery( isset( $this->prop['blockinfo'] ) );
155 
156  $data = [];
157  $res = $this->select( __METHOD__ );
158  $this->resetQueryParams();
159 
160  // get user groups if needed
161  if ( isset( $this->prop['groups'] ) || isset( $this->prop['rights'] ) ) {
162  $userGroups = [];
163 
164  $this->addTables( 'user' );
165  if ( $useNames ) {
166  $this->addWhereFld( 'user_name', $goodNames );
167  } else {
168  $this->addWhereFld( 'user_id', $userids );
169  }
170 
171  $this->addTables( 'user_groups' );
172  $this->addJoinConds( [ 'user_groups' => [ 'JOIN', 'ug_user=user_id' ] ] );
173  $this->addFields( [ 'user_name' ] );
175  $this->addWhere( 'ug_expiry IS NULL OR ug_expiry >= ' .
176  $db->addQuotes( $db->timestamp() ) );
177  $userGroupsRes = $this->select( __METHOD__ );
178 
179  foreach ( $userGroupsRes as $row ) {
180  $userGroups[$row->user_name][] = $row;
181  }
182  }
183 
184  foreach ( $res as $row ) {
185  // create user object and pass along $userGroups if set
186  // that reduces the number of database queries needed in User dramatically
187  if ( !isset( $userGroups ) ) {
188  $user = User::newFromRow( $row );
189  } else {
190  if ( !isset( $userGroups[$row->user_name] ) || !is_array( $userGroups[$row->user_name] ) ) {
191  $userGroups[$row->user_name] = [];
192  }
193  $user = User::newFromRow( $row, [ 'user_groups' => $userGroups[$row->user_name] ] );
194  }
195  if ( $useNames ) {
196  $key = $user->getName();
197  } else {
198  $key = $user->getId();
199  }
200  $data[$key]['userid'] = $user->getId();
201  $data[$key]['name'] = $user->getName();
202 
203  if ( isset( $this->prop['editcount'] ) ) {
204  $data[$key]['editcount'] = $user->getEditCount();
205  }
206 
207  if ( isset( $this->prop['registration'] ) ) {
208  $data[$key]['registration'] = wfTimestampOrNull( TS_ISO_8601, $user->getRegistration() );
209  }
210 
211  if ( isset( $this->prop['groups'] ) ) {
212  $data[$key]['groups'] = $user->getEffectiveGroups();
213  }
214 
215  if ( isset( $this->prop['groupmemberships'] ) ) {
216  $data[$key]['groupmemberships'] = array_map( function ( $ugm ) {
217  return [
218  'group' => $ugm->getGroup(),
219  'expiry' => ApiResult::formatExpiry( $ugm->getExpiry() ),
220  ];
221  }, $user->getGroupMemberships() );
222  }
223 
224  if ( isset( $this->prop['implicitgroups'] ) ) {
225  $data[$key]['implicitgroups'] = $user->getAutomaticGroups();
226  }
227 
228  if ( isset( $this->prop['rights'] ) ) {
229  $data[$key]['rights'] = $this->getPermissionManager()
230  ->getUserPermissions( $user );
231  }
232  if ( $row->ipb_deleted ) {
233  $data[$key]['hidden'] = true;
234  }
235  if ( isset( $this->prop['blockinfo'] ) && $row->ipb_by_text !== null ) {
236  $data[$key] += $this->getBlockDetails( DatabaseBlock::newFromRow( $row ) );
237  }
238 
239  if ( isset( $this->prop['emailable'] ) ) {
240  $data[$key]['emailable'] = $user->canReceiveEmail();
241  }
242 
243  if ( isset( $this->prop['gender'] ) ) {
244  $gender = $user->getOption( 'gender' );
245  if ( strval( $gender ) === '' ) {
246  $gender = 'unknown';
247  }
248  $data[$key]['gender'] = $gender;
249  }
250 
251  if ( isset( $this->prop['centralids'] ) ) {
253  $this->getConfig(), $user, $params['attachedwiki']
254  );
255  }
256 
257  if ( $params['token'] !== null ) {
259  foreach ( $params['token'] as $t ) {
260  $val = call_user_func( $tokenFunctions[$t], $user );
261  if ( $val === false ) {
262  $this->addWarning( [ 'apiwarn-tokennotallowed', $t ] );
263  } else {
264  $data[$key][$t . 'token'] = $val;
265  }
266  }
267  }
268  }
269  }
270 
271  $context = $this->getContext();
272  // Second pass: add result data to $retval
273  foreach ( $parameters as $u ) {
274  if ( !isset( $data[$u] ) ) {
275  if ( $useNames ) {
276  $data[$u] = [ 'name' => $u ];
277  $urPage = new UserrightsPage;
278  $urPage->setContext( $context );
279 
280  $iwUser = $urPage->fetchUser( $u );
281 
282  if ( $iwUser instanceof UserRightsProxy ) {
283  $data[$u]['interwiki'] = true;
284 
285  if ( $params['token'] !== null ) {
287 
288  foreach ( $params['token'] as $t ) {
289  $val = call_user_func( $tokenFunctions[$t], $iwUser );
290  if ( $val === false ) {
291  $this->addWarning( [ 'apiwarn-tokennotallowed', $t ] );
292  } else {
293  $data[$u][$t . 'token'] = $val;
294  }
295  }
296  }
297  } else {
298  $data[$u]['missing'] = true;
299  if ( isset( $this->prop['cancreate'] ) ) {
300  $status = MediaWiki\Auth\AuthManager::singleton()->canCreateAccount( $u );
301  $data[$u]['cancreate'] = $status->isGood();
302  if ( !$status->isGood() ) {
303  $data[$u]['cancreateerror'] = $this->getErrorFormatter()->arrayFromStatus( $status );
304  }
305  }
306  }
307  } else {
308  $data[$u] = [ 'userid' => $u, 'missing' => true ];
309  }
310 
311  } else {
312  if ( isset( $this->prop['groups'] ) && isset( $data[$u]['groups'] ) ) {
313  ApiResult::setArrayType( $data[$u]['groups'], 'array' );
314  ApiResult::setIndexedTagName( $data[$u]['groups'], 'g' );
315  }
316  if ( isset( $this->prop['groupmemberships'] ) && isset( $data[$u]['groupmemberships'] ) ) {
317  ApiResult::setArrayType( $data[$u]['groupmemberships'], 'array' );
318  ApiResult::setIndexedTagName( $data[$u]['groupmemberships'], 'groupmembership' );
319  }
320  if ( isset( $this->prop['implicitgroups'] ) && isset( $data[$u]['implicitgroups'] ) ) {
321  ApiResult::setArrayType( $data[$u]['implicitgroups'], 'array' );
322  ApiResult::setIndexedTagName( $data[$u]['implicitgroups'], 'g' );
323  }
324  if ( isset( $this->prop['rights'] ) && isset( $data[$u]['rights'] ) ) {
325  ApiResult::setArrayType( $data[$u]['rights'], 'array' );
326  ApiResult::setIndexedTagName( $data[$u]['rights'], 'r' );
327  }
328  }
329 
330  $fit = $result->addValue( [ 'query', $this->getModuleName() ], null, $data[$u] );
331  if ( !$fit ) {
332  if ( $useNames ) {
333  $this->setContinueEnumParameter( 'users',
334  implode( '|', array_diff( $users, $done ) ) );
335  } else {
336  $this->setContinueEnumParameter( 'userids',
337  implode( '|', array_diff( $userids, $done ) ) );
338  }
339  break;
340  }
341  $done[] = $u;
342  }
343  $result->addIndexedTagName( [ 'query', $this->getModuleName() ], 'user' );
344  }
345 
346  public function getCacheMode( $params ) {
347  if ( isset( $params['token'] ) ) {
348  return 'private';
349  } elseif ( array_diff( (array)$params['prop'], static::$publicProps ) ) {
350  return 'anon-public-user-private';
351  } else {
352  return 'public';
353  }
354  }
355 
356  public function getAllowedParams() {
357  return [
358  'prop' => [
359  ApiBase::PARAM_ISMULTI => true,
361  'blockinfo',
362  'groups',
363  'groupmemberships',
364  'implicitgroups',
365  'rights',
366  'editcount',
367  'registration',
368  'emailable',
369  'gender',
370  'centralids',
371  'cancreate',
372  // When adding a prop, consider whether it should be added
373  // to self::$publicProps
374  ],
376  ],
377  'attachedwiki' => null,
378  'users' => [
380  ],
381  'userids' => [
382  ApiBase::PARAM_ISMULTI => true,
383  ApiBase::PARAM_TYPE => 'integer'
384  ],
385  'token' => [
387  ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() ),
389  ],
390  ];
391  }
392 
393  protected function getExamplesMessages() {
394  return [
395  'action=query&list=users&ususers=Example&usprop=groups|editcount|gender'
396  => 'apihelp-query+users-example-simple',
397  ];
398  }
399 
400  public function getHelpUrls() {
401  return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Users';
402  }
403 }
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:208
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:1334
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:152
true
return true
Definition: router.php:90
ApiBase\PARAM_TYPE
const PARAM_TYPE
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:68
ApiBase\getResult
getResult()
Get the result object.
Definition: ApiBase.php:546
UserRightsProxy
Cut-down copy of User interface for local-interwiki-database user rights manipulation.
Definition: UserRightsProxy.php:29
$res
$res
Definition: testCompression.php:57
User\newFromRow
static newFromRow( $row, $data=null)
Create a new user object from a user row.
Definition: User.php:716
ApiBase\lacksSameOriginSecurity
lacksSameOriginSecurity()
Returns true if the current request breaks the same-origin policy.
Definition: ApiBase.php:474
ApiQueryUsers\getUserrightsToken
static getUserrightsToken( $user)
Definition: ApiQueryUsers.php:91
ApiBase\PARAM_DEPRECATED
const PARAM_DEPRECATED
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:73
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:28
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:716
ApiQueryBase
This is a base class for all Query modules.
Definition: ApiQueryBase.php:35
wfTimestampOrNull
wfTimestampOrNull( $outputtype=TS_UNIX, $ts=null)
Return a formatted timestamp, or null if input is null.
Definition: GlobalFunctions.php:1822
ApiQueryBase\getDB
getDB()
Get the Query database connection (read-only)
Definition: ApiQueryBase.php:113
ApiQueryBase\addTables
addTables( $tables, $alias=null)
Add a set of tables to the internal array.
Definition: ApiQueryBase.php:178
ApiQueryBase\select
select( $method, $extraQuery=[], array &$hookData=null)
Execute a SELECT query based on the values in the internal arrays.
Definition: ApiQueryBase.php:395
ApiBase\extractRequestParams
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition: ApiBase.php:695
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:400
ApiResult\setIndexedTagName
static setIndexedTagName(array &$arr, $tag)
Set the tag name for numeric-keyed values in XML format.
Definition: ApiResult.php:604
ApiBase\requireMaxOneParameter
requireMaxOneParameter( $params,... $required)
Die if more than one of a certain set of parameters is set and not false.
Definition: ApiBase.php:867
ApiBase\getPermissionManager
getPermissionManager()
Obtain a PermissionManager instance that subclasses may use in their authorization checks.
Definition: ApiBase.php:616
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:197
ApiQueryUsers\__construct
__construct(ApiQuery $query, $moduleName)
Definition: ApiQueryUsers.php:55
ApiQueryBase\addWhereFld
addWhereFld( $field, $value)
Equivalent to addWhere( [ $field => $value ] )
Definition: ApiQueryBase.php:278
ApiQueryUsers\getTokenFunctions
getTokenFunctions()
Get an array mapping token names to their handler functions.
Definition: ApiQueryUsers.php:66
User\getQueryInfo
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new user object.
Definition: User.php:4775
ApiQueryUsers\getCacheMode
getCacheMode( $params)
Get the cache mode for the data generated by this module.
Definition: ApiQueryUsers.php:346
ApiQueryUsers\getExamplesMessages
getExamplesMessages()
Returns usage examples for this module.
Definition: ApiQueryUsers.php:393
MediaWiki\Auth\AuthManager\singleton
static singleton()
Get the global AuthManager.
Definition: AuthManager.php:171
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:1127
SpecialPage\setContext
setContext( $context)
Sets the context this SpecialPage is executed in.
Definition: SpecialPage.php:679
ApiBase\getModuleName
getModuleName()
Get the name of the module being executed by this instance.
Definition: ApiBase.php:426
ApiBase\PARAM_ISMULTI
const PARAM_ISMULTI
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:67
ApiQueryUsers\getAllowedParams
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
Definition: ApiQueryUsers.php:356
ApiResult\formatExpiry
static formatExpiry( $expiry, $infinity='infinity')
Format an expiry timestamp for API output.
Definition: ApiResult.php:1193
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:241
ApiQueryUserInfo\getCentralUserInfo
static getCentralUserInfo(Config $config, User $user, $attachedWiki=null)
Get central user info.
Definition: ApiQueryUserInfo.php:69
$t
$t
Definition: testCompression.php:74
ApiQueryBase\setContinueEnumParameter
setContinueEnumParameter( $paramName, $paramValue)
Set a query-continue value.
Definition: ApiQueryBase.php:512
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:137
ApiBase\getHookRunner
getHookRunner()
Get an ApiHookRunner for running core API hooks.
Definition: ApiBase.php:641
ApiQueryUsers\$prop
$prop
Definition: ApiQueryUsers.php:33
ApiBase\getErrorFormatter
getErrorFormatter()
Get the error formatter.
Definition: ApiBase.php:560
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:38