3 * Copyright © 2007 Roan Kattouw <roan.kattouw@gmail.com>
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
23 namespace MediaWiki\Api
;
25 use MediaWiki\Auth\AuthManager
;
26 use MediaWiki\Cache\GenderCache
;
27 use MediaWiki\User\User
;
28 use MediaWiki\User\UserFactory
;
29 use MediaWiki\User\UserGroupManager
;
30 use MediaWiki\User\UserNameUtils
;
31 use Wikimedia\ParamValidator\ParamValidator
;
34 * Query module to get information about a list of users
38 class ApiQueryUsers
extends ApiQueryBase
{
39 use ApiQueryBlockInfoTrait
;
41 /** @var array<string,true> */
44 private UserNameUtils
$userNameUtils;
45 private UserFactory
$userFactory;
46 private UserGroupManager
$userGroupManager;
47 private GenderCache
$genderCache;
48 private AuthManager
$authManager;
51 * Properties whose contents does not depend on who is looking at them. If the usprops field
52 * contains anything not listed here, the cache mode will never be public for logged-in users.
55 protected static $publicProps = [
56 // everything except 'blockinfo' which might show hidden records if the user
57 // making the request has the appropriate permissions
70 public function __construct(
73 UserNameUtils
$userNameUtils,
74 UserFactory
$userFactory,
75 UserGroupManager
$userGroupManager,
76 GenderCache
$genderCache,
77 AuthManager
$authManager
79 parent
::__construct( $query, $moduleName, 'us' );
80 $this->userNameUtils
= $userNameUtils;
81 $this->userFactory
= $userFactory;
82 $this->userGroupManager
= $userGroupManager;
83 $this->genderCache
= $genderCache;
84 $this->authManager
= $authManager;
87 public function execute() {
89 $params = $this->extractRequestParams();
90 $this->requireMaxOneParameter( $params, 'userids', 'users' );
92 if ( $params['prop'] !== null ) {
93 $this->prop
= array_fill_keys( $params['prop'], true );
97 $useNames = $params['users'] !== null;
99 $users = (array)$params['users'];
100 $userids = (array)$params['userids'];
102 $goodNames = $done = [];
103 $result = $this->getResult();
104 // Canonicalize user names
105 foreach ( $users as $u ) {
106 $n = $this->userNameUtils
->getCanonical( $u );
107 if ( $n === false ||
$n === '' ) {
108 $vals = [ 'name' => $u, 'invalid' => true ];
109 $fit = $result->addValue( [ 'query', $this->getModuleName() ],
112 $this->setContinueEnumParameter( 'users',
113 implode( '|', array_diff( $users, $done ) ) );
124 $parameters = &$goodNames;
126 $parameters = &$userids;
129 $result = $this->getResult();
131 if ( count( $parameters ) ) {
132 $this->getQueryBuilder()->merge( User
::newQueryBuilder( $db ) );
134 $this->addWhereFld( 'user_name', $goodNames );
136 $this->addWhereFld( 'user_id', $userids );
139 $this->addDeletedUserFilter();
142 $res = $this->select( __METHOD__
);
143 $this->resetQueryParams();
145 // get user groups if needed
146 if ( isset( $this->prop
['groups'] ) ||
isset( $this->prop
['rights'] ) ) {
149 $this->addTables( 'user' );
151 $this->addWhereFld( 'user_name', $goodNames );
153 $this->addWhereFld( 'user_id', $userids );
156 $this->addTables( 'user_groups' );
157 $this->addJoinConds( [ 'user_groups' => [ 'JOIN', 'ug_user=user_id' ] ] );
158 $this->addFields( [ 'user_name' ] );
159 $this->addFields( [ 'ug_user', 'ug_group', 'ug_expiry' ] );
161 $db->expr( 'ug_expiry', '=', null )->or( 'ug_expiry', '>=', $db->timestamp() )
163 $userGroupsRes = $this->select( __METHOD__
);
165 foreach ( $userGroupsRes as $row ) {
166 $userGroups[$row->user_name
][] = $row;
169 if ( isset( $this->prop
['gender'] ) ) {
171 foreach ( $res as $row ) {
172 $userNames[] = $row->user_name
;
174 $this->genderCache
->doQuery( $userNames, __METHOD__
);
177 if ( isset( $this->prop
['blockinfo'] ) ) {
178 $blockInfos = $this->getBlockDetailsForRows( $res );
183 foreach ( $res as $row ) {
184 // create user object and pass along $userGroups if set
185 // that reduces the number of database queries needed in User dramatically
186 if ( !isset( $userGroups ) ) {
187 $user = $this->userFactory
->newFromRow( $row );
189 if ( !isset( $userGroups[$row->user_name
] ) ||
!is_array( $userGroups[$row->user_name
] ) ) {
190 $userGroups[$row->user_name
] = [];
192 $user = $this->userFactory
->newFromRow( $row, [ 'user_groups' => $userGroups[$row->user_name
] ] );
195 $key = $user->getName();
197 $key = $user->getId();
199 $data[$key]['userid'] = $user->getId();
200 $data[$key]['name'] = $user->getName();
202 if ( $user->isSystemUser() ) {
203 $data[$key]['systemuser'] = true;
206 if ( isset( $this->prop
['editcount'] ) ) {
207 $data[$key]['editcount'] = $user->getEditCount();
210 if ( isset( $this->prop
['registration'] ) ) {
211 $data[$key]['registration'] = wfTimestampOrNull( TS_ISO_8601
, $user->getRegistration() );
214 if ( isset( $this->prop
['groups'] ) ) {
215 $data[$key]['groups'] = $this->userGroupManager
->getUserEffectiveGroups( $user );
218 if ( isset( $this->prop
['groupmemberships'] ) ) {
219 $data[$key]['groupmemberships'] = array_map( static function ( $ugm ) {
221 'group' => $ugm->getGroup(),
222 'expiry' => ApiResult
::formatExpiry( $ugm->getExpiry() ),
224 }, $this->userGroupManager
->getUserGroupMemberships( $user ) );
227 if ( isset( $this->prop
['implicitgroups'] ) ) {
228 $data[$key]['implicitgroups'] = $this->userGroupManager
->getUserImplicitGroups( $user );
231 if ( isset( $this->prop
['rights'] ) ) {
232 $data[$key]['rights'] = $this->getPermissionManager()
233 ->getUserPermissions( $user );
235 if ( $row->hu_deleted
) {
236 $data[$key]['hidden'] = true;
238 if ( isset( $this->prop
['blockinfo'] ) && isset( $blockInfos[$row->user_id
] ) ) {
239 $data[$key] +
= $blockInfos[$row->user_id
];
242 if ( isset( $this->prop
['emailable'] ) ) {
243 $data[$key]['emailable'] = $user->canReceiveEmail();
246 if ( isset( $this->prop
['gender'] ) ) {
247 $data[$key]['gender'] = $this->genderCache
->getGenderOf( $user, __METHOD__
);
250 if ( isset( $this->prop
['centralids'] ) ) {
251 $data[$key] +
= ApiQueryUserInfo
::getCentralUserInfo(
252 $this->getConfig(), $user, $params['attachedwiki']
258 // Second pass: add result data to $retval
259 foreach ( $parameters as $u ) {
260 if ( !isset( $data[$u] ) ) {
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 );
271 $data[$u] = [ 'userid' => $u, 'missing' => true ];
275 if ( isset( $this->prop
['groups'] ) && isset( $data[$u]['groups'] ) ) {
276 ApiResult
::setArrayType( $data[$u]['groups'], 'array' );
277 ApiResult
::setIndexedTagName( $data[$u]['groups'], 'g' );
279 if ( isset( $this->prop
['groupmemberships'] ) && isset( $data[$u]['groupmemberships'] ) ) {
280 ApiResult
::setArrayType( $data[$u]['groupmemberships'], 'array' );
281 ApiResult
::setIndexedTagName( $data[$u]['groupmemberships'], 'groupmembership' );
283 if ( isset( $this->prop
['implicitgroups'] ) && isset( $data[$u]['implicitgroups'] ) ) {
284 ApiResult
::setArrayType( $data[$u]['implicitgroups'], 'array' );
285 ApiResult
::setIndexedTagName( $data[$u]['implicitgroups'], 'g' );
287 if ( isset( $this->prop
['rights'] ) && isset( $data[$u]['rights'] ) ) {
288 ApiResult
::setArrayType( $data[$u]['rights'], 'array' );
289 ApiResult
::setIndexedTagName( $data[$u]['rights'], 'r' );
293 $fit = $result->addValue( [ 'query', $this->getModuleName() ], null, $data[$u] );
296 $this->setContinueEnumParameter( 'users',
297 implode( '|', array_diff( $users, $done ) ) );
299 $this->setContinueEnumParameter( 'userids',
300 implode( '|', array_diff( $userids, $done ) ) );
306 $result->addIndexedTagName( [ 'query', $this->getModuleName() ], 'user' );
309 public function getCacheMode( $params ) {
310 if ( array_diff( (array)$params['prop'], static::$publicProps ) ) {
311 return 'anon-public-user-private';
317 public function getAllowedParams() {
320 ParamValidator
::PARAM_ISMULTI
=> true,
321 ParamValidator
::PARAM_TYPE
=> [
333 // When adding a prop, consider whether it should be added
334 // to self::$publicProps
336 ApiBase
::PARAM_HELP_MSG_PER_VALUE
=> [],
338 'attachedwiki' => null,
340 ParamValidator
::PARAM_ISMULTI
=> true
343 ParamValidator
::PARAM_ISMULTI
=> true,
344 ParamValidator
::PARAM_TYPE
=> 'integer'
349 protected function getExamplesMessages() {
351 'action=query&list=users&ususers=Example&usprop=groups|editcount|gender'
352 => 'apihelp-query+users-example-simple',
356 public function getHelpUrls() {
357 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Users';
361 /** @deprecated class alias since 1.43 */
362 class_alias( ApiQueryUsers
::class, 'ApiQueryUsers' );