MediaWiki  master
ApiQueryUserContribs.php
Go to the documentation of this file.
1 <?php
26 
33 
34  public function __construct( ApiQuery $query, $moduleName ) {
35  parent::__construct( $query, $moduleName, 'uc' );
36  }
37 
39 
40  private $fld_ids = false, $fld_title = false, $fld_timestamp = false,
41  $fld_comment = false, $fld_parsedcomment = false, $fld_flags = false,
42  $fld_patrolled = false, $fld_tags = false, $fld_size = false, $fld_sizediff = false;
43 
44  public function execute() {
45  // Parse some parameters
46  $this->params = $this->extractRequestParams();
47 
48  $this->commentStore = CommentStore::getStore();
49 
50  $prop = array_flip( $this->params['prop'] );
51  $this->fld_ids = isset( $prop['ids'] );
52  $this->fld_title = isset( $prop['title'] );
53  $this->fld_comment = isset( $prop['comment'] );
54  $this->fld_parsedcomment = isset( $prop['parsedcomment'] );
55  $this->fld_size = isset( $prop['size'] );
56  $this->fld_sizediff = isset( $prop['sizediff'] );
57  $this->fld_flags = isset( $prop['flags'] );
58  $this->fld_timestamp = isset( $prop['timestamp'] );
59  $this->fld_patrolled = isset( $prop['patrolled'] );
60  $this->fld_tags = isset( $prop['tags'] );
61 
62  // The main query may use the 'contributions' group DB, which can map to replica DBs
63  // with extra user based indexes or partioning by user. The additional metadata
64  // queries should use a regular replica DB since the lookup pattern is not all by user.
65  $dbSecondary = $this->getDB(); // any random replica DB
66 
67  $sort = ( $this->params['dir'] == 'newer' ? '' : ' DESC' );
68  $op = ( $this->params['dir'] == 'older' ? '<' : '>' );
69 
70  // Create an Iterator that produces the UserIdentity objects we need, depending
71  // on which of the 'userprefix', 'userids', or 'user' params was
72  // specified.
73  $this->requireOnlyOneParameter( $this->params, 'userprefix', 'userids', 'user' );
74  if ( isset( $this->params['userprefix'] ) ) {
75  $this->multiUserMode = true;
76  $this->orderBy = 'name';
77  $fname = __METHOD__;
78 
79  // Because 'userprefix' might produce a huge number of users (e.g.
80  // a wiki with users "Test00000001" to "Test99999999"), use a
81  // generator with batched lookup and continuation.
82  $userIter = call_user_func( function () use ( $dbSecondary, $sort, $op, $fname ) {
83  $fromName = false;
84  if ( !is_null( $this->params['continue'] ) ) {
85  $continue = explode( '|', $this->params['continue'] );
86  $this->dieContinueUsageIf( count( $continue ) != 4 );
87  $this->dieContinueUsageIf( $continue[0] !== 'name' );
88  $fromName = $continue[1];
89  }
90  $like = $dbSecondary->buildLike( $this->params['userprefix'], $dbSecondary->anyString() );
91 
92  $limit = 501;
93 
94  do {
95  $from = $fromName ? "$op= " . $dbSecondary->addQuotes( $fromName ) : false;
96  $res = $dbSecondary->select(
97  'actor',
98  [ 'actor_id', 'user_id' => 'COALESCE(actor_user,0)', 'user_name' => 'actor_name' ],
99  array_merge( [ "actor_name$like" ], $from ? [ "actor_name $from" ] : [] ),
100  $fname,
101  [ 'ORDER BY' => [ "user_name $sort" ], 'LIMIT' => $limit ]
102  );
103 
104  $count = 0;
105  $fromName = false;
106  foreach ( $res as $row ) {
107  if ( ++$count >= $limit ) {
108  $fromName = $row->user_name;
109  break;
110  }
111  yield User::newFromRow( $row );
112  }
113  } while ( $fromName !== false );
114  } );
115  // Do the actual sorting client-side, because otherwise
116  // prepareQuery might try to sort by actor and confuse everything.
117  $batchSize = 1;
118  } elseif ( isset( $this->params['userids'] ) ) {
119  if ( $this->params['userids'] === [] ) {
120  $encParamName = $this->encodeParamName( 'userids' );
121  $this->dieWithError( [ 'apierror-paramempty', $encParamName ], "paramempty_$encParamName" );
122  }
123 
124  $ids = [];
125  foreach ( $this->params['userids'] as $uid ) {
126  if ( $uid <= 0 ) {
127  $this->dieWithError( [ 'apierror-invaliduserid', $uid ], 'invaliduserid' );
128  }
129  $ids[] = $uid;
130  }
131 
132  $this->orderBy = 'id';
133  $this->multiUserMode = count( $ids ) > 1;
134 
135  $from = $fromId = false;
136  if ( $this->multiUserMode && !is_null( $this->params['continue'] ) ) {
137  $continue = explode( '|', $this->params['continue'] );
138  $this->dieContinueUsageIf( count( $continue ) != 4 );
139  $this->dieContinueUsageIf( $continue[0] !== 'id' && $continue[0] !== 'actor' );
140  $fromId = (int)$continue[1];
141  $this->dieContinueUsageIf( $continue[1] !== (string)$fromId );
142  $from = "$op= $fromId";
143  }
144 
145  $res = $dbSecondary->select(
146  'actor',
147  [ 'actor_id', 'user_id' => 'actor_user', 'user_name' => 'actor_name' ],
148  array_merge( [ 'actor_user' => $ids ], $from ? [ "actor_id $from" ] : [] ),
149  __METHOD__,
150  [ 'ORDER BY' => "user_id $sort" ]
151  );
152  $userIter = UserArray::newFromResult( $res );
153  $batchSize = count( $ids );
154  } else {
155  $names = [];
156  if ( !count( $this->params['user'] ) ) {
157  $encParamName = $this->encodeParamName( 'user' );
158  $this->dieWithError(
159  [ 'apierror-paramempty', $encParamName ], "paramempty_$encParamName"
160  );
161  }
162  foreach ( $this->params['user'] as $u ) {
163  if ( $u === '' ) {
164  $encParamName = $this->encodeParamName( 'user' );
165  $this->dieWithError(
166  [ 'apierror-paramempty', $encParamName ], "paramempty_$encParamName"
167  );
168  }
169 
170  if ( User::isIP( $u ) || ExternalUserNames::isExternal( $u ) ) {
171  $names[$u] = null;
172  } else {
173  $name = User::getCanonicalName( $u, 'valid' );
174  if ( $name === false ) {
175  $encParamName = $this->encodeParamName( 'user' );
176  $this->dieWithError(
177  [ 'apierror-baduser', $encParamName, wfEscapeWikiText( $u ) ], "baduser_$encParamName"
178  );
179  }
180  $names[$name] = null;
181  }
182  }
183 
184  $this->orderBy = 'name';
185  $this->multiUserMode = count( $names ) > 1;
186 
187  $from = $fromName = false;
188  if ( $this->multiUserMode && !is_null( $this->params['continue'] ) ) {
189  $continue = explode( '|', $this->params['continue'] );
190  $this->dieContinueUsageIf( count( $continue ) != 4 );
191  $this->dieContinueUsageIf( $continue[0] !== 'name' && $continue[0] !== 'actor' );
192  $fromName = $continue[1];
193  $from = "$op= " . $dbSecondary->addQuotes( $fromName );
194  }
195 
196  $res = $dbSecondary->select(
197  'actor',
198  [ 'actor_id', 'user_id' => 'actor_user', 'user_name' => 'actor_name' ],
199  array_merge( [ 'actor_name' => array_keys( $names ) ], $from ? [ "actor_id $from" ] : [] ),
200  __METHOD__,
201  [ 'ORDER BY' => "actor_name $sort" ]
202  );
203  $userIter = UserArray::newFromResult( $res );
204  $batchSize = count( $names );
205  }
206 
207  // The DB query will order by actor so update $this->orderBy to match.
208  if ( $batchSize > 1 ) {
209  $this->orderBy = 'actor';
210  }
211 
212  $count = 0;
213  $limit = $this->params['limit'];
214  $userIter->rewind();
215  while ( $userIter->valid() ) {
216  $users = [];
217  while ( count( $users ) < $batchSize && $userIter->valid() ) {
218  $users[] = $userIter->current();
219  $userIter->next();
220  }
221 
222  $hookData = [];
223  $this->prepareQuery( $users, $limit - $count );
224  $res = $this->select( __METHOD__, [], $hookData );
225 
226  if ( $this->fld_sizediff ) {
227  $revIds = [];
228  foreach ( $res as $row ) {
229  if ( $row->rev_parent_id ) {
230  $revIds[] = $row->rev_parent_id;
231  }
232  }
233  $this->parentLens = MediaWikiServices::getInstance()->getRevisionStore()
234  ->listRevisionSizes( $dbSecondary, $revIds );
235  }
236 
237  foreach ( $res as $row ) {
238  if ( ++$count > $limit ) {
239  // We've reached the one extra which shows that there are
240  // additional pages to be had. Stop here...
241  $this->setContinueEnumParameter( 'continue', $this->continueStr( $row ) );
242  break 2;
243  }
244 
245  $vals = $this->extractRowInfo( $row );
246  $fit = $this->processRow( $row, $vals, $hookData ) &&
247  $this->getResult()->addValue( [ 'query', $this->getModuleName() ], null, $vals );
248  if ( !$fit ) {
249  $this->setContinueEnumParameter( 'continue', $this->continueStr( $row ) );
250  break 2;
251  }
252  }
253  }
254 
255  $this->getResult()->addIndexedTagName( [ 'query', $this->getModuleName() ], 'item' );
256  }
257 
263  private function prepareQuery( array $users, $limit ) {
264  $this->resetQueryParams();
265  $db = $this->getDB();
266 
267  $revQuery = MediaWikiServices::getInstance()->getRevisionStore()->getQueryInfo( [ 'page' ] );
268 
269  $revWhere = ActorMigration::newMigration()->getWhere( $db, 'rev_user', $users );
270  $orderUserField = 'rev_actor';
271  $userField = $this->orderBy === 'actor' ? 'revactor_actor' : 'actor_name';
272  $tsField = 'revactor_timestamp';
273  $idField = 'revactor_rev';
274 
275  // T221511: MySQL/MariaDB (10.1.37) can sometimes irrationally decide that querying `actor`
276  // before `revision_actor_temp` and filesorting is somehow better than querying $limit+1 rows
277  // from `revision_actor_temp`. Tell it not to reorder the query (and also reorder it ourselves
278  // because as generated by RevisionStore it'll have `revision` first rather than
279  // `revision_actor_temp`). But not when uctag is used, as it seems as likely to be harmed as
280  // helped in that case, and not when there's only one User because in that case it fetches
281  // the one `actor` row as a constant and doesn't filesort.
282  if ( count( $users ) > 1 && !isset( $this->params['tag'] ) ) {
283  $revQuery['joins']['revision'] = $revQuery['joins']['temp_rev_user'];
284  unset( $revQuery['joins']['temp_rev_user'] );
285  $this->addOption( 'STRAIGHT_JOIN' );
286  // It isn't actually necesssary to reorder $revQuery['tables'] as Database does the right thing
287  // when join conditions are given for all joins, but GergÅ‘ is wary of relying on that so pull
288  // `revision_actor_temp` to the start.
289  $revQuery['tables'] =
290  [ 'temp_rev_user' => $revQuery['tables']['temp_rev_user'] ] + $revQuery['tables'];
291  }
292 
293  $this->addTables( $revQuery['tables'] );
294  $this->addJoinConds( $revQuery['joins'] );
295  $this->addFields( $revQuery['fields'] );
296  $this->addWhere( $revWhere['conds'] );
297 
298  // Handle continue parameter
299  if ( !is_null( $this->params['continue'] ) ) {
300  $continue = explode( '|', $this->params['continue'] );
301  if ( $this->multiUserMode ) {
302  $this->dieContinueUsageIf( count( $continue ) != 4 );
303  $modeFlag = array_shift( $continue );
304  $this->dieContinueUsageIf( $modeFlag !== $this->orderBy );
305  $encUser = $db->addQuotes( array_shift( $continue ) );
306  } else {
307  $this->dieContinueUsageIf( count( $continue ) != 2 );
308  }
309  $encTS = $db->addQuotes( $db->timestamp( $continue[0] ) );
310  $encId = (int)$continue[1];
311  $this->dieContinueUsageIf( $encId != $continue[1] );
312  $op = ( $this->params['dir'] == 'older' ? '<' : '>' );
313  if ( $this->multiUserMode ) {
314  $this->addWhere(
315  "$userField $op $encUser OR " .
316  "($userField = $encUser AND " .
317  "($tsField $op $encTS OR " .
318  "($tsField = $encTS AND " .
319  "$idField $op= $encId)))"
320  );
321  } else {
322  $this->addWhere(
323  "$tsField $op $encTS OR " .
324  "($tsField = $encTS AND " .
325  "$idField $op= $encId)"
326  );
327  }
328  }
329 
330  // Don't include any revisions where we're not supposed to be able to
331  // see the username.
332  $user = $this->getUser();
333  if ( !$this->getPermissionManager()->userHasRight( $user, 'deletedhistory' ) ) {
334  $bitmask = RevisionRecord::DELETED_USER;
335  } elseif ( !$this->getPermissionManager()
336  ->userHasAnyRight( $user, 'suppressrevision', 'viewsuppressed' )
337  ) {
338  $bitmask = RevisionRecord::DELETED_USER | RevisionRecord::DELETED_RESTRICTED;
339  } else {
340  $bitmask = 0;
341  }
342  if ( $bitmask ) {
343  $this->addWhere( $db->bitAnd( 'rev_deleted', $bitmask ) . " != $bitmask" );
344  }
345 
346  // Add the user field to ORDER BY if there are multiple users
347  if ( count( $users ) > 1 ) {
348  $this->addWhereRange( $orderUserField, $this->params['dir'], null, null );
349  }
350 
351  // Then timestamp
352  $this->addTimestampWhereRange( $tsField,
353  $this->params['dir'], $this->params['start'], $this->params['end'] );
354 
355  // Then rev_id for a total ordering
356  $this->addWhereRange( $idField, $this->params['dir'], null, null );
357 
358  $this->addWhereFld( 'page_namespace', $this->params['namespace'] );
359 
360  $show = $this->params['show'];
361  if ( $this->params['toponly'] ) { // deprecated/old param
362  $show[] = 'top';
363  }
364  if ( !is_null( $show ) ) {
365  $show = array_flip( $show );
366 
367  if ( ( isset( $show['minor'] ) && isset( $show['!minor'] ) )
368  || ( isset( $show['patrolled'] ) && isset( $show['!patrolled'] ) )
369  || ( isset( $show['autopatrolled'] ) && isset( $show['!autopatrolled'] ) )
370  || ( isset( $show['autopatrolled'] ) && isset( $show['!patrolled'] ) )
371  || ( isset( $show['top'] ) && isset( $show['!top'] ) )
372  || ( isset( $show['new'] ) && isset( $show['!new'] ) )
373  ) {
374  $this->dieWithError( 'apierror-show' );
375  }
376 
377  $this->addWhereIf( 'rev_minor_edit = 0', isset( $show['!minor'] ) );
378  $this->addWhereIf( 'rev_minor_edit != 0', isset( $show['minor'] ) );
379  $this->addWhereIf(
380  'rc_patrolled = ' . RecentChange::PRC_UNPATROLLED,
381  isset( $show['!patrolled'] )
382  );
383  $this->addWhereIf(
384  'rc_patrolled != ' . RecentChange::PRC_UNPATROLLED,
385  isset( $show['patrolled'] )
386  );
387  $this->addWhereIf(
388  'rc_patrolled != ' . RecentChange::PRC_AUTOPATROLLED,
389  isset( $show['!autopatrolled'] )
390  );
391  $this->addWhereIf(
392  'rc_patrolled = ' . RecentChange::PRC_AUTOPATROLLED,
393  isset( $show['autopatrolled'] )
394  );
395  $this->addWhereIf( $idField . ' != page_latest', isset( $show['!top'] ) );
396  $this->addWhereIf( $idField . ' = page_latest', isset( $show['top'] ) );
397  $this->addWhereIf( 'rev_parent_id != 0', isset( $show['!new'] ) );
398  $this->addWhereIf( 'rev_parent_id = 0', isset( $show['new'] ) );
399  }
400  $this->addOption( 'LIMIT', $limit + 1 );
401 
402  if ( isset( $show['patrolled'] ) || isset( $show['!patrolled'] ) ||
403  isset( $show['autopatrolled'] ) || isset( $show['!autopatrolled'] ) || $this->fld_patrolled
404  ) {
405  if ( !$user->useRCPatrol() && !$user->useNPPatrol() ) {
406  $this->dieWithError( 'apierror-permissiondenied-patrolflag', 'permissiondenied' );
407  }
408 
409  $isFilterset = isset( $show['patrolled'] ) || isset( $show['!patrolled'] ) ||
410  isset( $show['autopatrolled'] ) || isset( $show['!autopatrolled'] );
411  $this->addTables( 'recentchanges' );
412  $this->addJoinConds( [ 'recentchanges' => [
413  $isFilterset ? 'JOIN' : 'LEFT JOIN',
414  [
415  // This is a crazy hack. recentchanges has no index on rc_this_oldid, so instead of adding
416  // one T19237 did a join using rc_user_text and rc_timestamp instead. Now rc_user_text is
417  // probably unavailable, so just do rc_timestamp.
418  'rc_timestamp = ' . $tsField,
419  'rc_this_oldid = ' . $idField,
420  ]
421  ] ] );
422  }
423 
424  $this->addFieldsIf( 'rc_patrolled', $this->fld_patrolled );
425 
426  if ( $this->fld_tags ) {
427  $this->addFields( [ 'ts_tags' => ChangeTags::makeTagSummarySubquery( 'revision' ) ] );
428  }
429 
430  if ( isset( $this->params['tag'] ) ) {
431  $this->addTables( 'change_tag' );
432  $this->addJoinConds(
433  [ 'change_tag' => [ 'JOIN', [ $idField . ' = ct_rev_id' ] ] ]
434  );
435  $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
436  try {
437  $this->addWhereFld( 'ct_tag_id', $changeTagDefStore->getId( $this->params['tag'] ) );
438  } catch ( NameTableAccessException $exception ) {
439  // Return nothing.
440  $this->addWhere( '1=0' );
441  }
442  }
443  }
444 
451  private function extractRowInfo( $row ) {
452  $vals = [];
453  $anyHidden = false;
454 
455  if ( $row->rev_deleted & RevisionRecord::DELETED_TEXT ) {
456  $vals['texthidden'] = true;
457  $anyHidden = true;
458  }
459 
460  // Any rows where we can't view the user were filtered out in the query.
461  $vals['userid'] = (int)$row->rev_user;
462  $vals['user'] = $row->rev_user_text;
463  if ( $row->rev_deleted & RevisionRecord::DELETED_USER ) {
464  $vals['userhidden'] = true;
465  $anyHidden = true;
466  }
467  if ( $this->fld_ids ) {
468  $vals['pageid'] = (int)$row->rev_page;
469  $vals['revid'] = (int)$row->rev_id;
470 
471  if ( !is_null( $row->rev_parent_id ) ) {
472  $vals['parentid'] = (int)$row->rev_parent_id;
473  }
474  }
475 
476  $title = Title::makeTitle( $row->page_namespace, $row->page_title );
477 
478  if ( $this->fld_title ) {
480  }
481 
482  if ( $this->fld_timestamp ) {
483  $vals['timestamp'] = wfTimestamp( TS_ISO_8601, $row->rev_timestamp );
484  }
485 
486  if ( $this->fld_flags ) {
487  $vals['new'] = $row->rev_parent_id == 0 && !is_null( $row->rev_parent_id );
488  $vals['minor'] = (bool)$row->rev_minor_edit;
489  $vals['top'] = $row->page_latest == $row->rev_id;
490  }
491 
492  if ( $this->fld_comment || $this->fld_parsedcomment ) {
493  if ( $row->rev_deleted & RevisionRecord::DELETED_COMMENT ) {
494  $vals['commenthidden'] = true;
495  $anyHidden = true;
496  }
497 
498  $userCanView = RevisionRecord::userCanBitfield(
499  $row->rev_deleted,
500  RevisionRecord::DELETED_COMMENT, $this->getUser()
501  );
502 
503  if ( $userCanView ) {
504  $comment = $this->commentStore->getComment( 'rev_comment', $row )->text;
505  if ( $this->fld_comment ) {
506  $vals['comment'] = $comment;
507  }
508 
509  if ( $this->fld_parsedcomment ) {
510  $vals['parsedcomment'] = Linker::formatComment( $comment, $title );
511  }
512  }
513  }
514 
515  if ( $this->fld_patrolled ) {
516  $vals['patrolled'] = $row->rc_patrolled != RecentChange::PRC_UNPATROLLED;
517  $vals['autopatrolled'] = $row->rc_patrolled == RecentChange::PRC_AUTOPATROLLED;
518  }
519 
520  if ( $this->fld_size && !is_null( $row->rev_len ) ) {
521  $vals['size'] = (int)$row->rev_len;
522  }
523 
524  if ( $this->fld_sizediff
525  && !is_null( $row->rev_len )
526  && !is_null( $row->rev_parent_id )
527  ) {
528  $parentLen = $this->parentLens[$row->rev_parent_id] ?? 0;
529  $vals['sizediff'] = (int)$row->rev_len - $parentLen;
530  }
531 
532  if ( $this->fld_tags ) {
533  if ( $row->ts_tags ) {
534  $tags = explode( ',', $row->ts_tags );
535  ApiResult::setIndexedTagName( $tags, 'tag' );
536  $vals['tags'] = $tags;
537  } else {
538  $vals['tags'] = [];
539  }
540  }
541 
542  if ( $anyHidden && ( $row->rev_deleted & RevisionRecord::DELETED_RESTRICTED ) ) {
543  $vals['suppressed'] = true;
544  }
545 
546  return $vals;
547  }
548 
549  private function continueStr( $row ) {
550  if ( $this->multiUserMode ) {
551  switch ( $this->orderBy ) {
552  case 'id':
553  return "id|$row->rev_user|$row->rev_timestamp|$row->rev_id";
554  case 'name':
555  return "name|$row->rev_user_text|$row->rev_timestamp|$row->rev_id";
556  case 'actor':
557  return "actor|$row->rev_actor|$row->rev_timestamp|$row->rev_id";
558  }
559  } else {
560  return "$row->rev_timestamp|$row->rev_id";
561  }
562  }
563 
564  public function getCacheMode( $params ) {
565  // This module provides access to deleted revisions and patrol flags if
566  // the requester is logged in
567  return 'anon-public-user-private';
568  }
569 
570  public function getAllowedParams() {
571  return [
572  'limit' => [
573  ApiBase::PARAM_DFLT => 10,
574  ApiBase::PARAM_TYPE => 'limit',
575  ApiBase::PARAM_MIN => 1,
578  ],
579  'start' => [
580  ApiBase::PARAM_TYPE => 'timestamp'
581  ],
582  'end' => [
583  ApiBase::PARAM_TYPE => 'timestamp'
584  ],
585  'continue' => [
586  ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
587  ],
588  'user' => [
589  ApiBase::PARAM_TYPE => 'user',
591  ],
592  'userids' => [
593  ApiBase::PARAM_TYPE => 'integer',
595  ],
596  'userprefix' => null,
597  'dir' => [
598  ApiBase::PARAM_DFLT => 'older',
600  'newer',
601  'older'
602  ],
603  ApiBase::PARAM_HELP_MSG => 'api-help-param-direction',
604  ],
605  'namespace' => [
606  ApiBase::PARAM_ISMULTI => true,
607  ApiBase::PARAM_TYPE => 'namespace'
608  ],
609  'prop' => [
610  ApiBase::PARAM_ISMULTI => true,
611  ApiBase::PARAM_DFLT => 'ids|title|timestamp|comment|size|flags',
613  'ids',
614  'title',
615  'timestamp',
616  'comment',
617  'parsedcomment',
618  'size',
619  'sizediff',
620  'flags',
621  'patrolled',
622  'tags'
623  ],
625  ],
626  'show' => [
627  ApiBase::PARAM_ISMULTI => true,
629  'minor',
630  '!minor',
631  'patrolled',
632  '!patrolled',
633  'autopatrolled',
634  '!autopatrolled',
635  'top',
636  '!top',
637  'new',
638  '!new',
639  ],
641  'apihelp-query+usercontribs-param-show',
642  $this->getConfig()->get( 'RCMaxAge' )
643  ],
644  ],
645  'tag' => null,
646  'toponly' => [
647  ApiBase::PARAM_DFLT => false,
649  ],
650  ];
651  }
652 
653  protected function getExamplesMessages() {
654  return [
655  'action=query&list=usercontribs&ucuser=Example'
656  => 'apihelp-query+usercontribs-example-user',
657  'action=query&list=usercontribs&ucuserprefix=192.0.2.'
658  => 'apihelp-query+usercontribs-example-ipprefix',
659  ];
660  }
661 
662  public function getHelpUrls() {
663  return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Usercontribs';
664  }
665 }
666 
671 class_alias( ApiQueryUserContribs::class, 'ApiQueryContributions' );
select( $method, $extraQuery=[], array &$hookData=null)
Execute a SELECT query based on the values in the internal arrays.
static newFromResult( $res)
Definition: UserArray.php:30
const PARAM_TYPE
(string|string[]) Either an array of allowed value strings, or a string type as described below...
Definition: ApiBase.php:94
requireOnlyOneParameter( $params, $required)
Die if none or more than one of a certain set of parameters is set and not false. ...
Definition: ApiBase.php:893
getDB()
Get the Query database connection (read-only)
const LIMIT_BIG2
Fast query, apihighlimits limit.
Definition: ApiBase.php:261
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking, formatting, etc.
static addTitleInfo(&$arr, $title, $prefix='')
Add information (title and namespace) about a Title object to a result array.
getResult()
Get the result object.
Definition: ApiBase.php:640
addJoinConds( $join_conds)
Add a set of JOIN conditions to the internal array.
processRow( $row, array &$data, array &$hookData)
Call the ApiQueryBaseProcessRow hook.
static isIP( $name)
Does the string match an anonymous IP address?
Definition: User.php:891
const PARAM_DFLT
(null|boolean|integer|string) Default value of the parameter.
Definition: ApiBase.php:55
const LIMIT_BIG1
Fast query, standard limit.
Definition: ApiBase.php:259
Exception representing a failure to look up a row from a name table.
const PARAM_MAX
(integer) Max value allowed for the parameter, for PARAM_TYPE &#39;integer&#39; and &#39;limit&#39;.
Definition: ApiBase.php:97
if(ini_get( 'mbstring.func_overload')) if(!defined('MW_ENTRY_POINT'))
Pre-config setup: Before loading LocalSettings.php.
Definition: Setup.php:57
This is a base class for all Query modules.
$sort
dieWithError( $msg, $code=null, $data=null, $httpCode=null)
Abort execution with an error.
Definition: ApiBase.php:2014
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user...
Definition: ApiBase.php:761
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:164
static setIndexedTagName(array &$arr, $tag)
Set the tag name for numeric-keyed values in XML format.
Definition: ApiResult.php:616
extractRowInfo( $row)
Extract fields from the database row and append them to a result array.
static getCanonicalName( $name, $validate='valid')
Given unvalidated user input, return a canonical username, or false if the username is invalid...
Definition: User.php:1139
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
static newMigration()
Static constructor.
addTables( $tables, $alias=null)
Add a set of tables to the internal array.
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:1165
addWhereIf( $value, $condition)
Same as addWhere(), but add the WHERE clauses only if a condition is met.
dieContinueUsageIf( $condition)
Die with the &#39;badcontinue&#39; error.
Definition: ApiBase.php:2208
__construct(ApiQuery $query, $moduleName)
getModuleName()
Get the name of the module being executed by this instance.
Definition: ApiBase.php:520
addFields( $value)
Add a set of fields to select to the internal array.
const PARAM_MAX2
(integer) Max value allowed for the parameter for users with the apihighlimits right, for PARAM_TYPE &#39;limit&#39;.
Definition: ApiBase.php:103
This is the main query class.
Definition: ApiQuery.php:37
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter...
Definition: ApiBase.php:131
const PRC_UNPATROLLED
addTimestampWhereRange( $field, $dir, $start, $end, $sort=true)
Add a WHERE clause corresponding to a range, similar to addWhereRange, but converts $start and $end t...
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:586
encodeParamName( $paramName)
This method mangles parameter name based on the prefix supplied to the constructor.
Definition: ApiBase.php:739
static getStore()
static newFromRow( $row, $data=null)
Create a new user object from a user row.
Definition: User.php:696
addWhere( $value)
Add a set of WHERE clauses to the internal array.
$revQuery
const PARAM_ISMULTI
(boolean) Accept multiple pipe-separated values for this parameter (e.g.
Definition: ApiBase.php:58
getPermissionManager()
Obtain a PermissionManager instance that subclasses may use in their authorization checks...
Definition: ApiBase.php:710
addWhereFld( $field, $value)
Equivalent to addWhere( [ $field => $value ] )
This query action adds a list of a specified user&#39;s contributions to the output.
addFieldsIf( $value, $condition)
Same as addFields(), but add the fields only if a condition is met.
addOption( $name, $value=null)
Add an option such as LIMIT or USE INDEX.
const PARAM_DEPRECATED
(boolean) Is the parameter deprecated (will show a warning)?
Definition: ApiBase.php:112
const PARAM_MIN
(integer) Lowest value allowed for the parameter, for PARAM_TYPE &#39;integer&#39; and &#39;limit&#39;.
Definition: ApiBase.php:106
static makeTagSummarySubquery( $tables)
Make the tag summary subquery based on the given tables and return it.
Definition: ChangeTags.php:837
const PRC_AUTOPATROLLED
prepareQuery(array $users, $limit)
Prepares the query and returns the limit of rows requested.
return true
Definition: router.php:82
resetQueryParams()
Blank the internal arrays with query parameters.
static isExternal( $username)
Tells whether the username is external or not.
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...
setContinueEnumParameter( $paramName, $paramValue)
Set a query-continue value.