Restore ApiQueryUserInfo::getBlockInfo() as a stub.
[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\MediaWikiServices;
24
25 /**
26 * Query module to get information about the currently logged-in user
27 *
28 * @ingroup API
29 */
30 class ApiQueryUserInfo extends ApiQueryBase {
31
32 use ApiBlockInfoTrait;
33
34 const WL_UNREAD_LIMIT = 1000;
35
36 private $params = [];
37 private $prop = [];
38
39 public function __construct( ApiQuery $query, $moduleName ) {
40 parent::__construct( $query, $moduleName, 'ui' );
41 }
42
43 public function execute() {
44 $this->params = $this->extractRequestParams();
45 $result = $this->getResult();
46
47 if ( !is_null( $this->params['prop'] ) ) {
48 $this->prop = array_flip( $this->params['prop'] );
49 }
50
51 $r = $this->getCurrentUserInfo();
52 $result->addValue( 'query', $this->getModuleName(), $r );
53 }
54
55 /**
56 * Get basic info about a given block
57 *
58 * @deprecated since 1.34 Use ApiBlockInfoTrait::getBlockDetails() instead.
59 * @param Block $block
60 * @return array See ApiBlockInfoTrait::getBlockDetails
61 */
62 public static function getBlockInfo( Block $block ) {
63 wfDeprecated( __METHOD__, '1.34' );
64
65 // Hack to access a private method from a trait:
66 $dummy = new class {
67 use ApiBlockInfoTrait {
68 getBlockDetails as public;
69 }
70 };
71
72 return $dummy->getBlockDetails( $block );
73 }
74
75 /**
76 * Get central user info
77 * @param Config $config
78 * @param User $user
79 * @param string|null $attachedWiki
80 * @return array Central user info
81 * - centralids: Array mapping non-local Central ID provider names to IDs
82 * - attachedlocal: Array mapping Central ID provider names to booleans
83 * indicating whether the local user is attached.
84 * - attachedwiki: Array mapping Central ID provider names to booleans
85 * indicating whether the user is attached to $attachedWiki.
86 */
87 public static function getCentralUserInfo( Config $config, User $user, $attachedWiki = null ) {
88 $providerIds = array_keys( $config->get( 'CentralIdLookupProviders' ) );
89
90 $ret = [
91 'centralids' => [],
92 'attachedlocal' => [],
93 ];
94 ApiResult::setArrayType( $ret['centralids'], 'assoc' );
95 ApiResult::setArrayType( $ret['attachedlocal'], 'assoc' );
96 if ( $attachedWiki ) {
97 $ret['attachedwiki'] = [];
98 ApiResult::setArrayType( $ret['attachedwiki'], 'assoc' );
99 }
100
101 $name = $user->getName();
102 foreach ( $providerIds as $providerId ) {
103 $provider = CentralIdLookup::factory( $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() {
115 $user = $this->getUser();
116 $vals = [];
117 $vals['id'] = (int)$user->getId();
118 $vals['name'] = $user->getName();
119
120 if ( $user->isAnon() ) {
121 $vals['anon'] = true;
122 }
123
124 if ( isset( $this->prop['blockinfo'] ) ) {
125 $block = $user->getBlock();
126 if ( $block ) {
127 $vals = array_merge( $vals, $this->getBlockDetails( $block ) );
128 }
129 }
130
131 if ( isset( $this->prop['hasmsg'] ) ) {
132 $vals['messages'] = $user->getNewtalk();
133 }
134
135 if ( isset( $this->prop['groups'] ) ) {
136 $vals['groups'] = $user->getEffectiveGroups();
137 ApiResult::setArrayType( $vals['groups'], 'array' ); // even if empty
138 ApiResult::setIndexedTagName( $vals['groups'], 'g' ); // even if empty
139 }
140
141 if ( isset( $this->prop['groupmemberships'] ) ) {
142 $ugms = $user->getGroupMemberships();
143 $vals['groupmemberships'] = [];
144 foreach ( $ugms as $group => $ugm ) {
145 $vals['groupmemberships'][] = [
146 'group' => $group,
147 'expiry' => ApiResult::formatExpiry( $ugm->getExpiry() ),
148 ];
149 }
150 ApiResult::setArrayType( $vals['groupmemberships'], 'array' ); // even if empty
151 ApiResult::setIndexedTagName( $vals['groupmemberships'], 'groupmembership' ); // even if empty
152 }
153
154 if ( isset( $this->prop['implicitgroups'] ) ) {
155 $vals['implicitgroups'] = $user->getAutomaticGroups();
156 ApiResult::setArrayType( $vals['implicitgroups'], 'array' ); // even if empty
157 ApiResult::setIndexedTagName( $vals['implicitgroups'], 'g' ); // even if empty
158 }
159
160 if ( isset( $this->prop['rights'] ) ) {
161 // User::getRights() may return duplicate values, strip them
162 $vals['rights'] = array_values( array_unique( $user->getRights() ) );
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 }