Merge "Remove empty RevisionRecordTest"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Tue, 19 Dec 2017 23:18:47 +0000 (23:18 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 19 Dec 2017 23:18:47 +0000 (23:18 +0000)
42 files changed:
RELEASE-NOTES-1.31
docs/hooks.txt
includes/Revision.php
includes/ServiceWiring.php
includes/Storage/SqlBlobStore.php
includes/actions/HistoryAction.php
includes/cache/MessageCache.php
includes/installer/i18n/eu.json
includes/libs/rdbms/database/Database.php
includes/libs/rdbms/loadbalancer/ILoadBalancer.php
includes/libs/rdbms/loadbalancer/LoadBalancer.php
includes/page/WikiPage.php
includes/parser/Parser.php
includes/resourceloader/ResourceLoaderWikiModule.php
includes/specials/SpecialNewpages.php
languages/i18n/ast.json
languages/i18n/be-tarask.json
languages/i18n/bg.json
languages/i18n/ce.json
languages/i18n/de.json
languages/i18n/diq.json
languages/i18n/es.json
languages/i18n/fi.json
languages/i18n/fr.json
languages/i18n/he.json
languages/i18n/hr.json
languages/i18n/it.json
languages/i18n/ja.json
languages/i18n/nn.json
languages/i18n/pt-br.json
languages/i18n/ru.json
maintenance/oracle/archives/patch-auto_increment_triggers.sql
maintenance/oracle/archives/patch-externallinks-el_index_60.sql
maintenance/oracle/tables.sql
maintenance/oracle/user.sql
resources/src/mediawiki.language/specialcharacters.json
resources/src/mediawiki.skinning/elements.css
resources/src/mediawiki/mediawiki.debug.less
resources/src/mediawiki/mediawiki.feedback.js
tests/phpunit/includes/MediaWikiServicesTest.php
tests/phpunit/includes/RevisionDbTestBase.php
tests/phpunit/includes/RevisionTest.php

index 67026f4..1a1a9f7 100644 (file)
@@ -123,9 +123,6 @@ changes to languages because of Phabricator reports.
 * The Block class will no longer accept usable-but-missing usernames for
   'byText' or ->setBlocker(). Callers should either ensure the blocker exists
   locally or use a new interwiki-format username like "iw>Example".
-* The RevisionInsertComplete hook is now deprecated, use RevisionRecordInserted instead.
-  RevisionInsertComplete is still called, but the second and third parameter will always be null.
-  Hard deprecation is scheduled for 1.32.
 * The following methods that get and set ParserOutput state are deprecated.
   Callers should use the new stateless $options parameter to
   ParserOutput::getText() instead.
index 1f4a5f4..ee38ea9 100644 (file)
@@ -2810,14 +2810,14 @@ called after the addition of 'qunit' and MediaWiki testing resources.
   added to any module.
 &$ResourceLoader: object
 
-'RevisionRecordInserted': Called after a revision is inserted into the database.
-$revisionRecord: the RevisionRecord that has just been inserted.
-
-'RevisionInsertComplete': DEPRECATED! Use RevisionRecordInserted hook instead.
-Called after a revision is inserted into the database.
-$revision: the Revision
-$data: DEPRECATED! Always null!
-$flags: DEPRECATED! Always null!
+'RevisionInsertComplete': Called after a revision is inserted into the database.
+&$revision: the Revision
+$data: the data stored in old_text.  The meaning depends on $flags: if external
+  is set, it's the URL of the revision text in external storage; otherwise,
+  it's the revision text itself.  In either case, if gzip is set, the revision
+  text is gzipped.
+$flags: a comma-delimited list of strings representing the options used.  May
+  include: utf8 (this will always be set for new revisions); gzip; external.
 
 'SearchableNamespaces': An option to modify which namespaces are searchable.
 &$arr: Array of namespaces ($nsId => $name) which will be used.
index ea73a61..25c89c2 100644 (file)
  * @file
  */
 
-use MediaWiki\Storage\MutableRevisionRecord;
-use MediaWiki\Storage\RevisionAccessException;
-use MediaWiki\Storage\RevisionRecord;
-use MediaWiki\Storage\RevisionStore;
-use MediaWiki\Storage\RevisionStoreRecord;
-use MediaWiki\Storage\SlotRecord;
-use MediaWiki\Storage\SqlBlobStore;
-use MediaWiki\User\UserIdentityValue;
+use Wikimedia\Rdbms\Database;
 use Wikimedia\Rdbms\IDatabase;
 use MediaWiki\Linker\LinkTarget;
 use MediaWiki\MediaWikiServices;
@@ -35,50 +28,78 @@ use Wikimedia\Rdbms\ResultWrapper;
 use Wikimedia\Rdbms\FakeResultWrapper;
 
 /**
- * @deprecated since 1.31, use RevisionRecord, RevisionStore, and BlobStore instead.
+ * @todo document
  */
 class Revision implements IDBAccessObject {
-
-       /** @var RevisionRecord */
-       protected $mRecord;
+       /** @var int|null */
+       protected $mId;
+       /** @var int|null */
+       protected $mPage;
+       /** @var string */
+       protected $mUserText;
+       /** @var string */
+       protected $mOrigUserText;
+       /** @var int */
+       protected $mUser;
+       /** @var bool */
+       protected $mMinorEdit;
+       /** @var string */
+       protected $mTimestamp;
+       /** @var int */
+       protected $mDeleted;
+       /** @var int */
+       protected $mSize;
+       /** @var string */
+       protected $mSha1;
+       /** @var int */
+       protected $mParentId;
+       /** @var string */
+       protected $mComment;
+       /** @var string */
+       protected $mText;
+       /** @var int */
+       protected $mTextId;
+       /** @var int */
+       protected $mUnpatrolled;
+
+       /** @var stdClass|null */
+       protected $mTextRow;
+
+       /**  @var null|Title */
+       protected $mTitle;
+       /** @var bool */
+       protected $mCurrent;
+       /** @var string */
+       protected $mContentModel;
+       /** @var string */
+       protected $mContentFormat;
+
+       /** @var Content|null|bool */
+       protected $mContent;
+       /** @var null|ContentHandler */
+       protected $mContentHandler;
+
+       /** @var int */
+       protected $mQueryFlags = 0;
+       /** @var bool Used for cached values to reload user text and rev_deleted */
+       protected $mRefreshMutableFields = false;
+       /** @var string Wiki ID; false means the current wiki */
+       protected $mWiki = false;
 
        // Revision deletion constants
-       const DELETED_TEXT = RevisionRecord::DELETED_TEXT;
-       const DELETED_COMMENT = RevisionRecord::DELETED_COMMENT;
-       const DELETED_USER = RevisionRecord::DELETED_USER;
-       const DELETED_RESTRICTED = RevisionRecord::DELETED_RESTRICTED;
-       const SUPPRESSED_USER = RevisionRecord::SUPPRESSED_USER;
-       const SUPPRESSED_ALL = RevisionRecord::SUPPRESSED_ALL;
+       const DELETED_TEXT = 1;
+       const DELETED_COMMENT = 2;
+       const DELETED_USER = 4;
+       const DELETED_RESTRICTED = 8;
+       const SUPPRESSED_USER = 12; // convenience
+       const SUPPRESSED_ALL = 15; // convenience
 
        // Audience options for accessors
-       const FOR_PUBLIC = RevisionRecord::FOR_PUBLIC;
-       const FOR_THIS_USER = RevisionRecord::FOR_THIS_USER;
-       const RAW = RevisionRecord::RAW;
-
-       const TEXT_CACHE_GROUP = SqlBlobStore::TEXT_CACHE_GROUP;
+       const FOR_PUBLIC = 1;
+       const FOR_THIS_USER = 2;
+       const RAW = 3;
 
-       /**
-        * @return RevisionStore
-        */
-       protected static function getRevisionStore() {
-               return MediaWikiServices::getInstance()->getRevisionStore();
-       }
-
-       /**
-        * @return SqlBlobStore
-        */
-       protected static function getBlobStore() {
-               $store = MediaWikiServices::getInstance()->getBlobStore();
-
-               if ( !$store instanceof SqlBlobStore ) {
-                       throw new RuntimeException(
-                               'The backwards compatibility code in Revision currently requires the BlobStore '
-                               . 'service to be an SqlBlobStore instance, but it is a ' . get_class( $store )
-                       );
-               }
-
-               return $store;
-       }
+       const TEXT_CACHE_GROUP = 'revisiontext:10'; // process cache name and max key count
 
        /**
         * Load a page revision from a given revision ID number.
@@ -93,8 +114,7 @@ class Revision implements IDBAccessObject {
         * @return Revision|null
         */
        public static function newFromId( $id, $flags = 0 ) {
-               $rec = self::getRevisionStore()->getRevisionById( $id, $flags );
-               return $rec === null ? null : new Revision( $rec, $flags );
+               return self::newFromConds( [ 'rev_id' => intval( $id ) ], $flags );
        }
 
        /**
@@ -112,8 +132,20 @@ class Revision implements IDBAccessObject {
         * @return Revision|null
         */
        public static function newFromTitle( LinkTarget $linkTarget, $id = 0, $flags = 0 ) {
-               $rec = self::getRevisionStore()->getRevisionByTitle( $linkTarget, $id, $flags );
-               return $rec === null ? null : new Revision( $rec, $flags );
+               $conds = [
+                       'page_namespace' => $linkTarget->getNamespace(),
+                       'page_title' => $linkTarget->getDBkey()
+               ];
+               if ( $id ) {
+                       // Use the specified ID
+                       $conds['rev_id'] = $id;
+                       return self::newFromConds( $conds, $flags );
+               } else {
+                       // Use a join to get the latest revision
+                       $conds[] = 'rev_id=page_latest';
+                       $db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_REPLICA );
+                       return self::loadFromConds( $db, $conds, $flags );
+               }
        }
 
        /**
@@ -131,13 +163,22 @@ class Revision implements IDBAccessObject {
         * @return Revision|null
         */
        public static function newFromPageId( $pageId, $revId = 0, $flags = 0 ) {
-               $rec = self::getRevisionStore()->getRevisionByPageId( $pageId, $revId, $flags );
-               return $rec === null ? null : new Revision( $rec, $flags );
+               $conds = [ 'page_id' => $pageId ];
+               if ( $revId ) {
+                       $conds['rev_id'] = $revId;
+                       return self::newFromConds( $conds, $flags );
+               } else {
+                       // Use a join to get the latest revision
+                       $conds[] = 'rev_id = page_latest';
+                       $db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_REPLICA );
+                       return self::loadFromConds( $db, $conds, $flags );
+               }
        }
 
        /**
         * Make a fake revision object from an archive table row. This is queried
         * for permissions or even inserted (as in Special:Undelete)
+        * @todo FIXME: Should be a subclass for RevisionDelete. [TS]
         *
         * @param object $row
         * @param array $overrides
@@ -146,45 +187,68 @@ class Revision implements IDBAccessObject {
         * @return Revision
         */
        public static function newFromArchiveRow( $row, $overrides = [] ) {
-               $rec = self::getRevisionStore()->newRevisionFromArchiveRow( $row, 0, null, $overrides );
-               return new Revision( $rec );
+               global $wgContentHandlerUseDB;
+
+               $attribs = $overrides + [
+                       'page'       => isset( $row->ar_page_id ) ? $row->ar_page_id : null,
+                       'id'         => isset( $row->ar_rev_id ) ? $row->ar_rev_id : null,
+                       'comment'    => CommentStore::newKey( 'ar_comment' )
+                               // Legacy because $row may have come from self::selectArchiveFields()
+                               ->getCommentLegacy( wfGetDB( DB_REPLICA ), $row, true )->text,
+                       'user'       => $row->ar_user,
+                       'user_text'  => $row->ar_user_text,
+                       'timestamp'  => $row->ar_timestamp,
+                       'minor_edit' => $row->ar_minor_edit,
+                       'text_id'    => isset( $row->ar_text_id ) ? $row->ar_text_id : null,
+                       'deleted'    => $row->ar_deleted,
+                       'len'        => $row->ar_len,
+                       'sha1'       => isset( $row->ar_sha1 ) ? $row->ar_sha1 : null,
+                       'content_model'   => isset( $row->ar_content_model ) ? $row->ar_content_model : null,
+                       'content_format'  => isset( $row->ar_content_format ) ? $row->ar_content_format : null,
+               ];
+
+               if ( !$wgContentHandlerUseDB ) {
+                       unset( $attribs['content_model'] );
+                       unset( $attribs['content_format'] );
+               }
+
+               if ( !isset( $attribs['title'] )
+                       && isset( $row->ar_namespace )
+                       && isset( $row->ar_title )
+               ) {
+                       $attribs['title'] = Title::makeTitle( $row->ar_namespace, $row->ar_title );
+               }
+
+               if ( isset( $row->ar_text ) && !$row->ar_text_id ) {
+                       // Pre-1.5 ar_text row
+                       $attribs['text'] = self::getRevisionText( $row, 'ar_' );
+                       if ( $attribs['text'] === false ) {
+                               throw new MWException( 'Unable to load text from archive row (possibly T24624)' );
+                       }
+               }
+               return new self( $attribs );
        }
 
        /**
         * @since 1.19
         *
-        * MCR migration note: replaced by RevisionStore::newRevisionFromRow(). Note that
-        * newFromRow() also accepts arrays, while newRevisionFromRow() does not. Instead,
-        * a MutableRevisionRecord should be constructed directly. RevisionStore::newRevisionFromArray()
-        * can be used as a temporary replacement, but should be avoided.
-        *
-        * @param object|array $row
+        * @param object $row
         * @return Revision
         */
        public static function newFromRow( $row ) {
-               if ( is_array( $row ) ) {
-                       $rec = self::getRevisionStore()->newMutableRevisionFromArray( $row );
-               } else {
-                       $rec = self::getRevisionStore()->newRevisionFromRow( $row );
-               }
-
-               return new Revision( $rec );
+               return new self( $row );
        }
 
        /**
         * Load a page revision from a given revision ID number.
         * Returns null if no such revision can be found.
         *
-        * @deprecated since 1.31, use RevisionStore::getRevisionById() instead.
-        *
         * @param IDatabase $db
         * @param int $id
         * @return Revision|null
         */
        public static function loadFromId( $db, $id ) {
-               wfDeprecated( __METHOD__, '1.31' ); // no known callers
-               $rec = self::getRevisionStore()->loadRevisionFromId( $db, $id );
-               return $rec === null ? null : new Revision( $rec );
+               return self::loadFromConds( $db, [ 'rev_id' => intval( $id ) ] );
        }
 
        /**
@@ -192,16 +256,19 @@ class Revision implements IDBAccessObject {
         * that's attached to a given page. If not attached
         * to that page, will return null.
         *
-        * @deprecated since 1.31, use RevisionStore::getRevisionByPageId() instead.
-        *
         * @param IDatabase $db
         * @param int $pageid
         * @param int $id
         * @return Revision|null
         */
        public static function loadFromPageId( $db, $pageid, $id = 0 ) {
-               $rec = self::getRevisionStore()->loadRevisionFromPageId( $db, $pageid, $id );
-               return $rec === null ? null : new Revision( $rec );
+               $conds = [ 'rev_page' => intval( $pageid ), 'page_id' => intval( $pageid ) ];
+               if ( $id ) {
+                       $conds['rev_id'] = intval( $id );
+               } else {
+                       $conds[] = 'rev_id=page_latest';
+               }
+               return self::loadFromConds( $db, $conds );
        }
 
        /**
@@ -209,16 +276,24 @@ class Revision implements IDBAccessObject {
         * that's attached to a given page. If not attached
         * to that page, will return null.
         *
-        * @deprecated since 1.31, use RevisionStore::getRevisionByTitle() instead.
-        *
         * @param IDatabase $db
         * @param Title $title
         * @param int $id
         * @return Revision|null
         */
        public static function loadFromTitle( $db, $title, $id = 0 ) {
-               $rec = self::getRevisionStore()->loadRevisionFromTitle( $db, $title, $id );
-               return $rec === null ? null : new Revision( $rec );
+               if ( $id ) {
+                       $matchId = intval( $id );
+               } else {
+                       $matchId = 'page_latest';
+               }
+               return self::loadFromConds( $db,
+                       [
+                               "rev_id=$matchId",
+                               'page_namespace' => $title->getNamespace(),
+                               'page_title' => $title->getDBkey()
+                       ]
+               );
        }
 
        /**
@@ -226,17 +301,73 @@ class Revision implements IDBAccessObject {
         * WARNING: Timestamps may in some circumstances not be unique,
         * so this isn't the best key to use.
         *
-        * @deprecated since 1.31, use RevisionStore::loadRevisionFromTimestamp() instead.
-        *
         * @param IDatabase $db
         * @param Title $title
         * @param string $timestamp
         * @return Revision|null
         */
        public static function loadFromTimestamp( $db, $title, $timestamp ) {
-               // XXX: replace loadRevisionFromTimestamp by getRevisionByTimestamp?
-               $rec = self::getRevisionStore()->loadRevisionFromTimestamp( $db, $title, $timestamp );
-               return $rec === null ? null : new Revision( $rec );
+               return self::loadFromConds( $db,
+                       [
+                               'rev_timestamp' => $db->timestamp( $timestamp ),
+                               'page_namespace' => $title->getNamespace(),
+                               'page_title' => $title->getDBkey()
+                       ]
+               );
+       }
+
+       /**
+        * Given a set of conditions, fetch a revision
+        *
+        * This method is used then a revision ID is qualified and
+        * will incorporate some basic replica DB/master fallback logic
+        *
+        * @param array $conditions
+        * @param int $flags (optional)
+        * @return Revision|null
+        */
+       private static function newFromConds( $conditions, $flags = 0 ) {
+               $db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_REPLICA );
+
+               $rev = self::loadFromConds( $db, $conditions, $flags );
+               // Make sure new pending/committed revision are visibile later on
+               // within web requests to certain avoid bugs like T93866 and T94407.
+               if ( !$rev
+                       && !( $flags & self::READ_LATEST )
+                       && wfGetLB()->getServerCount() > 1
+                       && wfGetLB()->hasOrMadeRecentMasterChanges()
+               ) {
+                       $flags = self::READ_LATEST;
+                       $db = wfGetDB( DB_MASTER );
+                       $rev = self::loadFromConds( $db, $conditions, $flags );
+               }
+
+               if ( $rev ) {
+                       $rev->mQueryFlags = $flags;
+               }
+
+               return $rev;
+       }
+
+       /**
+        * Given a set of conditions, fetch a revision from
+        * the given database connection.
+        *
+        * @param IDatabase $db
+        * @param array $conditions
+        * @param int $flags (optional)
+        * @return Revision|null
+        */
+       private static function loadFromConds( $db, $conditions, $flags = 0 ) {
+               $row = self::fetchFromConds( $db, $conditions, $flags );
+               if ( $row ) {
+                       $rev = new Revision( $row );
+                       $rev->mWiki = $db->getDomainID();
+
+                       return $rev;
+               }
+
+               return null;
        }
 
        /**
@@ -246,18 +377,52 @@ class Revision implements IDBAccessObject {
         *
         * @param LinkTarget $title
         * @return ResultWrapper
-        * @deprecated Since 1.28, no callers in core nor in known extensions. No-op since 1.31.
+        * @deprecated Since 1.28
         */
        public static function fetchRevision( LinkTarget $title ) {
-               wfDeprecated( __METHOD__, '1.31' );
-               return new FakeResultWrapper( [] );
+               $row = self::fetchFromConds(
+                       wfGetDB( DB_REPLICA ),
+                       [
+                               'rev_id=page_latest',
+                               'page_namespace' => $title->getNamespace(),
+                               'page_title' => $title->getDBkey()
+                       ]
+               );
+
+               return new FakeResultWrapper( $row ? [ $row ] : [] );
+       }
+
+       /**
+        * Given a set of conditions, return a ResultWrapper
+        * which will return matching database rows with the
+        * fields necessary to build Revision objects.
+        *
+        * @param IDatabase $db
+        * @param array $conditions
+        * @param int $flags (optional)
+        * @return stdClass
+        */
+       private static function fetchFromConds( $db, $conditions, $flags = 0 ) {
+               $revQuery = self::getQueryInfo( [ 'page', 'user' ] );
+               $options = [];
+               if ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING ) {
+                       $options[] = 'FOR UPDATE';
+               }
+               return $db->selectRow(
+                       $revQuery['tables'],
+                       $revQuery['fields'],
+                       $conditions,
+                       __METHOD__,
+                       $options,
+                       $revQuery['joins']
+               );
        }
 
        /**
         * Return the value of a select() JOIN conds array for the user table.
         * This will get user table rows for logged-in users.
         * @since 1.19
-        * @deprecated since 1.31, use RevisionStore::getQueryInfo( [ 'user' ] ) instead.
+        * @deprecated since 1.31, use self::getQueryInfo( [ 'user' ] ) instead.
         * @return array
         */
        public static function userJoinCond() {
@@ -269,7 +434,7 @@ class Revision implements IDBAccessObject {
         * Return the value of a select() page conds array for the page table.
         * This will assure that the revision(s) are not orphaned from live pages.
         * @since 1.19
-        * @deprecated since 1.31, use RevisionStore::getQueryInfo( [ 'page' ] ) instead.
+        * @deprecated since 1.31, use self::getQueryInfo( [ 'page' ] ) instead.
         * @return array
         */
        public static function pageJoinCond() {
@@ -280,7 +445,7 @@ class Revision implements IDBAccessObject {
        /**
         * Return the list of revision fields that should be selected to create
         * a new revision.
-        * @deprecated since 1.31, use RevisionStore::getQueryInfo() instead.
+        * @deprecated since 1.31, use self::getQueryInfo() instead.
         * @return array
         */
        public static function selectFields() {
@@ -315,7 +480,7 @@ class Revision implements IDBAccessObject {
        /**
         * Return the list of revision fields that should be selected to create
         * a new revision from an archive row.
-        * @deprecated since 1.31, use RevisionStore::getArchiveQueryInfo() instead.
+        * @deprecated since 1.31, use self::getArchiveQueryInfo() instead.
         * @return array
         */
        public static function selectArchiveFields() {
@@ -351,7 +516,7 @@ class Revision implements IDBAccessObject {
        /**
         * Return the list of text fields that should be selected to read the
         * revision text
-        * @deprecated since 1.31, use RevisionStore::getQueryInfo( [ 'text' ] ) instead.
+        * @deprecated since 1.31, use self::getQueryInfo( [ 'text' ] ) instead.
         * @return array
         */
        public static function selectTextFields() {
@@ -364,7 +529,7 @@ class Revision implements IDBAccessObject {
 
        /**
         * Return the list of page fields that should be selected from page table
-        * @deprecated since 1.31, use RevisionStore::getQueryInfo( [ 'page' ] ) instead.
+        * @deprecated since 1.31, use self::getQueryInfo( [ 'page' ] ) instead.
         * @return array
         */
        public static function selectPageFields() {
@@ -381,7 +546,7 @@ class Revision implements IDBAccessObject {
 
        /**
         * Return the list of user fields that should be selected from user table
-        * @deprecated since 1.31, use RevisionStore::getQueryInfo( [ 'user' ] ) instead.
+        * @deprecated since 1.31, use self::getQueryInfo( [ 'user' ] ) instead.
         * @return array
         */
        public static function selectUserFields() {
@@ -393,7 +558,6 @@ class Revision implements IDBAccessObject {
         * Return the tables, fields, and join conditions to be selected to create
         * a new revision object.
         * @since 1.31
-        * @deprecated since 1.31, use RevisionStore::getQueryInfo() instead.
         * @param array $options Any combination of the following strings
         *  - 'page': Join with the page table, and select fields to identify the page
         *  - 'user': Join with the user table, and select the user name
@@ -404,21 +568,104 @@ class Revision implements IDBAccessObject {
         *   - joins: (array) to include in the `$join_conds` to `IDatabase->select()`
         */
        public static function getQueryInfo( $options = [] ) {
-               return self::getRevisionStore()->getQueryInfo( $options );
+               global $wgContentHandlerUseDB;
+
+               $commentQuery = CommentStore::newKey( 'rev_comment' )->getJoin();
+               $ret = [
+                       'tables' => [ 'revision' ] + $commentQuery['tables'],
+                       'fields' => [
+                               'rev_id',
+                               'rev_page',
+                               'rev_text_id',
+                               'rev_timestamp',
+                               'rev_user_text',
+                               'rev_user',
+                               'rev_minor_edit',
+                               'rev_deleted',
+                               'rev_len',
+                               'rev_parent_id',
+                               'rev_sha1',
+                       ] + $commentQuery['fields'],
+                       'joins' => $commentQuery['joins'],
+               ];
+
+               if ( $wgContentHandlerUseDB ) {
+                       $ret['fields'][] = 'rev_content_format';
+                       $ret['fields'][] = 'rev_content_model';
+               }
+
+               if ( in_array( 'page', $options, true ) ) {
+                       $ret['tables'][] = 'page';
+                       $ret['fields'] = array_merge( $ret['fields'], [
+                               'page_namespace',
+                               'page_title',
+                               'page_id',
+                               'page_latest',
+                               'page_is_redirect',
+                               'page_len',
+                       ] );
+                       $ret['joins']['page'] = [ 'INNER JOIN', [ 'page_id = rev_page' ] ];
+               }
+
+               if ( in_array( 'user', $options, true ) ) {
+                       $ret['tables'][] = 'user';
+                       $ret['fields'] = array_merge( $ret['fields'], [
+                               'user_name',
+                       ] );
+                       $ret['joins']['user'] = [ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] ];
+               }
+
+               if ( in_array( 'text', $options, true ) ) {
+                       $ret['tables'][] = 'text';
+                       $ret['fields'] = array_merge( $ret['fields'], [
+                               'old_text',
+                               'old_flags'
+                       ] );
+                       $ret['joins']['text'] = [ 'INNER JOIN', [ 'rev_text_id=old_id' ] ];
+               }
+
+               return $ret;
        }
 
        /**
         * Return the tables, fields, and join conditions to be selected to create
         * a new archived revision object.
         * @since 1.31
-        * @deprecated since 1.31, use RevisionStore::getArchiveQueryInfo() instead.
         * @return array With three keys:
         *   - tables: (string[]) to include in the `$table` to `IDatabase->select()`
         *   - fields: (string[]) to include in the `$vars` to `IDatabase->select()`
         *   - joins: (array) to include in the `$join_conds` to `IDatabase->select()`
         */
        public static function getArchiveQueryInfo() {
-               return self::getRevisionStore()->getArchiveQueryInfo();
+               global $wgContentHandlerUseDB;
+
+               $commentQuery = CommentStore::newKey( 'ar_comment' )->getJoin();
+               $ret = [
+                       'tables' => [ 'archive' ] + $commentQuery['tables'],
+                       'fields' => [
+                               'ar_id',
+                               'ar_page_id',
+                               'ar_rev_id',
+                               'ar_text',
+                               'ar_text_id',
+                               'ar_timestamp',
+                               'ar_user_text',
+                               'ar_user',
+                               'ar_minor_edit',
+                               'ar_deleted',
+                               'ar_len',
+                               'ar_parent_id',
+                               'ar_sha1',
+                       ] + $commentQuery['fields'],
+                       'joins' => $commentQuery['joins'],
+               ];
+
+               if ( $wgContentHandlerUseDB ) {
+                       $ret['fields'][] = 'ar_content_format';
+                       $ret['fields'][] = 'ar_content_model';
+               }
+
+               return $ret;
        }
 
        /**
@@ -428,49 +675,203 @@ class Revision implements IDBAccessObject {
         * @return array
         */
        public static function getParentLengths( $db, array $revIds ) {
-               return self::getRevisionStore()->listRevisionSizes( $db, $revIds );
+               $revLens = [];
+               if ( !$revIds ) {
+                       return $revLens; // empty
+               }
+               $res = $db->select( 'revision',
+                       [ 'rev_id', 'rev_len' ],
+                       [ 'rev_id' => $revIds ],
+                       __METHOD__ );
+               foreach ( $res as $row ) {
+                       $revLens[$row->rev_id] = $row->rev_len;
+               }
+               return $revLens;
        }
 
        /**
-        * @param object|array|RevisionRecord $row Either a database row or an array
-        * @param int $queryFlags
-        * @param Title|null $title
-        *
+        * @param object|array $row Either a database row or an array
+        * @throws MWException
         * @access private
         */
-       function __construct( $row, $queryFlags = 0, Title $title = null ) {
-               global $wgUser;
-
-               if ( $row instanceof RevisionRecord ) {
-                       $this->mRecord = $row;
+       public function __construct( $row ) {
+               if ( is_object( $row ) ) {
+                       $this->constructFromDbRowObject( $row );
                } elseif ( is_array( $row ) ) {
-                       if ( !isset( $row['user'] ) && !isset( $row['user_text'] ) ) {
-                               $row['user'] = $wgUser;
-                       }
+                       $this->constructFromRowArray( $row );
+               } else {
+                       throw new MWException( 'Revision constructor passed invalid row format.' );
+               }
+               $this->mUnpatrolled = null;
+       }
 
-                       $this->mRecord = self::getRevisionStore()->newMutableRevisionFromArray(
-                               $row,
-                               $queryFlags,
-                               $title
-                       );
-               } elseif ( is_object( $row ) ) {
-                       $this->mRecord = self::getRevisionStore()->newRevisionFromRow(
-                               $row,
-                               $queryFlags,
-                               $title
-                       );
+       /**
+        * @param object $row
+        */
+       private function constructFromDbRowObject( $row ) {
+               $this->mId = intval( $row->rev_id );
+               $this->mPage = intval( $row->rev_page );
+               $this->mTextId = intval( $row->rev_text_id );
+               $this->mComment = CommentStore::newKey( 'rev_comment' )
+                       // Legacy because $row may have come from self::selectFields()
+                       ->getCommentLegacy( wfGetDB( DB_REPLICA ), $row, true )->text;
+               $this->mUser = intval( $row->rev_user );
+               $this->mMinorEdit = intval( $row->rev_minor_edit );
+               $this->mTimestamp = $row->rev_timestamp;
+               $this->mDeleted = intval( $row->rev_deleted );
+
+               if ( !isset( $row->rev_parent_id ) ) {
+                       $this->mParentId = null;
                } else {
-                       throw new InvalidArgumentException(
-                               '$row must be a row object, an associative array, or a RevisionRecord'
-                       );
+                       $this->mParentId = intval( $row->rev_parent_id );
                }
+
+               if ( !isset( $row->rev_len ) ) {
+                       $this->mSize = null;
+               } else {
+                       $this->mSize = intval( $row->rev_len );
+               }
+
+               if ( !isset( $row->rev_sha1 ) ) {
+                       $this->mSha1 = null;
+               } else {
+                       $this->mSha1 = $row->rev_sha1;
+               }
+
+               if ( isset( $row->page_latest ) ) {
+                       $this->mCurrent = ( $row->rev_id == $row->page_latest );
+                       $this->mTitle = Title::newFromRow( $row );
+               } else {
+                       $this->mCurrent = false;
+                       $this->mTitle = null;
+               }
+
+               if ( !isset( $row->rev_content_model ) ) {
+                       $this->mContentModel = null; # determine on demand if needed
+               } else {
+                       $this->mContentModel = strval( $row->rev_content_model );
+               }
+
+               if ( !isset( $row->rev_content_format ) ) {
+                       $this->mContentFormat = null; # determine on demand if needed
+               } else {
+                       $this->mContentFormat = strval( $row->rev_content_format );
+               }
+
+               // Lazy extraction...
+               $this->mText = null;
+               if ( isset( $row->old_text ) ) {
+                       $this->mTextRow = $row;
+               } else {
+                       // 'text' table row entry will be lazy-loaded
+                       $this->mTextRow = null;
+               }
+
+               // Use user_name for users and rev_user_text for IPs...
+               $this->mUserText = null; // lazy load if left null
+               if ( $this->mUser == 0 ) {
+                       $this->mUserText = $row->rev_user_text; // IP user
+               } elseif ( isset( $row->user_name ) ) {
+                       $this->mUserText = $row->user_name; // logged-in user
+               }
+               $this->mOrigUserText = $row->rev_user_text;
        }
 
        /**
-        * @return RevisionRecord
+        * @param array $row
+        *
+        * @throws MWException
         */
-       public function getRevisionRecord() {
-               return $this->mRecord;
+       private function constructFromRowArray( array $row ) {
+               // Build a new revision to be saved...
+               global $wgUser; // ugh
+
+               # if we have a content object, use it to set the model and type
+               if ( !empty( $row['content'] ) ) {
+                       if ( !( $row['content'] instanceof Content ) ) {
+                               throw new MWException( '`content` field must contain a Content object.' );
+                       }
+
+                       // @todo when is that set? test with external store setup! check out insertOn() [dk]
+                       if ( !empty( $row['text_id'] ) ) {
+                               throw new MWException( "Text already stored in external store (id {$row['text_id']}), " .
+                                       "can't serialize content object" );
+                       }
+
+                       $row['content_model'] = $row['content']->getModel();
+                       # note: mContentFormat is initializes later accordingly
+                       # note: content is serialized later in this method!
+                       # also set text to null?
+               }
+
+               $this->mId = isset( $row['id'] ) ? intval( $row['id'] ) : null;
+               $this->mPage = isset( $row['page'] ) ? intval( $row['page'] ) : null;
+               $this->mTextId = isset( $row['text_id'] ) ? intval( $row['text_id'] ) : null;
+               $this->mUserText = isset( $row['user_text'] )
+                       ? strval( $row['user_text'] ) : $wgUser->getName();
+               $this->mUser = isset( $row['user'] ) ? intval( $row['user'] ) : $wgUser->getId();
+               $this->mMinorEdit = isset( $row['minor_edit'] ) ? intval( $row['minor_edit'] ) : 0;
+               $this->mTimestamp = isset( $row['timestamp'] )
+                       ? strval( $row['timestamp'] ) : wfTimestampNow();
+               $this->mDeleted = isset( $row['deleted'] ) ? intval( $row['deleted'] ) : 0;
+               $this->mSize = isset( $row['len'] ) ? intval( $row['len'] ) : null;
+               $this->mParentId = isset( $row['parent_id'] ) ? intval( $row['parent_id'] ) : null;
+               $this->mSha1 = isset( $row['sha1'] ) ? strval( $row['sha1'] ) : null;
+
+               $this->mContentModel = isset( $row['content_model'] )
+                       ? strval( $row['content_model'] ) : null;
+               $this->mContentFormat = isset( $row['content_format'] )
+                       ? strval( $row['content_format'] ) : null;
+
+               // Enforce spacing trimming on supplied text
+               $this->mComment = isset( $row['comment'] ) ? trim( strval( $row['comment'] ) ) : null;
+               $this->mText = isset( $row['text'] ) ? rtrim( strval( $row['text'] ) ) : null;
+               $this->mTextRow = null;
+
+               $this->mTitle = isset( $row['title'] ) ? $row['title'] : null;
+
+               // if we have a Content object, override mText and mContentModel
+               if ( !empty( $row['content'] ) ) {
+                       $handler = $this->getContentHandler();
+                       $this->mContent = $row['content'];
+
+                       $this->mContentModel = $this->mContent->getModel();
+                       $this->mContentHandler = null;
+
+                       $this->mText = $handler->serializeContent( $row['content'], $this->getContentFormat() );
+               } elseif ( $this->mText !== null ) {
+                       $handler = $this->getContentHandler();
+                       $this->mContent = $handler->unserializeContent( $this->mText );
+               }
+
+               // If we have a Title object, make sure it is consistent with mPage.
+               if ( $this->mTitle && $this->mTitle->exists() ) {
+                       if ( $this->mPage === null ) {
+                               // if the page ID wasn't known, set it now
+                               $this->mPage = $this->mTitle->getArticleID();
+                       } elseif ( $this->mTitle->getArticleID() !== $this->mPage ) {
+                               // Got different page IDs. This may be legit (e.g. during undeletion),
+                               // but it seems worth mentioning it in the log.
+                               wfDebug( "Page ID " . $this->mPage . " mismatches the ID " .
+                                       $this->mTitle->getArticleID() . " provided by the Title object." );
+                       }
+               }
+
+               $this->mCurrent = false;
+
+               // If we still have no length, see it we have the text to figure it out
+               if ( !$this->mSize && $this->mContent !== null ) {
+                       $this->mSize = $this->mContent->getSize();
+               }
+
+               // Same for sha1
+               if ( $this->mSha1 === null ) {
+                       $this->mSha1 = $this->mText === null ? null : self::base36Sha1( $this->mText );
+               }
+
+               // force lazy init
+               $this->getContentModel();
+               $this->getContentFormat();
        }
 
        /**
@@ -479,27 +880,19 @@ class Revision implements IDBAccessObject {
         * @return int|null
         */
        public function getId() {
-               return $this->mRecord->getId();
+               return $this->mId;
        }
 
        /**
         * Set the revision ID
         *
-        * This should only be used for proposed revisions that turn out to be null edits.
-        *
-        * @note Only supported on Revisions that were constructed based on associative arrays,
-        *       since they are mutable.
+        * This should only be used for proposed revisions that turn out to be null edits
         *
         * @since 1.19
-        * @param int|string $id
-        * @throws MWException
+        * @param int $id
         */
        public function setId( $id ) {
-               if ( $this->mRecord instanceof MutableRevisionRecord ) {
-                       $this->mRecord->setId( intval( $id ) );
-               } else {
-                       throw new MWException( __METHOD__ . ' is not supported on this instance' );
-               }
+               $this->mId = (int)$id;
        }
 
        /**
@@ -507,107 +900,106 @@ class Revision implements IDBAccessObject {
         *
         * This should only be used for proposed revisions that turn out to be null edits
         *
-        * @note Only supported on Revisions that were constructed based on associative arrays,
-        *       since they are mutable.
-        *
         * @since 1.28
         * @deprecated since 1.31, please reuse old Revision object
         * @param int $id User ID
         * @param string $name User name
-        * @throws MWException
         */
        public function setUserIdAndName( $id, $name ) {
-               if ( $this->mRecord instanceof MutableRevisionRecord ) {
-                       $user = new UserIdentityValue( intval( $id ), $name );
-                       $this->mRecord->setUser( $user );
-               } else {
-                       throw new MWException( __METHOD__ . ' is not supported on this instance' );
-               }
+               $this->mUser = (int)$id;
+               $this->mUserText = $name;
+               $this->mOrigUserText = $name;
        }
 
        /**
-        * @return SlotRecord
-        */
-       private function getMainSlotRaw() {
-               return $this->mRecord->getSlot( 'main', RevisionRecord::RAW );
-       }
-
-       /**
-        * Get the ID of the row of the text table that contains the content of the
-        * revision's main slot, if that content is stored in the text table.
-        *
-        * If the content is stored elsewhere, this returns null.
-        *
-        * @deprecated since 1.31, use RevisionRecord()->getSlot()->getContentAddress() to
-        * get that actual address that can be used with BlobStore::getBlob(); or use
-        * RevisionRecord::hasSameContent() to check if two revisions have the same content.
+        * Get text row ID
         *
         * @return int|null
         */
        public function getTextId() {
-               $slot = $this->getMainSlotRaw();
-               return $slot->hasAddress()
-                       ? self::getBlobStore()->getTextIdFromAddress( $slot->getAddress() )
-                       : null;
+               return $this->mTextId;
        }
 
        /**
         * Get parent revision ID (the original previous page revision)
         *
-        * @return int|null The ID of the parent revision. 0 indicates that there is no
-        * parent revision. Null indicates that the parent revision is not known.
+        * @return int|null
         */
        public function getParentId() {
-               return $this->mRecord->getParentId();
+               return $this->mParentId;
        }
 
        /**
         * Returns the length of the text in this revision, or null if unknown.
         *
-        * @return int
+        * @return int|null
         */
        public function getSize() {
-               return $this->mRecord->getSize();
+               return $this->mSize;
        }
 
        /**
-        * Returns the base36 sha1 of the content in this revision, or null if unknown.
+        * Returns the base36 sha1 of the text in this revision, or null if unknown.
         *
-        * @return string
+        * @return string|null
         */
        public function getSha1() {
-               // XXX: we may want to drop all the hashing logic, it's not worth the overhead.
-               return $this->mRecord->getSha1();
+               return $this->mSha1;
        }
 
        /**
-        * Returns the title of the page associated with this entry.
-        * Since 1.31, this will never return null.
+        * Returns the title of the page associated with this entry or null.
         *
         * Will do a query, when title is not set and id is given.
         *
-        * @return Title
+        * @return Title|null
         */
        public function getTitle() {
-               $linkTarget = $this->mRecord->getPageAsLinkTarget();
-               return Title::newFromLinkTarget( $linkTarget );
+               if ( $this->mTitle !== null ) {
+                       return $this->mTitle;
+               }
+               // rev_id is defined as NOT NULL, but this revision may not yet have been inserted.
+               if ( $this->mId !== null ) {
+                       $dbr = wfGetLB( $this->mWiki )->getConnectionRef( DB_REPLICA, [], $this->mWiki );
+                       // @todo: Title::getSelectFields(), or Title::getQueryInfo(), or something like that
+                       $row = $dbr->selectRow(
+                               [ 'revision', 'page' ],
+                               [
+                                       'page_namespace',
+                                       'page_title',
+                                       'page_id',
+                                       'page_latest',
+                                       'page_is_redirect',
+                                       'page_len',
+                               ],
+                               [ 'rev_id' => $this->mId ],
+                               __METHOD__,
+                               [],
+                               [ 'page' => [ 'JOIN', 'page_id=rev_page' ] ]
+                       );
+                       if ( $row ) {
+                               // @TODO: better foreign title handling
+                               $this->mTitle = Title::newFromRow( $row );
+                       }
+               }
+
+               if ( $this->mWiki === false || $this->mWiki === wfWikiID() ) {
+                       // Loading by ID is best, though not possible for foreign titles
+                       if ( !$this->mTitle && $this->mPage !== null && $this->mPage > 0 ) {
+                               $this->mTitle = Title::newFromID( $this->mPage );
+                       }
+               }
+
+               return $this->mTitle;
        }
 
        /**
         * Set the title of the revision
         *
-        * @deprecated: since 1.31, this is now a noop. Pass the Title to the constructor instead.
-        *
         * @param Title $title
         */
        public function setTitle( $title ) {
-               if ( !$title->equals( $this->getTitle() ) ) {
-                       throw new InvalidArgumentException(
-                               $title->getPrefixedText()
-                                       . ' is not the same as '
-                                       . $this->mRecord->getPageAsLinkTarget()->__toString()
-                       );
-               }
+               $this->mTitle = $title;
        }
 
        /**
@@ -616,7 +1008,7 @@ class Revision implements IDBAccessObject {
         * @return int|null
         */
        public function getPage() {
-               return $this->mRecord->getPageId();
+               return $this->mPage;
        }
 
        /**
@@ -633,14 +1025,13 @@ class Revision implements IDBAccessObject {
         * @return int
         */
        public function getUser( $audience = self::FOR_PUBLIC, User $user = null ) {
-               global $wgUser;
-
-               if ( $audience === self::FOR_THIS_USER && !$user ) {
-                       $user = $wgUser;
+               if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_USER ) ) {
+                       return 0;
+               } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_USER, $user ) ) {
+                       return 0;
+               } else {
+                       return $this->mUser;
                }
-
-               $user = $this->mRecord->getUser( $audience, $user );
-               return $user ? $user->getId() : 0;
        }
 
        /**
@@ -668,14 +1059,23 @@ class Revision implements IDBAccessObject {
         * @return string
         */
        public function getUserText( $audience = self::FOR_PUBLIC, User $user = null ) {
-               global $wgUser;
+               $this->loadMutableFields();
 
-               if ( $audience === self::FOR_THIS_USER && !$user ) {
-                       $user = $wgUser;
+               if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_USER ) ) {
+                       return '';
+               } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_USER, $user ) ) {
+                       return '';
+               } else {
+                       if ( $this->mUserText === null ) {
+                               $this->mUserText = User::whoIs( $this->mUser ); // load on demand
+                               if ( $this->mUserText === false ) {
+                                       # This shouldn't happen, but it can if the wiki was recovered
+                                       # via importing revs and there is no user table entry yet.
+                                       $this->mUserText = $this->mOrigUserText;
+                               }
+                       }
+                       return $this->mUserText;
                }
-
-               $user = $this->mRecord->getUser( $audience, $user );
-               return $user ? $user->getName() : '';
        }
 
        /**
@@ -703,14 +1103,13 @@ class Revision implements IDBAccessObject {
         * @return string
         */
        function getComment( $audience = self::FOR_PUBLIC, User $user = null ) {
-               global $wgUser;
-
-               if ( $audience === self::FOR_THIS_USER && !$user ) {
-                       $user = $wgUser;
+               if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_COMMENT ) ) {
+                       return '';
+               } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_COMMENT, $user ) ) {
+                       return '';
+               } else {
+                       return $this->mComment;
                }
-
-               $comment = $this->mRecord->getComment( $audience, $user );
-               return $comment === null ? null : $comment->text;
        }
 
        /**
@@ -728,14 +1127,23 @@ class Revision implements IDBAccessObject {
         * @return bool
         */
        public function isMinor() {
-               return $this->mRecord->isMinor();
+               return (bool)$this->mMinorEdit;
        }
 
        /**
         * @return int Rcid of the unpatrolled row, zero if there isn't one
         */
        public function isUnpatrolled() {
-               return self::getRevisionStore()->isUnpatrolled( $this->mRecord );
+               if ( $this->mUnpatrolled !== null ) {
+                       return $this->mUnpatrolled;
+               }
+               $rc = $this->getRecentChange();
+               if ( $rc && $rc->getAttribute( 'rc_patrolled' ) == 0 ) {
+                       $this->mUnpatrolled = $rc->getAttribute( 'rc_id' );
+               } else {
+                       $this->mUnpatrolled = 0;
+               }
+               return $this->mUnpatrolled;
        }
 
        /**
@@ -748,7 +1156,19 @@ class Revision implements IDBAccessObject {
         * @return RecentChange|null
         */
        public function getRecentChange( $flags = 0 ) {
-               return self::getRevisionStore()->getRecentChange( $this->mRecord, $flags );
+               $dbr = wfGetDB( DB_REPLICA );
+
+               list( $dbType, ) = DBAccessObjectUtils::getDBOptions( $flags );
+
+               return RecentChange::newFromConds(
+                       [
+                               'rc_user_text' => $this->getUserText( self::RAW ),
+                               'rc_timestamp' => $dbr->timestamp( $this->getTimestamp() ),
+                               'rc_this_oldid' => $this->getId()
+                       ],
+                       __METHOD__,
+                       $dbType
+               );
        }
 
        /**
@@ -757,7 +1177,14 @@ class Revision implements IDBAccessObject {
         * @return bool
         */
        public function isDeleted( $field ) {
-               return $this->mRecord->isDeleted( $field );
+               if ( $this->isCurrent() && $field === self::DELETED_TEXT ) {
+                       // Current revisions of pages cannot have the content hidden. Skipping this
+                       // check is very useful for Parser as it fetches templates using newKnownCurrent().
+                       // Calling getVisibility() in that case triggers a verification database query.
+                       return false; // no need to check
+               }
+
+               return ( $this->getVisibility() & $field ) == $field;
        }
 
        /**
@@ -766,17 +1193,19 @@ class Revision implements IDBAccessObject {
         * @return int
         */
        public function getVisibility() {
-               return $this->mRecord->getVisibility();
+               $this->loadMutableFields();
+
+               return (int)$this->mDeleted;
        }
 
        /**
         * Fetch revision content if it's available to the specified audience.
         * If the specified audience does not have the ability to view this
-        * revision, or the content could not be loaded, null will be returned.
+        * revision, null will be returned.
         *
         * @param int $audience One of:
         *   Revision::FOR_PUBLIC       to be displayed to all users
-        *   Revision::FOR_THIS_USER    to be displayed to $user
+        *   Revision::FOR_THIS_USER    to be displayed to $wgUser
         *   Revision::RAW              get the text regardless of permissions
         * @param User $user User object to check for, only if FOR_THIS_USER is passed
         *   to the $audience parameter
@@ -784,17 +1213,12 @@ class Revision implements IDBAccessObject {
         * @return Content|null
         */
        public function getContent( $audience = self::FOR_PUBLIC, User $user = null ) {
-               global $wgUser;
-
-               if ( $audience === self::FOR_THIS_USER && !$user ) {
-                       $user = $wgUser;
-               }
-
-               try {
-                       return $this->mRecord->getContent( 'main', $audience, $user );
-               }
-               catch ( RevisionAccessException $e ) {
+               if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_TEXT ) ) {
+                       return null;
+               } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_TEXT, $user ) ) {
                        return null;
+               } else {
+                       return $this->getContentInternal();
                }
        }
 
@@ -802,51 +1226,86 @@ class Revision implements IDBAccessObject {
         * Get original serialized data (without checking view restrictions)
         *
         * @since 1.21
-        * @deprecated since 1.31, use BlobStore::getBlob instead.
-        *
         * @return string
         */
        public function getSerializedData() {
-               $slot = $this->getMainSlotRaw();
-               return $slot->getContent()->serialize();
+               if ( $this->mText === null ) {
+                       // Revision is immutable. Load on demand.
+                       $this->mText = $this->loadText();
+               }
+
+               return $this->mText;
        }
 
        /**
-        * Returns the content model for the main slot of this revision.
+        * Gets the content object for the revision (or null on failure).
+        *
+        * Note that for mutable Content objects, each call to this method will return a
+        * fresh clone.
+        *
+        * @since 1.21
+        * @return Content|null The Revision's content, or null on failure.
+        */
+       protected function getContentInternal() {
+               if ( $this->mContent === null ) {
+                       $text = $this->getSerializedData();
+
+                       if ( $text !== null && $text !== false ) {
+                               // Unserialize content
+                               $handler = $this->getContentHandler();
+                               $format = $this->getContentFormat();
+
+                               $this->mContent = $handler->unserializeContent( $text, $format );
+                       }
+               }
+
+               // NOTE: copy() will return $this for immutable content objects
+               return $this->mContent ? $this->mContent->copy() : null;
+       }
+
+       /**
+        * Returns the content model for this revision.
         *
         * If no content model was stored in the database, the default content model for the title is
         * used to determine the content model to use. If no title is know, CONTENT_MODEL_WIKITEXT
         * is used as a last resort.
         *
-        * @todo: drop this, with MCR, there no longer is a single model associated with a revision.
-        *
         * @return string The content model id associated with this revision,
         *     see the CONTENT_MODEL_XXX constants.
         */
        public function getContentModel() {
-               return $this->getMainSlotRaw()->getModel();
+               if ( !$this->mContentModel ) {
+                       $title = $this->getTitle();
+                       if ( $title ) {
+                               $this->mContentModel = ContentHandler::getDefaultModelFor( $title );
+                       } else {
+                               $this->mContentModel = CONTENT_MODEL_WIKITEXT;
+                       }
+
+                       assert( !empty( $this->mContentModel ) );
+               }
+
+               return $this->mContentModel;
        }
 
        /**
-        * Returns the content format for the main slot of this revision.
+        * Returns the content format for this revision.
         *
         * If no content format was stored in the database, the default format for this
         * revision's content model is returned.
         *
-        * @todo: drop this, the format is irrelevant to the revision!
-        *
         * @return string The content format id associated with this revision,
         *     see the CONTENT_FORMAT_XXX constants.
         */
        public function getContentFormat() {
-               $format = $this->getMainSlotRaw()->getFormat();
+               if ( !$this->mContentFormat ) {
+                       $handler = $this->getContentHandler();
+                       $this->mContentFormat = $handler->getDefaultFormat();
 
-               if ( $format === null ) {
-                       // if no format was stored along with the blob, fall back to default format
-                       $format = $this->getContentHandler()->getDefaultFormat();
+                       assert( !empty( $this->mContentFormat ) );
                }
 
-               return $format;
+               return $this->mContentFormat;
        }
 
        /**
@@ -856,21 +1315,33 @@ class Revision implements IDBAccessObject {
         * @return ContentHandler
         */
        public function getContentHandler() {
-               return ContentHandler::getForModelID( $this->getContentModel() );
+               if ( !$this->mContentHandler ) {
+                       $model = $this->getContentModel();
+                       $this->mContentHandler = ContentHandler::getForModelID( $model );
+
+                       $format = $this->getContentFormat();
+
+                       if ( !$this->mContentHandler->isSupportedFormat( $format ) ) {
+                               throw new MWException( "Oops, the content format $format is not supported for "
+                                       . "this content model, $model" );
+                       }
+               }
+
+               return $this->mContentHandler;
        }
 
        /**
         * @return string
         */
        public function getTimestamp() {
-               return $this->mRecord->getTimestamp();
+               return wfTimestamp( TS_MW, $this->mTimestamp );
        }
 
        /**
         * @return bool
         */
        public function isCurrent() {
-               return ( $this->mRecord instanceof RevisionStoreRecord ) && $this->mRecord->isCurrent();
+               return $this->mCurrent;
        }
 
        /**
@@ -879,8 +1350,13 @@ class Revision implements IDBAccessObject {
         * @return Revision|null
         */
        public function getPrevious() {
-               $rec = self::getRevisionStore()->getPreviousRevision( $this->mRecord );
-               return $rec === null ? null : new Revision( $rec );
+               if ( $this->getTitle() ) {
+                       $prev = $this->getTitle()->getPreviousRevisionID( $this->getId() );
+                       if ( $prev ) {
+                               return self::newFromTitle( $this->getTitle(), $prev );
+                       }
+               }
+               return null;
        }
 
        /**
@@ -889,8 +1365,38 @@ class Revision implements IDBAccessObject {
         * @return Revision|null
         */
        public function getNext() {
-               $rec = self::getRevisionStore()->getNextRevision( $this->mRecord );
-               return $rec === null ? null : new Revision( $rec );
+               if ( $this->getTitle() ) {
+                       $next = $this->getTitle()->getNextRevisionID( $this->getId() );
+                       if ( $next ) {
+                               return self::newFromTitle( $this->getTitle(), $next );
+                       }
+               }
+               return null;
+       }
+
+       /**
+        * Get previous revision Id for this page_id
+        * This is used to populate rev_parent_id on save
+        *
+        * @param IDatabase $db
+        * @return int
+        */
+       private function getPreviousRevisionId( $db ) {
+               if ( $this->mPage === null ) {
+                       return 0;
+               }
+               # Use page_latest if ID is not given
+               if ( !$this->mId ) {
+                       $prevId = $db->selectField( 'page', 'page_latest',
+                               [ 'page_id' => $this->mPage ],
+                               __METHOD__ );
+               } else {
+                       $prevId = $db->selectField( 'revision', 'rev_id',
+                               [ 'rev_page' => $this->mPage, 'rev_id < ' . $this->mId ],
+                               __METHOD__,
+                               [ 'ORDER BY' => 'rev_id DESC' ] );
+               }
+               return intval( $prevId );
        }
 
        /**
@@ -923,9 +1429,35 @@ class Revision implements IDBAccessObject {
                        return false;
                }
 
-               $cacheKey = isset( $row->old_id ) ? ( 'tt:' . $row->old_id ) : null;
+               // Use external methods for external objects, text in table is URL-only then
+               if ( in_array( 'external', $flags ) ) {
+                       $url = $text;
+                       $parts = explode( '://', $url, 2 );
+                       if ( count( $parts ) == 1 || $parts[1] == '' ) {
+                               return false;
+                       }
+
+                       if ( isset( $row->old_id ) && $wiki === false ) {
+                               // Make use of the wiki-local revision text cache
+                               $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
+                               // The cached value should be decompressed, so handle that and return here
+                               return $cache->getWithSetCallback(
+                                       $cache->makeKey( 'revisiontext', 'textid', $row->old_id ),
+                                       self::getCacheTTL( $cache ),
+                                       function () use ( $url, $wiki, $flags ) {
+                                               // No negative caching per Revision::loadText()
+                                               $text = ExternalStore::fetchFromURL( $url, [ 'wiki' => $wiki ] );
+
+                                               return self::decompressRevisionText( $text, $flags );
+                                       },
+                                       [ 'pcGroup' => self::TEXT_CACHE_GROUP, 'pcTTL' => $cache::TTL_PROC_LONG ]
+                               );
+                       } else {
+                               $text = ExternalStore::fetchFromURL( $url, [ 'wiki' => $wiki ] );
+                       }
+               }
 
-               return self::getBlobStore()->expandBlob( $text, $flags, $cacheKey );
+               return self::decompressRevisionText( $text, $flags );
        }
 
        /**
@@ -939,7 +1471,28 @@ class Revision implements IDBAccessObject {
         * @return string
         */
        public static function compressRevisionText( &$text ) {
-               return self::getBlobStore()->compressData( $text );
+               global $wgCompressRevisions;
+               $flags = [];
+
+               # Revisions not marked this way will be converted
+               # on load if $wgLegacyCharset is set in the future.
+               $flags[] = 'utf-8';
+
+               if ( $wgCompressRevisions ) {
+                       if ( function_exists( 'gzdeflate' ) ) {
+                               $deflated = gzdeflate( $text );
+
+                               if ( $deflated === false ) {
+                                       wfLogWarning( __METHOD__ . ': gzdeflate() failed' );
+                               } else {
+                                       $text = $deflated;
+                                       $flags[] = 'gzip';
+                               }
+                       } else {
+                               wfDebug( __METHOD__ . " -- no zlib support, not compressing\n" );
+                       }
+               }
+               return implode( ',', $flags );
        }
 
        /**
@@ -950,7 +1503,46 @@ class Revision implements IDBAccessObject {
         * @return string|bool Decompressed text, or false on failure
         */
        public static function decompressRevisionText( $text, $flags ) {
-               return self::getBlobStore()->decompressData( $text, $flags );
+               global $wgLegacyEncoding, $wgContLang;
+
+               if ( $text === false ) {
+                       // Text failed to be fetched; nothing to do
+                       return false;
+               }
+
+               if ( in_array( 'gzip', $flags ) ) {
+                       # Deal with optional compression of archived pages.
+                       # This can be done periodically via maintenance/compressOld.php, and
+                       # as pages are saved if $wgCompressRevisions is set.
+                       $text = gzinflate( $text );
+
+                       if ( $text === false ) {
+                               wfLogWarning( __METHOD__ . ': gzinflate() failed' );
+                               return false;
+                       }
+               }
+
+               if ( in_array( 'object', $flags ) ) {
+                       # Generic compressed storage
+                       $obj = unserialize( $text );
+                       if ( !is_object( $obj ) ) {
+                               // Invalid object
+                               return false;
+                       }
+                       $text = $obj->getText();
+               }
+
+               if ( $text !== false && $wgLegacyEncoding
+                       && !in_array( 'utf-8', $flags ) && !in_array( 'utf8', $flags )
+               ) {
+                       # Old revisions kept around in a legacy encoding?
+                       # Upconvert on demand.
+                       # ("utf8" checked for compatibility with some broken
+                       #  conversion scripts 2008-12-30)
+                       $text = $wgContLang->iconv( $wgLegacyEncoding, 'UTF-8', $text );
+               }
+
+               return $text;
        }
 
        /**
@@ -962,27 +1554,192 @@ class Revision implements IDBAccessObject {
         * @return int The revision ID
         */
        public function insertOn( $dbw ) {
-               global $wgUser;
+               global $wgDefaultExternalStore, $wgContentHandlerUseDB;
+
+               // We're inserting a new revision, so we have to use master anyway.
+               // If it's a null revision, it may have references to rows that
+               // are not in the replica yet (the text row).
+               $this->mQueryFlags |= self::READ_LATEST;
+
+               // Not allowed to have rev_page equal to 0, false, etc.
+               if ( !$this->mPage ) {
+                       $title = $this->getTitle();
+                       if ( $title instanceof Title ) {
+                               $titleText = ' for page ' . $title->getPrefixedText();
+                       } else {
+                               $titleText = '';
+                       }
+                       throw new MWException( "Cannot insert revision$titleText: page ID must be nonzero" );
+               }
 
-               // Note that $this->mRecord->getId() will typically return null here, but not always,
-               // e.g. not when restoring a revision.
+               $this->checkContentModel();
 
-               if ( $this->mRecord->getUser( RevisionRecord::RAW ) === null ) {
-                       if ( $this->mRecord instanceof MutableRevisionRecord ) {
-                               $this->mRecord->setUser( $wgUser );
-                       } else {
-                               throw new MWException( 'Cannot insert revision with no associated user.' );
+               $data = $this->mText;
+               $flags = self::compressRevisionText( $data );
+
+               # Write to external storage if required
+               if ( $wgDefaultExternalStore ) {
+                       // Store and get the URL
+                       $data = ExternalStore::insertToDefault( $data );
+                       if ( !$data ) {
+                               throw new MWException( "Unable to store text to external storage" );
+                       }
+                       if ( $flags ) {
+                               $flags .= ',';
+                       }
+                       $flags .= 'external';
+               }
+
+               # Record the text (or external storage URL) to the text table
+               if ( $this->mTextId === null ) {
+                       $dbw->insert( 'text',
+                               [
+                                       'old_text' => $data,
+                                       'old_flags' => $flags,
+                               ], __METHOD__
+                       );
+                       $this->mTextId = $dbw->insertId();
+               }
+
+               if ( $this->mComment === null ) {
+                       $this->mComment = "";
+               }
+
+               # Record the edit in revisions
+               $row = [
+                       'rev_page'       => $this->mPage,
+                       'rev_text_id'    => $this->mTextId,
+                       'rev_minor_edit' => $this->mMinorEdit ? 1 : 0,
+                       'rev_user'       => $this->mUser,
+                       'rev_user_text'  => $this->mUserText,
+                       'rev_timestamp'  => $dbw->timestamp( $this->mTimestamp ),
+                       'rev_deleted'    => $this->mDeleted,
+                       'rev_len'        => $this->mSize,
+                       'rev_parent_id'  => $this->mParentId === null
+                               ? $this->getPreviousRevisionId( $dbw )
+                               : $this->mParentId,
+                       'rev_sha1'       => $this->mSha1 === null
+                               ? self::base36Sha1( $this->mText )
+                               : $this->mSha1,
+               ];
+               if ( $this->mId !== null ) {
+                       $row['rev_id'] = $this->mId;
+               }
+
+               list( $commentFields, $commentCallback ) =
+                       CommentStore::newKey( 'rev_comment' )->insertWithTempTable( $dbw, $this->mComment );
+               $row += $commentFields;
+
+               if ( $wgContentHandlerUseDB ) {
+                       // NOTE: Store null for the default model and format, to save space.
+                       // XXX: Makes the DB sensitive to changed defaults.
+                       // Make this behavior optional? Only in miser mode?
+
+                       $model = $this->getContentModel();
+                       $format = $this->getContentFormat();
+
+                       $title = $this->getTitle();
+
+                       if ( $title === null ) {
+                               throw new MWException( "Insufficient information to determine the title of the "
+                                       . "revision's page!" );
                        }
+
+                       $defaultModel = ContentHandler::getDefaultModelFor( $title );
+                       $defaultFormat = ContentHandler::getForModelID( $defaultModel )->getDefaultFormat();
+
+                       $row['rev_content_model'] = ( $model === $defaultModel ) ? null : $model;
+                       $row['rev_content_format'] = ( $format === $defaultFormat ) ? null : $format;
                }
 
-               $rec = self::getRevisionStore()->insertRevisionOn( $this->mRecord, $dbw );
+               $dbw->insert( 'revision', $row, __METHOD__ );
 
-               $this->mRecord = $rec;
+               if ( $this->mId === null ) {
+                       // Only if auto-increment was used
+                       $this->mId = $dbw->insertId();
+               }
+               $commentCallback( $this->mId );
 
-               // TODO: hard-deprecate in 1.32 (or even 1.31?)
-               Hooks::run( 'RevisionInsertComplete', [ $this, null, null ] );
+               // Assertion to try to catch T92046
+               if ( (int)$this->mId === 0 ) {
+                       throw new UnexpectedValueException(
+                               'After insert, Revision mId is ' . var_export( $this->mId, 1 ) . ': ' .
+                                       var_export( $row, 1 )
+                       );
+               }
 
-               return $rec->getId();
+               // Insert IP revision into ip_changes for use when querying for a range.
+               if ( $this->mUser === 0 && IP::isValid( $this->mUserText ) ) {
+                       $ipcRow = [
+                               'ipc_rev_id'        => $this->mId,
+                               'ipc_rev_timestamp' => $row['rev_timestamp'],
+                               'ipc_hex'           => IP::toHex( $row['rev_user_text'] ),
+                       ];
+                       $dbw->insert( 'ip_changes', $ipcRow, __METHOD__ );
+               }
+
+               // Avoid PHP 7.1 warning of passing $this by reference
+               $revision = $this;
+               Hooks::run( 'RevisionInsertComplete', [ &$revision, $data, $flags ] );
+
+               return $this->mId;
+       }
+
+       protected function checkContentModel() {
+               global $wgContentHandlerUseDB;
+
+               // Note: may return null for revisions that have not yet been inserted
+               $title = $this->getTitle();
+
+               $model = $this->getContentModel();
+               $format = $this->getContentFormat();
+               $handler = $this->getContentHandler();
+
+               if ( !$handler->isSupportedFormat( $format ) ) {
+                       $t = $title->getPrefixedDBkey();
+
+                       throw new MWException( "Can't use format $format with content model $model on $t" );
+               }
+
+               if ( !$wgContentHandlerUseDB && $title ) {
+                       // if $wgContentHandlerUseDB is not set,
+                       // all revisions must use the default content model and format.
+
+                       $defaultModel = ContentHandler::getDefaultModelFor( $title );
+                       $defaultHandler = ContentHandler::getForModelID( $defaultModel );
+                       $defaultFormat = $defaultHandler->getDefaultFormat();
+
+                       if ( $this->getContentModel() != $defaultModel ) {
+                               $t = $title->getPrefixedDBkey();
+
+                               throw new MWException( "Can't save non-default content model with "
+                                       . "\$wgContentHandlerUseDB disabled: model is $model, "
+                                       . "default for $t is $defaultModel" );
+                       }
+
+                       if ( $this->getContentFormat() != $defaultFormat ) {
+                               $t = $title->getPrefixedDBkey();
+
+                               throw new MWException( "Can't use non-default content format with "
+                                       . "\$wgContentHandlerUseDB disabled: format is $format, "
+                                       . "default for $t is $defaultFormat" );
+                       }
+               }
+
+               $content = $this->getContent( self::RAW );
+               $prefixedDBkey = $title->getPrefixedDBkey();
+               $revId = $this->mId;
+
+               if ( !$content ) {
+                       throw new MWException(
+                               "Content of revision $revId ($prefixedDBkey) could not be loaded for validation!"
+                       );
+               }
+               if ( !$content->isValid() ) {
+                       throw new MWException(
+                               "Content of revision $revId ($prefixedDBkey) is not valid! Content model is $model"
+                       );
+               }
        }
 
        /**
@@ -991,7 +1748,103 @@ class Revision implements IDBAccessObject {
         * @return string
         */
        public static function base36Sha1( $text ) {
-               return SlotRecord::base36Sha1( $text );
+               return Wikimedia\base_convert( sha1( $text ), 16, 36, 31 );
+       }
+
+       /**
+        * Get the text cache TTL
+        *
+        * @param WANObjectCache $cache
+        * @return int
+        */
+       private static function getCacheTTL( WANObjectCache $cache ) {
+               global $wgRevisionCacheExpiry;
+
+               if ( $cache->getQoS( $cache::ATTR_EMULATION ) <= $cache::QOS_EMULATION_SQL ) {
+                       // Do not cache RDBMs blobs in...the RDBMs store
+                       $ttl = $cache::TTL_UNCACHEABLE;
+               } else {
+                       $ttl = $wgRevisionCacheExpiry ?: $cache::TTL_UNCACHEABLE;
+               }
+
+               return $ttl;
+       }
+
+       /**
+        * Lazy-load the revision's text.
+        * Currently hardcoded to the 'text' table storage engine.
+        *
+        * @return string|bool The revision's text, or false on failure
+        */
+       private function loadText() {
+               $cache = ObjectCache::getMainWANInstance();
+
+               // No negative caching; negative hits on text rows may be due to corrupted replica DBs
+               return $cache->getWithSetCallback(
+                       $cache->makeKey( 'revisiontext', 'textid', $this->getTextId() ),
+                       self::getCacheTTL( $cache ),
+                       function () {
+                               return $this->fetchText();
+                       },
+                       [ 'pcGroup' => self::TEXT_CACHE_GROUP, 'pcTTL' => $cache::TTL_PROC_LONG ]
+               );
+       }
+
+       private function fetchText() {
+               $textId = $this->getTextId();
+
+               // If we kept data for lazy extraction, use it now...
+               if ( $this->mTextRow !== null ) {
+                       $row = $this->mTextRow;
+                       $this->mTextRow = null;
+               } else {
+                       $row = null;
+               }
+
+               // Callers doing updates will pass in READ_LATEST as usual. Since the text/blob tables
+               // do not normally get rows changed around, set READ_LATEST_IMMUTABLE in those cases.
+               $flags = $this->mQueryFlags;
+               $flags |= DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST )
+                       ? self::READ_LATEST_IMMUTABLE
+                       : 0;
+
+               list( $index, $options, $fallbackIndex, $fallbackOptions ) =
+                       DBAccessObjectUtils::getDBOptions( $flags );
+
+               if ( !$row ) {
+                       // Text data is immutable; check replica DBs first.
+                       $row = wfGetDB( $index )->selectRow(
+                               'text',
+                               [ 'old_text', 'old_flags' ],
+                               [ 'old_id' => $textId ],
+                               __METHOD__,
+                               $options
+                       );
+               }
+
+               // Fallback to DB_MASTER in some cases if the row was not found
+               if ( !$row && $fallbackIndex !== null ) {
+                       // Use FOR UPDATE if it was used to fetch this revision. This avoids missing the row
+                       // due to REPEATABLE-READ. Also fallback to the master if READ_LATEST is provided.
+                       $row = wfGetDB( $fallbackIndex )->selectRow(
+                               'text',
+                               [ 'old_text', 'old_flags' ],
+                               [ 'old_id' => $textId ],
+                               __METHOD__,
+                               $fallbackOptions
+                       );
+               }
+
+               if ( !$row ) {
+                       wfDebugLog( 'Revision', "No text row with ID '$textId' (revision {$this->getId()})." );
+               }
+
+               $text = self::getRevisionText( $row );
+               if ( $row && $text === false ) {
+                       wfDebugLog( 'Revision', "No blob for text row '$textId' (revision {$this->getId()})." );
+               }
+
+               return is_string( $text ) ? $text : false;
        }
 
        /**
@@ -1010,17 +1863,58 @@ class Revision implements IDBAccessObject {
         * @return Revision|null Revision or null on error
         */
        public static function newNullRevision( $dbw, $pageId, $summary, $minor, $user = null ) {
-               global $wgUser;
-               if ( !$user ) {
-                       $user = $wgUser;
+               global $wgContentHandlerUseDB;
+
+               $fields = [ 'page_latest', 'page_namespace', 'page_title',
+                                               'rev_text_id', 'rev_len', 'rev_sha1' ];
+
+               if ( $wgContentHandlerUseDB ) {
+                       $fields[] = 'rev_content_model';
+                       $fields[] = 'rev_content_format';
                }
 
-               $comment = CommentStoreComment::newUnsavedComment( $summary, null );
+               $current = $dbw->selectRow(
+                       [ 'page', 'revision' ],
+                       $fields,
+                       [
+                               'page_id' => $pageId,
+                               'page_latest=rev_id',
+                       ],
+                       __METHOD__,
+                       [ 'FOR UPDATE' ] // T51581
+               );
+
+               if ( $current ) {
+                       if ( !$user ) {
+                               global $wgUser;
+                               $user = $wgUser;
+                       }
+
+                       $row = [
+                               'page'       => $pageId,
+                               'user_text'  => $user->getName(),
+                               'user'       => $user->getId(),
+                               'comment'    => $summary,
+                               'minor_edit' => $minor,
+                               'text_id'    => $current->rev_text_id,
+                               'parent_id'  => $current->page_latest,
+                               'len'        => $current->rev_len,
+                               'sha1'       => $current->rev_sha1
+                       ];
+
+                       if ( $wgContentHandlerUseDB ) {
+                               $row['content_model'] = $current->rev_content_model;
+                               $row['content_format'] = $current->rev_content_format;
+                       }
+
+                       $row['title'] = Title::makeTitle( $current->page_namespace, $current->page_title );
 
-               $title = Title::newFromID( $pageId );
-               $rec = self::getRevisionStore()->newNullRevision( $dbw, $title, $comment, $minor, $user );
+                       $revision = new Revision( $row );
+               } else {
+                       $revision = null;
+               }
 
-               return new Revision( $rec );
+               return $revision;
        }
 
        /**
@@ -1054,13 +1948,35 @@ class Revision implements IDBAccessObject {
        public static function userCanBitfield( $bitfield, $field, User $user = null,
                Title $title = null
        ) {
-               global $wgUser;
-
-               if ( !$user ) {
-                       $user = $wgUser;
+               if ( $bitfield & $field ) { // aspect is deleted
+                       if ( $user === null ) {
+                               global $wgUser;
+                               $user = $wgUser;
+                       }
+                       if ( $bitfield & self::DELETED_RESTRICTED ) {
+                               $permissions = [ 'suppressrevision', 'viewsuppressed' ];
+                       } elseif ( $field & self::DELETED_TEXT ) {
+                               $permissions = [ 'deletedtext' ];
+                       } else {
+                               $permissions = [ 'deletedhistory' ];
+                       }
+                       $permissionlist = implode( ', ', $permissions );
+                       if ( $title === null ) {
+                               wfDebug( "Checking for $permissionlist due to $field match on $bitfield\n" );
+                               return call_user_func_array( [ $user, 'isAllowedAny' ], $permissions );
+                       } else {
+                               $text = $title->getPrefixedText();
+                               wfDebug( "Checking for $permissionlist on $text due to $field match on $bitfield\n" );
+                               foreach ( $permissions as $perm ) {
+                                       if ( $title->userCan( $perm, $user ) ) {
+                                               return true;
+                                       }
+                               }
+                               return false;
+                       }
+               } else {
+                       return true;
                }
-
-               return RevisionRecord::userCanBitfield( $bitfield, $field, $user, $title );
        }
 
        /**
@@ -1072,7 +1988,18 @@ class Revision implements IDBAccessObject {
         * @return string|bool False if not found
         */
        static function getTimestampFromId( $title, $id, $flags = 0 ) {
-               return self::getRevisionStore()->getTimestampFromId( $title, $id, $flags );
+               $db = ( $flags & self::READ_LATEST )
+                       ? wfGetDB( DB_MASTER )
+                       : wfGetDB( DB_REPLICA );
+               // Casting fix for databases that can't take '' for rev_id
+               if ( $id == '' ) {
+                       $id = 0;
+               }
+               $conds = [ 'rev_id' => $id ];
+               $conds['rev_page'] = $title->getArticleID();
+               $timestamp = $db->selectField( 'revision', 'rev_timestamp', $conds, __METHOD__ );
+
+               return ( $timestamp !== false ) ? wfTimestamp( TS_MW, $timestamp ) : false;
        }
 
        /**
@@ -1083,7 +2010,12 @@ class Revision implements IDBAccessObject {
         * @return int
         */
        static function countByPageId( $db, $id ) {
-               return self::getRevisionStore()->countRevisionsByPageId( $db, $id );
+               $row = $db->selectRow( 'revision', [ 'revCount' => 'COUNT(*)' ],
+                       [ 'rev_page' => $id ], __METHOD__ );
+               if ( $row ) {
+                       return $row->revCount;
+               }
+               return 0;
        }
 
        /**
@@ -1094,7 +2026,11 @@ class Revision implements IDBAccessObject {
         * @return int
         */
        static function countByTitle( $db, $title ) {
-               return self::getRevisionStore()->countRevisionsByTitle( $db, $title );
+               $id = $title->getArticleID();
+               if ( $id ) {
+                       return self::countByPageId( $db, $id );
+               }
+               return 0;
        }
 
        /**
@@ -1114,11 +2050,28 @@ class Revision implements IDBAccessObject {
         * @return bool True if the given user was the only one to edit since the given timestamp
         */
        public static function userWasLastToEdit( $db, $pageId, $userId, $since ) {
+               if ( !$userId ) {
+                       return false;
+               }
+
                if ( is_int( $db ) ) {
                        $db = wfGetDB( $db );
                }
 
-               return self::getRevisionStore()->userWasLastToEdit( $db, $pageId, $userId, $since );
+               $res = $db->select( 'revision',
+                       'rev_user',
+                       [
+                               'rev_page' => $pageId,
+                               'rev_timestamp > ' . $db->addQuotes( $db->timestamp( $since ) )
+                       ],
+                       __METHOD__,
+                       [ 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 50 ] );
+               foreach ( $res as $row ) {
+                       if ( $row->rev_user != $userId ) {
+                               return false;
+                       }
+               }
+               return true;
        }
 
        /**
@@ -1126,20 +2079,54 @@ class Revision implements IDBAccessObject {
         *
         * This method allows for the use of caching, though accessing anything that normally
         * requires permission checks (aside from the text) will trigger a small DB lookup.
-        * The title will also be loaded if $pageIdOrTitle is an integer ID.
+        * The title will also be lazy loaded, though setTitle() can be used to preload it.
         *
-        * @param IDatabase $db ignored!
-        * @param int|Title $pageIdOrTitle Page ID or Title object
-        * @param int $revId Known current revision of this page. Determined automatically if not given.
+        * @param IDatabase $db
+        * @param int $pageId Page ID
+        * @param int $revId Known current revision of this page
         * @return Revision|bool Returns false if missing
         * @since 1.28
         */
-       public static function newKnownCurrent( IDatabase $db, $pageIdOrTitle, $revId = 0 ) {
-               $title = $pageIdOrTitle instanceof Title
-                       ? $pageIdOrTitle
-                       : Title::newFromID( $pageIdOrTitle );
+       public static function newKnownCurrent( IDatabase $db, $pageId, $revId ) {
+               $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
+               return $cache->getWithSetCallback(
+                       // Page/rev IDs passed in from DB to reflect history merges
+                       $cache->makeGlobalKey( 'revision', $db->getDomainID(), $pageId, $revId ),
+                       $cache::TTL_WEEK,
+                       function ( $curValue, &$ttl, array &$setOpts ) use ( $db, $pageId, $revId ) {
+                               $setOpts += Database::getCacheSetOptions( $db );
+
+                               $rev = Revision::loadFromPageId( $db, $pageId, $revId );
+                               // Reflect revision deletion and user renames
+                               if ( $rev ) {
+                                       $rev->mTitle = null; // mutable; lazy-load
+                                       $rev->mRefreshMutableFields = true;
+                               }
+
+                               return $rev ?: false; // don't cache negatives
+                       }
+               );
+       }
+
+       /**
+        * For cached revisions, make sure the user name and rev_deleted is up-to-date
+        */
+       private function loadMutableFields() {
+               if ( !$this->mRefreshMutableFields ) {
+                       return; // not needed
+               }
 
-               $record = self::getRevisionStore()->getKnownCurrentRevision( $title, $revId );
-               return $record ? new Revision( $record ) : false;
+               $this->mRefreshMutableFields = false;
+               $dbr = wfGetLB( $this->mWiki )->getConnectionRef( DB_REPLICA, [], $this->mWiki );
+               $row = $dbr->selectRow(
+                       [ 'revision', 'user' ],
+                       [ 'rev_deleted', 'user_name' ],
+                       [ 'rev_id' => $this->mId, 'user_id = rev_user' ],
+                       __METHOD__
+               );
+               if ( $row ) { // update values
+                       $this->mDeleted = (int)$row->rev_deleted;
+                       $this->mUserText = $row->user_name;
+               }
        }
 }
index 575970d..d21bcef 100644 (file)
@@ -450,46 +450,6 @@ return [
                return $factory;
        },
 
-       'RevisionStore' => function ( MediaWikiServices $services ) {
-               /** @var SqlBlobStore $blobStore */
-               $blobStore = $services->getService( '_SqlBlobStore' );
-
-               $store = new RevisionStore(
-                       $services->getDBLoadBalancer(),
-                       $blobStore,
-                       $services->getMainWANObjectCache()
-               );
-
-               $config = $services->getMainConfig();
-               $store->setContentHandlerUseDB( $config->get( 'ContentHandlerUseDB' ) );
-
-               return $store;
-       },
-
-       'BlobStore' => function ( MediaWikiServices $services ) {
-               return $services->getService( '_SqlBlobStore' );
-       },
-
-       '_SqlBlobStore' => function ( MediaWikiServices $services ) {
-               global $wgContLang; // TODO: manage $wgContLang as a service
-
-               $store = new SqlBlobStore(
-                       $services->getDBLoadBalancer(),
-                       $services->getMainWANObjectCache()
-               );
-
-               $config = $services->getMainConfig();
-               $store->setCompressRevisions( $config->get( 'CompressRevisions' ) );
-               $store->setCacheExpiry( $config->get( 'RevisionCacheExpiry' ) );
-               $store->setUseExternalStore( $config->get( 'DefaultExternalStore' ) !== false );
-
-               if ( $config->get( 'LegacyEncoding' ) ) {
-                       $store->setLegacyEncoding( $config->get( 'LegacyEncoding' ), $wgContLang );
-               }
-
-               return $store;
-       },
-
        'ExternalStoreFactory' => function ( MediaWikiServices $services ) {
                $config = $services->getMainConfig();
 
index 0714633..fcdc1b9 100644 (file)
@@ -228,7 +228,7 @@ class SqlBlobStore implements IDBAccessObject, BlobStore {
                                // used. We'll need to assess expected fallout before doing that.
                        }
 
-                       $dbw = $this->getDBConnection( DB_REPLICA );
+                       $dbw = $this->getDBConnection( DB_MASTER );
 
                        $old_id = $dbw->nextSequenceValue( 'text_old_id_seq' );
                        $dbw->insert(
index 85e8db6..0e964bf 100644 (file)
@@ -335,8 +335,8 @@ class HistoryAction extends FormlessAction {
         * @return FeedItem
         */
        function feedItem( $row ) {
-               $rev = new Revision( $row, 0, $this->getTitle() );
-
+               $rev = new Revision( $row );
+               $rev->setTitle( $this->getTitle() );
                $text = FeedUtils::formatDiffRow(
                        $this->getTitle(),
                        $this->getTitle()->getPreviousRevisionID( $rev->getId() ),
@@ -639,10 +639,12 @@ class HistoryPager extends ReverseChronologicalPager {
         */
        function historyLine( $row, $next, $notificationtimestamp = false,
                $latest = false, $firstInList = false ) {
-               $rev = new Revision( $row, 0, $this->getTitle() );
+               $rev = new Revision( $row );
+               $rev->setTitle( $this->getTitle() );
 
                if ( is_object( $next ) ) {
-                       $prevRev = new Revision( $next, 0, $this->getTitle() );
+                       $prevRev = new Revision( $next );
+                       $prevRev->setTitle( $this->getTitle() );
                } else {
                        $prevRev = null;
                }
index d6e9b74..768f980 100644 (file)
@@ -1048,7 +1048,8 @@ class MessageCache {
                if ( $titleObj->getLatestRevID() ) {
                        $revision = Revision::newKnownCurrent(
                                $dbr,
-                               $titleObj
+                               $titleObj->getArticleID(),
+                               $titleObj->getLatestRevID()
                        );
                } else {
                        $revision = false;
index 53abf4b..29fc67e 100644 (file)
@@ -86,6 +86,7 @@
        "config-db-host": "Datu-basearen zerbitzaria:",
        "config-db-host-help": "Zure datu-basearen zerbitzaria beste zerbitzari batean badago, sartu ostalariaren izena edo IP helbidea hemen.\n\nPartekatutako web-ostatua erabiltzen ari bazara, zure ostalaritza-hornitzaileak dokumentazio-ostalariaren izen egokia eman beharko lizuke.\n\nWindows zerbitzari batean instalatzen bazara eta MySQL erabiliz, \"localhost\" agian ez du zerbitzariaren izenerako funtzionatuko. Ez badago, saiatu \"127.0.0.1\" tokiko IP helbideetarako.\n\nPostgreSQL erabiltzen ari bazara, utzi eremu hau hutsik Unix socket bidez konektatzeko.",
        "config-db-host-oracle": "Datu-baseko TNS:",
+       "config-db-host-oracle-help": "Sartu baliozko [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Konekzio izan lokala]; instalazio honetarako tnsnames.ora fitxategia ikusgai egon behar da. <br/> Bezeroen 10g liburutegiak edo berriagoak erabiltzen ari bazara, [http://download.oracle.com/docs/cd/E11882_01/network.112 ere erabil dezakezu. /e10836/naming.htm Konektatzeko erraza] izendatzeko metodoa.",
        "config-db-wiki-settings": "Wiki hau identifikatu",
        "config-db-name": "Datu-base izena:",
        "config-db-name-help": "Aukeratu zure Wikia identifikatzen duen izena.\nEzin dira espazioak eabili.\n\nErabiltzen ari bazara web hosting partekatua, hostin-eko hornitzaileak emango dizu datu-basearen izen espezifikoa edo kontrol panel baten bitzrtez zure datu-basea sortzea utziko dizu.",
        "config-db-schema-help": "Patroi hau normalean egokia da. Bakarrik aldatu beharrezkoa bada.",
        "config-pg-test-error": "Ezin da datu-basearekin konektatu <strong>$1</strong>: $2",
        "config-sqlite-dir": "SQLite -eko informazioaren direktorioa:",
+       "config-sqlite-dir-help": "SQLite-k datu guztiak fitxategi bakarrean gordetzen ditu.\n\nHornitu duzun direktorioa web zerbitzariaren bidez idatzia izateko aukera eman beharko duu instalazioan zehar.\n\n<Strong>Ez</strong> da webgunearen bidez eskuragarri egon behar; horregatik zure PHP fitxategiak non dauden ez dugu erakutsi.\n\nInstalatzaileak <code>.htaccess</code> fitxategi bat idatziko du bertan, baina horrek huts egiten badu zure datu base gordinera norbait sar daiteke.\nErabiltzaileen datu gordinak (helbide elektronikoak, pasahitzak), ezabatutako berrikusketa eta gainontzeko datu mugatuak ere barnean hartuz.\n\nDatu-basea beste nonbait jartzearen inguruan hausnartu, adibidez, <code>/var/lib/mediawiki/yourwiki</code>-n.",
        "config-oracle-def-ts": "Taula-toki lehenetsia:",
        "config-oracle-temp-ts": "Aldi baterako taula:",
        "config-type-mysql": "MySQL (edo bateragarria)",
        "config-mysql-myisam": "MyISAM",
        "config-mysql-myisam-dep": "<strong>Oharra:</strong> MyISAM MySQL biltegiratze-motor gisa aukeratu duzu, MediaWikirekin erabiltzeko gomendagarria ez dena honengatik:\n*taula blokeoak direla-eta gauza gutxi onartu ohi du\n*beste motore batzuek baino ustelkeria gehiago izateko aukerak ditu\n*MediaWiki-ren kode baseak ez du beti kudeatzen MyISAM behar bezala\n\nZure MySQL instalazioa InnoDB onartzen badu, hori aukeratzeko gomendatzen da.\nZure MySQL instalazioa InnoDB ez badu onartzen, baliteke bertsioa berritzeko ordua izatea.",
        "config-mysql-only-myisam-dep": "<strong> Oharra: </strong> MyISAM makinaren MySQL biltegiratze motarako bakarra da, eta hau ez da MediaWiki-rekin erabiltzeko gomendatzen, honengatik:\n* maiztasunez taula blokeoek konkurrentzia ez dute onartzen \n* Beste motore batzuek baino ustelkeria gehiago izaten dute\n* MediaWiki-ren kodekak ez du beti kudeatzen MyISAM behar bezala\n\nZure MySQL instalazioak ez du InnoDB onartzen, agian bertsio berritzeko ordua da.",
+       "config-mysql-engine-help": "<strong>InnoDB</strong> ia beti aukerarik onena da, konkurrentzia-laguntza ona duelako.\n\n<strong>MyISAM</strong> erabiltzaile bakarreko edo irakurketa bakarreko instalazioetan azkarragoa izan daiteke.\nMyISAM datu-basea gehiagokotan hondatuta ageri da InnoDB datu-baseareakin baino.",
        "config-mysql-charset": "Datu-basearen karaktere multzoa:",
        "config-mysql-binary": "Bitarra",
        "config-mysql-utf8": "UTF-8",
        "config-profile-no-anon": "Kontua sortzea beharrezkoa da",
        "config-profile-fishbowl": "Baimendutako editoreak bakarrik",
        "config-profile-private": "Wiki pribatua",
+       "config-profile-help": "Wikiak hobeto funtzionatzen dute ahalik eta jende gehiagok editatzeko aukera duenean.\nMediaWikian, erraza da azken aldaketak berrikustea eta erabiltzaile desegokiek egindako kalteak konpontzea.\n\nHala eta guztiz ere, askok MediaWiki rol ezberdinetarako baliagarri ikusi dute, nahiz eta batzuetan erraza ez izan wikiaren onureen inguruan konbentzitzea.\nBeraz, aukera zuk egiten duzu.\n\n<Strong>{{int:config-profile-wiki}}</strong> ereduak edonork edita dezake, nahiz eta saioa hasi.\n<Strong>{{int:config-profile-no-anon}}</strong> -ko wiki batek aparteko erantzukizuna ematen du, baina aldi baterako laguntzaileak alda ditzake.\n\n<Strong>{{int:config-profile-fishbowl}}</strong> planteamenduak aukera ematen du baimendutako erabiltzaileek editatzeko, baina publikoak orrialdeak ikusi ditzake, historiak barne.\n<Strong>{{int:config-profile-private}}</strong> bat soilik onartutako erabiltzaileei orriak ikusteko aukera ematen die, talde berak editatu ahal izateko.\n\nErabiltzaileen eskubideen ezarpen konplexuagoak eskuragarri daude instalazioa egin ondoren, ikus ezazu [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights eskuzko sarrera garrantzitsua].",
        "config-license": "Copyright eta lizentzia:",
        "config-license-none": "Ez jarri lizentzia orriaren baimenik",
        "config-license-cc-by-sa": "Creative Commons-eko esleipen-lizentzia",
        "config-license-cc-by": "Creative Commons Aitorpena",
+       "config-license-cc-by-nc-sa": "Creative Commons Attribution-NonCommercial-ShareAlike",
        "config-license-cc-0": "Creative Commons Zero (Jabari Publikoa)",
        "config-license-gfdl": "\nGNU Free Documentation License 1.3 edo berriagoa",
        "config-license-pd": "Domeinu Askea",
        "config-license-cc-choose": "Aukeratu Creative Commons lizentzia pertsonalizatua",
        "config-email-settings": "E-posta hobespenak",
        "config-enable-email": "Aktibatu irteerako emaila.",
+       "config-enable-email-help": "Lan egiteko email-a nahi baduzu, [http://www.php.net/manual/en/mail.configuration.php PHP's mail settings] ondo konfiguratu egin behar da. Email ezaugarririk ez baduzu nahi, hemen kendu ditzakezu.",
        "config-email-user": "Aktibatu erabiltzaileen arteko emaila.",
        "config-email-user-help": "Baimena eman erabiltzaileei beraien artean emailak bidaltzeko, lehentasunetan aukera aktibatuta badaukate.",
        "config-email-usertalk": "Aktibatu erabiltzaileen eztabaida orrien jakinarazpena",
        "config-email-auth": "Aktibatu emailaren autentifikazioa.",
        "config-email-auth-help": "Aukera hau aktibatuta badago, erabiltzaileak konfirmatu behar du bere emaila, sortzerakoen edota aldetzerakoan bidali zaion linka erabiltzen.\n\nBakarrik kautotaku emailak gai izango dira beste erabiltzaileen emailak jasotzeko edota jakinarazpen emailak aldatzeko.\n\nAukera hau hautatzea <strong>gomendagarria</strong> da Wiki publikoentzat, emaileen erramintien abusua dela eta.",
        "config-email-sender": "Itzuli helbide elektronikoa:",
+       "config-email-sender-help": "Idatzi helbide elektronikoa bueltan mezuak jasotzeko helbide elektroniko gisa.\nErreboteak bidaliko dira horra.\nPosta-zerbitzari askok gutxienez domeinu izenaren zati bat behar dute baliozkoa izan dadin.",
        "config-upload-settings": "Irudi eta fitxategi igoerak",
        "config-upload-enable": "Fitxategi igoera gaitu",
        "config-upload-help": "Fitxategiak kargatzeak zure zerbitzaria segurtasun arriskuei eragin diezaioke.\nInformazio gehiagorako, irakurri [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security security section] eskuliburuan.\n\nFitxategiak igotzea aktibatzeko, aldatu <code> images </ code> subdirektorio modua MediaWiki-ren erroko direktorioaren azpian, web zerbitzariak honela idatz dezake.\nOndoren, gaitu aukera hau.",
        "config-skins-must-enable-some": "Gutxienez aukeratu behar duzu ikusizko estilo bat aktibatzeko.",
        "config-skins-must-enable-default": "Lehenetsia bezala aukeratu duzun ikusizko estilo aktibatuta egon behar da.",
        "config-install-alreadydone": "<strong>Oharra:</strong>Badirudi MediaWikia instalatu daukazula eta berriz instalatzen saiatzen ari zarela.\n\nMesedez, hurrengo orrian jarraitu",
+       "config-install-begin": "\"{{int:config-continue}}\" sakatuz, MediaWiki instalazioa hasiko duzu.\nAldaketak oraindik egin nahi badituzu, sakatu \"{{int:config-back}}\".",
        "config-install-step-done": "egina",
        "config-install-step-failed": "Huts egin du",
        "config-install-extensions": "Luzapenak barne",
        "config-install-mainpage-failed": "Orri nagusia ezin izan da txertatu: $1",
        "config-install-done": "<strong>Zorionak!</strong>\nMediaWiki instalatu duzu.\n\nInstalatzaileak <code>LocalSettings.php</code> fitxategia sortu egin du. \nZure konfigurazio guztia darama.\n\nDeskargatu egin beharko duzu eta zure wiki instalazio oinarrian jarri (index.php-rako direktorio berean). Deskarga automakikoki hasi behar izan da.\n\nDeskargatzeko aukerarik ez bazaizu eskaini, edo kantzelatu egin baduzu, hurrengo linkean klikatu berrabiarazteko deskarga:\n\n$3\n\n<strong>Oharra:</strong> Orain ez baduzu egiten, sortutako konfigurazio fitxategi hau ez da erabilgarri egongo geroago instalazioa bertan behera uzten baduzu deskargatu gabe.\n\nBehin hori eginda, <strong>[$2 zure wikia sartu]</strong> ahal duzu.",
        "config-install-done-path": "<strong>Zorionak!</strong>\nMediaWiki instalatu duzu.\n\nInstalatzaileak sortu egin du <code>LocalSettings.php</code>\nZure konfigurazio guztia dauka.\n\nDeskargatu egin behar duzu eta jarri <code>$4</code> -ean . Deskarga automakikoki hasiko da.\n\nEz badizu deskargatzeko aukerarik eman, edo kantzalatu egin baduzu, hurrengo linkean klikatu berrabiatzeko:\n\n$3\n\n<strong>Oharra:</strong> Instalazio prozesuatik ateratzen bazara konfigurazio artxikoa deskargatu barik, gero ez da egongo eskuragarri.\n\nBehin hori eginda, <strong>[$2 enter your wiki]</strong> ahal duzu.",
+       "config-install-success": "MediaWiki arrakastaz instalatu da. Orain <$1$2> bisitatu dezakezu zure wikia ikusteko.\nGalderarik izanez gero, begiratu gure maiztasunez egiten diren galderen zerrenda:\n<https://www.mediawiki.org/wiki/Manual:FAQ> edo erabili orrialde honi lotuta dauden laguntza foroetako bat.",
        "config-download-localsettings": "Jaitsi <code>LocalSettings.php</code>",
        "config-help": "Laguntza",
        "config-help-tooltip": "sakatu zabaltzeko",
index 7f0718c..1c9cad5 100644 (file)
@@ -236,6 +236,12 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
        /** @var TransactionProfiler */
        protected $trxProfiler;
 
+       /**
+        * @var bool Whether writing is allowed on this connection.
+        *      Should be false for connections to replicas.
+        */
+       protected $allowWrite = true;
+
        /**
         * Constructor and database handle and attempt to connect to the DB server
         *
@@ -277,6 +283,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                $this->connLogger = $params['connLogger'];
                $this->queryLogger = $params['queryLogger'];
                $this->errorLogger = $params['errorLogger'];
+               $this->allowWrite = empty( $params['noWrite'] );
 
                // Set initial dummy domain until open() sets the final DB/prefix
                $this->currentDomain = DatabaseDomain::newUnspecified();
@@ -908,6 +915,13 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                }
 
                if ( $isWrite ) {
+                       if ( !$this->allowWrite ) {
+                               throw new DBError(
+                                       $this,
+                                       'Write operations are not allowed on this database connection!'
+                               );
+                       }
+
                        # In theory, non-persistent writes are allowed in read-only mode, but due to things
                        # like https://bugs.mysql.com/bug.php?id=33669 that might not work anyway...
                        $reason = $this->getReadOnlyReason();
@@ -2951,6 +2965,13 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
        }
 
        final public function begin( $fname = __METHOD__, $mode = self::TRANSACTION_EXPLICIT ) {
+               if ( !$this->allowWrite ) {
+                       throw new DBError(
+                               $this,
+                               'Write operations are not allowed on this database connection!'
+                       );
+               }
+
                // Protect against mismatched atomic section, transaction nesting, and snapshot loss
                if ( $this->mTrxLevel ) {
                        if ( $this->mTrxAtomicLevels ) {
@@ -3012,6 +3033,14 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
        }
 
        final public function commit( $fname = __METHOD__, $flush = '' ) {
+               if ( !$this->allowWrite ) {
+                       // we should never get here, since begin() would already throw
+                       throw new DBError(
+                               $this,
+                               'Write operations are not allowed on this database connection!'
+                       );
+               }
+
                if ( $this->mTrxLevel && $this->mTrxAtomicLevels ) {
                        // There are still atomic sections open. This cannot be ignored
                        $levels = implode( ', ', $this->mTrxAtomicLevels );
index 86c4335..b565b3b 100644 (file)
@@ -87,6 +87,9 @@ interface ILoadBalancer {
        /** @var int DB handle should have DBO_TRX disabled and the caller will leave it as such */
        const CONN_TRX_AUTO = 1;
 
+       /** Disable writing for the given connection. Used internally. Do not use with DB_MASTER! */
+       const CONN_NO_WRITE = 2;
+
        /**
         * Construct a manager of IDatabase connection objects
         *
index a9eaa99..eb288dd 100644 (file)
@@ -645,6 +645,12 @@ class LoadBalancer implements ILoadBalancer {
                $oldConnsOpened = $this->connsOpened; // connections open now
 
                if ( $i == self::DB_MASTER ) {
+                       if ( $flags & self::CONN_NO_WRITE ) {
+                               throw new InvalidArgumentException(
+                                       'Cannot set CONN_NO_WRITE flag on master connection!'
+                               );
+                       }
+
                        $i = $this->getWriterIndex();
                } else {
                        # Try to find an available server in any the query groups (in order)
@@ -655,6 +661,9 @@ class LoadBalancer implements ILoadBalancer {
                                        break;
                                }
                        }
+
+                       // Request no-write connection, even if $i == $this->getWriterIndex().
+                       $flags |= self::CONN_NO_WRITE;
                }
 
                # Operation-based index
@@ -671,6 +680,9 @@ class LoadBalancer implements ILoadBalancer {
                                $this->reportConnectionError();
                                return null; // not reached
                        }
+
+                       // Request no-write connection, even if $i == $this->getWriterIndex().
+                       $flags |= self::CONN_NO_WRITE;
                }
 
                # Now we have an explicit index into the servers array
@@ -791,6 +803,13 @@ class LoadBalancer implements ILoadBalancer {
                // a) those are usually set to implicitly use transaction rounds via DBO_TRX
                // b) those must support the use of explicit transaction rounds via beginMasterChanges()
                $autoCommit = ( ( $flags & self::CONN_TRX_AUTO ) == self::CONN_TRX_AUTO );
+               $noWrite = ( ( $flags & self::CONN_NO_WRITE ) == self::CONN_NO_WRITE );
+
+               if ( $noWrite && $i === $this->getWriterIndex() ) {
+                       // We can't disable writes on the master connection!
+                       // TODO: Wrap the master connection, so write operations fail!
+                       $noWrite = false;
+               }
 
                if ( $domain !== false ) {
                        // Connection is to a foreign domain
@@ -807,6 +826,7 @@ class LoadBalancer implements ILoadBalancer {
                                // Open a new connection
                                $server = $this->mServers[$i];
                                $server['serverIndex'] = $i;
+                               $server['noWrite'] = $noWrite;
                                $server['autoCommitOnly'] = $autoCommit;
                                $conn = $this->reallyOpenConnection( $server, false );
                                $host = $this->getServerName( $i );
@@ -863,6 +883,13 @@ class LoadBalancer implements ILoadBalancer {
                $dbName = $domainInstance->getDatabase();
                $prefix = $domainInstance->getTablePrefix();
                $autoCommit = ( ( $flags & self::CONN_TRX_AUTO ) == self::CONN_TRX_AUTO );
+               $noWrite = ( ( $flags & self::CONN_NO_WRITE ) == self::CONN_NO_WRITE );
+
+               if ( $noWrite && $i === $this->getWriterIndex() ) {
+                       // We can't disable writes on the master connection!
+                       // TODO: Wrap the master connection, so write operations fail!
+                       $noWrite = false;
+               }
 
                if ( $autoCommit ) {
                        $connFreeKey = self::KEY_FOREIGN_FREE_NOROUND;
@@ -910,6 +937,7 @@ class LoadBalancer implements ILoadBalancer {
                        $server['foreignPoolRefCount'] = 0;
                        $server['foreign'] = true;
                        $server['autoCommitOnly'] = $autoCommit;
+                       $server['noWrite'] = $noWrite;
                        $conn = $this->reallyOpenConnection( $server, $dbName );
                        if ( !$conn->isOpen() ) {
                                $this->connLogger->warning( __METHOD__ . ": connection error for $i/$domain" );
index c37566b..ac9cd84 100644 (file)
@@ -23,7 +23,6 @@
 use MediaWiki\Edit\PreparedEdit;
 use \MediaWiki\Logger\LoggerFactory;
 use \MediaWiki\MediaWikiServices;
-use Wikimedia\Assert\Assert;
 use Wikimedia\Rdbms\FakeResultWrapper;
 use Wikimedia\Rdbms\IDatabase;
 use Wikimedia\Rdbms\DBError;
@@ -672,7 +671,7 @@ class WikiPage implements Page, IDBAccessObject {
                        $revision = Revision::newFromPageId( $this->getId(), $latest, $flags );
                } else {
                        $dbr = wfGetDB( DB_REPLICA );
-                       $revision = Revision::newKnownCurrent( $dbr, $this->getTitle(), $latest );
+                       $revision = Revision::newKnownCurrent( $dbr, $this->getId(), $latest );
                }
 
                if ( $revision ) { // sanity
@@ -1265,11 +1264,8 @@ class WikiPage implements Page, IDBAccessObject {
                        $conditions['page_latest'] = $lastRevision;
                }
 
-               $revId = $revision->getId();
-               Assert::parameter( $revId > 0, '$revision->getId()', 'must be > 0' );
-
                $row = [ /* SET */
-                       'page_latest'      => $revId,
+                       'page_latest'      => $revision->getId(),
                        'page_touched'     => $dbw->timestamp( $revision->getTimestamp() ),
                        'page_is_new'      => ( $lastRevision === 0 ) ? 1 : 0,
                        'page_is_redirect' => $rt !== null ? 1 : 0,
index 10a338e..13d8a3a 100644 (file)
@@ -3498,7 +3498,13 @@ class Parser {
         * @return Revision|bool False if missing
         */
        public static function statelessFetchRevision( Title $title, $parser = false ) {
-               $rev = Revision::newKnownCurrent( wfGetDB( DB_REPLICA ), $title );
+               $pageId = $title->getArticleID();
+               $revId = $title->getLatestRevID();
+
+               $rev = Revision::newKnownCurrent( wfGetDB( DB_REPLICA ), $pageId, $revId );
+               if ( $rev ) {
+                       $rev->setTitle( $title );
+               }
 
                return $rev;
        }
index 6eddfc0..bebc188 100644 (file)
@@ -183,10 +183,12 @@ class ResourceLoaderWikiModule extends ResourceLoaderModule {
         * @return Content|null
         */
        protected function getContentObj( Title $title ) {
-               $revision = Revision::newKnownCurrent( wfGetDB( DB_REPLICA ), $title );
+               $revision = Revision::newKnownCurrent( wfGetDB( DB_REPLICA ), $title->getArticleID(),
+                       $title->getLatestRevID() );
                if ( !$revision ) {
                        return null;
                }
+               $revision->setTitle( $title );
                $content = $revision->getContent( Revision::RAW );
                if ( !$content ) {
                        wfDebugLog( 'resourceloader', __METHOD__ . ': failed to load content of JS/CSS page!' );
index 1639386..671ab6f 100644 (file)
@@ -290,16 +290,15 @@ class SpecialNewpages extends IncludableSpecialPage {
 
        /**
         * @param stdClass $result Result row from recent changes
-        * @param Title $title
-        * @return bool|Revision
+        * @return Revision|bool
         */
-       protected function revisionFromRcResult( stdClass $result, Title $title ) {
+       protected function revisionFromRcResult( stdClass $result ) {
                return new Revision( [
                        'comment' => CommentStore::newKey( 'rc_comment' )->getComment( $result )->text,
                        'deleted' => $result->rc_deleted,
                        'user_text' => $result->rc_user_text,
                        'user' => $result->rc_user,
-               ], 0, $title );
+               ] );
        }
 
        /**
@@ -314,7 +313,8 @@ class SpecialNewpages extends IncludableSpecialPage {
 
                // Revision deletion works on revisions,
                // so cast our recent change row to a revision row.
-               $rev = $this->revisionFromRcResult( $result, $title );
+               $rev = $this->revisionFromRcResult( $result );
+               $rev->setTitle( $title );
 
                $classes = [];
                $attribs = [ 'data-mw-revid' => $result->rev_id ];
index 64af5da..6d2ed73 100644 (file)
        "botpasswords-insert-failed": "Nun pudo amestase'l nome de bot «$1». ¿Taba añadíu yá?",
        "botpasswords-update-failed": "Nun pudo anovase'l nome de bot «$1». ¿Desaniciaríase?",
        "botpasswords-created-title": "Creóse la contraseña de bot",
-       "botpasswords-created-body": "Creóse la contraseña del bot llamáu «$1» del usuariu «$2».",
+       "botpasswords-created-body": "Creóse la contraseña del bot llamáu «$1» {{GENDER:$2|del usuariu|de la usuaria}} «$2».",
        "botpasswords-updated-title": "Anovóse la contraseña de bot",
-       "botpasswords-updated-body": "Anovóse la contraseña del bot llamáu «$1» del usuariu «$2».",
+       "botpasswords-updated-body": "Anovóse la contraseña del bot llamáu «$1» {{GENDER:$2|del usuariu|de la usuaria}} «$2».",
        "botpasswords-deleted-title": "Desanicióse la contraseña de bot",
-       "botpasswords-deleted-body": "Desanicióse la contraseña del bot llamáu «$1» del usuariu «$2».",
+       "botpasswords-deleted-body": "Desanicióse la contraseña del bot llamáu «$1» {{GENDER:$2|del usuariu|de la usuaria}} «$2».",
        "botpasswords-newpassword": "La nueva contraseña p'aniciar sesión con <strong>$1</strong> ye <strong>$2</strong>. <em>Por favor, rexistra esto pa referencies futures.</em> <br> (Pa los bots antiguos que necesiten que'l nome d'aniciu de sesión sía'l mesmu que'l nome d'usuariu, tamién pue usase <strong>$3</strong> como nome d'usuariu y <strong>$4</strong> como contraseña.)",
        "botpasswords-no-provider": "BotPasswordsSessionProvider nun ta disponible.",
        "botpasswords-restriction-failed": "Hai torgues de contraseña de bot que torgaron esti aniciu de sesión.",
        "recentchangesdays-max": "Máximo $1 {{PLURAL:$1|día|díes}}",
        "recentchangescount": "Númberu d'ediciones p'amosar de mou predetermináu:",
        "prefs-help-recentchangescount": "Incluye los cambios recientes, los historiales de páxines y los rexistros.",
-       "prefs-help-watchlist-token2": "Esta ye la clave secreta pa la canal de noticies web de la so llista de vixilancia.\nCualquiera que la sepa podrá lleer la so llista de vixilancia; nun la comparta.\n[[Special:ResetTokens|Calque equí si necesita reaniciala]].",
+       "prefs-help-watchlist-token2": "Esta ye la clave secreta pa la canal de noticies web de la to llista de vixilancia.\nCualquiera que la sepa podrá lleer la to llista de vixilancia; nun la compartas.\nSi lo necesites [[Special:ResetTokens|puedes reaniciala]].",
        "savedprefs": "Guardáronse les preferencies.",
        "savedrights": "Guardáronse los grupos {{GENDER:$1|del usuariu|de la usuaria}} $1.",
        "timezonelegend": "Estaya horaria:",
        "timezoneregion-indian": "Océanu Índicu",
        "timezoneregion-pacific": "Océanu Pacíficu",
        "allowemail": "Permitir qu'otros usuarios m'unvien correos",
+       "email-allow-new-users-label": "Permitir los correos de los usuarios nuevos",
        "email-blacklist-label": "Torgar qu'estos usuarios m'unvien correos electrónicos:",
        "prefs-searchoptions": "Buscar",
        "prefs-namespaces": "Espacios de nome",
        "right-siteadmin": "Candar y descandar la base de datos",
        "right-override-export-depth": "Esportar páxines, incluyendo páxines enllazaes fasta una fondura de 5",
        "right-sendemail": "Unviar corréu a otros usuarios",
+       "right-sendemail-new-users": "Unviar corréu electrónicu a usuarios ensin aiciones rexistraes",
        "right-managechangetags": "Crear y (des)activar [[Special:Tags|etiquetes]]",
        "right-applychangetags": "Aplicar [[Special:Tags|etiquetes]] xunto colos cambios propios",
        "right-changetags": "Amestar y desaniciar [[Special:Tags|etiquetes]] arbitraries en revisiones individuales y entraes del rexistru",
        "recentchanges-noresult": "Nengún cambiu nel periodu conseñáu coincide con esos criterios.",
        "recentchanges-timeout": "Esta gueta escosó'l tiempu. Escurque quieras tentar con parámetros de gueta distintos.",
        "recentchanges-network": "Nun se cargó nenguna resultancia por cuenta d'un problema técnicu. Tenta volver a cargar la páxina.",
+       "recentchanges-notargetpage": "Escribe'l nome d'una páxina más arriba pa ver los cambeos rellacionaos con esa páxina.",
        "recentchanges-feed-description": "Sigui nesta canal los últimos cambios de la wiki.",
        "recentchanges-label-newpage": "Esta edición creó una páxina nueva",
        "recentchanges-label-minor": "Esta ye una edición menor",
        "rcfilters-watchlist-showupdated": "Los cambeos fechos en páxines que nun visitasti desque se ficieron apaecen en <strong>negrina</strong>, con marcadores sólidos.",
        "rcfilters-preference-label": "Tapecer la versión meyorada de Cambios recién",
        "rcfilters-preference-help": "Revierte'l rediseñu de la interfaz de 2017 y toles ferramientes añadíes d'entós aquí.",
+       "rcfilters-filter-showlinkedfrom-label": "Amosar los cambios nes páxines enllazaes dende",
+       "rcfilters-filter-showlinkedfrom-option-label": "<strong>Páxines enllazaes dende</strong> la páxina seleicionada",
+       "rcfilters-filter-showlinkedto-label": "Amosar los cambios nes páxines qu'enllacen a",
+       "rcfilters-filter-showlinkedto-option-label": "<strong>Páxines qu'enllacen a</strong> la páxina seleicionada",
+       "rcfilters-target-page-placeholder": "Escribe'l nome de la páxina",
        "rcnotefrom": "Abaxo {{PLURAL:$5|tá'l cambiu|tan los cambios}} dende'l <strong>$3</strong>, a les <strong>$4</strong> (s'amuesen un máximu de <strong>$1</strong>).",
        "rclistfromreset": "Reaniciar la seleición de data",
        "rclistfrom": "Amosar los nuevos cambios dende'l $3 a les $2",
        "recentchangeslinked-feed": "Cambios rellacionaos",
        "recentchangeslinked-toolbox": "Cambios rellacionaos",
        "recentchangeslinked-title": "Cambios rellacionaos con \"$1\"",
-       "recentchangeslinked-summary": "Esta ye una llista de los caberos cambios fechos nes páxines enllaciaes dende una páxina determinada (o nos miembros d'una categoría determinada).\nLes páxines de [[Special:Watchlist|la to llista de siguimientu]] tán en <strong>negrina</strong>.",
+       "recentchangeslinked-summary": "Esscribe'l nome d'una páxina pa ver los cambios nes páxines enllazaes a o dende esa páxina. (Pa ver los miembros d'una categoría, escribe Categoría:Nome de la categoría). Los cambios nes páxines de [[Special:Watchlist|la to llista de siguimientu]] tán en <strong>negrina</strong>.",
        "recentchangeslinked-page": "Nome de la páxina:",
        "recentchangeslinked-to": "Amosar los cambios de les páxines qu'enllacen en cuenta de los de la páxina dada",
        "recentchanges-page-added-to-category": "[[:$1]] amestóse a la categoría",
index 0781fa8..276c1d8 100644 (file)
        "rcfilters-preference-label": "Схаваць палепшаную вэрсію апошніх зьменаў",
        "rcfilters-preference-help": "Адкатвае рэдызайн інтэрфэйсу 2017 году і ўсе інструмэнты, дададзеныя з таго часу.",
        "rcfilters-filter-showlinkedfrom-label": "Паказаць зьмены на старонках, на якія спасылаецца",
-       "rcfilters-filter-showlinkedfrom-option-label": "Паказаць зьмены старонак, на якія ёсьць спасылкі <strong>З</strong> старонкі",
+       "rcfilters-filter-showlinkedfrom-option-label": "<strong>Старонкі, на якія спасылаецца</strong> абраная старонка",
        "rcfilters-filter-showlinkedto-label": "Паказаць зьмены старонак, якія спасылаюцца на",
        "rcfilters-filter-showlinkedto-option-label": "Паказаць зьмены старонак, якія спасылаюцца <strong>НА</strong> старонку",
        "rcfilters-target-page-placeholder": "Увядзіце назву старонкі",
index 5a6cb01..9440d37 100644 (file)
        "passwordreset-emailelement": "Потребителско име: \n$1\n\nВременна парола: \n$2",
        "passwordreset-emailsentemail": "Ако електронната Ви поща е свързана със сметката Ви, на нея е изпратено писмо за възстановяване на паролата.",
        "passwordreset-emailsentusername": "Ако това потребителско име е свързано с електронна поща, е изпратено писмо за възстановяване на паролата.",
+       "passwordreset-nosuchcaller": "Източникът на извикването не съществува: $1",
        "passwordreset-invalidemail": "Неправилен email адрес",
        "passwordreset-nodata": "Не сте указали нито потребителско име, нито адрес на ел. поща",
        "changeemail": "Промяна или премахване на адреса за е-поща",
        "recentchangesdays-max": "(най-много $1 {{PLURAL:$1|ден|дни}})",
        "recentchangescount": "Брой показвани редакции по подразбиране:",
        "prefs-help-recentchangescount": "Това включва последните промени, историите на страниците и дневниците.",
-       "prefs-help-watchlist-token2": "Това е секретният ключ към уеб хранилката на вашия списък за наблюдение. Всеки, който го знае, би могъл да прегледа списъка ви за наблюдение, така че не го споделяйте. При нужда можете да го [[Специални:ResetTokens|изчистите]].",
+       "prefs-help-watchlist-token2": "Това е секретният ключ към уеб хранилката на вашия списък за наблюдение.\nВсеки, който го знае, би могъл да прегледа списъка ви за наблюдение, така че не го споделяйте.\nПри нужда можете да го [[Special:ResetTokens|изчистите]].",
        "savedprefs": "Настройките ви бяха съхранени.",
        "savedrights": "Потребителските групи на {{GENDER:$1|$1}} са запазени.",
        "timezonelegend": "Часова зона:",
        "rcfilters-filtergroup-lastRevision": "Текущи версии",
        "rcfilters-filter-lastrevision-label": "Текуща версия",
        "rcfilters-filter-lastrevision-description": "Последната промяна на страница.",
-       "rcfilters-filter-previousrevision-label": "Ð\9fо-Ñ\80анни Ð²ÐµÑ\80Ñ\81ии",
-       "rcfilters-filter-previousrevision-description": "Всички редакции, които не са последните на страница.",
+       "rcfilters-filter-previousrevision-label": "Ð\9dе Ð¿Ð¾Ñ\81леднаÑ\82а Ð²ÐµÑ\80Ñ\81иÑ\8f",
+       "rcfilters-filter-previousrevision-description": "Всички редакции, които не са „последната версия“.",
        "rcfilters-filter-excluded": "Изключени",
        "rcfilters-tag-prefix-namespace-inverted": "<strong>:не</strong> $1",
        "rcfilters-exclude-button-off": "Изключване на избраните",
        "backend-fail-read": "Файлът „$1“ не може да бъде прочетен.",
        "backend-fail-create": "Файлът „$1“ не може да бъде съхранен.",
        "backend-fail-maxsize": "Файлът „$1“ не може да бъде съхранен, тъй като размерът му надвишава {{PLURAL:$2|един байт|$2 байт}}.",
-       "backend-fail-connect": "Не е възможно свързването към бекенда за съхранение „1“.",
+       "backend-fail-connect": "Не е възможно свързването към бекенда за съхранение „$1“.",
        "backend-fail-internal": "Възникна неизвестна грешка в бекенда за съхранение „1“.",
        "zip-file-open-error": "Възникна грешка при отваряне на файла за проверка на ZIP.",
        "zip-wrong-format": "Указаният файл не е ZIP файл.",
        "listfiles_size": "Размер",
        "listfiles_description": "Описание",
        "listfiles_count": "Версии",
-       "listfiles-show-all": "Включване на старите версии на изображенията",
+       "listfiles-show-all": "Включване на стари версии на файлове",
        "listfiles-latestversion": "Текущата версия",
        "listfiles-latestversion-yes": "Да",
        "listfiles-latestversion-no": "Не",
        "filerevert-comment": "Причина:",
        "filerevert-defaultcomment": "Възвръщане към версия от $2, $1 ($3)",
        "filerevert-submit": "Възвръщане",
-       "filerevert-success": "Файлът '''[[Media:$1|$1]]''' беше възвърнат към [$4 версия от $3, $2].",
+       "filerevert-success": "Файлът <strong>[[Media:$1|$1]]</strong> беше възвърнат към [$4 версия от $3, $2].",
        "filerevert-badversion": "Не съществува предишна локална версия на файла със зададения времеви отпечатък.",
        "filedelete": "Изтриване на $1",
        "filedelete-legend": "Изтриване на файл",
        "filedelete-comment": "Причина:",
        "filedelete-submit": "Изтриване",
        "filedelete-success": "Файлът '''$1''' беше изтрит.",
-       "filedelete-success-old": "Версията на '''[[Media:$1|$1]]''' към $3, $2 е била изтрита.",
-       "filedelete-nofile": "Файлът '''$1''' не съществува.",
+       "filedelete-success-old": "Версията на <strong>[[Media:$1|$1]]</strong> към $3, $2 е била изтрита.",
+       "filedelete-nofile": "Файлът <strong>$1</strong> не съществува.",
        "filedelete-nofile-old": "Не съществува архивна версия на '''$1''' с указаните параметри.",
        "filedelete-otherreason": "Друга/допълнителна причина:",
        "filedelete-reason-otherlist": "Друга причина",
        "movereason": "Причина:",
        "revertmove": "връщане",
        "delete_and_move_text": "Целевата страница „[[:$1]]“ вече съществува.\nИскате ли да я изтриете, за да освободите място за преместването?",
-       "delete_and_move_confirm": "Да, искам да изтрия тази страница.",
+       "delete_and_move_confirm": "Да, искам да изтрия тази страница",
        "delete_and_move_reason": "Изтрита, за да се освободи място за преместване от „[[$1]]“",
        "selfmove": "Страницата не може да бъде преместена, тъй като целевото име съвпада с първоначалното ѝ заглавие.",
        "immobile-source-namespace": "Не могат да се местят страници в именно пространство „$1“",
        "import-nonewrevisions": "Не са импортирани версии (всички вече съществуват или са пропуснати поради грешки).",
        "xml-error-string": "$1 на ред $2, колона $3 (байт $4): $5",
        "import-upload": "Качване на XML данни",
-       "import-token-mismatch": "Загуба на данните за текущата сесия.\n\nМоже би сте излезли от системата. <strong>Моля, уверете се, че сте влезли в профила си и опитайте отново</strong>.\nАко все още не работи, опитайте да [[Special:UserLogout|излезете]] и да влезете отново, също така проверете дали браузърът ви позволява бисквитки от този сайт.",
+       "import-token-mismatch": "Загуба на данните за текущата сесия.\n\nМоже би сте излезли от системата. '''Моля, уверете се, че сте влезли в профила си и опитайте отново'''.\nАко все още не работи, опитайте да [[Special:UserLogout|излезете]] и да влезете отново, също така проверете дали браузърът ви позволява бисквитки от този сайт.",
        "import-invalid-interwiki": "Не може да бъде извършено внасяне от посоченото уики.",
        "import-error-edit": "Страницата „$1“ не беше внесена, тъй като нямате права да я редактирате.",
        "import-error-create": "Страницата „$1“ не беше внесена, тъй като нямате права да я създадете.",
        "exif-software": "Използван софтуер",
        "exif-artist": "Автор",
        "exif-copyright": "Притежател на авторското право",
-       "exif-exifversion": "Exif версия",
+       "exif-exifversion": "Версия на Exif",
        "exif-flashpixversion": "Поддържана версия на Flashpix",
        "exif-colorspace": "Цветово пространство",
        "exif-componentsconfiguration": "Значение на всеки компонент",
index 21c5df1..46c8b60 100644 (file)
        "tooltip-n-mainpage-description": "Коьрта агӀона дехьа гӀо",
        "tooltip-n-portal": "Оцу кхолламах, мичахь хlу йу лаьташ а хlудалур ду шуьга",
        "tooltip-n-currentevents": "ДӀаоьхуш болу хаамашна могӀам",
-       "tooltip-n-recentchanges": "ТӀаьххьаралера хийцаман могӀам",
+       "tooltip-n-recentchanges": "ТӀаьххьара хийцамийн могӀам",
        "tooltip-n-randompage": "Хьажа цахууш нисйеллачу агlоне",
        "tooltip-n-help": "ГӀоде меттиг",
        "tooltip-t-whatlinkshere": "ХӀокху агӀонан тӀе хьажийна йолу массо агӀонийн могӀам",
        "exif-focalplaneresolutionunit": "Магоран фокалан дустар",
        "exif-sensingmethod": "Сенсоран тайп",
        "exif-filesource": "Файлан хьост",
+       "exif-scenetype": "Сценан тайпа",
        "exif-customrendered": "Кхин тӀе кечдар",
        "exif-exposuremode": "Сурт доккхуш йолу серлон хьал харжар",
        "exif-whitebalance": "Къайн баланс",
        "tag-filter": "[[Special:Tags|Билгалонаш]] луьттург:",
        "tag-filter-submit": "Литта",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|1=Билгало|Билгалонаш}}]]: $2)",
+       "tag-mw-new-redirect": "Керла дӀасахьажорг",
        "tag-mw-rollback": "Юхаяккха",
        "tags-title": "Билгалонаш",
        "tags-intro": "ХӀокху агӀона чохь гойтуш бу билгалонийн могӀам царца программин латторо билгал доху нисдарш, кхин билгалонийн маьӀна а.",
index 1836c65..8935e33 100644 (file)
        "rcfilters-preference-label": "Die verbesserte Version der Letzten Änderungen ausblenden",
        "rcfilters-preference-help": "Macht die Neugestaltung der Oberfläche aus dem Jahr 2017 und alle seitdem hinzugefügten Werkzeuge wieder rückgängig.",
        "rcfilters-filter-showlinkedfrom-label": "Änderungen auf Seiten anzeigen, die verlinkt sind von",
-       "rcfilters-filter-showlinkedfrom-option-label": "Änderungen auf Seiten anzeigen, die <strong>VON</strong> einer Seite verlinkt sind.",
-       "rcfilters-filter-showlinkedto-label": "Änderungen auf Seiten anzeigen, die verlinkt sind auf",
-       "rcfilters-filter-showlinkedto-option-label": "Änderungen auf Seiten anzeigen, die <strong>AUF</strong> eine Seite verlinkt sind.",
+       "rcfilters-filter-showlinkedfrom-option-label": "<strong>Seiten</strong>, die <strong>von</strong> der ausgewählten Seite <strong>verlinkt</strong> sind",
+       "rcfilters-filter-showlinkedto-label": "Änderungen auf Seiten anzeigen, die verlinken auf",
+       "rcfilters-filter-showlinkedto-option-label": "<strong>Seiten</strong>, die <strong>auf</strong> die ausgewählte Seite <strong>verlinken</strong>",
        "rcfilters-target-page-placeholder": "Einen Seitennamen eingeben",
        "rcnotefrom": "Angezeigt {{PLURAL:$5|wird die Änderung|werden die Änderungen}} seit <strong>$3, $4</strong> (max. <strong>$1</strong> Einträge).",
        "rclistfromreset": "Datumsauswahl zurücksetzen",
index e70d1c8..bbaa3bc 100644 (file)
        "nstab-main": "Perre",
        "nstab-user": "Pera karberi",
        "nstab-media": "Perra medya",
-       "nstab-special": "Pera hısusi",
+       "nstab-special": "Perra xısusiye",
        "nstab-project": "Perra proji",
        "nstab-image": "Dosya",
        "nstab-mediawiki": "Mesac",
index 9daaeb3..b7407e1 100644 (file)
        "rcfilters-watchlist-showupdated": "Los cambios hechos a páginas que no has visitado desde que se efectuaron aparecen en <strong>negrita</strong>, acompañados de marcadores sólidos.",
        "rcfilters-preference-label": "Ocultar la versión mejorada de Cambios recientes",
        "rcfilters-preference-help": "Revierte el rediseño de interfaz de 2017 y desactiva todas las herramientas añadidas desde entonces.",
+       "rcfilters-filter-showlinkedfrom-option-label": "<strong>Páginas enlazadas desde</strong> la página seleccionada",
+       "rcfilters-filter-showlinkedto-label": "Mostrar cambios en páginas que enlazan a",
+       "rcfilters-filter-showlinkedto-option-label": "<strong>Páginas que enlazan hacia</strong> la página seleccionada",
        "rcfilters-target-page-placeholder": "Escribe el nombre de una página",
        "rcnotefrom": "Debajo {{PLURAL:$5|aparece el cambio|aparecen los cambios}} desde <strong>$3, $4</strong> (se muestran hasta <strong>$1</strong>).",
        "rclistfromreset": "Restablecer selección de fecha",
index dbc52f6..b0ad41e 100644 (file)
        "emailccsubject": "Kopio lähettämästäsi viestistä osoitteeseen $1: $2",
        "emailsent": "Sähköposti lähetetty",
        "emailsenttext": "Sähköpostiviestisi on lähetetty.",
-       "emailuserfooter": "Tämän sähköpostin {{GENDER:$1|lähetti}} $1 vastaanottajalle {{GENDER:$2|$2}} käyttämällä ”{{int:emailuser}}” -toimintoa {{GRAMMAR:inessive|{{SITENAME}}}}. Jos vastaat tähän sähköpostiin, sähköpostisi lähetetään suoraan {{GENDER:$1|alkuperäiselle lähettäjälle}}, paljastaen {{GENDER:$2|sinun}} sähköpostiosoitteesi {{GENDER:$1|hänelle}}.",
+       "emailuserfooter": "Tämän sähköpostin {{GENDER:$1|lähetti}} $1 vastaanottajalle {{GENDER:$2|$2}} käyttämällä ”{{int:emailuser}}” -toimintoa {{GRAMMAR:inessive|{{SITENAME}}}}. Jos vastaat tähän sähköpostiin, sinun sähköpostiviestisi lähetetään suoraan {{GENDER:$1|alkuperäiselle lähettäjälle}} ja samalla paljastetaan {{GENDER:$2|sinun}} sähköpostiosoitteesi {{GENDER:$1|hänelle}}.",
        "usermessage-summary": "Jätetään järjestelmäviesti.",
        "usermessage-editor": "Järjestelmäviestittäjä",
        "watchlist": "Tarkkailulista",
index eb6f531..ae0b09a 100644 (file)
        "rcfilters-preference-label": "Masquer la version améliorée des modifications récentes",
        "rcfilters-preference-help": "Désactive la version 2017 de l'interface ainsi que de tous les outils ajoutés alors et depuis.",
        "rcfilters-filter-showlinkedfrom-label": "Montrer les modifications des pages liées depuis",
-       "rcfilters-filter-showlinkedfrom-option-label": "Montrer les modifications des pages liées <strong>DEPUIS</strong> une page",
-       "rcfilters-filter-showlinkedto-label": "Montrer les modifications des pages liées vers",
-       "rcfilters-filter-showlinkedto-option-label": "Montrer les modifications des pages liées <strong>VERS</strong> une page",
+       "rcfilters-filter-showlinkedfrom-option-label": "<strong>Pages liées depuis</strong> la page sélectionnée",
+       "rcfilters-filter-showlinkedto-label": "Montrer les modifications des pages pointant vers",
+       "rcfilters-filter-showlinkedto-option-label": "<strong>Pages pointant vers</strong> la page sélectionnée",
        "rcfilters-target-page-placeholder": "Entrer un nom de page",
        "rcnotefrom": "Ci-dessous {{PLURAL:$5|la modification effectuée|les modifications effectuées}} depuis le <strong>$3, $4</strong> (affichées jusqu’à <strong>$1</strong>).",
        "rclistfromreset": "Réinitialiser la sélection de la date",
index 0175d2d..34f0b79 100644 (file)
        "rcfilters-preference-label": "הסתרת הגרסה המשופרת של השינויים האחרונים",
        "rcfilters-preference-help": "ביטול של העיצוב מחדש של הממשק (שבוצע בשנת 2017) ושל כל הכלים שנוספו אז ומאז.",
        "rcfilters-filter-showlinkedfrom-label": "הצגת שינויים בדפים המקושרים מ",
-       "rcfilters-filter-showlinkedfrom-option-label": "הצגת שינויים בדפים המקושרים <strong>מתוך</strong> דף",
-       "rcfilters-filter-showlinkedto-label": "×\94צ×\92ת ×©×\99× ×\95×\99×\99×\9d ×\91×\93פ×\99×\9d ×\94×\9eקשר×\99×\9d ×\9c",
-       "rcfilters-filter-showlinkedto-option-label": "הצגת שינויים בדפים המקשרים <strong>אל</strong> דף",
+       "rcfilters-filter-showlinkedfrom-option-label": "<strong>דפים שמקושרים מתוך</strong> הדף שנבחר",
+       "rcfilters-filter-showlinkedto-label": "×\94צ×\92ת ×©×\99× ×\95×\99×\99×\9d ×\91×\93פ×\99×\9d ×©×\9eקשר×\99×\9d ×\90×\9c ×\93×£",
+       "rcfilters-filter-showlinkedto-option-label": "<strong>דפים שמקושרים אל</strong> הדף שנבחר",
        "rcfilters-target-page-placeholder": "הקלדת שם דף",
        "rcnotefrom": "להלן {{PLURAL:$5|השינוי שבוצע|השינויים שבוצעו}} מאז <strong>$3, $4</strong> (מוצגים עד <strong>$1</strong>).",
        "rclistfromreset": "איפוס בחירת התאריך",
index 38fd1ea..d739d97 100644 (file)
        "protect-cascadeon": "Ova stranica je zaštićena jer je uključena u {{PLURAL:$1|stranicu, koja ima|stranice, koje imaju|stranice, koje imaju}} uključenu prenosivu zaštitu. Možete promijeniti stupanj zaštite ove stranice, no to neće utjecati na prenosivu zaštitu.",
        "protect-default": "Omogućeno svim suradnicima",
        "protect-fallback": "Potrebno je imati \"$1\" ovlasti",
-       "protect-level-autoconfirmed": "Onemogućeno novim i neprijavljenim suradnicima",
+       "protect-level-autoconfirmed": "Dopušteno samo autopotvrđenima",
        "protect-level-sysop": "Samo administratori",
        "protect-summary-cascade": "prenosiva zaštita",
        "protect-expiring": "istječe $1 (UTC)",
index e5ae975..f6fd689 100644 (file)
        "rcfilters-preference-label": "Nascondi la versione migliorata delle ultime modifiche",
        "rcfilters-preference-help": "Ripristina la riprogettazione dell'interfaccia 2017 e tutti gli strumenti aggiunti allora e da allora.",
        "rcfilters-filter-showlinkedfrom-label": "Mostra le modifiche alle pagine collegate da",
-       "rcfilters-filter-showlinkedfrom-option-label": "Mostra le modifiche alle pagine collegate <strong>DA</strong> una pagina",
-       "rcfilters-filter-showlinkedto-label": "Mostra le modifiche alle pagine collegate a",
-       "rcfilters-filter-showlinkedto-option-label": "Mostra le modifiche alle pagine collegate <strong>A</strong> una pagina",
+       "rcfilters-filter-showlinkedfrom-option-label": "<strong>Pagine con collegamenti da</strong> la pagina selezionata",
+       "rcfilters-filter-showlinkedto-label": "Mostra le modifiche alle pagine che colegano a",
+       "rcfilters-filter-showlinkedto-option-label": "<strong>Pagine con collegamenti a</strong> la pagina selezionata",
        "rcnotefrom": "Di seguito {{PLURAL:$5|è elencata la modifica apportata|sono elencate le modifiche apportate}} a partire da <strong>$3, $4</strong> (mostrate fino a <strong>$1</strong>).",
        "rclistfromreset": "Reimposta la selezione della data",
        "rclistfrom": "Mostra le nuove modifiche a partire daː $2, $3",
index 6b3e1b3..72963d6 100644 (file)
        "yourtext": "編集中の文章",
        "storedversion": "保存された版",
        "editingold": "<strong>警告: このページの古い版を編集しています。</strong>\n保存すると、この版以降になされた変更がすべて失われます。",
+       "unicode-support-fail": "お使いのブラウザはUnicodeをサポートしていないようです。 ページを編集する必要があるため、編集内容は保存されませんでした。",
        "yourdiff": "差分",
        "copyrightwarning": "{{SITENAME}}への投稿はすべて、$2 (詳細は$1を参照)のもとで公開したと見なされることにご注意ください。\n自分が書いたものが他の人に容赦なく編集され、自由に配布されるのを望まない場合は、ここに投稿しないでください。<br />\nまた、投稿するのは、自分で書いたものか、パブリック ドメインまたはそれに類するフリーな資料からの複製であることを約束してください。\n<strong>著作権保護されている作品は、許諾なしに投稿しないでください!</strong>",
        "copyrightwarning2": "{{SITENAME}}への投稿はすべて、他の投稿者によって編集、変更、除去される場合があります。\n自分が書いたものが他の人に容赦なく編集されるのを望まない場合は、ここに投稿しないでください。<br />\nまた、投稿するのは、自分で書いたものか、パブリック ドメインまたはそれに類するフリーな資料からの複製であることを約束してください(詳細は$1を参照)。\n<strong>著作権保護されている作品は、許諾なしに投稿しないでください!</strong>",
        "right-siteadmin": "データベースをロックおよびロック解除",
        "right-override-export-depth": "リンク先ページを5階層まで含めて書き出す",
        "right-sendemail": "他の利用者にメールを送信",
+       "right-sendemail-new-users": "ログに記録されていないユーザーにメールを送信する",
        "right-managechangetags": "[[Special:Tags|タグ]]の作成、有効化および無効化",
        "right-applychangetags": "自分の編集に[[Special:Tags|タグ]]を適用する",
        "right-changetags": "個々の版と記録項目の任意の[[Special:Tags|タグ]]の追加と削除",
        "recentchanges-legend": "最近の更新のオプション",
        "recentchanges-summary": "このページでは、このウィキでの最近の更新を確認できます。",
        "recentchanges-noresult": "指定した条件に該当する期間の変更はありません。",
+       "recentchanges-timeout": "この検索はタイムアウトしました。 さまざまな検索パラメータを試してみることもできます。",
+       "recentchanges-network": "技術的なエラーのため、結果をロードできませんでした。ページをリフレッシュしてみてください。",
+       "recentchanges-notargetpage": "上記のページ名を入力すると、そのページに関連する変更が表示されます。",
        "recentchanges-feed-description": "このフィードでこのウィキの最近の更新を追跡できます。",
        "recentchanges-label-newpage": "ページの新規作成",
        "recentchanges-label-minor": "細部の編集",
        "rcfilters-activefilters": "絞り込み",
        "rcfilters-advancedfilters": "詳細フィルター",
        "rcfilters-limit-title": "表示件数の変更",
+       "rcfilters-date-popup-title": "検索期間",
        "rcfilters-days-title": "日数",
        "rcfilters-hours-title": "時間",
        "rcfilters-days-show-days": "$1 {{PLURAL:$1|日}}",
        "rcfilters-savedqueries-apply-and-setdefault-label": "既定フィルターを作成",
        "rcfilters-savedqueries-cancel-label": "キャンセル",
        "rcfilters-savedqueries-add-new-title": "現在のフィルター設定を保存する",
+       "rcfilters-savedqueries-already-saved": "これらのフィルタは既に保存されています。設定を変更して、新しい保存フィルタを作成します。",
        "rcfilters-restore-default-filters": "標準設定の絞り込み条件を適用",
        "rcfilters-clear-all-filters": "すべてのフィルターをクリア",
        "rcfilters-show-new-changes": "最新の変更を表示",
        "rcfilters-invalid-filter": "無効なフィルター",
        "rcfilters-empty-filter": "絞り込みは行われていません。全ての項目が表示さます。",
        "rcfilters-filterlist-title": "フィルター",
-       "rcfilters-filterlist-whatsthis": "ã\81\93ã\82\8cã\81¯ä½\95?",
+       "rcfilters-filterlist-whatsthis": "ã\81\93ã\82\8cã\82\89ã\81¯ã\81©ã\81®ã\82\88ã\81\86ã\81«æ©\9fè\83½ã\81\97ã\81¾ã\81\99ã\81\8b?",
        "rcfilters-filterlist-feedbacklink": "(新しい)絞り込み機能に関するフィードバックをお願いします",
        "rcfilters-highlightbutton-title": "該当項目を強調表示する",
        "rcfilters-highlightmenu-title": "色を選ぶ",
        "rcfilters-filter-watchlist-watchednew-description": "ウォッチリストに登録されていて、前回訪れた後に更新があったページ。",
        "rcfilters-filter-watchlist-notwatched-label": "ウォッチリスト登録外",
        "rcfilters-filter-watchlist-notwatched-description": "ウォッチリストに登録されているページ以外の全ての変更。",
+       "rcfilters-filter-watchlistactivity-unseen-label": "保存していません!",
+       "rcfilters-filter-watchlistactivity-unseen-description": "ウォッチリストに登録されていて、前回訪れた後に更新があったページ。",
+       "rcfilters-filter-watchlistactivity-seen-label": "最近の更新",
        "rcfilters-filtergroup-changetype": "変更の種類",
        "rcfilters-filter-pageedits-label": "ページの編集",
        "rcfilters-filter-pageedits-description": "ウィキの本文、議論、カテゴリの説明などの編集",
        "rcfilters-watchlist-showupdated": "最終訪問以降に変更されたページは、塗りつぶされた丸印と一緒に、<strong>太字</strong>で表示されます。",
        "rcfilters-preference-label": "最近の更新の改善版を隠す",
        "rcfilters-preference-help": "2017年のインターフェース更新、当時追加したや以来の新しいツールの使用を断る。",
+       "rcfilters-filter-showlinkedfrom-label": "リンク先ページの変更を表示する",
        "rcnotefrom": "以下は<strong>$3 $4</strong>以降の{{PLURAL:$5|更新です}} (最大 <strong>$1</strong> 件)。",
        "rclistfromreset": "日時指定をリセット",
        "rclistfrom": "$3の$2以降の更新を表示する",
index 9ffc4f9..225857c 100644 (file)
        "action-upload_by_url": "laste påå denne fila frå ein URL",
        "action-writeapi": "bruke skrive-API",
        "action-delete": "slette denne sida",
-       "action-deleterevision": "slette denne endringa",
+       "action-deleterevision": "slette versjonar",
        "action-deletedhistory": "sjå slettehistorikken til sida",
+       "action-deletedtext": "sjå teksten til sletta versjonar",
        "action-browsearchive": "søke i sletta sider",
        "action-undelete": "attopprette denne sida",
        "action-suppressrevision": "sjå og attopprette denne skjulte endringa",
        "table_pager_limit_submit": "Gå",
        "table_pager_empty": "Ingen resultat",
        "autosumm-blank": "Tømde sida",
-       "autosumm-replace": "Erstattar innhaldet på sida med «$1»",
+       "autosumm-replace": "Erstatta innhaldet på sida med «$1»",
        "autoredircomment": "Omdirigerer til [[$1]]",
+       "autosumm-removed-redirect": "Fjerna omdirigering til [[$1]]",
+       "autosumm-changed-redirect-target": "Endra omdirigeringsmål frå [[$1]] til [[$2]]",
        "autosumm-new": "Oppretta sida med «$1»",
        "autosumm-newblank": "Oppretta tom side",
        "lag-warn-normal": "Endringar som er nyare enn {{PLURAL:$1|sekund|sekund}} er ikkje viste på denne lista.",
        "tag-filter-submit": "Filtrer",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Merke}}]]: $2)",
        "tag-mw-contentmodelchange": "endring av innhaldsmodell",
+       "tag-mw-new-redirect": "Ny omdirigering",
        "tag-mw-removed-redirect": "Fjerna omdirigering",
        "tag-mw-changed-redirect-target": "Omdirigeringsmål endra",
        "tag-mw-changed-redirect-target-description": "Endringar som endrar målet til ei omdirigering",
        "compare-invalid-title": "Tittelen du oppgav er ugild.",
        "compare-title-not-exists": "Tittelen du oppgav finst ikkje.",
        "compare-revision-not-exists": "Versjonen du oppgav finst ikkje.",
-       "diff-form": "eit '''skjema'''",
+       "diff-form": "Skilnader",
+       "permanentlink": "Fast lenkje",
+       "permanentlink-revid": "Versjons-ID",
+       "permanentlink-submit": "Gå til versjon",
        "dberr-problems": "Nettstaden har tekniske problem.",
        "dberr-again": "Venta nokre minutt og last sida inn på nytt.",
        "dberr-info": "(Kan ikkje kontakta databasetenaren: $1)",
        "logentry-delete-delete": "$1 {{GENDER:$2|sletta}} sida $3",
        "logentry-delete-delete_redir": "$1 {{GENDER:$2|sletta}} omdirigeringa $3 gjennom overskriving",
        "logentry-delete-restore": "$1 {{GENDER:$2|attoppretta}} sida $3 ($4)",
+       "restore-count-revisions": "{{PLURAL:$1|éin versjon|$1 versjonar}}",
        "logentry-delete-event": "$1 {{GENDER:$2|endra}} synlegdomen av {{PLURAL:$5|éi loggoppføring|$5 loggoppføringar}} på $3: $4",
        "logentry-delete-revision": "$1 {{GENDER:$2|endra}} synlegdomen til {{PLURAL:$5|éin versjon|$5 versjonar}} på sida $3: $4",
        "logentry-delete-event-legacy": "$1 {{GENDER:$2|endra}} synlegdomen til loggoppføringar på $3",
        "date-range-to": "Til dato:",
        "randomrootpage": "Tilfeldig rotside",
        "log-action-filter-rights": "Type endring av rettar:",
+       "log-action-filter-delete-delete_redir": "Overskriving av omdirigering",
+       "log-action-filter-delete-restore": "Attoppretting av side",
+       "log-action-filter-delete-revision": "Versjonssletting",
+       "log-action-filter-move-move": "Flytting utan overskriving av omdirigeringar",
+       "log-action-filter-move-move_redir": "Flytting med overskriving av omdirigeringar",
+       "log-action-filter-suppress-revision": "Versjonsundertrykking",
        "authmanager-userdoesnotexist": "Brukarkontoen «$1» er ikkje oppretta.",
        "authmanager-provider-temporarypassword": "Mellombels passord",
        "userjsispublic": "Merk: JavaScript-undersider bør ikkje innehalda konfidensielle data sidan dei er synlege for andre brukarar.",
-       "usercssispublic": "Merk: CSS-undersider bør ikkje innehalda konfidensielle data sidan dei er synlege for andre brukarar."
+       "usercssispublic": "Merk: CSS-undersider bør ikkje innehalda konfidensielle data sidan dei er synlege for andre brukarar.",
+       "revid": "versjon $1"
 }
index 45b2dd6..44e8dd5 100644 (file)
        "rcfilters-preference-label": "Ocultar a versão melhorada das Mudanças Recentes",
        "rcfilters-preference-help": "Reverte o redesenho da interface de 2017 e todas as ferramentas adicionadas na altura e desde então.",
        "rcfilters-filter-showlinkedfrom-label": "Mostrar alterações nas páginas ligadas de",
-       "rcfilters-filter-showlinkedfrom-option-label": "Mostrar mudanças de páginas <strong>PARA AS QUAIS</strong> uma página contém hiperligações",
-       "rcfilters-filter-showlinkedto-label": "Mostrar mudanças de páginas que contêm hiperligações para a página",
-       "rcfilters-filter-showlinkedto-option-label": "Mostrar mudanças de páginas <strong>QUE CONTÊM</strong> hiperligações para uma página",
+       "rcfilters-filter-showlinkedfrom-option-label": "<strong>Páginas ligadas da</strong> página selecionada",
+       "rcfilters-filter-showlinkedto-label": "Mostrar alterações nas páginas que ligam para",
+       "rcfilters-filter-showlinkedto-option-label": "<strong>Páginas que ligam para</strong> página selecionada",
        "rcfilters-target-page-placeholder": "Digite o nome de uma página",
        "rcnotefrom": "Abaixo {{PLURAL:$5|é a mudança|são as mudanças}} desde <strong>$3, $4</strong> (up to <strong>$1</strong> shown).",
        "rclistfromreset": "Redefinir seleção da data",
index 2cc0f97..49ff907 100644 (file)
        "timezoneregion-indian": "Индийский океан",
        "timezoneregion-pacific": "Тихий океан",
        "allowemail": "Разрешить другим участникам отправлять мне электронную почту",
+       "email-allow-new-users-label": "Разрешить электронные письма от совсем новых участников",
        "email-blacklist-label": "Запретить этим участникам отправлять мне электронную почту:",
        "prefs-searchoptions": "Поиск",
        "prefs-namespaces": "Пространства имён",
        "recentchangeslinked-feed": "Связанные правки",
        "recentchangeslinked-toolbox": "Связанные правки",
        "recentchangeslinked-title": "Связанные правки для «$1»",
-       "recentchangeslinked-summary": "ЭÑ\82о Ñ\81пиÑ\81ок Ð½ÐµÐ´Ð°Ð²Ð½Ð¸Ñ\85 Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ð¹ Ð² Ñ\81Ñ\82Ñ\80аниÑ\86аÑ\85, Ð½Ð° ÐºÐ¾Ñ\82оÑ\80Ñ\8bе Ñ\81Ñ\81Ñ\8bлаеÑ\82Ñ\81Ñ\8f Ñ\83казаннаÑ\8f Ñ\81Ñ\82Ñ\80аниÑ\86а (или Ð²Ñ\85одÑ\8fÑ\89иÑ\85 Ð² Ñ\83казаннÑ\83Ñ\8e ÐºÐ°Ñ\82егоÑ\80иÑ\8e).\nСÑ\82Ñ\80аниÑ\86Ñ\8b, Ð²Ñ\85одÑ\8fÑ\89ие Ð² [[Special:Watchlist|ваÑ\88 Ñ\81пиÑ\81ок Ð½Ð°Ð±Ð»Ñ\8eдениÑ\8f]] '''вÑ\8bделенÑ\8b'''.",
+       "recentchangeslinked-summary": "Ð\92ведиÑ\82е Ð¸Ð¼Ñ\8f Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\8b, Ñ\87Ñ\82обÑ\8b Ñ\83видеÑ\82Ñ\8c Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ\8f Ð½Ð° Ñ\81Ñ\82Ñ\80аниÑ\86аÑ\85, Ñ\81Ñ\81Ñ\8bлаÑ\8eÑ\89иÑ\85Ñ\81Ñ\8f Ð½Ð° Ñ\8dÑ\82Ñ\83 Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 Ð¸Ð»Ð¸, Ð½Ð°Ð¾Ð±Ð¾Ñ\80оÑ\82, c Ð½ÐµÑ\91. (ЧÑ\82обÑ\8b Ñ\83видеÑ\82Ñ\8c Ñ\87ленов ÐºÐ°Ñ\82егоÑ\80ии, Ð²Ð²ÐµÐ´Ð¸Ñ\82е Category:Ð\9dазвание ÐºÐ°Ñ\82егоÑ\80ии). Ð\98зменениÑ\8f Ð½Ð° Ñ\81Ñ\82Ñ\80аниÑ\86аÑ\85 [[Special:Watchlist|ваÑ\88его Ñ\81пиÑ\81ка Ð½Ð°Ð±Ð»Ñ\8eдениÑ\8f]] Ð¾Ñ\82меÑ\87енÑ\8b <strong>жиÑ\80нÑ\8bм Ñ\88Ñ\80иÑ\84Ñ\82ом</strong>.",
        "recentchangeslinked-page": "Название страницы:",
        "recentchangeslinked-to": "Наоборот, показать изменения на страницах, которые ссылаются на указанную страницу",
        "recentchanges-page-added-to-category": "[[:$1]] добавлена в категорию",
        "uploadstash-bad-path-no-handler": "Не найден обработчик для mime-типа $1 файла $2.",
        "uploadstash-bad-path-bad-format": "Ключ «$1» — в неподходящем формате.",
        "uploadstash-file-not-found-no-thumb": "Не удалось получить миниатюру.",
+       "uploadstash-file-not-found-no-object": "Не удалось создать объект локального файла для миниатюры.",
+       "uploadstash-file-not-found-no-remote-thumb": "Извлечение эскиза не удалось: $1\nURL = $2",
        "uploadstash-file-not-found-missing-content-type": "Отсутствует заголовок content-type.",
+       "uploadstash-file-too-large": "Невозможно обработать файл размером более $1 байт.",
        "uploadstash-wrong-owner": "Этот файл ($1) не принадлежит текущему участнику.",
        "uploadstash-no-extension": "Пустое расширение.",
        "uploadstash-zero-length": "Файл нулевой длины.",
        "exif-exposureindex": "Индекс экспозиции",
        "exif-sensingmethod": "Тип сенсора",
        "exif-filesource": "Источник файла",
-       "exif-scenetype": "СÑ\86енан ÐºÐµÐ¿",
+       "exif-scenetype": "Тип Ñ\81Ñ\86енÑ\8b",
        "exif-customrendered": "Дополнительная обработка",
        "exif-exposuremode": "Режим выбора экспозиции",
        "exif-whitebalance": "Баланс белого",
index 6b471b0..62a2f4f 100644 (file)
@@ -24,7 +24,7 @@ END;
 /*$mw$*/
 
 /*$mw$*/
-CREATE TRIGGER &mw_prefix.mwuser_default_user_id BEFORE INSERT ON &mw_prefix.mwuser
+CREATE TRIGGER &mw_prefix.mwuser_seq_trg BEFORE INSERT ON &mw_prefix.mwuser
        FOR EACH ROW WHEN (new.user_id IS NULL)
 BEGIN
        &mw_prefix.lastval_pkg.setLastval(user_user_id_seq.nextval, :new.user_id);
@@ -32,7 +32,7 @@ END;
 /*$mw$*/
 
 /*$mw$*/
-CREATE TRIGGER &mw_prefix.page_default_page_id BEFORE INSERT ON &mw_prefix.page
+CREATE TRIGGER &mw_prefix.page_seq_trg BEFORE INSERT ON &mw_prefix.page
        FOR EACH ROW WHEN (new.page_id IS NULL)
 BEGIN
        &mw_prefix.lastval_pkg.setLastval(page_page_id_seq.nextval, :new.page_id);
@@ -40,7 +40,7 @@ END;
 /*$mw$*/
 
 /*$mw$*/
-CREATE TRIGGER &mw_prefix.revision_default_rev_id BEFORE INSERT ON &mw_prefix.revision
+CREATE TRIGGER &mw_prefix.revision_seq_trg BEFORE INSERT ON &mw_prefix.revision
        FOR EACH ROW WHEN (new.rev_id IS NULL)
 BEGIN
        &mw_prefix.lastval_pkg.setLastval(revision_rev_id_seq.nextval, :new.rev_id);
@@ -48,7 +48,7 @@ END;
 /*$mw$*/
 
 /*$mw$*/
-CREATE TRIGGER &mw_prefix.text_default_old_id BEFORE INSERT ON &mw_prefix.text
+CREATE TRIGGER &mw_prefix.pagecontent_seq_trg BEFORE INSERT ON &mw_prefix.pagecontent
        FOR EACH ROW WHEN (new.old_id IS NULL)
 BEGIN
        &mw_prefix.lastval_pkg.setLastval(text_old_id_seq.nextval, :new.old_id);
@@ -56,7 +56,7 @@ END;
 /*$mw$*/
 
 /*$mw$*/
-CREATE TRIGGER &mw_prefix.archive_default_ar_id BEFORE INSERT ON &mw_prefix.archive
+CREATE TRIGGER &mw_prefix.archive_seq_trg BEFORE INSERT ON &mw_prefix.archive
        FOR EACH ROW WHEN (new.ar_id IS NULL)
 BEGIN
        &mw_prefix.lastval_pkg.setLastval(archive_ar_id_seq.nextval, :new.ar_id);
@@ -64,7 +64,7 @@ END;
 /*$mw$*/
 
 /*$mw$*/
-CREATE TRIGGER &mw_prefix.category_default_cat_id BEFORE INSERT ON &mw_prefix.category
+CREATE TRIGGER &mw_prefix.category_seq_trg BEFORE INSERT ON &mw_prefix.category
        FOR EACH ROW WHEN (new.cat_id IS NULL)
 BEGIN
        &mw_prefix.lastval_pkg.setLastval(category_cat_id_seq.nextval, :new.cat_id);
@@ -72,7 +72,7 @@ END;
 /*$mw$*/
 
 /*$mw$*/
-CREATE TRIGGER &mw_prefix.externallinks_default_el_id BEFORE INSERT ON &mw_prefix.externallinks
+CREATE TRIGGER &mw_prefix.externallinks_seq_trg BEFORE INSERT ON &mw_prefix.externallinks
        FOR EACH ROW WHEN (new.el_id IS NULL)
 BEGIN
        &mw_prefix.lastval_pkg.setLastval(externallinks_el_id_seq.nextval, :new.el_id);
@@ -80,7 +80,7 @@ END;
 /*$mw$*/
 
 /*$mw$*/
-CREATE TRIGGER &mw_prefix.ipblocks_default_ipb_id BEFORE INSERT ON &mw_prefix.ipblocks
+CREATE TRIGGER &mw_prefix.ipblocks_seq_trg BEFORE INSERT ON &mw_prefix.ipblocks
        FOR EACH ROW WHEN (new.ipb_id IS NULL)
 BEGIN
        &mw_prefix.lastval_pkg.setLastval(ipblocks_ipb_id_seq.nextval, :new.ipb_id);
@@ -88,7 +88,7 @@ END;
 /*$mw$*/
 
 /*$mw$*/
-CREATE TRIGGER &mw_prefix.filearchive_default_fa_id BEFORE INSERT ON &mw_prefix.filearchive
+CREATE TRIGGER &mw_prefix.filearchive_seq_trg BEFORE INSERT ON &mw_prefix.filearchive
        FOR EACH ROW WHEN (new.fa_id IS NULL)
 BEGIN
        &mw_prefix.lastval_pkg.setLastval(filearchive_fa_id_seq.nextval, :new.fa_id);
@@ -96,7 +96,7 @@ END;
 /*$mw$*/
 
 /*$mw$*/
-CREATE TRIGGER &mw_prefix.uploadstash_default_us_id BEFORE INSERT ON &mw_prefix.uploadstash
+CREATE TRIGGER &mw_prefix.uploadstash_seq_trg BEFORE INSERT ON &mw_prefix.uploadstash
        FOR EACH ROW WHEN (new.us_id IS NULL)
 BEGIN
        &mw_prefix.lastval_pkg.setLastval(uploadstash_us_id_seq.nextval, :new.us_id);
@@ -104,7 +104,7 @@ END;
 /*$mw$*/
 
 /*$mw$*/
-CREATE TRIGGER &mw_prefix.recentchanges_default_rc_id BEFORE INSERT ON &mw_prefix.recentchanges
+CREATE TRIGGER &mw_prefix.recentchanges_seq_trg BEFORE INSERT ON &mw_prefix.recentchanges
        FOR EACH ROW WHEN (new.rc_id IS NULL)
 BEGIN
        &mw_prefix.lastval_pkg.setLastval(recentchanges_rc_id_seq.nextval, :new.rc_id);
@@ -112,7 +112,7 @@ END;
 /*$mw$*/
 
 /*$mw$*/
-CREATE TRIGGER &mw_prefix.logging_default_log_id BEFORE INSERT ON &mw_prefix.logging
+CREATE TRIGGER &mw_prefix.logging_seq_trg BEFORE INSERT ON &mw_prefix.logging
        FOR EACH ROW WHEN (new.log_id IS NULL)
 BEGIN
        &mw_prefix.lastval_pkg.setLastval(logging_log_id_seq.nextval, :new.log_id);
@@ -120,7 +120,7 @@ END;
 /*$mw$*/
 
 /*$mw$*/
-CREATE TRIGGER &mw_prefix.job_default_job_id BEFORE INSERT ON &mw_prefix.job
+CREATE TRIGGER &mw_prefix.job_seq_trg BEFORE INSERT ON &mw_prefix.job
        FOR EACH ROW WHEN (new.job_id IS NULL)
 BEGIN
        &mw_prefix.lastval_pkg.setLastval(job_job_id_seq.nextval, :new.job_id);
@@ -128,7 +128,7 @@ END;
 /*$mw$*/
 
 /*$mw$*/
-CREATE TRIGGER &mw_prefix.page_restrictions_default_pr_id BEFORE INSERT ON &mw_prefix.page_restrictions
+CREATE TRIGGER &mw_prefix.page_restrictions_seq_trg BEFORE INSERT ON &mw_prefix.page_restrictions
        FOR EACH ROW WHEN (new.pr_id IS NULL)
 BEGIN
        &mw_prefix.lastval_pkg.setLastval(page_restrictions_pr_id_seq.nextval, :new.pr_id);
@@ -136,7 +136,7 @@ END;
 /*$mw$*/
 
 /*$mw$*/
-CREATE TRIGGER &mw_prefix.sites_default_site_id BEFORE INSERT ON &mw_prefix.sites
+CREATE TRIGGER &mw_prefix.sites_seq_trg BEFORE INSERT ON &mw_prefix.sites
        FOR EACH ROW WHEN (new.site_id IS NULL)
 BEGIN
        &mw_prefix.lastval_pkg.setLastval(sites_site_id_seq.nextval, :new.site_id);
index c4b906d..39680ef 100644 (file)
@@ -1,5 +1,5 @@
 define mw_prefix='{$wgDBprefix}';
 
-ALTER TABLE &mw_prefix.externallinks ADD el_index_60 VARBINARY(60) NOT NULL DEFAULT '';
+ALTER TABLE &mw_prefix.externallinks ADD el_index_60 VARCHAR2(60);
 CREATE INDEX &mw_prefix.externallinks_i04 ON &mw_prefix.externallinks (el_index_60, el_id);
 CREATE INDEX &mw_prefix.externallinks_i05 ON &mw_prefix.externallinks (el_from, el_index_60, el_id);
index e6e2e56..d588e3a 100644 (file)
@@ -48,7 +48,7 @@ CREATE UNIQUE INDEX &mw_prefix.mwuser_u01 ON &mw_prefix.mwuser (user_name);
 CREATE INDEX &mw_prefix.mwuser_i01 ON &mw_prefix.mwuser (user_email_token);
 CREATE INDEX &mw_prefix.mwuser_i02 ON &mw_prefix.mwuser (user_email, user_name);
 /*$mw$*/
-CREATE TRIGGER &mw_prefix.mwuser_default_user_id BEFORE INSERT ON &mw_prefix.mwuser
+CREATE TRIGGER &mw_prefix.mwuser_seq_trg BEFORE INSERT ON &mw_prefix.mwuser
        FOR EACH ROW WHEN (new.user_id IS NULL)
 BEGIN
        &mw_prefix.lastval_pkg.setLastval(user_user_id_seq.nextval, :new.user_id);
@@ -116,7 +116,7 @@ CREATE INDEX &mw_prefix.page_i01 ON &mw_prefix.page (page_random);
 CREATE INDEX &mw_prefix.page_i02 ON &mw_prefix.page (page_len);
 CREATE INDEX &mw_prefix.page_i03 ON &mw_prefix.page (page_is_redirect, page_namespace, page_len);
 /*$mw$*/
-CREATE TRIGGER &mw_prefix.page_default_page_id BEFORE INSERT ON &mw_prefix.page
+CREATE TRIGGER &mw_prefix.page_seq_trg BEFORE INSERT ON &mw_prefix.page
        FOR EACH ROW WHEN (new.page_id IS NULL)
 BEGIN
        &mw_prefix.lastval_pkg.setLastval(page_page_id_seq.nextval, :new.page_id);
@@ -162,7 +162,7 @@ CREATE INDEX &mw_prefix.revision_i03 ON &mw_prefix.revision (rev_user,rev_timest
 CREATE INDEX &mw_prefix.revision_i04 ON &mw_prefix.revision (rev_user_text,rev_timestamp);
 CREATE INDEX &mw_prefix.revision_i05 ON &mw_prefix.revision (rev_page,rev_user,rev_timestamp);
 /*$mw$*/
-CREATE TRIGGER &mw_prefix.revision_default_rev_id BEFORE INSERT ON &mw_prefix.revision
+CREATE TRIGGER &mw_prefix.revision_seq_trg BEFORE INSERT ON &mw_prefix.revision
        FOR EACH ROW WHEN (new.rev_id IS NULL)
 BEGIN
        &mw_prefix.lastval_pkg.setLastval(revision_rev_id_seq.nextval, :new.rev_id);
@@ -177,7 +177,7 @@ CREATE TABLE &mw_prefix.pagecontent ( -- replaces reserved word 'text'
 );
 ALTER TABLE &mw_prefix.pagecontent ADD CONSTRAINT &mw_prefix.pagecontent_pk PRIMARY KEY (old_id);
 /*$mw$*/
-CREATE TRIGGER &mw_prefix.text_default_old_id BEFORE INSERT ON &mw_prefix.text
+CREATE TRIGGER &mw_prefix.pagecontent_seq_trg BEFORE INSERT ON &mw_prefix.pagecontent
        FOR EACH ROW WHEN (new.old_id IS NULL)
 BEGIN
        &mw_prefix.lastval_pkg.setLastval(text_old_id_seq.nextval, :new.old_id);
@@ -212,7 +212,7 @@ CREATE INDEX &mw_prefix.archive_i01 ON &mw_prefix.archive (ar_namespace,ar_title
 CREATE INDEX &mw_prefix.archive_i02 ON &mw_prefix.archive (ar_user_text,ar_timestamp);
 CREATE INDEX &mw_prefix.archive_i03 ON &mw_prefix.archive (ar_rev_id);
 /*$mw$*/
-CREATE TRIGGER &mw_prefix.archive_default_ar_id BEFORE INSERT ON &mw_prefix.archive
+CREATE TRIGGER &mw_prefix.archive_seq_trg BEFORE INSERT ON &mw_prefix.archive
        FOR EACH ROW WHEN (new.ar_id IS NULL)
 BEGIN
        &mw_prefix.lastval_pkg.setLastval(archive_ar_id_seq.nextval, :new.ar_id);
@@ -273,7 +273,7 @@ ALTER TABLE &mw_prefix.category ADD CONSTRAINT &mw_prefix.category_pk PRIMARY KE
 CREATE UNIQUE INDEX &mw_prefix.category_u01 ON &mw_prefix.category (cat_title);
 CREATE INDEX &mw_prefix.category_i01 ON &mw_prefix.category (cat_pages);
 /*$mw$*/
-CREATE TRIGGER &mw_prefix.category_default_cat_id BEFORE INSERT ON &mw_prefix.category
+CREATE TRIGGER &mw_prefix.category_seq_trg BEFORE INSERT ON &mw_prefix.category
        FOR EACH ROW WHEN (new.cat_id IS NULL)
 BEGIN
        &mw_prefix.lastval_pkg.setLastval(category_cat_id_seq.nextval, :new.cat_id);
@@ -286,7 +286,7 @@ CREATE TABLE &mw_prefix.externallinks (
   el_from   NUMBER  NOT NULL,
   el_to     VARCHAR2(2048) NOT NULL,
   el_index  VARCHAR2(2048) NOT NULL,
-  el_index_60  VARBINARY(60) NOT NULL DEFAULT ''
+  el_index_60  VARCHAR2(60)
 );
 ALTER TABLE &mw_prefix.externallinks ADD CONSTRAINT &mw_prefix.externallinks_pk PRIMARY KEY (el_id);
 ALTER TABLE &mw_prefix.externallinks ADD CONSTRAINT &mw_prefix.externallinks_fk1 FOREIGN KEY (el_from) REFERENCES &mw_prefix.page(page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
@@ -296,7 +296,7 @@ CREATE INDEX &mw_prefix.externallinks_i03 ON &mw_prefix.externallinks (el_index)
 CREATE INDEX &mw_prefix.externallinks_i04 ON &mw_prefix.externallinks (el_index_60, el_id);
 CREATE INDEX &mw_prefix.externallinks_i05 ON &mw_prefix.externallinks (el_from, el_index_60, el_id);
 /*$mw$*/
-CREATE TRIGGER &mw_prefix.externallinks_default_el_id BEFORE INSERT ON &mw_prefix.externallinks
+CREATE TRIGGER &mw_prefix.externallinks_seq_trg BEFORE INSERT ON &mw_prefix.externallinks
        FOR EACH ROW WHEN (new.el_id IS NULL)
 BEGIN
        &mw_prefix.lastval_pkg.setLastval(externallinks_el_id_seq.nextval, :new.el_id);
@@ -361,7 +361,7 @@ CREATE INDEX &mw_prefix.ipblocks_i03 ON &mw_prefix.ipblocks (ipb_timestamp);
 CREATE INDEX &mw_prefix.ipblocks_i04 ON &mw_prefix.ipblocks (ipb_expiry);
 CREATE INDEX &mw_prefix.ipblocks_i05 ON &mw_prefix.ipblocks (ipb_parent_block_id);
 /*$mw$*/
-CREATE TRIGGER &mw_prefix.ipblocks_default_ipb_id BEFORE INSERT ON &mw_prefix.ipblocks
+CREATE TRIGGER &mw_prefix.ipblocks_seq_trg BEFORE INSERT ON &mw_prefix.ipblocks
        FOR EACH ROW WHEN (new.ipb_id IS NULL)
 BEGIN
        &mw_prefix.lastval_pkg.setLastval(ipblocks_ipb_id_seq.nextval, :new.ipb_id);
@@ -452,7 +452,7 @@ CREATE INDEX &mw_prefix.filearchive_i03 ON &mw_prefix.filearchive (fa_deleted_ti
 CREATE INDEX &mw_prefix.filearchive_i04 ON &mw_prefix.filearchive (fa_user_text,fa_timestamp);
 CREATE INDEX &mw_prefix.filearchive_i05 ON &mw_prefix.filearchive (fa_sha1);
 /*$mw$*/
-CREATE TRIGGER &mw_prefix.filearchive_default_fa_id BEFORE INSERT ON &mw_prefix.filearchive
+CREATE TRIGGER &mw_prefix.filearchive_seq_trg BEFORE INSERT ON &mw_prefix.filearchive
        FOR EACH ROW WHEN (new.fa_id IS NULL)
 BEGIN
        &mw_prefix.lastval_pkg.setLastval(filearchive_fa_id_seq.nextval, :new.fa_id);
@@ -485,7 +485,7 @@ CREATE INDEX &mw_prefix.uploadstash_i01 ON &mw_prefix.uploadstash (us_user);
 CREATE INDEX &mw_prefix.uploadstash_i02 ON &mw_prefix.uploadstash (us_timestamp);
 CREATE UNIQUE INDEX &mw_prefix.uploadstash_u01 ON &mw_prefix.uploadstash (us_key);
 /*$mw$*/
-CREATE TRIGGER &mw_prefix.uploadstash_default_us_id BEFORE INSERT ON &mw_prefix.uploadstash
+CREATE TRIGGER &mw_prefix.uploadstash_seq_trg BEFORE INSERT ON &mw_prefix.uploadstash
        FOR EACH ROW WHEN (new.us_id IS NULL)
 BEGIN
        &mw_prefix.lastval_pkg.setLastval(uploadstash_us_id_seq.nextval, :new.us_id);
@@ -532,7 +532,7 @@ CREATE INDEX &mw_prefix.recentchanges_i06 ON &mw_prefix.recentchanges (rc_namesp
 CREATE INDEX &mw_prefix.recentchanges_i07 ON &mw_prefix.recentchanges (rc_user_text, rc_timestamp);
 CREATE INDEX &mw_prefix.recentchanges_i08 ON &mw_prefix.recentchanges (rc_namespace, rc_type, rc_patrolled, rc_timestamp);
 /*$mw$*/
-CREATE TRIGGER &mw_prefix.recentchanges_default_rc_id BEFORE INSERT ON &mw_prefix.recentchanges
+CREATE TRIGGER &mw_prefix.recentchanges_seq_trg BEFORE INSERT ON &mw_prefix.recentchanges
        FOR EACH ROW WHEN (new.rc_id IS NULL)
 BEGIN
        &mw_prefix.lastval_pkg.setLastval(recentchanges_rc_id_seq.nextval, :new.rc_id);
@@ -617,7 +617,7 @@ CREATE INDEX &mw_prefix.logging_i05 ON &mw_prefix.logging (log_type, log_action,
 CREATE INDEX &mw_prefix.logging_i06 ON &mw_prefix.logging (log_user_text, log_type, log_timestamp);
 CREATE INDEX &mw_prefix.logging_i07 ON &mw_prefix.logging (log_user_text, log_timestamp);
 /*$mw$*/
-CREATE TRIGGER &mw_prefix.logging_default_log_id BEFORE INSERT ON &mw_prefix.logging
+CREATE TRIGGER &mw_prefix.logging_seq_trg BEFORE INSERT ON &mw_prefix.logging
        FOR EACH ROW WHEN (new.log_id IS NULL)
 BEGIN
        &mw_prefix.lastval_pkg.setLastval(logging_log_id_seq.nextval, :new.log_id);
@@ -654,7 +654,7 @@ CREATE INDEX &mw_prefix.job_i03 ON &mw_prefix.job (job_sha1);
 CREATE INDEX &mw_prefix.job_i04 ON &mw_prefix.job (job_cmd,job_token,job_random);
 CREATE INDEX &mw_prefix.job_i05 ON &mw_prefix.job (job_attempts);
 /*$mw$*/
-CREATE TRIGGER &mw_prefix.job_default_job_id BEFORE INSERT ON &mw_prefix.job
+CREATE TRIGGER &mw_prefix.job_seq_trg BEFORE INSERT ON &mw_prefix.job
        FOR EACH ROW WHEN (new.job_id IS NULL)
 BEGIN
        &mw_prefix.lastval_pkg.setLastval(job_job_id_seq.nextval, :new.job_id);
@@ -706,7 +706,7 @@ CREATE INDEX &mw_prefix.page_restrictions_i01 ON &mw_prefix.page_restrictions (p
 CREATE INDEX &mw_prefix.page_restrictions_i02 ON &mw_prefix.page_restrictions (pr_level);
 CREATE INDEX &mw_prefix.page_restrictions_i03 ON &mw_prefix.page_restrictions (pr_cascade);
 /*$mw$*/
-CREATE TRIGGER &mw_prefix.page_restrictions_default_pr_id BEFORE INSERT ON &mw_prefix.page_restrictions
+CREATE TRIGGER &mw_prefix.page_restrictions_seq_trg BEFORE INSERT ON &mw_prefix.page_restrictions
        FOR EACH ROW WHEN (new.pr_id IS NULL)
 BEGIN
        &mw_prefix.lastval_pkg.setLastval(page_restrictions_pr_id_seq.nextval, :new.pr_id);
@@ -821,7 +821,7 @@ CREATE INDEX &mw_prefix.sites_i05 ON &mw_prefix.sites (site_protocol);
 CREATE INDEX &mw_prefix.sites_i06 ON &mw_prefix.sites (site_domain);
 CREATE INDEX &mw_prefix.sites_i07 ON &mw_prefix.sites (site_forward);
 /*$mw$*/
-CREATE TRIGGER &mw_prefix.sites_default_site_id BEFORE INSERT ON &mw_prefix.sites
+CREATE TRIGGER &mw_prefix.sites_seq_trg BEFORE INSERT ON &mw_prefix.sites
        FOR EACH ROW WHEN (new.site_id IS NULL)
 BEGIN
        &mw_prefix.lastval_pkg.setLastval(sites_site_id_seq.nextval, :new.site_id);
index 57688ea..0a36ac4 100644 (file)
@@ -14,3 +14,5 @@ grant create synonym to &wiki_user.;
 grant create table to &wiki_user.;
 grant create sequence to &wiki_user.;
 grant create trigger to &wiki_user.;
+grant create type to &wiki_user.;
+grant create procedure to &wiki_user.;
index f0a86ff..1c9294c 100644 (file)
                        "◌ֿ",
                        "ֿ"
                ],
+               [
+                       "◌ֽ",
+                       "ֽ"
+               ],
                [
                        "◌׀",
                        "׀"
index 58fd500..19f3553 100644 (file)
@@ -205,11 +205,7 @@ tt,
 kbd,
 samp,
 .mw-code {
-       /*
-        * Some browsers will render the monospace text too small, namely Firefox, Chrome and Safari.
-        * Specifying any valid, second value will trigger correct behavior without forcing a different font.
-        * See T176636
-        */
+       /* Support: Blink, Gecko, Webkit; enable unified font sizes for monospace font. T176636 */
        font-family: monospace, monospace;
 }
 
index 00faf84..a56e459 100644 (file)
@@ -92,7 +92,7 @@ a.mw-debug-panelabel:visited {
        height: 300px;
        overflow: scroll;
        display: none;
-       font-family: monospace;
+       font-family: monospace, monospace;
        font-size: 11px;
        background-color: #e1eff2;
        box-sizing: border-box;
index 2d55094..bfd5c06 100644 (file)
@@ -83,8 +83,7 @@
 
        /**
         * Respond to dialog submit event. If the information was
-        * submitted, either successfully or with an error, open
-        * a MessageDialog to thank the user.
+        * submitted successfully, open a MessageDialog to thank the user.
         *
         * @param {string} [status] A status of the end of operation
         *  of the main feedback dialog. Empty if the dialog was
         *  to the external task reporting site.
         */
        mw.Feedback.prototype.onDialogSubmit = function ( status ) {
-               var dialogConfig = {};
-               switch ( status ) {
-                       case 'submitted':
-                               dialogConfig = {
-                                       title: mw.msg( 'feedback-thanks-title' ),
-                                       message: $( '<span>' ).msg(
-                                               'feedback-thanks',
-                                               this.feedbackPageTitle.getNameText(),
-                                               $( '<a>' ).attr( {
-                                                       target: '_blank',
-                                                       href: this.feedbackPageTitle.getUrl()
-                                               } )
-                                       ),
-                                       actions: [
-                                               {
-                                                       action: 'accept',
-                                                       label: mw.msg( 'feedback-close' ),
-                                                       flags: 'primary'
-                                               }
-                                       ]
-                               };
-                               break;
+               var dialogConfig;
+
+               if ( status !== 'submitted' ) {
+                       return;
                }
 
+               dialogConfig = {
+                       title: mw.msg( 'feedback-thanks-title' ),
+                       message: $( '<span>' ).msg(
+                               'feedback-thanks',
+                               this.feedbackPageTitle.getNameText(),
+                               $( '<a>' ).attr( {
+                                       target: '_blank',
+                                       href: this.feedbackPageTitle.getUrl()
+                               } )
+                       ),
+                       actions: [
+                               {
+                                       action: 'accept',
+                                       label: mw.msg( 'feedback-close' ),
+                                       flags: 'primary'
+                               }
+                       ]
+               };
+
                // Show the message dialog
-               if ( !$.isEmptyObject( dialogConfig ) ) {
-                       this.constructor.static.windowManager.openWindow(
-                               this.thankYouDialog,
-                               dialogConfig
-                       );
-               }
+               this.constructor.static.windowManager.openWindow(
+                       this.thankYouDialog,
+                       dialogConfig
+               );
        };
 
        /**
         * @return {OO.ui.Error}
         */
        mw.Feedback.Dialog.prototype.getErrorMessage = function () {
-               switch ( this.status ) {
-                       case 'error1':
-                       case 'error2':
-                       case 'error3':
-                       case 'error4':
-                               // Messages: feedback-error1, feedback-error2, feedback-error3, feedback-error4
-                               return new OO.ui.Error( mw.msg( 'feedback-' + this.status ) );
-               }
+               // Messages: feedback-error1, feedback-error2, feedback-error3, feedback-error4
+               return new OO.ui.Error( mw.msg( 'feedback-' + this.status ) );
        };
 
        /**
index 4d39f7b..a5c4688 100644 (file)
@@ -7,8 +7,6 @@ use MediaWiki\Services\DestructibleService;
 use MediaWiki\Services\SalvageableService;
 use MediaWiki\Services\ServiceDisabledException;
 use MediaWiki\Shell\CommandFactory;
-use MediaWiki\Storage\BlobStore;
-use MediaWiki\Storage\RevisionStore;
 
 /**
  * @covers MediaWiki\MediaWikiServices
@@ -333,8 +331,6 @@ class MediaWikiServicesTest extends MediaWikiTestCase {
                        'LocalServerObjectCache' => [ 'LocalServerObjectCache', BagOStuff::class ],
                        'VirtualRESTServiceClient' => [ 'VirtualRESTServiceClient', VirtualRESTServiceClient::class ],
                        'ShellCommandFactory' => [ 'ShellCommandFactory', CommandFactory::class ],
-                       'BlobStore' => [ 'BlobStore', BlobStore::class ],
-                       'RevisionStore' => [ 'RevisionStore', RevisionStore::class ],
                ];
        }
 
index 9ab76c8..91dbf2c 100644 (file)
@@ -1,8 +1,4 @@
 <?php
-use MediaWiki\MediaWikiServices;
-use MediaWiki\Storage\RevisionStore;
-use MediaWiki\Storage\IncompleteRevisionException;
-use MediaWiki\Storage\RevisionRecord;
 
 /**
  * RevisionDbTestBase contains test cases for the Revision class that have Database interactions.
@@ -76,7 +72,6 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                MWNamespace::clearCaches();
                // Reset namespace cache
                $wgContLang->resetNamespaces();
-
                if ( !$this->testPage ) {
                        /**
                         * We have to create a new page for each subclass as the page creation may result
@@ -107,14 +102,6 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                        $props['text'] = 'Lorem Ipsum';
                }
 
-               if ( !isset( $props['user_text'] ) ) {
-                       $props['user_text'] = 'Tester';
-               }
-
-               if ( !isset( $props['user'] ) ) {
-                       $props['user'] = 0;
-               }
-
                if ( !isset( $props['comment'] ) ) {
                        $props['comment'] = 'just a test';
                }
@@ -123,10 +110,6 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                        $props['page'] = $this->testPage->getId();
                }
 
-               if ( !isset( $props['content_model'] ) ) {
-                       $props['content_model'] = CONTENT_MODEL_WIKITEXT;
-               }
-
                $rev = new Revision( $props );
 
                $dbw = wfGetDB( DB_MASTER );
@@ -219,23 +202,14 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                $revId = $rev->insertOn( wfGetDB( DB_MASTER ) );
 
                $this->assertInternalType( 'integer', $revId );
-               $this->assertSame( $revId, $rev->getId() );
-
-               // getTextId() must be an int!
                $this->assertInternalType( 'integer', $rev->getTextId() );
-
-               $mainSlot = $rev->getRevisionRecord()->getSlot( 'main', RevisionRecord::RAW );
-
-               // we currently only support storage in the text table
-               $textId = MediaWikiServices::getInstance()
-                       ->getBlobStore()
-                       ->getTextIdFromAddress( $mainSlot->getAddress() );
+               $this->assertSame( $revId, $rev->getId() );
 
                $this->assertSelect(
                        'text',
                        [ 'old_id', 'old_text' ],
-                       "old_id = $textId",
-                       [ [ strval( $textId ), 'Revision Text' ] ]
+                       "old_id = {$rev->getTextId()}",
+                       [ [ strval( $rev->getTextId() ), 'Revision Text' ] ]
                );
                $this->assertSelect(
                        'revision',
@@ -254,7 +228,7 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                        [ [
                                strval( $rev->getId() ),
                                strval( $this->testPage->getId() ),
-                               strval( $textId ),
+                               strval( $rev->getTextId() ),
                                '0',
                                '0',
                                '0',
@@ -272,12 +246,11 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                // If an ExternalStore is set don't use it.
                $this->setMwGlobals( 'wgDefaultExternalStore', false );
                $this->setExpectedException(
-                       IncompleteRevisionException::class,
-                       "rev_page field must not be 0!"
+                       MWException::class,
+                       "Cannot insert revision: page ID must be nonzero"
                );
 
-               $title = Title::newFromText( 'Nonexistant-' . __METHOD__ );
-               $rev = new Revision( [], 0, $title );
+               $rev = new Revision( [] );
 
                $rev->insertOn( wfGetDB( DB_MASTER ) );
        }
@@ -348,42 +321,12 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                                return $f + [ 'ar_namespace', 'ar_title' ];
                        },
                ];
-               yield [
-                       function ( $f ) {
-                               unset( $f['ar_text'] );
-                               return $f;
-                       },
-               ];
                yield [
                        function ( $f ) {
                                unset( $f['ar_text_id'] );
                                return $f;
                        },
                ];
-               yield [
-                       function ( $f ) {
-                               unset( $f['ar_page_id'] );
-                               return $f;
-                       },
-               ];
-               yield [
-                       function ( $f ) {
-                               unset( $f['ar_parent_id'] );
-                               return $f;
-                       },
-               ];
-               yield [
-                       function ( $f ) {
-                               unset( $f['ar_rev_id'] );
-                               return $f;
-                       },
-               ];
-               yield [
-                       function ( $f ) {
-                               unset( $f['ar_sha1'] );
-                               return $f;
-                       },
-               ];
        }
 
        /**
@@ -391,17 +334,6 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
         * @covers Revision::newFromArchiveRow
         */
        public function testNewFromArchiveRow( $selectModifier ) {
-               $services = MediaWikiServices::getInstance();
-
-               $store = new RevisionStore(
-                       $services->getDBLoadBalancer(),
-                       $services->getService( '_SqlBlobStore' ),
-                       $services->getMainWANObjectCache()
-               );
-
-               $store->setContentHandlerUseDB( $this->getContentHandlerUseDB() );
-               $this->setService( 'RevisionStore', $store );
-
                $page = $this->createPage(
                        'RevisionStorageTest_testNewFromArchiveRow',
                        'Lorem Ipsum',
@@ -422,8 +354,6 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                $row = $res->fetchObject();
                $res->free();
 
-               // MCR migration note: $row is now required to contain ar_title and ar_namespace.
-               // Alternatively, a Title object can be passed to RevisionStore::newRevisionFromArchiveRow
                $rev = Revision::newFromArchiveRow( $row );
 
                $this->assertRevEquals( $orig, $rev );
@@ -452,7 +382,7 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                $row = $res->fetchObject();
                $res->free();
 
-               $rev = Revision::newFromArchiveRow( $row, [ 'comment_text' => 'SOMEOVERRIDE' ] );
+               $rev = Revision::newFromArchiveRow( $row, [ 'comment' => 'SOMEOVERRIDE' ] );
 
                $this->assertNotEquals( $orig->getComment(), $rev->getComment() );
                $this->assertEquals( 'SOMEOVERRIDE', $rev->getComment() );
@@ -496,8 +426,7 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
         * @covers Revision::newFromPageId
         */
        public function testNewFromPageIdWithNotLatestId() {
-               $content = new WikitextContent( __METHOD__ );
-               $this->testPage->doEditContent( $content, __METHOD__ );
+               $this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
                $rev = Revision::newFromPageId(
                        $this->testPage->getId(),
                        $this->testPage->getRevision()->getPrevious()->getId()
@@ -518,7 +447,6 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                $this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
                $id = $this->testPage->getRevision()->getId();
 
-               $this->hideDeprecated( 'Revision::fetchRevision' );
                $res = Revision::fetchRevision( $this->testPage->getTitle() );
 
                # note: order is unspecified
@@ -527,7 +455,8 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                        $rows[$row->rev_id] = $row;
                }
 
-               $this->assertEmpty( $rows, 'expected empty set' );
+               $this->assertEquals( 1, count( $rows ), 'expected exactly one revision' );
+               $this->assertArrayHasKey( $id, $rows, 'missing revision with id ' . $id );
        }
 
        /**
@@ -612,10 +541,6 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                        'new null revision should have a different id from the original revision' );
                $this->assertEquals( $orig->getTextId(), $rev->getTextId(),
                        'new null revision should have the same text id as the original revision' );
-               $this->assertEquals( $orig->getSha1(), $rev->getSha1(),
-                       'new null revision should have the same SHA1 as the original revision' );
-               $this->assertTrue( $orig->getRevisionRecord()->hasSameContent( $rev->getRevisionRecord() ),
-                       'new null revision should have the same content as the original revision' );
                $this->assertEquals( __METHOD__, $rev->getContent()->getNativeData() );
        }
 
@@ -681,7 +606,7 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                        'user' => $userA->getId(),
                        'text' => 'zero',
                        'content_model' => CONTENT_MODEL_WIKITEXT,
-                       'comment' => 'edit zero'
+                       'summary' => 'edit zero'
                ] );
                $revisions[0]->insertOn( $dbw );
 
@@ -693,7 +618,7 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                        'user' => $userA->getId(),
                        'text' => 'one',
                        'content_model' => CONTENT_MODEL_WIKITEXT,
-                       'comment' => 'edit one'
+                       'summary' => 'edit one'
                ] );
                $revisions[1]->insertOn( $dbw );
 
@@ -704,7 +629,7 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                        'user' => $userB->getId(),
                        'text' => 'two',
                        'content_model' => CONTENT_MODEL_WIKITEXT,
-                       'comment' => 'edit two'
+                       'summary' => 'edit two'
                ] );
                $revisions[2]->insertOn( $dbw );
 
@@ -715,7 +640,7 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                        'user' => $userA->getId(),
                        'text' => 'three',
                        'content_model' => CONTENT_MODEL_WIKITEXT,
-                       'comment' => 'edit three'
+                       'summary' => 'edit three'
                ] );
                $revisions[3]->insertOn( $dbw );
 
@@ -726,24 +651,13 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                        'user' => $userA->getId(),
                        'text' => 'zero',
                        'content_model' => CONTENT_MODEL_WIKITEXT,
-                       'comment' => 'edit four'
+                       'summary' => 'edit four'
                ] );
                $revisions[4]->insertOn( $dbw );
 
                // test it ---------------------------------
                $since = $revisions[$sinceIdx]->getTimestamp();
 
-               $allRows = iterator_to_array( $dbw->select(
-                       'revision',
-                       [ 'rev_id', 'rev_timestamp', 'rev_user' ],
-                       [
-                               'rev_page' => $page->getId(),
-                               //'rev_timestamp > ' . $dbw->addQuotes( $dbw->timestamp( $since ) )
-                       ],
-                       __METHOD__,
-                       [ 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 50 ]
-               ) );
-
                $wasLast = Revision::userWasLastToEdit( $dbw, $page->getId(), $userA->getId(), $since );
 
                $this->assertEquals( $expectedLast, $wasLast );
@@ -891,16 +805,12 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                        'text_id' => 123456789, // not in the test DB
                ] );
 
-               MediaWiki\suppressWarnings(); // bad text_id will trigger a warning.
-
                $this->assertNull( $rev->getContent(),
                        "getContent() should return null if the revision's text blob could not be loaded." );
 
                // NOTE: check this twice, once for lazy initialization, and once with the cached value.
                $this->assertNull( $rev->getContent(),
                        "getContent() should return null if the revision's text blob could not be loaded." );
-
-               MediaWiki\suppressWarnings( 'end' );
        }
 
        public function provideGetSize() {
@@ -994,7 +904,6 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
         */
        public function testLoadFromId() {
                $rev = $this->testPage->getRevision();
-               $this->hideDeprecated( 'Revision::loadFromId' );
                $this->assertRevEquals(
                        $rev,
                        Revision::loadFromId( wfGetDB( DB_MASTER ), $rev->getId() )
@@ -1117,7 +1026,7 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                $rev[1] = $this->testPage->getLatest();
 
                $this->assertSame(
-                       [ $rev[1] => $textLength ],
+                       [ $rev[1] => strval( $textLength ) ],
                        Revision::getParentLengths(
                                wfGetDB( DB_MASTER ),
                                [ $rev[1] ]
@@ -1140,7 +1049,7 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                $rev[2] = $this->testPage->getLatest();
 
                $this->assertSame(
-                       [ $rev[1] => $textOneLength, $rev[2] => $textTwoLength ],
+                       [ $rev[1] => strval( $textOneLength ), $rev[2] => strval( $textTwoLength ) ],
                        Revision::getParentLengths(
                                wfGetDB( DB_MASTER ),
                                [ $rev[1], $rev[2] ]
@@ -1171,6 +1080,14 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                );
        }
 
+       /**
+        * @covers Revision::getTitle
+        */
+       public function testGetTitle_forBadRevision() {
+               $rev = new Revision( [] );
+               $this->assertNull( $rev->getTitle() );
+       }
+
        /**
         * @covers Revision::isMinor
         */
@@ -1346,21 +1263,14 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                $rev = $this->testPage->getRevision();
 
                // Clear any previous cache for the revision during creation
-               $key = $cache->makeGlobalKey( 'revision-row-1.29',
-                       $db->getDomainID(),
-                       $rev->getPage(),
-                       $rev->getId()
-               );
+               $key = $cache->makeGlobalKey( 'revision', $db->getDomainID(), $rev->getPage(), $rev->getId() );
                $cache->delete( $key, WANObjectCache::HOLDOFF_NONE );
                $this->assertFalse( $cache->get( $key ) );
 
                // Get the new revision and make sure it is in the cache and correct
                $newRev = Revision::newKnownCurrent( $db, $rev->getPage(), $rev->getId() );
                $this->assertRevEquals( $rev, $newRev );
-
-               $cachedRow = $cache->get( $key );
-               $this->assertNotFalse( $cachedRow );
-               $this->assertEquals( $rev->getId(), $cachedRow->rev_id );
+               $this->assertRevEquals( $rev, $cache->get( $key ) );
        }
 
        public function provideUserCanBitfield() {
@@ -1467,7 +1377,7 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                        ]
                );
                $user = $this->getTestUser( $userGroups )->getUser();
-               $revision = new Revision( [ 'deleted' => $bitField ], 0, $this->testPage->getTitle() );
+               $revision = new Revision( [ 'deleted' => $bitField ] );
 
                $this->assertSame(
                        $expected,
index ef55e72..e4ea40f 100644 (file)
@@ -1,9 +1,6 @@
 <?php
 
-use MediaWiki\Storage\RevisionStore;
-use MediaWiki\Storage\SqlBlobStore;
-use Wikimedia\Rdbms\IDatabase;
-use Wikimedia\Rdbms\LoadBalancer;
+use Wikimedia\TestingAccessWrapper;
 
 /**
  * Test cases in RevisionTest should not interact with the Database.
@@ -57,10 +54,10 @@ class RevisionTest extends MediaWikiTestCase {
        /**
         * @dataProvider provideConstructFromArray
         * @covers Revision::__construct
-        * @covers \MediaWiki\Storage\RevisionStore::newMutableRevisionFromArray
+        * @covers Revision::constructFromRowArray
         */
-       public function testConstructFromArray( $rowArray ) {
-               $rev = new Revision( $rowArray, 0, $this->getMockTitle() );
+       public function testConstructFromArray( array $rowArray ) {
+               $rev = new Revision( $rowArray );
                $this->assertNotNull( $rev->getContent(), 'no content object available' );
                $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContent()->getModel() );
                $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContentModel() );
@@ -68,7 +65,7 @@ class RevisionTest extends MediaWikiTestCase {
 
        /**
         * @covers Revision::__construct
-        * @covers \MediaWiki\Storage\RevisionStore::newMutableRevisionFromArray
+        * @covers Revision::constructFromRowArray
         */
        public function testConstructFromEmptyArray() {
                $rev = new Revision( [], 0, $this->getMockTitle() );
@@ -93,20 +90,30 @@ class RevisionTest extends MediaWikiTestCase {
                        99,
                        'SomeTextUserName',
                ];
-               yield 'user text only' => [
+               // Note: the below XXX test cases are odd and probably result in unexpected behaviour if used
+               // in production code.
+               yield 'XXX: user text only' => [
                        [
                                'content' => new JavaScriptContent( 'hello world.' ),
                                'user_text' => '111.111.111.111',
                        ],
-                       0,
+                       null,
                        '111.111.111.111',
                ];
+               yield 'XXX: user id only' => [
+                       [
+                               'content' => new JavaScriptContent( 'hello world.' ),
+                               'user' => 9989,
+                       ],
+                       9989,
+                       null,
+               ];
        }
 
        /**
         * @dataProvider provideConstructFromArray_userSetAsExpected
         * @covers Revision::__construct
-        * @covers \MediaWiki\Storage\RevisionStore::newMutableRevisionFromArray
+        * @covers Revision::constructFromRowArray
         *
         * @param array $rowArray
         * @param mixed $expectedUserId null to expect the current wgUser ID
@@ -126,7 +133,7 @@ class RevisionTest extends MediaWikiTestCase {
                        $expectedUserName = $testUser->getName();
                }
 
-               $rev = new Revision( $rowArray, 0, $this->getMockTitle() );
+               $rev = new Revision( $rowArray );
                $this->assertEquals( $expectedUserId, $rev->getUser() );
                $this->assertEquals( $expectedUserName, $rev->getUserText() );
        }
@@ -136,37 +143,28 @@ class RevisionTest extends MediaWikiTestCase {
                        [
                                'content' => new WikitextContent( 'GOAT' ),
                                'text_id' => 'someid',
-                       ],
+                               ],
                        new MWException( "Text already stored in external store (id someid), " .
                                "can't serialize content object" )
                ];
-               yield 'unknown user id and no user name' => [
-                       [
-                               'content' => new JavaScriptContent( 'hello world.' ),
-                               'user' => 9989,
-                       ],
-                       new MWException( 'user_text not given, and unknown user ID 9989' )
-               ];
                yield 'with bad content object (class)' => [
                        [ 'content' => new stdClass() ],
-                       new MWException( 'content field must contain a Content object.' )
+                       new MWException( '`content` field must contain a Content object.' )
                ];
                yield 'with bad content object (string)' => [
                        [ 'content' => 'ImAGoat' ],
-                       new MWException( 'content field must contain a Content object.' )
+                       new MWException( '`content` field must contain a Content object.' )
                ];
                yield 'bad row format' => [
                        'imastring, not a row',
-                       new InvalidArgumentException(
-                               '$row must be a row object, an associative array, or a RevisionRecord'
-                       )
+                       new MWException( 'Revision constructor passed invalid row format.' )
                ];
        }
 
        /**
         * @dataProvider provideConstructFromArrayThrowsExceptions
         * @covers Revision::__construct
-        * @covers \MediaWiki\Storage\RevisionStore::newMutableRevisionFromArray
+        * @covers Revision::constructFromRowArray
         */
        public function testConstructFromArrayThrowsExceptions( $rowArray, Exception $expectedException ) {
                $this->setExpectedException(
@@ -174,25 +172,23 @@ class RevisionTest extends MediaWikiTestCase {
                        $expectedException->getMessage(),
                        $expectedException->getCode()
                );
-               new Revision( $rowArray, 0, $this->getMockTitle() );
+               new Revision( $rowArray );
        }
 
        /**
         * @covers Revision::__construct
-        * @covers \MediaWiki\Storage\RevisionStore::newMutableRevisionFromArray
+        * @covers Revision::constructFromRowArray
         */
        public function testConstructFromNothing() {
-               $this->setExpectedException(
-                       InvalidArgumentException::class
-               );
-               new Revision( [] );
+               $rev = new Revision( [] );
+               $this->assertNull( $rev->getId(), 'getId()' );
        }
 
        public function provideConstructFromRow() {
                yield 'Full construction' => [
                        [
-                               'rev_id' => '42',
-                               'rev_page' => '23',
+                               'rev_id' => '2',
+                               'rev_page' => '1',
                                'rev_text_id' => '2',
                                'rev_timestamp' => '20171017114835',
                                'rev_user_text' => '127.0.0.1',
@@ -209,8 +205,8 @@ class RevisionTest extends MediaWikiTestCase {
                                'rev_content_model' => 'GOATMODEL',
                        ],
                        function ( RevisionTest $testCase, Revision $rev ) {
-                               $testCase->assertSame( 42, $rev->getId() );
-                               $testCase->assertSame( 23, $rev->getPage() );
+                               $testCase->assertSame( 2, $rev->getId() );
+                               $testCase->assertSame( 1, $rev->getPage() );
                                $testCase->assertSame( 2, $rev->getTextId() );
                                $testCase->assertSame( '20171017114835', $rev->getTimestamp() );
                                $testCase->assertSame( '127.0.0.1', $rev->getUserText() );
@@ -225,10 +221,10 @@ class RevisionTest extends MediaWikiTestCase {
                                $testCase->assertSame( 'GOATMODEL', $rev->getContentModel() );
                        }
                ];
-               yield 'default field values' => [
+               yield 'null fields' => [
                        [
-                               'rev_id' => '42',
-                               'rev_page' => '23',
+                               'rev_id' => '2',
+                               'rev_page' => '1',
                                'rev_text_id' => '2',
                                'rev_timestamp' => '20171017114835',
                                'rev_user_text' => '127.0.0.1',
@@ -240,24 +236,11 @@ class RevisionTest extends MediaWikiTestCase {
                                'rev_comment_cid' => null,
                        ],
                        function ( RevisionTest $testCase, Revision $rev ) {
-                               // parent ID may be null
-                               $testCase->assertSame( null, $rev->getParentId(), 'revision id' );
-
-                               // given fields
-                               $testCase->assertSame( $rev->getTimestamp(), '20171017114835', 'timestamp' );
-                               $testCase->assertSame( $rev->getUserText(), '127.0.0.1', 'user name' );
-                               $testCase->assertSame( $rev->getUser(), 0, 'user id' );
-                               $testCase->assertSame( $rev->getComment(), 'Goat Comment!' );
-                               $testCase->assertSame( false, $rev->isMinor(), 'minor edit' );
-                               $testCase->assertSame( 0, $rev->getVisibility(), 'visibility flags' );
-
-                               // computed fields
-                               $testCase->assertNotNull( $rev->getSize(), 'size' );
-                               $testCase->assertNotNull( $rev->getSha1(), 'hash' );
-
-                               // NOTE: model and format will be detected based on the namespace of the (mock) title
-                               $testCase->assertSame( 'text/x-wiki', $rev->getContentFormat(), 'format' );
-                               $testCase->assertSame( 'wikitext', $rev->getContentModel(), 'model' );
+                               $testCase->assertNull( $rev->getSize() );
+                               $testCase->assertNull( $rev->getParentId() );
+                               $testCase->assertNull( $rev->getSha1() );
+                               $testCase->assertSame( 'text/x-wiki', $rev->getContentFormat() );
+                               $testCase->assertSame( 'wikitext', $rev->getContentModel() );
                        }
                ];
        }
@@ -265,34 +248,11 @@ class RevisionTest extends MediaWikiTestCase {
        /**
         * @dataProvider provideConstructFromRow
         * @covers Revision::__construct
-        * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromRow
+        * @covers Revision::constructFromRowArray
         */
        public function testConstructFromRow( array $arrayData, $assertions ) {
-               $data = 'Hello goat.'; // needs to match model and format
-
-               $blobStore = $this->getMockBuilder( SqlBlobStore::class )
-                       ->disableOriginalConstructor()
-                       ->getMock();
-
-               $blobStore->method( 'getBlob' )
-                       ->will( $this->returnValue( $data ) );
-
-               $blobStore->method( 'getTextIdFromAddress' )
-                       ->will( $this->returnCallback(
-                               function ( $address ) {
-                                       // Turn "tt:1234" into 12345.
-                                       // Note that this must be functional so we can test getTextId().
-                                       // Ideally, we'd un-mock getTextIdFromAddress and use its actual implementation.
-                                       $parts = explode( ':', $address );
-                                       return (int)array_pop( $parts );
-                               }
-                       ) );
-
-               // Note override internal service, so RevisionStore uses it as well.
-               $this->setService( '_SqlBlobStore', $blobStore );
-
                $row = (object)$arrayData;
-               $rev = new Revision( $row, 0, $this->getMockTitle() );
+               $rev = new Revision( $row );
                $assertions( $this, $rev );
        }
 
@@ -322,7 +282,7 @@ class RevisionTest extends MediaWikiTestCase {
         * @covers Revision::getId
         */
        public function testGetId( $rowArray, $expectedId ) {
-               $rev = new Revision( $rowArray, 0, $this->getMockTitle() );
+               $rev = new Revision( $rowArray );
                $this->assertEquals( $expectedId, $rev->getId() );
        }
 
@@ -336,7 +296,7 @@ class RevisionTest extends MediaWikiTestCase {
         * @covers Revision::setId
         */
        public function testSetId( $input, $expected ) {
-               $rev = new Revision( [], 0, $this->getMockTitle() );
+               $rev = new Revision( [] );
                $rev->setId( $input );
                $this->assertSame( $expected, $rev->getId() );
        }
@@ -351,7 +311,7 @@ class RevisionTest extends MediaWikiTestCase {
         * @covers Revision::setUserIdAndName
         */
        public function testSetUserIdAndName( $inputId, $expectedId, $name ) {
-               $rev = new Revision( [], 0, $this->getMockTitle() );
+               $rev = new Revision( [] );
                $rev->setUserIdAndName( $inputId, $name );
                $this->assertSame( $expectedId, $rev->getUser( Revision::RAW ) );
                $this->assertEquals( $name, $rev->getUserText( Revision::RAW ) );
@@ -368,7 +328,7 @@ class RevisionTest extends MediaWikiTestCase {
         * @covers Revision::getTextId()
         */
        public function testGetTextId( $rowArray, $expected ) {
-               $rev = new Revision( $rowArray, 0, $this->getMockTitle() );
+               $rev = new Revision( $rowArray );
                $this->assertSame( $expected, $rev->getTextId() );
        }
 
@@ -383,7 +343,7 @@ class RevisionTest extends MediaWikiTestCase {
         * @covers Revision::getParentId()
         */
        public function testGetParentId( $rowArray, $expected ) {
-               $rev = new Revision( $rowArray, 0, $this->getMockTitle() );
+               $rev = new Revision( $rowArray );
                $this->assertSame( $expected, $rev->getParentId() );
        }
 
@@ -416,44 +376,9 @@ class RevisionTest extends MediaWikiTestCase {
                $this->testGetRevisionText( $expected, $rowData );
        }
 
-       private function getWANObjectCache() {
-               return new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
-       }
-
-       /**
-        * @return SqlBlobStore
-        */
-       private function getBlobStore() {
-               /** @var LoadBalancer $lb */
-               $lb = $this->getMockBuilder( LoadBalancer::class )
-                       ->disableOriginalConstructor()
-                       ->getMock();
-
-               $cache = $this->getWANObjectCache();
-
-               $blobStore = new SqlBlobStore( $lb, $cache );
-               return $blobStore;
-       }
-
-       /**
-        * @return RevisionStore
-        */
-       private function getRevisionStore() {
-               /** @var LoadBalancer $lb */
-               $lb = $this->getMockBuilder( LoadBalancer::class )
-                       ->disableOriginalConstructor()
-                       ->getMock();
-
-               $cache = $this->getWANObjectCache();
-
-               $blobStore = new RevisionStore( $lb, $this->getBlobStore(), $cache );
-               return $blobStore;
-       }
-
        public function provideGetRevisionTextWithLegacyEncoding() {
                yield 'Utf8Native' => [
                        "Wiki est l'\xc3\xa9cole superieur !",
-                       'fr',
                        'iso-8859-1',
                        [
                                'old_flags' => 'utf-8',
@@ -462,7 +387,6 @@ class RevisionTest extends MediaWikiTestCase {
                ];
                yield 'Utf8Legacy' => [
                        "Wiki est l'\xc3\xa9cole superieur !",
-                       'fr',
                        'iso-8859-1',
                        [
                                'old_flags' => '',
@@ -475,11 +399,8 @@ class RevisionTest extends MediaWikiTestCase {
         * @covers Revision::getRevisionText
         * @dataProvider provideGetRevisionTextWithLegacyEncoding
         */
-       public function testGetRevisionWithLegacyEncoding( $expected, $lang, $encoding, $rowData ) {
-               $blobStore = $this->getBlobStore();
-               $blobStore->setLegacyEncoding( $encoding, Language::factory( $lang ) );
-               $this->setService( 'BlobStore', $blobStore );
-
+       public function testGetRevisionWithLegacyEncoding( $expected, $encoding, $rowData ) {
+               $this->setMwGlobals( 'wgLegacyEncoding', $encoding );
                $this->testGetRevisionText( $expected, $rowData );
        }
 
@@ -491,7 +412,6 @@ class RevisionTest extends MediaWikiTestCase {
                 */
                yield 'Utf8NativeGzip' => [
                        "Wiki est l'\xc3\xa9cole superieur !",
-                       'fr',
                        'iso-8859-1',
                        [
                                'old_flags' => 'gzip,utf-8',
@@ -500,7 +420,6 @@ class RevisionTest extends MediaWikiTestCase {
                ];
                yield 'Utf8LegacyGzip' => [
                        "Wiki est l'\xc3\xa9cole superieur !",
-                       'fr',
                        'iso-8859-1',
                        [
                                'old_flags' => 'gzip',
@@ -513,13 +432,9 @@ class RevisionTest extends MediaWikiTestCase {
         * @covers Revision::getRevisionText
         * @dataProvider provideGetRevisionTextWithGzipAndLegacyEncoding
         */
-       public function testGetRevisionWithGzipAndLegacyEncoding( $expected, $lang, $encoding, $rowData ) {
+       public function testGetRevisionWithGzipAndLegacyEncoding( $expected, $encoding, $rowData ) {
                $this->checkPHPExtension( 'zlib' );
-
-               $blobStore = $this->getBlobStore();
-               $blobStore->setLegacyEncoding( $encoding, Language::factory( $lang ) );
-               $this->setService( 'BlobStore', $blobStore );
-
+               $this->setMwGlobals( 'wgLegacyEncoding', $encoding );
                $this->testGetRevisionText( $expected, $rowData );
        }
 
@@ -545,10 +460,7 @@ class RevisionTest extends MediaWikiTestCase {
         */
        public function testCompressRevisionTextUtf8Gzip() {
                $this->checkPHPExtension( 'zlib' );
-
-               $blobStore = $this->getBlobStore();
-               $blobStore->setCompressBlobs( true );
-               $this->setService( 'BlobStore', $blobStore );
+               $this->setMwGlobals( 'wgCompressRevisions', true );
 
                $row = new stdClass;
                $row->old_text = "Wiki est l'\xc3\xa9cole superieur !";
@@ -563,41 +475,20 @@ class RevisionTest extends MediaWikiTestCase {
                        Revision::getRevisionText( $row ), "getRevisionText" );
        }
 
+       public function provideFetchFromConds() {
+               yield [ 0, [] ];
+               yield [ Revision::READ_LOCKING, [ 'FOR UPDATE' ] ];
+       }
+
        /**
-        * @covers Revision::loadFromTitle
+        * @dataProvider provideFetchFromConds
+        * @covers Revision::fetchFromConds
         */
-       public function testLoadFromTitle() {
-               $title = $this->getMockTitle();
-
-               $conditions = [
-                       'rev_id=page_latest',
-                       'page_namespace' => $title->getNamespace(),
-                       'page_title' => $title->getDBkey()
-               ];
-
-               $row = (object)[
-                       'rev_id' => '42',
-                       'rev_page' => $title->getArticleID(),
-                       'rev_text_id' => '2',
-                       'rev_timestamp' => '20171017114835',
-                       'rev_user_text' => '127.0.0.1',
-                       'rev_user' => '0',
-                       'rev_minor_edit' => '0',
-                       'rev_deleted' => '0',
-                       'rev_len' => '46',
-                       'rev_parent_id' => '1',
-                       'rev_sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
-                       'rev_comment_text' => 'Goat Comment!',
-                       'rev_comment_data' => null,
-                       'rev_comment_cid' => null,
-                       'rev_content_format' => 'GOATFORMAT',
-                       'rev_content_model' => 'GOATMODEL',
-               ];
+       public function testFetchFromConds( $flags, array $options ) {
+               $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', MIGRATION_OLD );
+               $conditions = [ 'conditionsArray' ];
 
                $db = $this->getMock( IDatabase::class );
-               $db->expects( $this->any() )
-                       ->method( 'getDomainId' )
-                       ->will( $this->returnValue( wfWikiID() ) );
                $db->expects( $this->once() )
                        ->method( 'selectRow' )
                        ->with(
@@ -606,24 +497,17 @@ class RevisionTest extends MediaWikiTestCase {
                                $this->isType( 'array' ),
                                $this->equalTo( $conditions ),
                                // Method name
-                               $this->stringContains( 'fetchRevisionRowFromConds' ),
-                               // We don't really care about the options here
-                               $this->isType( 'array' ),
+                               $this->equalTo( 'Revision::fetchFromConds' ),
+                               $this->equalTo( $options ),
                                // We don't really care about the join conds are they come from the joinCond methods
                                $this->isType( 'array' )
                        )
-                       ->willReturn( $row );
-
-               $revision = Revision::loadFromTitle( $db, $title );
-
-               $this->assertEquals( $title->getArticleID(), $revision->getTitle()->getArticleID() );
-               $this->assertEquals( $row->rev_id, $revision->getId() );
-               $this->assertEquals( $row->rev_len, $revision->getSize() );
-               $this->assertEquals( $row->rev_sha1, $revision->getSha1() );
-               $this->assertEquals( $row->rev_parent_id, $revision->getParentId() );
-               $this->assertEquals( $row->rev_timestamp, $revision->getTimestamp() );
-               $this->assertEquals( $row->rev_comment_text, $revision->getComment() );
-               $this->assertEquals( $row->rev_user_text, $revision->getUserText() );
+                       ->willReturn( 'RETURNVALUE' );
+
+               $wrapper = TestingAccessWrapper::newFromClass( Revision::class );
+               $result = $wrapper->fetchFromConds( $db, $conditions, $flags );
+
+               $this->assertEquals( 'RETURNVALUE', $result );
        }
 
        public function provideDecompressRevisionText() {
@@ -688,12 +572,8 @@ class RevisionTest extends MediaWikiTestCase {
         * @param mixed $expected
         */
        public function testDecompressRevisionText( $legacyEncoding, $text, $flags, $expected ) {
-               $blobStore = $this->getBlobStore();
-               if ( $legacyEncoding ) {
-                       $blobStore->setLegacyEncoding( $legacyEncoding, Language::factory( 'en' ) );
-               }
-
-               $this->setService( 'BlobStore', $blobStore );
+               $this->setMwGlobals( 'wgLegacyEncoding', $legacyEncoding );
+               $this->setMwGlobals( 'wgLanguageCode', 'en' );
                $this->assertSame(
                        $expected,
                        Revision::decompressRevisionText( $text, $flags )
@@ -789,20 +669,14 @@ class RevisionTest extends MediaWikiTestCase {
         * @covers Revision::getRevisionText
         */
        public function testGetRevisionText_external_oldId() {
-               $cache = $this->getWANObjectCache();
+               $cache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
                $this->setService( 'MainWANObjectCache', $cache );
-
                $this->setService(
                        'ExternalStoreFactory',
                        new ExternalStoreFactory( [ 'ForTesting' ] )
                );
 
-               $lb = $this->getMockBuilder( LoadBalancer::class )
-                       ->disableOriginalConstructor()
-                       ->getMock();
-
-               $blobStore = new SqlBlobStore( $lb, $cache );
-               $this->setService( 'BlobStore', $blobStore );
+               $cacheKey = $cache->makeKey( 'revisiontext', 'textid', '7777' );
 
                $this->assertSame(
                        'AAAABBAAA',
@@ -814,8 +688,6 @@ class RevisionTest extends MediaWikiTestCase {
                                ]
                        )
                );
-
-               $cacheKey = $cache->makeKey( 'revisiontext', 'textid', 'tt:7777' );
                $this->assertSame( 'AAAABBAAA', $cache->get( $cacheKey ) );
        }
 
@@ -1011,8 +883,6 @@ class RevisionTest extends MediaWikiTestCase {
                                'fields' => [
                                        'ar_id',
                                        'ar_page_id',
-                                       'ar_namespace',
-                                       'ar_title',
                                        'ar_rev_id',
                                        'ar_text',
                                        'ar_text_id',
@@ -1041,8 +911,6 @@ class RevisionTest extends MediaWikiTestCase {
                                'fields' => [
                                        'ar_id',
                                        'ar_page_id',
-                                       'ar_namespace',
-                                       'ar_title',
                                        'ar_rev_id',
                                        'ar_text',
                                        'ar_text_id',
@@ -1076,8 +944,6 @@ class RevisionTest extends MediaWikiTestCase {
                                'fields' => [
                                        'ar_id',
                                        'ar_page_id',
-                                       'ar_namespace',
-                                       'ar_title',
                                        'ar_rev_id',
                                        'ar_text',
                                        'ar_text_id',
@@ -1114,8 +980,6 @@ class RevisionTest extends MediaWikiTestCase {
                                'fields' => [
                                        'ar_id',
                                        'ar_page_id',
-                                       'ar_namespace',
-                                       'ar_title',
                                        'ar_rev_id',
                                        'ar_text',
                                        'ar_text_id',
@@ -1152,8 +1016,6 @@ class RevisionTest extends MediaWikiTestCase {
                                'fields' => [
                                        'ar_id',
                                        'ar_page_id',
-                                       'ar_namespace',
-                                       'ar_title',
                                        'ar_rev_id',
                                        'ar_text',
                                        'ar_text_id',
@@ -1185,11 +1047,6 @@ class RevisionTest extends MediaWikiTestCase {
         */
        public function testGetArchiveQueryInfo( $globals, $expected ) {
                $this->setMwGlobals( $globals );
-
-               $revisionStore = $this->getRevisionStore();
-               $revisionStore->setContentHandlerUseDB( $globals['wgContentHandlerUseDB'] );
-               $this->setService( 'RevisionStore', $revisionStore );
-
                $this->assertEquals(
                        $expected,
                        Revision::getArchiveQueryInfo()
@@ -1541,11 +1398,6 @@ class RevisionTest extends MediaWikiTestCase {
         */
        public function testGetQueryInfo( $globals, $options, $expected ) {
                $this->setMwGlobals( $globals );
-
-               $revisionStore = $this->getRevisionStore();
-               $revisionStore->setContentHandlerUseDB( $globals['wgContentHandlerUseDB'] );
-               $this->setService( 'RevisionStore', $revisionStore );
-
                $this->assertEquals(
                        $expected,
                        Revision::getQueryInfo( $options )