MediaWiki master
ApiQueryUserInfo.php
Go to the documentation of this file.
1<?php
9namespace MediaWiki\Api;
10
25use Wikimedia\Timestamp\TimestampFormat as TS;
26
33
35
36 private const WL_UNREAD_LIMIT = 1000;
37
39 private $params = [];
40
42 private $prop = [];
43
44 public function __construct(
45 ApiQuery $query,
46 string $moduleName,
47 private readonly TalkPageNotificationManager $talkPageNotificationManager,
48 private readonly WatchedItemStore $watchedItemStore,
49 private readonly UserEditTracker $userEditTracker,
50 private readonly UserOptionsLookup $userOptionsLookup,
51 private readonly UserGroupManager $userGroupManager,
52 private readonly WatchlistLabelStore $watchlistLabelStore,
53 ) {
54 parent::__construct( $query, $moduleName, 'ui' );
55 }
56
57 public function execute() {
58 $this->params = $this->extractRequestParams();
59 $result = $this->getResult();
60
61 if ( $this->params['prop'] !== null ) {
62 $this->prop = array_fill_keys( $this->params['prop'], true );
63 }
64
65 $r = $this->getCurrentUserInfo();
66 $result->addValue( 'query', $this->getModuleName(), $r );
67 }
68
81 public static function getCentralUserInfo(
82 Config $config,
83 UserIdentity $user,
84 $attachedWiki = UserIdentity::LOCAL
85 ) {
86 $providerIds = array_keys( $config->get( MainConfigNames::CentralIdLookupProviders ) );
87
88 $ret = [
89 'centralids' => [],
90 'attachedlocal' => [],
91 ];
92 ApiResult::setArrayType( $ret['centralids'], 'assoc' );
93 ApiResult::setArrayType( $ret['attachedlocal'], 'assoc' );
94 if ( $attachedWiki ) {
95 $ret['attachedwiki'] = [];
96 ApiResult::setArrayType( $ret['attachedwiki'], 'assoc' );
97 }
98
99 $name = $user->getName();
100 $centralIdLookupFactory = MediaWikiServices::getInstance()
101 ->getCentralIdLookupFactory();
102 foreach ( $providerIds as $providerId ) {
103 $provider = $centralIdLookupFactory->getLookup( $providerId );
104 $ret['centralids'][$providerId] = $provider->centralIdFromName( $name );
105 $ret['attachedlocal'][$providerId] = $provider->isAttached( $user );
106 if ( $attachedWiki ) {
107 $ret['attachedwiki'][$providerId] = $provider->isAttached( $user, $attachedWiki );
108 }
109 }
110
111 return $ret;
112 }
113
114 protected function getCurrentUserInfo(): array {
115 $user = $this->getUser();
116 $vals = [];
117 $vals['id'] = $user->getId();
118 $vals['name'] = $user->getName();
119
120 if ( !$user->isRegistered() ) {
121 $vals['anon'] = true;
122 }
123
124 if ( $user->isTemp() ) {
125 $vals['temp'] = true;
126 }
127
128 if ( isset( $this->prop['blockinfo'] ) ) {
129 $block = $user->getBlock();
130 if ( $block ) {
131 $vals = array_merge( $vals, $this->getBlockDetails( $block ) );
132 }
133 }
134
135 if ( isset( $this->prop['hasmsg'] ) ) {
136 $vals['messages'] = $this->talkPageNotificationManager->userHasNewMessages( $user );
137 }
138
139 if ( isset( $this->prop['groups'] ) ) {
140 $vals['groups'] = $this->userGroupManager->getUserEffectiveGroups( $user );
141 ApiResult::setArrayType( $vals['groups'], 'array' ); // even if empty
142 ApiResult::setIndexedTagName( $vals['groups'], 'g' ); // even if empty
143 }
144
145 if ( isset( $this->prop['groupmemberships'] ) ) {
146 $ugms = $this->userGroupManager->getUserGroupMemberships( $user );
147 $vals['groupmemberships'] = [];
148 foreach ( $ugms as $group => $ugm ) {
149 $vals['groupmemberships'][] = [
150 'group' => $group,
151 'expiry' => ApiResult::formatExpiry( $ugm->getExpiry() ),
152 ];
153 }
154 ApiResult::setArrayType( $vals['groupmemberships'], 'array' ); // even if empty
155 ApiResult::setIndexedTagName( $vals['groupmemberships'], 'groupmembership' ); // even if empty
156 }
157
158 if ( isset( $this->prop['implicitgroups'] ) ) {
159 $vals['implicitgroups'] = $this->userGroupManager->getUserImplicitGroups( $user );
160 ApiResult::setArrayType( $vals['implicitgroups'], 'array' ); // even if empty
161 ApiResult::setIndexedTagName( $vals['implicitgroups'], 'g' ); // even if empty
162 }
163
164 if ( isset( $this->prop['rights'] ) ) {
165 $vals['rights'] = $this->getPermissionManager()->getUserPermissions( $user );
166 ApiResult::setArrayType( $vals['rights'], 'array' ); // even if empty
167 ApiResult::setIndexedTagName( $vals['rights'], 'r' ); // even if empty
168 }
169
170 if ( isset( $this->prop['changeablegroups'] ) ) {
171 $vals['changeablegroups'] = $this->userGroupManager->getGroupsChangeableBy( $this->getAuthority() );
172 ApiResult::setIndexedTagName( $vals['changeablegroups']['add'], 'g' );
173 ApiResult::setIndexedTagName( $vals['changeablegroups']['remove'], 'g' );
174 ApiResult::setIndexedTagName( $vals['changeablegroups']['add-self'], 'g' );
175 ApiResult::setIndexedTagName( $vals['changeablegroups']['remove-self'], 'g' );
176 }
177
178 if ( isset( $this->prop['options'] ) ) {
179 $vals['options'] = $this->userOptionsLookup->getOptions( $user );
180 $vals['options'][ApiResult::META_BC_BOOLS] = array_keys( $vals['options'] );
181 }
182
183 if ( isset( $this->prop['editcount'] ) ) {
184 // use intval to prevent null if a non-logged-in user calls
185 // api.php?format=jsonfm&action=query&meta=userinfo&uiprop=editcount
186 $vals['editcount'] = (int)$user->getEditCount();
187 }
188
189 if ( isset( $this->prop['ratelimits'] ) ) {
190 // true = real rate limits, taking User::isPingLimitable into account
191 $vals['ratelimits'] = $this->getRateLimits( true );
192 }
193 if ( isset( $this->prop['theoreticalratelimits'] ) ) {
194 // false = ignore User::isPingLimitable
195 $vals['theoreticalratelimits'] = $this->getRateLimits( false );
196 }
197
198 if ( isset( $this->prop['realname'] ) &&
199 !in_array( 'realname', $this->getConfig()->get( MainConfigNames::HiddenPrefs ) )
200 ) {
201 $vals['realname'] = $user->getRealName();
202 }
203
204 if ( $this->getAuthority()->isAllowed( 'viewmyprivateinfo' ) && isset( $this->prop['email'] ) ) {
205 $vals['email'] = $user->getEmail();
206 $auth = $user->getEmailAuthenticationTimestamp();
207 if ( $auth !== null ) {
208 $vals['emailauthenticated'] = wfTimestamp( TS::ISO_8601, $auth );
209 }
210 }
211
212 if ( isset( $this->prop['registrationdate'] ) ) {
213 $regDate = $user->getRegistration();
214 if ( $regDate !== false ) {
215 $vals['registrationdate'] = wfTimestampOrNull( TS::ISO_8601, $regDate );
216 }
217 }
218
219 if ( isset( $this->prop['acceptlang'] ) ) {
220 $langs = $this->getRequest()->getAcceptLang();
221 $acceptLang = [];
222 foreach ( $langs as $lang => $val ) {
223 $r = [ 'q' => $val ];
224 ApiResult::setContentValue( $r, 'code', $lang );
225 $acceptLang[] = $r;
226 }
227 ApiResult::setIndexedTagName( $acceptLang, 'lang' );
228 $vals['acceptlang'] = $acceptLang;
229 }
230
231 if ( isset( $this->prop['unreadcount'] ) ) {
232 $unreadNotifications = $this->watchedItemStore->countUnreadNotifications(
233 $user,
234 self::WL_UNREAD_LIMIT
235 );
236
237 if ( $unreadNotifications === true ) {
238 $vals['unreadcount'] = self::WL_UNREAD_LIMIT . '+';
239 } else {
240 $vals['unreadcount'] = $unreadNotifications;
241 }
242 }
243
244 // T409375
245 if ( isset( $this->prop['watchlistlabels'] ) &&
247 ) {
248 $labels = $this->watchlistLabelStore->loadAllForUser( $user );
249 $wl = array_map( static fn ( $label ) => [
250 'id' => $label->getId(),
251 'name' => $label->getName(),
252 ], $labels );
253 ApiResult::setArrayType( $wl, 'array' );
254 ApiResult::setIndexedTagName( $wl, 'label' );
255 $vals['watchlistlabels'] = $wl;
256 }
257
258 if ( isset( $this->prop['centralids'] ) ) {
260 $this->getConfig(), $this->getUser(), $this->params['attachedwiki']
261 );
262 }
263
264 if ( isset( $this->prop['latestcontrib'] ) ) {
265 $ts = $this->getLatestContributionTime();
266 if ( $ts !== null ) {
267 $vals['latestcontrib'] = $ts;
268 }
269 }
270
271 if ( isset( $this->prop['cancreateaccount'] ) ) {
272 $status = PermissionStatus::newEmpty();
273 $vals['cancreateaccount'] = $user->definitelyCan( 'createaccount',
274 SpecialPage::getTitleFor( 'CreateAccount' ), $status );
275 if ( !$status->isGood() ) {
276 $vals['cancreateaccounterror'] = $this->getErrorFormatter()->arrayFromStatus( $status );
277 }
278 }
279
280 return $vals;
281 }
282
290 protected function getRateLimits( bool $applyNoRateLimit ) {
291 $retval = [
292 ApiResult::META_TYPE => 'assoc',
293 ];
294
295 $user = $this->getUser();
296 if ( $applyNoRateLimit && !$user->isPingLimitable() ) {
297 return $retval; // No limits
298 }
299
300 // Find out which categories we belong to
301 $categories = [];
302 if ( !$user->isRegistered() ) {
303 $categories[] = 'anon';
304 } else {
305 $categories[] = 'user';
306 }
307 if ( $user->isNewbie() ) {
308 $categories[] = 'ip';
309 $categories[] = 'subnet';
310 if ( $user->isRegistered() ) {
311 $categories[] = 'newbie';
312 }
313 }
314 $categories = array_merge( $categories, $this->userGroupManager->getUserGroups( $user ) );
315
316 // Now get the actual limits
317 foreach ( $this->getConfig()->get( MainConfigNames::RateLimits ) as $action => $limits ) {
318 foreach ( $categories as $cat ) {
319 if ( isset( $limits[$cat] ) ) {
320 $retval[$action][$cat]['hits'] = (int)$limits[$cat][0];
321 $retval[$action][$cat]['seconds'] = (int)$limits[$cat][1];
322 }
323 }
324 }
325
326 return $retval;
327 }
328
332 protected function getLatestContributionTime() {
333 $timestamp = $this->userEditTracker->getLatestEditTimestamp( $this->getUser() );
334 if ( $timestamp === false ) {
335 return null;
336 }
337 return MWTimestamp::convert( TS::ISO_8601, $timestamp );
338 }
339
341 public function getAllowedParams() {
342 return [
343 'prop' => [
344 ParamValidator::PARAM_ISMULTI => true,
345 ParamValidator::PARAM_ALL => true,
346 ParamValidator::PARAM_TYPE => [
347 'blockinfo',
348 'hasmsg',
349 'groups',
350 'groupmemberships',
351 'implicitgroups',
352 'rights',
353 'changeablegroups',
354 'options',
355 'editcount',
356 'ratelimits',
357 'theoreticalratelimits',
358 'email',
359 'realname',
360 'acceptlang',
361 'registrationdate',
362 'unreadcount',
363 'watchlistlabels',
364 'centralids',
365 'latestcontrib',
366 'cancreateaccount',
367 ],
369 'unreadcount' => [
370 'apihelp-query+userinfo-paramvalue-prop-unreadcount',
371 self::WL_UNREAD_LIMIT - 1,
372 self::WL_UNREAD_LIMIT . '+',
373 ],
374 ],
375 ],
376 'attachedwiki' => null,
377 ];
378 }
379
381 protected function getExamplesMessages() {
382 return [
383 'action=query&meta=userinfo'
384 => 'apihelp-query+userinfo-example-simple',
385 'action=query&meta=userinfo&uiprop=blockinfo|groups|rights|hasmsg'
386 => 'apihelp-query+userinfo-example-data',
387 ];
388 }
389
391 public function getHelpUrls() {
392 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Userinfo';
393 }
394}
395
397class_alias( ApiQueryUserInfo::class, 'ApiQueryUserInfo' );
wfTimestampOrNull( $outputtype=TS::UNIX, $ts=null)
Return a formatted timestamp, or null if input is null.
wfTimestamp( $outputtype=TS::UNIX, $ts=0)
Get a timestamp string in one of various formats.
getModuleName()
Get the name of the module being executed by this instance.
Definition ApiBase.php:542
getResult()
Get the result object.
Definition ApiBase.php:681
const PARAM_HELP_MSG_PER_VALUE
((string|array|Message)[]) When PARAM_TYPE is an array, or 'string' with PARAM_ISMULTI,...
Definition ApiBase.php:206
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:822
getPermissionManager()
Obtain a PermissionManager instance that subclasses may use in their authorization checks.
Definition ApiBase.php:741
This is a base class for all Query modules.
Query module to get information about the currently logged-in user.
getRateLimits(bool $applyNoRateLimit)
Get the rate limits that apply to the user, or the rate limits that would apply if the user didn't ha...
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
getHelpUrls()
Return links to more detailed help pages about the module.1.25, returning boolean false is deprecated...
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
static getCentralUserInfo(Config $config, UserIdentity $user, $attachedWiki=UserIdentity::LOCAL)
Get central user info.
__construct(ApiQuery $query, string $moduleName, private readonly TalkPageNotificationManager $talkPageNotificationManager, private readonly WatchedItemStore $watchedItemStore, private readonly UserEditTracker $userEditTracker, private readonly UserOptionsLookup $userOptionsLookup, private readonly UserGroupManager $userGroupManager, private readonly WatchlistLabelStore $watchlistLabelStore,)
getExamplesMessages()
Returns usage examples for this module.Return value has query strings as keys, with values being eith...
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.
static setContentValue(array &$arr, $name, $value, $flags=0)
Add an output value to the array by name and mark as META_CONTENT.
const META_BC_BOOLS
Key for the 'BC bools' metadata item.
const META_TYPE
Key for the 'type' metadata item.
A class containing constants representing the names of configuration variables.
const HiddenPrefs
Name constant for the HiddenPrefs setting, for use with Config::get()
const CentralIdLookupProviders
Name constant for the CentralIdLookupProviders setting, for use with Config::get()
const EnableWatchlistLabels
Name constant for the EnableWatchlistLabels setting, for use with Config::get()
const RateLimits
Name constant for the RateLimits setting, for use with Config::get()
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
A StatusValue for permission errors.
Parent class for all special pages.
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
Provides access to user options.
Track info about user edit counts and timings.
Manage user group memberships.
Library for creating and parsing MW-style timestamps.
Storage layer class for WatchedItems.
Service class for storage of watchlist labels.
Service for formatting and validating API parameters.
Interface for configuration instances.
Definition Config.php:18
get( $name)
Get a configuration variable such as "Sitename" or "UploadMaintenance.".
Interface for objects representing user identity.