MediaWiki 1.43.1
ApiQueryRevisions.php
Go to the documentation of this file.
1<?php
23namespace MediaWiki\Api;
24
45
55
56 private RevisionStore $revisionStore;
57 private NameTableStore $changeTagDefStore;
58 private ActorMigration $actorMigration;
59 private TitleFormatter $titleFormatter;
60
61 public function __construct(
62 ApiQuery $query,
63 string $moduleName,
64 RevisionStore $revisionStore,
65 IContentHandlerFactory $contentHandlerFactory,
66 ParserFactory $parserFactory,
67 SlotRoleRegistry $slotRoleRegistry,
68 NameTableStore $changeTagDefStore,
69 ActorMigration $actorMigration,
70 ContentRenderer $contentRenderer,
71 ContentTransformer $contentTransformer,
72 CommentFormatter $commentFormatter,
73 TempUserCreator $tempUserCreator,
74 UserFactory $userFactory,
75 TitleFormatter $titleFormatter
76 ) {
77 parent::__construct(
78 $query,
79 $moduleName,
80 'rv',
81 $revisionStore,
82 $contentHandlerFactory,
83 $parserFactory,
84 $slotRoleRegistry,
85 $contentRenderer,
86 $contentTransformer,
87 $commentFormatter,
88 $tempUserCreator,
89 $userFactory
90 );
91 $this->revisionStore = $revisionStore;
92 $this->changeTagDefStore = $changeTagDefStore;
93 $this->actorMigration = $actorMigration;
94 $this->titleFormatter = $titleFormatter;
95 }
96
97 protected function run( ?ApiPageSet $resultPageSet = null ) {
98 $params = $this->extractRequestParams( false );
99
100 // If any of those parameters are used, work in 'enumeration' mode.
101 // Enum mode can only be used when exactly one page is provided.
102 // Enumerating revisions on multiple pages make it extremely
103 // difficult to manage continuations and require additional SQL indexes
104 $enumRevMode = ( $params['user'] !== null || $params['excludeuser'] !== null ||
105 $params['limit'] !== null || $params['startid'] !== null ||
106 $params['endid'] !== null || $params['dir'] === 'newer' ||
107 $params['start'] !== null || $params['end'] !== null );
108
109 $pageSet = $this->getPageSet();
110 $pageCount = $pageSet->getGoodTitleCount();
111 $revCount = $pageSet->getRevisionCount();
112
113 // Optimization -- nothing to do
114 if ( $revCount === 0 && $pageCount === 0 ) {
115 // Nothing to do
116 return;
117 }
118 if ( $revCount > 0 && count( $pageSet->getLiveRevisionIDs() ) === 0 ) {
119 // We're in revisions mode but all given revisions are deleted
120 return;
121 }
122
123 if ( $revCount > 0 && $enumRevMode ) {
124 $this->dieWithError(
125 [ 'apierror-revisions-norevids', $this->getModulePrefix() ], 'invalidparammix'
126 );
127 }
128
129 if ( $pageCount > 1 && $enumRevMode ) {
130 $this->dieWithError(
131 [ 'apierror-revisions-singlepage', $this->getModulePrefix() ], 'invalidparammix'
132 );
133 }
134
135 // In non-enum mode, rvlimit can't be directly used. Use the maximum
136 // allowed value.
137 if ( !$enumRevMode ) {
138 $this->setParsedLimit = false;
139 $params['limit'] = 'max';
140 }
141
142 $db = $this->getDB();
143
144 $idField = 'rev_id';
145 $tsField = 'rev_timestamp';
146 $pageField = 'rev_page';
147
148 $ignoreIndex = [
149 // T224017: `rev_timestamp` is never the correct index to use for this module, but
150 // MariaDB sometimes insists on trying to use it anyway. Tell it not to.
151 // Last checked with MariaDB 10.4.13
152 'revision' => 'rev_timestamp',
153 ];
154 $useIndex = [];
155 if ( $resultPageSet === null ) {
156 $this->parseParameters( $params );
157 $queryBuilder = $this->revisionStore->newSelectQueryBuilder( $db )
158 ->joinComment()
159 ->joinPage();
160 if ( $this->fld_user ) {
161 $queryBuilder->joinUser();
162 }
163 $this->getQueryBuilder()->merge( $queryBuilder );
164 } else {
165 $this->limit = $this->getParameter( 'limit' ) ?: 10;
166 // Always join 'page' so orphaned revisions are filtered out
167 $this->addTables( [ 'revision', 'page' ] );
168 $this->addJoinConds(
169 [ 'page' => [ 'JOIN', [ 'page_id = rev_page' ] ] ]
170 );
171 $this->addFields( [
172 'rev_id' => $idField, 'rev_timestamp' => $tsField, 'rev_page' => $pageField
173 ] );
174 }
175
176 if ( $this->fld_tags ) {
177 $this->addFields( [
178 'ts_tags' => MediaWikiServices::getInstance()->getChangeTagsStore()
179 ->makeTagSummarySubquery( 'revision' )
180 ] );
181 }
182
183 if ( $params['tag'] !== null ) {
184 $this->addTables( 'change_tag' );
185 $this->addJoinConds(
186 [ 'change_tag' => [ 'JOIN', [ 'rev_id=ct_rev_id' ] ] ]
187 );
188 try {
189 $this->addWhereFld( 'ct_tag_id', $this->changeTagDefStore->getId( $params['tag'] ) );
190 } catch ( NameTableAccessException $exception ) {
191 // Return nothing.
192 $this->addWhere( '1=0' );
193 }
194 }
195
196 if ( $resultPageSet === null && $this->fetchContent ) {
197 // For each page we will request, the user must have read rights for that page
198 $status = Status::newGood();
199
201 foreach ( $pageSet->getGoodPages() as $pageIdentity ) {
202 if ( !$this->getAuthority()->authorizeRead( 'read', $pageIdentity ) ) {
203 $status->fatal( ApiMessage::create(
204 [
205 'apierror-cannotviewtitle',
206 wfEscapeWikiText( $this->titleFormatter->getPrefixedText( $pageIdentity ) ),
207 ],
208 'accessdenied'
209 ) );
210 }
211 }
212 if ( !$status->isGood() ) {
213 $this->dieStatus( $status );
214 }
215 }
216
217 if ( $enumRevMode ) {
218 // Indexes targeted:
219 // page_timestamp if we don't have rvuser
220 // page_actor_timestamp (on revision_actor_temp) if we have rvuser in READ_NEW mode
221 // page_user_timestamp if we have a logged-in rvuser
222 // page_timestamp or usertext_timestamp if we have an IP rvuser
223
224 // This is mostly to prevent parameter errors (and optimize SQL?)
225 $this->requireMaxOneParameter( $params, 'startid', 'start' );
226 $this->requireMaxOneParameter( $params, 'endid', 'end' );
227 $this->requireMaxOneParameter( $params, 'user', 'excludeuser' );
228
229 if ( $params['continue'] !== null ) {
230 $cont = $this->parseContinueParamOrDie( $params['continue'], [ 'timestamp', 'int' ] );
231 $op = ( $params['dir'] === 'newer' ? '>=' : '<=' );
232 $continueTimestamp = $db->timestamp( $cont[0] );
233 $continueId = (int)$cont[1];
234 $this->addWhere( $db->buildComparison( $op, [
235 $tsField => $continueTimestamp,
236 $idField => $continueId,
237 ] ) );
238 }
239
240 // Convert startid/endid to timestamps (T163532)
241 $revids = [];
242 if ( $params['startid'] !== null ) {
243 $revids[] = (int)$params['startid'];
244 }
245 if ( $params['endid'] !== null ) {
246 $revids[] = (int)$params['endid'];
247 }
248 if ( $revids ) {
249 $db = $this->getDB();
250 $uqb = $db->newUnionQueryBuilder();
251 $uqb->add(
252 $db->newSelectQueryBuilder()
253 ->select( [ 'id' => 'rev_id', 'ts' => 'rev_timestamp' ] )
254 ->from( 'revision' )
255 ->where( [ 'rev_id' => $revids ] )
256 );
257 $uqb->add(
258 $db->newSelectQueryBuilder()
259 ->select( [ 'id' => 'ar_rev_id', 'ts' => 'ar_timestamp' ] )
260 ->from( 'archive' )
261 ->where( [ 'ar_rev_id' => $revids ] )
262 );
263 $res = $uqb->caller( __METHOD__ )->fetchResultSet();
264 foreach ( $res as $row ) {
265 if ( (int)$row->id === (int)$params['startid'] ) {
266 $params['start'] = $row->ts;
267 }
268 if ( (int)$row->id === (int)$params['endid'] ) {
269 $params['end'] = $row->ts;
270 }
271 }
272 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset False positive
273 if ( $params['startid'] !== null && $params['start'] === null ) {
274 $p = $this->encodeParamName( 'startid' );
275 $this->dieWithError( [ 'apierror-revisions-badid', $p ], "badid_$p" );
276 }
277 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset False positive
278 if ( $params['endid'] !== null && $params['end'] === null ) {
279 $p = $this->encodeParamName( 'endid' );
280 $this->dieWithError( [ 'apierror-revisions-badid', $p ], "badid_$p" );
281 }
282
283 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset False positive
284 if ( $params['start'] !== null ) {
285 $op = ( $params['dir'] === 'newer' ? '>=' : '<=' );
286 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset False positive
287 $ts = $db->timestampOrNull( $params['start'] );
288 if ( $params['startid'] !== null ) {
289 $this->addWhere( $db->buildComparison( $op, [
290 $tsField => $ts,
291 $idField => (int)$params['startid'],
292 ] ) );
293 } else {
294 $this->addWhere( $db->buildComparison( $op, [ $tsField => $ts ] ) );
295 }
296 }
297 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset False positive
298 if ( $params['end'] !== null ) {
299 $op = ( $params['dir'] === 'newer' ? '<=' : '>=' ); // Yes, opposite of the above
300 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset False positive
301 $ts = $db->timestampOrNull( $params['end'] );
302 if ( $params['endid'] !== null ) {
303 $this->addWhere( $db->buildComparison( $op, [
304 $tsField => $ts,
305 $idField => (int)$params['endid'],
306 ] ) );
307 } else {
308 $this->addWhere( $db->buildComparison( $op, [ $tsField => $ts ] ) );
309 }
310 }
311 } else {
312 $this->addTimestampWhereRange( $tsField, $params['dir'],
313 $params['start'], $params['end'] );
314 }
315
316 $sort = ( $params['dir'] === 'newer' ? '' : 'DESC' );
317 $this->addOption( 'ORDER BY', [ "rev_timestamp $sort", "rev_id $sort" ] );
318
319 // There is only one ID, use it
320 $ids = array_keys( $pageSet->getGoodPages() );
321 $this->addWhereFld( $pageField, reset( $ids ) );
322
323 if ( $params['user'] !== null ) {
324 $actorQuery = $this->actorMigration->getWhere( $db, 'rev_user', $params['user'] );
325 $this->addTables( $actorQuery['tables'] );
326 $this->addJoinConds( $actorQuery['joins'] );
327 $this->addWhere( $actorQuery['conds'] );
328 } elseif ( $params['excludeuser'] !== null ) {
329 $actorQuery = $this->actorMigration->getWhere( $db, 'rev_user', $params['excludeuser'] );
330 $this->addTables( $actorQuery['tables'] );
331 $this->addJoinConds( $actorQuery['joins'] );
332 $this->addWhere( 'NOT(' . $actorQuery['conds'] . ')' );
333 } else {
334 // T258480: MariaDB ends up using rev_page_actor_timestamp in some cases here.
335 // Last checked with MariaDB 10.4.13
336 // Unless we are filtering by user (see above), we always want to use the
337 // "history" index on the revision table, namely page_timestamp.
338 $useIndex['revision'] = 'rev_page_timestamp';
339 }
340
341 if ( $params['user'] !== null || $params['excludeuser'] !== null ) {
342 // Paranoia: avoid brute force searches (T19342)
343 if ( !$this->getAuthority()->isAllowed( 'deletedhistory' ) ) {
344 $bitmask = RevisionRecord::DELETED_USER;
345 } elseif ( !$this->getAuthority()->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
346 $bitmask = RevisionRecord::DELETED_USER | RevisionRecord::DELETED_RESTRICTED;
347 } else {
348 $bitmask = 0;
349 }
350 if ( $bitmask ) {
351 $this->addWhere( $db->bitAnd( 'rev_deleted', $bitmask ) . " != $bitmask" );
352 }
353 }
354 } elseif ( $revCount > 0 ) {
355 // Always targets the PRIMARY index
356
357 $revs = $pageSet->getLiveRevisionIDs();
358
359 // Get all revision IDs
360 $this->addWhereFld( 'rev_id', array_keys( $revs ) );
361
362 if ( $params['continue'] !== null ) {
363 $this->addWhere( $db->buildComparison( '>=', [
364 'rev_id' => (int)$params['continue']
365 ] ) );
366 }
367 $this->addOption( 'ORDER BY', 'rev_id' );
368 } elseif ( $pageCount > 0 ) {
369 // Always targets the rev_page_id index
370
371 $pageids = array_keys( $pageSet->getGoodPages() );
372
373 // When working in multi-page non-enumeration mode,
374 // limit to the latest revision only
375 $this->addWhere( 'page_latest=rev_id' );
376
377 // Get all page IDs
378 $this->addWhereFld( 'page_id', $pageids );
379 // Every time someone relies on equality propagation, god kills a kitten :)
380 $this->addWhereFld( 'rev_page', $pageids );
381
382 if ( $params['continue'] !== null ) {
383 $cont = $this->parseContinueParamOrDie( $params['continue'], [ 'int', 'int' ] );
384 $this->addWhere( $db->buildComparison( '>=', [
385 'rev_page' => $cont[0],
386 'rev_id' => $cont[1],
387 ] ) );
388 }
389 $this->addOption( 'ORDER BY', [
390 'rev_page',
391 'rev_id'
392 ] );
393 } else {
394 ApiBase::dieDebug( __METHOD__, 'param validation?' );
395 }
396
397 $this->addOption( 'LIMIT', $this->limit + 1 );
398
399 $this->addOption( 'IGNORE INDEX', $ignoreIndex );
400
401 if ( $useIndex ) {
402 $this->addOption( 'USE INDEX', $useIndex );
403 }
404
405 $count = 0;
406 $generated = [];
407 $hookData = [];
408 $res = $this->select( __METHOD__, [], $hookData );
409
410 foreach ( $res as $row ) {
411 if ( ++$count > $this->limit ) {
412 // We've reached the one extra which shows that there are
413 // additional pages to be had. Stop here...
414 if ( $enumRevMode ) {
415 $this->setContinueEnumParameter( 'continue',
416 $row->rev_timestamp . '|' . (int)$row->rev_id );
417 } elseif ( $revCount > 0 ) {
418 $this->setContinueEnumParameter( 'continue', (int)$row->rev_id );
419 } else {
420 $this->setContinueEnumParameter( 'continue', (int)$row->rev_page .
421 '|' . (int)$row->rev_id );
422 }
423 break;
424 }
425
426 if ( $resultPageSet !== null ) {
427 $generated[] = $row->rev_id;
428 } else {
429 $revision = $this->revisionStore->newRevisionFromRow( $row, 0, Title::newFromRow( $row ) );
430 $rev = $this->extractRevisionInfo( $revision, $row );
431 $fit = $this->processRow( $row, $rev, $hookData ) &&
432 $this->addPageSubItem( $row->rev_page, $rev, 'rev' );
433 if ( !$fit ) {
434 if ( $enumRevMode ) {
435 $this->setContinueEnumParameter( 'continue',
436 $row->rev_timestamp . '|' . (int)$row->rev_id );
437 } elseif ( $revCount > 0 ) {
438 $this->setContinueEnumParameter( 'continue', (int)$row->rev_id );
439 } else {
440 $this->setContinueEnumParameter( 'continue', (int)$row->rev_page .
441 '|' . (int)$row->rev_id );
442 }
443 break;
444 }
445 }
446 }
447
448 if ( $resultPageSet !== null ) {
449 $resultPageSet->populateFromRevisionIDs( $generated );
450 }
451 }
452
453 public function getAllowedParams() {
454 $ret = parent::getAllowedParams() + [
455 'startid' => [
456 ParamValidator::PARAM_TYPE => 'integer',
457 ApiBase::PARAM_HELP_MSG_INFO => [ [ 'singlepageonly' ] ],
458 ],
459 'endid' => [
460 ParamValidator::PARAM_TYPE => 'integer',
461 ApiBase::PARAM_HELP_MSG_INFO => [ [ 'singlepageonly' ] ],
462 ],
463 'start' => [
464 ParamValidator::PARAM_TYPE => 'timestamp',
465 ApiBase::PARAM_HELP_MSG_INFO => [ [ 'singlepageonly' ] ],
466 ],
467 'end' => [
468 ParamValidator::PARAM_TYPE => 'timestamp',
469 ApiBase::PARAM_HELP_MSG_INFO => [ [ 'singlepageonly' ] ],
470 ],
471 'dir' => [
472 ParamValidator::PARAM_DEFAULT => 'older',
473 ParamValidator::PARAM_TYPE => [
474 'newer',
475 'older'
476 ],
477 ApiBase::PARAM_HELP_MSG => 'api-help-param-direction',
479 'newer' => 'api-help-paramvalue-direction-newer',
480 'older' => 'api-help-paramvalue-direction-older',
481 ],
482 ApiBase::PARAM_HELP_MSG_INFO => [ [ 'singlepageonly' ] ],
483 ],
484 'user' => [
485 ParamValidator::PARAM_TYPE => 'user',
486 UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'ip', 'temp', 'id', 'interwiki' ],
487 UserDef::PARAM_RETURN_OBJECT => true,
488 ApiBase::PARAM_HELP_MSG_INFO => [ [ 'singlepageonly' ] ],
489 ],
490 'excludeuser' => [
491 ParamValidator::PARAM_TYPE => 'user',
492 UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'ip', 'temp', 'id', 'interwiki' ],
493 UserDef::PARAM_RETURN_OBJECT => true,
494 ApiBase::PARAM_HELP_MSG_INFO => [ [ 'singlepageonly' ] ],
495 ],
496 'tag' => null,
497 'continue' => [
498 ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
499 ],
500 ];
501
502 $ret['limit'][ApiBase::PARAM_HELP_MSG_INFO] = [ [ 'singlepageonly' ] ];
503
504 return $ret;
505 }
506
507 protected function getExamplesMessages() {
508 $title = Title::newMainPage()->getPrefixedText();
509 $mp = rawurlencode( $title );
510
511 return [
512 "action=query&prop=revisions&titles=API|{$mp}&" .
513 'rvslots=*&rvprop=timestamp|user|comment|content'
514 => 'apihelp-query+revisions-example-content',
515 "action=query&prop=revisions&titles={$mp}&rvlimit=5&" .
516 'rvprop=timestamp|user|comment'
517 => 'apihelp-query+revisions-example-last5',
518 "action=query&prop=revisions&titles={$mp}&rvlimit=5&" .
519 'rvprop=timestamp|user|comment&rvdir=newer'
520 => 'apihelp-query+revisions-example-first5',
521 "action=query&prop=revisions&titles={$mp}&rvlimit=5&" .
522 'rvprop=timestamp|user|comment&rvdir=newer&rvstart=2006-05-01T00:00:00Z'
523 => 'apihelp-query+revisions-example-first5-after',
524 "action=query&prop=revisions&titles={$mp}&rvlimit=5&" .
525 'rvprop=timestamp|user|comment&rvexcludeuser=127.0.0.1'
526 => 'apihelp-query+revisions-example-first5-not-localhost',
527 "action=query&prop=revisions&titles={$mp}&rvlimit=5&" .
528 'rvprop=timestamp|user|comment&rvuser=MediaWiki%20default'
529 => 'apihelp-query+revisions-example-first5-user',
530 ];
531 }
532
533 public function getHelpUrls() {
534 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Revisions';
535 }
536}
537
539class_alias( ApiQueryRevisions::class, 'ApiQueryRevisions' );
wfEscapeWikiText( $input)
Escapes the given text so that it may be output using addWikiText() without any linking,...
array $params
The job parameters.
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
Definition ApiBase.php:1577
getModulePrefix()
Get parameter prefix (usually two letters or an empty string).
Definition ApiBase.php:580
const PARAM_HELP_MSG_INFO
(array) Specify additional information tags for the parameter.
Definition ApiBase.php:202
parseContinueParamOrDie(string $continue, array $types)
Parse the 'continue' parameter in the usual format and validate the types of each part,...
Definition ApiBase.php:1768
const PARAM_HELP_MSG_PER_VALUE
((string|array|Message)[]) When PARAM_TYPE is an array, or 'string' with PARAM_ISMULTI,...
Definition ApiBase.php:224
requireMaxOneParameter( $params,... $required)
Dies if more than one parameter from a certain set of parameters are set and not false.
Definition ApiBase.php:1025
static dieDebug( $method, $message)
Internal code errors should be reported with this method.
Definition ApiBase.php:1820
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition ApiBase.php:184
dieStatus(StatusValue $status)
Throw an ApiUsageException based on the Status object.
Definition ApiBase.php:1632
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:851
getParameter( $paramName, $parseLimit=true)
Get a value for the given parameter.
Definition ApiBase.php:973
static create( $msg, $code=null, ?array $data=null)
Create an IApiMessage for the message.
This class contains a list of pages that the client has requested.
addOption( $name, $value=null)
Add an option such as LIMIT or USE INDEX.
addPageSubItem( $pageId, $item, $elemname=null)
Same as addPageSubItems(), but one element of $data at a time.
select( $method, $extraQuery=[], ?array &$hookData=null)
Execute a SELECT query based on the values in the internal arrays.
addTimestampWhereRange( $field, $dir, $start, $end, $sort=true)
Add a WHERE clause corresponding to a range, similar to addWhereRange, but converts $start and $end t...
processRow( $row, array &$data, array &$hookData)
Call the ApiQueryBaseProcessRow hook.
addWhereFld( $field, $value)
Equivalent to addWhere( [ $field => $value ] )
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.
extractRevisionInfo(RevisionRecord $revision, $row)
Extract information from the RevisionRecord.
parseParameters( $params)
Parse the parameters into the various instance fields.
A query action to enumerate revisions of a given page, or show top revisions of multiple pages.
__construct(ApiQuery $query, string $moduleName, RevisionStore $revisionStore, IContentHandlerFactory $contentHandlerFactory, ParserFactory $parserFactory, SlotRoleRegistry $slotRoleRegistry, NameTableStore $changeTagDefStore, ActorMigration $actorMigration, ContentRenderer $contentRenderer, ContentTransformer $contentTransformer, CommentFormatter $commentFormatter, TempUserCreator $tempUserCreator, UserFactory $userFactory, TitleFormatter $titleFormatter)
getExamplesMessages()
Returns usage examples for this module.
getHelpUrls()
Return links to more detailed help pages about the module.
run(?ApiPageSet $resultPageSet=null)
This is the main query class.
Definition ApiQuery.php:48
This is the main service interface for converting single-line comments from various DB comment fields...
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
Type definition for user types.
Definition UserDef.php:27
Page revision base class.
Service for looking up page revisions.
A registry service for SlotRoleHandlers, used to define which slot roles are available on which page.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:54
Exception representing a failure to look up a row from a name table.
Represents a title within MediaWiki.
Definition Title.php:78
This is not intended to be a long-term part of MediaWiki; it will be deprecated and removed once acto...
Service for temporary user creation.
Creates User objects.
Service for formatting and validating API parameters.
Interface for objects (potentially) representing an editable wiki page.
A title formatter service for MediaWiki.
addTables( $tables, $alias=null)
addWhere( $conds)
addJoinConds( $conds)
addFields( $fields)