MediaWiki  master
ApiQueryAllUsers.php
Go to the documentation of this file.
1 <?php
24 
32 
33  public function __construct( ApiQuery $query, $moduleName ) {
34  parent::__construct( $query, $moduleName, 'au' );
35  }
36 
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 
53  $prop = $params['prop'];
54  if ( $prop !== null ) {
55  $prop = array_flip( $prop );
56  $fld_blockinfo = isset( $prop['blockinfo'] );
57  $fld_editcount = isset( $prop['editcount'] );
58  $fld_groups = isset( $prop['groups'] );
59  $fld_rights = isset( $prop['rights'] );
60  $fld_registration = isset( $prop['registration'] );
61  $fld_implicitgroups = isset( $prop['implicitgroups'] );
62  $fld_centralids = isset( $prop['centralids'] );
63  } else {
64  $fld_blockinfo = $fld_editcount = $fld_groups = $fld_registration =
65  $fld_rights = $fld_implicitgroups = $fld_centralids = false;
66  }
67 
68  $limit = $params['limit'];
69 
70  $this->addTables( 'user' );
71 
72  $dir = ( $params['dir'] == 'descending' ? 'older' : 'newer' );
73  $from = $params['from'] === null ? null : $this->getCanonicalUserName( $params['from'] );
74  $to = $params['to'] === null ? null : $this->getCanonicalUserName( $params['to'] );
75 
76  # MySQL can't figure out that 'user_name' and 'qcc_title' are the same
77  # despite the JOIN condition, so manually sort on the correct one.
78  $userFieldToSort = $params['activeusers'] ? 'qcc_title' : 'user_name';
79 
80  # Some of these subtable joins are going to give us duplicate rows, so
81  # calculate the maximum number of duplicates we might see.
82  $maxDuplicateRows = 1;
83 
84  $this->addWhereRange( $userFieldToSort, $dir, $from, $to );
85 
86  if ( $params['prefix'] !== null ) {
87  $this->addWhere( $userFieldToSort .
88  $db->buildLike( $this->getCanonicalUserName( $params['prefix'] ), $db->anyString() ) );
89  }
90 
91  if ( $params['rights'] !== null && count( $params['rights'] ) ) {
92  $groups = [];
93  foreach ( $params['rights'] as $r ) {
94  $groups = array_merge( $groups, $this->getPermissionManager()
95  ->getGroupsWithPermission( $r ) );
96  }
97 
98  // no group with the given right(s) exists, no need for a query
99  if ( $groups === [] ) {
100  $this->getResult()->addIndexedTagName( [ 'query', $this->getModuleName() ], '' );
101 
102  return;
103  }
104 
105  $groups = array_unique( $groups );
106 
107  if ( $params['group'] === null ) {
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 ( $params['group'] !== null && 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  '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 ( $params['excludegroup'] !== null && 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 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->addBlockInfoToQuery( $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  '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  $tables = [ 'recentchanges', 'actor' ];
186  $joins = [
187  'actor' => [ 'JOIN', 'rc_actor = actor_id' ],
188  ];
189  $timestamp = $db->timestamp( (int)wfTimestamp( TS_UNIX ) - $activeUserSeconds );
190  $this->addFields( [
191  'recentactions' => '(' . $db->selectSQLText(
192  $tables,
193  'COUNT(*)',
194  [
195  'actor_user = user_id',
196  'rc_type != ' . $db->addQuotes( RC_EXTERNAL ), // no wikidata
197  'rc_log_type IS NULL OR rc_log_type != ' . $db->addQuotes( 'newusers' ),
198  'rc_timestamp >= ' . $db->addQuotes( $timestamp ),
199  ],
200  __METHOD__,
201  [],
202  $joins
203  ) . ')'
204  ] );
205  }
206 
207  $sqlLimit = $limit + $maxDuplicateRows;
208  $this->addOption( 'LIMIT', $sqlLimit );
209 
210  $this->addFields( [
211  'user_name',
212  'user_id'
213  ] );
214  $this->addFieldsIf( 'user_editcount', $fld_editcount );
215  $this->addFieldsIf( 'user_registration', $fld_registration );
216 
217  $res = $this->select( __METHOD__ );
218  $count = 0;
219  $countDuplicates = 0;
220  $lastUser = false;
221  $result = $this->getResult();
222  foreach ( $res as $row ) {
223  $count++;
224 
225  if ( $lastUser === $row->user_name ) {
226  // Duplicate row due to one of the needed subtable joins.
227  // Ignore it, but count the number of them to sanely handle
228  // miscalculation of $maxDuplicateRows.
229  $countDuplicates++;
230  if ( $countDuplicates == $maxDuplicateRows ) {
231  ApiBase::dieDebug( __METHOD__, 'Saw more duplicate rows than expected' );
232  }
233  continue;
234  }
235 
236  $countDuplicates = 0;
237  $lastUser = $row->user_name;
238 
239  if ( $count > $limit ) {
240  // We've reached the one extra which shows that there are
241  // additional pages to be had. Stop here...
242  $this->setContinueEnumParameter( 'from', $row->user_name );
243  break;
244  }
245 
246  if ( $count == $sqlLimit ) {
247  // Should never hit this (either the $countDuplicates check or
248  // the $count > $limit check should hit first), but check it
249  // anyway just in case.
250  ApiBase::dieDebug( __METHOD__, 'Saw more duplicate rows than expected' );
251  }
252 
253  if ( $params['activeusers'] && $row->recentactions === 0 ) {
254  // activeusers cache was out of date
255  continue;
256  }
257 
258  $data = [
259  'userid' => (int)$row->user_id,
260  'name' => $row->user_name,
261  ];
262 
263  if ( $fld_centralids ) {
265  $this->getConfig(), User::newFromId( $row->user_id ), $params['attachedwiki']
266  );
267  }
268 
269  if ( $fld_blockinfo && $row->ipb_id !== null ) {
270  $data += $this->getBlockDetails( DatabaseBlock::newFromRow( $row ) );
271  }
272  if ( $row->ipb_deleted ) {
273  $data['hidden'] = true;
274  }
275  if ( $fld_editcount ) {
276  $data['editcount'] = (int)$row->user_editcount;
277  }
278  if ( $params['activeusers'] ) {
279  $data['recentactions'] = (int)$row->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'] = $this->getPermissionManager()->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( $flags = 0 ) {
328  $userGroups = User::getAllGroups();
329 
330  if ( $flags & ApiBase::GET_VALUES_FOR_HELP ) {
331  sort( $userGroups );
332  }
333 
334  return [
335  'from' => null,
336  'to' => null,
337  'prefix' => null,
338  'dir' => [
339  ApiBase::PARAM_DFLT => 'ascending',
341  'ascending',
342  'descending'
343  ],
344  ],
345  'group' => [
346  ApiBase::PARAM_TYPE => $userGroups,
347  ApiBase::PARAM_ISMULTI => true,
348  ],
349  'excludegroup' => [
350  ApiBase::PARAM_TYPE => $userGroups,
351  ApiBase::PARAM_ISMULTI => true,
352  ],
353  'rights' => [
354  ApiBase::PARAM_TYPE => $this->getPermissionManager()->getAllPermissions(),
355  ApiBase::PARAM_ISMULTI => true,
356  ],
357  'prop' => [
358  ApiBase::PARAM_ISMULTI => true,
360  'blockinfo',
361  'groups',
362  'implicitgroups',
363  'rights',
364  'editcount',
365  'registration',
366  'centralids',
367  ],
369  ],
370  'limit' => [
371  ApiBase::PARAM_DFLT => 10,
372  ApiBase::PARAM_TYPE => 'limit',
373  ApiBase::PARAM_MIN => 1,
376  ],
377  'witheditsonly' => false,
378  'activeusers' => [
379  ApiBase::PARAM_DFLT => false,
381  'apihelp-query+allusers-param-activeusers',
382  $this->getConfig()->get( 'ActiveUserDays' )
383  ],
384  ],
385  'attachedwiki' => null,
386  ];
387  }
388 
389  protected function getExamplesMessages() {
390  return [
391  'action=query&list=allusers&aufrom=Y'
392  => 'apihelp-query+allusers-example-y',
393  ];
394  }
395 
396  public function getHelpUrls() {
397  return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Allusers';
398  }
399 }
ContextSource\getConfig
getConfig()
Definition: ContextSource.php:67
User\newFromId
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition: User.php:562
ApiQueryBase\addFields
addFields( $value)
Add a set of fields to select to the internal array.
Definition: ApiQueryBase.php:215
RC_EXTERNAL
const RC_EXTERNAL
Definition: Defines.php:134
ApiQuery
This is the main query class.
Definition: ApiQuery.php:37
ApiQueryAllUsers\getExamplesMessages
getExamplesMessages()
Returns usage examples for this module.
Definition: ApiQueryAllUsers.php:389
if
if(ini_get( 'mbstring.func_overload')) if(!defined('MW_ENTRY_POINT'))
Pre-config setup: Before loading LocalSettings.php.
Definition: Setup.php:85
ApiBase\PARAM_HELP_MSG
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition: ApiBase.php:107
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1808
ApiBase\PARAM_TYPE
const PARAM_TYPE
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:71
ApiBase\getResult
getResult()
Get the result object.
Definition: ApiBase.php:565
ApiQueryBase\addOption
addOption( $name, $value=null)
Add an option such as LIMIT or USE INDEX.
Definition: ApiQueryBase.php:381
$res
$res
Definition: testCompression.php:57
ApiQueryBase\addFieldsIf
addFieldsIf( $value, $condition)
Same as addFields(), but add the fields only if a condition is met.
Definition: ApiQueryBase.php:225
ApiQueryAllUsers\getCacheMode
getCacheMode( $params)
Get the cache mode for the data generated by this module.
Definition: ApiQueryAllUsers.php:323
ApiQueryAllUsers\getAllowedParams
getAllowedParams( $flags=0)
Definition: ApiQueryAllUsers.php:327
MediaWiki\Block\DatabaseBlock
A DatabaseBlock (unlike a SystemBlock) is stored in the database, may give rise to autoblocks and may...
Definition: DatabaseBlock.php:52
ApiQueryBlockInfoTrait
trait ApiQueryBlockInfoTrait
Definition: ApiQueryBlockInfoTrait.php:28
ApiBase\PARAM_MIN
const PARAM_MIN
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:74
ApiResult\setArrayType
static setArrayType(array &$arr, $type, $kvpKeyName=null)
Set the array data type.
Definition: ApiResult.php:716
LIST_OR
const LIST_OR
Definition: Defines.php:51
ApiQueryAllUsers\getCanonicalUserName
getCanonicalUserName( $name)
This function converts the user name to a canonical form which is stored in the database.
Definition: ApiQueryAllUsers.php:43
ApiQueryBase
This is a base class for all Query modules.
Definition: ApiQueryBase.php:37
ApiBase\LIMIT_BIG1
const LIMIT_BIG1
Fast query, standard limit.
Definition: ApiBase.php:165
ApiQueryBase\getDB
getDB()
Get the Query database connection (read-only) Stable to override.
Definition: ApiQueryBase.php:119
ApiBase\PARAM_MAX
const PARAM_MAX
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:72
ApiQueryBase\addTables
addTables( $tables, $alias=null)
Add a set of tables to the internal array.
Definition: ApiQueryBase.php:185
ApiQueryBase\select
select( $method, $extraQuery=[], array &$hookData=null)
Execute a SELECT query based on the values in the internal arrays.
Definition: ApiQueryBase.php:402
ApiBase\extractRequestParams
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition: ApiBase.php:717
ApiQueryBase\addWhereRange
addWhereRange( $field, $dir, $start, $end, $sort=true)
Add a WHERE clause corresponding to a range, and an ORDER BY clause to sort in the right direction.
Definition: ApiQueryBase.php:340
ApiResult\setIndexedTagName
static setIndexedTagName(array &$arr, $tag)
Set the tag name for numeric-keyed values in XML format.
Definition: ApiResult.php:604
ApiBase\requireMaxOneParameter
requireMaxOneParameter( $params,... $required)
Die if more than one of a certain set of parameters is set and not false.
Definition: ApiBase.php:889
ApiBase\GET_VALUES_FOR_HELP
const GET_VALUES_FOR_HELP
getAllowedParams() flag: When set, the result could take longer to generate, but should be more thoro...
Definition: ApiBase.php:178
ApiBase\getPermissionManager
getPermissionManager()
Obtain a PermissionManager instance that subclasses may use in their authorization checks.
Definition: ApiBase.php:637
ApiQueryBase\addJoinConds
addJoinConds( $join_conds)
Add a set of JOIN conditions to the internal array.
Definition: ApiQueryBase.php:204
User\getAllGroups
static getAllGroups()
Return the set of defined explicit groups.
Definition: User.php:4099
ApiBase\LIMIT_BIG2
const LIMIT_BIG2
Fast query, apihighlimits limit.
Definition: ApiBase.php:167
ApiQueryAllUsers
Query module to enumerate all registered users.
Definition: ApiQueryAllUsers.php:30
ApiBase\PARAM_DFLT
const PARAM_DFLT
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:69
ApiBase\getModuleName
getModuleName()
Get the name of the module being executed by this instance.
Definition: ApiBase.php:444
ApiBase\PARAM_ISMULTI
const PARAM_ISMULTI
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:70
NS_USER
const NS_USER
Definition: Defines.php:71
ApiBase\PARAM_MAX2
const PARAM_MAX2
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:73
ApiQueryBase\addWhere
addWhere( $value)
Add a set of WHERE clauses to the internal array.
Definition: ApiQueryBase.php:248
ApiQueryUserInfo\getCentralUserInfo
static getCentralUserInfo(Config $config, User $user, $attachedWiki=null)
Get central user info.
Definition: ApiQueryUserInfo.php:69
ApiQueryBase\setContinueEnumParameter
setContinueEnumParameter( $paramName, $paramValue)
Set a query-continue value.
Definition: ApiQueryBase.php:519
ApiQueryAllUsers\execute
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
Definition: ApiQueryAllUsers.php:47
ApiBase\PARAM_HELP_MSG_PER_VALUE
const PARAM_HELP_MSG_PER_VALUE
((string|array|Message)[]) When PARAM_TYPE is an array, this is an array mapping those values to $msg...
Definition: ApiBase.php:140
ApiQueryAllUsers\getHelpUrls
getHelpUrls()
Return links to more detailed help pages about the module.
Definition: ApiQueryAllUsers.php:396
ApiQueryAllUsers\__construct
__construct(ApiQuery $query, $moduleName)
Definition: ApiQueryAllUsers.php:33
ApiBase\dieDebug
static dieDebug( $method, $message)
Internal code errors should be reported with this method.
Definition: ApiBase.php:1574
addBlockInfoToQuery
addBlockInfoToQuery( $showBlockInfo)
Filters hidden users (where the user doesn't have the right to view them) Also adds relevant block in...
Definition: ApiQueryBlockInfoTrait.php:38