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_title ) {
227  $this->executeGenderCacheFromResultWrapper( $res, __METHOD__ );
228  }
229 
230  if ( $this->fld_sizediff ) {
231  $revIds = [];
232  foreach ( $res as $row ) {
233  if ( $row->rev_parent_id ) {
234  $revIds[] = $row->rev_parent_id;
235  }
236  }
237  $this->parentLens = MediaWikiServices::getInstance()->getRevisionStore()
238  ->listRevisionSizes( $dbSecondary, $revIds );
239  }
240 
241  foreach ( $res as $row ) {
242  if ( ++$count > $limit ) {
243  // We've reached the one extra which shows that there are
244  // additional pages to be had. Stop here...
245  $this->setContinueEnumParameter( 'continue', $this->continueStr( $row ) );
246  break 2;
247  }
248 
249  $vals = $this->extractRowInfo( $row );
250  $fit = $this->processRow( $row, $vals, $hookData ) &&
251  $this->getResult()->addValue( [ 'query', $this->getModuleName() ], null, $vals );
252  if ( !$fit ) {
253  $this->setContinueEnumParameter( 'continue', $this->continueStr( $row ) );
254  break 2;
255  }
256  }
257  }
258 
259  $this->getResult()->addIndexedTagName( [ 'query', $this->getModuleName() ], 'item' );
260  }
261 
267  private function prepareQuery( array $users, $limit ) {
268  $this->resetQueryParams();
269  $db = $this->getDB();
270 
271  $revQuery = MediaWikiServices::getInstance()->getRevisionStore()->getQueryInfo( [ 'page' ] );
272 
273  $revWhere = ActorMigration::newMigration()->getWhere( $db, 'rev_user', $users );
274  $orderUserField = 'rev_actor';
275  $userField = $this->orderBy === 'actor' ? 'revactor_actor' : 'actor_name';
276  $tsField = 'revactor_timestamp';
277  $idField = 'revactor_rev';
278 
279  // T221511: MySQL/MariaDB (10.1.37) can sometimes irrationally decide that querying `actor`
280  // before `revision_actor_temp` and filesorting is somehow better than querying $limit+1 rows
281  // from `revision_actor_temp`. Tell it not to reorder the query (and also reorder it ourselves
282  // because as generated by RevisionStore it'll have `revision` first rather than
283  // `revision_actor_temp`). But not when uctag is used, as it seems as likely to be harmed as
284  // helped in that case, and not when there's only one User because in that case it fetches
285  // the one `actor` row as a constant and doesn't filesort.
286  if ( count( $users ) > 1 && !isset( $this->params['tag'] ) ) {
287  $revQuery['joins']['revision'] = $revQuery['joins']['temp_rev_user'];
288  unset( $revQuery['joins']['temp_rev_user'] );
289  $this->addOption( 'STRAIGHT_JOIN' );
290  // It isn't actually necesssary to reorder $revQuery['tables'] as Database does the right thing
291  // when join conditions are given for all joins, but GergÅ‘ is wary of relying on that so pull
292  // `revision_actor_temp` to the start.
293  $revQuery['tables'] =
294  [ 'temp_rev_user' => $revQuery['tables']['temp_rev_user'] ] + $revQuery['tables'];
295  }
296 
297  $this->addTables( $revQuery['tables'] );
298  $this->addJoinConds( $revQuery['joins'] );
299  $this->addFields( $revQuery['fields'] );
300  $this->addWhere( $revWhere['conds'] );
301 
302  // Handle continue parameter
303  if ( !is_null( $this->params['continue'] ) ) {
304  $continue = explode( '|', $this->params['continue'] );
305  if ( $this->multiUserMode ) {
306  $this->dieContinueUsageIf( count( $continue ) != 4 );
307  $modeFlag = array_shift( $continue );
308  $this->dieContinueUsageIf( $modeFlag !== $this->orderBy );
309  $encUser = $db->addQuotes( array_shift( $continue ) );
310  } else {
311  $this->dieContinueUsageIf( count( $continue ) != 2 );
312  }
313  $encTS = $db->addQuotes( $db->timestamp( $continue[0] ) );
314  $encId = (int)$continue[1];
315  $this->dieContinueUsageIf( $encId != $continue[1] );
316  $op = ( $this->params['dir'] == 'older' ? '<' : '>' );
317  if ( $this->multiUserMode ) {
318  $this->addWhere(
319  "$userField $op $encUser OR " .
320  "($userField = $encUser AND " .
321  "($tsField $op $encTS OR " .
322  "($tsField = $encTS AND " .
323  "$idField $op= $encId)))"
324  );
325  } else {
326  $this->addWhere(
327  "$tsField $op $encTS OR " .
328  "($tsField = $encTS AND " .
329  "$idField $op= $encId)"
330  );
331  }
332  }
333 
334  // Don't include any revisions where we're not supposed to be able to
335  // see the username.
336  $user = $this->getUser();
337  if ( !$this->getPermissionManager()->userHasRight( $user, 'deletedhistory' ) ) {
338  $bitmask = RevisionRecord::DELETED_USER;
339  } elseif ( !$this->getPermissionManager()
340  ->userHasAnyRight( $user, 'suppressrevision', 'viewsuppressed' )
341  ) {
342  $bitmask = RevisionRecord::DELETED_USER | RevisionRecord::DELETED_RESTRICTED;
343  } else {
344  $bitmask = 0;
345  }
346  if ( $bitmask ) {
347  $this->addWhere( $db->bitAnd( 'rev_deleted', $bitmask ) . " != $bitmask" );
348  }
349 
350  // Add the user field to ORDER BY if there are multiple users
351  if ( count( $users ) > 1 ) {
352  $this->addWhereRange( $orderUserField, $this->params['dir'], null, null );
353  }
354 
355  // Then timestamp
356  $this->addTimestampWhereRange( $tsField,
357  $this->params['dir'], $this->params['start'], $this->params['end'] );
358 
359  // Then rev_id for a total ordering
360  $this->addWhereRange( $idField, $this->params['dir'], null, null );
361 
362  $this->addWhereFld( 'page_namespace', $this->params['namespace'] );
363 
364  $show = $this->params['show'];
365  if ( $this->params['toponly'] ) { // deprecated/old param
366  $show[] = 'top';
367  }
368  if ( !is_null( $show ) ) {
369  $show = array_flip( $show );
370 
371  if ( ( isset( $show['minor'] ) && isset( $show['!minor'] ) )
372  || ( isset( $show['patrolled'] ) && isset( $show['!patrolled'] ) )
373  || ( isset( $show['autopatrolled'] ) && isset( $show['!autopatrolled'] ) )
374  || ( isset( $show['autopatrolled'] ) && isset( $show['!patrolled'] ) )
375  || ( isset( $show['top'] ) && isset( $show['!top'] ) )
376  || ( isset( $show['new'] ) && isset( $show['!new'] ) )
377  ) {
378  $this->dieWithError( 'apierror-show' );
379  }
380 
381  $this->addWhereIf( 'rev_minor_edit = 0', isset( $show['!minor'] ) );
382  $this->addWhereIf( 'rev_minor_edit != 0', isset( $show['minor'] ) );
383  $this->addWhereIf(
384  'rc_patrolled = ' . RecentChange::PRC_UNPATROLLED,
385  isset( $show['!patrolled'] )
386  );
387  $this->addWhereIf(
388  'rc_patrolled != ' . RecentChange::PRC_UNPATROLLED,
389  isset( $show['patrolled'] )
390  );
391  $this->addWhereIf(
392  'rc_patrolled != ' . RecentChange::PRC_AUTOPATROLLED,
393  isset( $show['!autopatrolled'] )
394  );
395  $this->addWhereIf(
396  'rc_patrolled = ' . RecentChange::PRC_AUTOPATROLLED,
397  isset( $show['autopatrolled'] )
398  );
399  $this->addWhereIf( $idField . ' != page_latest', isset( $show['!top'] ) );
400  $this->addWhereIf( $idField . ' = page_latest', isset( $show['top'] ) );
401  $this->addWhereIf( 'rev_parent_id != 0', isset( $show['!new'] ) );
402  $this->addWhereIf( 'rev_parent_id = 0', isset( $show['new'] ) );
403  }
404  $this->addOption( 'LIMIT', $limit + 1 );
405 
406  if ( isset( $show['patrolled'] ) || isset( $show['!patrolled'] ) ||
407  isset( $show['autopatrolled'] ) || isset( $show['!autopatrolled'] ) || $this->fld_patrolled
408  ) {
409  if ( !$user->useRCPatrol() && !$user->useNPPatrol() ) {
410  $this->dieWithError( 'apierror-permissiondenied-patrolflag', 'permissiondenied' );
411  }
412 
413  $isFilterset = isset( $show['patrolled'] ) || isset( $show['!patrolled'] ) ||
414  isset( $show['autopatrolled'] ) || isset( $show['!autopatrolled'] );
415  $this->addTables( 'recentchanges' );
416  $this->addJoinConds( [ 'recentchanges' => [
417  $isFilterset ? 'JOIN' : 'LEFT JOIN',
418  [
419  // This is a crazy hack. recentchanges has no index on rc_this_oldid, so instead of adding
420  // one T19237 did a join using rc_user_text and rc_timestamp instead. Now rc_user_text is
421  // probably unavailable, so just do rc_timestamp.
422  'rc_timestamp = ' . $tsField,
423  'rc_this_oldid = ' . $idField,
424  ]
425  ] ] );
426  }
427 
428  $this->addFieldsIf( 'rc_patrolled', $this->fld_patrolled );
429 
430  if ( $this->fld_tags ) {
431  $this->addFields( [ 'ts_tags' => ChangeTags::makeTagSummarySubquery( 'revision' ) ] );
432  }
433 
434  if ( isset( $this->params['tag'] ) ) {
435  $this->addTables( 'change_tag' );
436  $this->addJoinConds(
437  [ 'change_tag' => [ 'JOIN', [ $idField . ' = ct_rev_id' ] ] ]
438  );
439  $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
440  try {
441  $this->addWhereFld( 'ct_tag_id', $changeTagDefStore->getId( $this->params['tag'] ) );
442  } catch ( NameTableAccessException $exception ) {
443  // Return nothing.
444  $this->addWhere( '1=0' );
445  }
446  }
447  }
448 
455  private function extractRowInfo( $row ) {
456  $vals = [];
457  $anyHidden = false;
458 
459  if ( $row->rev_deleted & RevisionRecord::DELETED_TEXT ) {
460  $vals['texthidden'] = true;
461  $anyHidden = true;
462  }
463 
464  // Any rows where we can't view the user were filtered out in the query.
465  $vals['userid'] = (int)$row->rev_user;
466  $vals['user'] = $row->rev_user_text;
467  if ( $row->rev_deleted & RevisionRecord::DELETED_USER ) {
468  $vals['userhidden'] = true;
469  $anyHidden = true;
470  }
471  if ( $this->fld_ids ) {
472  $vals['pageid'] = (int)$row->rev_page;
473  $vals['revid'] = (int)$row->rev_id;
474 
475  if ( !is_null( $row->rev_parent_id ) ) {
476  $vals['parentid'] = (int)$row->rev_parent_id;
477  }
478  }
479 
480  $title = Title::makeTitle( $row->page_namespace, $row->page_title );
481 
482  if ( $this->fld_title ) {
484  }
485 
486  if ( $this->fld_timestamp ) {
487  $vals['timestamp'] = wfTimestamp( TS_ISO_8601, $row->rev_timestamp );
488  }
489 
490  if ( $this->fld_flags ) {
491  $vals['new'] = $row->rev_parent_id == 0 && !is_null( $row->rev_parent_id );
492  $vals['minor'] = (bool)$row->rev_minor_edit;
493  $vals['top'] = $row->page_latest == $row->rev_id;
494  }
495 
496  if ( $this->fld_comment || $this->fld_parsedcomment ) {
497  if ( $row->rev_deleted & RevisionRecord::DELETED_COMMENT ) {
498  $vals['commenthidden'] = true;
499  $anyHidden = true;
500  }
501 
502  $userCanView = RevisionRecord::userCanBitfield(
503  $row->rev_deleted,
504  RevisionRecord::DELETED_COMMENT, $this->getUser()
505  );
506 
507  if ( $userCanView ) {
508  $comment = $this->commentStore->getComment( 'rev_comment', $row )->text;
509  if ( $this->fld_comment ) {
510  $vals['comment'] = $comment;
511  }
512 
513  if ( $this->fld_parsedcomment ) {
514  $vals['parsedcomment'] = Linker::formatComment( $comment, $title );
515  }
516  }
517  }
518 
519  if ( $this->fld_patrolled ) {
520  $vals['patrolled'] = $row->rc_patrolled != RecentChange::PRC_UNPATROLLED;
521  $vals['autopatrolled'] = $row->rc_patrolled == RecentChange::PRC_AUTOPATROLLED;
522  }
523 
524  if ( $this->fld_size && !is_null( $row->rev_len ) ) {
525  $vals['size'] = (int)$row->rev_len;
526  }
527 
528  if ( $this->fld_sizediff
529  && !is_null( $row->rev_len )
530  && !is_null( $row->rev_parent_id )
531  ) {
532  $parentLen = $this->parentLens[$row->rev_parent_id] ?? 0;
533  $vals['sizediff'] = (int)$row->rev_len - $parentLen;
534  }
535 
536  if ( $this->fld_tags ) {
537  if ( $row->ts_tags ) {
538  $tags = explode( ',', $row->ts_tags );
539  ApiResult::setIndexedTagName( $tags, 'tag' );
540  $vals['tags'] = $tags;
541  } else {
542  $vals['tags'] = [];
543  }
544  }
545 
546  if ( $anyHidden && ( $row->rev_deleted & RevisionRecord::DELETED_RESTRICTED ) ) {
547  $vals['suppressed'] = true;
548  }
549 
550  return $vals;
551  }
552 
553  private function continueStr( $row ) {
554  if ( $this->multiUserMode ) {
555  switch ( $this->orderBy ) {
556  case 'id':
557  return "id|$row->rev_user|$row->rev_timestamp|$row->rev_id";
558  case 'name':
559  return "name|$row->rev_user_text|$row->rev_timestamp|$row->rev_id";
560  case 'actor':
561  return "actor|$row->rev_actor|$row->rev_timestamp|$row->rev_id";
562  }
563  } else {
564  return "$row->rev_timestamp|$row->rev_id";
565  }
566  }
567 
568  public function getCacheMode( $params ) {
569  // This module provides access to deleted revisions and patrol flags if
570  // the requester is logged in
571  return 'anon-public-user-private';
572  }
573 
574  public function getAllowedParams() {
575  return [
576  'limit' => [
577  ApiBase::PARAM_DFLT => 10,
578  ApiBase::PARAM_TYPE => 'limit',
579  ApiBase::PARAM_MIN => 1,
582  ],
583  'start' => [
584  ApiBase::PARAM_TYPE => 'timestamp'
585  ],
586  'end' => [
587  ApiBase::PARAM_TYPE => 'timestamp'
588  ],
589  'continue' => [
590  ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
591  ],
592  'user' => [
593  ApiBase::PARAM_TYPE => 'user',
595  ],
596  'userids' => [
597  ApiBase::PARAM_TYPE => 'integer',
599  ],
600  'userprefix' => null,
601  'dir' => [
602  ApiBase::PARAM_DFLT => 'older',
604  'newer',
605  'older'
606  ],
607  ApiBase::PARAM_HELP_MSG => 'api-help-param-direction',
608  ],
609  'namespace' => [
610  ApiBase::PARAM_ISMULTI => true,
611  ApiBase::PARAM_TYPE => 'namespace'
612  ],
613  'prop' => [
614  ApiBase::PARAM_ISMULTI => true,
615  ApiBase::PARAM_DFLT => 'ids|title|timestamp|comment|size|flags',
617  'ids',
618  'title',
619  'timestamp',
620  'comment',
621  'parsedcomment',
622  'size',
623  'sizediff',
624  'flags',
625  'patrolled',
626  'tags'
627  ],
629  ],
630  'show' => [
631  ApiBase::PARAM_ISMULTI => true,
633  'minor',
634  '!minor',
635  'patrolled',
636  '!patrolled',
637  'autopatrolled',
638  '!autopatrolled',
639  'top',
640  '!top',
641  'new',
642  '!new',
643  ],
645  'apihelp-query+usercontribs-param-show',
646  $this->getConfig()->get( 'RCMaxAge' )
647  ],
648  ],
649  'tag' => null,
650  'toponly' => [
651  ApiBase::PARAM_DFLT => false,
653  ],
654  ];
655  }
656 
657  protected function getExamplesMessages() {
658  return [
659  'action=query&list=usercontribs&ucuser=Example'
660  => 'apihelp-query+usercontribs-example-user',
661  'action=query&list=usercontribs&ucuserprefix=192.0.2.'
662  => 'apihelp-query+usercontribs-example-ipprefix',
663  ];
664  }
665 
666  public function getHelpUrls() {
667  return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Usercontribs';
668  }
669 }
670 
675 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
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:951
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:58
This is a base class for all Query modules.
dieWithError( $msg, $code=null, $data=null, $httpCode=null)
Abort execution with an error.
Definition: ApiBase.php:2006
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
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
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:1198
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.
executeGenderCacheFromResultWrapper(IResultWrapper $res, $fname=__METHOD__, $fieldPrefix='page')
Preprocess the result set to fill the GenderCache with the necessary information before using self::a...
dieContinueUsageIf( $condition)
Die with the &#39;badcontinue&#39; error.
Definition: ApiBase.php:2200
__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:584
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:717
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:842
const PRC_AUTOPATROLLED
prepareQuery(array $users, $limit)
Prepares the query and returns the limit of rows requested.
return true
Definition: router.php:92
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.