Localisation updates from https://translatewiki.net.
[mediawiki.git] / includes / api / ApiQueryUsers.php
blob03c7bbed711f47a9a404a17d2bd34c9463e3ad18
1 <?php
2 /**
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
20 * @file
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;
33 /**
34 * Query module to get information about a list of users
36 * @ingroup API
38 class ApiQueryUsers extends ApiQueryBase {
39 use ApiQueryBlockInfoTrait;
41 /** @var array<string,true> */
42 private $prop;
44 private UserNameUtils $userNameUtils;
45 private UserFactory $userFactory;
46 private UserGroupManager $userGroupManager;
47 private GenderCache $genderCache;
48 private AuthManager $authManager;
50 /**
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.
53 * @var array
55 protected static $publicProps = [
56 // everything except 'blockinfo' which might show hidden records if the user
57 // making the request has the appropriate permissions
58 'groups',
59 'groupmemberships',
60 'implicitgroups',
61 'rights',
62 'editcount',
63 'registration',
64 'emailable',
65 'gender',
66 'centralids',
67 'cancreate',
70 public function __construct(
71 ApiQuery $query,
72 string $moduleName,
73 UserNameUtils $userNameUtils,
74 UserFactory $userFactory,
75 UserGroupManager $userGroupManager,
76 GenderCache $genderCache,
77 AuthManager $authManager
78 ) {
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() {
88 $db = $this->getDB();
89 $params = $this->extractRequestParams();
90 $this->requireMaxOneParameter( $params, 'userids', 'users' );
92 if ( $params['prop'] !== null ) {
93 $this->prop = array_fill_keys( $params['prop'], true );
94 } else {
95 $this->prop = [];
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() ],
110 null, $vals );
111 if ( !$fit ) {
112 $this->setContinueEnumParameter( 'users',
113 implode( '|', array_diff( $users, $done ) ) );
114 $goodNames = [];
115 break;
117 $done[] = $u;
118 } else {
119 $goodNames[] = $n;
123 if ( $useNames ) {
124 $parameters = &$goodNames;
125 } else {
126 $parameters = &$userids;
129 $result = $this->getResult();
131 if ( count( $parameters ) ) {
132 $this->getQueryBuilder()->merge( User::newQueryBuilder( $db ) );
133 if ( $useNames ) {
134 $this->addWhereFld( 'user_name', $goodNames );
135 } else {
136 $this->addWhereFld( 'user_id', $userids );
139 $this->addDeletedUserFilter();
141 $data = [];
142 $res = $this->select( __METHOD__ );
143 $this->resetQueryParams();
145 // get user groups if needed
146 if ( isset( $this->prop['groups'] ) || isset( $this->prop['rights'] ) ) {
147 $userGroups = [];
149 $this->addTables( 'user' );
150 if ( $useNames ) {
151 $this->addWhereFld( 'user_name', $goodNames );
152 } else {
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' ] );
160 $this->addWhere(
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'] ) ) {
170 $userNames = [];
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 );
179 } else {
180 $blockInfos = null;
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 );
188 } else {
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] ] );
194 if ( $useNames ) {
195 $key = $user->getName();
196 } else {
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 ) {
220 return [
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] ) ) {
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 );
270 } else {
271 $data[$u] = [ 'userid' => $u, 'missing' => true ];
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' );
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] );
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 ) ) );
302 break;
304 $done[] = $u;
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';
312 } else {
313 return 'public';
317 public function getAllowedParams() {
318 return [
319 'prop' => [
320 ParamValidator::PARAM_ISMULTI => true,
321 ParamValidator::PARAM_TYPE => [
322 'blockinfo',
323 'groups',
324 'groupmemberships',
325 'implicitgroups',
326 'rights',
327 'editcount',
328 'registration',
329 'emailable',
330 'gender',
331 'centralids',
332 'cancreate',
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,
339 'users' => [
340 ParamValidator::PARAM_ISMULTI => true
342 'userids' => [
343 ParamValidator::PARAM_ISMULTI => true,
344 ParamValidator::PARAM_TYPE => 'integer'
349 protected function getExamplesMessages() {
350 return [
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' );