MediaWiki REL1_35
ApiQueryRevisions.php
Go to the documentation of this file.
1<?php
27
37
38 private $token = null;
39
40 public function __construct( ApiQuery $query, $moduleName ) {
41 parent::__construct( $query, $moduleName, 'rv' );
42 }
43
45
47 protected function getTokenFunctions() {
48 // tokenname => function
49 // function prototype is func($pageid, $title, $rev)
50 // should return token or false
51
52 // Don't call the hooks twice
53 if ( isset( $this->tokenFunctions ) ) {
55 }
56
57 // If we're in a mode that breaks the same-origin policy, no tokens can
58 // be obtained
59 if ( $this->lacksSameOriginSecurity() ) {
60 return [];
61 }
62
63 $this->tokenFunctions = [
64 'rollback' => [ self::class, 'getRollbackToken' ]
65 ];
66 $this->getHookRunner()->onAPIQueryRevisionsTokens( $this->tokenFunctions );
67
69 }
70
78 public static function getRollbackToken( $pageid, $title, $rev ) {
79 if ( $rev instanceof Revision ) {
80 // Don't actually need to use the Revision(Record), just emit warnings
81 wfDeprecated( __METHOD__ . ' with a Revision object', '1.35' );
82 }
83
84 global $wgUser;
85 if ( !MediaWikiServices::getInstance()->getPermissionManager()
86 ->userHasRight( $wgUser, 'rollback' ) ) {
87 return false;
88 }
89
90 return $wgUser->getEditToken( 'rollback' );
91 }
92
93 protected function run( ApiPageSet $resultPageSet = null ) {
94 $params = $this->extractRequestParams( false );
95 $revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
96
97 // If any of those parameters are used, work in 'enumeration' mode.
98 // Enum mode can only be used when exactly one page is provided.
99 // Enumerating revisions on multiple pages make it extremely
100 // difficult to manage continuations and require additional SQL indexes
101 $enumRevMode = ( $params['user'] !== null || $params['excludeuser'] !== null ||
102 $params['limit'] !== null || $params['startid'] !== null ||
103 $params['endid'] !== null || $params['dir'] === 'newer' ||
104 $params['start'] !== null || $params['end'] !== null );
105
106 $pageSet = $this->getPageSet();
107 $pageCount = $pageSet->getGoodTitleCount();
108 $revCount = $pageSet->getRevisionCount();
109
110 // Optimization -- nothing to do
111 if ( $revCount === 0 && $pageCount === 0 ) {
112 // Nothing to do
113 return;
114 }
115 if ( $revCount > 0 && count( $pageSet->getLiveRevisionIDs() ) === 0 ) {
116 // We're in revisions mode but all given revisions are deleted
117 return;
118 }
119
120 if ( $revCount > 0 && $enumRevMode ) {
121 $this->dieWithError(
122 [ 'apierror-revisions-norevids', $this->getModulePrefix() ], 'invalidparammix'
123 );
124 }
125
126 if ( $pageCount > 1 && $enumRevMode ) {
127 $this->dieWithError(
128 [ 'apierror-revisions-singlepage', $this->getModulePrefix() ], 'invalidparammix'
129 );
130 }
131
132 // In non-enum mode, rvlimit can't be directly used. Use the maximum
133 // allowed value.
134 if ( !$enumRevMode ) {
135 $this->setParsedLimit = false;
136 $params['limit'] = 'max';
137 }
138
139 $db = $this->getDB();
140
141 $idField = 'rev_id';
142 $tsField = 'rev_timestamp';
143 $pageField = 'rev_page';
144 if ( $params['user'] !== null ) {
145 // We're going to want to use the page_actor_timestamp index (on revision_actor_temp)
146 // so use that table's denormalized fields.
147 $idField = 'revactor_rev';
148 $tsField = 'revactor_timestamp';
149 $pageField = 'revactor_page';
150 }
151
152 if ( $resultPageSet === null ) {
153 $this->parseParameters( $params );
154 $this->token = $params['token'];
155 $opts = [ 'page' ];
156 if ( $this->fld_user ) {
157 $opts[] = 'user';
158 }
159 $revQuery = $revisionStore->getQueryInfo( $opts );
160
161 if ( $idField !== 'rev_id' ) {
162 $aliasFields = [ 'rev_id' => $idField, 'rev_timestamp' => $tsField, 'rev_page' => $pageField ];
163 $revQuery['fields'] = array_merge(
164 $aliasFields,
165 array_diff( $revQuery['fields'], array_keys( $aliasFields ) )
166 );
167 }
168
169 $this->addTables( $revQuery['tables'] );
170 $this->addFields( $revQuery['fields'] );
171 $this->addJoinConds( $revQuery['joins'] );
172 } else {
173 $this->limit = $this->getParameter( 'limit' ) ?: 10;
174 // Always join 'page' so orphaned revisions are filtered out
175 $this->addTables( [ 'revision', 'page' ] );
176 $this->addJoinConds(
177 [ 'page' => [ 'JOIN', [ 'page_id = rev_page' ] ] ]
178 );
179 $this->addFields( [
180 'rev_id' => $idField, 'rev_timestamp' => $tsField, 'rev_page' => $pageField
181 ] );
182 }
183
184 if ( $this->fld_tags ) {
185 $this->addFields( [ 'ts_tags' => ChangeTags::makeTagSummarySubquery( 'revision' ) ] );
186 }
187
188 if ( $params['tag'] !== null ) {
189 $this->addTables( 'change_tag' );
190 $this->addJoinConds(
191 [ 'change_tag' => [ 'JOIN', [ 'rev_id=ct_rev_id' ] ] ]
192 );
193 $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
194 try {
195 $this->addWhereFld( 'ct_tag_id', $changeTagDefStore->getId( $params['tag'] ) );
196 } catch ( NameTableAccessException $exception ) {
197 // Return nothing.
198 $this->addWhere( '1=0' );
199 }
200 }
201
202 if ( $resultPageSet === null && $this->fetchContent ) {
203 // For each page we will request, the user must have read rights for that page
204 $status = Status::newGood();
205 $user = $this->getUser();
206
208 foreach ( $pageSet->getGoodTitles() as $title ) {
209 if ( !$this->getPermissionManager()->userCan( 'read', $user, $title ) ) {
210 $status->fatal( ApiMessage::create(
211 [ 'apierror-cannotviewtitle', wfEscapeWikiText( $title->getPrefixedText() ) ],
212 'accessdenied'
213 ) );
214 }
215 }
216 if ( !$status->isGood() ) {
217 $this->dieStatus( $status );
218 }
219 }
220
221 if ( $enumRevMode ) {
222 // Indexes targeted:
223 // page_timestamp if we don't have rvuser
224 // page_actor_timestamp (on revision_actor_temp) if we have rvuser in READ_NEW mode
225 // page_user_timestamp if we have a logged-in rvuser
226 // page_timestamp or usertext_timestamp if we have an IP rvuser
227
228 // This is mostly to prevent parameter errors (and optimize SQL?)
229 $this->requireMaxOneParameter( $params, 'startid', 'start' );
230 $this->requireMaxOneParameter( $params, 'endid', 'end' );
231 $this->requireMaxOneParameter( $params, 'user', 'excludeuser' );
232
233 if ( $params['continue'] !== null ) {
234 $cont = explode( '|', $params['continue'] );
235 $this->dieContinueUsageIf( count( $cont ) != 2 );
236 $op = ( $params['dir'] === 'newer' ? '>' : '<' );
237 $continueTimestamp = $db->addQuotes( $db->timestamp( $cont[0] ) );
238 $continueId = (int)$cont[1];
239 $this->dieContinueUsageIf( $continueId != $cont[1] );
240 $this->addWhere( "$tsField $op $continueTimestamp OR " .
241 "($tsField = $continueTimestamp AND " .
242 "$idField $op= $continueId)"
243 );
244 }
245
246 // Convert startid/endid to timestamps (T163532)
247 $revids = [];
248 if ( $params['startid'] !== null ) {
249 $revids[] = (int)$params['startid'];
250 }
251 if ( $params['endid'] !== null ) {
252 $revids[] = (int)$params['endid'];
253 }
254 if ( $revids ) {
255 $db = $this->getDB();
256 $sql = $db->unionQueries( [
257 $db->selectSQLText(
258 'revision',
259 [ 'id' => 'rev_id', 'ts' => 'rev_timestamp' ],
260 [ 'rev_id' => $revids ],
261 __METHOD__
262 ),
263 $db->selectSQLText(
264 'archive',
265 [ 'id' => 'ar_rev_id', 'ts' => 'ar_timestamp' ],
266 [ 'ar_rev_id' => $revids ],
267 __METHOD__
268 ),
269 ], $db::UNION_DISTINCT );
270 $res = $db->query( $sql, __METHOD__ );
271 foreach ( $res as $row ) {
272 if ( (int)$row->id === (int)$params['startid'] ) {
273 $params['start'] = $row->ts;
274 }
275 if ( (int)$row->id === (int)$params['endid'] ) {
276 $params['end'] = $row->ts;
277 }
278 }
279 if ( $params['startid'] !== null && $params['start'] === null ) {
280 $p = $this->encodeParamName( 'startid' );
281 $this->dieWithError( [ 'apierror-revisions-badid', $p ], "badid_$p" );
282 }
283 if ( $params['endid'] !== null && $params['end'] === null ) {
284 $p = $this->encodeParamName( 'endid' );
285 $this->dieWithError( [ 'apierror-revisions-badid', $p ], "badid_$p" );
286 }
287
288 if ( $params['start'] !== null ) {
289 $op = ( $params['dir'] === 'newer' ? '>' : '<' );
290 $ts = $db->addQuotes( $db->timestampOrNull( $params['start'] ) );
291 if ( $params['startid'] !== null ) {
292 $this->addWhere( "$tsField $op $ts OR "
293 . "$tsField = $ts AND $idField $op= " . (int)$params['startid'] );
294 } else {
295 $this->addWhere( "$tsField $op= $ts" );
296 }
297 }
298 if ( $params['end'] !== null ) {
299 $op = ( $params['dir'] === 'newer' ? '<' : '>' ); // Yes, opposite of the above
300 $ts = $db->addQuotes( $db->timestampOrNull( $params['end'] ) );
301 if ( $params['endid'] !== null ) {
302 $this->addWhere( "$tsField $op $ts OR "
303 . "$tsField = $ts AND $idField $op= " . (int)$params['endid'] );
304 } else {
305 $this->addWhere( "$tsField $op= $ts" );
306 }
307 }
308 } else {
309 $this->addTimestampWhereRange( $tsField, $params['dir'],
310 $params['start'], $params['end'] );
311 }
312
313 $sort = ( $params['dir'] === 'newer' ? '' : 'DESC' );
314 $this->addOption( 'ORDER BY', [ "rev_timestamp $sort", "rev_id $sort" ] );
315
316 // There is only one ID, use it
317 $ids = array_keys( $pageSet->getGoodTitles() );
318 $this->addWhereFld( $pageField, reset( $ids ) );
319
320 if ( $params['user'] !== null ) {
321 $actorQuery = ActorMigration::newMigration()
322 ->getWhere( $db, 'rev_user', $params['user'] );
323 $this->addTables( $actorQuery['tables'] );
324 $this->addJoinConds( $actorQuery['joins'] );
325 $this->addWhere( $actorQuery['conds'] );
326 } elseif ( $params['excludeuser'] !== null ) {
327 $actorQuery = ActorMigration::newMigration()
328 ->getWhere( $db, 'rev_user', $params['excludeuser'] );
329 $this->addTables( $actorQuery['tables'] );
330 $this->addJoinConds( $actorQuery['joins'] );
331 $this->addWhere( 'NOT(' . $actorQuery['conds'] . ')' );
332 }
333 if ( $params['user'] !== null || $params['excludeuser'] !== null ) {
334 // Paranoia: avoid brute force searches (T19342)
335 if ( !$this->getPermissionManager()->userHasRight( $this->getUser(), 'deletedhistory' ) ) {
336 $bitmask = RevisionRecord::DELETED_USER;
337 } elseif ( !$this->getPermissionManager()
338 ->userHasAnyRight( $this->getUser(), 'suppressrevision', 'viewsuppressed' )
339 ) {
340 $bitmask = RevisionRecord::DELETED_USER | RevisionRecord::DELETED_RESTRICTED;
341 } else {
342 $bitmask = 0;
343 }
344 if ( $bitmask ) {
345 $this->addWhere( $db->bitAnd( 'rev_deleted', $bitmask ) . " != $bitmask" );
346 }
347 }
348 } elseif ( $revCount > 0 ) {
349 // Always targets the PRIMARY index
350
351 $revs = $pageSet->getLiveRevisionIDs();
352
353 // Get all revision IDs
354 $this->addWhereFld( 'rev_id', array_keys( $revs ) );
355
356 if ( $params['continue'] !== null ) {
357 $this->addWhere( 'rev_id >= ' . (int)$params['continue'] );
358 }
359 $this->addOption( 'ORDER BY', 'rev_id' );
360 } elseif ( $pageCount > 0 ) {
361 // Always targets the rev_page_id index
362
363 $titles = $pageSet->getGoodTitles();
364
365 // When working in multi-page non-enumeration mode,
366 // limit to the latest revision only
367 $this->addWhere( 'page_latest=rev_id' );
368
369 // Get all page IDs
370 $this->addWhereFld( 'page_id', array_keys( $titles ) );
371 // Every time someone relies on equality propagation, god kills a kitten :)
372 $this->addWhereFld( 'rev_page', array_keys( $titles ) );
373
374 if ( $params['continue'] !== null ) {
375 $cont = explode( '|', $params['continue'] );
376 $this->dieContinueUsageIf( count( $cont ) != 2 );
377 $pageid = (int)$cont[0];
378 $revid = (int)$cont[1];
379 $this->addWhere(
380 "rev_page > $pageid OR " .
381 "(rev_page = $pageid AND " .
382 "rev_id >= $revid)"
383 );
384 }
385 $this->addOption( 'ORDER BY', [
386 'rev_page',
387 'rev_id'
388 ] );
389 } else {
390 ApiBase::dieDebug( __METHOD__, 'param validation?' );
391 }
392
393 $this->addOption( 'LIMIT', $this->limit + 1 );
394
395 // T224017: `rev_timestamp` is never the correct index to use for this module, but
396 // MariaDB (10.1.37-39) sometimes insists on trying to use it anyway. Tell it not to.
397 $this->addOption( 'IGNORE INDEX', [ 'revision' => 'rev_timestamp' ] );
398
399 $count = 0;
400 $generated = [];
401 $hookData = [];
402 $res = $this->select( __METHOD__, [], $hookData );
403
404 foreach ( $res as $row ) {
405 if ( ++$count > $this->limit ) {
406 // We've reached the one extra which shows that there are
407 // additional pages to be had. Stop here...
408 if ( $enumRevMode ) {
409 $this->setContinueEnumParameter( 'continue',
410 $row->rev_timestamp . '|' . (int)$row->rev_id );
411 } elseif ( $revCount > 0 ) {
412 $this->setContinueEnumParameter( 'continue', (int)$row->rev_id );
413 } else {
414 $this->setContinueEnumParameter( 'continue', (int)$row->rev_page .
415 '|' . (int)$row->rev_id );
416 }
417 break;
418 }
419
420 if ( $resultPageSet !== null ) {
421 $generated[] = $row->rev_id;
422 } else {
423 $revision = $revisionStore->newRevisionFromRow( $row, 0, Title::newFromRow( $row ) );
424 $rev = $this->extractRevisionInfo( $revision, $row );
425
426 if ( $this->token !== null ) {
427 $title = Title::newFromLinkTarget( $revision->getPageAsLinkTarget() );
429 foreach ( $this->token as $t ) {
430 if ( $t === 'rollback' ) {
431 $val = call_user_func(
433 $title->getArticleID(),
434 $title,
435 $revision
436 );
437 } else {
438 // Token function added via APIQueryRevisionsTokens,
439 // Hook is hard deprecated, so any use of
440 // Revision objects is okay
441 $val = call_user_func(
443 $title->getArticleID(),
444 $title,
445 new Revision( $revision )
446 );
447 }
448 if ( $val === false ) {
449 $this->addWarning( [ 'apiwarn-tokennotallowed', $t ] );
450 } else {
451 $rev[$t . 'token'] = $val;
452 }
453 }
454 }
455
456 $fit = $this->processRow( $row, $rev, $hookData ) &&
457 $this->addPageSubItem( $row->rev_page, $rev, 'rev' );
458 if ( !$fit ) {
459 if ( $enumRevMode ) {
460 $this->setContinueEnumParameter( 'continue',
461 $row->rev_timestamp . '|' . (int)$row->rev_id );
462 } elseif ( $revCount > 0 ) {
463 $this->setContinueEnumParameter( 'continue', (int)$row->rev_id );
464 } else {
465 $this->setContinueEnumParameter( 'continue', (int)$row->rev_page .
466 '|' . (int)$row->rev_id );
467 }
468 break;
469 }
470 }
471 }
472
473 if ( $resultPageSet !== null ) {
474 $resultPageSet->populateFromRevisionIDs( $generated );
475 }
476 }
477
478 public function getCacheMode( $params ) {
479 if ( isset( $params['token'] ) ) {
480 return 'private';
481 }
482 return parent::getCacheMode( $params );
483 }
484
485 public function getAllowedParams() {
486 $ret = parent::getAllowedParams() + [
487 'startid' => [
488 ApiBase::PARAM_TYPE => 'integer',
489 ApiBase::PARAM_HELP_MSG_INFO => [ [ 'singlepageonly' ] ],
490 ],
491 'endid' => [
492 ApiBase::PARAM_TYPE => 'integer',
493 ApiBase::PARAM_HELP_MSG_INFO => [ [ 'singlepageonly' ] ],
494 ],
495 'start' => [
496 ApiBase::PARAM_TYPE => 'timestamp',
497 ApiBase::PARAM_HELP_MSG_INFO => [ [ 'singlepageonly' ] ],
498 ],
499 'end' => [
500 ApiBase::PARAM_TYPE => 'timestamp',
501 ApiBase::PARAM_HELP_MSG_INFO => [ [ 'singlepageonly' ] ],
502 ],
503 'dir' => [
504 ApiBase::PARAM_DFLT => 'older',
506 'newer',
507 'older'
508 ],
509 ApiBase::PARAM_HELP_MSG => 'api-help-param-direction',
510 ApiBase::PARAM_HELP_MSG_INFO => [ [ 'singlepageonly' ] ],
511 ],
512 'user' => [
513 ApiBase::PARAM_TYPE => 'user',
514 UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'ip', 'id', 'interwiki' ],
515 UserDef::PARAM_RETURN_OBJECT => true,
516 ApiBase::PARAM_HELP_MSG_INFO => [ [ 'singlepageonly' ] ],
517 ],
518 'excludeuser' => [
519 ApiBase::PARAM_TYPE => 'user',
520 UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'ip', 'id', 'interwiki' ],
521 UserDef::PARAM_RETURN_OBJECT => true,
522 ApiBase::PARAM_HELP_MSG_INFO => [ [ 'singlepageonly' ] ],
523 ],
524 'tag' => null,
525 'token' => [
527 ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() ),
529 ],
530 'continue' => [
531 ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
532 ],
533 ];
534
535 $ret['limit'][ApiBase::PARAM_HELP_MSG_INFO] = [ [ 'singlepageonly' ] ];
536
537 return $ret;
538 }
539
540 protected function getExamplesMessages() {
541 return [
542 'action=query&prop=revisions&titles=API|Main%20Page&' .
543 'rvslots=*&rvprop=timestamp|user|comment|content'
544 => 'apihelp-query+revisions-example-content',
545 'action=query&prop=revisions&titles=Main%20Page&rvlimit=5&' .
546 'rvprop=timestamp|user|comment'
547 => 'apihelp-query+revisions-example-last5',
548 'action=query&prop=revisions&titles=Main%20Page&rvlimit=5&' .
549 'rvprop=timestamp|user|comment&rvdir=newer'
550 => 'apihelp-query+revisions-example-first5',
551 'action=query&prop=revisions&titles=Main%20Page&rvlimit=5&' .
552 'rvprop=timestamp|user|comment&rvdir=newer&rvstart=2006-05-01T00:00:00Z'
553 => 'apihelp-query+revisions-example-first5-after',
554 'action=query&prop=revisions&titles=Main%20Page&rvlimit=5&' .
555 'rvprop=timestamp|user|comment&rvexcludeuser=127.0.0.1'
556 => 'apihelp-query+revisions-example-first5-not-localhost',
557 'action=query&prop=revisions&titles=Main%20Page&rvlimit=5&' .
558 'rvprop=timestamp|user|comment&rvuser=MediaWiki%20default'
559 => 'apihelp-query+revisions-example-first5-user',
560 ];
561 }
562
563 public function getHelpUrls() {
564 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Revisions';
565 }
566}
getUser()
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that $function is deprecated.
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
Definition ApiBase.php:1437
getModulePrefix()
Get parameter prefix (usually two letters or an empty string).
Definition ApiBase.php:507
const PARAM_DEPRECATED
Definition ApiBase.php:98
getParameter( $paramName, $parseLimit=true)
Get a value for the given parameter.
Definition ApiBase.php:892
dieContinueUsageIf( $condition)
Die with the 'badcontinue' error.
Definition ApiBase.php:1617
static dieDebug( $method, $message)
Internal code errors should be reported with this method.
Definition ApiBase.php:1629
const PARAM_TYPE
Definition ApiBase.php:78
const PARAM_HELP_MSG_INFO
(array) Specify additional information tags for the parameter.
Definition ApiBase.php:179
const PARAM_DFLT
Definition ApiBase.php:70
getPermissionManager()
Obtain a PermissionManager instance that subclasses may use in their authorization checks.
Definition ApiBase.php:692
requireMaxOneParameter( $params,... $required)
Die if more than one of a certain set of parameters is set and not false.
Definition ApiBase.php:944
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:772
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition ApiBase.php:162
addWarning( $msg, $code=null, $data=null)
Add a warning for this module.
Definition ApiBase.php:1356
dieStatus(StatusValue $status)
Throw an ApiUsageException based on the Status object.
Definition ApiBase.php:1495
getHookRunner()
Get an ApiHookRunner for running core API hooks.
Definition ApiBase.php:717
const PARAM_ISMULTI
Definition ApiBase.php:74
lacksSameOriginSecurity()
Returns true if the current request breaks the same-origin policy.
Definition ApiBase.php:548
This class contains a list of pages that the client has requested.
processRow( $row, array &$data, array &$hookData)
Call the ApiQueryBaseProcessRow hook.
addFields( $value)
Add a set of fields to select to the internal array.
addPageSubItem( $pageId, $item, $elemname=null)
Same as addPageSubItems(), but one element of $data at a time.
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) Stable to override.
select( $method, $extraQuery=[], array &$hookData=null)
Execute a SELECT query based on the values in the internal arrays.
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.
setContinueEnumParameter( $paramName, $paramValue)
Overridden to set the generator param if in generator mode.
getPageSet()
Get the PageSet object to work on.
encodeParamName( $paramName)
Overrides ApiBase to prepend 'g' to every generator parameter.
A base class for functions common to producing a list of revisions.
parseParameters( $params)
Parse the parameters into the various instance fields.
extractRevisionInfo(RevisionRecord $revision, $row)
Extract information from the RevisionRecord.
A query action to enumerate revisions of a given page, or show top revisions of multiple pages.
__construct(ApiQuery $query, $moduleName)
getCacheMode( $params)
Stable to override.
getHelpUrls()
Return links to more detailed help pages about the module.
run(ApiPageSet $resultPageSet=null)
getAllowedParams()
Stable to override.
static getRollbackToken( $pageid, $title, $rev)
getExamplesMessages()
Returns usage examples for this module.
This is the main query class.
Definition ApiQuery.php:37
static makeTagSummarySubquery( $tables)
Make the tag summary subquery based on the given tables and return it.
MediaWikiServices is the service locator for the application scope of MediaWiki.
Type definition for user types.
Definition UserDef.php:23
Page revision base class.
Exception representing a failure to look up a row from a name table.
Represents a title within MediaWiki.
Definition Title.php:42
return true
Definition router.php:92