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