MediaWiki fundraising/REL1_35
ApiQueryUsers.php
Go to the documentation of this file.
1<?php
25
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 ) ) {
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
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' => [
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' => [
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}
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
Definition ApiBase.php:98
const PARAM_TYPE
Definition ApiBase.php:78
getErrorFormatter()
Get the error formatter Stable to override.
Definition ApiBase.php:635
getPermissionManager()
Obtain a PermissionManager instance that subclasses may use in their authorization checks.
Definition ApiBase.php:692
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:195
requireMaxOneParameter( $params,... $required)
Die if more than one of a certain set of parameters is set and not false.
Definition ApiBase.php:944
getResult()
Get the result object.
Definition ApiBase.php:620
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:772
addWarning( $msg, $code=null, $data=null)
Add a warning for this module.
Definition ApiBase.php:1356
getModuleName()
Get the name of the module being executed by this instance.
Definition ApiBase.php:499
getHookRunner()
Get an ApiHookRunner for running core API hooks.
Definition ApiBase.php:717
const PARAM_ISMULTI
Definition ApiBase.php:74
lacksSameOriginSecurity()
Returns true if the current request breaks the same-origin policy.
Definition ApiBase.php:548
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) Stable to override.
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...
MediaWikiServices is the service locator for the application scope of MediaWiki.
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:92