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