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