MediaWiki  master
ApiQueryUsers.php
Go to the documentation of this file.
1 <?php
25 
31 class ApiQueryUsers extends ApiQueryBase {
33 
35 
41  protected static $publicProps = [
42  // everything except 'blockinfo' which might show hidden records if the user
43  // making the request has the appropriate permissions
44  'groups',
45  'groupmemberships',
46  'implicitgroups',
47  'rights',
48  'editcount',
49  'registration',
50  'emailable',
51  'gender',
52  'centralids',
53  'cancreate',
54  ];
55 
56  public function __construct( ApiQuery $query, $moduleName ) {
57  parent::__construct( $query, $moduleName, 'us' );
58  }
59 
67  protected function getTokenFunctions() {
68  // Don't call the hooks twice
69  if ( isset( $this->tokenFunctions ) ) {
70  return $this->tokenFunctions;
71  }
72 
73  // If we're in a mode that breaks the same-origin policy, no tokens can
74  // be obtained
75  if ( $this->lacksSameOriginSecurity() ) {
76  return [];
77  }
78 
79  $this->tokenFunctions = [
80  'userrights' => [ self::class, 'getUserrightsToken' ],
81  ];
82  $this->getHookRunner()->onAPIQueryUsersTokens( $this->tokenFunctions );
83 
84  return $this->tokenFunctions;
85  }
86 
92  public static function getUserrightsToken( $user ) {
93  global $wgUser;
94 
95  // Since the permissions check for userrights is non-trivial,
96  // don't bother with it here
97  return $wgUser->getEditToken( $user->getName() );
98  }
99 
100  public function execute() {
101  $db = $this->getDB();
102  $params = $this->extractRequestParams();
103  $this->requireMaxOneParameter( $params, 'userids', 'users' );
104 
105  if ( $params['prop'] !== null ) {
106  $this->prop = array_flip( $params['prop'] );
107  } else {
108  $this->prop = [];
109  }
110  $useNames = $params['users'] !== null;
111 
112  $users = (array)$params['users'];
113  $userids = (array)$params['userids'];
114 
115  $goodNames = $done = [];
116  $result = $this->getResult();
117  // Canonicalize user names
118  foreach ( $users as $u ) {
119  $n = User::getCanonicalName( $u );
120  if ( $n === false || $n === '' ) {
121  $vals = [ 'name' => $u, 'invalid' => true ];
122  $fit = $result->addValue( [ 'query', $this->getModuleName() ],
123  null, $vals );
124  if ( !$fit ) {
125  $this->setContinueEnumParameter( 'users',
126  implode( '|', array_diff( $users, $done ) ) );
127  $goodNames = [];
128  break;
129  }
130  $done[] = $u;
131  } else {
132  $goodNames[] = $n;
133  }
134  }
135 
136  if ( $useNames ) {
137  $parameters = &$goodNames;
138  } else {
139  $parameters = &$userids;
140  }
141 
142  $result = $this->getResult();
143 
144  if ( count( $parameters ) ) {
145  $userQuery = User::getQueryInfo();
146  $this->addTables( $userQuery['tables'] );
147  $this->addFields( $userQuery['fields'] );
148  $this->addJoinConds( $userQuery['joins'] );
149  if ( $useNames ) {
150  $this->addWhereFld( 'user_name', $goodNames );
151  } else {
152  $this->addWhereFld( 'user_id', $userids );
153  }
154 
155  $this->addBlockInfoToQuery( isset( $this->prop['blockinfo'] ) );
156 
157  $data = [];
158  $res = $this->select( __METHOD__ );
159  $this->resetQueryParams();
160 
161  // get user groups if needed
162  if ( isset( $this->prop['groups'] ) || isset( $this->prop['rights'] ) ) {
163  $userGroups = [];
164 
165  $this->addTables( 'user' );
166  if ( $useNames ) {
167  $this->addWhereFld( 'user_name', $goodNames );
168  } else {
169  $this->addWhereFld( 'user_id', $userids );
170  }
171 
172  $this->addTables( 'user_groups' );
173  $this->addJoinConds( [ 'user_groups' => [ 'JOIN', 'ug_user=user_id' ] ] );
174  $this->addFields( [ 'user_name' ] );
175  $this->addFields( MediaWikiServices::getInstance()
176  ->getUserGroupManager()
177  ->getQueryInfo()['fields']
178  );
179  $this->addWhere( 'ug_expiry IS NULL OR ug_expiry >= ' .
180  $db->addQuotes( $db->timestamp() ) );
181  $userGroupsRes = $this->select( __METHOD__ );
182 
183  foreach ( $userGroupsRes as $row ) {
184  $userGroups[$row->user_name][] = $row;
185  }
186  }
187 
188  foreach ( $res as $row ) {
189  // create user object and pass along $userGroups if set
190  // that reduces the number of database queries needed in User dramatically
191  if ( !isset( $userGroups ) ) {
192  $user = User::newFromRow( $row );
193  } else {
194  if ( !isset( $userGroups[$row->user_name] ) || !is_array( $userGroups[$row->user_name] ) ) {
195  $userGroups[$row->user_name] = [];
196  }
197  $user = User::newFromRow( $row, [ 'user_groups' => $userGroups[$row->user_name] ] );
198  }
199  if ( $useNames ) {
200  $key = $user->getName();
201  } else {
202  $key = $user->getId();
203  }
204  $data[$key]['userid'] = $user->getId();
205  $data[$key]['name'] = $user->getName();
206 
207  if ( isset( $this->prop['editcount'] ) ) {
208  $data[$key]['editcount'] = $user->getEditCount();
209  }
210 
211  if ( isset( $this->prop['registration'] ) ) {
212  $data[$key]['registration'] = wfTimestampOrNull( TS_ISO_8601, $user->getRegistration() );
213  }
214 
215  if ( isset( $this->prop['groups'] ) ) {
216  $data[$key]['groups'] = $user->getEffectiveGroups();
217  }
218 
219  if ( isset( $this->prop['groupmemberships'] ) ) {
220  $data[$key]['groupmemberships'] = array_map( function ( $ugm ) {
221  return [
222  'group' => $ugm->getGroup(),
223  'expiry' => ApiResult::formatExpiry( $ugm->getExpiry() ),
224  ];
225  }, $user->getGroupMemberships() );
226  }
227 
228  if ( isset( $this->prop['implicitgroups'] ) ) {
229  $data[$key]['implicitgroups'] = $user->getAutomaticGroups();
230  }
231 
232  if ( isset( $this->prop['rights'] ) ) {
233  $data[$key]['rights'] = $this->getPermissionManager()
234  ->getUserPermissions( $user );
235  }
236  if ( $row->ipb_deleted ) {
237  $data[$key]['hidden'] = true;
238  }
239  if ( isset( $this->prop['blockinfo'] ) && $row->ipb_by_text !== null ) {
240  $data[$key] += $this->getBlockDetails( DatabaseBlock::newFromRow( $row ) );
241  }
242 
243  if ( isset( $this->prop['emailable'] ) ) {
244  $data[$key]['emailable'] = $user->canReceiveEmail();
245  }
246 
247  if ( isset( $this->prop['gender'] ) ) {
248  $gender = $user->getOption( 'gender' );
249  if ( strval( $gender ) === '' ) {
250  $gender = 'unknown';
251  }
252  $data[$key]['gender'] = $gender;
253  }
254 
255  if ( isset( $this->prop['centralids'] ) ) {
257  $this->getConfig(), $user, $params['attachedwiki']
258  );
259  }
260 
261  if ( $params['token'] !== null ) {
263  foreach ( $params['token'] as $t ) {
264  $val = call_user_func( $tokenFunctions[$t], $user );
265  if ( $val === false ) {
266  $this->addWarning( [ 'apiwarn-tokennotallowed', $t ] );
267  } else {
268  $data[$key][$t . 'token'] = $val;
269  }
270  }
271  }
272  }
273  }
274 
275  $context = $this->getContext();
276  // Second pass: add result data to $retval
277  foreach ( $parameters as $u ) {
278  if ( !isset( $data[$u] ) ) {
279  if ( $useNames ) {
280  $data[$u] = [ 'name' => $u ];
281  $urPage = new UserrightsPage;
282  $urPage->setContext( $context );
283 
284  $iwUser = $urPage->fetchUser( $u );
285 
286  if ( $iwUser instanceof UserRightsProxy ) {
287  $data[$u]['interwiki'] = true;
288 
289  if ( $params['token'] !== null ) {
291 
292  foreach ( $params['token'] as $t ) {
293  $val = call_user_func( $tokenFunctions[$t], $iwUser );
294  if ( $val === false ) {
295  $this->addWarning( [ 'apiwarn-tokennotallowed', $t ] );
296  } else {
297  $data[$u][$t . 'token'] = $val;
298  }
299  }
300  }
301  } else {
302  $data[$u]['missing'] = true;
303  if ( isset( $this->prop['cancreate'] ) ) {
304  $status = MediaWikiServices::getInstance()->getAuthManager()
305  ->canCreateAccount( $u );
306  $data[$u]['cancreate'] = $status->isGood();
307  if ( !$status->isGood() ) {
308  $data[$u]['cancreateerror'] = $this->getErrorFormatter()->arrayFromStatus( $status );
309  }
310  }
311  }
312  } else {
313  $data[$u] = [ 'userid' => $u, 'missing' => true ];
314  }
315 
316  } else {
317  if ( isset( $this->prop['groups'] ) && isset( $data[$u]['groups'] ) ) {
318  ApiResult::setArrayType( $data[$u]['groups'], 'array' );
319  ApiResult::setIndexedTagName( $data[$u]['groups'], 'g' );
320  }
321  if ( isset( $this->prop['groupmemberships'] ) && isset( $data[$u]['groupmemberships'] ) ) {
322  ApiResult::setArrayType( $data[$u]['groupmemberships'], 'array' );
323  ApiResult::setIndexedTagName( $data[$u]['groupmemberships'], 'groupmembership' );
324  }
325  if ( isset( $this->prop['implicitgroups'] ) && isset( $data[$u]['implicitgroups'] ) ) {
326  ApiResult::setArrayType( $data[$u]['implicitgroups'], 'array' );
327  ApiResult::setIndexedTagName( $data[$u]['implicitgroups'], 'g' );
328  }
329  if ( isset( $this->prop['rights'] ) && isset( $data[$u]['rights'] ) ) {
330  ApiResult::setArrayType( $data[$u]['rights'], 'array' );
331  ApiResult::setIndexedTagName( $data[$u]['rights'], 'r' );
332  }
333  }
334 
335  $fit = $result->addValue( [ 'query', $this->getModuleName() ], null, $data[$u] );
336  if ( !$fit ) {
337  if ( $useNames ) {
338  $this->setContinueEnumParameter( 'users',
339  implode( '|', array_diff( $users, $done ) ) );
340  } else {
341  $this->setContinueEnumParameter( 'userids',
342  implode( '|', array_diff( $userids, $done ) ) );
343  }
344  break;
345  }
346  $done[] = $u;
347  }
348  $result->addIndexedTagName( [ 'query', $this->getModuleName() ], 'user' );
349  }
350 
351  public function getCacheMode( $params ) {
352  if ( isset( $params['token'] ) ) {
353  return 'private';
354  } elseif ( array_diff( (array)$params['prop'], static::$publicProps ) ) {
355  return 'anon-public-user-private';
356  } else {
357  return 'public';
358  }
359  }
360 
361  public function getAllowedParams() {
362  return [
363  'prop' => [
364  ApiBase::PARAM_ISMULTI => true,
366  'blockinfo',
367  'groups',
368  'groupmemberships',
369  'implicitgroups',
370  'rights',
371  'editcount',
372  'registration',
373  'emailable',
374  'gender',
375  'centralids',
376  'cancreate',
377  // When adding a prop, consider whether it should be added
378  // to self::$publicProps
379  ],
381  ],
382  'attachedwiki' => null,
383  'users' => [
385  ],
386  'userids' => [
387  ApiBase::PARAM_ISMULTI => true,
388  ApiBase::PARAM_TYPE => 'integer'
389  ],
390  'token' => [
392  ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() ),
394  ],
395  ];
396  }
397 
398  protected function getExamplesMessages() {
399  return [
400  'action=query&list=users&ususers=Example&usprop=groups|editcount|gender'
401  => 'apihelp-query+users-example-simple',
402  ];
403  }
404 
405  public function getHelpUrls() {
406  return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Users';
407  }
408 }
ContextSource\$context
IContextSource $context
Definition: ContextSource.php:34
ContextSource\getConfig
getConfig()
Definition: ContextSource.php:67
ApiQueryBase\addFields
addFields( $value)
Add a set of fields to select to the internal array.
Definition: ApiQueryBase.php:215
ContextSource\getContext
getContext()
Get the base IContextSource object.
Definition: ContextSource.php:42
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:1301
ApiQueryUsers
Query module to get information about a list of users.
Definition: ApiQueryUsers.php:31
ApiQueryBase\resetQueryParams
resetQueryParams()
Blank the internal arrays with query parameters.
Definition: ApiQueryBase.php:159
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:154
true
return true
Definition: router.php:90
ApiBase\PARAM_TYPE
const PARAM_TYPE
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:71
ApiBase\getResult
getResult()
Get the result object.
Definition: ApiBase.php:565
UserRightsProxy
Cut-down copy of User interface for local-interwiki-database user rights manipulation.
Definition: UserRightsProxy.php:32
$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:715
ApiBase\lacksSameOriginSecurity
lacksSameOriginSecurity()
Returns true if the current request breaks the same-origin policy.
Definition: ApiBase.php:493
ApiQueryUsers\getUserrightsToken
static getUserrightsToken( $user)
Definition: ApiQueryUsers.php:92
ApiBase\PARAM_DEPRECATED
const PARAM_DEPRECATED
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:76
MediaWiki\Block\DatabaseBlock
A DatabaseBlock (unlike a SystemBlock) is stored in the database, may give rise to autoblocks and may...
Definition: DatabaseBlock.php:52
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:37
wfTimestampOrNull
wfTimestampOrNull( $outputtype=TS_UNIX, $ts=null)
Return a formatted timestamp, or null if input is null.
Definition: GlobalFunctions.php:1824
ApiQueryBase\getDB
getDB()
Get the Query database connection (read-only) Stable to override.
Definition: ApiQueryBase.php:119
ApiQueryBase\addTables
addTables( $tables, $alias=null)
Add a set of tables to the internal array.
Definition: ApiQueryBase.php:185
ApiQueryBase\select
select( $method, $extraQuery=[], array &$hookData=null)
Execute a SELECT query based on the values in the internal arrays.
Definition: ApiQueryBase.php:402
ApiBase\extractRequestParams
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition: ApiBase.php:717
ApiQueryUsers\getHelpUrls
getHelpUrls()
Return links to more detailed help pages about the module.
Definition: ApiQueryUsers.php:405
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:889
ApiBase\getPermissionManager
getPermissionManager()
Obtain a PermissionManager instance that subclasses may use in their authorization checks.
Definition: ApiBase.php:637
ApiQueryUsers\execute
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
Definition: ApiQueryUsers.php:100
ApiQueryBase\addJoinConds
addJoinConds( $join_conds)
Add a set of JOIN conditions to the internal array.
Definition: ApiQueryBase.php:204
ApiQueryUsers\__construct
__construct(ApiQuery $query, $moduleName)
Definition: ApiQueryUsers.php:56
ApiQueryBase\addWhereFld
addWhereFld( $field, $value)
Equivalent to addWhere( [ $field => $value ] )
Definition: ApiQueryBase.php:285
ApiQueryUsers\getTokenFunctions
getTokenFunctions()
Get an array mapping token names to their handler functions.
Definition: ApiQueryUsers.php:67
User\getQueryInfo
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new user object.
Definition: User.php:4442
ApiQueryUsers\getCacheMode
getCacheMode( $params)
Get the cache mode for the data generated by this module.
Definition: ApiQueryUsers.php:351
ApiQueryUsers\getExamplesMessages
getExamplesMessages()
Returns usage examples for this module.
Definition: ApiQueryUsers.php:398
ApiQueryUsers\$tokenFunctions
$tokenFunctions
Definition: ApiQueryUsers.php:34
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:1128
SpecialPage\setContext
setContext( $context)
Sets the context this SpecialPage is executed in.
Definition: SpecialPage.php:707
ApiBase\getModuleName
getModuleName()
Get the name of the module being executed by this instance.
Definition: ApiBase.php:444
ApiBase\PARAM_ISMULTI
const PARAM_ISMULTI
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:70
ApiQueryUsers\getAllowedParams
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
Definition: ApiQueryUsers.php:361
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:41
ApiQueryBase\addWhere
addWhere( $value)
Add a set of WHERE clauses to the internal array.
Definition: ApiQueryBase.php:248
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:519
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:140
ApiBase\getHookRunner
getHookRunner()
Get an ApiHookRunner for running core API hooks.
Definition: ApiBase.php:662
ApiQueryUsers\$prop
$prop
Definition: ApiQueryUsers.php:34
ApiBase\getErrorFormatter
getErrorFormatter()
Get the error formatter Stable to override.
Definition: ApiBase.php:580
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