MediaWiki  master
ApiQueryUserInfo.php
Go to the documentation of this file.
1 <?php
32 
39 
41 
42  private const WL_UNREAD_LIMIT = 1000;
43 
45  private $params = [];
46 
48  private $prop = [];
49 
53  private $talkPageNotificationManager;
54 
58  private $watchedItemStore;
59 
63  private $userEditTracker;
64 
68  private $userOptionsLookup;
69 
71  private $userGroupManager;
72 
82  public function __construct(
83  ApiQuery $query,
84  $moduleName,
85  TalkPageNotificationManager $talkPageNotificationManager,
86  WatchedItemStore $watchedItemStore,
87  UserEditTracker $userEditTracker,
88  UserOptionsLookup $userOptionsLookup,
89  UserGroupManager $userGroupManager
90  ) {
91  parent::__construct( $query, $moduleName, 'ui' );
92  $this->talkPageNotificationManager = $talkPageNotificationManager;
93  $this->watchedItemStore = $watchedItemStore;
94  $this->userEditTracker = $userEditTracker;
95  $this->userOptionsLookup = $userOptionsLookup;
96  $this->userGroupManager = $userGroupManager;
97  }
98 
99  public function execute() {
100  $this->params = $this->extractRequestParams();
101  $result = $this->getResult();
102 
103  if ( $this->params['prop'] !== null ) {
104  $this->prop = array_fill_keys( $this->params['prop'], true );
105  }
106 
107  $r = $this->getCurrentUserInfo();
108  $result->addValue( 'query', $this->getModuleName(), $r );
109  }
110 
123  public static function getCentralUserInfo(
124  Config $config,
125  UserIdentity $user,
126  $attachedWiki = UserIdentity::LOCAL
127  ) {
128  $providerIds = array_keys( $config->get( MainConfigNames::CentralIdLookupProviders ) );
129 
130  $ret = [
131  'centralids' => [],
132  'attachedlocal' => [],
133  ];
134  ApiResult::setArrayType( $ret['centralids'], 'assoc' );
135  ApiResult::setArrayType( $ret['attachedlocal'], 'assoc' );
136  if ( $attachedWiki ) {
137  $ret['attachedwiki'] = [];
138  ApiResult::setArrayType( $ret['attachedwiki'], 'assoc' );
139  }
140 
141  $name = $user->getName();
142  $centralIdLookupFactory = MediaWikiServices::getInstance()
143  ->getCentralIdLookupFactory();
144  foreach ( $providerIds as $providerId ) {
145  $provider = $centralIdLookupFactory->getLookup( $providerId );
146  $ret['centralids'][$providerId] = $provider->centralIdFromName( $name );
147  $ret['attachedlocal'][$providerId] = $provider->isAttached( $user );
148  if ( $attachedWiki ) {
149  $ret['attachedwiki'][$providerId] = $provider->isAttached( $user, $attachedWiki );
150  }
151  }
152 
153  return $ret;
154  }
155 
156  protected function getCurrentUserInfo() {
157  $user = $this->getUser();
158  $vals = [];
159  $vals['id'] = $user->getId();
160  $vals['name'] = $user->getName();
161 
162  if ( !$user->isRegistered() ) {
163  $vals['anon'] = true;
164  }
165 
166  if ( isset( $this->prop['blockinfo'] ) ) {
167  $block = $user->getBlock();
168  if ( $block ) {
169  $vals = array_merge( $vals, $this->getBlockDetails( $block ) );
170  }
171  }
172 
173  if ( isset( $this->prop['hasmsg'] ) ) {
174  $vals['messages'] = $this->talkPageNotificationManager->userHasNewMessages( $user );
175  }
176 
177  if ( isset( $this->prop['groups'] ) ) {
178  $vals['groups'] = $this->userGroupManager->getUserEffectiveGroups( $user );
179  ApiResult::setArrayType( $vals['groups'], 'array' ); // even if empty
180  ApiResult::setIndexedTagName( $vals['groups'], 'g' ); // even if empty
181  }
182 
183  if ( isset( $this->prop['groupmemberships'] ) ) {
184  $ugms = $this->userGroupManager->getUserGroupMemberships( $user );
185  $vals['groupmemberships'] = [];
186  foreach ( $ugms as $group => $ugm ) {
187  $vals['groupmemberships'][] = [
188  'group' => $group,
189  'expiry' => ApiResult::formatExpiry( $ugm->getExpiry() ),
190  ];
191  }
192  ApiResult::setArrayType( $vals['groupmemberships'], 'array' ); // even if empty
193  ApiResult::setIndexedTagName( $vals['groupmemberships'], 'groupmembership' ); // even if empty
194  }
195 
196  if ( isset( $this->prop['implicitgroups'] ) ) {
197  $vals['implicitgroups'] = $this->userGroupManager->getUserImplicitGroups( $user );
198  ApiResult::setArrayType( $vals['implicitgroups'], 'array' ); // even if empty
199  ApiResult::setIndexedTagName( $vals['implicitgroups'], 'g' ); // even if empty
200  }
201 
202  if ( isset( $this->prop['rights'] ) ) {
203  $vals['rights'] = $this->getPermissionManager()->getUserPermissions( $user );
204  ApiResult::setArrayType( $vals['rights'], 'array' ); // even if empty
205  ApiResult::setIndexedTagName( $vals['rights'], 'r' ); // even if empty
206  }
207 
208  if ( isset( $this->prop['changeablegroups'] ) ) {
209  $vals['changeablegroups'] = $this->userGroupManager->getGroupsChangeableBy( $this->getAuthority() );
210  ApiResult::setIndexedTagName( $vals['changeablegroups']['add'], 'g' );
211  ApiResult::setIndexedTagName( $vals['changeablegroups']['remove'], 'g' );
212  ApiResult::setIndexedTagName( $vals['changeablegroups']['add-self'], 'g' );
213  ApiResult::setIndexedTagName( $vals['changeablegroups']['remove-self'], 'g' );
214  }
215 
216  if ( isset( $this->prop['options'] ) ) {
217  $vals['options'] = $this->userOptionsLookup->getOptions( $user );
218  $vals['options'][ApiResult::META_BC_BOOLS] = array_keys( $vals['options'] );
219  }
220 
221  if ( isset( $this->prop['editcount'] ) ) {
222  // use intval to prevent null if a non-logged-in user calls
223  // api.php?format=jsonfm&action=query&meta=userinfo&uiprop=editcount
224  $vals['editcount'] = (int)$user->getEditCount();
225  }
226 
227  if ( isset( $this->prop['ratelimits'] ) ) {
228  // true = real rate limits, taking User::isPingLimitable into account
229  $vals['ratelimits'] = $this->getRateLimits( true );
230  }
231  if ( isset( $this->prop['theoreticalratelimits'] ) ) {
232  // false = ignore User::isPingLimitable
233  $vals['theoreticalratelimits'] = $this->getRateLimits( false );
234  }
235 
236  if ( isset( $this->prop['realname'] ) &&
237  !in_array( 'realname', $this->getConfig()->get( MainConfigNames::HiddenPrefs ) )
238  ) {
239  $vals['realname'] = $user->getRealName();
240  }
241 
242  if ( $this->getAuthority()->isAllowed( 'viewmyprivateinfo' ) && isset( $this->prop['email'] ) ) {
243  $vals['email'] = $user->getEmail();
244  $auth = $user->getEmailAuthenticationTimestamp();
245  if ( $auth !== null ) {
246  $vals['emailauthenticated'] = wfTimestamp( TS_ISO_8601, $auth );
247  }
248  }
249 
250  if ( isset( $this->prop['registrationdate'] ) ) {
251  $regDate = $user->getRegistration();
252  if ( $regDate !== false ) {
253  $vals['registrationdate'] = wfTimestamp( TS_ISO_8601, $regDate );
254  }
255  }
256 
257  if ( isset( $this->prop['acceptlang'] ) ) {
258  $langs = $this->getRequest()->getAcceptLang();
259  $acceptLang = [];
260  foreach ( $langs as $lang => $val ) {
261  $r = [ 'q' => $val ];
262  ApiResult::setContentValue( $r, 'code', $lang );
263  $acceptLang[] = $r;
264  }
265  ApiResult::setIndexedTagName( $acceptLang, 'lang' );
266  $vals['acceptlang'] = $acceptLang;
267  }
268 
269  if ( isset( $this->prop['unreadcount'] ) ) {
270  $unreadNotifications = $this->watchedItemStore->countUnreadNotifications(
271  $user,
272  self::WL_UNREAD_LIMIT
273  );
274 
275  if ( $unreadNotifications === true ) {
276  $vals['unreadcount'] = self::WL_UNREAD_LIMIT . '+';
277  } else {
278  $vals['unreadcount'] = $unreadNotifications;
279  }
280  }
281 
282  if ( isset( $this->prop['centralids'] ) ) {
283  $vals += self::getCentralUserInfo(
284  $this->getConfig(), $this->getUser(), $this->params['attachedwiki']
285  );
286  }
287 
288  if ( isset( $this->prop['latestcontrib'] ) ) {
289  $ts = $this->getLatestContributionTime();
290  if ( $ts !== null ) {
291  $vals['latestcontrib'] = $ts;
292  }
293  }
294 
295  if ( isset( $this->prop['cancreateaccount'] ) ) {
296  $status = PermissionStatus::newEmpty();
297  $vals['cancreateaccount'] = $user->definitelyCan( 'createaccount',
298  SpecialPage::getTitleFor( 'CreateAccount' ), $status );
299  if ( !$status->isGood() ) {
300  $vals['cancreateaccounterror'] = $this->getErrorFormatter()->arrayFromStatus( $status );
301  }
302  }
303 
304  return $vals;
305  }
306 
314  protected function getRateLimits( bool $applyNoRateLimit ) {
315  $retval = [
316  ApiResult::META_TYPE => 'assoc',
317  ];
318 
319  $user = $this->getUser();
320  if ( $applyNoRateLimit && !$user->isPingLimitable() ) {
321  return $retval; // No limits
322  }
323 
324  // Find out which categories we belong to
325  $categories = [];
326  if ( !$user->isRegistered() ) {
327  $categories[] = 'anon';
328  } else {
329  $categories[] = 'user';
330  }
331  if ( $user->isNewbie() ) {
332  $categories[] = 'ip';
333  $categories[] = 'subnet';
334  if ( $user->isRegistered() ) {
335  $categories[] = 'newbie';
336  }
337  }
338  $categories = array_merge( $categories, $this->userGroupManager->getUserGroups( $user ) );
339 
340  // Now get the actual limits
341  foreach ( $this->getConfig()->get( MainConfigNames::RateLimits ) as $action => $limits ) {
342  foreach ( $categories as $cat ) {
343  if ( isset( $limits[$cat] ) ) {
344  $retval[$action][$cat]['hits'] = (int)$limits[$cat][0];
345  $retval[$action][$cat]['seconds'] = (int)$limits[$cat][1];
346  }
347  }
348  }
349 
350  return $retval;
351  }
352 
356  protected function getLatestContributionTime() {
357  $timestamp = $this->userEditTracker->getLatestEditTimestamp( $this->getUser() );
358  if ( $timestamp === false ) {
359  return null;
360  }
361  return MWTimestamp::convert( TS_ISO_8601, $timestamp );
362  }
363 
364  public function getAllowedParams() {
365  return [
366  'prop' => [
367  ParamValidator::PARAM_ISMULTI => true,
368  ParamValidator::PARAM_ALL => true,
369  ParamValidator::PARAM_TYPE => [
370  'blockinfo',
371  'hasmsg',
372  'groups',
373  'groupmemberships',
374  'implicitgroups',
375  'rights',
376  'changeablegroups',
377  'options',
378  'editcount',
379  'ratelimits',
380  'theoreticalratelimits',
381  'email',
382  'realname',
383  'acceptlang',
384  'registrationdate',
385  'unreadcount',
386  'centralids',
387  'latestcontrib',
388  'cancreateaccount',
389  ],
391  'unreadcount' => [
392  'apihelp-query+userinfo-paramvalue-prop-unreadcount',
393  self::WL_UNREAD_LIMIT - 1,
394  self::WL_UNREAD_LIMIT . '+',
395  ],
396  ],
397  ],
398  'attachedwiki' => null,
399  ];
400  }
401 
402  protected function getExamplesMessages() {
403  return [
404  'action=query&meta=userinfo'
405  => 'apihelp-query+userinfo-example-simple',
406  'action=query&meta=userinfo&uiprop=blockinfo|groups|rights|hasmsg'
407  => 'apihelp-query+userinfo-example-data',
408  ];
409  }
410 
411  public function getHelpUrls() {
412  return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Userinfo';
413  }
414 }
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
getErrorFormatter()
Definition: ApiBase.php:641
getPermissionManager()
Obtain a PermissionManager instance that subclasses may use in their authorization checks.
Definition: ApiBase.php:687
const PARAM_HELP_MSG_PER_VALUE
((string|array|Message)[]) When PARAM_TYPE is an array, this is an array mapping those values to $msg...
Definition: ApiBase.php:197
getResult()
Get the result object.
Definition: ApiBase.php:630
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition: ApiBase.php:766
getModuleName()
Get the name of the module being executed by this instance.
Definition: ApiBase.php:499
This is a base class for all Query modules.
Query module to get information about the currently logged-in user.
getExamplesMessages()
Returns usage examples for this module.
static getCentralUserInfo(Config $config, UserIdentity $user, $attachedWiki=UserIdentity::LOCAL)
Get central user info.
getHelpUrls()
Return links to more detailed help pages about the module.
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
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.
__construct(ApiQuery $query, $moduleName, TalkPageNotificationManager $talkPageNotificationManager, WatchedItemStore $watchedItemStore, UserEditTracker $userEditTracker, UserOptionsLookup $userOptionsLookup, UserGroupManager $userGroupManager)
This is the main query class.
Definition: ApiQuery.php:41
const META_TYPE
Key for the 'type' metadata item.
Definition: ApiResult.php:110
static setArrayType(array &$arr, $type, $kvpKeyName=null)
Set the array data type.
Definition: ApiResult.php:716
const META_BC_BOOLS
Key for the 'BC bools' metadata item.
Definition: ApiResult.php:136
static setIndexedTagName(array &$arr, $tag)
Set the tag name for numeric-keyed values in XML format.
Definition: ApiResult.php:604
static setContentValue(array &$arr, $name, $value, $flags=0)
Add an output value to the array by name and mark as META_CONTENT.
Definition: ApiResult.php:467
static formatExpiry( $expiry, $infinity='infinity')
Format an expiry timestamp for API output.
Definition: ApiResult.php:1199
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
A StatusValue for permission errors.
Track info about user edit counts and timings.
Provides access to user options.
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,...
Storage layer class for WatchedItems.
Service for formatting and validating API parameters.
trait ApiBlockInfoTrait
Interface for configuration instances.
Definition: Config.php:30
get( $name)
Get a configuration variable such as "Sitename" or "UploadMaintenance.".
Interface for objects representing user identity.
if(!isset( $args[0])) $lang