MediaWiki REL1_34
ApiQueryUsers.php
Go to the documentation of this file.
1<?php
24
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 ) ) {
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
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' ] );
176 $this->addFields( UserGroupMembership::selectFields() );
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 $fit = $result->addValue( [ 'query', $this->getModuleName() ], null, $data[$u] );
333 if ( !$fit ) {
334 if ( $useNames ) {
335 $this->setContinueEnumParameter( 'users',
336 implode( '|', array_diff( $users, $done ) ) );
337 } else {
338 $this->setContinueEnumParameter( 'userids',
339 implode( '|', array_diff( $userids, $done ) ) );
340 }
341 break;
342 }
343 $done[] = $u;
344 }
345 $result->addIndexedTagName( [ 'query', $this->getModuleName() ], 'user' );
346 }
347
348 public function getCacheMode( $params ) {
349 if ( isset( $params['token'] ) ) {
350 return 'private';
351 } elseif ( array_diff( (array)$params['prop'], static::$publicProps ) ) {
352 return 'anon-public-user-private';
353 } else {
354 return 'public';
355 }
356 }
357
358 public function getAllowedParams() {
359 return [
360 'prop' => [
363 'blockinfo',
364 'groups',
365 'groupmemberships',
366 'implicitgroups',
367 'rights',
368 'editcount',
369 'registration',
370 'emailable',
371 'gender',
372 'centralids',
373 'cancreate',
374 // When adding a prop, consider whether it should be added
375 // to self::$publicProps
376 ],
378 ],
379 'attachedwiki' => null,
380 'users' => [
382 ],
383 'userids' => [
385 ApiBase::PARAM_TYPE => 'integer'
386 ],
387 'token' => [
389 ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() ),
391 ],
392 ];
393 }
394
395 protected function getExamplesMessages() {
396 return [
397 'action=query&list=users&ususers=Example&usprop=groups|editcount|gender'
398 => 'apihelp-query+users-example-simple',
399 ];
400 }
401
402 public function getHelpUrls() {
403 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Users';
404 }
405}
addBlockInfoToQuery( $showBlockInfo)
Filters hidden users (where the user doesn't have the right to view them) Also adds relevant block in...
wfTimestampOrNull( $outputtype=TS_UNIX, $ts=null)
Return a formatted timestamp, or null if input is null.
const PARAM_DEPRECATED
(boolean) Is the parameter deprecated (will show a warning)?
Definition ApiBase.php:112
const PARAM_TYPE
(string|string[]) Either an array of allowed value strings, or a string type as described below.
Definition ApiBase.php:94
getErrorFormatter()
Get the error formatter.
Definition ApiBase.php:654
getPermissionManager()
Obtain a PermissionManager instance that subclasses may use in their authorization checks.
Definition ApiBase.php:710
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
getResult()
Get the result object.
Definition ApiBase.php:640
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:761
requireMaxOneParameter( $params, $required)
Die if more than one of a certain set of parameters is set and not false.
Definition ApiBase.php:931
addWarning( $msg, $code=null, $data=null)
Add a warning for this module.
Definition ApiBase.php:1933
getModuleName()
Get the name of the module being executed by this instance.
Definition ApiBase.php:520
const PARAM_ISMULTI
(boolean) Accept multiple pipe-separated values for this parameter (e.g.
Definition ApiBase.php:58
lacksSameOriginSecurity()
Returns true if the current request breaks the same-origin policy.
Definition ApiBase.php:568
This is a base class for all Query modules.
setContinueEnumParameter( $paramName, $paramValue)
Set a query-continue value.
resetQueryParams()
Blank the internal arrays with query parameters.
addFields( $value)
Add a set of fields to select to the internal array.
addTables( $tables, $alias=null)
Add a set of tables to the internal array.
getDB()
Get the Query database connection (read-only)
select( $method, $extraQuery=[], array &$hookData=null)
Execute a SELECT query based on the values in the internal arrays.
addJoinConds( $join_conds)
Add a set of JOIN conditions to the internal array.
addWhereFld( $field, $value)
Equivalent to addWhere( [ $field => $value ] )
addWhere( $value)
Add a set of WHERE clauses to the internal array.
static getCentralUserInfo(Config $config, User $user, $attachedWiki=null)
Get central user info.
Query module to get information about a list of users.
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
static getUserrightsToken( $user)
static array $publicProps
Properties whose contents does not depend on who is looking at them.
getHelpUrls()
Return links to more detailed help pages about the module.
getTokenFunctions()
Get an array mapping token names to their handler functions.
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
getExamplesMessages()
Returns usage examples for this module.
getCacheMode( $params)
Get the cache mode for the data generated by this module.
__construct(ApiQuery $query, $moduleName)
This is the main query class.
Definition ApiQuery.php:37
IContextSource $context
getContext()
Get the base IContextSource object.
A DatabaseBlock (unlike a SystemBlock) is stored in the database, may give rise to autoblocks and may...
setContext( $context)
Sets the context this SpecialPage is executed in.
Cut-down copy of User interface for local-interwiki-database user rights manipulation.
Special page to allow managing user group membership.
trait ApiQueryBlockInfoTrait
return true
Definition router.php:94