diff options
Diffstat (limited to 'includes/api/ApiQueryAllUsers.php')
-rw-r--r-- | includes/api/ApiQueryAllUsers.php | 273 |
1 files changed, 104 insertions, 169 deletions
diff --git a/includes/api/ApiQueryAllUsers.php b/includes/api/ApiQueryAllUsers.php index affddda7..0cea84f8 100644 --- a/includes/api/ApiQueryAllUsers.php +++ b/includes/api/ApiQueryAllUsers.php @@ -48,11 +48,6 @@ class ApiQueryAllUsers extends ApiQueryBase { $params = $this->extractRequestParams(); $activeUserDays = $this->getConfig()->get( 'ActiveUserDays' ); - if ( $params['activeusers'] ) { - // Update active user cache - SpecialActiveUsers::mergeActiveUsers( 600, $activeUserDays ); - } - $db = $this->getDB(); $prop = $params['prop']; @@ -72,7 +67,6 @@ class ApiQueryAllUsers extends ApiQueryBase { $limit = $params['limit']; $this->addTables( 'user' ); - $useIndex = true; $dir = ( $params['dir'] == 'descending' ? 'older' : 'newer' ); $from = is_null( $params['from'] ) ? null : $this->getCanonicalUserName( $params['from'] ); @@ -82,6 +76,10 @@ class ApiQueryAllUsers extends ApiQueryBase { # despite the JOIN condition, so manually sort on the correct one. $userFieldToSort = $params['activeusers'] ? 'qcc_title' : 'user_name'; + # Some of these subtable joins are going to give us duplicate rows, so + # calculate the maximum number of duplicates we might see. + $maxDuplicateRows = 1; + $this->addWhereRange( $userFieldToSort, $dir, $from, $to ); if ( !is_null( $params['prefix'] ) ) { @@ -97,7 +95,7 @@ class ApiQueryAllUsers extends ApiQueryBase { // no group with the given right(s) exists, no need for a query if ( !count( $groups ) ) { - $this->getResult()->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), '' ); + $this->getResult()->addIndexedTagName( array( 'query', $this->getModuleName() ), '' ); return; } @@ -116,16 +114,17 @@ class ApiQueryAllUsers extends ApiQueryBase { } if ( !is_null( $params['group'] ) && count( $params['group'] ) ) { - $useIndex = false; - // Filter only users that belong to a given group + // Filter only users that belong to a given group. This might + // produce as many rows-per-user as there are groups being checked. $this->addTables( 'user_groups', 'ug1' ); $this->addJoinConds( array( 'ug1' => array( 'INNER JOIN', array( 'ug1.ug_user=user_id', 'ug1.ug_group' => $params['group'] ) ) ) ); + $maxDuplicateRows *= count( $params['group'] ); } if ( !is_null( $params['excludegroup'] ) && count( $params['excludegroup'] ) ) { - $useIndex = false; - // Filter only users don't belong to a given group + // Filter only users don't belong to a given group. This can only + // produce one row-per-user, because we only keep on "no match". $this->addTables( 'user_groups', 'ug1' ); if ( count( $params['excludegroup'] ) == 1 ) { @@ -149,22 +148,16 @@ class ApiQueryAllUsers extends ApiQueryBase { $this->showHiddenUsersAddBlockInfo( $fld_blockinfo ); if ( $fld_groups || $fld_rights ) { - // Show the groups the given users belong to - // request more than needed to avoid not getting all rows that belong to one user - $groupCount = count( User::getAllGroups() ); - $sqlLimit = $limit + $groupCount + 1; - - $this->addTables( 'user_groups', 'ug2' ); - $this->addJoinConds( array( 'ug2' => array( 'LEFT JOIN', 'ug2.ug_user=user_id' ) ) ); - $this->addFields( array( 'ug_group2' => 'ug2.ug_group' ) ); - } else { - $sqlLimit = $limit + 1; + $this->addFields( array( 'groups' => + $db->buildGroupConcatField( '|', 'user_groups', 'ug_group', 'ug_user=user_id' ) + ) ); } if ( $params['activeusers'] ) { $activeUserSeconds = $activeUserDays * 86400; - // Filter query to only include users in the active users cache + // Filter query to only include users in the active users cache. + // There shouldn't be any duplicate rows in querycachetwo here. $this->addTables( 'querycachetwo' ); $this->addJoinConds( array( 'querycachetwo' => array( 'INNER JOIN', array( @@ -190,6 +183,7 @@ class ApiQueryAllUsers extends ApiQueryBase { ) ); } + $sqlLimit = $limit + $maxDuplicateRows; $this->addOption( 'LIMIT', $sqlLimit ); $this->addFields( array( @@ -199,148 +193,112 @@ class ApiQueryAllUsers extends ApiQueryBase { $this->addFieldsIf( 'user_editcount', $fld_editcount ); $this->addFieldsIf( 'user_registration', $fld_registration ); - if ( $useIndex ) { - $this->addOption( 'USE INDEX', array( 'user' => 'user_name' ) ); - } - $res = $this->select( __METHOD__ ); - $count = 0; - $lastUserData = false; + $countDuplicates = 0; $lastUser = false; $result = $this->getResult(); - - // This loop keeps track of the last entry. For each new row, if the - // new row is for different user then the last, the last entry is added - // to results. Otherwise, the group of the new row is appended to the - // last entry. The setContinue... is more complex because of this, and - // takes into account the higher sql limit to make sure all rows that - // belong to the same user are received. - foreach ( $res as $row ) { $count++; - if ( $lastUser !== $row->user_name ) { - // Save the last pass's user data - if ( is_array( $lastUserData ) ) { - if ( $params['activeusers'] && $lastUserData['recentactions'] === 0 ) { - // activeusers cache was out of date - $fit = true; - } else { - $fit = $result->addValue( array( 'query', $this->getModuleName() ), - null, $lastUserData ); - } - - $lastUserData = null; - - if ( !$fit ) { - $this->setContinueEnumParameter( 'from', $lastUserData['name'] ); - break; - } + if ( $lastUser === $row->user_name ) { + // Duplicate row due to one of the needed subtable joins. + // Ignore it, but count the number of them to sanely handle + // miscalculation of $maxDuplicateRows. + $countDuplicates++; + if ( $countDuplicates == $maxDuplicateRows ) { + ApiBase::dieDebug( __METHOD__, 'Saw more duplicate rows than expected' ); } + continue; + } - if ( $count > $limit ) { - // We've reached the one extra which shows that there are - // additional pages to be had. Stop here... - $this->setContinueEnumParameter( 'from', $row->user_name ); - break; - } + $countDuplicates = 0; + $lastUser = $row->user_name; - // Record new user's data - $lastUser = $row->user_name; - $lastUserData = array( - 'userid' => $row->user_id, - 'name' => $lastUser, - ); - if ( $fld_blockinfo && !is_null( $row->ipb_by_text ) ) { - $lastUserData['blockid'] = $row->ipb_id; - $lastUserData['blockedby'] = $row->ipb_by_text; - $lastUserData['blockedbyid'] = $row->ipb_by; - $lastUserData['blockedtimestamp'] = wfTimestamp( TS_ISO_8601, $row->ipb_timestamp ); - $lastUserData['blockreason'] = $row->ipb_reason; - $lastUserData['blockexpiry'] = $row->ipb_expiry; - } - if ( $row->ipb_deleted ) { - $lastUserData['hidden'] = ''; - } - if ( $fld_editcount ) { - $lastUserData['editcount'] = intval( $row->user_editcount ); - } - if ( $params['activeusers'] ) { - $lastUserData['recentactions'] = intval( $row->recentactions ); - // @todo 'recenteditcount' is set for BC, remove in 1.25 - $lastUserData['recenteditcount'] = $lastUserData['recentactions']; - } - if ( $fld_registration ) { - $lastUserData['registration'] = $row->user_registration ? - wfTimestamp( TS_ISO_8601, $row->user_registration ) : ''; - } + if ( $count > $limit ) { + // We've reached the one extra which shows that there are + // additional pages to be had. Stop here... + $this->setContinueEnumParameter( 'from', $row->user_name ); + break; } - if ( $sqlLimit == $count ) { - // @todo BUG! database contains group name that User::getAllGroups() does not return - // Should handle this more gracefully - ApiBase::dieDebug( - __METHOD__, - 'MediaWiki configuration error: The database contains more ' . - 'user groups than known to User::getAllGroups() function' - ); + if ( $count == $sqlLimit ) { + // Should never hit this (either the $countDuplicates check or + // the $count > $limit check should hit first), but check it + // anyway just in case. + ApiBase::dieDebug( __METHOD__, 'Saw more duplicate rows than expected' ); } - $lastUserObj = User::newFromId( $row->user_id ); - - // Add user's group info - if ( $fld_groups ) { - if ( !isset( $lastUserData['groups'] ) ) { - if ( $lastUserObj ) { - $lastUserData['groups'] = $lastUserObj->getAutomaticGroups(); - } else { - // This should not normally happen - $lastUserData['groups'] = array(); - } - } - - if ( !is_null( $row->ug_group2 ) ) { - $lastUserData['groups'][] = $row->ug_group2; - } - - $result->setIndexedTagName( $lastUserData['groups'], 'g' ); + if ( $params['activeusers'] && $row->recentactions === 0 ) { + // activeusers cache was out of date + continue; } - if ( $fld_implicitgroups && !isset( $lastUserData['implicitgroups'] ) && $lastUserObj ) { - $lastUserData['implicitgroups'] = $lastUserObj->getAutomaticGroups(); - $result->setIndexedTagName( $lastUserData['implicitgroups'], 'g' ); + $data = array( + 'userid' => $row->user_id, + 'name' => $row->user_name, + ); + + if ( $fld_blockinfo && !is_null( $row->ipb_by_text ) ) { + $data['blockid'] = $row->ipb_id; + $data['blockedby'] = $row->ipb_by_text; + $data['blockedbyid'] = $row->ipb_by; + $data['blockedtimestamp'] = wfTimestamp( TS_ISO_8601, $row->ipb_timestamp ); + $data['blockreason'] = $row->ipb_reason; + $data['blockexpiry'] = $row->ipb_expiry; + } + if ( $row->ipb_deleted ) { + $data['hidden'] = true; + } + if ( $fld_editcount ) { + $data['editcount'] = intval( $row->user_editcount ); + } + if ( $params['activeusers'] ) { + $data['recentactions'] = intval( $row->recentactions ); + // @todo 'recenteditcount' is set for BC, remove in 1.25 + $data['recenteditcount'] = $data['recentactions']; } - if ( $fld_rights ) { - if ( !isset( $lastUserData['rights'] ) ) { - if ( $lastUserObj ) { - $lastUserData['rights'] = User::getGroupPermissions( $lastUserObj->getAutomaticGroups() ); - } else { - // This should not normally happen - $lastUserData['rights'] = array(); - } + if ( $fld_registration ) { + $data['registration'] = $row->user_registration ? + wfTimestamp( TS_ISO_8601, $row->user_registration ) : ''; + } + + if ( $fld_implicitgroups || $fld_groups || $fld_rights ) { + $user = User::newFromId( $row->user_id ); + $implicitGroups = User::newFromId( $row->user_id )->getAutomaticGroups(); + if ( isset( $row->groups ) && $row->groups !== '' ) { + $groups = array_merge( $implicitGroups, explode( '|', $row->groups ) ); + } else { + $groups = $implicitGroups; } - if ( !is_null( $row->ug_group2 ) ) { - $lastUserData['rights'] = array_unique( array_merge( $lastUserData['rights'], - User::getGroupPermissions( array( $row->ug_group2 ) ) ) ); + if ( $fld_groups ) { + $data['groups'] = $groups; + ApiResult::setIndexedTagName( $data['groups'], 'g' ); + ApiResult::setArrayType( $data['groups'], 'array' ); } - $result->setIndexedTagName( $lastUserData['rights'], 'r' ); + if ( $fld_implicitgroups ) { + $data['implicitgroups'] = $implicitGroups; + ApiResult::setIndexedTagName( $data['implicitgroups'], 'g' ); + ApiResult::setArrayType( $data['implicitgroups'], 'array' ); + } + + if ( $fld_rights ) { + $data['rights'] = User::getGroupPermissions( $groups ); + ApiResult::setIndexedTagName( $data['rights'], 'r' ); + ApiResult::setArrayType( $data['rights'], 'array' ); + } } - } - if ( is_array( $lastUserData ) && - !( $params['activeusers'] && $lastUserData['recentactions'] === 0 ) - ) { - $fit = $result->addValue( array( 'query', $this->getModuleName() ), - null, $lastUserData ); + $fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $data ); if ( !$fit ) { - $this->setContinueEnumParameter( 'from', $lastUserData['name'] ); + $this->setContinueEnumParameter( 'from', $data['name'] ); + break; } } - $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'u' ); + $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'u' ); } public function getCacheMode( $params ) { @@ -392,43 +350,20 @@ class ApiQueryAllUsers extends ApiQueryBase { ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2 ), 'witheditsonly' => false, - 'activeusers' => false, - ); - } - - public function getParamDescription() { - return array( - 'from' => 'The user name to start enumerating from', - 'to' => 'The user name to stop enumerating at', - 'prefix' => 'Search for all users that begin with this value', - 'dir' => 'Direction to sort in', - 'group' => 'Limit users to given group name(s)', - 'excludegroup' => 'Exclude users in given group name(s)', - 'rights' => 'Limit users to given right(s) (does not include rights ' . - 'granted by implicit or auto-promoted groups like *, user, or autoconfirmed)', - 'prop' => array( - 'What pieces of information to include.', - ' blockinfo - Adds the information about a current block on the user', - ' groups - Lists groups that the user is in. This uses ' . - 'more server resources and may return fewer results than the limit', - ' implicitgroups - Lists all the groups the user is automatically in', - ' rights - Lists rights that the user has', - ' editcount - Adds the edit count of the user', - ' registration - Adds the timestamp of when the user registered if available (may be blank)', + 'activeusers' => array( + ApiBase::PARAM_DFLT => false, + ApiBase::PARAM_HELP_MSG => array( + 'apihelp-query+allusers-param-activeusers', + $this->getConfig()->get( 'ActiveUserDays' ) + ), ), - 'limit' => 'How many total user names to return', - 'witheditsonly' => 'Only list users who have made edits', - 'activeusers' => "Only list users active in the last {$this->getConfig()->get( 'ActiveUserDays' )} days(s)" ); } - public function getDescription() { - return 'Enumerate all registered users.'; - } - - public function getExamples() { + protected function getExamplesMessages() { return array( - 'api.php?action=query&list=allusers&aufrom=Y', + 'action=query&list=allusers&aufrom=Y' + => 'apihelp-query+allusers-example-Y', ); } |