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