Localisation updates from https://translatewiki.net.
[mediawiki.git] / includes / api / ApiQueryUserInfo.php
blob76ef0e6cd1339b37dfc98716415a1fde9e83b81d
1 <?php
2 /**
3 * Copyright © 2007 Yuri Astrakhan "<Firstname><Lastname>@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\Config\Config;
26 use MediaWiki\MainConfigNames;
27 use MediaWiki\MediaWikiServices;
28 use MediaWiki\Permissions\PermissionStatus;
29 use MediaWiki\SpecialPage\SpecialPage;
30 use MediaWiki\User\Options\UserOptionsLookup;
31 use MediaWiki\User\TalkPageNotificationManager;
32 use MediaWiki\User\UserEditTracker;
33 use MediaWiki\User\UserGroupManager;
34 use MediaWiki\User\UserIdentity;
35 use MediaWiki\Utils\MWTimestamp;
36 use MediaWiki\Watchlist\WatchedItemStore;
37 use Wikimedia\ParamValidator\ParamValidator;
39 /**
40 * Query module to get information about the currently logged-in user
42 * @ingroup API
44 class ApiQueryUserInfo extends ApiQueryBase {
46 use ApiBlockInfoTrait;
48 private const WL_UNREAD_LIMIT = 1000;
50 /** @var array */
51 private $params = [];
53 /** @var array */
54 private $prop = [];
56 private TalkPageNotificationManager $talkPageNotificationManager;
57 private WatchedItemStore $watchedItemStore;
58 private UserEditTracker $userEditTracker;
59 private UserOptionsLookup $userOptionsLookup;
60 private UserGroupManager $userGroupManager;
62 public function __construct(
63 ApiQuery $query,
64 string $moduleName,
65 TalkPageNotificationManager $talkPageNotificationManager,
66 WatchedItemStore $watchedItemStore,
67 UserEditTracker $userEditTracker,
68 UserOptionsLookup $userOptionsLookup,
69 UserGroupManager $userGroupManager
70 ) {
71 parent::__construct( $query, $moduleName, 'ui' );
72 $this->talkPageNotificationManager = $talkPageNotificationManager;
73 $this->watchedItemStore = $watchedItemStore;
74 $this->userEditTracker = $userEditTracker;
75 $this->userOptionsLookup = $userOptionsLookup;
76 $this->userGroupManager = $userGroupManager;
79 public function execute() {
80 $this->params = $this->extractRequestParams();
81 $result = $this->getResult();
83 if ( $this->params['prop'] !== null ) {
84 $this->prop = array_fill_keys( $this->params['prop'], true );
87 $r = $this->getCurrentUserInfo();
88 $result->addValue( 'query', $this->getModuleName(), $r );
91 /**
92 * Get central user info
93 * @param Config $config
94 * @param UserIdentity $user
95 * @param string|false $attachedWiki
96 * @return array Central user info
97 * - centralids: Array mapping non-local Central ID provider names to IDs
98 * - attachedlocal: Array mapping Central ID provider names to booleans
99 * indicating whether the local user is attached.
100 * - attachedwiki: Array mapping Central ID provider names to booleans
101 * indicating whether the user is attached to $attachedWiki.
103 public static function getCentralUserInfo(
104 Config $config,
105 UserIdentity $user,
106 $attachedWiki = UserIdentity::LOCAL
108 $providerIds = array_keys( $config->get( MainConfigNames::CentralIdLookupProviders ) );
110 $ret = [
111 'centralids' => [],
112 'attachedlocal' => [],
114 ApiResult::setArrayType( $ret['centralids'], 'assoc' );
115 ApiResult::setArrayType( $ret['attachedlocal'], 'assoc' );
116 if ( $attachedWiki ) {
117 $ret['attachedwiki'] = [];
118 ApiResult::setArrayType( $ret['attachedwiki'], 'assoc' );
121 $name = $user->getName();
122 $centralIdLookupFactory = MediaWikiServices::getInstance()
123 ->getCentralIdLookupFactory();
124 foreach ( $providerIds as $providerId ) {
125 $provider = $centralIdLookupFactory->getLookup( $providerId );
126 $ret['centralids'][$providerId] = $provider->centralIdFromName( $name );
127 $ret['attachedlocal'][$providerId] = $provider->isAttached( $user );
128 if ( $attachedWiki ) {
129 $ret['attachedwiki'][$providerId] = $provider->isAttached( $user, $attachedWiki );
133 return $ret;
136 protected function getCurrentUserInfo() {
137 $user = $this->getUser();
138 $vals = [];
139 $vals['id'] = $user->getId();
140 $vals['name'] = $user->getName();
142 if ( !$user->isRegistered() ) {
143 $vals['anon'] = true;
146 if ( $user->isTemp() ) {
147 $vals['temp'] = true;
150 if ( isset( $this->prop['blockinfo'] ) ) {
151 $block = $user->getBlock();
152 if ( $block ) {
153 $vals = array_merge( $vals, $this->getBlockDetails( $block ) );
157 if ( isset( $this->prop['hasmsg'] ) ) {
158 $vals['messages'] = $this->talkPageNotificationManager->userHasNewMessages( $user );
161 if ( isset( $this->prop['groups'] ) ) {
162 $vals['groups'] = $this->userGroupManager->getUserEffectiveGroups( $user );
163 ApiResult::setArrayType( $vals['groups'], 'array' ); // even if empty
164 ApiResult::setIndexedTagName( $vals['groups'], 'g' ); // even if empty
167 if ( isset( $this->prop['groupmemberships'] ) ) {
168 $ugms = $this->userGroupManager->getUserGroupMemberships( $user );
169 $vals['groupmemberships'] = [];
170 foreach ( $ugms as $group => $ugm ) {
171 $vals['groupmemberships'][] = [
172 'group' => $group,
173 'expiry' => ApiResult::formatExpiry( $ugm->getExpiry() ),
176 ApiResult::setArrayType( $vals['groupmemberships'], 'array' ); // even if empty
177 ApiResult::setIndexedTagName( $vals['groupmemberships'], 'groupmembership' ); // even if empty
180 if ( isset( $this->prop['implicitgroups'] ) ) {
181 $vals['implicitgroups'] = $this->userGroupManager->getUserImplicitGroups( $user );
182 ApiResult::setArrayType( $vals['implicitgroups'], 'array' ); // even if empty
183 ApiResult::setIndexedTagName( $vals['implicitgroups'], 'g' ); // even if empty
186 if ( isset( $this->prop['rights'] ) ) {
187 $vals['rights'] = $this->getPermissionManager()->getUserPermissions( $user );
188 ApiResult::setArrayType( $vals['rights'], 'array' ); // even if empty
189 ApiResult::setIndexedTagName( $vals['rights'], 'r' ); // even if empty
192 if ( isset( $this->prop['changeablegroups'] ) ) {
193 $vals['changeablegroups'] = $this->userGroupManager->getGroupsChangeableBy( $this->getAuthority() );
194 ApiResult::setIndexedTagName( $vals['changeablegroups']['add'], 'g' );
195 ApiResult::setIndexedTagName( $vals['changeablegroups']['remove'], 'g' );
196 ApiResult::setIndexedTagName( $vals['changeablegroups']['add-self'], 'g' );
197 ApiResult::setIndexedTagName( $vals['changeablegroups']['remove-self'], 'g' );
200 if ( isset( $this->prop['options'] ) ) {
201 $vals['options'] = $this->userOptionsLookup->getOptions( $user );
202 $vals['options'][ApiResult::META_BC_BOOLS] = array_keys( $vals['options'] );
205 if ( isset( $this->prop['editcount'] ) ) {
206 // use intval to prevent null if a non-logged-in user calls
207 // api.php?format=jsonfm&action=query&meta=userinfo&uiprop=editcount
208 $vals['editcount'] = (int)$user->getEditCount();
211 if ( isset( $this->prop['ratelimits'] ) ) {
212 // true = real rate limits, taking User::isPingLimitable into account
213 $vals['ratelimits'] = $this->getRateLimits( true );
215 if ( isset( $this->prop['theoreticalratelimits'] ) ) {
216 // false = ignore User::isPingLimitable
217 $vals['theoreticalratelimits'] = $this->getRateLimits( false );
220 if ( isset( $this->prop['realname'] ) &&
221 !in_array( 'realname', $this->getConfig()->get( MainConfigNames::HiddenPrefs ) )
223 $vals['realname'] = $user->getRealName();
226 if ( $this->getAuthority()->isAllowed( 'viewmyprivateinfo' ) && isset( $this->prop['email'] ) ) {
227 $vals['email'] = $user->getEmail();
228 $auth = $user->getEmailAuthenticationTimestamp();
229 if ( $auth !== null ) {
230 $vals['emailauthenticated'] = wfTimestamp( TS_ISO_8601, $auth );
234 if ( isset( $this->prop['registrationdate'] ) ) {
235 $regDate = $user->getRegistration();
236 if ( $regDate !== false ) {
237 $vals['registrationdate'] = wfTimestampOrNull( TS_ISO_8601, $regDate );
241 if ( isset( $this->prop['acceptlang'] ) ) {
242 $langs = $this->getRequest()->getAcceptLang();
243 $acceptLang = [];
244 foreach ( $langs as $lang => $val ) {
245 $r = [ 'q' => $val ];
246 ApiResult::setContentValue( $r, 'code', $lang );
247 $acceptLang[] = $r;
249 ApiResult::setIndexedTagName( $acceptLang, 'lang' );
250 $vals['acceptlang'] = $acceptLang;
253 if ( isset( $this->prop['unreadcount'] ) ) {
254 $unreadNotifications = $this->watchedItemStore->countUnreadNotifications(
255 $user,
256 self::WL_UNREAD_LIMIT
259 if ( $unreadNotifications === true ) {
260 $vals['unreadcount'] = self::WL_UNREAD_LIMIT . '+';
261 } else {
262 $vals['unreadcount'] = $unreadNotifications;
266 if ( isset( $this->prop['centralids'] ) ) {
267 $vals += self::getCentralUserInfo(
268 $this->getConfig(), $this->getUser(), $this->params['attachedwiki']
272 if ( isset( $this->prop['latestcontrib'] ) ) {
273 $ts = $this->getLatestContributionTime();
274 if ( $ts !== null ) {
275 $vals['latestcontrib'] = $ts;
279 if ( isset( $this->prop['cancreateaccount'] ) ) {
280 $status = PermissionStatus::newEmpty();
281 $vals['cancreateaccount'] = $user->definitelyCan( 'createaccount',
282 SpecialPage::getTitleFor( 'CreateAccount' ), $status );
283 if ( !$status->isGood() ) {
284 $vals['cancreateaccounterror'] = $this->getErrorFormatter()->arrayFromStatus( $status );
288 return $vals;
292 * Get the rate limits that apply to the user, or the rate limits
293 * that would apply if the user didn't have `noratelimit`
295 * @param bool $applyNoRateLimit
296 * @return array
298 protected function getRateLimits( bool $applyNoRateLimit ) {
299 $retval = [
300 ApiResult::META_TYPE => 'assoc',
303 $user = $this->getUser();
304 if ( $applyNoRateLimit && !$user->isPingLimitable() ) {
305 return $retval; // No limits
308 // Find out which categories we belong to
309 $categories = [];
310 if ( !$user->isRegistered() ) {
311 $categories[] = 'anon';
312 } else {
313 $categories[] = 'user';
315 if ( $user->isNewbie() ) {
316 $categories[] = 'ip';
317 $categories[] = 'subnet';
318 if ( $user->isRegistered() ) {
319 $categories[] = 'newbie';
322 $categories = array_merge( $categories, $this->userGroupManager->getUserGroups( $user ) );
324 // Now get the actual limits
325 foreach ( $this->getConfig()->get( MainConfigNames::RateLimits ) as $action => $limits ) {
326 foreach ( $categories as $cat ) {
327 if ( isset( $limits[$cat] ) ) {
328 $retval[$action][$cat]['hits'] = (int)$limits[$cat][0];
329 $retval[$action][$cat]['seconds'] = (int)$limits[$cat][1];
334 return $retval;
338 * @return string|null ISO 8601 timestamp of current user's last contribution or null if none
340 protected function getLatestContributionTime() {
341 $timestamp = $this->userEditTracker->getLatestEditTimestamp( $this->getUser() );
342 if ( $timestamp === false ) {
343 return null;
345 return MWTimestamp::convert( TS_ISO_8601, $timestamp );
348 public function getAllowedParams() {
349 return [
350 'prop' => [
351 ParamValidator::PARAM_ISMULTI => true,
352 ParamValidator::PARAM_ALL => true,
353 ParamValidator::PARAM_TYPE => [
354 'blockinfo',
355 'hasmsg',
356 'groups',
357 'groupmemberships',
358 'implicitgroups',
359 'rights',
360 'changeablegroups',
361 'options',
362 'editcount',
363 'ratelimits',
364 'theoreticalratelimits',
365 'email',
366 'realname',
367 'acceptlang',
368 'registrationdate',
369 'unreadcount',
370 'centralids',
371 'latestcontrib',
372 'cancreateaccount',
374 ApiBase::PARAM_HELP_MSG_PER_VALUE => [
375 'unreadcount' => [
376 'apihelp-query+userinfo-paramvalue-prop-unreadcount',
377 self::WL_UNREAD_LIMIT - 1,
378 self::WL_UNREAD_LIMIT . '+',
382 'attachedwiki' => null,
386 protected function getExamplesMessages() {
387 return [
388 'action=query&meta=userinfo'
389 => 'apihelp-query+userinfo-example-simple',
390 'action=query&meta=userinfo&uiprop=blockinfo|groups|rights|hasmsg'
391 => 'apihelp-query+userinfo-example-data',
395 public function getHelpUrls() {
396 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Userinfo';
400 /** @deprecated class alias since 1.43 */
401 class_alias( ApiQueryUserInfo::class, 'ApiQueryUserInfo' );