MediaWiki master
ApiQueryUsers.php
Go to the documentation of this file.
1<?php
9namespace MediaWiki\Api;
10
21use Wikimedia\Timestamp\TimestampFormat as TS;
22
30
32 private $prop;
33
39 protected static $publicProps = [
40 // everything except 'blockinfo' which might show hidden records if the user
41 // making the request has the appropriate permissions
42 'groups',
43 'groupmemberships',
44 'implicitgroups',
45 'rights',
46 'editcount',
47 'registration',
48 'emailable',
49 'gender',
50 'centralids',
51 'cancreate',
52 'tempexpired',
53 ];
54
55 public function __construct(
56 ApiQuery $query,
57 string $moduleName,
58 private readonly UserNameUtils $userNameUtils,
59 private readonly UserFactory $userFactory,
60 private readonly UserGroupManager $userGroupManager,
61 private readonly GenderCache $genderCache,
62 private readonly AuthManager $authManager,
63 private readonly TempUserConfig $tempUserConfig,
64 private readonly TempUserDetailsLookup $tempUserDetailsLookup
65 ) {
66 parent::__construct( $query, $moduleName, 'us' );
67 }
68
69 public function execute() {
70 $db = $this->getDB();
71 $params = $this->extractRequestParams();
72 $this->requireMaxOneParameter( $params, 'userids', 'users' );
73
74 if ( $params['prop'] !== null ) {
75 $this->prop = array_fill_keys( $params['prop'], true );
76 } else {
77 $this->prop = [];
78 }
79 $useNames = $params['users'] !== null;
80
81 $users = (array)$params['users'];
82 $userids = (array)$params['userids'];
83
84 $goodNames = $done = [];
85 $result = $this->getResult();
86 // Canonicalize user names
87 foreach ( $users as $u ) {
88 $n = $this->userNameUtils->getCanonical( $u );
89 if ( $n === false || $n === '' ) {
90 $vals = [ 'name' => $u, 'invalid' => true ];
91 $fit = $result->addValue( [ 'query', $this->getModuleName() ],
92 null, $vals );
93 if ( !$fit ) {
94 $this->setContinueEnumParameter( 'users',
95 implode( '|', array_diff( $users, $done ) ) );
96 $goodNames = [];
97 break;
98 }
99 $done[] = $u;
100 } else {
101 $goodNames[] = $n;
102 }
103 }
104
105 if ( $useNames ) {
106 $parameters = &$goodNames;
107 } else {
108 $parameters = &$userids;
109 }
110
111 $result = $this->getResult();
112
113 if ( count( $parameters ) ) {
114 $this->getQueryBuilder()->merge( User::newQueryBuilder( $db ) );
115 if ( $useNames ) {
116 $this->addWhereFld( 'user_name', $goodNames );
117 } else {
118 $this->addWhereFld( 'user_id', $userids );
119 }
120
121 $this->addDeletedUserFilter();
122
123 $data = [];
124 $res = $this->select( __METHOD__ );
125 $this->resetQueryParams();
126
127 // get user groups if needed
128 if ( isset( $this->prop['groups'] ) || isset( $this->prop['rights'] ) ) {
129 $userGroups = [];
130
131 $this->addTables( 'user' );
132 if ( $useNames ) {
133 $this->addWhereFld( 'user_name', $goodNames );
134 } else {
135 $this->addWhereFld( 'user_id', $userids );
136 }
137
138 $this->addTables( 'user_groups' );
139 $this->addJoinConds( [ 'user_groups' => [ 'JOIN', 'ug_user=user_id' ] ] );
140 $this->addFields( [ 'user_name' ] );
141 $this->addFields( [ 'ug_user', 'ug_group', 'ug_expiry' ] );
142 $this->addWhere(
143 $db->expr( 'ug_expiry', '=', null )->or( 'ug_expiry', '>=', $db->timestamp() )
144 );
145 $userGroupsRes = $this->select( __METHOD__ );
146
147 foreach ( $userGroupsRes as $row ) {
148 $userGroups[$row->user_name][] = $row;
149 }
150 }
151 if ( isset( $this->prop['gender'] ) ) {
152 $userNames = [];
153 foreach ( $res as $row ) {
154 $userNames[] = $row->user_name;
155 }
156 $this->genderCache->doQuery( $userNames, __METHOD__ );
157 }
158
159 if ( isset( $this->prop['blockinfo'] ) ) {
160 $blockInfos = $this->getBlockDetailsForRows( $res );
161 } else {
162 $blockInfos = null;
163 }
164
165 if ( isset( $this->prop['tempexpired'] ) ) {
166 $tempUsers = [];
167 foreach ( $res as $row ) {
168 if ( $this->tempUserConfig->isTempName( $row->user_name ) ) {
169 $tempUsers[] = UserIdentityValue::newRegistered( $row->user_id, $row->user_name );
170 }
171 }
172 $this->tempUserDetailsLookup->preloadExpirationStatus( $tempUsers );
173 }
174
175 foreach ( $res as $row ) {
176 // create user object and pass along $userGroups if set
177 // that reduces the number of database queries needed in User dramatically
178 if ( !isset( $userGroups ) ) {
179 $user = $this->userFactory->newFromRow( $row );
180 } else {
181 if ( !isset( $userGroups[$row->user_name] ) || !is_array( $userGroups[$row->user_name] ) ) {
182 $userGroups[$row->user_name] = [];
183 }
184 $user = $this->userFactory->newFromRow( $row, [ 'user_groups' => $userGroups[$row->user_name] ] );
185 }
186 if ( $useNames ) {
187 $key = $user->getName();
188 } else {
189 $key = $user->getId();
190 }
191 $data[$key]['userid'] = $user->getId();
192 $data[$key]['name'] = $user->getName();
193
194 if ( $user->isSystemUser() ) {
195 $data[$key]['systemuser'] = true;
196 }
197
198 if ( isset( $this->prop['editcount'] ) ) {
199 $data[$key]['editcount'] = $user->getEditCount();
200 }
201
202 if ( isset( $this->prop['registration'] ) ) {
203 $data[$key]['registration'] = wfTimestampOrNull( TS::ISO_8601, $user->getRegistration() );
204 }
205
206 if ( isset( $this->prop['groups'] ) ) {
207 $data[$key]['groups'] = $this->userGroupManager->getUserEffectiveGroups( $user );
208 }
209
210 if ( isset( $this->prop['groupmemberships'] ) ) {
211 $data[$key]['groupmemberships'] = array_map( static function ( $ugm ) {
212 return [
213 'group' => $ugm->getGroup(),
214 'expiry' => ApiResult::formatExpiry( $ugm->getExpiry() ),
215 ];
216 }, $this->userGroupManager->getUserGroupMemberships( $user ) );
217 }
218
219 if ( isset( $this->prop['implicitgroups'] ) ) {
220 $data[$key]['implicitgroups'] = $this->userGroupManager->getUserImplicitGroups( $user );
221 }
222
223 if ( isset( $this->prop['rights'] ) ) {
224 $data[$key]['rights'] = $this->getPermissionManager()
225 ->getUserPermissions( $user );
226 }
227 if ( $row->hu_deleted ) {
228 $data[$key]['hidden'] = true;
229 }
230 if ( isset( $this->prop['blockinfo'] ) && isset( $blockInfos[$row->user_id] ) ) {
231 $data[$key] += $blockInfos[$row->user_id];
232 }
233
234 if ( isset( $this->prop['emailable'] ) ) {
235 $data[$key]['emailable'] = $user->canReceiveEmail();
236 }
237
238 if ( isset( $this->prop['gender'] ) ) {
239 $data[$key]['gender'] = $this->genderCache->getGenderOf( $user, __METHOD__ );
240 }
241
242 if ( isset( $this->prop['centralids'] ) ) {
244 $this->getConfig(), $user, $params['attachedwiki']
245 );
246 }
247
248 if ( isset( $this->prop['tempexpired'] ) ) {
249 if ( $this->tempUserConfig->isTempName( $row->user_name ) ) {
250 $data[$key]['tempexpired'] = $this->tempUserDetailsLookup->isExpired( $user );
251 } else {
252 $data[$key]['tempexpired'] = null;
253 }
254 }
255 }
256 }
257
258 // Second pass: add result data to $retval
259 foreach ( $parameters as $u ) {
260 if ( !isset( $data[$u] ) ) {
261 if ( $useNames ) {
262 $data[$u] = [ 'name' => $u, 'missing' => true ];
263 if ( isset( $this->prop['cancreate'] ) ) {
264 $status = $this->authManager->canCreateAccount( $u );
265 $data[$u]['cancreate'] = $status->isGood();
266 if ( !$status->isGood() ) {
267 $data[$u]['cancreateerror'] = $this->getErrorFormatter()->arrayFromStatus( $status );
268 }
269 }
270 } else {
271 $data[$u] = [ 'userid' => $u, 'missing' => true ];
272 }
273
274 } else {
275 if ( isset( $this->prop['groups'] ) && isset( $data[$u]['groups'] ) ) {
276 ApiResult::setArrayType( $data[$u]['groups'], 'array' );
277 ApiResult::setIndexedTagName( $data[$u]['groups'], 'g' );
278 }
279 if ( isset( $this->prop['groupmemberships'] ) && isset( $data[$u]['groupmemberships'] ) ) {
280 ApiResult::setArrayType( $data[$u]['groupmemberships'], 'array' );
281 ApiResult::setIndexedTagName( $data[$u]['groupmemberships'], 'groupmembership' );
282 }
283 if ( isset( $this->prop['implicitgroups'] ) && isset( $data[$u]['implicitgroups'] ) ) {
284 ApiResult::setArrayType( $data[$u]['implicitgroups'], 'array' );
285 ApiResult::setIndexedTagName( $data[$u]['implicitgroups'], 'g' );
286 }
287 if ( isset( $this->prop['rights'] ) && isset( $data[$u]['rights'] ) ) {
288 ApiResult::setArrayType( $data[$u]['rights'], 'array' );
289 ApiResult::setIndexedTagName( $data[$u]['rights'], 'r' );
290 }
291 }
292
293 $fit = $result->addValue( [ 'query', $this->getModuleName() ], null, $data[$u] );
294 if ( !$fit ) {
295 if ( $useNames ) {
296 $this->setContinueEnumParameter( 'users',
297 implode( '|', array_diff( $users, $done ) ) );
298 } else {
299 $this->setContinueEnumParameter( 'userids',
300 implode( '|', array_diff( $userids, $done ) ) );
301 }
302 break;
303 }
304 $done[] = $u;
305 }
306 $result->addIndexedTagName( [ 'query', $this->getModuleName() ], 'user' );
307 }
308
310 public function getCacheMode( $params ) {
311 if ( array_diff( (array)$params['prop'], static::$publicProps ) ) {
312 return 'anon-public-user-private';
313 } else {
314 return 'public';
315 }
316 }
317
319 public function getAllowedParams() {
320 return [
321 'prop' => [
322 ParamValidator::PARAM_ISMULTI => true,
323 ParamValidator::PARAM_TYPE => [
324 'blockinfo',
325 'groups',
326 'groupmemberships',
327 'implicitgroups',
328 'rights',
329 'editcount',
330 'registration',
331 'emailable',
332 'gender',
333 'centralids',
334 'cancreate',
335 'tempexpired',
336 // When adding a prop, consider whether it should be added
337 // to self::$publicProps
338 ],
340 ],
341 'attachedwiki' => null,
342 'users' => [
343 ParamValidator::PARAM_ISMULTI => true
344 ],
345 'userids' => [
346 ParamValidator::PARAM_ISMULTI => true,
347 ParamValidator::PARAM_TYPE => 'integer'
348 ],
349 ];
350 }
351
353 protected function getExamplesMessages() {
354 return [
355 'action=query&list=users&ususers=Example&usprop=groups|editcount|gender'
356 => 'apihelp-query+users-example-simple',
357 ];
358 }
359
361 public function getHelpUrls() {
362 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Users';
363 }
364}
365
367class_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:543
getResult()
Get the result object.
Definition ApiBase.php:682
const PARAM_HELP_MSG_PER_VALUE
((string|array|Message)[]) When PARAM_TYPE is an array, or 'string' with PARAM_ISMULTI,...
Definition ApiBase.php:207
requireMaxOneParameter( $params,... $required)
Dies if more than one parameter from a certain set of parameters are set and not false.
Definition ApiBase.php:998
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:823
getPermissionManager()
Obtain a PermissionManager instance that subclasses may use in their authorization checks.
Definition ApiBase.php:742
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.
__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)
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....
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:130
Service for formatting and validating API parameters.
Interface for temporary user creation config and name matching.