Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 180 |
|
0.00% |
0 / 6 |
CRAP | |
0.00% |
0 / 1 |
ApiQueryUsers | |
0.00% |
0 / 179 |
|
0.00% |
0 / 6 |
3080 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 136 |
|
0.00% |
0 / 1 |
2450 | |||
getCacheMode | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
getAllowedParams | |
0.00% |
0 / 29 |
|
0.00% |
0 / 1 |
2 | |||
getExamplesMessages | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
getHelpUrls | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * Copyright © 2007 Roan Kattouw <roan.kattouw@gmail.com> |
4 | * |
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. |
9 | * |
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. |
14 | * |
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 |
19 | * |
20 | * @file |
21 | */ |
22 | |
23 | namespace MediaWiki\Api; |
24 | |
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; |
32 | |
33 | /** |
34 | * Query module to get information about a list of users |
35 | * |
36 | * @ingroup API |
37 | */ |
38 | class ApiQueryUsers extends ApiQueryBase { |
39 | use ApiQueryBlockInfoTrait; |
40 | |
41 | /** @var array<string,true> */ |
42 | private $prop; |
43 | |
44 | private UserNameUtils $userNameUtils; |
45 | private UserFactory $userFactory; |
46 | private UserGroupManager $userGroupManager; |
47 | private GenderCache $genderCache; |
48 | private AuthManager $authManager; |
49 | |
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 |
54 | */ |
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', |
68 | ]; |
69 | |
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; |
85 | } |
86 | |
87 | public function execute() { |
88 | $db = $this->getDB(); |
89 | $params = $this->extractRequestParams(); |
90 | $this->requireMaxOneParameter( $params, 'userids', 'users' ); |
91 | |
92 | if ( $params['prop'] !== null ) { |
93 | $this->prop = array_fill_keys( $params['prop'], true ); |
94 | } else { |
95 | $this->prop = []; |
96 | } |
97 | $useNames = $params['users'] !== null; |
98 | |
99 | $users = (array)$params['users']; |
100 | $userids = (array)$params['userids']; |
101 | |
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; |
116 | } |
117 | $done[] = $u; |
118 | } else { |
119 | $goodNames[] = $n; |
120 | } |
121 | } |
122 | |
123 | if ( $useNames ) { |
124 | $parameters = &$goodNames; |
125 | } else { |
126 | $parameters = &$userids; |
127 | } |
128 | |
129 | $result = $this->getResult(); |
130 | |
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 ); |
137 | } |
138 | |
139 | $this->addDeletedUserFilter(); |
140 | |
141 | $data = []; |
142 | $res = $this->select( __METHOD__ ); |
143 | $this->resetQueryParams(); |
144 | |
145 | // get user groups if needed |
146 | if ( isset( $this->prop['groups'] ) || isset( $this->prop['rights'] ) ) { |
147 | $userGroups = []; |
148 | |
149 | $this->addTables( 'user' ); |
150 | if ( $useNames ) { |
151 | $this->addWhereFld( 'user_name', $goodNames ); |
152 | } else { |
153 | $this->addWhereFld( 'user_id', $userids ); |
154 | } |
155 | |
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() ) |
162 | ); |
163 | $userGroupsRes = $this->select( __METHOD__ ); |
164 | |
165 | foreach ( $userGroupsRes as $row ) { |
166 | $userGroups[$row->user_name][] = $row; |
167 | } |
168 | } |
169 | if ( isset( $this->prop['gender'] ) ) { |
170 | $userNames = []; |
171 | foreach ( $res as $row ) { |
172 | $userNames[] = $row->user_name; |
173 | } |
174 | $this->genderCache->doQuery( $userNames, __METHOD__ ); |
175 | } |
176 | |
177 | if ( isset( $this->prop['blockinfo'] ) ) { |
178 | $blockInfos = $this->getBlockDetailsForRows( $res ); |
179 | } else { |
180 | $blockInfos = null; |
181 | } |
182 | |
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] = []; |
191 | } |
192 | $user = $this->userFactory->newFromRow( $row, [ 'user_groups' => $userGroups[$row->user_name] ] ); |
193 | } |
194 | if ( $useNames ) { |
195 | $key = $user->getName(); |
196 | } else { |
197 | $key = $user->getId(); |
198 | } |
199 | $data[$key]['userid'] = $user->getId(); |
200 | $data[$key]['name'] = $user->getName(); |
201 | |
202 | if ( $user->isSystemUser() ) { |
203 | $data[$key]['systemuser'] = true; |
204 | } |
205 | |
206 | if ( isset( $this->prop['editcount'] ) ) { |
207 | $data[$key]['editcount'] = $user->getEditCount(); |
208 | } |
209 | |
210 | if ( isset( $this->prop['registration'] ) ) { |
211 | $data[$key]['registration'] = wfTimestampOrNull( TS_ISO_8601, $user->getRegistration() ); |
212 | } |
213 | |
214 | if ( isset( $this->prop['groups'] ) ) { |
215 | $data[$key]['groups'] = $this->userGroupManager->getUserEffectiveGroups( $user ); |
216 | } |
217 | |
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() ), |
223 | ]; |
224 | }, $this->userGroupManager->getUserGroupMemberships( $user ) ); |
225 | } |
226 | |
227 | if ( isset( $this->prop['implicitgroups'] ) ) { |
228 | $data[$key]['implicitgroups'] = $this->userGroupManager->getUserImplicitGroups( $user ); |
229 | } |
230 | |
231 | if ( isset( $this->prop['rights'] ) ) { |
232 | $data[$key]['rights'] = $this->getPermissionManager() |
233 | ->getUserPermissions( $user ); |
234 | } |
235 | if ( $row->hu_deleted ) { |
236 | $data[$key]['hidden'] = true; |
237 | } |
238 | if ( isset( $this->prop['blockinfo'] ) && isset( $blockInfos[$row->user_id] ) ) { |
239 | $data[$key] += $blockInfos[$row->user_id]; |
240 | } |
241 | |
242 | if ( isset( $this->prop['emailable'] ) ) { |
243 | $data[$key]['emailable'] = $user->canReceiveEmail(); |
244 | } |
245 | |
246 | if ( isset( $this->prop['gender'] ) ) { |
247 | $data[$key]['gender'] = $this->genderCache->getGenderOf( $user, __METHOD__ ); |
248 | } |
249 | |
250 | if ( isset( $this->prop['centralids'] ) ) { |
251 | $data[$key] += ApiQueryUserInfo::getCentralUserInfo( |
252 | $this->getConfig(), $user, $params['attachedwiki'] |
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 | |
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'; |
314 | } |
315 | } |
316 | |
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 |
335 | ], |
336 | ApiBase::PARAM_HELP_MSG_PER_VALUE => [], |
337 | ], |
338 | 'attachedwiki' => null, |
339 | 'users' => [ |
340 | ParamValidator::PARAM_ISMULTI => true |
341 | ], |
342 | 'userids' => [ |
343 | ParamValidator::PARAM_ISMULTI => true, |
344 | ParamValidator::PARAM_TYPE => 'integer' |
345 | ], |
346 | ]; |
347 | } |
348 | |
349 | protected function getExamplesMessages() { |
350 | return [ |
351 | 'action=query&list=users&ususers=Example&usprop=groups|editcount|gender' |
352 | => 'apihelp-query+users-example-simple', |
353 | ]; |
354 | } |
355 | |
356 | public function getHelpUrls() { |
357 | return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Users'; |
358 | } |
359 | } |
360 | |
361 | /** @deprecated class alias since 1.43 */ |
362 | class_alias( ApiQueryUsers::class, 'ApiQueryUsers' ); |