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