Merge "mw.Feedback: If the message is posted remotely, link the title correctly"
[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->mReason;
71 $vals['blockedtimestamp'] = wfTimestamp( TS_ISO_8601, $block->mTimestamp );
72 $vals['blockexpiry'] = ApiResult::formatExpiry( $block->getExpiry(), 'infinite' );
73 if ( $block->getSystemBlockType() !== null ) {
74 $vals['systemblocktype'] = $block->getSystemBlockType();
75 }
76 return $vals;
77 }
78
79 /**
80 * Get central user info
81 * @param Config $config
82 * @param User $user
83 * @param string|null $attachedWiki
84 * @return array Central user info
85 * - centralids: Array mapping non-local Central ID provider names to IDs
86 * - attachedlocal: Array mapping Central ID provider names to booleans
87 * indicating whether the local user is attached.
88 * - attachedwiki: Array mapping Central ID provider names to booleans
89 * indicating whether the user is attached to $attachedWiki.
90 */
91 public static function getCentralUserInfo( Config $config, User $user, $attachedWiki = null ) {
92 $providerIds = array_keys( $config->get( 'CentralIdLookupProviders' ) );
93
94 $ret = [
95 'centralids' => [],
96 'attachedlocal' => [],
97 ];
98 ApiResult::setArrayType( $ret['centralids'], 'assoc' );
99 ApiResult::setArrayType( $ret['attachedlocal'], 'assoc' );
100 if ( $attachedWiki ) {
101 $ret['attachedwiki'] = [];
102 ApiResult::setArrayType( $ret['attachedwiki'], 'assoc' );
103 }
104
105 $name = $user->getName();
106 foreach ( $providerIds as $providerId ) {
107 $provider = CentralIdLookup::factory( $providerId );
108 $ret['centralids'][$providerId] = $provider->centralIdFromName( $name );
109 $ret['attachedlocal'][$providerId] = $provider->isAttached( $user );
110 if ( $attachedWiki ) {
111 $ret['attachedwiki'][$providerId] = $provider->isAttached( $user, $attachedWiki );
112 }
113 }
114
115 return $ret;
116 }
117
118 protected function getCurrentUserInfo() {
119 $user = $this->getUser();
120 $vals = [];
121 $vals['id'] = intval( $user->getId() );
122 $vals['name'] = $user->getName();
123
124 if ( $user->isAnon() ) {
125 $vals['anon'] = true;
126 }
127
128 if ( isset( $this->prop['blockinfo'] ) && $user->isBlocked() ) {
129 $vals = array_merge( $vals, self::getBlockInfo( $user->getBlock() ) );
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 // User::getRights() may return duplicate values, strip them
163 $vals['rights'] = array_values( array_unique( $user->getRights() ) );
164 ApiResult::setArrayType( $vals['rights'], 'array' ); // even if empty
165 ApiResult::setIndexedTagName( $vals['rights'], 'r' ); // even if empty
166 }
167
168 if ( isset( $this->prop['changeablegroups'] ) ) {
169 $vals['changeablegroups'] = $user->changeableGroups();
170 ApiResult::setIndexedTagName( $vals['changeablegroups']['add'], 'g' );
171 ApiResult::setIndexedTagName( $vals['changeablegroups']['remove'], 'g' );
172 ApiResult::setIndexedTagName( $vals['changeablegroups']['add-self'], 'g' );
173 ApiResult::setIndexedTagName( $vals['changeablegroups']['remove-self'], 'g' );
174 }
175
176 if ( isset( $this->prop['options'] ) ) {
177 $vals['options'] = $user->getOptions();
178 $vals['options'][ApiResult::META_BC_BOOLS] = array_keys( $vals['options'] );
179 }
180
181 if ( isset( $this->prop['preferencestoken'] ) &&
182 !$this->lacksSameOriginSecurity() &&
183 $user->isAllowed( 'editmyoptions' )
184 ) {
185 $vals['preferencestoken'] = $user->getEditToken( '', $this->getMain()->getRequest() );
186 }
187
188 if ( isset( $this->prop['editcount'] ) ) {
189 // use intval to prevent null if a non-logged-in user calls
190 // api.php?format=jsonfm&action=query&meta=userinfo&uiprop=editcount
191 $vals['editcount'] = intval( $user->getEditCount() );
192 }
193
194 if ( isset( $this->prop['ratelimits'] ) ) {
195 $vals['ratelimits'] = $this->getRateLimits();
196 }
197
198 if ( isset( $this->prop['realname'] ) &&
199 !in_array( 'realname', $this->getConfig()->get( 'HiddenPrefs' ) )
200 ) {
201 $vals['realname'] = $user->getRealName();
202 }
203
204 if ( $user->isAllowed( 'viewmyprivateinfo' ) ) {
205 if ( isset( $this->prop['email'] ) ) {
206 $vals['email'] = $user->getEmail();
207 $auth = $user->getEmailAuthenticationTimestamp();
208 if ( !is_null( $auth ) ) {
209 $vals['emailauthenticated'] = wfTimestamp( TS_ISO_8601, $auth );
210 }
211 }
212 }
213
214 if ( isset( $this->prop['registrationdate'] ) ) {
215 $regDate = $user->getRegistration();
216 if ( $regDate !== false ) {
217 $vals['registrationdate'] = wfTimestamp( TS_ISO_8601, $regDate );
218 }
219 }
220
221 if ( isset( $this->prop['acceptlang'] ) ) {
222 $langs = $this->getRequest()->getAcceptLang();
223 $acceptLang = [];
224 foreach ( $langs as $lang => $val ) {
225 $r = [ 'q' => $val ];
226 ApiResult::setContentValue( $r, 'code', $lang );
227 $acceptLang[] = $r;
228 }
229 ApiResult::setIndexedTagName( $acceptLang, 'lang' );
230 $vals['acceptlang'] = $acceptLang;
231 }
232
233 if ( isset( $this->prop['unreadcount'] ) ) {
234 $store = MediaWikiServices::getInstance()->getWatchedItemStore();
235 $unreadNotifications = $store->countUnreadNotifications(
236 $user,
237 self::WL_UNREAD_LIMIT
238 );
239
240 if ( $unreadNotifications === true ) {
241 $vals['unreadcount'] = self::WL_UNREAD_LIMIT . '+';
242 } else {
243 $vals['unreadcount'] = $unreadNotifications;
244 }
245 }
246
247 if ( isset( $this->prop['centralids'] ) ) {
248 $vals += self::getCentralUserInfo(
249 $this->getConfig(), $this->getUser(), $this->params['attachedwiki']
250 );
251 }
252
253 return $vals;
254 }
255
256 protected function getRateLimits() {
257 $retval = [
258 ApiResult::META_TYPE => 'assoc',
259 ];
260
261 $user = $this->getUser();
262 if ( !$user->isPingLimitable() ) {
263 return $retval; // No limits
264 }
265
266 // Find out which categories we belong to
267 $categories = [];
268 if ( $user->isAnon() ) {
269 $categories[] = 'anon';
270 } else {
271 $categories[] = 'user';
272 }
273 if ( $user->isNewbie() ) {
274 $categories[] = 'ip';
275 $categories[] = 'subnet';
276 if ( !$user->isAnon() ) {
277 $categories[] = 'newbie';
278 }
279 }
280 $categories = array_merge( $categories, $user->getGroups() );
281
282 // Now get the actual limits
283 foreach ( $this->getConfig()->get( 'RateLimits' ) as $action => $limits ) {
284 foreach ( $categories as $cat ) {
285 if ( isset( $limits[$cat] ) && !is_null( $limits[$cat] ) ) {
286 $retval[$action][$cat]['hits'] = intval( $limits[$cat][0] );
287 $retval[$action][$cat]['seconds'] = intval( $limits[$cat][1] );
288 }
289 }
290 }
291
292 return $retval;
293 }
294
295 public function getAllowedParams() {
296 return [
297 'prop' => [
298 ApiBase::PARAM_ISMULTI => true,
299 ApiBase::PARAM_TYPE => [
300 'blockinfo',
301 'hasmsg',
302 'groups',
303 'groupmemberships',
304 'implicitgroups',
305 'rights',
306 'changeablegroups',
307 'options',
308 'editcount',
309 'ratelimits',
310 'email',
311 'realname',
312 'acceptlang',
313 'registrationdate',
314 'unreadcount',
315 'centralids',
316 'preferencestoken',
317 ],
318 ApiBase::PARAM_HELP_MSG_PER_VALUE => [
319 'unreadcount' => [
320 'apihelp-query+userinfo-paramvalue-prop-unreadcount',
321 self::WL_UNREAD_LIMIT - 1,
322 self::WL_UNREAD_LIMIT . '+',
323 ],
324 ],
325 ApiBase::PARAM_DEPRECATED_VALUES => [
326 'preferencestoken' => [
327 'apiwarn-deprecation-withreplacement',
328 $this->getModulePrefix() . "prop=preferencestoken",
329 'action=query&meta=tokens',
330 ]
331 ],
332 ],
333 'attachedwiki' => null,
334 ];
335 }
336
337 protected function getExamplesMessages() {
338 return [
339 'action=query&meta=userinfo'
340 => 'apihelp-query+userinfo-example-simple',
341 'action=query&meta=userinfo&uiprop=blockinfo|groups|rights|hasmsg'
342 => 'apihelp-query+userinfo-example-data',
343 ];
344 }
345
346 public function getHelpUrls() {
347 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Userinfo';
348 }
349 }