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