37use InvalidArgumentException;
44use MediaWiki\Page\LegacyArticleIdAccess;
59use Psr\Log\LoggerAwareInterface;
60use Psr\Log\LoggerInterface;
61use Psr\Log\NullLogger;
70use Wikimedia\Assert\Assert;
90 use LegacyArticleIdAccess;
203 $wikiId = WikiAwareEntity::LOCAL
205 Assert::parameterType(
'string|boolean',
$wikiId,
'$wikiId' );
217 $this->logger =
new NullLogger();
221 $this->hookRunner =
new HookRunner( $hookContainer );
232 return $this->blobStore->isReadOnly();
257 list( $mode, ) = DBAccessObjectUtils::getDBOptions( $queryFlags );
269 return $lb->getConnectionRef( $mode, $groups, $this->wikiId );
288 public function getTitle( $pageId, $revId, $queryFlags = self::READ_NORMAL ) {
290 if ( $this->wikiId !== WikiAwareEntity::LOCAL ) {
291 wfDeprecatedMsg(
'Using a Title object to refer to a page on another site.',
'1.36' );
294 $page = $this->
getPage( $pageId, $revId, $queryFlags );
295 return $this->titleFactory->castFromPageIdentity( $page );
308 private function getPage( ?
int $pageId, ?
int $revId,
int $queryFlags = self::READ_NORMAL ) {
309 if ( !$pageId && !$revId ) {
310 throw new InvalidArgumentException(
'$pageId and $revId cannot both be 0 or null' );
315 if ( DBAccessObjectUtils::hasFlags( $queryFlags, self::READ_LATEST_IMMUTABLE ) ) {
316 $queryFlags = self::READ_NORMAL;
319 $canUsePageId = ( $pageId !==
null && $pageId > 0 );
322 if ( $canUsePageId ) {
323 $page = $this->pageStore->getPageById( $pageId, $queryFlags );
330 $canUseRevId = ( $revId !==
null && $revId > 0 );
332 if ( $canUseRevId ) {
333 $pageQuery = $this->pageStore->newSelectQueryBuilder( $queryFlags )
334 ->join(
'revision',
null,
'page_id=rev_page' )
335 ->conds( [
'rev_id' => $revId ] )
336 ->caller( __METHOD__ );
338 $page = $pageQuery->fetchPageRecord();
345 if ( $queryFlags === self::READ_NORMAL ) {
346 $title = $this->
getPage( $pageId, $revId, self::READ_LATEST );
349 __METHOD__ .
' fell back to READ_LATEST and got a Title.',
357 'Could not determine title for page ID {page_id} and revision ID {rev_id}',
359 'page_id' => $pageId,
378 return $this->titleFactory->castFromPageIdentity( $page );
392 if ( $value ===
null ) {
394 "$name must not be " . var_export( $value,
true ) .
"!"
409 if ( $value ===
null || $value === 0 || $value ===
'' ) {
411 "$name must not be " . var_export( $value,
true ) .
"!"
431 $this->checkDatabaseDomain( $dbw );
438 'main slot must be provided'
443 $this->failOnNull( $rev->
getSize(),
'size field' );
444 $this->failOnEmpty( $rev->
getSha1(),
'sha1 field' );
445 $this->failOnEmpty( $rev->
getTimestamp(),
'timestamp field' );
448 $this->failOnNull( $user->getId(),
'user field' );
449 $this->failOnEmpty( $user->getName(),
'user_text field' );
461 Assert::precondition(
462 $mainSlot->getSize() === $rev->
getSize(),
463 'The revisions\'s size must match the main slot\'s size (see T239717)'
465 Assert::precondition(
466 $mainSlot->getSha1() === $rev->
getSha1(),
467 'The revisions\'s SHA1 hash must match the main slot\'s SHA1 hash (see T239717)'
471 $pageId = $this->failOnEmpty( $rev->
getPageId( $this->wikiId ),
'rev_page field' );
473 $parentId = $rev->
getParentId() ?? $this->getPreviousRevisionId( $dbw, $rev );
478 function (
IDatabase $dbw, $fname ) use (
485 return $this->insertRevisionInternal(
498 Assert::postcondition( $rev->
getId( $this->wikiId ) > 0,
'revision must have an ID' );
499 Assert::postcondition( $rev->
getPageId( $this->wikiId ) > 0,
'revision must have a page ID' );
500 Assert::postcondition(
502 'revision must have a comment'
504 Assert::postcondition(
506 'revision must have a user'
515 foreach ( $slotRoles as $role ) {
517 Assert::postcondition(
518 $slot->getContent() !==
null,
519 $role .
' slot must have content'
521 Assert::postcondition(
522 $slot->hasRevision(),
523 $role .
' slot must have a revision associated'
527 $this->hookRunner->onRevisionRecordInserted( $rev );
549 $this->checkDatabaseDomain( $dbw );
553 Assert::precondition(
554 $this->slotRoleRegistry->getRoleHandler( $role )->isDerived(),
555 'Trying to modify a slot that is not derived'
559 $isDerived = $this->slotRoleRegistry->getRoleHandler( $role )->isDerived();
560 Assert::precondition(
562 'Trying to remove a slot that is not derived'
564 throw new LogicException(
'Removing derived slots is not yet implemented. See T277394.' );
570 function ( IDatabase $dbw, $fname ) use (
574 return $this->updateSlotsInternal(
576 $revisionSlotsUpdate,
582 foreach ( $slotRecords as $role => $slot ) {
583 Assert::postcondition(
584 $slot->getContent() !==
null,
585 $role .
' slot must have content'
587 Assert::postcondition(
588 $slot->hasRevision(),
589 $role .
' slot must have a revision associated'
607 $page = $revision->getPage();
608 $revId = $revision->
getId( $this->wikiId );
610 BlobStore::PAGE_HINT => $page->getId( $this->wikiId ),
611 BlobStore::REVISION_HINT => $revId,
612 BlobStore::PARENT_HINT => $revision->
getParentId( $this->wikiId ),
618 $newSlots[$role] = $this->insertSlotOn( $dbw, $revId, $slot, $page, $blobHints );
635 $revisionRow = $this->insertRevisionRowOn(
641 $revisionId = $revisionRow[
'rev_id'];
644 BlobStore::PAGE_HINT => $pageId,
645 BlobStore::REVISION_HINT => $revisionId,
646 BlobStore::PARENT_HINT => $parentId,
650 foreach ( $slotRoles as $role ) {
651 $slot = $rev->
getSlot( $role, RevisionRecord::RAW );
660 if ( $slot->hasRevision() && $slot->hasContentId() ) {
663 $slot->getRevision() === $revisionId,
664 'slot role ' . $slot->getRole(),
665 'Existing slot should belong to revision '
666 . $revisionId .
', but belongs to revision ' . $slot->getRevision() .
'!'
672 $newSlots[$role] = $slot;
674 $newSlots[$role] = $this->insertSlotOn( $dbw, $revisionId, $slot, $page, $blobHints );
678 $this->insertIpChangesRow( $dbw, $user, $rev, $revisionId );
684 (
object)$revisionRow,
705 array $blobHints = []
710 $blobAddress = $this->storeContentBlob( $protoSlot, $page, $blobHints );
718 $contentId = $this->insertContentRowOn( $protoSlot, $dbw, $blobAddress );
721 $this->insertSlotRowOn( $protoSlot, $dbw, $revisionId, $contentId );
723 return SlotRecord::newSaved(
744 if ( $user->
getId() === 0 && IPUtils::isValid( $user->
getName() ) ) {
746 'ipc_rev_id' => $revisionId,
748 'ipc_hex' => IPUtils::toHex( $user->
getName() ),
750 $dbw->
insert(
'ip_changes', $ipcRow, __METHOD__ );
769 $revisionRow = $this->getBaseRevisionRow( $dbw, $rev, $parentId );
771 list( $commentFields, $commentCallback ) =
772 $this->commentStore->insertWithTempTable(
777 $revisionRow += $commentFields;
779 list( $actorFields, $actorCallback ) =
780 $this->actorMigration->getInsertValuesWithTempTable(
783 $rev->
getUser( RevisionRecord::RAW )
785 $revisionRow += $actorFields;
787 $dbw->
insert(
'revision', $revisionRow, __METHOD__ );
789 if ( !isset( $revisionRow[
'rev_id'] ) ) {
791 $revisionRow[
'rev_id'] = intval( $dbw->
insertId() );
793 if ( $dbw->
getType() ===
'mysql' ) {
798 $maxRevId = intval( $dbw->
selectField(
'archive',
'MAX(ar_rev_id)',
'', __METHOD__ ) );
800 $maxRevId2 = intval( $dbw->
selectField(
'slots',
'MAX(slot_revision_id)',
'', __METHOD__ ) );
801 if ( $maxRevId2 >= $maxRevId ) {
802 $maxRevId = $maxRevId2;
806 if ( $maxRevId >= $revisionRow[
'rev_id'] ) {
807 $this->logger->debug(
808 '__METHOD__: Inserted revision {revid} but {table} has revisions up to {maxrevid}.'
809 .
' Trying to fix it.',
811 'revid' => $revisionRow[
'rev_id'],
813 'maxrevid' => $maxRevId,
817 if ( !$dbw->
lock(
'fix-for-T202032', __METHOD__ ) ) {
818 throw new MWException(
'Failed to get database lock for T202032' );
822 static function ( $trigger,
IDatabase $dbw ) use ( $fname ) {
823 $dbw->
unlock(
'fix-for-T202032', $fname );
828 $dbw->
delete(
'revision', [
'rev_id' => $revisionRow[
'rev_id'] ], __METHOD__ );
840 $dbw->
selectSQLText(
'archive', [
'v' =>
"MAX(ar_rev_id)" ],
'', __METHOD__ ) .
' FOR UPDATE',
845 $dbw->
selectSQLText(
'slots', [
'v' =>
"MAX(slot_revision_id)" ],
'', __METHOD__ )
852 $row1 ? intval( $row1->v ) : 0,
853 $row2 ? intval( $row2->v ) : 0
859 $revisionRow[
'rev_id'] = $maxRevId + 1;
860 $dbw->
insert(
'revision', $revisionRow, __METHOD__ );
865 $commentCallback( $revisionRow[
'rev_id'] );
866 $actorCallback( $revisionRow[
'rev_id'], $revisionRow );
885 'rev_page' => $rev->
getPageId( $this->wikiId ),
886 'rev_parent_id' => $parentId,
887 'rev_minor_edit' => $rev->
isMinor() ? 1 : 0,
894 if ( $rev->
getId( $this->wikiId ) !==
null ) {
896 $revisionRow[
'rev_id'] = $rev->
getId( $this->wikiId );
913 array $blobHints = []
916 $format =
$content->getDefaultFormat();
921 return $this->blobStore->storeBlob(
928 BlobStore::DESIGNATION_HINT =>
'page-content',
929 BlobStore::ROLE_HINT => $slot->
getRole(),
930 BlobStore::SHA1_HINT => $slot->
getSha1(),
931 BlobStore::MODEL_HINT => $model,
932 BlobStore::FORMAT_HINT => $format,
946 'slot_revision_id' => $revisionId,
947 'slot_role_id' => $this->slotRoleStore->acquireId( $slot->
getRole() ),
948 'slot_content_id' => $contentId,
953 $dbw->
insert(
'slots', $slotRow, __METHOD__ );
964 'content_size' => $slot->
getSize(),
965 'content_sha1' => $slot->
getSha1(),
966 'content_model' => $this->contentModelStore->acquireId( $slot->
getModel() ),
967 'content_address' => $blobAddress,
969 $dbw->
insert(
'content', $contentRow, __METHOD__ );
987 $format =
$content->getDefaultFormat();
988 $handler =
$content->getContentHandler();
990 if ( !$handler->isSupportedFormat( $format ) ) {
992 "Can't use format $format with content model $model on $page role $role"
998 "New content for $page role $role is not valid! Content model is $model"
1035 $this->checkDatabaseDomain( $dbw );
1037 $pageId = $this->getArticleId( $page );
1045 [
'page_id' => $pageId ],
1050 if ( !$pageLatest ) {
1051 $msg =
'T235589: Failed to select table row during null revision creation' .
1052 " Page id '$pageId' does not exist.";
1053 $this->logger->error(
1055 [
'exception' =>
new RuntimeException( $msg ) ]
1062 $oldRevision = $this->loadRevisionFromConds(
1064 [
'rev_id' => intval( $pageLatest ) ],
1069 if ( !$oldRevision ) {
1070 $msg =
"Failed to load latest revision ID $pageLatest of page ID $pageId.";
1071 $this->logger->error(
1073 [
'exception' =>
new RuntimeException( $msg ) ]
1079 $timestamp = MWTimestamp::now( TS_MW );
1080 $newRevision = MutableRevisionRecord::newFromParentRevision( $oldRevision );
1082 $newRevision->setComment( $comment );
1083 $newRevision->setUser( $user );
1084 $newRevision->setTimestamp( $timestamp );
1085 $newRevision->setMinorEdit( $minor );
1087 return $newRevision;
1100 $rc = $this->getRecentChange( $rev );
1101 if ( $rc && $rc->getAttribute(
'rc_patrolled' ) == RecentChange::PRC_UNPATROLLED ) {
1102 return $rc->getAttribute(
'rc_id' );
1122 list( $dbType, ) = DBAccessObjectUtils::getDBOptions( $flags );
1124 $rc = RecentChange::newFromConds(
1125 [
'rc_this_oldid' => $rev->
getId( $this->wikiId ) ],
1155 ?
string $blobData =
null,
1156 ?
string $blobFlags =
null,
1157 ?
string $blobFormat =
null,
1160 if ( $blobData !==
null ) {
1163 if ( $blobFlags ===
null ) {
1167 $data = $this->blobStore->expandBlob( $blobData, $blobFlags, $cacheKey );
1168 if ( $data ===
false ) {
1170 'Failed to expand blob data using flags {flags} (key: {cache_key})',
1172 'flags' => $blobFlags,
1173 'cache_key' => $cacheKey,
1182 $data = $this->blobStore->getBlob( $address, $queryFlags );
1185 'Failed to load data blob from {address}'
1186 .
'If this problem persist, use the findBadBlobs maintenance script '
1187 .
'to investigate the issue and mark bad blobs.',
1188 [
'address' => $e->getMessage() ],
1198 if ( !$this->contentHandlerFactory->isDefinedModel( $model ) ) {
1199 $this->logger->warning(
1200 "Undefined content model '$model', falling back to UnknownContent",
1204 'role_name' => $slot->
getRole(),
1205 'model_name' => $model,
1213 return $this->contentHandlerFactory
1214 ->getContentHandler( $model )
1215 ->unserializeContent( $data, $blobFormat );
1236 return $this->newRevisionFromConds( [
'rev_id' => intval( $id ) ], $flags, $page );
1257 'page_namespace' => $page->getNamespace(),
1258 'page_title' => $page->getDBkey()
1263 $page = $this->wikiId === WikiAwareEntity::LOCAL ? Title::castFromLinkTarget( $page ) :
null;
1272 $conds[
'rev_id'] = $revId;
1273 return $this->newRevisionFromConds( $conds, $flags, $page );
1280 $db = $this->getDBConnectionRefForQueryFlags( $flags );
1281 $conds[] =
'rev_id=page_latest';
1282 return $this->loadRevisionFromConds( $db, $conds, $flags, $page );
1303 $conds = [
'page_id' => $pageId ];
1310 $conds[
'rev_id'] = $revId;
1311 return $this->newRevisionFromConds( $conds, $flags );
1318 $db = $this->getDBConnectionRefForQueryFlags( $flags );
1320 $conds[] =
'rev_id=page_latest';
1322 return $this->loadRevisionFromConds( $db, $conds, $flags );
1344 int $flags = IDBAccessObject::READ_NORMAL
1348 $page = $this->wikiId === WikiAwareEntity::LOCAL ? Title::castFromLinkTarget( $page ) :
null;
1350 $db = $this->getDBConnectionRefForQueryFlags( $flags );
1351 return $this->newRevisionFromConds(
1353 'rev_timestamp' => $db->timestamp( $timestamp ),
1354 'page_namespace' => $page->getNamespace(),
1355 'page_title' => $page->getDBkey()
1370 $revQuery = $this->getSlotsQueryInfo( [
'content' ] );
1372 list( $dbMode, $dbOptions ) = DBAccessObjectUtils::getDBOptions( $queryFlags );
1373 $db = $this->getDBConnectionRef( $dbMode );
1379 'slot_revision_id' => $revId,
1386 if ( !
$res->numRows() && !( $queryFlags & self::READ_LATEST ) ) {
1388 $this->logger->info(
1389 __METHOD__ .
' falling back to READ_LATEST.',
1395 return $this->loadSlotRecords(
1397 $queryFlags | self::READ_LATEST,
1402 return $this->constructSlotRecords( $revId,
$res, $queryFlags, $page );
1422 $slotContents =
null
1426 foreach ( $slotRows as $row ) {
1428 if ( !isset( $row->role_name ) ) {
1429 $row->role_name = $this->slotRoleStore->getName( (
int)$row->slot_role_id );
1432 if ( !isset( $row->model_name ) ) {
1433 if ( isset( $row->content_model ) ) {
1434 $row->model_name = $this->contentModelStore->getName( (
int)$row->content_model );
1438 $slotRoleHandler = $this->slotRoleRegistry->getRoleHandler( $row->role_name );
1439 $row->model_name = $slotRoleHandler->getDefaultModel( $page );
1444 if ( isset( $row->blob_data ) ) {
1445 $slotContents[$row->content_address] = $row->blob_data;
1448 $contentCallback =
function (
SlotRecord $slot ) use ( $slotContents, $queryFlags ) {
1450 if ( isset( $slotContents[$slot->getAddress()] ) ) {
1451 $blob = $slotContents[$slot->getAddress()];
1456 return $this->loadSlotContent( $slot,
$blob,
null,
null, $queryFlags );
1459 $slots[$row->role_name] =
new SlotRecord( $row, $contentCallback );
1462 if ( !isset( $slots[SlotRecord::MAIN] ) ) {
1463 $this->logger->error(
1464 __METHOD__ .
': Main slot of revision not found in database. See T212428.',
1467 'queryFlags' => $queryFlags,
1473 'Main slot of revision not found in database. See T212428.'
1504 $this->constructSlotRecords( $revId, $slotRows, $queryFlags, $page )
1510 $slots =
new RevisionSlots(
function () use( $revId, $queryFlags, $page ) {
1511 return $this->loadSlotRecords( $revId, $queryFlags, $page );
1543 array $overrides = []
1545 return $this->newRevisionFromArchiveRowAndSlots( $row,
null, $queryFlags, $page, $overrides );
1566 return $this->newRevisionFromRowAndSlots( $row,
null, $queryFlags, $page, $fromCache );
1591 int $queryFlags = 0,
1593 array $overrides = []
1595 if ( !$page && isset( $overrides[
'title'] ) ) {
1596 if ( !( $overrides[
'title'] instanceof
PageIdentity ) ) {
1597 throw new MWException(
'title field override must contain a PageIdentity object.' );
1600 $page = $overrides[
'title'];
1603 if ( !isset( $page ) ) {
1604 if ( isset( $row->ar_namespace ) && isset( $row->ar_title ) ) {
1605 $page = Title::makeTitle( $row->ar_namespace, $row->ar_title );
1607 throw new InvalidArgumentException(
1608 'A Title or ar_namespace and ar_title must be given'
1613 foreach ( $overrides as $key => $value ) {
1615 $row->$field = $value;
1619 $user = $this->actorStore->newActorFromRowFields(
1620 $row->ar_user ??
null,
1621 $row->ar_user_text ??
null,
1622 $row->ar_actor ??
null
1624 }
catch ( InvalidArgumentException $ex ) {
1625 $this->logger->warning(
'Could not load user for archive revision {rev_id}', [
1626 'ar_rev_id' => $row->ar_rev_id,
1627 'ar_actor' => $row->ar_actor ??
'null',
1628 'ar_user_text' => $row->ar_user_text ??
'null',
1629 'ar_user' => $row->ar_user ??
'null',
1632 $user = $this->actorStore->getUnknownActor();
1635 $db = $this->getDBConnectionRefForQueryFlags( $queryFlags );
1637 $comment = $this->commentStore->getCommentLegacy( $db,
'ar_comment', $row,
true );
1640 $slots = $this->newRevisionSlots( $row->ar_rev_id, $row, $slots, $queryFlags, $page );
1666 int $queryFlags = 0,
1668 bool $fromCache =
false
1671 if ( isset( $row->page_id )
1672 && isset( $row->page_namespace )
1673 && isset( $row->page_title )
1677 (
int)$row->page_namespace,
1682 $page = $this->wrapPage( $page );
1684 $pageId = (int)( $row->rev_page ?? 0 );
1685 $revId = (int)( $row->rev_id ?? 0 );
1687 $page = $this->getPage( $pageId, $revId, $queryFlags );
1690 $page = $this->ensureRevisionRowMatchesPage( $row, $page );
1697 "Failed to determine page associated with revision {$row->rev_id}"
1702 $user = $this->actorStore->newActorFromRowFields(
1703 $row->rev_user ??
null,
1704 $row->rev_user_text ??
null,
1705 $row->rev_actor ??
null
1707 }
catch ( InvalidArgumentException $ex ) {
1708 $this->logger->warning(
'Could not load user for revision {rev_id}', [
1709 'rev_id' => $row->rev_id,
1710 'rev_actor' => $row->rev_actor ??
'null',
1711 'rev_user_text' => $row->rev_user_text ??
'null',
1712 'rev_user' => $row->rev_user ??
'null',
1715 $user = $this->actorStore->getUnknownActor();
1718 $db = $this->getDBConnectionRefForQueryFlags( $queryFlags );
1720 $comment = $this->commentStore->getCommentLegacy( $db,
'rev_comment', $row,
true );
1723 $slots = $this->newRevisionSlots( $row->rev_id, $row, $slots, $queryFlags, $page );
1729 function ( $revId ) use ( $queryFlags ) {
1730 $db = $this->getDBConnectionRefForQueryFlags( $queryFlags );
1731 $row = $this->fetchRevisionRowFromConds(
1733 [
'rev_id' => intval( $revId ) ]
1735 if ( !$row && !( $queryFlags & self::READ_LATEST ) ) {
1737 $this->logger->info(
1738 'RevisionStoreCacheRecord refresh callback falling back to READ_LATEST.',
1744 $dbw = $this->getDBConnectionRefForQueryFlags( self::READ_LATEST );
1745 $row = $this->fetchRevisionRowFromConds(
1747 [
'rev_id' => intval( $revId ) ]
1751 return [
null, null ];
1755 $this->actorStore->newActorFromRowFields(
1756 $row->rev_user ??
null,
1757 $row->rev_user_text ??
null,
1758 $row->rev_actor ??
null
1762 $page, $user, $comment, $row, $slots, $this->wikiId
1766 $page, $user, $comment, $row, $slots, $this->wikiId );
1783 $revId = (int)( $row->rev_id ?? 0 );
1784 $revPageId = (int)( $row->rev_page ?? 0 );
1785 $expectedPageId = $page->
getId( $this->wikiId );
1787 if ( $revPageId && $expectedPageId && $revPageId !== $expectedPageId ) {
1789 $pageRec = $this->pageStore->getPageByName(
1792 PageStore::READ_LATEST
1794 $masterPageId = $pageRec->getId( $this->wikiId );
1795 $masterLatest = $pageRec->getLatest( $this->wikiId );
1796 if ( $revPageId === $masterPageId ) {
1797 if ( $page instanceof
Title ) {
1800 $page->resetArticleID( $masterPageId );
1806 $this->logger->info(
1807 "Encountered stale Title object",
1809 'page_id_stale' => $expectedPageId,
1810 'page_id_reloaded' => $masterPageId,
1811 'page_latest' => $masterLatest,
1817 $expectedTitle = (string)$page;
1818 if ( $page instanceof
Title ) {
1820 $page = $this->titleFactory->newFromID( $revPageId );
1831 $this->logger->error(
1832 "Encountered mismatching Title object (see T259022, T268910, T279832, T263340)",
1834 'expected_page_id' => $masterPageId,
1835 'expected_page_title' => $expectedTitle,
1836 'rev_page' => $revPageId,
1837 'rev_page_title' => (
string)$page,
1838 'page_latest' => $masterLatest,
1876 array $options = [],
1881 $archiveMode = $options[
'archive'] ??
false;
1883 if ( $archiveMode ) {
1884 $revIdField =
'ar_rev_id';
1886 $revIdField =
'rev_id';
1890 $pageIdsToFetchTitles = [];
1891 $titlesByPageKey = [];
1892 foreach ( $rows as $row ) {
1893 if ( isset( $rowsByRevId[$row->$revIdField] ) ) {
1895 'internalerror_info',
1896 "Duplicate rows in newRevisionsFromBatch, $revIdField {$row->$revIdField}"
1902 $archiveMode ? $row->ar_namespace .
':' . $row->ar_title : $row->rev_page;
1905 if ( !$archiveMode && $row->rev_page != $this->getArticleId( $page ) ) {
1906 throw new InvalidArgumentException(
1907 "Revision {$row->$revIdField} doesn't belong to page "
1908 . $this->getArticleId( $page )
1913 && ( $row->ar_namespace != $page->getNamespace()
1914 || $row->ar_title !== $page->getDBkey() )
1916 throw new InvalidArgumentException(
1917 "Revision {$row->$revIdField} doesn't belong to page "
1921 } elseif ( !isset( $titlesByPageKey[ $row->_page_key ] ) ) {
1922 if ( isset( $row->page_namespace ) && isset( $row->page_title )
1925 && isset( $row->page_id ) && isset( $row->rev_page )
1926 && $row->rev_page === $row->page_id
1928 $titlesByPageKey[ $row->_page_key ] = Title::newFromRow( $row );
1929 } elseif ( $archiveMode ) {
1931 $titlesByPageKey[ $row->_page_key ] =
1932 Title::makeTitle( $row->ar_namespace, $row->ar_title );
1934 $pageIdsToFetchTitles[] = $row->rev_page;
1937 $rowsByRevId[$row->$revIdField] = $row;
1940 if ( empty( $rowsByRevId ) ) {
1941 $result->setResult(
true, [] );
1948 $pageKey = $archiveMode
1949 ? $page->getNamespace() .
':' . $page->getDBkey()
1950 : $this->getArticleId( $page );
1952 $titlesByPageKey[$pageKey] = $page;
1953 } elseif ( !empty( $pageIdsToFetchTitles ) ) {
1956 Assert::invariant( !$archiveMode,
'Titles are not loaded by ID in archive mode.' );
1958 $pageIdsToFetchTitles = array_unique( $pageIdsToFetchTitles );
1959 foreach ( Title::newFromIDs( $pageIdsToFetchTitles ) as
$t ) {
1960 $titlesByPageKey[
$t->getArticleID()] =
$t;
1965 $newRevisionRecord = [
1967 $archiveMode ?
'newRevisionFromArchiveRowAndSlots' :
'newRevisionFromRowAndSlots'
1970 if ( !isset( $options[
'slots'] ) ) {
1974 static function ( $row )
1975 use ( $queryFlags, $titlesByPageKey, $result, $newRevisionRecord, $revIdField ) {
1977 if ( !isset( $titlesByPageKey[$row->_page_key] ) ) {
1979 'internalerror_info',
1980 "Couldn't find title for rev {$row->$revIdField} "
1981 .
"(page key {$row->_page_key})"
1985 return $newRevisionRecord( $row,
null, $queryFlags,
1986 $titlesByPageKey[ $row->_page_key ] );
1988 $result->warning(
'internalerror_info', $e->getMessage() );
1999 'slots' => $options[
'slots'] ??
true,
2000 'blobs' => $options[
'content'] ??
false,
2003 if ( is_array( $slotRowOptions[
'slots'] )
2004 && !in_array( SlotRecord::MAIN, $slotRowOptions[
'slots'] )
2007 $slotRowOptions[
'slots'][] = SlotRecord::MAIN;
2010 $slotRowsStatus = $this->getSlotRowsForBatch( $rowsByRevId, $slotRowOptions, $queryFlags );
2012 $result->merge( $slotRowsStatus );
2013 $slotRowsByRevId = $slotRowsStatus->getValue();
2019 use ( $slotRowsByRevId, $queryFlags, $titlesByPageKey, $result,
2020 $revIdField, $newRevisionRecord
2022 if ( !isset( $slotRowsByRevId[$row->$revIdField] ) ) {
2024 'internalerror_info',
2025 "Couldn't find slots for rev {$row->$revIdField}"
2029 if ( !isset( $titlesByPageKey[$row->_page_key] ) ) {
2031 'internalerror_info',
2032 "Couldn't find title for rev {$row->$revIdField} "
2033 .
"(page key {$row->_page_key})"
2038 return $newRevisionRecord(
2041 $this->constructSlotRecords(
2043 $slotRowsByRevId[$row->$revIdField],
2045 $titlesByPageKey[$row->_page_key]
2049 $titlesByPageKey[$row->_page_key]
2052 $result->warning(
'internalerror_info', $e->getMessage() );
2087 array $options = [],
2093 foreach ( $rowsOrIds as $row ) {
2094 if ( is_object( $row ) ) {
2095 $revIds[] = isset( $row->ar_rev_id ) ? (int)$row->ar_rev_id : (int)$row->rev_id;
2097 $revIds[] = (int)$row;
2103 if ( empty( $revIds ) ) {
2104 $result->setResult(
true, [] );
2109 $slotQueryInfo = $this->getSlotsQueryInfo( [
'content' ] );
2110 $revIdField = $slotQueryInfo[
'keys'][
'rev_id'];
2111 $slotQueryConds = [ $revIdField => $revIds ];
2113 if ( isset( $options[
'slots'] ) && is_array( $options[
'slots'] ) ) {
2114 if ( empty( $options[
'slots'] ) ) {
2116 $result->setResult(
true, array_fill_keys( $revIds, [] ) );
2120 $roleIdField = $slotQueryInfo[
'keys'][
'role_id'];
2121 $slotQueryConds[$roleIdField] = array_map(
function ( $slot_name ) {
2122 return $this->slotRoleStore->getId( $slot_name );
2123 }, $options[
'slots'] );
2126 $db = $this->getDBConnectionRefForQueryFlags( $queryFlags );
2127 $slotRows = $db->select(
2128 $slotQueryInfo[
'tables'],
2129 $slotQueryInfo[
'fields'],
2133 $slotQueryInfo[
'joins']
2136 $slotContents =
null;
2137 if ( $options[
'blobs'] ??
false ) {
2138 $blobAddresses = [];
2139 foreach ( $slotRows as $slotRow ) {
2140 $blobAddresses[] = $slotRow->content_address;
2142 $slotContentFetchStatus = $this->blobStore
2143 ->getBlobBatch( $blobAddresses, $queryFlags );
2144 foreach ( $slotContentFetchStatus->getErrors() as $error ) {
2145 $result->warning( $error[
'message'], ...$error[
'params'] );
2147 $slotContents = $slotContentFetchStatus->getValue();
2150 $slotRowsByRevId = [];
2151 foreach ( $slotRows as $slotRow ) {
2152 if ( $slotContents ===
null ) {
2154 } elseif ( isset( $slotContents[$slotRow->content_address] ) ) {
2155 $slotRow->blob_data = $slotContents[$slotRow->content_address];
2158 'internalerror_info',
2159 "Couldn't find blob data for rev {$slotRow->slot_revision_id}"
2161 $slotRow->blob_data =
null;
2165 if ( !isset( $slotRow->role_name ) && isset( $slotRow->slot_role_id ) ) {
2166 $slotRow->role_name = $this->slotRoleStore->getName( (
int)$slotRow->slot_role_id );
2170 if ( !isset( $slotRow->model_name ) && isset( $slotRow->content_model ) ) {
2171 $slotRow->model_name = $this->contentModelStore->getName( (
int)$slotRow->content_model );
2174 $slotRowsByRevId[$slotRow->slot_revision_id][$slotRow->role_name] = $slotRow;
2177 $result->setResult(
true, $slotRowsByRevId );
2206 $result = $this->getSlotRowsForBatch(
2208 [
'slots' => $slots,
'blobs' =>
true ],
2212 if ( $result->isOK() ) {
2214 foreach ( $result->value as $revId => $rowsByRole ) {
2215 foreach ( $rowsByRole as $role => $slotRow ) {
2216 if ( is_array( $slots ) && !in_array( $role, $slots ) ) {
2219 unset( $result->value[$revId][$role] );
2223 $result->value[$revId][$role] = (object)[
2224 'blob_data' => $slotRow->blob_data,
2225 'model_name' => $slotRow->model_name,
2252 int $flags = IDBAccessObject::READ_NORMAL,
2256 $db = $this->getDBConnectionRefForQueryFlags( $flags );
2257 $rev = $this->loadRevisionFromConds( $db, $conditions, $flags, $page, $options );
2259 $lb = $this->getDBLoadBalancer();
2264 && !( $flags & self::READ_LATEST )
2265 && $lb->hasStreamingReplicaServers()
2266 && $lb->hasOrMadeRecentPrimaryChanges()
2268 $flags = self::READ_LATEST;
2269 $dbw = $this->getDBConnectionRef(
DB_PRIMARY );
2270 $rev = $this->loadRevisionFromConds( $dbw, $conditions, $flags, $page, $options );
2293 int $flags = IDBAccessObject::READ_NORMAL,
2297 $row = $this->fetchRevisionRowFromConds( $db, $conditions, $flags, $options );
2299 return $this->newRevisionFromRow( $row, $flags, $page );
2314 $storeDomain = $this->loadBalancer->resolveDomainID( $this->wikiId );
2315 if ( $dbDomain === $storeDomain ) {
2319 throw new MWException(
"DB connection domain '$dbDomain' does not match '$storeDomain'" );
2338 int $flags = IDBAccessObject::READ_NORMAL,
2341 $this->checkDatabaseDomain( $db );
2343 $revQuery = $this->getQueryInfo( [
'page',
'user' ] );
2344 if ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING ) {
2345 $options[] =
'FOR UPDATE';
2385 $ret[
'tables'][] =
'revision';
2386 $ret[
'fields'] = array_merge( $ret[
'fields'], [
2397 $commentQuery = $this->commentStore->getJoin(
'rev_comment' );
2398 $ret[
'tables'] = array_merge( $ret[
'tables'], $commentQuery[
'tables'] );
2399 $ret[
'fields'] = array_merge( $ret[
'fields'], $commentQuery[
'fields'] );
2400 $ret[
'joins'] = array_merge( $ret[
'joins'], $commentQuery[
'joins'] );
2402 $actorQuery = $this->actorMigration->getJoin(
'rev_user' );
2403 $ret[
'tables'] = array_merge( $ret[
'tables'], $actorQuery[
'tables'] );
2404 $ret[
'fields'] = array_merge( $ret[
'fields'], $actorQuery[
'fields'] );
2405 $ret[
'joins'] = array_merge( $ret[
'joins'], $actorQuery[
'joins'] );
2407 if ( in_array(
'page', $options,
true ) ) {
2408 $ret[
'tables'][] =
'page';
2409 $ret[
'fields'] = array_merge( $ret[
'fields'], [
2417 $ret[
'joins'][
'page'] = [
'JOIN', [
'page_id = rev_page' ] ];
2420 if ( in_array(
'user', $options,
true ) ) {
2421 $ret[
'tables'][] =
'user';
2422 $ret[
'fields'] = array_merge( $ret[
'fields'], [
2425 $u = $actorQuery[
'fields'][
'rev_user'];
2426 $ret[
'joins'][
'user'] = [
'LEFT JOIN', [
"$u != 0",
"user_id = $u" ] ];
2429 if ( in_array(
'text', $options,
true ) ) {
2430 throw new InvalidArgumentException(
2431 'The `text` option is no longer supported in MediaWiki 1.35 and later.'
2466 $ret[
'keys'][
'rev_id'] =
'slot_revision_id';
2467 $ret[
'keys'][
'role_id'] =
'slot_role_id';
2469 $ret[
'tables'][] =
'slots';
2470 $ret[
'fields'] = array_merge( $ret[
'fields'], [
2477 if ( in_array(
'role', $options,
true ) ) {
2480 $ret[
'tables'][] =
'slot_roles';
2481 $ret[
'joins'][
'slot_roles'] = [
'LEFT JOIN', [
'slot_role_id = role_id' ] ];
2482 $ret[
'fields'][] =
'role_name';
2485 if ( in_array(
'content', $options,
true ) ) {
2486 $ret[
'keys'][
'model_id'] =
'content_model';
2488 $ret[
'tables'][] =
'content';
2489 $ret[
'fields'] = array_merge( $ret[
'fields'], [
2495 $ret[
'joins'][
'content'] = [
'JOIN', [
'slot_content_id = content_id' ] ];
2497 if ( in_array(
'model', $options,
true ) ) {
2500 $ret[
'tables'][] =
'content_models';
2501 $ret[
'joins'][
'content_models'] = [
'LEFT JOIN', [
'content_model = model_id' ] ];
2502 $ret[
'fields'][] =
'model_name';
2519 if ( !( $row instanceof stdClass ) ) {
2522 $queryInfo = $table ===
'archive' ? $this->getArchiveQueryInfo() : $this->getQueryInfo();
2523 foreach ( $queryInfo[
'fields'] as $alias => $field ) {
2524 $name = is_numeric( $alias ) ? $field : $alias;
2525 if ( !property_exists( $row, $name ) ) {
2550 $commentQuery = $this->commentStore->getJoin(
'ar_comment' );
2554 'archive_actor' =>
'actor'
2555 ] + $commentQuery[
'tables'],
2569 'ar_user' =>
'archive_actor.actor_user',
2570 'ar_user_text' =>
'archive_actor.actor_name',
2571 ] + $commentQuery[
'fields'],
2573 'archive_actor' => [
'JOIN',
'actor_id=ar_actor' ]
2574 ] + $commentQuery[
'joins'],
2598 [
'rev_id',
'rev_len' ],
2599 [
'rev_id' => $revIds ],
2603 foreach (
$res as $row ) {
2604 $revLens[$row->rev_id] = intval( $row->rev_len );
2619 $op = $dir ===
'next' ?
'>' :
'<';
2620 $sort = $dir ===
'next' ?
'ASC' :
'DESC';
2622 $revisionIdValue = $rev->
getId( $this->wikiId );
2624 if ( !$revisionIdValue || !$rev->
getPageId( $this->wikiId ) ) {
2634 list( $dbType, ) = DBAccessObjectUtils::getDBOptions( $flags );
2635 $db = $this->getDBConnectionRef( $dbType, [
'contributions' ] );
2637 $ts = $this->getTimestampFromId( $revisionIdValue, $flags );
2638 if ( $ts ===
false ) {
2640 $ts = $db->selectField(
'archive',
'ar_timestamp',
2641 [
'ar_rev_id' => $revisionIdValue ], __METHOD__ );
2642 if ( $ts ===
false ) {
2647 $dbts = $db->addQuotes( $db->timestamp( $ts ) );
2649 $revId = $db->selectField(
'revision',
'rev_id',
2651 'rev_page' => $rev->
getPageId( $this->wikiId ),
2652 "rev_timestamp $op $dbts OR (rev_timestamp = $dbts AND rev_id $op $revisionIdValue )"
2656 'ORDER BY' => [
"rev_timestamp $sort",
"rev_id $sort" ],
2657 'IGNORE INDEX' =>
'rev_timestamp',
2661 if ( $revId ===
false ) {
2665 return $this->getRevisionById( intval( $revId ) );
2683 return $this->getRelativeRevision( $rev, $flags,
'prev' );
2698 return $this->getRelativeRevision( $rev, $flags,
'next' );
2713 $this->checkDatabaseDomain( $db );
2715 if ( $rev->
getPageId( $this->wikiId ) ===
null ) {
2718 # Use page_latest if ID is not given
2719 if ( !$rev->
getId( $this->wikiId ) ) {
2721 'page',
'page_latest',
2722 [
'page_id' => $rev->
getPageId( $this->wikiId ) ],
2727 'revision',
'rev_id',
2728 [
'rev_page' => $rev->
getPageId( $this->wikiId ),
'rev_id < ' . $rev->
getId( $this->wikiId ) ],
2730 [
'ORDER BY' =>
'rev_id DESC' ]
2733 return intval( $prevId );
2749 if ( $id instanceof
Title ) {
2752 $flags = func_num_args() > 2 ? func_get_arg( 2 ) : 0;
2760 if ( $id ===
null || $id <= 0 ) {
2764 $db = $this->getDBConnectionRefForQueryFlags( $flags );
2767 $db->selectField(
'revision',
'rev_timestamp', [
'rev_id' => $id ], __METHOD__ );
2769 return ( $timestamp !==
false ) ? MWTimestamp::convert( TS_MW, $timestamp ) :
false;
2782 $this->checkDatabaseDomain( $db );
2785 [
'revCount' =>
'COUNT(*)' ],
2786 [
'rev_page' => $id ],
2790 return intval( $row->revCount );
2805 $id = $this->getArticleId( $page );
2807 return $this->countRevisionsByPageId( $db, $id );
2831 $this->checkDatabaseDomain( $db );
2841 'rev_user' =>
$revQuery[
'fields'][
'rev_user'],
2844 'rev_page' => $pageId,
2848 [
'ORDER BY' =>
'rev_timestamp ASC',
'LIMIT' => 50 ],
2851 foreach (
$res as $row ) {
2852 if ( $row->rev_user != $userId ) {
2873 $db = $this->getDBConnectionRef(
DB_REPLICA );
2874 $revIdPassed = $revId;
2875 $pageId = $this->getArticleId( $page );
2881 if ( $page instanceof
Title ) {
2882 $revId = $page->getLatestRevID();
2884 $pageRecord = $this->pageStore->getPageByReference( $page );
2885 if ( $pageRecord ) {
2886 $revId = $pageRecord->getLatest( $this->getWikiId() );
2892 $this->logger->warning(
2893 'No latest revision known for page {page} even though it exists with page ID {page_id}', [
2895 'page_id' => $pageId,
2896 'wiki_id' => $this->getWikiId() ?:
'local',
2905 $row = $this->cache->getWithSetCallback(
2907 $this->getRevisionRowCacheKey( $db, $pageId, $revId ),
2908 WANObjectCache::TTL_WEEK,
2909 function ( $curValue, &$ttl, array &$setOpts ) use (
2910 $db, $revId, &$fromCache
2912 $setOpts += Database::getCacheSetOptions( $db );
2913 $row = $this->fetchRevisionRowFromConds( $db, [
'rev_id' => intval( $revId ) ] );
2923 $title = $this->ensureRevisionRowMatchesPage( $row, $page, [
2924 'from_cache_flag' => $fromCache,
2925 'page_id_initial' => $pageId,
2926 'rev_id_used' => $revId,
2927 'rev_id_requested' => $revIdPassed,
2930 return $this->newRevisionFromRow( $row, 0,
$title, $fromCache );
2946 int $flags = IDBAccessObject::READ_NORMAL
2950 $page = $this->wikiId === WikiAwareEntity::LOCAL ? Title::castFromLinkTarget( $page ) :
null;
2952 return $this->newRevisionFromConds(
2954 'page_namespace' => $page->getNamespace(),
2955 'page_title' => $page->getDBkey()
2960 'ORDER BY' => [
'rev_timestamp ASC',
'rev_id ASC' ],
2961 'IGNORE INDEX' => [
'revision' =>
'rev_timestamp' ],
2978 return $this->cache->makeGlobalKey(
2979 self::ROW_CACHE_KEY,
2995 if ( $rev->getId( $this->wikiId ) ===
null ) {
2996 throw new InvalidArgumentException(
"Unsaved {$paramName} revision passed" );
2998 if ( $rev->getPageId( $this->wikiId ) !== $pageId ) {
2999 throw new InvalidArgumentException(
3000 "Revision {$rev->getId( $this->wikiId )} doesn't belong to page {$pageId}"
3026 $options = (array)$options;
3029 if ( in_array( self::INCLUDE_OLD, $options ) ) {
3032 if ( in_array( self::INCLUDE_NEW, $options ) ) {
3035 if ( in_array( self::INCLUDE_BOTH, $options ) ) {
3042 $oldTs =
$dbr->addQuotes(
$dbr->timestamp( $old->getTimestamp() ) );
3043 $conds[] =
"(rev_timestamp = {$oldTs} AND rev_id {$oldCmp} {$old->getId( $this->wikiId )}) " .
3044 "OR rev_timestamp > {$oldTs}";
3047 $newTs =
$dbr->addQuotes(
$dbr->timestamp( $new->getTimestamp() ) );
3048 $conds[] =
"(rev_timestamp = {$newTs} AND rev_id {$newCmp} {$new->getId( $this->wikiId )}) " .
3049 "OR rev_timestamp < {$newTs}";
3086 ?
string $order =
null,
3087 int $flags = IDBAccessObject::READ_NORMAL
3089 $this->assertRevisionParameter(
'old', $pageId, $old );
3090 $this->assertRevisionParameter(
'new', $pageId, $new );
3092 $options = (array)$options;
3093 $includeOld = in_array( self::INCLUDE_OLD, $options ) ||
3094 in_array( self::INCLUDE_BOTH, $options );
3095 $includeNew = in_array( self::INCLUDE_NEW, $options ) ||
3096 in_array( self::INCLUDE_BOTH, $options );
3102 if ( $old && $new && $new->getId( $this->wikiId ) === $old->getId( $this->wikiId ) ) {
3103 return $includeOld || $includeNew ? [ $new->getId( $this->wikiId ) ] : [];
3106 $db = $this->getDBConnectionRefForQueryFlags( $flags );
3107 $conds = array_merge(
3109 'rev_page' => $pageId,
3110 $db->bitAnd(
'rev_deleted', RevisionRecord::DELETED_TEXT ) .
' = 0'
3112 $this->getRevisionLimitConditions( $db, $old, $new, $options )
3116 if ( $order !==
null ) {
3117 $queryOptions[
'ORDER BY'] = [
"rev_timestamp $order",
"rev_id $order" ];
3119 if ( $max !==
null ) {
3120 $queryOptions[
'LIMIT'] = $max + 1;
3123 $values = $db->selectFieldValues(
3130 return array_map(
'intval', $values );
3162 $this->assertRevisionParameter(
'old', $pageId, $old );
3163 $this->assertRevisionParameter(
'new', $pageId, $new );
3164 $options = (array)$options;
3170 if ( $old && $new && $new->getId( $this->wikiId ) === $old->getId( $this->wikiId ) ) {
3171 if ( empty( $options ) ) {
3173 } elseif ( $performer ) {
3174 return [ $new->getUser( RevisionRecord::FOR_THIS_USER, $performer ) ];
3176 return [ $new->getUser() ];
3181 $conds = array_merge(
3183 'rev_page' => $pageId,
3184 $dbr->bitAnd(
'rev_deleted', RevisionRecord::DELETED_USER ) .
" = 0"
3186 $this->getRevisionLimitConditions(
$dbr, $old, $new, $options )
3189 $queryOpts = [
'DISTINCT' ];
3190 if ( $max !==
null ) {
3191 $queryOpts[
'LIMIT'] = $max + 1;
3194 $actorQuery = $this->actorMigration->getJoin(
'rev_user' );
3195 return array_map(
function ( $row ) {
3196 return $this->actorStore->newActorFromRowFields(
3198 $row->rev_user_text,
3201 }, iterator_to_array(
$dbr->select(
3202 array_merge( [
'revision' ], $actorQuery[
'tables'] ),
3203 $actorQuery[
'fields'],
3206 $actorQuery[
'joins']
3241 return count( $this->getAuthorsBetween( $pageId, $old, $new, $performer, $max, $options ) );
3271 $this->assertRevisionParameter(
'old', $pageId, $old );
3272 $this->assertRevisionParameter(
'new', $pageId, $new );
3278 if ( $old && $new && $new->getId( $this->wikiId ) === $old->getId( $this->wikiId ) ) {
3283 $conds = array_merge(
3285 'rev_page' => $pageId,
3286 $dbr->bitAnd(
'rev_deleted', RevisionRecord::DELETED_TEXT ) .
" = 0"
3288 $this->getRevisionLimitConditions(
$dbr, $old, $new, $options )
3290 if ( $max !==
null ) {
3291 return $dbr->selectRowCount(
'revision',
'1',
3294 [
'LIMIT' => $max + 1 ]
3297 return (
int)
$dbr->selectField(
'revision',
'count(*)', $conds, __METHOD__ );
3316 $revision->assertWiki( $this->wikiId );
3317 $db = $this->getDBConnectionRef(
DB_REPLICA );
3319 $subquery = $db->buildSelectSubquery(
3322 [
'rev_page' => $revision->
getPageId( $this->wikiId ) ],
3326 'rev_timestamp DESC',
3330 'LIMIT' => $searchLimit,
3338 $revisionRow = $db->selectRow(
3339 [
'recent_revs' => $subquery ],
3341 [
'rev_sha1' => $revision->
getSha1() ],
3345 return $revisionRow ? $this->newRevisionFromRow( $revisionRow ) : null;
3355class_alias( RevisionStore::class,
'MediaWiki\Storage\RevisionStore' );
wfBacktrace( $raw=null)
Get a debug backtrace as a string.
wfDeprecatedMsg( $msg, $version=false, $component=false, $callerOffset=2)
Log a deprecation warning with arbitrary message text.
if(ini_get('mbstring.func_overload')) if(!defined('MW_ENTRY_POINT'))
Pre-config setup: Before loading LocalSettings.php.
This is not intended to be a long-term part of MediaWiki; it will be deprecated and removed once acto...
Helper class for DAO classes.
Content object implementation representing unknown content.
Library for creating and parsing MW-style timestamps.
Exception thrown when an unregistered content model is requested.
Immutable value object representing a page identity.
Utility class for creating new RC entries.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Represents a title within MediaWiki.
Multi-datacenter aware caching interface.
Base interface for content objects.
Interface for database access objects.
Interface for objects (potentially) representing an editable wiki page.
getId( $wikiId=self::LOCAL)
Returns the page ID.