MediaWiki master
ApiQueryUsers.php
Go to the documentation of this file.
1<?php
9namespace MediaWiki\Api;
10
22use Wikimedia\Timestamp\TimestampFormat as TS;
23
31
33 private $prop;
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 'tempexpired',
54 ];
55
56 public function __construct(
57 ApiQuery $query,
58 string $moduleName,
59 private readonly UserNameUtils $userNameUtils,
60 private readonly UserFactory $userFactory,
61 private readonly UserGroupManager $userGroupManager,
62 private readonly GenderCache $genderCache,
63 private readonly AuthManager $authManager,
64 private readonly TempUserConfig $tempUserConfig,
65 private readonly TempUserDetailsLookup $tempUserDetailsLookup,
66 ) {
67 parent::__construct( $query, $moduleName, 'us' );
68 }
69
70 public function execute() {
71 $db = $this->getDB();
72 $params = $this->extractRequestParams();
73 $this->requireMaxOneParameter( $params, 'userids', 'users' );
74
75 if ( $params['prop'] !== null ) {
76 $this->prop = array_fill_keys( $params['prop'], true );
77 } else {
78 $this->prop = [];
79 }
80 $useNames = $params['users'] !== null;
81
82 $users = (array)$params['users'];
83 $userids = (array)$params['userids'];
84
85 $goodNames = $done = [];
86 $result = $this->getResult();
87 // Canonicalize user names
88 foreach ( $users as $u ) {
89 $n = $this->userNameUtils->getCanonical( $u );
90 if ( $n === false || $n === '' ) {
91 $vals = [ 'name' => $u, 'invalid' => true ];
92 $fit = $result->addValue( [ 'query', $this->getModuleName() ],
93 null, $vals );
94 if ( !$fit ) {
95 $this->setContinueEnumParameter( 'users',
96 implode( '|', array_diff( $users, $done ) ) );
97 $goodNames = [];
98 break;
99 }
100 $done[] = $u;
101 } else {
102 $goodNames[] = $n;
103 }
104 }
105
106 if ( $useNames ) {
107 $parameters = &$goodNames;
108 } else {
109 $parameters = &$userids;
110 }
111
112 $result = $this->getResult();
113
114 if ( count( $parameters ) ) {
115 $this->getQueryBuilder()->merge( User::newQueryBuilder( $db ) );
116 if ( $useNames ) {
117 $this->addWhereFld( 'user_name', $goodNames );
118 } else {
119 $this->addWhereFld( 'user_id', $userids );
120 }
121
122 $this->addDeletedUserFilter();
123
124 $data = [];
125 $res = $this->select( __METHOD__ );
126 $this->resetQueryParams();
127
128 // get user groups if needed
129 if ( isset( $this->prop['groups'] ) || isset( $this->prop['rights'] ) ) {
130 $userGroups = [];
131
132 $this->addTables( 'user' );
133 if ( $useNames ) {
134 $this->addWhereFld( 'user_name', $goodNames );
135 } else {
136 $this->addWhereFld( 'user_id', $userids );
137 }
138
139 $this->addTables( 'user_groups' );
140 $this->addJoinConds( [ 'user_groups' => [ 'JOIN', 'ug_user=user_id' ] ] );
141 $this->addFields( [ 'user_name' ] );
142 $this->addFields( [ 'ug_user', 'ug_group', 'ug_expiry' ] );
143 $this->addWhere(
144 $db->expr( 'ug_expiry', '=', null )->or( 'ug_expiry', '>=', $db->timestamp() )
145 );
146 $userGroupsRes = $this->select( __METHOD__ );
147
148 foreach ( $userGroupsRes as $row ) {
149 $userGroups[$row->user_name][] = $row;
150 }
151 }
152 if ( isset( $this->prop['gender'] ) ) {
153 $userNames = [];
154 foreach ( $res as $row ) {
155 $userNames[] = $row->user_name;
156 }
157 $this->genderCache->doQuery( $userNames, __METHOD__ );
158 }
159
160 if ( isset( $this->prop['blockinfo'] ) ) {
161 $blockInfos = $this->getBlockDetailsForRows( $res );
162 } else {
163 $blockInfos = null;
164 }
165
166 if ( isset( $this->prop['tempexpired'] ) ) {
167 $tempUsers = [];
168 foreach ( $res as $row ) {
169 if ( $this->tempUserConfig->isTempName( $row->user_name ) ) {
170 $tempUsers[] = UserIdentityValue::newRegistered( $row->user_id, $row->user_name );
171 }
172 }
173 $this->tempUserDetailsLookup->preloadExpirationStatus( $tempUsers );
174 }
175
176 foreach ( $res as $row ) {
177 // create user object and pass along $userGroups if set
178 // that reduces the number of database queries needed in User dramatically
179 if ( !isset( $userGroups ) ) {
180 $user = $this->userFactory->newFromRow( $row );
181 } else {
182 if ( !isset( $userGroups[$row->user_name] ) || !is_array( $userGroups[$row->user_name] ) ) {
183 $userGroups[$row->user_name] = [];
184 }
185 $user = $this->userFactory->newFromRow( $row, [ 'user_groups' => $userGroups[$row->user_name] ] );
186 }
187 if ( $useNames ) {
188 $key = $user->getName();
189 } else {
190 $key = $user->getId();
191 }
192 $data[$key]['userid'] = $user->getId();
193 $data[$key]['name'] = $user->getName();
194
195 if ( $user->isSystemUser() ) {
196 $data[$key]['systemuser'] = true;
197 }
198
199 if ( isset( $this->prop['editcount'] ) ) {
200 $data[$key]['editcount'] = $user->getEditCount();
201 }
202
203 if ( isset( $this->prop['registration'] ) ) {
204 $data[$key]['registration'] = wfTimestampOrNull( TS::ISO_8601, $user->getRegistration() );
205 }
206
207 if ( isset( $this->prop['groups'] ) ) {
208 $data[$key]['groups'] = $this->userGroupManager->getUserEffectiveGroups(
209 $user, IDBAccessObject::READ_NORMAL, false, false );
210 }
211
212 if ( isset( $this->prop['groupmemberships'] ) ) {
213 $data[$key]['groupmemberships'] = array_map( static function ( $ugm ) {
214 return [
215 'group' => $ugm->getGroup(),
216 'expiry' => ApiResult::formatExpiry( $ugm->getExpiry() ),
217 ];
218 }, $this->userGroupManager->getUserGroupMemberships( $user ) );
219 }
220
221 if ( isset( $this->prop['implicitgroups'] ) ) {
222 $data[$key]['implicitgroups'] = $this->userGroupManager->getUserImplicitGroups( $user );
223 }
224
225 if ( isset( $this->prop['rights'] ) ) {
226 $data[$key]['rights'] = $this->getPermissionManager()
227 ->getUserPermissions( $user, false );
228 }
229 if ( $row->hu_deleted ) {
230 $data[$key]['hidden'] = true;
231 }
232 if ( isset( $this->prop['blockinfo'] ) && isset( $blockInfos[$row->user_id] ) ) {
233 $data[$key] += $blockInfos[$row->user_id];
234 }
235
236 if ( isset( $this->prop['emailable'] ) ) {
237 $data[$key]['emailable'] = $user->canReceiveEmail();
238 }
239
240 if ( isset( $this->prop['gender'] ) ) {
241 $data[$key]['gender'] = $this->genderCache->getGenderOf( $user, __METHOD__ );
242 }
243
244 if ( isset( $this->prop['centralids'] ) ) {
246 $this->getConfig(), $user, $params['attachedwiki']
247 );
248 }
249
250 if ( isset( $this->prop['tempexpired'] ) ) {
251 if ( $this->tempUserConfig->isTempName( $row->user_name ) ) {
252 $data[$key]['tempexpired'] = $this->tempUserDetailsLookup->isExpired( $user );
253 } else {
254 $data[$key]['tempexpired'] = null;
255 }
256 }
257 }
258 }
259
260 // Second pass: add result data to $retval
261 foreach ( $parameters as $u ) {
262 if ( !isset( $data[$u] ) ) {
263 if ( $useNames ) {
264 $data[$u] = [ 'name' => $u, 'missing' => true ];
265 if ( isset( $this->prop['cancreate'] ) ) {
266 $status = $this->authManager->canCreateAccount( $u );
267 $data[$u]['cancreate'] = $status->isGood();
268 if ( !$status->isGood() ) {
269 $data[$u]['cancreateerror'] = $this->getErrorFormatter()->arrayFromStatus( $status );
270 }
271 }
272 } else {
273 $data[$u] = [ 'userid' => $u, 'missing' => true ];
274 }
275
276 } else {
277 if ( isset( $this->prop['groups'] ) && isset( $data[$u]['groups'] ) ) {
278 ApiResult::setArrayType( $data[$u]['groups'], 'array' );
279 ApiResult::setIndexedTagName( $data[$u]['groups'], 'g' );
280 }
281 if ( isset( $this->prop['groupmemberships'] ) && isset( $data[$u]['groupmemberships'] ) ) {
282 ApiResult::setArrayType( $data[$u]['groupmemberships'], 'array' );
283 ApiResult::setIndexedTagName( $data[$u]['groupmemberships'], 'groupmembership' );
284 }
285 if ( isset( $this->prop['implicitgroups'] ) && isset( $data[$u]['implicitgroups'] ) ) {
286 ApiResult::setArrayType( $data[$u]['implicitgroups'], 'array' );
287 ApiResult::setIndexedTagName( $data[$u]['implicitgroups'], 'g' );
288 }
289 if ( isset( $this->prop['rights'] ) && isset( $data[$u]['rights'] ) ) {
290 ApiResult::setArrayType( $data[$u]['rights'], 'array' );
291 ApiResult::setIndexedTagName( $data[$u]['rights'], 'r' );
292 }
293 }
294
295 $fit = $result->addValue( [ 'query', $this->getModuleName() ], null, $data[$u] );
296 if ( !$fit ) {
297 if ( $useNames ) {
298 $this->setContinueEnumParameter( 'users',
299 implode( '|', array_diff( $users, $done ) ) );
300 } else {
301 $this->setContinueEnumParameter( 'userids',
302 implode( '|', array_diff( $userids, $done ) ) );
303 }
304 break;
305 }
306 $done[] = $u;
307 }
308 $result->addIndexedTagName( [ 'query', $this->getModuleName() ], 'user' );
309 }
310
312 public function getCacheMode( $params ) {
313 if ( array_diff( (array)$params['prop'], static::$publicProps ) ) {
314 return 'anon-public-user-private';
315 } else {
316 return 'public';
317 }
318 }
319
321 public function getAllowedParams() {
322 return [
323 'prop' => [
324 ParamValidator::PARAM_ISMULTI => true,
325 ParamValidator::PARAM_TYPE => [
326 'blockinfo',
327 'groups',
328 'groupmemberships',
329 'implicitgroups',
330 'rights',
331 'editcount',
332 'registration',
333 'emailable',
334 'gender',
335 'centralids',
336 'cancreate',
337 'tempexpired',
338 // When adding a prop, consider whether it should be added
339 // to self::$publicProps
340 ],
342 ],
343 'attachedwiki' => null,
344 'users' => [
345 ParamValidator::PARAM_ISMULTI => true
346 ],
347 'userids' => [
348 ParamValidator::PARAM_ISMULTI => true,
349 ParamValidator::PARAM_TYPE => 'integer'
350 ],
351 ];
352 }
353
355 protected function getExamplesMessages() {
356 return [
357 'action=query&list=users&ususers=Example&usprop=groups|editcount|gender'
358 => 'apihelp-query+users-example-simple',
359 ];
360 }
361
363 public function getHelpUrls() {
364 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Users';
365 }
366}
367
369class_alias( ApiQueryUsers::class, 'ApiQueryUsers' );
wfTimestampOrNull( $outputtype=TS::UNIX, $ts=null)
Return a formatted timestamp, or null if input is null.
getModuleName()
Get the name of the module being executed by this instance.
Definition ApiBase.php:557
getResult()
Get the result object.
Definition ApiBase.php:696
const PARAM_HELP_MSG_PER_VALUE
((string|array|Message)[]) When PARAM_TYPE is an array, or 'string' with PARAM_ISMULTI,...
Definition ApiBase.php:206
requireMaxOneParameter( $params,... $required)
Dies if more than one parameter from a certain set of parameters are set and not false.
Definition ApiBase.php:1012
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:837
getPermissionManager()
Obtain a PermissionManager instance that subclasses may use in their authorization checks.
Definition ApiBase.php:756
This is a base class for all Query modules.
addTables( $tables, $alias=null)
Add a set of tables to the internal array.
addJoinConds( $join_conds)
Add a set of JOIN conditions 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.
addWhere( $value)
Add a set of WHERE clauses to the internal array.
getQueryBuilder()
Get the SelectQueryBuilder.
setContinueEnumParameter( $paramName, $paramValue)
Set a query-continue value.
resetQueryParams()
Blank the internal arrays with query parameters.
addWhereFld( $field, $value)
Equivalent to addWhere( [ $field => $value ] )
addFields( $value)
Add a set of fields to select to the internal array.
static getCentralUserInfo(Config $config, UserIdentity $user, $attachedWiki=UserIdentity::LOCAL)
Get central user info.
Query module to get information about a list of users.
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
static array $publicProps
Properties whose contents does not depend on who is looking at them.
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
getHelpUrls()
Return links to more detailed help pages about the module.1.25, returning boolean false is deprecated...
getExamplesMessages()
Returns usage examples for this module.Return value has query strings as keys, with values being eith...
getCacheMode( $params)
Get the cache mode for the data generated by this module.Override this in the module subclass....
__construct(ApiQuery $query, string $moduleName, private readonly UserNameUtils $userNameUtils, private readonly UserFactory $userFactory, private readonly UserGroupManager $userGroupManager, private readonly GenderCache $genderCache, private readonly AuthManager $authManager, private readonly TempUserConfig $tempUserConfig, private readonly TempUserDetailsLookup $tempUserDetailsLookup,)
This is the main query class.
Definition ApiQuery.php:36
static formatExpiry( $expiry, $infinity='infinity')
Format an expiry timestamp for API output.
static setIndexedTagName(array &$arr, $tag)
Set the tag name for numeric-keyed values in XML format.
static setArrayType(array &$arr, $type, $kvpKeyName=null)
Set the array data type.
AuthManager is the authentication system in MediaWiki and serves entry point for authentication.
Look up "gender" user preference.
Caching lookup service for metadata related to temporary accounts, such as expiration.
Create User objects.
Manage user group memberships.
Value object representing a user's identity.
UserNameUtils service.
User class for the MediaWiki software.
Definition User.php:129
Service for formatting and validating API parameters.
Interface for temporary user creation config and name matching.
Interface for database access objects.