]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - includes/api/ApiQueryAllUsers.php
MediaWiki 1.30.2-scripts2
[autoinstalls/mediawiki.git] / includes / api / ApiQueryAllUsers.php
1 <?php
2 /**
3  *
4  *
5  * Created on July 7, 2007
6  *
7  * Copyright © 2007 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License along
20  * with this program; if not, write to the Free Software Foundation, Inc.,
21  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22  * http://www.gnu.org/copyleft/gpl.html
23  *
24  * @file
25  */
26
27 /**
28  * Query module to enumerate all registered users.
29  *
30  * @ingroup API
31  */
32 class ApiQueryAllUsers extends ApiQueryBase {
33         public function __construct( ApiQuery $query, $moduleName ) {
34                 parent::__construct( $query, $moduleName, 'au' );
35         }
36
37         /**
38          * This function converts the user name to a canonical form
39          * which is stored in the database.
40          * @param string $name
41          * @return string
42          */
43         private function getCanonicalUserName( $name ) {
44                 return strtr( $name, '_', ' ' );
45         }
46
47         public function execute() {
48                 $params = $this->extractRequestParams();
49                 $activeUserDays = $this->getConfig()->get( 'ActiveUserDays' );
50
51                 $db = $this->getDB();
52                 $commentStore = new CommentStore( 'ipb_reason' );
53
54                 $prop = $params['prop'];
55                 if ( !is_null( $prop ) ) {
56                         $prop = array_flip( $prop );
57                         $fld_blockinfo = isset( $prop['blockinfo'] );
58                         $fld_editcount = isset( $prop['editcount'] );
59                         $fld_groups = isset( $prop['groups'] );
60                         $fld_rights = isset( $prop['rights'] );
61                         $fld_registration = isset( $prop['registration'] );
62                         $fld_implicitgroups = isset( $prop['implicitgroups'] );
63                         $fld_centralids = isset( $prop['centralids'] );
64                 } else {
65                         $fld_blockinfo = $fld_editcount = $fld_groups = $fld_registration =
66                                 $fld_rights = $fld_implicitgroups = $fld_centralids = false;
67                 }
68
69                 $limit = $params['limit'];
70
71                 $this->addTables( 'user' );
72
73                 $dir = ( $params['dir'] == 'descending' ? 'older' : 'newer' );
74                 $from = is_null( $params['from'] ) ? null : $this->getCanonicalUserName( $params['from'] );
75                 $to = is_null( $params['to'] ) ? null : $this->getCanonicalUserName( $params['to'] );
76
77                 # MySQL can't figure out that 'user_name' and 'qcc_title' are the same
78                 # despite the JOIN condition, so manually sort on the correct one.
79                 $userFieldToSort = $params['activeusers'] ? 'qcc_title' : 'user_name';
80
81                 # Some of these subtable joins are going to give us duplicate rows, so
82                 # calculate the maximum number of duplicates we might see.
83                 $maxDuplicateRows = 1;
84
85                 $this->addWhereRange( $userFieldToSort, $dir, $from, $to );
86
87                 if ( !is_null( $params['prefix'] ) ) {
88                         $this->addWhere( $userFieldToSort .
89                                 $db->buildLike( $this->getCanonicalUserName( $params['prefix'] ), $db->anyString() ) );
90                 }
91
92                 if ( !is_null( $params['rights'] ) && count( $params['rights'] ) ) {
93                         $groups = [];
94                         foreach ( $params['rights'] as $r ) {
95                                 $groups = array_merge( $groups, User::getGroupsWithPermission( $r ) );
96                         }
97
98                         // no group with the given right(s) exists, no need for a query
99                         if ( !count( $groups ) ) {
100                                 $this->getResult()->addIndexedTagName( [ 'query', $this->getModuleName() ], '' );
101
102                                 return;
103                         }
104
105                         $groups = array_unique( $groups );
106
107                         if ( is_null( $params['group'] ) ) {
108                                 $params['group'] = $groups;
109                         } else {
110                                 $params['group'] = array_unique( array_merge( $params['group'], $groups ) );
111                         }
112                 }
113
114                 $this->requireMaxOneParameter( $params, 'group', 'excludegroup' );
115
116                 if ( !is_null( $params['group'] ) && count( $params['group'] ) ) {
117                         // Filter only users that belong to a given group. This might
118                         // produce as many rows-per-user as there are groups being checked.
119                         $this->addTables( 'user_groups', 'ug1' );
120                         $this->addJoinConds( [
121                                 'ug1' => [
122                                         'INNER JOIN',
123                                         [
124                                                 'ug1.ug_user=user_id',
125                                                 'ug1.ug_group' => $params['group'],
126                                                 'ug1.ug_expiry IS NULL OR ug1.ug_expiry >= ' . $db->addQuotes( $db->timestamp() )
127                                         ]
128                                 ]
129                         ] );
130                         $maxDuplicateRows *= count( $params['group'] );
131                 }
132
133                 if ( !is_null( $params['excludegroup'] ) && count( $params['excludegroup'] ) ) {
134                         // Filter only users don't belong to a given group. This can only
135                         // produce one row-per-user, because we only keep on "no match".
136                         $this->addTables( 'user_groups', 'ug1' );
137
138                         if ( count( $params['excludegroup'] ) == 1 ) {
139                                 $exclude = [ 'ug1.ug_group' => $params['excludegroup'][0] ];
140                         } else {
141                                 $exclude = [ $db->makeList(
142                                         [ 'ug1.ug_group' => $params['excludegroup'] ],
143                                         LIST_OR
144                                 ) ];
145                         }
146                         $this->addJoinConds( [ 'ug1' => [ 'LEFT OUTER JOIN',
147                                 array_merge( [
148                                         'ug1.ug_user=user_id',
149                                         'ug1.ug_expiry IS NULL OR ug1.ug_expiry >= ' . $db->addQuotes( $db->timestamp() )
150                                 ], $exclude )
151                         ] ] );
152                         $this->addWhere( 'ug1.ug_user IS NULL' );
153                 }
154
155                 if ( $params['witheditsonly'] ) {
156                         $this->addWhere( 'user_editcount > 0' );
157                 }
158
159                 $this->showHiddenUsersAddBlockInfo( $fld_blockinfo );
160
161                 if ( $fld_groups || $fld_rights ) {
162                         $this->addFields( [ 'groups' =>
163                                 $db->buildGroupConcatField( '|', 'user_groups', 'ug_group', [
164                                         'ug_user=user_id',
165                                         'ug_expiry IS NULL OR ug_expiry >= ' . $db->addQuotes( $db->timestamp() )
166                                 ] )
167                         ] );
168                 }
169
170                 if ( $params['activeusers'] ) {
171                         $activeUserSeconds = $activeUserDays * 86400;
172
173                         // Filter query to only include users in the active users cache.
174                         // There shouldn't be any duplicate rows in querycachetwo here.
175                         $this->addTables( 'querycachetwo' );
176                         $this->addJoinConds( [ 'querycachetwo' => [
177                                 'INNER JOIN', [
178                                         'qcc_type' => 'activeusers',
179                                         'qcc_namespace' => NS_USER,
180                                         'qcc_title=user_name',
181                                 ],
182                         ] ] );
183
184                         // Actually count the actions using a subquery (T66505 and T66507)
185                         $timestamp = $db->timestamp( wfTimestamp( TS_UNIX ) - $activeUserSeconds );
186                         $this->addFields( [
187                                 'recentactions' => '(' . $db->selectSQLText(
188                                         'recentchanges',
189                                         'COUNT(*)',
190                                         [
191                                                 'rc_user_text = user_name',
192                                                 'rc_type != ' . $db->addQuotes( RC_EXTERNAL ), // no wikidata
193                                                 'rc_log_type IS NULL OR rc_log_type != ' . $db->addQuotes( 'newusers' ),
194                                                 'rc_timestamp >= ' . $db->addQuotes( $timestamp ),
195                                         ]
196                                 ) . ')'
197                         ] );
198                 }
199
200                 $sqlLimit = $limit + $maxDuplicateRows;
201                 $this->addOption( 'LIMIT', $sqlLimit );
202
203                 $this->addFields( [
204                         'user_name',
205                         'user_id'
206                 ] );
207                 $this->addFieldsIf( 'user_editcount', $fld_editcount );
208                 $this->addFieldsIf( 'user_registration', $fld_registration );
209
210                 $res = $this->select( __METHOD__ );
211                 $count = 0;
212                 $countDuplicates = 0;
213                 $lastUser = false;
214                 $result = $this->getResult();
215                 foreach ( $res as $row ) {
216                         $count++;
217
218                         if ( $lastUser === $row->user_name ) {
219                                 // Duplicate row due to one of the needed subtable joins.
220                                 // Ignore it, but count the number of them to sanely handle
221                                 // miscalculation of $maxDuplicateRows.
222                                 $countDuplicates++;
223                                 if ( $countDuplicates == $maxDuplicateRows ) {
224                                         ApiBase::dieDebug( __METHOD__, 'Saw more duplicate rows than expected' );
225                                 }
226                                 continue;
227                         }
228
229                         $countDuplicates = 0;
230                         $lastUser = $row->user_name;
231
232                         if ( $count > $limit ) {
233                                 // We've reached the one extra which shows that there are
234                                 // additional pages to be had. Stop here...
235                                 $this->setContinueEnumParameter( 'from', $row->user_name );
236                                 break;
237                         }
238
239                         if ( $count == $sqlLimit ) {
240                                 // Should never hit this (either the $countDuplicates check or
241                                 // the $count > $limit check should hit first), but check it
242                                 // anyway just in case.
243                                 ApiBase::dieDebug( __METHOD__, 'Saw more duplicate rows than expected' );
244                         }
245
246                         if ( $params['activeusers'] && $row->recentactions === 0 ) {
247                                 // activeusers cache was out of date
248                                 continue;
249                         }
250
251                         $data = [
252                                 'userid' => (int)$row->user_id,
253                                 'name' => $row->user_name,
254                         ];
255
256                         if ( $fld_centralids ) {
257                                 $data += ApiQueryUserInfo::getCentralUserInfo(
258                                         $this->getConfig(), User::newFromId( $row->user_id ), $params['attachedwiki']
259                                 );
260                         }
261
262                         if ( $fld_blockinfo && !is_null( $row->ipb_by_text ) ) {
263                                 $data['blockid'] = (int)$row->ipb_id;
264                                 $data['blockedby'] = $row->ipb_by_text;
265                                 $data['blockedbyid'] = (int)$row->ipb_by;
266                                 $data['blockedtimestamp'] = wfTimestamp( TS_ISO_8601, $row->ipb_timestamp );
267                                 $data['blockreason'] = $commentStore->getComment( $row )->text;
268                                 $data['blockexpiry'] = $row->ipb_expiry;
269                         }
270                         if ( $row->ipb_deleted ) {
271                                 $data['hidden'] = true;
272                         }
273                         if ( $fld_editcount ) {
274                                 $data['editcount'] = intval( $row->user_editcount );
275                         }
276                         if ( $params['activeusers'] ) {
277                                 $data['recentactions'] = intval( $row->recentactions );
278                                 // @todo 'recenteditcount' is set for BC, remove in 1.25
279                                 $data['recenteditcount'] = $data['recentactions'];
280                         }
281                         if ( $fld_registration ) {
282                                 $data['registration'] = $row->user_registration ?
283                                         wfTimestamp( TS_ISO_8601, $row->user_registration ) : '';
284                         }
285
286                         if ( $fld_implicitgroups || $fld_groups || $fld_rights ) {
287                                 $implicitGroups = User::newFromId( $row->user_id )->getAutomaticGroups();
288                                 if ( isset( $row->groups ) && $row->groups !== '' ) {
289                                         $groups = array_merge( $implicitGroups, explode( '|', $row->groups ) );
290                                 } else {
291                                         $groups = $implicitGroups;
292                                 }
293
294                                 if ( $fld_groups ) {
295                                         $data['groups'] = $groups;
296                                         ApiResult::setIndexedTagName( $data['groups'], 'g' );
297                                         ApiResult::setArrayType( $data['groups'], 'array' );
298                                 }
299
300                                 if ( $fld_implicitgroups ) {
301                                         $data['implicitgroups'] = $implicitGroups;
302                                         ApiResult::setIndexedTagName( $data['implicitgroups'], 'g' );
303                                         ApiResult::setArrayType( $data['implicitgroups'], 'array' );
304                                 }
305
306                                 if ( $fld_rights ) {
307                                         $data['rights'] = User::getGroupPermissions( $groups );
308                                         ApiResult::setIndexedTagName( $data['rights'], 'r' );
309                                         ApiResult::setArrayType( $data['rights'], 'array' );
310                                 }
311                         }
312
313                         $fit = $result->addValue( [ 'query', $this->getModuleName() ], null, $data );
314                         if ( !$fit ) {
315                                 $this->setContinueEnumParameter( 'from', $data['name'] );
316                                 break;
317                         }
318                 }
319
320                 $result->addIndexedTagName( [ 'query', $this->getModuleName() ], 'u' );
321         }
322
323         public function getCacheMode( $params ) {
324                 return 'anon-public-user-private';
325         }
326
327         public function getAllowedParams() {
328                 $userGroups = User::getAllGroups();
329
330                 return [
331                         'from' => null,
332                         'to' => null,
333                         'prefix' => null,
334                         'dir' => [
335                                 ApiBase::PARAM_DFLT => 'ascending',
336                                 ApiBase::PARAM_TYPE => [
337                                         'ascending',
338                                         'descending'
339                                 ],
340                         ],
341                         'group' => [
342                                 ApiBase::PARAM_TYPE => $userGroups,
343                                 ApiBase::PARAM_ISMULTI => true,
344                         ],
345                         'excludegroup' => [
346                                 ApiBase::PARAM_TYPE => $userGroups,
347                                 ApiBase::PARAM_ISMULTI => true,
348                         ],
349                         'rights' => [
350                                 ApiBase::PARAM_TYPE => User::getAllRights(),
351                                 ApiBase::PARAM_ISMULTI => true,
352                         ],
353                         'prop' => [
354                                 ApiBase::PARAM_ISMULTI => true,
355                                 ApiBase::PARAM_TYPE => [
356                                         'blockinfo',
357                                         'groups',
358                                         'implicitgroups',
359                                         'rights',
360                                         'editcount',
361                                         'registration',
362                                         'centralids',
363                                 ],
364                                 ApiBase::PARAM_HELP_MSG_PER_VALUE => [],
365                         ],
366                         'limit' => [
367                                 ApiBase::PARAM_DFLT => 10,
368                                 ApiBase::PARAM_TYPE => 'limit',
369                                 ApiBase::PARAM_MIN => 1,
370                                 ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
371                                 ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
372                         ],
373                         'witheditsonly' => false,
374                         'activeusers' => [
375                                 ApiBase::PARAM_DFLT => false,
376                                 ApiBase::PARAM_HELP_MSG => [
377                                         'apihelp-query+allusers-param-activeusers',
378                                         $this->getConfig()->get( 'ActiveUserDays' )
379                                 ],
380                         ],
381                         'attachedwiki' => null,
382                 ];
383         }
384
385         protected function getExamplesMessages() {
386                 return [
387                         'action=query&list=allusers&aufrom=Y'
388                                 => 'apihelp-query+allusers-example-Y',
389                 ];
390         }
391
392         public function getHelpUrls() {
393                 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Allusers';
394         }
395 }