ApiQueryUserInfo.php 11 KB

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