1<?php 2/** 3 * Copyright © 2007 Yuri Astrakhan "<Firstname><Lastname>@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 23use MediaWiki\MediaWikiServices; 24 25/** 26 * Query module to get information about the currently logged-in user 27 * 28 * @ingroup API 29 */ 30class ApiQueryUserInfo extends ApiQueryBase { 31 32 use ApiBlockInfoTrait; 33 34 private const WL_UNREAD_LIMIT = 1000; 35 36 /** @var array */ 37 private $params = []; 38 /** @var array */ 39 private $prop = []; 40 41 public function __construct( ApiQuery $query, $moduleName ) { 42 parent::__construct( $query, $moduleName, 'ui' ); 43 } 44 45 public function execute() { 46 $this->params = $this->extractRequestParams(); 47 $result = $this->getResult(); 48 49 if ( $this->params['prop'] !== null ) { 50 $this->prop = array_flip( $this->params['prop'] ); 51 } 52 53 $r = $this->getCurrentUserInfo(); 54 $result->addValue( 'query', $this->getModuleName(), $r ); 55 } 56 57 /** 58 * Get central user info 59 * @param Config $config 60 * @param User $user 61 * @param string|null $attachedWiki 62 * @return array Central user info 63 * - centralids: Array mapping non-local Central ID provider names to IDs 64 * - attachedlocal: Array mapping Central ID provider names to booleans 65 * indicating whether the local user is attached. 66 * - attachedwiki: Array mapping Central ID provider names to booleans 67 * indicating whether the user is attached to $attachedWiki. 68 */ 69 public static function getCentralUserInfo( Config $config, User $user, $attachedWiki = null ) { 70 $providerIds = array_keys( $config->get( 'CentralIdLookupProviders' ) ); 71 72 $ret = [ 73 'centralids' => [], 74 'attachedlocal' => [], 75 ]; 76 ApiResult::setArrayType( $ret['centralids'], 'assoc' ); 77 ApiResult::setArrayType( $ret['attachedlocal'], 'assoc' ); 78 if ( $attachedWiki ) { 79 $ret['attachedwiki'] = []; 80 ApiResult::setArrayType( $ret['attachedwiki'], 'assoc' ); 81 } 82 83 $name = $user->getName(); 84 foreach ( $providerIds as $providerId ) { 85 $provider = CentralIdLookup::factory( $providerId ); 86 $ret['centralids'][$providerId] = $provider->centralIdFromName( $name ); 87 $ret['attachedlocal'][$providerId] = $provider->isAttached( $user ); 88 if ( $attachedWiki ) { 89 $ret['attachedwiki'][$providerId] = $provider->isAttached( $user, $attachedWiki ); 90 } 91 } 92 93 return $ret; 94 } 95 96 protected function getCurrentUserInfo() { 97 $user = $this->getUser(); 98 $vals = []; 99 $vals['id'] = (int)$user->getId(); 100 $vals['name'] = $user->getName(); 101 102 if ( $user->isAnon() ) { 103 $vals['anon'] = true; 104 } 105 106 if ( isset( $this->prop['blockinfo'] ) ) { 107 $block = $user->getBlock(); 108 if ( $block ) { 109 $vals = array_merge( $vals, $this->getBlockDetails( $block ) ); 110 } 111 } 112 113 if ( isset( $this->prop['hasmsg'] ) ) { 114 $vals['messages'] = MediaWikiServices::getInstance() 115 ->getTalkPageNotificationManager()->userHasNewMessages( $user ); 116 } 117 118 if ( isset( $this->prop['groups'] ) ) { 119 $vals['groups'] = $user->getEffectiveGroups(); 120 ApiResult::setArrayType( $vals['groups'], 'array' ); // even if empty 121 ApiResult::setIndexedTagName( $vals['groups'], 'g' ); // even if empty 122 } 123 124 if ( isset( $this->prop['groupmemberships'] ) ) { 125 $ugms = $user->getGroupMemberships(); 126 $vals['groupmemberships'] = []; 127 foreach ( $ugms as $group => $ugm ) { 128 $vals['groupmemberships'][] = [ 129 'group' => $group, 130 'expiry' => ApiResult::formatExpiry( $ugm->getExpiry() ), 131 ]; 132 } 133 ApiResult::setArrayType( $vals['groupmemberships'], 'array' ); // even if empty 134 ApiResult::setIndexedTagName( $vals['groupmemberships'], 'groupmembership' ); // even if empty 135 } 136 137 if ( isset( $this->prop['implicitgroups'] ) ) { 138 $vals['implicitgroups'] = $user->getAutomaticGroups(); 139 ApiResult::setArrayType( $vals['implicitgroups'], 'array' ); // even if empty 140 ApiResult::setIndexedTagName( $vals['implicitgroups'], 'g' ); // even if empty 141 } 142 143 if ( isset( $this->prop['rights'] ) ) { 144 $vals['rights'] = $this->getPermissionManager()->getUserPermissions( $user ); 145 ApiResult::setArrayType( $vals['rights'], 'array' ); // even if empty 146 ApiResult::setIndexedTagName( $vals['rights'], 'r' ); // even if empty 147 } 148 149 if ( isset( $this->prop['changeablegroups'] ) ) { 150 $vals['changeablegroups'] = $user->changeableGroups(); 151 ApiResult::setIndexedTagName( $vals['changeablegroups']['add'], 'g' ); 152 ApiResult::setIndexedTagName( $vals['changeablegroups']['remove'], 'g' ); 153 ApiResult::setIndexedTagName( $vals['changeablegroups']['add-self'], 'g' ); 154 ApiResult::setIndexedTagName( $vals['changeablegroups']['remove-self'], 'g' ); 155 } 156 157 if ( isset( $this->prop['options'] ) ) { 158 $vals['options'] = $user->getOptions(); 159 $vals['options'][ApiResult::META_BC_BOOLS] = array_keys( $vals['options'] ); 160 } 161 162 if ( isset( $this->prop['preferencestoken'] ) && 163 !$this->lacksSameOriginSecurity() && 164 $this->getPermissionManager()->userHasRight( $user, 'editmyoptions' ) 165 ) { 166 $vals['preferencestoken'] = $user->getEditToken( '', $this->getMain()->getRequest() ); 167 } 168 169 if ( isset( $this->prop['editcount'] ) ) { 170 // use intval to prevent null if a non-logged-in user calls 171 // api.php?format=jsonfm&action=query&meta=userinfo&uiprop=editcount 172 $vals['editcount'] = (int)$user->getEditCount(); 173 } 174 175 if ( isset( $this->prop['ratelimits'] ) ) { 176 $vals['ratelimits'] = $this->getRateLimits(); 177 } 178 179 if ( isset( $this->prop['realname'] ) && 180 !in_array( 'realname', $this->getConfig()->get( 'HiddenPrefs' ) ) 181 ) { 182 $vals['realname'] = $user->getRealName(); 183 } 184 185 if ( $this->getPermissionManager()->userHasRight( $user, 'viewmyprivateinfo' ) && 186 isset( $this->prop['email'] ) ) { 187 $vals['email'] = $user->getEmail(); 188 $auth = $user->getEmailAuthenticationTimestamp(); 189 if ( $auth !== null ) { 190 $vals['emailauthenticated'] = wfTimestamp( TS_ISO_8601, $auth ); 191 } 192 } 193 194 if ( isset( $this->prop['registrationdate'] ) ) { 195 $regDate = $user->getRegistration(); 196 if ( $regDate !== false ) { 197 $vals['registrationdate'] = wfTimestamp( TS_ISO_8601, $regDate ); 198 } 199 } 200 201 if ( isset( $this->prop['acceptlang'] ) ) { 202 $langs = $this->getRequest()->getAcceptLang(); 203 $acceptLang = []; 204 foreach ( $langs as $lang => $val ) { 205 $r = [ 'q' => $val ]; 206 ApiResult::setContentValue( $r, 'code', $lang ); 207 $acceptLang[] = $r; 208 } 209 ApiResult::setIndexedTagName( $acceptLang, 'lang' ); 210 $vals['acceptlang'] = $acceptLang; 211 } 212 213 if ( isset( $this->prop['unreadcount'] ) ) { 214 $store = MediaWikiServices::getInstance()->getWatchedItemStore(); 215 $unreadNotifications = $store->countUnreadNotifications( 216 $user, 217 self::WL_UNREAD_LIMIT 218 ); 219 220 if ( $unreadNotifications === true ) { 221 $vals['unreadcount'] = self::WL_UNREAD_LIMIT . '+'; 222 } else { 223 $vals['unreadcount'] = $unreadNotifications; 224 } 225 } 226 227 if ( isset( $this->prop['centralids'] ) ) { 228 $vals += self::getCentralUserInfo( 229 $this->getConfig(), $this->getUser(), $this->params['attachedwiki'] 230 ); 231 } 232 233 if ( isset( $this->prop['latestcontrib'] ) ) { 234 $ts = $this->getLatestContributionTime(); 235 if ( $ts !== null ) { 236 $vals['latestcontrib'] = $ts; 237 } 238 } 239 240 return $vals; 241 } 242 243 protected function getRateLimits() { 244 $retval = [ 245 ApiResult::META_TYPE => 'assoc', 246 ]; 247 248 $user = $this->getUser(); 249 if ( !$user->isPingLimitable() ) { 250 return $retval; // No limits 251 } 252 253 // Find out which categories we belong to 254 $categories = []; 255 if ( $user->isAnon() ) { 256 $categories[] = 'anon'; 257 } else { 258 $categories[] = 'user'; 259 } 260 if ( $user->isNewbie() ) { 261 $categories[] = 'ip'; 262 $categories[] = 'subnet'; 263 if ( !$user->isAnon() ) { 264 $categories[] = 'newbie'; 265 } 266 } 267 $categories = array_merge( $categories, $user->getGroups() ); 268 269 // Now get the actual limits 270 foreach ( $this->getConfig()->get( 'RateLimits' ) as $action => $limits ) { 271 foreach ( $categories as $cat ) { 272 if ( isset( $limits[$cat] ) ) { 273 $retval[$action][$cat]['hits'] = (int)$limits[$cat][0]; 274 $retval[$action][$cat]['seconds'] = (int)$limits[$cat][1]; 275 } 276 } 277 } 278 279 return $retval; 280 } 281 282 /** 283 * @return string|null ISO 8601 timestamp of current user's last contribution or null if none 284 */ 285 protected function getLatestContributionTime() { 286 $user = $this->getUser(); 287 $dbr = $this->getDB(); 288 289 if ( $user->getActorId() === null ) { 290 return null; 291 } 292 $res = $dbr->selectField( 'revision_actor_temp', 293 'MAX(revactor_timestamp)', 294 [ 'revactor_actor' => $user->getActorId() ], 295 __METHOD__ 296 ); 297 298 return $res ? wfTimestamp( TS_ISO_8601, $res ) : null; 299 } 300 301 public function getAllowedParams() { 302 return [ 303 'prop' => [ 304 ApiBase::PARAM_ISMULTI => true, 305 ApiBase::PARAM_ALL => true, 306 ApiBase::PARAM_TYPE => [ 307 'blockinfo', 308 'hasmsg', 309 'groups', 310 'groupmemberships', 311 'implicitgroups', 312 'rights', 313 'changeablegroups', 314 'options', 315 'editcount', 316 'ratelimits', 317 'email', 318 'realname', 319 'acceptlang', 320 'registrationdate', 321 'unreadcount', 322 'centralids', 323 'preferencestoken', 324 'latestcontrib', 325 ], 326 ApiBase::PARAM_HELP_MSG_PER_VALUE => [ 327 'unreadcount' => [ 328 'apihelp-query+userinfo-paramvalue-prop-unreadcount', 329 self::WL_UNREAD_LIMIT - 1, 330 self::WL_UNREAD_LIMIT . '+', 331 ], 332 ], 333 ApiBase::PARAM_DEPRECATED_VALUES => [ 334 'preferencestoken' => [ 335 'apiwarn-deprecation-withreplacement', 336 $this->getModulePrefix() . "prop=preferencestoken", 337 'action=query&meta=tokens', 338 ] 339 ], 340 ], 341 'attachedwiki' => null, 342 ]; 343 } 344 345 protected function getExamplesMessages() { 346 return [ 347 'action=query&meta=userinfo' 348 => 'apihelp-query+userinfo-example-simple', 349 'action=query&meta=userinfo&uiprop=blockinfo|groups|rights|hasmsg' 350 => 'apihelp-query+userinfo-example-data', 351 ]; 352 } 353 354 public function getHelpUrls() { 355 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Userinfo'; 356 } 357} 358