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