parent::__construct( $query, $moduleName, 'uc' );
}
- private $params, $prefixMode, $userprefix, $multiUserMode, $usernames, $parentLens;
+ private $params, $prefixMode, $userprefix, $multiUserMode, $idMode, $usernames, $userids,
+ $parentLens;
private $fld_ids = false, $fld_title = false, $fld_timestamp = false,
$fld_comment = false, $fld_parsedcomment = false, $fld_flags = false,
$fld_patrolled = false, $fld_tags = false, $fld_size = false, $fld_sizediff = false;
$this->fld_patrolled = isset( $prop['patrolled'] );
$this->fld_tags = isset( $prop['tags'] );
- // Most of this code will use the 'contributions' group DB, which can map to slaves
+ // Most of this code will use the 'contributions' group DB, which can map to replica DBs
// with extra user based indexes or partioning by user. The additional metadata
- // queries should use a regular slave since the lookup pattern is not all by user.
- $dbSecondary = $this->getDB(); // any random slave
+ // queries should use a regular replica DB since the lookup pattern is not all by user.
+ $dbSecondary = $this->getDB(); // any random replica DB
// TODO: if the query is going only against the revision table, should this be done?
- $this->selectNamedDB( 'contributions', DB_SLAVE, 'contributions' );
+ $this->selectNamedDB( 'contributions', DB_REPLICA, 'contributions' );
+ $this->requireOnlyOneParameter( $this->params, 'userprefix', 'userids', 'user' );
+
+ $this->idMode = false;
if ( isset( $this->params['userprefix'] ) ) {
$this->prefixMode = true;
$this->multiUserMode = true;
$this->userprefix = $this->params['userprefix'];
+ } elseif ( isset( $this->params['userids'] ) ) {
+ $this->userids = [];
+
+ if ( !count( $this->params['userids'] ) ) {
+ $encParamName = $this->encodeParamName( 'userids' );
+ $this->dieWithError( [ 'apierror-paramempty', $encParamName ], "paramempty_$encParamName" );
+ }
+
+ foreach ( $this->params['userids'] as $uid ) {
+ if ( $uid <= 0 ) {
+ $this->dieWithError( [ 'apierror-invaliduserid', $uid ], 'invaliduserid' );
+ }
+
+ $this->userids[] = $uid;
+ }
+
+ $this->prefixMode = false;
+ $this->multiUserMode = ( count( $this->params['userids'] ) > 1 );
+ $this->idMode = true;
} else {
+ $anyIPs = false;
+ $this->userids = [];
$this->usernames = [];
- if ( !is_array( $this->params['user'] ) ) {
- $this->params['user'] = [ $this->params['user'] ];
- }
if ( !count( $this->params['user'] ) ) {
- $this->dieUsage( 'User parameter may not be empty.', 'param_user' );
+ $encParamName = $this->encodeParamName( 'user' );
+ $this->dieWithError(
+ [ 'apierror-paramempty', $encParamName ], "paramempty_$encParamName"
+ );
}
foreach ( $this->params['user'] as $u ) {
- $this->prepareUsername( $u );
+ if ( $u === '' ) {
+ $encParamName = $this->encodeParamName( 'user' );
+ $this->dieWithError(
+ [ 'apierror-paramempty', $encParamName ], "paramempty_$encParamName"
+ );
+ }
+
+ if ( User::isIP( $u ) ) {
+ $anyIPs = true;
+ $this->usernames[] = $u;
+ } else {
+ $name = User::getCanonicalName( $u, 'valid' );
+ if ( $name === false ) {
+ $encParamName = $this->encodeParamName( 'user' );
+ $this->dieWithError(
+ [ 'apierror-baduser', $encParamName, wfEscapeWikiText( $u ) ], "baduser_$encParamName"
+ );
+ }
+ $this->usernames[] = $name;
+ }
}
$this->prefixMode = false;
$this->multiUserMode = ( count( $this->params['user'] ) > 1 );
+
+ if ( !$anyIPs ) {
+ $dbr = $this->getDB();
+ $res = $dbr->select( 'user', 'user_id', [ 'user_name' => $this->usernames ], __METHOD__ );
+ foreach ( $res as $row ) {
+ $this->userids[] = $row->user_id;
+ }
+ $this->idMode = count( $this->userids ) === count( $this->usernames );
+ }
}
$this->prepareQuery();
+ $hookData = [];
// Do the actual query.
- $res = $this->select( __METHOD__ );
+ $res = $this->select( __METHOD__, [], $hookData );
if ( $this->fld_sizediff ) {
$revIds = [];
}
$vals = $this->extractRowInfo( $row );
- $fit = $this->getResult()->addValue( [ 'query', $this->getModuleName() ], null, $vals );
+ $fit = $this->processRow( $row, $vals, $hookData ) &&
+ $this->getResult()->addValue( [ 'query', $this->getModuleName() ], null, $vals );
if ( !$fit ) {
$this->setContinueEnumParameter( 'continue', $this->continueStr( $row ) );
break;
);
}
- /**
- * Validate the 'user' parameter and set the value to compare
- * against `revision`.`rev_user_text`
- *
- * @param string $user
- */
- private function prepareUsername( $user ) {
- if ( !is_null( $user ) && $user !== '' ) {
- $name = User::isIP( $user )
- ? $user
- : User::getCanonicalName( $user, 'valid' );
- if ( $name === false ) {
- $this->dieUsage( "User name {$user} is not valid", 'param_user' );
- } else {
- $this->usernames[] = $name;
- }
- } else {
- $this->dieUsage( 'User parameter may not be empty', 'param_user' );
- }
- }
-
/**
* Prepares the query and returns the limit of rows requested
*/
$continue = explode( '|', $this->params['continue'] );
$db = $this->getDB();
if ( $this->multiUserMode ) {
- $this->dieContinueUsageIf( count( $continue ) != 3 );
+ $this->dieContinueUsageIf( count( $continue ) != 4 );
+ $modeFlag = array_shift( $continue );
+ $this->dieContinueUsageIf( !in_array( $modeFlag, [ 'id', 'name' ] ) );
+ if ( $this->idMode && $modeFlag === 'name' ) {
+ // The users were created since this query started, but we
+ // can't go back and change modes now. So just keep on with
+ // name mode.
+ $this->idMode = false;
+ }
+ $this->dieContinueUsageIf( ( $modeFlag === 'id' ) !== $this->idMode );
+ $userField = $this->idMode ? 'rev_user' : 'rev_user_text';
$encUser = $db->addQuotes( array_shift( $continue ) );
} else {
$this->dieContinueUsageIf( count( $continue ) != 2 );
$op = ( $this->params['dir'] == 'older' ? '<' : '>' );
if ( $this->multiUserMode ) {
$this->addWhere(
- "rev_user_text $op $encUser OR " .
- "(rev_user_text = $encUser AND " .
+ "$userField $op $encUser OR " .
+ "($userField = $encUser AND " .
"(rev_timestamp $op $encTS OR " .
"(rev_timestamp = $encTS AND " .
"rev_id $op= $encId)))"
if ( $this->prefixMode ) {
$this->addWhere( 'rev_user_text' .
$this->getDB()->buildLike( $this->userprefix, $this->getDB()->anyString() ) );
+ } elseif ( $this->idMode ) {
+ $this->addWhereFld( 'rev_user', $this->userids );
} else {
$this->addWhereFld( 'rev_user_text', $this->usernames );
}
// ... and in the specified timeframe.
- // Ensure the same sort order for rev_user_text and rev_timestamp
+ // Ensure the same sort order for rev_user/rev_user_text and rev_timestamp
// so our query is indexed
if ( $this->multiUserMode ) {
- $this->addWhereRange( 'rev_user_text', $this->params['dir'], null, null );
+ $this->addWhereRange( $this->idMode ? 'rev_user' : 'rev_user_text',
+ $this->params['dir'], null, null );
}
$this->addTimestampWhereRange( 'rev_timestamp',
$this->params['dir'], $this->params['start'], $this->params['end'] );
|| ( isset( $show['top'] ) && isset( $show['!top'] ) )
|| ( isset( $show['new'] ) && isset( $show['!new'] ) )
) {
- $this->dieUsageMsg( 'show' );
+ $this->dieWithError( 'apierror-show' );
}
$this->addWhereIf( 'rev_minor_edit = 0', isset( $show['!minor'] ) );
$this->addWhereIf( 'rev_parent_id = 0', isset( $show['new'] ) );
}
$this->addOption( 'LIMIT', $this->params['limit'] + 1 );
- $index = [ 'revision' => 'usertext_timestamp' ];
// Mandatory fields: timestamp allows request continuation
// ns+title checks if the user has access rights for this page
$this->fld_patrolled
) {
if ( !$user->useRCPatrol() && !$user->useNPPatrol() ) {
- $this->dieUsage(
- 'You need the patrol right to request the patrolled flag',
- 'permissiondenied'
- );
+ $this->dieWithError( 'apierror-permissiondenied-patrolflag', 'permissiondenied' );
}
// Use a redundant join condition on both
$this->addWhereFld( 'ct_tag', $this->params['tag'] );
}
- $this->addOption( 'USE INDEX', $index );
+ if ( isset( $index ) ) {
+ $this->addOption( 'USE INDEX', $index );
+ }
}
/**
private function continueStr( $row ) {
if ( $this->multiUserMode ) {
- return "$row->rev_user_text|$row->rev_timestamp|$row->rev_id";
+ if ( $this->idMode ) {
+ return "id|$row->rev_user|$row->rev_timestamp|$row->rev_id";
+ } else {
+ return "name|$row->rev_user_text|$row->rev_timestamp|$row->rev_id";
+ }
} else {
return "$row->rev_timestamp|$row->rev_id";
}
ApiBase::PARAM_TYPE => 'user',
ApiBase::PARAM_ISMULTI => true
],
+ 'userids' => [
+ ApiBase::PARAM_TYPE => 'integer',
+ ApiBase::PARAM_ISMULTI => true
+ ],
'userprefix' => null,
'dir' => [
ApiBase::PARAM_DFLT => 'older',
}
public function getHelpUrls() {
- return 'https://www.mediawiki.org/wiki/API:Usercontribs';
+ return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Usercontribs';
}
}