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