36 parent::__construct( $query, $moduleName,
'uc' );
52 $this->commentStore = CommentStore::getStore();
54 $prop = array_flip( $this->params[
'prop'] );
55 $this->fld_ids = isset( $prop[
'ids'] );
56 $this->fld_title = isset( $prop[
'title'] );
57 $this->fld_comment = isset( $prop[
'comment'] );
58 $this->fld_parsedcomment = isset( $prop[
'parsedcomment'] );
59 $this->fld_size = isset( $prop[
'size'] );
60 $this->fld_sizediff = isset( $prop[
'sizediff'] );
61 $this->fld_flags = isset( $prop[
'flags'] );
62 $this->fld_timestamp = isset( $prop[
'timestamp'] );
63 $this->fld_patrolled = isset( $prop[
'patrolled'] );
64 $this->fld_tags = isset( $prop[
'tags'] );
69 $dbSecondary = $this->
getDB();
71 $sort = ( $this->params[
'dir'] ==
'newer' ?
'' :
' DESC' );
72 $op = ( $this->params[
'dir'] ==
'older' ?
'<' :
'>' );
78 if ( isset( $this->params[
'userprefix'] ) ) {
79 $this->multiUserMode =
true;
80 $this->orderBy =
'name';
86 $userIter = call_user_func(
function () use ( $dbSecondary, $sort, $op, $fname ) {
88 if ( $this->params[
'continue'] !==
null ) {
89 $continue = explode(
'|', $this->params[
'continue'] );
92 $fromName = $continue[1];
94 $like = $dbSecondary->buildLike( $this->params[
'userprefix'], $dbSecondary->anyString() );
99 $from = $fromName ?
"$op= " . $dbSecondary->addQuotes( $fromName ) :
false;
100 $res = $dbSecondary->select(
102 [
'actor_id',
'user_id' =>
'COALESCE(actor_user,0)',
'user_name' =>
'actor_name' ],
103 array_merge( [
"actor_name$like" ], $from ? [
"actor_name $from" ] : [] ),
105 [
'ORDER BY' => [
"user_name $sort" ],
'LIMIT' => $limit ]
110 foreach (
$res as $row ) {
111 if ( ++$count >= $limit ) {
112 $fromName = $row->user_name;
117 }
while ( $fromName !==
false );
122 } elseif ( isset( $this->params[
'userids'] ) ) {
123 if ( $this->params[
'userids'] === [] ) {
125 $this->
dieWithError( [
'apierror-paramempty', $encParamName ],
"paramempty_$encParamName" );
129 foreach ( $this->params[
'userids'] as $uid ) {
131 $this->
dieWithError( [
'apierror-invaliduserid', $uid ],
'invaliduserid' );
136 $this->orderBy =
'id';
137 $this->multiUserMode = count( $ids ) > 1;
139 $from = $fromId =
false;
140 if ( $this->multiUserMode && $this->params[
'continue'] !==
null ) {
141 $continue = explode(
'|', $this->params[
'continue'] );
144 $fromId = (int)$continue[1];
146 $from =
"$op= $fromId";
149 $res = $dbSecondary->select(
151 [
'actor_id',
'user_id' =>
'actor_user',
'user_name' =>
'actor_name' ],
152 array_merge( [
'actor_user' => $ids ], $from ? [
"actor_id $from" ] : [] ),
154 [
'ORDER BY' =>
"user_id $sort" ]
157 $batchSize = count( $ids );
160 if ( !count( $this->params[
'user'] ) ) {
163 [
'apierror-paramempty', $encParamName ],
"paramempty_$encParamName"
166 foreach ( $this->params[
'user'] as $u ) {
170 [
'apierror-paramempty', $encParamName ],
"paramempty_$encParamName"
174 if (
User::isIP( $u ) || ExternalUserNames::isExternal( $u ) ) {
178 if ( $name ===
false ) {
181 [
'apierror-baduser', $encParamName,
wfEscapeWikiText( $u ) ],
"baduser_$encParamName"
184 $names[$name] =
null;
188 $this->orderBy =
'name';
189 $this->multiUserMode = count( $names ) > 1;
191 $from = $fromName =
false;
192 if ( $this->multiUserMode && $this->params[
'continue'] !==
null ) {
193 $continue = explode(
'|', $this->params[
'continue'] );
196 $fromName = $continue[1];
197 $from =
"$op= " . $dbSecondary->addQuotes( $fromName );
200 $res = $dbSecondary->select(
202 [
'actor_id',
'user_id' =>
'actor_user',
'user_name' =>
'actor_name' ],
204 [
'actor_name' => array_map(
'strval', array_keys( $names ) ) ],
205 $from ? [
"actor_id $from" ] : []
208 [
'ORDER BY' =>
"actor_name $sort" ]
211 $batchSize = count( $names );
215 if ( $batchSize > 1 ) {
216 $this->orderBy =
'actor';
220 $limit = $this->params[
'limit'];
222 while ( $userIter->valid() ) {
224 while ( count( $users ) < $batchSize && $userIter->valid() ) {
225 $users[] = $userIter->current();
231 $res = $this->
select( __METHOD__, [], $hookData );
233 if ( $this->fld_title ) {
237 if ( $this->fld_sizediff ) {
239 foreach (
$res as $row ) {
240 if ( $row->rev_parent_id ) {
241 $revIds[] = $row->rev_parent_id;
244 $this->parentLens = MediaWikiServices::getInstance()->getRevisionStore()
245 ->getRevisionSizes( $revIds );
248 foreach (
$res as $row ) {
249 if ( ++$count > $limit ) {
257 $fit = $this->
processRow( $row, $vals, $hookData ) &&
276 $db = $this->
getDB();
278 $revQuery = MediaWikiServices::getInstance()->getRevisionStore()->getQueryInfo( [
'page' ] );
280 $revWhere = ActorMigration::newMigration()->getWhere( $db,
'rev_user', $users );
281 $orderUserField =
'rev_actor';
282 $userField = $this->orderBy ===
'actor' ?
'revactor_actor' :
'actor_name';
283 $tsField =
'revactor_timestamp';
284 $idField =
'revactor_rev';
293 if ( count( $users ) > 1 && !isset( $this->params[
'tag'] ) ) {
295 unset(
$revQuery[
'joins'][
'temp_rev_user'] );
307 $this->
addWhere( $revWhere[
'conds'] );
310 if ( $this->params[
'continue'] !==
null ) {
311 $continue = explode(
'|', $this->params[
'continue'] );
312 if ( $this->multiUserMode ) {
314 $modeFlag = array_shift( $continue );
316 $encUser = $db->addQuotes( array_shift( $continue ) );
320 $encTS = $db->addQuotes( $db->timestamp( $continue[0] ) );
321 $encId = (int)$continue[1];
323 $op = ( $this->params[
'dir'] ==
'older' ?
'<' :
'>' );
324 if ( $this->multiUserMode ) {
326 "$userField $op $encUser OR " .
327 "($userField = $encUser AND " .
328 "($tsField $op $encTS OR " .
329 "($tsField = $encTS AND " .
330 "$idField $op= $encId)))"
334 "$tsField $op $encTS OR " .
335 "($tsField = $encTS AND " .
336 "$idField $op= $encId)"
345 $bitmask = RevisionRecord::DELETED_USER;
347 ->userHasAnyRight( $user,
'suppressrevision',
'viewsuppressed' )
349 $bitmask = RevisionRecord::DELETED_USER | RevisionRecord::DELETED_RESTRICTED;
354 $this->
addWhere( $db->bitAnd(
'rev_deleted', $bitmask ) .
" != $bitmask" );
358 if ( count( $users ) > 1 ) {
359 $this->
addWhereRange( $orderUserField, $this->params[
'dir'],
null,
null );
364 $this->params[
'dir'], $this->params[
'start'], $this->params[
'end'] );
367 $this->
addWhereRange( $idField, $this->params[
'dir'],
null,
null );
369 $this->
addWhereFld(
'page_namespace', $this->params[
'namespace'] );
371 $show = $this->params[
'show'];
372 if ( $this->params[
'toponly'] ) {
375 if ( $show !==
null ) {
376 $show = array_flip( $show );
378 if ( ( isset( $show[
'minor'] ) && isset( $show[
'!minor'] ) )
379 || ( isset( $show[
'patrolled'] ) && isset( $show[
'!patrolled'] ) )
380 || ( isset( $show[
'autopatrolled'] ) && isset( $show[
'!autopatrolled'] ) )
381 || ( isset( $show[
'autopatrolled'] ) && isset( $show[
'!patrolled'] ) )
382 || ( isset( $show[
'top'] ) && isset( $show[
'!top'] ) )
383 || ( isset( $show[
'new'] ) && isset( $show[
'!new'] ) )
388 $this->
addWhereIf(
'rev_minor_edit = 0', isset( $show[
'!minor'] ) );
389 $this->
addWhereIf(
'rev_minor_edit != 0', isset( $show[
'minor'] ) );
391 'rc_patrolled = ' . RecentChange::PRC_UNPATROLLED,
392 isset( $show[
'!patrolled'] )
395 'rc_patrolled != ' . RecentChange::PRC_UNPATROLLED,
396 isset( $show[
'patrolled'] )
399 'rc_patrolled != ' . RecentChange::PRC_AUTOPATROLLED,
400 isset( $show[
'!autopatrolled'] )
403 'rc_patrolled = ' . RecentChange::PRC_AUTOPATROLLED,
404 isset( $show[
'autopatrolled'] )
406 $this->
addWhereIf( $idField .
' != page_latest', isset( $show[
'!top'] ) );
407 $this->
addWhereIf( $idField .
' = page_latest', isset( $show[
'top'] ) );
408 $this->
addWhereIf(
'rev_parent_id != 0', isset( $show[
'!new'] ) );
409 $this->
addWhereIf(
'rev_parent_id = 0', isset( $show[
'new'] ) );
413 if ( isset( $show[
'patrolled'] ) || isset( $show[
'!patrolled'] ) ||
414 isset( $show[
'autopatrolled'] ) || isset( $show[
'!autopatrolled'] ) || $this->fld_patrolled
416 if ( !$user->useRCPatrol() && !$user->useNPPatrol() ) {
417 $this->
dieWithError(
'apierror-permissiondenied-patrolflag',
'permissiondenied' );
420 $isFilterset = isset( $show[
'patrolled'] ) || isset( $show[
'!patrolled'] ) ||
421 isset( $show[
'autopatrolled'] ) || isset( $show[
'!autopatrolled'] );
424 $isFilterset ?
'JOIN' :
'LEFT JOIN',
425 [
'rc_this_oldid = ' . $idField ]
429 $this->
addFieldsIf(
'rc_patrolled', $this->fld_patrolled );
431 if ( $this->fld_tags ) {
435 if ( isset( $this->params[
'tag'] ) ) {
438 [
'change_tag' => [
'JOIN', [ $idField .
' = ct_rev_id' ] ] ]
440 $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
442 $this->
addWhereFld(
'ct_tag_id', $changeTagDefStore->getId( $this->params[
'tag'] ) );
449 'MAX_EXECUTION_TIME',
450 $this->
getConfig()->
get(
'MaxExecutionTimeForExpensiveQueries' )
464 if ( $row->rev_deleted & RevisionRecord::DELETED_TEXT ) {
465 $vals[
'texthidden'] =
true;
470 $vals[
'userid'] = (int)$row->rev_user;
471 $vals[
'user'] = $row->rev_user_text;
472 if ( $row->rev_deleted & RevisionRecord::DELETED_USER ) {
473 $vals[
'userhidden'] =
true;
476 if ( $this->fld_ids ) {
477 $vals[
'pageid'] = (int)$row->rev_page;
478 $vals[
'revid'] = (int)$row->rev_id;
480 if ( $row->rev_parent_id !==
null ) {
481 $vals[
'parentid'] = (int)$row->rev_parent_id;
485 $title = Title::makeTitle( $row->page_namespace, $row->page_title );
487 if ( $this->fld_title ) {
491 if ( $this->fld_timestamp ) {
492 $vals[
'timestamp'] =
wfTimestamp( TS_ISO_8601, $row->rev_timestamp );
495 if ( $this->fld_flags ) {
496 $vals[
'new'] = $row->rev_parent_id == 0 && $row->rev_parent_id !==
null;
497 $vals[
'minor'] = (bool)$row->rev_minor_edit;
498 $vals[
'top'] = $row->page_latest == $row->rev_id;
501 if ( $this->fld_comment || $this->fld_parsedcomment ) {
502 if ( $row->rev_deleted & RevisionRecord::DELETED_COMMENT ) {
503 $vals[
'commenthidden'] =
true;
507 $userCanView = RevisionRecord::userCanBitfield(
509 RevisionRecord::DELETED_COMMENT, $this->getUser()
512 if ( $userCanView ) {
513 $comment = $this->commentStore->getComment(
'rev_comment', $row )->text;
514 if ( $this->fld_comment ) {
515 $vals[
'comment'] = $comment;
518 if ( $this->fld_parsedcomment ) {
524 if ( $this->fld_patrolled ) {
525 $vals[
'patrolled'] = $row->rc_patrolled != RecentChange::PRC_UNPATROLLED;
526 $vals[
'autopatrolled'] = $row->rc_patrolled == RecentChange::PRC_AUTOPATROLLED;
529 if ( $this->fld_size && $row->rev_len !==
null ) {
530 $vals[
'size'] = (int)$row->rev_len;
533 if ( $this->fld_sizediff
534 && $row->rev_len !==
null
535 && $row->rev_parent_id !==
null
537 $parentLen = $this->parentLens[$row->rev_parent_id] ?? 0;
538 $vals[
'sizediff'] = (int)$row->rev_len - $parentLen;
541 if ( $this->fld_tags ) {
542 if ( $row->ts_tags ) {
543 $tags = explode(
',', $row->ts_tags );
544 ApiResult::setIndexedTagName( $tags,
'tag' );
545 $vals[
'tags'] = $tags;
551 if ( $anyHidden && ( $row->rev_deleted & RevisionRecord::DELETED_RESTRICTED ) ) {
552 $vals[
'suppressed'] =
true;
559 if ( $this->multiUserMode ) {
560 switch ( $this->orderBy ) {
562 return "id|$row->rev_user|$row->rev_timestamp|$row->rev_id";
564 return "name|$row->rev_user_text|$row->rev_timestamp|$row->rev_id";
566 return "actor|$row->rev_actor|$row->rev_timestamp|$row->rev_id";
569 return "$row->rev_timestamp|$row->rev_id";
576 return 'anon-public-user-private';
599 UserDef::PARAM_ALLOWED_USER_TYPES => [
'name',
'ip',
'interwiki' ],
606 'userprefix' =>
null,
651 'apihelp-query+usercontribs-param-show',
665 'action=query&list=usercontribs&ucuser=Example'
666 =>
'apihelp-query+usercontribs-example-user',
667 'action=query&list=usercontribs&ucuserprefix=192.0.2.'
668 =>
'apihelp-query+usercontribs-example-ipprefix',
673 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Usercontribs';
681class_alias( ApiQueryUserContribs::class,
'ApiQueryContributions' );
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
encodeParamName( $paramName)
This method mangles parameter name based on the prefix supplied to the constructor.
dieContinueUsageIf( $condition)
Die with the 'badcontinue' error.
getPermissionManager()
Obtain a PermissionManager instance that subclasses may use in their authorization checks.
requireOnlyOneParameter( $params,... $required)
Die if none or more than one of a certain set of parameters is set and not false.
const PARAM_HELP_MSG_PER_VALUE
((string|array|Message)[]) When PARAM_TYPE is an array, this is an array mapping those values to $msg...
const LIMIT_BIG1
Fast query, standard limit.
getResult()
Get the result object.
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
const LIMIT_BIG2
Fast query, apihighlimits limit.
getModuleName()
Get the name of the module being executed by this instance.
This is a base class for all Query modules.
static addTitleInfo(&$arr, $title, $prefix='')
Add information (title and namespace) about a Title object to a result array.
setContinueEnumParameter( $paramName, $paramValue)
Set a query-continue value.
processRow( $row, array &$data, array &$hookData)
Call the ApiQueryBaseProcessRow hook.
resetQueryParams()
Blank the internal arrays with query parameters.
addWhereIf( $value, $condition)
Same as addWhere(), but add the WHERE clauses only if a condition is met.
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.
addFields( $value)
Add a set of fields to select to the internal array.
addOption( $name, $value=null)
Add an option such as LIMIT or USE INDEX.
addTables( $tables, $alias=null)
Add a set of tables to the internal array.
addTimestampWhereRange( $field, $dir, $start, $end, $sort=true)
Add a WHERE clause corresponding to a range, similar to addWhereRange, but converts $start and $end t...
getDB()
Get the Query database connection (read-only) Stable to override.
executeGenderCacheFromResultWrapper(IResultWrapper $res, $fname=__METHOD__, $fieldPrefix='page')
Preprocess the result set to fill the GenderCache with the necessary information before using self::a...
select( $method, $extraQuery=[], array &$hookData=null)
Execute a SELECT query based on the values in the internal arrays.
addFieldsIf( $value, $condition)
Same as addFields(), but add the fields only if a condition is met.
addJoinConds( $join_conds)
Add a set of JOIN conditions to the internal array.
addWhereFld( $field, $value)
Equivalent to addWhere( [ $field => $value ] )
addWhere( $value)
Add a set of WHERE clauses to the internal array.
This query action adds a list of a specified user's contributions to the output.
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
CommentStore $commentStore
getCacheMode( $params)
Get the cache mode for the data generated by this module.
getHelpUrls()
Return links to more detailed help pages about the module.
getExamplesMessages()
Returns usage examples for this module.
__construct(ApiQuery $query, $moduleName)
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
prepareQuery(array $users, $limit)
Prepares the query and returns the limit of rows requested.
extractRowInfo( $row)
Extract fields from the database row and append them to a result array.
This is the main query class.
getUser()
Stable to override.
static formatComment( $comment, $title=null, $local=false, $wikiId=null)
This function is called by all recent changes variants, by the page history, and by the user contribu...
static newFromResult( $res)
static getCanonicalName( $name, $validate='valid')
Given unvalidated user input, return a canonical username, or false if the username is invalid.
static newFromRow( $row, $data=null)
Create a new user object from a user row.
static isIP( $name)
Does the string match an anonymous IP address?