35 parent::__construct( $query, $moduleName,
'uc' );
50 $prop = array_flip( $this->params[
'prop'] );
51 $this->fld_ids = isset( $prop[
'ids'] );
52 $this->fld_title = isset( $prop[
'title'] );
53 $this->fld_comment = isset( $prop[
'comment'] );
54 $this->fld_parsedcomment = isset( $prop[
'parsedcomment'] );
55 $this->fld_size = isset( $prop[
'size'] );
56 $this->fld_sizediff = isset( $prop[
'sizediff'] );
57 $this->fld_flags = isset( $prop[
'flags'] );
58 $this->fld_timestamp = isset( $prop[
'timestamp'] );
59 $this->fld_patrolled = isset( $prop[
'patrolled'] );
60 $this->fld_tags = isset( $prop[
'tags'] );
65 $dbSecondary = $this->
getDB();
67 $sort = ( $this->params[
'dir'] ==
'newer' ?
'' :
' DESC' );
68 $op = ( $this->params[
'dir'] ==
'older' ?
'<' :
'>' );
74 if ( isset( $this->params[
'userprefix'] ) ) {
75 $this->multiUserMode =
true;
76 $this->orderBy =
'name';
82 $userIter = call_user_func(
function () use ( $dbSecondary,
$sort, $op, $fname ) {
84 if ( !is_null( $this->params[
'continue'] ) ) {
85 $continue = explode(
'|', $this->params[
'continue'] );
88 $fromName = $continue[1];
90 $like = $dbSecondary->buildLike( $this->params[
'userprefix'], $dbSecondary->anyString() );
95 $from = $fromName ?
"$op= " . $dbSecondary->addQuotes( $fromName ) :
false;
96 $res = $dbSecondary->select(
98 [
'actor_id',
'user_id' =>
'COALESCE(actor_user,0)',
'user_name' =>
'actor_name' ],
99 array_merge( [
"actor_name$like" ], $from ? [
"actor_name $from" ] : [] ),
101 [
'ORDER BY' => [
"user_name $sort" ],
'LIMIT' => $limit ]
106 foreach (
$res as $row ) {
107 if ( ++$count >= $limit ) {
108 $fromName = $row->user_name;
113 }
while ( $fromName !==
false );
118 } elseif ( isset( $this->params[
'userids'] ) ) {
119 if ( $this->params[
'userids'] === [] ) {
121 $this->
dieWithError( [
'apierror-paramempty', $encParamName ],
"paramempty_$encParamName" );
125 foreach ( $this->params[
'userids'] as $uid ) {
127 $this->
dieWithError( [
'apierror-invaliduserid', $uid ],
'invaliduserid' );
132 $this->orderBy =
'id';
133 $this->multiUserMode = count( $ids ) > 1;
135 $from = $fromId =
false;
136 if ( $this->multiUserMode && !is_null( $this->params[
'continue'] ) ) {
137 $continue = explode(
'|', $this->params[
'continue'] );
140 $fromId = (int)$continue[1];
142 $from =
"$op= $fromId";
145 $res = $dbSecondary->select(
147 [
'actor_id',
'user_id' =>
'actor_user',
'user_name' =>
'actor_name' ],
148 array_merge( [
'actor_user' => $ids ], $from ? [
"actor_id $from" ] : [] ),
150 [
'ORDER BY' =>
"user_id $sort" ]
153 $batchSize = count( $ids );
156 if ( !count( $this->params[
'user'] ) ) {
159 [
'apierror-paramempty', $encParamName ],
"paramempty_$encParamName"
162 foreach ( $this->params[
'user'] as $u ) {
166 [
'apierror-paramempty', $encParamName ],
"paramempty_$encParamName"
174 if ( $name ===
false ) {
177 [
'apierror-baduser', $encParamName,
wfEscapeWikiText( $u ) ],
"baduser_$encParamName"
180 $names[$name] =
null;
184 $this->orderBy =
'name';
185 $this->multiUserMode = count( $names ) > 1;
187 $from = $fromName =
false;
188 if ( $this->multiUserMode && !is_null( $this->params[
'continue'] ) ) {
189 $continue = explode(
'|', $this->params[
'continue'] );
192 $fromName = $continue[1];
193 $from =
"$op= " . $dbSecondary->addQuotes( $fromName );
196 $res = $dbSecondary->select(
198 [
'actor_id',
'user_id' =>
'actor_user',
'user_name' =>
'actor_name' ],
199 array_merge( [
'actor_name' => array_keys( $names ) ], $from ? [
"actor_id $from" ] : [] ),
201 [
'ORDER BY' =>
"actor_name $sort" ]
204 $batchSize = count( $names );
208 if ( $batchSize > 1 ) {
209 $this->orderBy =
'actor';
213 $limit = $this->params[
'limit'];
215 while ( $userIter->valid() ) {
217 while ( count( $users ) < $batchSize && $userIter->valid() ) {
218 $users[] = $userIter->current();
224 $res = $this->
select( __METHOD__, [], $hookData );
226 if ( $this->fld_sizediff ) {
228 foreach (
$res as $row ) {
229 if ( $row->rev_parent_id ) {
230 $revIds[] = $row->rev_parent_id;
233 $this->parentLens = MediaWikiServices::getInstance()->getRevisionStore()
234 ->listRevisionSizes( $dbSecondary, $revIds );
237 foreach (
$res as $row ) {
238 if ( ++$count > $limit ) {
246 $fit = $this->
processRow( $row, $vals, $hookData ) &&
265 $db = $this->
getDB();
267 $revQuery = MediaWikiServices::getInstance()->getRevisionStore()->getQueryInfo( [
'page' ] );
270 $orderUserField =
'rev_actor';
271 $userField = $this->orderBy ===
'actor' ?
'revactor_actor' :
'actor_name';
272 $tsField =
'revactor_timestamp';
273 $idField =
'revactor_rev';
282 if ( count( $users ) > 1 && !isset( $this->params[
'tag'] ) ) {
284 unset(
$revQuery[
'joins'][
'temp_rev_user'] );
296 $this->
addWhere( $revWhere[
'conds'] );
299 if ( !is_null( $this->params[
'continue'] ) ) {
300 $continue = explode(
'|', $this->params[
'continue'] );
301 if ( $this->multiUserMode ) {
303 $modeFlag = array_shift( $continue );
305 $encUser = $db->addQuotes( array_shift( $continue ) );
309 $encTS = $db->addQuotes( $db->timestamp( $continue[0] ) );
310 $encId = (int)$continue[1];
312 $op = ( $this->params[
'dir'] ==
'older' ?
'<' :
'>' );
313 if ( $this->multiUserMode ) {
315 "$userField $op $encUser OR " .
316 "($userField = $encUser AND " .
317 "($tsField $op $encTS OR " .
318 "($tsField = $encTS AND " .
319 "$idField $op= $encId)))"
323 "$tsField $op $encTS OR " .
324 "($tsField = $encTS AND " .
325 "$idField $op= $encId)"
334 $bitmask = RevisionRecord::DELETED_USER;
336 ->userHasAnyRight( $user,
'suppressrevision',
'viewsuppressed' )
338 $bitmask = RevisionRecord::DELETED_USER | RevisionRecord::DELETED_RESTRICTED;
343 $this->
addWhere( $db->bitAnd(
'rev_deleted', $bitmask ) .
" != $bitmask" );
347 if ( count( $users ) > 1 ) {
348 $this->
addWhereRange( $orderUserField, $this->params[
'dir'],
null,
null );
353 $this->params[
'dir'], $this->params[
'start'], $this->params[
'end'] );
356 $this->
addWhereRange( $idField, $this->params[
'dir'],
null,
null );
358 $this->
addWhereFld(
'page_namespace', $this->params[
'namespace'] );
360 $show = $this->params[
'show'];
361 if ( $this->params[
'toponly'] ) {
364 if ( !is_null( $show ) ) {
365 $show = array_flip( $show );
367 if ( ( isset( $show[
'minor'] ) && isset( $show[
'!minor'] ) )
368 || ( isset( $show[
'patrolled'] ) && isset( $show[
'!patrolled'] ) )
369 || ( isset( $show[
'autopatrolled'] ) && isset( $show[
'!autopatrolled'] ) )
370 || ( isset( $show[
'autopatrolled'] ) && isset( $show[
'!patrolled'] ) )
371 || ( isset( $show[
'top'] ) && isset( $show[
'!top'] ) )
372 || ( isset( $show[
'new'] ) && isset( $show[
'!new'] ) )
377 $this->
addWhereIf(
'rev_minor_edit = 0', isset( $show[
'!minor'] ) );
378 $this->
addWhereIf(
'rev_minor_edit != 0', isset( $show[
'minor'] ) );
381 isset( $show[
'!patrolled'] )
385 isset( $show[
'patrolled'] )
389 isset( $show[
'!autopatrolled'] )
393 isset( $show[
'autopatrolled'] )
395 $this->
addWhereIf( $idField .
' != page_latest', isset( $show[
'!top'] ) );
396 $this->
addWhereIf( $idField .
' = page_latest', isset( $show[
'top'] ) );
397 $this->
addWhereIf(
'rev_parent_id != 0', isset( $show[
'!new'] ) );
398 $this->
addWhereIf(
'rev_parent_id = 0', isset( $show[
'new'] ) );
402 if ( isset( $show[
'patrolled'] ) || isset( $show[
'!patrolled'] ) ||
403 isset( $show[
'autopatrolled'] ) || isset( $show[
'!autopatrolled'] ) || $this->fld_patrolled
405 if ( !$user->useRCPatrol() && !$user->useNPPatrol() ) {
406 $this->
dieWithError(
'apierror-permissiondenied-patrolflag',
'permissiondenied' );
409 $isFilterset = isset( $show[
'patrolled'] ) || isset( $show[
'!patrolled'] ) ||
410 isset( $show[
'autopatrolled'] ) || isset( $show[
'!autopatrolled'] );
413 $isFilterset ?
'JOIN' :
'LEFT JOIN',
418 'rc_timestamp = ' . $tsField,
419 'rc_this_oldid = ' . $idField,
424 $this->
addFieldsIf(
'rc_patrolled', $this->fld_patrolled );
426 if ( $this->fld_tags ) {
430 if ( isset( $this->params[
'tag'] ) ) {
433 [
'change_tag' => [
'JOIN', [ $idField .
' = ct_rev_id' ] ] ]
435 $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
437 $this->
addWhereFld(
'ct_tag_id', $changeTagDefStore->getId( $this->params[
'tag'] ) );
455 if ( $row->rev_deleted & RevisionRecord::DELETED_TEXT ) {
456 $vals[
'texthidden'] =
true;
461 $vals[
'userid'] = (int)$row->rev_user;
462 $vals[
'user'] = $row->rev_user_text;
463 if ( $row->rev_deleted & RevisionRecord::DELETED_USER ) {
464 $vals[
'userhidden'] =
true;
467 if ( $this->fld_ids ) {
468 $vals[
'pageid'] = (int)$row->rev_page;
469 $vals[
'revid'] = (
int)$row->rev_id;
471 if ( !is_null( $row->rev_parent_id ) ) {
472 $vals[
'parentid'] = (int)$row->rev_parent_id;
478 if ( $this->fld_title ) {
482 if ( $this->fld_timestamp ) {
483 $vals[
'timestamp'] =
wfTimestamp( TS_ISO_8601, $row->rev_timestamp );
486 if ( $this->fld_flags ) {
487 $vals[
'new'] = $row->rev_parent_id == 0 && !is_null( $row->rev_parent_id );
488 $vals[
'minor'] = (bool)$row->rev_minor_edit;
489 $vals[
'top'] = $row->page_latest == $row->rev_id;
492 if ( $this->fld_comment || $this->fld_parsedcomment ) {
493 if ( $row->rev_deleted & RevisionRecord::DELETED_COMMENT ) {
494 $vals[
'commenthidden'] =
true;
498 $userCanView = RevisionRecord::userCanBitfield(
500 RevisionRecord::DELETED_COMMENT, $this->getUser()
503 if ( $userCanView ) {
504 $comment = $this->commentStore->getComment(
'rev_comment', $row )->text;
505 if ( $this->fld_comment ) {
506 $vals[
'comment'] = $comment;
509 if ( $this->fld_parsedcomment ) {
515 if ( $this->fld_patrolled ) {
520 if ( $this->fld_size && !is_null( $row->rev_len ) ) {
521 $vals[
'size'] = (int)$row->rev_len;
524 if ( $this->fld_sizediff
525 && !is_null( $row->rev_len )
526 && !is_null( $row->rev_parent_id )
528 $parentLen = $this->parentLens[$row->rev_parent_id] ?? 0;
529 $vals[
'sizediff'] = (int)$row->rev_len - $parentLen;
532 if ( $this->fld_tags ) {
533 if ( $row->ts_tags ) {
534 $tags = explode(
',', $row->ts_tags );
536 $vals[
'tags'] = $tags;
542 if ( $anyHidden && ( $row->rev_deleted & RevisionRecord::DELETED_RESTRICTED ) ) {
543 $vals[
'suppressed'] =
true;
550 if ( $this->multiUserMode ) {
551 switch ( $this->orderBy ) {
553 return "id|$row->rev_user|$row->rev_timestamp|$row->rev_id";
555 return "name|$row->rev_user_text|$row->rev_timestamp|$row->rev_id";
557 return "actor|$row->rev_actor|$row->rev_timestamp|$row->rev_id";
560 return "$row->rev_timestamp|$row->rev_id";
567 return 'anon-public-user-private';
596 'userprefix' =>
null,
641 'apihelp-query+usercontribs-param-show',
655 'action=query&list=usercontribs&ucuser=Example'
656 =>
'apihelp-query+usercontribs-example-user',
657 'action=query&list=usercontribs&ucuserprefix=192.0.2.'
658 =>
'apihelp-query+usercontribs-example-ipprefix',
663 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Usercontribs';
671 class_alias( ApiQueryUserContribs::class,
'ApiQueryContributions' );