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