MediaWiki master
ApiQueryUserContribs.php
Go to the documentation of this file.
1<?php
9namespace MediaWiki\Api;
10
28use stdClass;
29use Wikimedia\IPUtils;
33
40
41 private CommentStore $commentStore;
42 private UserIdentityLookup $userIdentityLookup;
43 private UserNameUtils $userNameUtils;
44 private RevisionStore $revisionStore;
45 private NameTableStore $changeTagDefStore;
46 private ChangeTagsStore $changeTagsStore;
47 private ActorMigration $actorMigration;
48 private CommentFormatter $commentFormatter;
49
50 public function __construct(
51 ApiQuery $query,
52 string $moduleName,
53 CommentStore $commentStore,
54 UserIdentityLookup $userIdentityLookup,
55 UserNameUtils $userNameUtils,
56 RevisionStore $revisionStore,
57 NameTableStore $changeTagDefStore,
58 ChangeTagsStore $changeTagsStore,
59 ActorMigration $actorMigration,
60 CommentFormatter $commentFormatter
61 ) {
62 parent::__construct( $query, $moduleName, 'uc' );
63 $this->commentStore = $commentStore;
64 $this->userIdentityLookup = $userIdentityLookup;
65 $this->userNameUtils = $userNameUtils;
66 $this->revisionStore = $revisionStore;
67 $this->changeTagDefStore = $changeTagDefStore;
68 $this->changeTagsStore = $changeTagsStore;
69 $this->actorMigration = $actorMigration;
70 $this->commentFormatter = $commentFormatter;
71 }
72
73 private array $params;
74 private bool $multiUserMode;
75 private string $orderBy;
76 private array $parentLens;
77
78 private bool $fld_ids = false;
79 private bool $fld_title = false;
80 private bool $fld_timestamp = false;
81 private bool $fld_comment = false;
82 private bool $fld_parsedcomment = false;
83 private bool $fld_flags = false;
84 private bool $fld_patrolled = false;
85 private bool $fld_tags = false;
86 private bool $fld_size = false;
87 private bool $fld_sizediff = false;
88
89 public function execute() {
90 // Parse some parameters
91 $this->params = $this->extractRequestParams();
92
93 $prop = array_fill_keys( $this->params['prop'], true );
94 $this->fld_ids = isset( $prop['ids'] );
95 $this->fld_title = isset( $prop['title'] );
96 $this->fld_comment = isset( $prop['comment'] );
97 $this->fld_parsedcomment = isset( $prop['parsedcomment'] );
98 $this->fld_size = isset( $prop['size'] );
99 $this->fld_sizediff = isset( $prop['sizediff'] );
100 $this->fld_flags = isset( $prop['flags'] );
101 $this->fld_timestamp = isset( $prop['timestamp'] );
102 $this->fld_patrolled = isset( $prop['patrolled'] );
103 $this->fld_tags = isset( $prop['tags'] );
104
105 $dbSecondary = $this->getDB(); // any random replica DB
106
107 $sort = ( $this->params['dir'] == 'newer' ?
108 SelectQueryBuilder::SORT_ASC : SelectQueryBuilder::SORT_DESC );
109 $op = ( $this->params['dir'] == 'older' ? '<=' : '>=' );
110
111 // Create an Iterator that produces the UserIdentity objects we need, depending
112 // on which of the 'userprefix', 'userids', 'iprange', or 'user' params
113 // was specified.
114 $this->requireOnlyOneParameter( $this->params, 'userprefix', 'userids', 'iprange', 'user' );
115 if ( isset( $this->params['userprefix'] ) ) {
116 $this->multiUserMode = true;
117 $this->orderBy = 'name';
118 $fname = __METHOD__;
119
120 // Because 'userprefix' might produce a huge number of users (e.g.
121 // a wiki with users "Test00000001" to "Test99999999"), use a
122 // generator with batched lookup and continuation.
123 $userIter = call_user_func( function () use ( $dbSecondary, $sort, $op, $fname ) {
124 $fromName = false;
125 if ( $this->params['continue'] !== null ) {
126 $continue = $this->parseContinueParamOrDie( $this->params['continue'],
127 [ 'string', 'string', 'string', 'int' ] );
128 $this->dieContinueUsageIf( $continue[0] !== 'name' );
129 $fromName = $continue[1];
130 }
131
132 $limit = 501;
133 do {
134 $usersBatch = $this->userIdentityLookup
135 ->newSelectQueryBuilder()
136 ->caller( $fname )
137 ->limit( $limit )
138 ->whereUserNamePrefix( $this->params['userprefix'] )
139 ->where( $fromName !== false
140 ? $dbSecondary->buildComparison( $op, [ 'actor_name' => $fromName ] )
141 : [] )
142 ->orderByName( $sort )
143 ->fetchUserIdentities();
144
145 $count = 0;
146 $fromName = false;
147 foreach ( $usersBatch as $user ) {
148 if ( ++$count >= $limit ) {
149 $fromName = $user->getName();
150 break;
151 }
152 yield $user;
153 }
154 } while ( $fromName !== false );
155 } );
156 // Do the actual sorting client-side, because otherwise
157 // prepareQuery might try to sort by actor and confuse everything.
158 $batchSize = 1;
159 } elseif ( isset( $this->params['userids'] ) ) {
160 if ( $this->params['userids'] === [] ) {
161 $encParamName = $this->encodeParamName( 'userids' );
162 $this->dieWithError( [ 'apierror-paramempty', $encParamName ], "paramempty_$encParamName" );
163 }
164
165 $ids = [];
166 foreach ( $this->params['userids'] as $uid ) {
167 if ( $uid <= 0 ) {
168 $this->dieWithError( [ 'apierror-invaliduserid', $uid ], 'invaliduserid' );
169 }
170 $ids[] = $uid;
171 }
172
173 $this->orderBy = 'actor';
174 $this->multiUserMode = count( $ids ) > 1;
175
176 $fromId = false;
177 if ( $this->multiUserMode && $this->params['continue'] !== null ) {
178 $continue = $this->parseContinueParamOrDie( $this->params['continue'],
179 [ 'string', 'int', 'string', 'int' ] );
180 $this->dieContinueUsageIf( $continue[0] !== 'actor' );
181 $fromId = $continue[1];
182 }
183
184 $userIter = $this->userIdentityLookup
185 ->newSelectQueryBuilder()
186 ->caller( __METHOD__ )
187 ->whereUserIds( $ids )
188 ->orderByUserId( $sort )
189 ->where( $fromId ? $dbSecondary->buildComparison( $op, [ 'actor_id' => $fromId ] ) : [] )
190 ->fetchUserIdentities();
191 $batchSize = count( $ids );
192 } elseif ( isset( $this->params['iprange'] ) ) {
193 // Make sure it is a valid range and within the CIDR limit
194 $ipRange = $this->params['iprange'];
195 $contribsCIDRLimit = $this->getConfig()->get( MainConfigNames::RangeContributionsCIDRLimit );
196 if ( IPUtils::isIPv4( $ipRange ) ) {
197 $type = 'IPv4';
198 $cidrLimit = $contribsCIDRLimit['IPv4'];
199 } elseif ( IPUtils::isIPv6( $ipRange ) ) {
200 $type = 'IPv6';
201 $cidrLimit = $contribsCIDRLimit['IPv6'];
202 } else {
203 $this->dieWithError( [ 'apierror-invalidiprange', $ipRange ], 'invalidiprange' );
204 }
205 $range = IPUtils::parseCIDR( $ipRange )[1];
206 if ( $range === false ) {
207 $this->dieWithError( [ 'apierror-invalidiprange', $ipRange ], 'invalidiprange' );
208 } elseif ( $range < $cidrLimit ) {
209 $this->dieWithError( [ 'apierror-cidrtoobroad', $type, $cidrLimit ] );
210 }
211
212 $this->multiUserMode = true;
213 $this->orderBy = 'name';
214 $fname = __METHOD__;
215
216 // Because 'iprange' might produce a huge number of ips, use a
217 // generator with batched lookup and continuation.
218 $userIter = call_user_func( function () use ( $dbSecondary, $sort, $op, $fname, $ipRange ) {
219 [ $start, $end ] = IPUtils::parseRange( $ipRange );
220 if ( $this->params['continue'] !== null ) {
221 $continue = $this->parseContinueParamOrDie( $this->params['continue'],
222 [ 'string', 'string', 'string', 'int' ] );
223 $this->dieContinueUsageIf( $continue[0] !== 'name' );
224 $fromName = $continue[1];
225 $fromIPHex = IPUtils::toHex( $fromName );
226 $this->dieContinueUsageIf( $fromIPHex === false );
227 if ( $op == '<=' ) {
228 $end = $fromIPHex;
229 } else {
230 $start = $fromIPHex;
231 }
232 }
233
234 $limit = 501;
235
236 do {
237 $res = $dbSecondary->newSelectQueryBuilder()
238 ->select( 'ipc_hex' )
239 ->from( 'ip_changes' )
240 ->where( $dbSecondary->expr( 'ipc_hex', '>=', $start )->and( 'ipc_hex', '<=', $end ) )
241 ->groupBy( 'ipc_hex' )
242 ->orderBy( 'ipc_hex', $sort )
243 ->limit( $limit )
244 ->caller( $fname )
245 ->fetchResultSet();
246
247 $count = 0;
248 $fromName = false;
249 foreach ( $res as $row ) {
250 $ipAddr = IPUtils::formatHex( $row->ipc_hex );
251 if ( ++$count >= $limit ) {
252 $fromName = $ipAddr;
253 break;
254 }
255 yield UserIdentityValue::newAnonymous( $ipAddr );
256 }
257 } while ( $fromName !== false );
258 } );
259 // Do the actual sorting client-side, because otherwise
260 // prepareQuery might try to sort by actor and confuse everything.
261 $batchSize = 1;
262 } else {
263 $names = [];
264 if ( !count( $this->params['user'] ) ) {
265 $encParamName = $this->encodeParamName( 'user' );
266 $this->dieWithError(
267 [ 'apierror-paramempty', $encParamName ], "paramempty_$encParamName"
268 );
269 }
270 foreach ( $this->params['user'] as $u ) {
271 if ( $u === '' ) {
272 $encParamName = $this->encodeParamName( 'user' );
273 $this->dieWithError(
274 [ 'apierror-paramempty', $encParamName ], "paramempty_$encParamName"
275 );
276 }
277
278 if ( $this->userNameUtils->isIP( $u ) || ExternalUserNames::isExternal( $u ) ) {
279 $names[$u] = null;
280 } else {
281 $name = $this->userNameUtils->getCanonical( $u );
282 if ( $name === false ) {
283 $encParamName = $this->encodeParamName( 'user' );
284 $this->dieWithError(
285 [ 'apierror-baduser', $encParamName, wfEscapeWikiText( $u ) ], "baduser_$encParamName"
286 );
287 }
288 $names[$name] = null;
289 }
290 }
291
292 $this->orderBy = 'actor';
293 $this->multiUserMode = count( $names ) > 1;
294
295 $fromId = false;
296 if ( $this->multiUserMode && $this->params['continue'] !== null ) {
297 $continue = $this->parseContinueParamOrDie( $this->params['continue'],
298 [ 'string', 'int', 'string', 'int' ] );
299 $this->dieContinueUsageIf( $continue[0] !== 'actor' );
300 $fromId = $continue[1];
301 }
302
303 $userIter = $this->userIdentityLookup
304 ->newSelectQueryBuilder()
305 ->caller( __METHOD__ )
306 ->whereUserNames( array_keys( $names ) )
307 ->orderByName( $sort )
308 ->where( $fromId ? $dbSecondary->buildComparison( $op, [ 'actor_id' => $fromId ] ) : [] )
309 ->fetchUserIdentities();
310 $batchSize = count( $names );
311 }
312
313 $count = 0;
314 $limit = $this->params['limit'];
315 $userIter->rewind();
316 while ( $userIter->valid() ) {
317 $users = [];
318 while ( count( $users ) < $batchSize && $userIter->valid() ) {
319 $users[] = $userIter->current();
320 $userIter->next();
321 }
322
323 $hookData = [];
324 $this->prepareQuery( $users, $limit - $count );
325 $res = $this->select( __METHOD__, [], $hookData );
326
327 if ( $this->fld_title ) {
328 $this->executeGenderCacheFromResultWrapper( $res, __METHOD__ );
329 }
330
331 if ( $this->fld_sizediff ) {
332 $revIds = [];
333 foreach ( $res as $row ) {
334 if ( $row->rev_parent_id ) {
335 $revIds[] = (int)$row->rev_parent_id;
336 }
337 }
338 $this->parentLens = $this->revisionStore->getRevisionSizes( $revIds );
339 }
340
341 foreach ( $res as $row ) {
342 if ( ++$count > $limit ) {
343 // We've reached the one extra which shows that there are
344 // additional pages to be had. Stop here...
345 $this->setContinueEnumParameter( 'continue', $this->continueStr( $row ) );
346 break 2;
347 }
348
349 $vals = $this->extractRowInfo( $row );
350 $fit = $this->processRow( $row, $vals, $hookData ) &&
351 $this->getResult()->addValue( [ 'query', $this->getModuleName() ], null, $vals );
352 if ( !$fit ) {
353 $this->setContinueEnumParameter( 'continue', $this->continueStr( $row ) );
354 break 2;
355 }
356 }
357 }
358
359 $this->getResult()->addIndexedTagName( [ 'query', $this->getModuleName() ], 'item' );
360 }
361
367 private function prepareQuery( array $users, $limit ) {
368 $this->resetQueryParams();
369 $db = $this->getDB();
370
371 $queryBuilder = $this->revisionStore->newSelectQueryBuilder( $db )->joinComment()->joinPage();
372 $revWhere = $this->actorMigration->getWhere( $db, 'rev_user', $users );
373
374 $orderUserField = 'rev_actor';
375 $userField = $this->orderBy === 'actor' ? 'rev_actor' : 'actor_name';
376 $tsField = 'rev_timestamp';
377 $idField = 'rev_id';
378
379 $this->getQueryBuilder()->merge( $queryBuilder );
380 $this->addWhere( $revWhere['conds'] );
381 // Force the appropriate index to avoid bad query plans (T307815 and T307295)
382 if ( isset( $revWhere['orconds']['newactor'] ) ) {
383 $this->addOption( 'USE INDEX', [ 'revision' => 'rev_actor_timestamp' ] );
384 }
385
386 // Handle continue parameter
387 if ( $this->params['continue'] !== null ) {
388 if ( $this->multiUserMode ) {
389 $continue = $this->parseContinueParamOrDie( $this->params['continue'],
390 [ 'string', 'string', 'timestamp', 'int' ] );
391 $modeFlag = array_shift( $continue );
392 $this->dieContinueUsageIf( $modeFlag !== $this->orderBy );
393 $encUser = array_shift( $continue );
394 } else {
395 $continue = $this->parseContinueParamOrDie( $this->params['continue'],
396 [ 'timestamp', 'int' ] );
397 }
398 $op = ( $this->params['dir'] == 'older' ? '<=' : '>=' );
399 if ( $this->multiUserMode ) {
400 $this->addWhere( $db->buildComparison( $op, [
401 // @phan-suppress-next-line PhanPossiblyUndeclaredVariable encUser is set when used
402 $userField => $encUser,
403 $tsField => $db->timestamp( $continue[0] ),
404 $idField => $continue[1],
405 ] ) );
406 } else {
407 $this->addWhere( $db->buildComparison( $op, [
408 $tsField => $db->timestamp( $continue[0] ),
409 $idField => $continue[1],
410 ] ) );
411 }
412 }
413
414 // Don't include any revisions where we're not supposed to be able to
415 // see the username.
416 if ( !$this->getAuthority()->isAllowed( 'deletedhistory' ) ) {
417 $bitmask = RevisionRecord::DELETED_USER;
418 } elseif ( !$this->getAuthority()->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
419 $bitmask = RevisionRecord::DELETED_USER | RevisionRecord::DELETED_RESTRICTED;
420 } else {
421 $bitmask = 0;
422 }
423 if ( $bitmask ) {
424 $this->addWhere( $db->bitAnd( 'rev_deleted', $bitmask ) . " != $bitmask" );
425 }
426
427 // Add the user field to ORDER BY if there are multiple users
428 if ( count( $users ) > 1 ) {
429 $this->addWhereRange( $orderUserField, $this->params['dir'], null, null );
430 }
431
432 // Then timestamp
433 $this->addTimestampWhereRange( $tsField,
434 $this->params['dir'], $this->params['start'], $this->params['end'] );
435
436 // Then rev_id for a total ordering
437 $this->addWhereRange( $idField, $this->params['dir'], null, null );
438
439 $this->addWhereFld( 'page_namespace', $this->params['namespace'] );
440
441 $show = $this->params['show'];
442 if ( $this->params['toponly'] ) { // deprecated/old param
443 $show[] = 'top';
444 }
445 if ( $show !== null ) {
446 $show = array_fill_keys( $show, true );
447
448 if ( ( isset( $show['minor'] ) && isset( $show['!minor'] ) )
449 || ( isset( $show['patrolled'] ) && isset( $show['!patrolled'] ) )
450 || ( isset( $show['autopatrolled'] ) && isset( $show['!autopatrolled'] ) )
451 || ( isset( $show['autopatrolled'] ) && isset( $show['!patrolled'] ) )
452 || ( isset( $show['top'] ) && isset( $show['!top'] ) )
453 || ( isset( $show['new'] ) && isset( $show['!new'] ) )
454 ) {
455 $this->dieWithError( 'apierror-show' );
456 }
457
458 $this->addWhereIf( [ 'rev_minor_edit' => 0 ], isset( $show['!minor'] ) );
459 $this->addWhereIf( $db->expr( 'rev_minor_edit', '!=', 0 ), isset( $show['minor'] ) );
460 $this->addWhereIf(
461 [ 'rc_patrolled' => RecentChange::PRC_UNPATROLLED ],
462 isset( $show['!patrolled'] )
463 );
464 $this->addWhereIf(
465 $db->expr( 'rc_patrolled', '!=', RecentChange::PRC_UNPATROLLED ),
466 isset( $show['patrolled'] )
467 );
468 $this->addWhereIf(
469 $db->expr( 'rc_patrolled', '!=', RecentChange::PRC_AUTOPATROLLED ),
470 isset( $show['!autopatrolled'] )
471 );
472 $this->addWhereIf(
473 [ 'rc_patrolled' => RecentChange::PRC_AUTOPATROLLED ],
474 isset( $show['autopatrolled'] )
475 );
476 $this->addWhereIf( $idField . ' != page_latest', isset( $show['!top'] ) );
477 $this->addWhereIf( $idField . ' = page_latest', isset( $show['top'] ) );
478 $this->addWhereIf( $db->expr( 'rev_parent_id', '!=', 0 ), isset( $show['!new'] ) );
479 $this->addWhereIf( [ 'rev_parent_id' => 0 ], isset( $show['new'] ) );
480 }
481 $this->addOption( 'LIMIT', $limit + 1 );
482
483 if ( isset( $show['patrolled'] ) || isset( $show['!patrolled'] ) ||
484 isset( $show['autopatrolled'] ) || isset( $show['!autopatrolled'] ) || $this->fld_patrolled
485 ) {
486 $user = $this->getUser();
487 if ( !$user->useRCPatrol() && !$user->useNPPatrol() ) {
488 $this->dieWithError( 'apierror-permissiondenied-patrolflag', 'permissiondenied' );
489 }
490
491 $isFilterset = isset( $show['patrolled'] ) || isset( $show['!patrolled'] ) ||
492 isset( $show['autopatrolled'] ) || isset( $show['!autopatrolled'] );
493 $this->addTables( 'recentchanges' );
494 $this->addJoinConds( [ 'recentchanges' => [
495 $isFilterset ? 'JOIN' : 'LEFT JOIN',
496 [ 'rc_this_oldid = ' . $idField ]
497 ] ] );
498 }
499
500 $this->addFieldsIf( 'rc_patrolled', $this->fld_patrolled );
501
502 if ( $this->fld_tags ) {
503 $this->addFields( [
504 'ts_tags' => $this->changeTagsStore->makeTagSummarySubquery( 'revision' )
505 ] );
506 }
507
508 if ( isset( $this->params['tag'] ) ) {
509 $this->addTables( 'change_tag' );
510 $this->addJoinConds(
511 [ 'change_tag' => [ 'JOIN', [ $idField . ' = ct_rev_id' ] ] ]
512 );
513 try {
514 $this->addWhereFld( 'ct_tag_id', $this->changeTagDefStore->getId( $this->params['tag'] ) );
515 } catch ( NameTableAccessException ) {
516 // Return nothing.
517 $this->addWhere( '1=0' );
518 }
519 }
520 $this->addOption(
521 'MAX_EXECUTION_TIME',
523 );
524 }
525
532 private function extractRowInfo( $row ) {
533 $vals = [];
534 $anyHidden = false;
535
536 if ( $row->rev_deleted & RevisionRecord::DELETED_TEXT ) {
537 $vals['texthidden'] = true;
538 $anyHidden = true;
539 }
540
541 // Any rows where we can't view the user were filtered out in the query.
542 $vals['userid'] = (int)$row->rev_user;
543 $vals['user'] = $row->rev_user_text;
544 if ( $row->rev_deleted & RevisionRecord::DELETED_USER ) {
545 $vals['userhidden'] = true;
546 $anyHidden = true;
547 }
548 if ( $this->fld_ids ) {
549 $vals['pageid'] = (int)$row->rev_page;
550 $vals['revid'] = (int)$row->rev_id;
551
552 if ( $row->rev_parent_id !== null ) {
553 $vals['parentid'] = (int)$row->rev_parent_id;
554 }
555 }
556
557 $title = Title::makeTitle( $row->page_namespace, $row->page_title );
558
559 if ( $this->fld_title ) {
560 ApiQueryBase::addTitleInfo( $vals, $title );
561 }
562
563 if ( $this->fld_timestamp ) {
564 $vals['timestamp'] = wfTimestamp( TS_ISO_8601, $row->rev_timestamp );
565 }
566
567 if ( $this->fld_flags ) {
568 $vals['new'] = $row->rev_parent_id == 0 && $row->rev_parent_id !== null;
569 $vals['minor'] = (bool)$row->rev_minor_edit;
570 $vals['top'] = $row->page_latest == $row->rev_id;
571 }
572
573 if ( $this->fld_comment || $this->fld_parsedcomment ) {
574 if ( $row->rev_deleted & RevisionRecord::DELETED_COMMENT ) {
575 $vals['commenthidden'] = true;
576 $anyHidden = true;
577 }
578
579 $userCanView = RevisionRecord::userCanBitfield(
580 $row->rev_deleted,
581 RevisionRecord::DELETED_COMMENT, $this->getAuthority()
582 );
583
584 if ( $userCanView ) {
585 $comment = $this->commentStore->getComment( 'rev_comment', $row )->text;
586 if ( $this->fld_comment ) {
587 $vals['comment'] = $comment;
588 }
589
590 if ( $this->fld_parsedcomment ) {
591 $vals['parsedcomment'] = $this->commentFormatter->format( $comment, $title );
592 }
593 }
594 }
595
596 if ( $this->fld_patrolled ) {
597 $vals['patrolled'] = $row->rc_patrolled != RecentChange::PRC_UNPATROLLED;
598 $vals['autopatrolled'] = $row->rc_patrolled == RecentChange::PRC_AUTOPATROLLED;
599 }
600
601 if ( $this->fld_size && $row->rev_len !== null ) {
602 $vals['size'] = (int)$row->rev_len;
603 }
604
605 if ( $this->fld_sizediff
606 && $row->rev_len !== null
607 && $row->rev_parent_id !== null
608 ) {
609 $parentLen = $this->parentLens[$row->rev_parent_id] ?? 0;
610 $vals['sizediff'] = (int)$row->rev_len - $parentLen;
611 }
612
613 if ( $this->fld_tags ) {
614 if ( $row->ts_tags ) {
615 $tags = explode( ',', $row->ts_tags );
616 ApiResult::setIndexedTagName( $tags, 'tag' );
617 $vals['tags'] = $tags;
618 } else {
619 $vals['tags'] = [];
620 }
621 }
622
623 if ( $anyHidden && ( $row->rev_deleted & RevisionRecord::DELETED_RESTRICTED ) ) {
624 $vals['suppressed'] = true;
625 }
626
627 return $vals;
628 }
629
630 private function continueStr( \stdClass $row ): string {
631 if ( $this->multiUserMode ) {
632 switch ( $this->orderBy ) {
633 case 'name':
634 return "name|$row->rev_user_text|$row->rev_timestamp|$row->rev_id";
635 case 'actor':
636 return "actor|$row->rev_actor|$row->rev_timestamp|$row->rev_id";
637 }
638 } else {
639 return "$row->rev_timestamp|$row->rev_id";
640 }
641 }
642
644 public function getCacheMode( $params ) {
645 // This module provides access to deleted revisions and patrol flags if
646 // the requester is logged in
647 return 'anon-public-user-private';
648 }
649
651 public function getAllowedParams() {
652 return [
653 'limit' => [
654 ParamValidator::PARAM_DEFAULT => 10,
655 ParamValidator::PARAM_TYPE => 'limit',
656 IntegerDef::PARAM_MIN => 1,
657 IntegerDef::PARAM_MAX => ApiBase::LIMIT_BIG1,
658 IntegerDef::PARAM_MAX2 => ApiBase::LIMIT_BIG2
659 ],
660 'start' => [
661 ParamValidator::PARAM_TYPE => 'timestamp'
662 ],
663 'end' => [
664 ParamValidator::PARAM_TYPE => 'timestamp'
665 ],
666 'continue' => [
667 ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
668 ],
669 'user' => [
670 ParamValidator::PARAM_TYPE => 'user',
671 UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'ip', 'temp', 'interwiki' ],
672 ParamValidator::PARAM_ISMULTI => true
673 ],
674 'userids' => [
675 ParamValidator::PARAM_TYPE => 'integer',
676 ParamValidator::PARAM_ISMULTI => true
677 ],
678 'userprefix' => null,
679 'iprange' => null,
680 'dir' => [
681 ParamValidator::PARAM_DEFAULT => 'older',
682 ParamValidator::PARAM_TYPE => [
683 'newer',
684 'older'
685 ],
686 ApiBase::PARAM_HELP_MSG => 'api-help-param-direction',
688 'newer' => 'api-help-paramvalue-direction-newer',
689 'older' => 'api-help-paramvalue-direction-older',
690 ],
691 ],
692 'namespace' => [
693 ParamValidator::PARAM_ISMULTI => true,
694 ParamValidator::PARAM_TYPE => 'namespace'
695 ],
696 'prop' => [
697 ParamValidator::PARAM_ISMULTI => true,
698 ParamValidator::PARAM_DEFAULT => 'ids|title|timestamp|comment|size|flags',
699 ParamValidator::PARAM_TYPE => [
700 'ids',
701 'title',
702 'timestamp',
703 'comment',
704 'parsedcomment',
705 'size',
706 'sizediff',
707 'flags',
708 'patrolled',
709 'tags'
710 ],
712 ],
713 'show' => [
714 ParamValidator::PARAM_ISMULTI => true,
715 ParamValidator::PARAM_TYPE => [
716 'minor',
717 '!minor',
718 'patrolled',
719 '!patrolled',
720 'autopatrolled',
721 '!autopatrolled',
722 'top',
723 '!top',
724 'new',
725 '!new',
726 ],
728 'apihelp-query+usercontribs-param-show',
729 $this->getConfig()->get( MainConfigNames::RCMaxAge )
730 ],
731 ],
732 'tag' => null,
733 'toponly' => [
734 ParamValidator::PARAM_DEFAULT => false,
735 ParamValidator::PARAM_DEPRECATED => true,
736 ],
737 ];
738 }
739
741 protected function getExamplesMessages() {
742 return [
743 'action=query&list=usercontribs&ucuser=Example'
744 => 'apihelp-query+usercontribs-example-user',
745 'action=query&list=usercontribs&ucuserprefix=192.0.2.'
746 => 'apihelp-query+usercontribs-example-ipprefix',
747 ];
748 }
749
751 public function getHelpUrls() {
752 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Usercontribs';
753 }
754}
755
757class_alias( ApiQueryUserContribs::class, 'ApiQueryUserContribs' );
wfEscapeWikiText( $input)
Escapes the given text so that it may be output using addWikiText() without any linking,...
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:68
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
Definition ApiBase.php:1511
getModuleName()
Get the name of the module being executed by this instance.
Definition ApiBase.php:543
dieContinueUsageIf( $condition)
Die with the 'badcontinue' error.
Definition ApiBase.php:1735
parseContinueParamOrDie(string $continue, array $types)
Parse the 'continue' parameter in the usual format and validate the types of each part,...
Definition ApiBase.php:1696
getResult()
Get the result object.
Definition ApiBase.php:682
const PARAM_HELP_MSG_PER_VALUE
((string|array|Message)[]) When PARAM_TYPE is an array, or 'string' with PARAM_ISMULTI,...
Definition ApiBase.php:207
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition ApiBase.php:167
const LIMIT_BIG2
Fast query, apihighlimits limit.
Definition ApiBase.php:234
encodeParamName( $paramName)
This method mangles parameter name based on the prefix supplied to the constructor.
Definition ApiBase.php:801
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:823
requireOnlyOneParameter( $params,... $required)
Die if 0 or more than one of a certain set of parameters is set and not false.
Definition ApiBase.php:961
const LIMIT_BIG1
Fast query, standard limit.
Definition ApiBase.php:232
This is a base class for all Query modules.
addOption( $name, $value=null)
Add an option such as LIMIT or USE INDEX.
addFieldsIf( $value, $condition)
Same as addFields(), but add the fields only if a condition is met.
static addTitleInfo(&$arr, $title, $prefix='')
Add information (title and namespace) about a Title object to a result array.
addWhereIf( $value, $condition)
Same as addWhere(), but add the WHERE clauses only if a condition is met.
addTables( $tables, $alias=null)
Add a set of tables to the internal array.
addJoinConds( $join_conds)
Add a set of JOIN conditions to the internal array.
getDB()
Get the Query database connection (read-only).
select( $method, $extraQuery=[], ?array &$hookData=null)
Execute a SELECT query based on the values in the internal arrays.
addWhere( $value)
Add a set of WHERE clauses to the internal array.
executeGenderCacheFromResultWrapper(IResultWrapper $res, $fname=__METHOD__, $fieldPrefix='page')
Preprocess the result set to fill the GenderCache with the necessary information before using self::a...
addTimestampWhereRange( $field, $dir, $start, $end, $sort=true)
Add a WHERE clause corresponding to a range, similar to addWhereRange, but converts $start and $end t...
getQueryBuilder()
Get the SelectQueryBuilder.
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.
addWhereFld( $field, $value)
Equivalent to addWhere( [ $field => $value ] )
addFields( $value)
Add a set of fields to select to the internal array.
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.
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...
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
getHelpUrls()
Return links to more detailed help pages about the module.1.25, returning boolean false is deprecated...
getExamplesMessages()
Returns usage examples for this module.Return value has query strings as keys, with values being eith...
getCacheMode( $params)
Get the cache mode for the data generated by this module.Override this in the module subclass....
__construct(ApiQuery $query, string $moduleName, CommentStore $commentStore, UserIdentityLookup $userIdentityLookup, UserNameUtils $userNameUtils, RevisionStore $revisionStore, NameTableStore $changeTagDefStore, ChangeTagsStore $changeTagsStore, ActorMigration $actorMigration, CommentFormatter $commentFormatter)
This is the main query class.
Definition ApiQuery.php:36
static setIndexedTagName(array &$arr, $tag)
Set the tag name for numeric-keyed values in XML format.
Read-write access to the change_tags table.
This is the main service interface for converting single-line comments from various DB comment fields...
Handle database storage of comments such as edit summaries and log reasons.
makeTitle( $linkId)
Convert a link ID to a Title.to override Title
A class containing constants representing the names of configuration variables.
const RCMaxAge
Name constant for the RCMaxAge setting, for use with Config::get()
const MaxExecutionTimeForExpensiveQueries
Name constant for the MaxExecutionTimeForExpensiveQueries setting, for use with Config::get()
const RangeContributionsCIDRLimit
Name constant for the RangeContributionsCIDRLimit setting, for use with Config::get()
Type definition for user types.
Definition UserDef.php:27
Utility class for creating and reading rows in the recentchanges table.
Page revision base class.
Service for looking up page revisions.
Exception representing a failure to look up a row from a name table.
Represents a title within MediaWiki.
Definition Title.php:69
This is not intended to be a long-term part of MediaWiki; it will be deprecated and removed once acto...
Class to parse and build external user names.
Value object representing a user's identity.
UserNameUtils service.
Service for formatting and validating API parameters.
Type definition for integer types.
Build SELECT queries with a fluent interface.
Service for looking up UserIdentity.
Interface for objects representing user identity.