24use Wikimedia\Timestamp\ConvertibleTimestamp;
25use Wikimedia\Timestamp\TimestampFormat as TS;
41 private readonly
Language $contentLanguage,
46 parent::__construct( $query, $moduleName,
'au' );
55 private function getCanonicalUserName( $name ) {
57 $name = $this->contentLanguage->ucfirst( ltrim( $name ) );
58 return strtr( $name,
'_',
' ' );
67 $prop = $params[
'prop'];
68 if ( $prop !==
null ) {
69 $prop = array_fill_keys( $prop,
true );
70 $fld_blockinfo = isset( $prop[
'blockinfo'] );
71 $fld_editcount = isset( $prop[
'editcount'] );
72 $fld_groups = isset( $prop[
'groups'] );
73 $fld_rights = isset( $prop[
'rights'] );
74 $fld_registration = isset( $prop[
'registration'] );
75 $fld_implicitgroups = isset( $prop[
'implicitgroups'] );
76 $fld_centralids = isset( $prop[
'centralids'] );
77 $fld_tempexpired = isset( $prop[
'tempexpired'] );
79 $fld_blockinfo = $fld_editcount = $fld_groups = $fld_registration =
80 $fld_rights = $fld_implicitgroups = $fld_centralids = $fld_tempexpired =
false;
83 $limit = $params[
'limit'];
87 $dir = ( $params[
'dir'] ==
'descending' ?
'older' :
'newer' );
88 $from = $params[
'from'] ===
null ? null : $this->getCanonicalUserName( $params[
'from'] );
89 $to = $params[
'to'] ===
null ? null : $this->getCanonicalUserName( $params[
'to'] );
91 # MySQL can't figure out that 'user_name' and 'qcc_title' are the same
92 # despite the JOIN condition, so manually sort on the correct one.
93 $userFieldToSort = $params[
'activeusers'] ?
'qcc_title' :
'user_name';
95 # Some of these subtable joins are going to give us duplicate rows, so
96 # calculate the maximum number of duplicates we might see.
97 $maxDuplicateRows = 1;
101 if ( $params[
'prefix'] !==
null ) {
106 new LikeValue( $this->getCanonicalUserName( $params[
'prefix'] ), $db->anyString() )
111 $excludeNamed = $params[
'excludenamed'];
112 $excludeTemp = $params[
'excludetemp'];
114 if ( $this->tempUserConfig->isKnown() ) {
115 if ( $excludeTemp ) {
117 $this->tempUserConfig->getMatchCondition( $db,
'user_name', IExpression::NOT_LIKE )
120 if ( $excludeNamed ) {
122 $this->tempUserConfig->getMatchCondition( $db,
'user_name', IExpression::LIKE )
127 if ( $params[
'rights'] !==
null && count( $params[
'rights'] ) ) {
130 foreach ( $params[
'rights'] as $r ) {
134 $groups = array_merge(
136 $this->groupPermissionsLookup->getGroupsWithPermission( $r )
141 if ( $groups === [] ) {
148 $groups = array_unique( $groups );
149 if ( in_array(
'*', $groups,
true ) || in_array(
'user', $groups,
true ) ) {
154 if ( $params[
'group'] ===
null ) {
155 $params[
'group'] = $groups;
157 $params[
'group'] = array_unique( array_merge( $params[
'group'], $groups ) );
163 if ( $params[
'group'] !==
null && count( $params[
'group'] ) ) {
166 $this->
addTables(
'user_groups',
'ug1' );
171 'ug1.ug_user=user_id',
172 'ug1.ug_group' => $params[
'group'],
173 $db->expr(
'ug1.ug_expiry',
'=',
null )->or(
'ug1.ug_expiry',
'>=', $db->timestamp() ),
177 $maxDuplicateRows *= count( $params[
'group'] );
180 if ( $params[
'excludegroup'] !==
null && count( $params[
'excludegroup'] ) ) {
183 $this->
addTables(
'user_groups',
'ug1' );
187 'ug1.ug_user=user_id',
188 $db->expr(
'ug1.ug_expiry',
'=',
null )->or(
'ug1.ug_expiry',
'>=', $db->timestamp() ),
189 'ug1.ug_group' => $params[
'excludegroup'],
192 $this->
addWhere( [
'ug1.ug_user' =>
null ] );
195 if ( $params[
'witheditsonly'] ) {
196 $this->
addWhere( $db->expr(
'user_editcount',
'>', 0 ) );
199 $this->addDeletedUserFilter();
201 if ( $fld_groups || $fld_rights ) {
203 $db->newSelectQueryBuilder()
204 ->table(
'user_groups' )
205 ->field(
'ug_group' )
208 $db->expr(
'ug_expiry',
'=',
null )->or(
'ug_expiry',
'>=', $db->timestamp() )
210 ->buildGroupConcatField(
'|' )
214 if ( $params[
'activeusers'] ) {
215 $activeUserSeconds = $activeUserDays * 86400;
222 'qcc_type' =>
'activeusers',
224 'qcc_title=user_name',
229 $timestamp = $db->timestamp( (
int)ConvertibleTimestamp::now( TS::UNIX ) - $activeUserSeconds );
230 $subqueryBuilder = $db->newSelectQueryBuilder()
231 ->select(
'COUNT(*)' )
232 ->from(
'recentchanges' )
233 ->join(
'actor',
null,
'rc_actor = actor_id' )
235 'actor_user = user_id',
236 $db->expr(
'rc_source',
'=', $this->recentChangeLookup->getPrimarySources() ),
237 $db->expr(
'rc_log_type',
'=',
null )
238 ->or(
'rc_log_type',
'!=',
'newusers' ),
239 $db->expr(
'rc_timestamp',
'>=', $timestamp ),
242 'recentactions' =>
'(' . $subqueryBuilder->caller( __METHOD__ )->getSQL() .
')'
246 $sqlLimit = $limit + $maxDuplicateRows;
253 $this->
addFieldsIf(
'user_editcount', $fld_editcount );
254 $this->
addFieldsIf(
'user_registration', $fld_registration );
256 $res = $this->
select( __METHOD__ );
258 $countDuplicates = 0;
261 $blockInfos = $fld_blockinfo ? $this->getBlockDetailsForRows( $res ) :
null;
262 foreach ( $res as $row ) {
265 if ( $lastUser === $row->user_name ) {
270 if ( $countDuplicates == $maxDuplicateRows ) {
276 $countDuplicates = 0;
277 $lastUser = $row->user_name;
279 if ( $count > $limit ) {
286 if ( $count == $sqlLimit ) {
293 if ( $params[
'activeusers'] && (
int)$row->recentactions === 0 ) {
299 'userid' => (int)$row->user_id,
300 'name' => $row->user_name,
303 if ( $fld_centralids ) {
305 $this->
getConfig(), $this->userFactory->newFromId( (
int)$row->user_id ), $params[
'attachedwiki']
309 if ( $fld_blockinfo && isset( $blockInfos[$row->user_id] ) ) {
310 $data += $blockInfos[$row->user_id];
312 if ( $row->hu_deleted ) {
313 $data[
'hidden'] =
true;
315 if ( $fld_editcount ) {
316 $data[
'editcount'] = (int)$row->user_editcount;
318 if ( $params[
'activeusers'] ) {
319 $data[
'recentactions'] = (int)$row->recentactions;
321 if ( $fld_registration ) {
322 $data[
'registration'] = $row->user_registration ?
323 wfTimestamp( TS::ISO_8601, $row->user_registration ) :
'';
326 if ( $fld_implicitgroups || $fld_groups || $fld_rights ) {
327 $implicitGroups = $this->userGroupManager
328 ->getUserImplicitGroups( $this->userFactory->newFromId( (
int)$row->user_id ) );
329 if ( isset( $row->groups ) && $row->groups !==
'' ) {
330 $groups = array_merge( $implicitGroups, explode(
'|', $row->groups ) );
332 $groups = $implicitGroups;
336 $data[
'groups'] = $groups;
341 if ( $fld_implicitgroups ) {
342 $data[
'implicitgroups'] = $implicitGroups;
348 $user = $this->userFactory->newFromId( (
int)$row->user_id );
355 if ( $fld_tempexpired ) {
356 if ( $this->tempUserConfig->isTempName( $row->user_name ) ) {
357 $userIdentity = UserIdentityValue::newRegistered( $row->user_id, $row->user_name );
358 $data[
'tempexpired'] = $this->tempUserDetailsLookup->isExpired( $userIdentity );
360 $data[
'tempexpired'] =
null;
364 $fit = $result->addValue( [
'query', $this->
getModuleName() ],
null, $data );
371 $result->addIndexedTagName( [
'query', $this->
getModuleName() ],
'u' );
376 return 'anon-public-user-private';
381 $userGroups = $this->userGroupManager->listAllGroups();
392 ParamValidator::PARAM_DEFAULT =>
'ascending',
393 ParamValidator::PARAM_TYPE => [
399 ParamValidator::PARAM_TYPE => $userGroups,
400 ParamValidator::PARAM_ISMULTI =>
true,
403 ParamValidator::PARAM_TYPE => $userGroups,
404 ParamValidator::PARAM_ISMULTI =>
true,
407 ParamValidator::PARAM_TYPE => array_unique( array_merge(
411 ParamValidator::PARAM_ISMULTI =>
true,
414 ParamValidator::PARAM_ISMULTI =>
true,
415 ParamValidator::PARAM_TYPE => [
428 ParamValidator::PARAM_DEFAULT => 10,
429 ParamValidator::PARAM_TYPE =>
'limit',
430 IntegerDef::PARAM_MIN => 1,
434 'witheditsonly' =>
false,
436 ParamValidator::PARAM_DEFAULT =>
false,
438 'apihelp-query+allusers-param-activeusers',
442 'attachedwiki' =>
null,
444 ParamValidator::PARAM_TYPE =>
'boolean',
447 ParamValidator::PARAM_TYPE =>
'boolean',
455 'action=query&list=allusers&aufrom=Y'
456 =>
'apihelp-query+allusers-example-y',
462 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Allusers';
467class_alias( ApiQueryAllUsers::class,
'ApiQueryAllUsers' );
wfTimestamp( $outputtype=TS::UNIX, $ts=0)
Get a timestamp string in one of various formats.
A class containing constants representing the names of configuration variables.
const ActiveUserDays
Name constant for the ActiveUserDays setting, for use with Config::get()
trait ApiQueryBlockInfoTrait