Merge "RCLFilters: change working of 'to-and-from' selector"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Mon, 18 Dec 2017 15:56:17 +0000 (15:56 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Mon, 18 Dec 2017 15:56:17 +0000 (15:56 +0000)
57 files changed:
RELEASE-NOTES-1.31
docs/hooks.txt
includes/AutoLoader.php
includes/MergeHistory.php
includes/Revision.php
includes/ServiceWiring.php
includes/actions/HistoryAction.php
includes/api/ApiMain.php
includes/api/i18n/gl.json
includes/api/i18n/lt.json
includes/cache/MessageCache.php
includes/changes/ChangesListFilter.php
includes/htmlform/fields/HTMLCheckMatrix.php
includes/installer/i18n/es.json
includes/installer/i18n/gl.json
includes/page/WikiPage.php
includes/parser/Parser.php
includes/parser/ParserOutput.php
includes/registration/ExtensionRegistry.php
includes/resourceloader/ResourceLoaderWikiModule.php
includes/specials/SpecialNewpages.php
includes/utils/AutoloadGenerator.php
languages/i18n/ar.json
languages/i18n/be-tarask.json
languages/i18n/bg.json
languages/i18n/bn.json
languages/i18n/ca.json
languages/i18n/cs.json
languages/i18n/csb.json
languages/i18n/da.json
languages/i18n/dty.json
languages/i18n/eo.json
languages/i18n/es.json
languages/i18n/fa.json
languages/i18n/fr.json
languages/i18n/gl.json
languages/i18n/hi.json
languages/i18n/hr.json
languages/i18n/jv.json
languages/i18n/ka.json
languages/i18n/lb.json
languages/i18n/lt.json
languages/i18n/lv.json
languages/i18n/nn.json
languages/i18n/ps.json
languages/i18n/ru.json
languages/i18n/sv.json
languages/i18n/tr.json
resources/src/mediawiki.legacy/shared.css
tests/phpunit/includes/MediaWikiServicesTest.php
tests/phpunit/includes/RevisionDbTestBase.php
tests/phpunit/includes/RevisionTest.php
tests/phpunit/includes/Storage/RevisionStoreRecordTest.php
tests/phpunit/includes/Storage/SqlBlobStoreTest.php
tests/phpunit/includes/jobqueue/JobTest.php
tests/phpunit/includes/page/WikiPageDbTestBase.php
tests/phpunit/structure/ResourcesTest.php

index 1a1a9f7..67026f4 100644 (file)
@@ -123,6 +123,9 @@ 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 29883b2..1f4a5f4 100644 (file)
@@ -1840,7 +1840,7 @@ $revisionInfo: Array of revision information
 Return false to stop further processing of the tag
 $reader: XMLReader object
 
-'ImportHandleUnknownUser': When a user does exist locally, this hook is called
+'ImportHandleUnknownUser': When a user doesn't exist locally, this hook is called
 to give extensions an opportunity to auto-create it. If the auto-creation is
 successful, return false.
 $name: User name
@@ -2810,14 +2810,14 @@ called after the addition of 'qunit' and MediaWiki testing resources.
   added to any module.
 &$ResourceLoader: object
 
-'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.
+'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!
 
 'SearchableNamespaces': An option to modify which namespaces are searchable.
 &$arr: Array of namespaces ($nsId => $name) which will be used.
index 675e347..52410fe 100644 (file)
@@ -133,5 +133,5 @@ class AutoLoader {
        }
 }
 
-Autoloader::$psr4Namespaces = AutoLoader::getAutoloadNamespaces();
+AutoLoader::$psr4Namespaces = AutoLoader::getAutoloadNamespaces();
 spl_autoload_register( [ 'AutoLoader', 'autoload' ] );
index 9d63869..b969e03 100644 (file)
@@ -24,6 +24,7 @@
  *
  * @file
  */
+use MediaWiki\MediaWikiServices;
 use Wikimedia\Timestamp\TimestampException;
 use Wikimedia\Rdbms\IDatabase;
 
@@ -335,6 +336,10 @@ class MergeHistory {
                }
                $this->dest->invalidateCache(); // update histories
 
+               // Duplicate watchers of the old article to the new article on history merge
+               $store = MediaWikiServices::getInstance()->getWatchedItemStore();
+               $store->duplicateAllAssociatedEntries( $this->source, $this->dest );
+
                // Update our logs
                $logEntry = new ManualLogEntry( 'merge', 'merge' );
                $logEntry->setPerformer( $user );
index 25c89c2..ea73a61 100644 (file)
  * @file
  */
 
-use Wikimedia\Rdbms\Database;
+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\IDatabase;
 use MediaWiki\Linker\LinkTarget;
 use MediaWiki\MediaWikiServices;
@@ -28,78 +35,50 @@ use Wikimedia\Rdbms\ResultWrapper;
 use Wikimedia\Rdbms\FakeResultWrapper;
 
 /**
- * @todo document
+ * @deprecated since 1.31, use RevisionRecord, RevisionStore, and BlobStore instead.
  */
 class Revision implements IDBAccessObject {
-       /** @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;
+
+       /** @var RevisionRecord */
+       protected $mRecord;
 
        // Revision deletion constants
-       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
+       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;
 
        // Audience options for accessors
-       const FOR_PUBLIC = 1;
-       const FOR_THIS_USER = 2;
-       const RAW = 3;
+       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 TEXT_CACHE_GROUP = 'revisiontext:10'; // process cache name and max key count
+       /**
+        * @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;
+       }
 
        /**
         * Load a page revision from a given revision ID number.
@@ -114,7 +93,8 @@ class Revision implements IDBAccessObject {
         * @return Revision|null
         */
        public static function newFromId( $id, $flags = 0 ) {
-               return self::newFromConds( [ 'rev_id' => intval( $id ) ], $flags );
+               $rec = self::getRevisionStore()->getRevisionById( $id, $flags );
+               return $rec === null ? null : new Revision( $rec, $flags );
        }
 
        /**
@@ -132,20 +112,8 @@ class Revision implements IDBAccessObject {
         * @return Revision|null
         */
        public static function newFromTitle( LinkTarget $linkTarget, $id = 0, $flags = 0 ) {
-               $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 );
-               }
+               $rec = self::getRevisionStore()->getRevisionByTitle( $linkTarget, $id, $flags );
+               return $rec === null ? null : new Revision( $rec, $flags );
        }
 
        /**
@@ -163,22 +131,13 @@ class Revision implements IDBAccessObject {
         * @return Revision|null
         */
        public static function newFromPageId( $pageId, $revId = 0, $flags = 0 ) {
-               $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 );
-               }
+               $rec = self::getRevisionStore()->getRevisionByPageId( $pageId, $revId, $flags );
+               return $rec === null ? null : new Revision( $rec, $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
@@ -187,68 +146,45 @@ class Revision implements IDBAccessObject {
         * @return Revision
         */
        public static function newFromArchiveRow( $row, $overrides = [] ) {
-               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 );
+               $rec = self::getRevisionStore()->newRevisionFromArchiveRow( $row, 0, null, $overrides );
+               return new Revision( $rec );
        }
 
        /**
         * @since 1.19
         *
-        * @param object $row
+        * 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
         * @return Revision
         */
        public static function newFromRow( $row ) {
-               return new self( $row );
+               if ( is_array( $row ) ) {
+                       $rec = self::getRevisionStore()->newMutableRevisionFromArray( $row );
+               } else {
+                       $rec = self::getRevisionStore()->newRevisionFromRow( $row );
+               }
+
+               return new Revision( $rec );
        }
 
        /**
         * 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 ) {
-               return self::loadFromConds( $db, [ 'rev_id' => intval( $id ) ] );
+               wfDeprecated( __METHOD__, '1.31' ); // no known callers
+               $rec = self::getRevisionStore()->loadRevisionFromId( $db, $id );
+               return $rec === null ? null : new Revision( $rec );
        }
 
        /**
@@ -256,19 +192,16 @@ 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 ) {
-               $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 );
+               $rec = self::getRevisionStore()->loadRevisionFromPageId( $db, $pageid, $id );
+               return $rec === null ? null : new Revision( $rec );
        }
 
        /**
@@ -276,24 +209,16 @@ 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 ) {
-               if ( $id ) {
-                       $matchId = intval( $id );
-               } else {
-                       $matchId = 'page_latest';
-               }
-               return self::loadFromConds( $db,
-                       [
-                               "rev_id=$matchId",
-                               'page_namespace' => $title->getNamespace(),
-                               'page_title' => $title->getDBkey()
-                       ]
-               );
+               $rec = self::getRevisionStore()->loadRevisionFromTitle( $db, $title, $id );
+               return $rec === null ? null : new Revision( $rec );
        }
 
        /**
@@ -301,73 +226,17 @@ 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 ) {
-               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;
+               // XXX: replace loadRevisionFromTimestamp by getRevisionByTimestamp?
+               $rec = self::getRevisionStore()->loadRevisionFromTimestamp( $db, $title, $timestamp );
+               return $rec === null ? null : new Revision( $rec );
        }
 
        /**
@@ -377,52 +246,18 @@ class Revision implements IDBAccessObject {
         *
         * @param LinkTarget $title
         * @return ResultWrapper
-        * @deprecated Since 1.28
+        * @deprecated Since 1.28, no callers in core nor in known extensions. No-op since 1.31.
         */
        public static function fetchRevision( LinkTarget $title ) {
-               $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']
-               );
+               wfDeprecated( __METHOD__, '1.31' );
+               return new FakeResultWrapper( [] );
        }
 
        /**
         * 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 self::getQueryInfo( [ 'user' ] ) instead.
+        * @deprecated since 1.31, use RevisionStore::getQueryInfo( [ 'user' ] ) instead.
         * @return array
         */
        public static function userJoinCond() {
@@ -434,7 +269,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 self::getQueryInfo( [ 'page' ] ) instead.
+        * @deprecated since 1.31, use RevisionStore::getQueryInfo( [ 'page' ] ) instead.
         * @return array
         */
        public static function pageJoinCond() {
@@ -445,7 +280,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 self::getQueryInfo() instead.
+        * @deprecated since 1.31, use RevisionStore::getQueryInfo() instead.
         * @return array
         */
        public static function selectFields() {
@@ -480,7 +315,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 self::getArchiveQueryInfo() instead.
+        * @deprecated since 1.31, use RevisionStore::getArchiveQueryInfo() instead.
         * @return array
         */
        public static function selectArchiveFields() {
@@ -516,7 +351,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 self::getQueryInfo( [ 'text' ] ) instead.
+        * @deprecated since 1.31, use RevisionStore::getQueryInfo( [ 'text' ] ) instead.
         * @return array
         */
        public static function selectTextFields() {
@@ -529,7 +364,7 @@ class Revision implements IDBAccessObject {
 
        /**
         * Return the list of page fields that should be selected from page table
-        * @deprecated since 1.31, use self::getQueryInfo( [ 'page' ] ) instead.
+        * @deprecated since 1.31, use RevisionStore::getQueryInfo( [ 'page' ] ) instead.
         * @return array
         */
        public static function selectPageFields() {
@@ -546,7 +381,7 @@ class Revision implements IDBAccessObject {
 
        /**
         * Return the list of user fields that should be selected from user table
-        * @deprecated since 1.31, use self::getQueryInfo( [ 'user' ] ) instead.
+        * @deprecated since 1.31, use RevisionStore::getQueryInfo( [ 'user' ] ) instead.
         * @return array
         */
        public static function selectUserFields() {
@@ -558,6 +393,7 @@ 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
@@ -568,104 +404,21 @@ class Revision implements IDBAccessObject {
         *   - joins: (array) to include in the `$join_conds` to `IDatabase->select()`
         */
        public static function 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 self::getRevisionStore()->getQueryInfo( $options );
        }
 
        /**
         * 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() {
-               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;
+               return self::getRevisionStore()->getArchiveQueryInfo();
        }
 
        /**
@@ -675,203 +428,49 @@ class Revision implements IDBAccessObject {
         * @return array
         */
        public static function getParentLengths( $db, array $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;
+               return self::getRevisionStore()->listRevisionSizes( $db, $revIds );
        }
 
        /**
-        * @param object|array $row Either a database row or an array
-        * @throws MWException
+        * @param object|array|RevisionRecord $row Either a database row or an array
+        * @param int $queryFlags
+        * @param Title|null $title
+        *
         * @access private
         */
-       public function __construct( $row ) {
-               if ( is_object( $row ) ) {
-                       $this->constructFromDbRowObject( $row );
-               } elseif ( is_array( $row ) ) {
-                       $this->constructFromRowArray( $row );
-               } else {
-                       throw new MWException( 'Revision constructor passed invalid row format.' );
-               }
-               $this->mUnpatrolled = null;
-       }
+       function __construct( $row, $queryFlags = 0, Title $title = null ) {
+               global $wgUser;
 
-       /**
-        * @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 {
-                       $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 );
-               }
+               if ( $row instanceof RevisionRecord ) {
+                       $this->mRecord = $row;
+               } elseif ( is_array( $row ) ) {
+                       if ( !isset( $row['user'] ) && !isset( $row['user_text'] ) ) {
+                               $row['user'] = $wgUser;
+                       }
 
-               // Lazy extraction...
-               $this->mText = null;
-               if ( isset( $row->old_text ) ) {
-                       $this->mTextRow = $row;
+                       $this->mRecord = self::getRevisionStore()->newMutableRevisionFromArray(
+                               $row,
+                               $queryFlags,
+                               $title
+                       );
+               } elseif ( is_object( $row ) ) {
+                       $this->mRecord = self::getRevisionStore()->newRevisionFromRow(
+                               $row,
+                               $queryFlags,
+                               $title
+                       );
                } 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
+                       throw new InvalidArgumentException(
+                               '$row must be a row object, an associative array, or a RevisionRecord'
+                       );
                }
-               $this->mOrigUserText = $row->rev_user_text;
        }
 
        /**
-        * @param array $row
-        *
-        * @throws MWException
+        * @return RevisionRecord
         */
-       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();
+       public function getRevisionRecord() {
+               return $this->mRecord;
        }
 
        /**
@@ -880,19 +479,27 @@ class Revision implements IDBAccessObject {
         * @return int|null
         */
        public function getId() {
-               return $this->mId;
+               return $this->mRecord->getId();
        }
 
        /**
         * Set the revision ID
         *
-        * This should only be used for proposed revisions that turn out to be null edits
+        * 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.19
-        * @param int $id
+        * @param int|string $id
+        * @throws MWException
         */
        public function setId( $id ) {
-               $this->mId = (int)$id;
+               if ( $this->mRecord instanceof MutableRevisionRecord ) {
+                       $this->mRecord->setId( intval( $id ) );
+               } else {
+                       throw new MWException( __METHOD__ . ' is not supported on this instance' );
+               }
        }
 
        /**
@@ -900,106 +507,107 @@ 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 ) {
-               $this->mUser = (int)$id;
-               $this->mUserText = $name;
-               $this->mOrigUserText = $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' );
+               }
        }
 
        /**
-        * Get text row ID
+        * @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.
         *
         * @return int|null
         */
        public function getTextId() {
-               return $this->mTextId;
+               $slot = $this->getMainSlotRaw();
+               return $slot->hasAddress()
+                       ? self::getBlobStore()->getTextIdFromAddress( $slot->getAddress() )
+                       : null;
        }
 
        /**
         * Get parent revision ID (the original previous page revision)
         *
-        * @return int|null
+        * @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.
         */
        public function getParentId() {
-               return $this->mParentId;
+               return $this->mRecord->getParentId();
        }
 
        /**
         * Returns the length of the text in this revision, or null if unknown.
         *
-        * @return int|null
+        * @return int
         */
        public function getSize() {
-               return $this->mSize;
+               return $this->mRecord->getSize();
        }
 
        /**
-        * Returns the base36 sha1 of the text in this revision, or null if unknown.
+        * Returns the base36 sha1 of the content in this revision, or null if unknown.
         *
-        * @return string|null
+        * @return string
         */
        public function getSha1() {
-               return $this->mSha1;
+               // XXX: we may want to drop all the hashing logic, it's not worth the overhead.
+               return $this->mRecord->getSha1();
        }
 
        /**
-        * Returns the title of the page associated with this entry or null.
+        * Returns the title of the page associated with this entry.
+        * Since 1.31, this will never return null.
         *
         * Will do a query, when title is not set and id is given.
         *
-        * @return Title|null
+        * @return Title
         */
        public function getTitle() {
-               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;
+               $linkTarget = $this->mRecord->getPageAsLinkTarget();
+               return Title::newFromLinkTarget( $linkTarget );
        }
 
        /**
         * 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 ) {
-               $this->mTitle = $title;
+               if ( !$title->equals( $this->getTitle() ) ) {
+                       throw new InvalidArgumentException(
+                               $title->getPrefixedText()
+                                       . ' is not the same as '
+                                       . $this->mRecord->getPageAsLinkTarget()->__toString()
+                       );
+               }
        }
 
        /**
@@ -1008,7 +616,7 @@ class Revision implements IDBAccessObject {
         * @return int|null
         */
        public function getPage() {
-               return $this->mPage;
+               return $this->mRecord->getPageId();
        }
 
        /**
@@ -1025,13 +633,14 @@ class Revision implements IDBAccessObject {
         * @return int
         */
        public function getUser( $audience = self::FOR_PUBLIC, User $user = null ) {
-               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;
+               global $wgUser;
+
+               if ( $audience === self::FOR_THIS_USER && !$user ) {
+                       $user = $wgUser;
                }
+
+               $user = $this->mRecord->getUser( $audience, $user );
+               return $user ? $user->getId() : 0;
        }
 
        /**
@@ -1059,23 +668,14 @@ class Revision implements IDBAccessObject {
         * @return string
         */
        public function getUserText( $audience = self::FOR_PUBLIC, User $user = null ) {
-               $this->loadMutableFields();
+               global $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;
+               if ( $audience === self::FOR_THIS_USER && !$user ) {
+                       $user = $wgUser;
                }
+
+               $user = $this->mRecord->getUser( $audience, $user );
+               return $user ? $user->getName() : '';
        }
 
        /**
@@ -1103,13 +703,14 @@ class Revision implements IDBAccessObject {
         * @return string
         */
        function getComment( $audience = self::FOR_PUBLIC, User $user = null ) {
-               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;
+               global $wgUser;
+
+               if ( $audience === self::FOR_THIS_USER && !$user ) {
+                       $user = $wgUser;
                }
+
+               $comment = $this->mRecord->getComment( $audience, $user );
+               return $comment === null ? null : $comment->text;
        }
 
        /**
@@ -1127,23 +728,14 @@ class Revision implements IDBAccessObject {
         * @return bool
         */
        public function isMinor() {
-               return (bool)$this->mMinorEdit;
+               return $this->mRecord->isMinor();
        }
 
        /**
         * @return int Rcid of the unpatrolled row, zero if there isn't one
         */
        public function isUnpatrolled() {
-               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;
+               return self::getRevisionStore()->isUnpatrolled( $this->mRecord );
        }
 
        /**
@@ -1156,19 +748,7 @@ class Revision implements IDBAccessObject {
         * @return RecentChange|null
         */
        public function getRecentChange( $flags = 0 ) {
-               $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
-               );
+               return self::getRevisionStore()->getRecentChange( $this->mRecord, $flags );
        }
 
        /**
@@ -1177,14 +757,7 @@ class Revision implements IDBAccessObject {
         * @return bool
         */
        public function 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;
+               return $this->mRecord->isDeleted( $field );
        }
 
        /**
@@ -1193,19 +766,17 @@ class Revision implements IDBAccessObject {
         * @return int
         */
        public function getVisibility() {
-               $this->loadMutableFields();
-
-               return (int)$this->mDeleted;
+               return $this->mRecord->getVisibility();
        }
 
        /**
         * Fetch revision content if it's available to the specified audience.
         * If the specified audience does not have the ability to view this
-        * revision, null will be returned.
+        * revision, or the content could not be loaded, 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 $wgUser
+        *   Revision::FOR_THIS_USER    to be displayed to $user
         *   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
@@ -1213,12 +784,17 @@ class Revision implements IDBAccessObject {
         * @return Content|null
         */
        public function getContent( $audience = self::FOR_PUBLIC, User $user = null ) {
-               if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_TEXT ) ) {
-                       return null;
-               } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_TEXT, $user ) ) {
+               global $wgUser;
+
+               if ( $audience === self::FOR_THIS_USER && !$user ) {
+                       $user = $wgUser;
+               }
+
+               try {
+                       return $this->mRecord->getContent( 'main', $audience, $user );
+               }
+               catch ( RevisionAccessException $e ) {
                        return null;
-               } else {
-                       return $this->getContentInternal();
                }
        }
 
@@ -1226,86 +802,51 @@ 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() {
-               if ( $this->mText === null ) {
-                       // Revision is immutable. Load on demand.
-                       $this->mText = $this->loadText();
-               }
-
-               return $this->mText;
+               $slot = $this->getMainSlotRaw();
+               return $slot->getContent()->serialize();
        }
 
        /**
-        * 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.
+        * Returns the content model for the main slot of 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() {
-               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;
+               return $this->getMainSlotRaw()->getModel();
        }
 
        /**
-        * Returns the content format for this revision.
+        * Returns the content format for the main slot of 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() {
-               if ( !$this->mContentFormat ) {
-                       $handler = $this->getContentHandler();
-                       $this->mContentFormat = $handler->getDefaultFormat();
+               $format = $this->getMainSlotRaw()->getFormat();
 
-                       assert( !empty( $this->mContentFormat ) );
+               if ( $format === null ) {
+                       // if no format was stored along with the blob, fall back to default format
+                       $format = $this->getContentHandler()->getDefaultFormat();
                }
 
-               return $this->mContentFormat;
+               return $format;
        }
 
        /**
@@ -1315,33 +856,21 @@ class Revision implements IDBAccessObject {
         * @return ContentHandler
         */
        public function getContentHandler() {
-               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 ContentHandler::getForModelID( $this->getContentModel() );
        }
 
        /**
         * @return string
         */
        public function getTimestamp() {
-               return wfTimestamp( TS_MW, $this->mTimestamp );
+               return $this->mRecord->getTimestamp();
        }
 
        /**
         * @return bool
         */
        public function isCurrent() {
-               return $this->mCurrent;
+               return ( $this->mRecord instanceof RevisionStoreRecord ) && $this->mRecord->isCurrent();
        }
 
        /**
@@ -1350,13 +879,8 @@ class Revision implements IDBAccessObject {
         * @return Revision|null
         */
        public function getPrevious() {
-               if ( $this->getTitle() ) {
-                       $prev = $this->getTitle()->getPreviousRevisionID( $this->getId() );
-                       if ( $prev ) {
-                               return self::newFromTitle( $this->getTitle(), $prev );
-                       }
-               }
-               return null;
+               $rec = self::getRevisionStore()->getPreviousRevision( $this->mRecord );
+               return $rec === null ? null : new Revision( $rec );
        }
 
        /**
@@ -1365,38 +889,8 @@ class Revision implements IDBAccessObject {
         * @return Revision|null
         */
        public function getNext() {
-               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 );
+               $rec = self::getRevisionStore()->getNextRevision( $this->mRecord );
+               return $rec === null ? null : new Revision( $rec );
        }
 
        /**
@@ -1429,35 +923,9 @@ class Revision implements IDBAccessObject {
                        return false;
                }
 
-               // 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 ] );
-                       }
-               }
+               $cacheKey = isset( $row->old_id ) ? ( 'tt:' . $row->old_id ) : null;
 
-               return self::decompressRevisionText( $text, $flags );
+               return self::getBlobStore()->expandBlob( $text, $flags, $cacheKey );
        }
 
        /**
@@ -1471,28 +939,7 @@ class Revision implements IDBAccessObject {
         * @return string
         */
        public static function compressRevisionText( &$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 );
+               return self::getBlobStore()->compressData( $text );
        }
 
        /**
@@ -1503,46 +950,7 @@ class Revision implements IDBAccessObject {
         * @return string|bool Decompressed text, or false on failure
         */
        public static function decompressRevisionText( $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;
+               return self::getBlobStore()->decompressData( $text, $flags );
        }
 
        /**
@@ -1554,192 +962,27 @@ class Revision implements IDBAccessObject {
         * @return int The revision ID
         */
        public function insertOn( $dbw ) {
-               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" );
-               }
+               global $wgUser;
 
-               $this->checkContentModel();
+               // Note that $this->mRecord->getId() will typically return null here, but not always,
+               // e.g. not when restoring a revision.
 
-               $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!" );
+               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.' );
                        }
-
-                       $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;
                }
 
-               $dbw->insert( 'revision', $row, __METHOD__ );
+               $rec = self::getRevisionStore()->insertRevisionOn( $this->mRecord, $dbw );
 
-               if ( $this->mId === null ) {
-                       // Only if auto-increment was used
-                       $this->mId = $dbw->insertId();
-               }
-               $commentCallback( $this->mId );
+               $this->mRecord = $rec;
 
-               // 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 )
-                       );
-               }
+               // TODO: hard-deprecate in 1.32 (or even 1.31?)
+               Hooks::run( 'RevisionInsertComplete', [ $this, null, null ] );
 
-               // 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"
-                       );
-               }
+               return $rec->getId();
        }
 
        /**
@@ -1748,103 +991,7 @@ class Revision implements IDBAccessObject {
         * @return string
         */
        public static function 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;
+               return SlotRecord::base36Sha1( $text );
        }
 
        /**
@@ -1863,58 +1010,17 @@ class Revision implements IDBAccessObject {
         * @return Revision|null Revision or null on error
         */
        public static function newNullRevision( $dbw, $pageId, $summary, $minor, $user = null ) {
-               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';
+               global $wgUser;
+               if ( !$user ) {
+                       $user = $wgUser;
                }
 
-               $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 );
+               $comment = CommentStoreComment::newUnsavedComment( $summary, null );
 
-                       $revision = new Revision( $row );
-               } else {
-                       $revision = null;
-               }
+               $title = Title::newFromID( $pageId );
+               $rec = self::getRevisionStore()->newNullRevision( $dbw, $title, $comment, $minor, $user );
 
-               return $revision;
+               return new Revision( $rec );
        }
 
        /**
@@ -1948,35 +1054,13 @@ class Revision implements IDBAccessObject {
        public static function userCanBitfield( $bitfield, $field, User $user = null,
                Title $title = null
        ) {
-               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;
+               global $wgUser;
+
+               if ( !$user ) {
+                       $user = $wgUser;
                }
+
+               return RevisionRecord::userCanBitfield( $bitfield, $field, $user, $title );
        }
 
        /**
@@ -1988,18 +1072,7 @@ class Revision implements IDBAccessObject {
         * @return string|bool False if not found
         */
        static function getTimestampFromId( $title, $id, $flags = 0 ) {
-               $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;
+               return self::getRevisionStore()->getTimestampFromId( $title, $id, $flags );
        }
 
        /**
@@ -2010,12 +1083,7 @@ class Revision implements IDBAccessObject {
         * @return int
         */
        static function countByPageId( $db, $id ) {
-               $row = $db->selectRow( 'revision', [ 'revCount' => 'COUNT(*)' ],
-                       [ 'rev_page' => $id ], __METHOD__ );
-               if ( $row ) {
-                       return $row->revCount;
-               }
-               return 0;
+               return self::getRevisionStore()->countRevisionsByPageId( $db, $id );
        }
 
        /**
@@ -2026,11 +1094,7 @@ class Revision implements IDBAccessObject {
         * @return int
         */
        static function countByTitle( $db, $title ) {
-               $id = $title->getArticleID();
-               if ( $id ) {
-                       return self::countByPageId( $db, $id );
-               }
-               return 0;
+               return self::getRevisionStore()->countRevisionsByTitle( $db, $title );
        }
 
        /**
@@ -2050,28 +1114,11 @@ 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 );
                }
 
-               $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;
+               return self::getRevisionStore()->userWasLastToEdit( $db, $pageId, $userId, $since );
        }
 
        /**
@@ -2079,54 +1126,20 @@ 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 lazy loaded, though setTitle() can be used to preload it.
+        * The title will also be loaded if $pageIdOrTitle is an integer ID.
         *
-        * @param IDatabase $db
-        * @param int $pageId Page ID
-        * @param int $revId Known current revision of this page
+        * @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.
         * @return Revision|bool Returns false if missing
         * @since 1.28
         */
-       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
-               }
+       public static function newKnownCurrent( IDatabase $db, $pageIdOrTitle, $revId = 0 ) {
+               $title = $pageIdOrTitle instanceof Title
+                       ? $pageIdOrTitle
+                       : Title::newFromID( $pageIdOrTitle );
 
-               $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;
-               }
+               $record = self::getRevisionStore()->getKnownCurrentRevision( $title, $revId );
+               return $record ? new Revision( $record ) : false;
        }
 }
index d21bcef..575970d 100644 (file)
@@ -450,6 +450,46 @@ 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 0e964bf..85e8db6 100644 (file)
@@ -335,8 +335,8 @@ class HistoryAction extends FormlessAction {
         * @return FeedItem
         */
        function feedItem( $row ) {
-               $rev = new Revision( $row );
-               $rev->setTitle( $this->getTitle() );
+               $rev = new Revision( $row, 0, $this->getTitle() );
+
                $text = FeedUtils::formatDiffRow(
                        $this->getTitle(),
                        $this->getTitle()->getPreviousRevisionID( $rev->getId() ),
@@ -639,12 +639,10 @@ class HistoryPager extends ReverseChronologicalPager {
         */
        function historyLine( $row, $next, $notificationtimestamp = false,
                $latest = false, $firstInList = false ) {
-               $rev = new Revision( $row );
-               $rev->setTitle( $this->getTitle() );
+               $rev = new Revision( $row, 0, $this->getTitle() );
 
                if ( is_object( $next ) ) {
-                       $prevRev = new Revision( $next );
-                       $prevRev->setTitle( $this->getTitle() );
+                       $prevRev = new Revision( $next, 0, $this->getTitle() );
                } else {
                        $prevRev = null;
                }
index edc1a3e..3bda3e8 100644 (file)
@@ -1933,7 +1933,7 @@ class ApiMain extends ApiBase {
                        $id = Sanitizer::escapeIdForAttribute( 'main/datatypes', Sanitizer::ID_PRIMARY );
                        $idFallback = Sanitizer::escapeIdForAttribute( 'main/datatypes', Sanitizer::ID_FALLBACK );
                        $headline = Linker::makeHeadline( min( 6, $level ),
-                               ' class="apihelp-header"',
+                               ' class="apihelp-header">',
                                $id,
                                $header,
                                '',
@@ -1961,7 +1961,7 @@ class ApiMain extends ApiBase {
                        $id = Sanitizer::escapeIdForAttribute( 'main/credits', Sanitizer::ID_PRIMARY );
                        $idFallback = Sanitizer::escapeIdForAttribute( 'main/credits', Sanitizer::ID_FALLBACK );
                        $headline = Linker::makeHeadline( min( 6, $level ),
-                               ' class="apihelp-header"',
+                               ' class="apihelp-header">',
                                $id,
                                $header,
                                '',
index aa21cd7..fb36923 100644 (file)
        "api-help-param-direction": "En que dirección enumerar:\n;newer:Lista os máis antigos primeiro. Nota: $1start ten que estar antes que $1end.\n;older:Lista os máis novos primeiro (por defecto). Nota: $1start ten que estar despois que $1end.",
        "api-help-param-continue": "Cando estean dispoñibles máis resultados, use isto para continuar.",
        "api-help-param-no-description": "<span class=\"apihelp-empty\">(sen descrición)</span>",
+       "api-help-param-maxbytes": "Non pode ser máis longo que $1 {{PLURAL:$1|byte|bytes}}.",
+       "api-help-param-maxchars": "Non pode ser máis longo que $1 {{PLURAL:$1|carácter|caracteres}}.",
        "api-help-examples": "{{PLURAL:$1|Exemplo|Exemplos}}:",
        "api-help-permissions": "{{PLURAL:$1|Permiso|Permisos}}:",
        "api-help-permissions-granted-to": "{{PLURAL:$1|Concedida a|Concedidas a}}: $2",
index 2726944..6f4c6a3 100644 (file)
        "apihelp-query+logevents-param-prop": "Kurias savybes gauti:",
        "apihelp-query+logevents-paramvalue-prop-ids": "Prideda žurnalo įvykio ID.",
        "apihelp-query+logevents-paramvalue-prop-type": "Prideda žurnalo įvykio tipą.",
+       "apihelp-query+search-paramvalue-prop-extensiondata": "Prideda papildomus duomenis, sugeneruotus plėtinių.",
        "apihelp-query+transcludedin-paramvalue-prop-pageid": "Kiekvieno puslapio ID.",
        "apihelp-query+transcludedin-paramvalue-prop-title": "Kiekvieno puslapio pavadinimas.",
        "apihelp-query+transcludedin-param-limit": "Kiek gražinti.",
index 768f980..d6e9b74 100644 (file)
@@ -1048,8 +1048,7 @@ class MessageCache {
                if ( $titleObj->getLatestRevID() ) {
                        $revision = Revision::newKnownCurrent(
                                $dbr,
-                               $titleObj->getArticleID(),
-                               $titleObj->getLatestRevID()
+                               $titleObj
                        );
                } else {
                        $revision = false;
index 2546f2b..1c86d44 100644 (file)
@@ -468,7 +468,7 @@ abstract class ChangesListFilter {
         * @param FormOptions $opts
         * @return bool
         */
-       public function activelyInConflictWithFilter( ChangeslistFilter $filter, FormOptions $opts ) {
+       public function activelyInConflictWithFilter( ChangesListFilter $filter, FormOptions $opts ) {
                if ( $this->isSelected( $opts ) && $filter->isSelected( $opts ) ) {
                        /** @var ChangesListFilter $siblingFilter */
                        foreach ( $this->getSiblings() as $siblingFilter ) {
@@ -484,7 +484,7 @@ abstract class ChangesListFilter {
                return false;
        }
 
-       private function hasConflictWithFilter( ChangeslistFilter $filter ) {
+       private function hasConflictWithFilter( ChangesListFilter $filter ) {
                return in_array( $filter, $this->getConflictingFilters() );
        }
 
index dd4e707..df44626 100644 (file)
@@ -106,6 +106,7 @@ class HTMLCheckMatrix extends HTMLFormField implements HTMLNestedFilterable {
                                $tooltipAttribs = [
                                        'class' => "mw-htmlform-tooltip $tooltipClass",
                                        'title' => $this->mParams['tooltips'][$rowLabel],
+                                       'aria-label' => $this->mParams['tooltips'][$rowLabel]
                                ];
                                $rowLabel .= ' ' . Html::element( 'span', $tooltipAttribs, '' );
                        }
index d7ed72b..62237a4 100644 (file)
        "config-email-auth-help": "Si esta opción está habilitada, los usuarios tienen que confirmar su dirección de correo electrónico mediante un enlace que se les envía a ellos cuando éstos lo establecen o lo cambian.\nSolo las direcciones de correo electrónico autenticadas pueden recibir correos electrónicos de otros usuarios o correos electrónicos de notificación de cambios.\nEsta opción está '''recomendada''' para wikis públicos debido a posibles abusos de las características del correo electrónico.",
        "config-email-sender": "Dirección de correo electrónico de retorno:",
        "config-email-sender-help": "Escribe la dirección de correo electrónico que se usará como dirección de retorno en los mensajes electrónicos de salida.\nAquí llegarán los correos electrónicos que no lleguen a su destino.\nMuchos servidores de correo electrónico exigen que por lo menos la parte del nombre del dominio sea válida.",
-       "config-upload-settings": "Subidas de imágenes y archivos",
+       "config-upload-settings": "Cargas de imágenes y archivos",
        "config-upload-enable": "Habilitar la subida de archivos",
        "config-upload-help": "La subida de archivos potencialmente expone tu servidor a riesgos de seguridad.\nPara obtener más información, consulta la [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security sección de seguridad] en el manual.\n\nPara activar la subida de archivos, cambia el modo en el subdirectorio <code>images</code> bajo el directorio raíz de MediaWiki para que el servidor web pueda escribir en él.\nLuego, activa esta opción.",
        "config-upload-deleted": "Directorio para los archivos eliminados:",
index e6e4b67..08434f6 100644 (file)
        "config-install-mainpage-failed": "Non se puido inserir a páxina principal: $1",
        "config-install-done": "<strong>Parabéns!</strong>\nInstalou MediaWiki.\n\nO programa de instalación xerou un ficheiro <code>LocalSettings.php</code>.\nEste ficheiro contén toda a súa configuración.\n\nTerá que descargalo e poñelo na base da instalación do seu wiki (no mesmo directorio ca index.php). A descarga debería comezar automaticamente.\n\nSe non comezou a descarga ou se a cancelou, pode facer que comece de novo premendo na ligazón que aparece a continuación:\n\n$3\n\n<strong>Nota:</strong> Se non fai iso agora, este ficheiro de configuración xerado non estará dispoñible máis adiante se sae da instalación sen descargalo.\n\nCando faga todo isto, xa poderá <strong>[$2 entrar no seu wiki]</strong>.",
        "config-install-done-path": "<strong>Parabéns!</strong>\nInstalou MediaWiki.\n\nO instalador xerou un ficheiro <code>LocalSettings.php</code>.\nEste contén toda a súa configuración.\n\nDeberá descargalo e poñerlo en <code>$4</code>. A descarga debería ter comezado automaticamente.\n\nSe non comenzou a descarga, ou se a cancelou, podes reiniciala descarga premendo na seguinte ligazón:\n\n$3\n\n<strong>Nota</strong>: se non fai isto agora, este ficheiro de configuración xerado non estará dispoñible máis tarde se sae da instalación sen descargarlo.\n\nCando o teña feito, poderá <strong>[$2 entrar na súa wiki]</strong>.",
+       "config-install-success": "MediaWiki instalouse con éxito. Agora podes \nvisitar <$1$2> para ver a túa wiki.\nSe tes dúbidas, revisa a nosa lista de preguntas frecuentes:\n<https://www.mediawiki.org/wiki/Manual:FAQ> ou usa un dos\nforos de axuda ligados nesa páxina.",
        "config-download-localsettings": "Descargar o <code>LocalSettings.php</code>",
        "config-help": "axuda",
        "config-help-tooltip": "prema para expandir",
index ff997ab..7498ca5 100644 (file)
@@ -23,6 +23,7 @@
 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;
@@ -671,7 +672,7 @@ class WikiPage implements Page, IDBAccessObject {
                        $revision = Revision::newFromPageId( $this->getId(), $latest, $flags );
                } else {
                        $dbr = wfGetDB( DB_REPLICA );
-                       $revision = Revision::newKnownCurrent( $dbr, $this->getId(), $latest );
+                       $revision = Revision::newKnownCurrent( $dbr, $this->getTitle(), $latest );
                }
 
                if ( $revision ) { // sanity
@@ -1264,8 +1265,11 @@ 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'      => $revision->getId(),
+                       'page_latest'      => $revId,
                        'page_touched'     => $dbw->timestamp( $revision->getTimestamp() ),
                        'page_is_new'      => ( $lastRevision === 0 ) ? 1 : 0,
                        'page_is_redirect' => $rt !== null ? 1 : 0,
index 58f0c0c..8986ddd 100644 (file)
@@ -3498,13 +3498,7 @@ class Parser {
         * @return Revision|bool False if missing
         */
        public static function statelessFetchRevision( Title $title, $parser = false ) {
-               $pageId = $title->getArticleID();
-               $revId = $title->getLatestRevID();
-
-               $rev = Revision::newKnownCurrent( wfGetDB( DB_REPLICA ), $pageId, $revId );
-               if ( $rev ) {
-                       $rev->setTitle( $title );
-               }
+               $rev = Revision::newKnownCurrent( wfGetDB( DB_REPLICA ), $title );
 
                return $rev;
        }
index ff9c28d..153a770 100644 (file)
@@ -596,7 +596,7 @@ class ParserOutput extends CacheTime {
 
                # Replace unnecessary URL escape codes with the referenced character
                # This prevents spammers from hiding links from the filters
-               $url = parser::normalizeLinkUrl( $url );
+               $url = Parser::normalizeLinkUrl( $url );
 
                $registerExternalLink = true;
                if ( !$wgRegisterInternalExternals ) {
index 994de97..6308461 100644 (file)
@@ -323,7 +323,7 @@ class ExtensionRegistry {
                }
 
                if ( isset( $info['autoloaderNS'] ) ) {
-                       Autoloader::$psr4Namespaces += $info['autoloaderNS'];
+                       AutoLoader::$psr4Namespaces += $info['autoloaderNS'];
                }
 
                foreach ( $info['defines'] as $name => $val ) {
index bebc188..6eddfc0 100644 (file)
@@ -183,12 +183,10 @@ class ResourceLoaderWikiModule extends ResourceLoaderModule {
         * @return Content|null
         */
        protected function getContentObj( Title $title ) {
-               $revision = Revision::newKnownCurrent( wfGetDB( DB_REPLICA ), $title->getArticleID(),
-                       $title->getLatestRevID() );
+               $revision = Revision::newKnownCurrent( wfGetDB( DB_REPLICA ), $title );
                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 671ab6f..1639386 100644 (file)
@@ -290,15 +290,16 @@ class SpecialNewpages extends IncludableSpecialPage {
 
        /**
         * @param stdClass $result Result row from recent changes
-        * @return Revision|bool
+        * @param Title $title
+        * @return bool|Revision
         */
-       protected function revisionFromRcResult( stdClass $result ) {
+       protected function revisionFromRcResult( stdClass $result, Title $title ) {
                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 );
        }
 
        /**
@@ -313,8 +314,7 @@ class SpecialNewpages extends IncludableSpecialPage {
 
                // Revision deletion works on revisions,
                // so cast our recent change row to a revision row.
-               $rev = $this->revisionFromRcResult( $result );
-               $rev->setTitle( $title );
+               $rev = $this->revisionFromRcResult( $result, $title );
 
                $classes = [];
                $attribs = [ 'data-mw-revid' => $result->rev_id ];
index 1c7c9b0..1415ea3 100644 (file)
@@ -74,7 +74,9 @@ class AutoloadGenerator {
         * @param string[] $paths
         */
        public function setExcludePaths( array $paths ) {
-               $this->excludePaths = $paths;
+               foreach ( $paths as $path ) {
+                       $this->excludePaths[] = self::normalizePathSeparator( $path );
+               }
        }
 
        /**
index 0dbc1e4..3ba0b96 100644 (file)
        "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|وسوم]] في مراجعات ومدخلات سجل فردية",
        "rcfilters-preference-label": "أخف النسخة المحسنة من أحدث التغييرات",
        "rcfilters-preference-help": "يسترجع عملية إعادة تصميم الواجهة لعام 2017 وكل الأدوات التي أضيفت منذ ذلك الوقت.",
        "rcfilters-filter-showlinkedfrom-label": "عرض التغييرات في الصفحات الموصولة من",
-       "rcfilters-filter-showlinkedfrom-option-label": "أظÙ\87ر Ø§Ù\84تغÙ\8aÙ\8aرات Ù\81Ù\8a Ø§Ù\84صÙ\81حات Ø§Ù\84Ù\85رتبطة <strong>من</strong> صفحة",
+       "rcfilters-filter-showlinkedfrom-option-label": "عرض Ø§Ù\84تغÙ\8aÙ\8aرات Ù\81Ù\8a Ø§Ù\84صÙ\81حات Ø§Ù\84Ù\85Ù\88صÙ\88Ù\84ة <strong>من</strong> صفحة",
        "rcfilters-filter-showlinkedto-label": "أظهر التغييرات في الصفحات الموصولة بصفحة",
-       "rcfilters-filter-showlinkedto-option-label": "اظÙ\87ر Ø§Ù\84تغÙ\8aÙ\8aرات Ù\81Ù\8a Ø§Ù\84صÙ\81حات Ø§Ù\84Ù\85رتبطة <strong>Ø¥Ù\84Ù\89</strong> Ø§Ù\84صفحة",
+       "rcfilters-filter-showlinkedto-option-label": "عرض Ø§Ù\84تغÙ\8aÙ\8aرات Ù\81Ù\8a Ø§Ù\84صÙ\81حات Ø§Ù\84Ù\85Ù\88صÙ\88Ù\84Ø© <strong>Ø¥Ù\84Ù\89</strong> صفحة",
        "rcfilters-target-page-placeholder": "أدخل اسم صفحة",
        "rcnotefrom": "بالأسفل {{PLURAL:$5|التغيير|التغييرات}} منذ <strong>$2</strong> (إلى <strong>$1</strong> معروضة).",
        "rclistfromreset": "إعادة ضبط خيار التاريخ",
index 73efb80..b8c3ea9 100644 (file)
        "uploadstash-not-logged-in": "Удзельнік не ўвайшоў у сыстэму, файлы мусяць належаць удзельнікам.",
        "uploadstash-wrong-owner": "Гэты файл ($1) не належыць цяперашняму ўдзельніку.",
        "uploadstash-no-such-key": "Няма такога ключа ($1), немагчыма выдаліць.",
+       "uploadstash-no-extension": "Пустое пашырэньне.",
+       "uploadstash-zero-length": "Файл мае нулявую даўжыню.",
        "invalid-chunk-offset": "Няслушнае зрушэньне фрагмэнту",
        "img-auth-accessdenied": "Доступ забаронены",
        "img-auth-nopathinfo": "Адсутнічае PATH_INFO.\nВаш сэрвэр не ўстаноўлены на пропуск гэтай інфармацыі.\nМагчма, ён працуе праз CGI і не падтрымлівае img_auth.\nГлядзіце https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization.",
        "import-mapping-namespace": "Імпарт у прастору назваў:",
        "import-mapping-subpage": "Імпарт у якасьці падстаронак наступнай старонкі:",
        "import-upload-filename": "Назва файла:",
+       "import-upload-username-prefix": "Прэфікс інтэрвікі:",
        "import-comment": "Камэнтар:",
        "importtext": "Калі ласка, экспартуйце файл з крынічнай вікі з дапамогай [[Special:Export|прылады экспарту]].\nЗахавайце яго на свой кампутар, а потым загрузіце сюды.",
        "importstart": "Імпартаваньне старонак…",
index a4a2623..5a6cb01 100644 (file)
@@ -71,7 +71,7 @@
        "tog-shownumberswatching": "Показване на броя на потребителите, наблюдаващи дадена страница",
        "tog-oldsig": "Вашият текущ подпис:",
        "tog-fancysig": "Без превръщане на подписа в препратка към потребителската страница",
-       "tog-uselivepreview": "Ð\98зползване Ð½Ð° Ð±Ñ\8aÑ\80з Ð¿Ñ\80едваÑ\80иÑ\82елен Ð¿Ñ\80еглед",
+       "tog-uselivepreview": "Ð\9fоказване Ð½Ð° Ð¿Ñ\80едваÑ\80иÑ\82елен Ð¿Ñ\80еглед Ð±ÐµÐ· Ð¿Ñ\80езаÑ\80еждане Ð½Ð° Ñ\81Ñ\82Ñ\80аниÑ\86аÑ\82а",
        "tog-forceeditsummary": "Предупреждаване при празно поле за резюме на редакцията",
        "tog-watchlisthideown": "Скриване на моите редакции в списъка ми за наблюдение",
        "tog-watchlisthidebots": "Скриване на редакциите на ботове в списъка ми за наблюдение",
        "anoneditwarning": "<strong>Внимание:</strong> Не сте влезли в системата. Ако направите редакция IP-адресът Ви ще бъде публично видим. Ако <strong>[$1 влезете]</strong> или си <strong>[$2 създадете акаунт]</strong>, редакциите Ви ще бъдат свързани с потребителското Ви име, заедно с други преимущества.",
        "anonpreviewwarning": "<em>Не сте влезли в системата. Ако съхраните редакцията си, тя ще бъде записана в историята на страницата с вашия IP-адрес.</em>",
        "missingsummary": "<strong>Напомняне:</strong> Не е въведено кратко описание на промените.\nПри повторно натискане на бутона „$1“, редакцията ще бъде съхранена без резюме.",
-       "missingcommenttext": "Ð\9fо-долÑ\83 Ð²Ñ\8aведеÑ\82е Ð²Ð°Ñ\88еÑ\82о Ñ\81Ñ\8aобÑ\89ение.",
+       "missingcommenttext": "Ð\9cолÑ\8f, Ð²Ñ\8aведеÑ\82е ÐºÐ¾Ð¼ÐµÐ½Ñ\82аÑ\80.",
        "missingcommentheader": "<strong>Напомняне:</strong> Не е въведено заглавие на коментара.\nПри повторно натискане на „$1“, редакцията ще бъде записана без коментар.",
        "summary-preview": "Предварителен преглед на резюмето:",
        "subject-preview": "Предварителен преглед на заглавието:",
        "prefs-editwatchlist-clear": "Изчистване на списъка за наблюдение",
        "prefs-watchlist-days": "Брой дни, които да се показват в списъка за наблюдение:",
        "prefs-watchlist-days-max": "Най-много $1 {{PLURAL:$1|ден|дни}}",
-       "prefs-watchlist-edits": "Ð\91Ñ\80ой Ñ\80едакÑ\86ии, ÐºÐ¾Ð¸Ñ\82о Ñ\81е Ð¿Ð¾ÐºÐ°Ð·Ð²Ð°Ñ\82 Ð² Ñ\80азÑ\88иÑ\80ениÑ\8f Ñ\81пиÑ\81Ñ\8aк за наблюдение:",
+       "prefs-watchlist-edits": "Ð\9cакÑ\81имален Ð±Ñ\80ой Ñ\80едакÑ\86ии Ð² Ñ\81пиÑ\81Ñ\8aка за наблюдение:",
        "prefs-watchlist-edits-max": "Максимален брой: 1000",
        "prefs-watchlist-token": "Уникален идентификатор на списъка за наблюдение:",
        "prefs-misc": "Други",
        "recentchangeslinked-feed": "Свързани промени",
        "recentchangeslinked-toolbox": "Свързани промени",
        "recentchangeslinked-title": "Промени, свързани с „$1“",
-       "recentchangeslinked-summary": "Тук се показват последните промени на страниците, към които се препраща от дадена страница. При избиране на категория, се показват промените по страниците, влизащи в нея. ''Пример:'' Ако изберете страницата '''А''', която съдържа препратки към '''Б''' и '''В''', тогава ще можете да прегледате промените по '''Б''' и '''В'''.\n\nАко пък сложите отметка пред '''Обръщане на релацията''', ще можете да прегледате промените в обратна посока: ще се включат тези страници, които съдържат препратки към посочената страница.\n\nСтраниците от списъка ви за наблюдение се показват в '''получер'''.",
+       "recentchangeslinked-summary": "Тук се показват последните промени на страниците, към които се препраща от дадена страница. При избиране на категория, се показват промените по страниците, влизащи в нея. ''Пример:'' Ако изберете страницата '''А''', която съдържа препратки към '''Б''' и '''В''', тогава ще можете да прегледате промените по '''Б''' и '''В'''.\n\nАко пък сложите отметка пред '''Обръщане на релацията''', ще можете да прегледате промените в обратна посока: ще се включат тези страници, които съдържат препратки към посочената страница.\n\nСтраниците от [[Special:Watchlist|списъка ви за наблюдение]] се показват в <strong>получер</strong>.",
        "recentchangeslinked-page": "Име на страницата:",
        "recentchangeslinked-to": "Обръщане на релацията, така че да се показват промените на страниците, сочещи към избраната страница",
        "recentchanges-page-added-to-category": "[[:$1]] е добавена към категория",
index f0bc3ad..21f66a7 100644 (file)
        "timezoneregion-indian": "ভারত মহাসাগর",
        "timezoneregion-pacific": "প্রশান্ত মহাসাগর",
        "allowemail": "অন্য ব্যবহারকারীদেরকে আমাকে ইমেল করতে অনুমতি দিন",
+       "email-allow-new-users-label": "নতুন ব্যবহারকারীদেরকে ইমেলের অনুমতি দিন",
        "email-blacklist-label": "আমাকে ইমেইল পাঠানো থেকে এই ব্যবহারকারীদের বিরত রাখুন:",
        "prefs-searchoptions": "অনুসন্ধান",
        "prefs-namespaces": "নামস্থানসমূহ",
index 98ce04f..8e9cc41 100644 (file)
        "uploadstash-thumbnail": "mostra una miniatura",
        "uploadstash-bad-path": "El camí no existeix.",
        "uploadstash-bad-path-invalid": "El camí no és vàlid.",
+       "uploadstash-bad-path-unknown-type": "El tipus «$1» és desconegut.",
        "invalid-chunk-offset": "El desplaçament del fragment no és vàlid",
        "img-auth-accessdenied": "Accés denegat",
        "img-auth-nopathinfo": "Hi manca PATH_INFO.\nEl servidor no està configurat per passar aquesta informació.\nPot estar basat en CGI i no ser compatible amb img_auth.\nConsulteu https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization",
index ab589f0..9d62dbb 100644 (file)
        "timezoneregion-indian": "Indický oceán",
        "timezoneregion-pacific": "Tichý oceán",
        "allowemail": "Dovolit ostatním uživatelům posílat mi e-maily",
+       "email-allow-new-users-label": "Povolit e-maily od zcela nových uživatelů",
        "email-blacklist-label": "Znemožnit těmto uživatelům posílat mi e-maily:",
        "prefs-searchoptions": "Vyhledávání",
        "prefs-namespaces": "Jmenné prostory",
        "right-siteadmin": "Zamykání a odemykání databáze",
        "right-override-export-depth": "Exportovat stránky včetně odkazovaných stránek až do hloubky 5",
        "right-sendemail": "Odesílání e-mailů ostatním uživatelům",
+       "right-sendemail-new-users": "Posílání e-mailů uživatelům bez zaznamenaných činností",
        "right-managechangetags": "Vytváření a (de)aktivace [[Special:Tags|značek]]",
        "right-applychangetags": "Přidávání [[Special:Tags|značek]] k vlastním změnám",
        "right-changetags": "Přidávání libovolných [[Special:Tags|značek]] na jednotlivé revize a protokolovací záznamy a jejich odebírání",
        "recentchangeslinked-feed": "Související změny",
        "recentchangeslinked-toolbox": "Související změny",
        "recentchangeslinked-title": "Související změny pro stránku „$1“",
-       "recentchangeslinked-summary": "Níže je seznam nedávných změn stránek odkazovaných ze zadané stránky (nebo patřících do dané kategorie). Vaše [[Special:Watchlist|sledované stránky]] jsou '''zvýrazněny'''.",
+       "recentchangeslinked-summary": "Vložením názvu stránky uvidíte změny stránek, které na stránku odkazují nebo na které stránka odkazuje. (Pro stránky zařazené do kategorie vložte Kategorie:Název kategorie.) Vámi [[Special:Watchlist|sledované stránky]] jsou <strong>zvýrazněny</strong>.",
        "recentchangeslinked-page": "Název stránky:",
        "recentchangeslinked-to": "Zobrazit změny na stránkách odkazujících na zadanou stránku",
        "recentchanges-page-added-to-category": "Stránka [[:$1]] zařazena do kategorie",
        "tag-mw-replace": "Nahrazeno",
        "tag-mw-replace-description": "Editace, které odstraňují více než 90 % obsahu stránky",
        "tag-mw-rollback": "Rychlý revert",
+       "tag-mw-rollback-description": "Editace, jimiž byly předchozí editace vráceny zpět pomocí rychlého revertu",
        "tags-title": "Značky",
        "tags-intro": "Tato stránka obsahuje seznam značek, kterými může software označovat jednotlivé editace, a jejich významy.",
        "tags-tag": "Název značky",
index 744059b..75b4e0c 100644 (file)
        "recentchanges-label-plusminus": "Zjinaczonô wiôlgòsc starnë (lëczba bajtów)",
        "recentchanges-legend-heading": "<strong>Légenda:</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (òbaczë téż [[Special:NewPages|lëstã nowëch strón]])",
+       "rcfilters-legend-heading": "<strong>Wëkôz skrodzënów:</strong>",
+       "rcfilters-other-review-tools": "Jiné nôrzãdza przezéru zjinaków",
+       "rcfilters-activefilters": "Aktiwné filtrë",
+       "rcfilters-search-placeholder": "Fitruj nowé zjinaczi (ùżij do te menu abò wëszukôj pòdle pòzwë filtra)",
+       "rcfilters-filterlist-title": "Filtrë",
+       "rcfilters-highlightbutton-title": "Pòdsztrichnąc wëniczi",
+       "rcfilters-filtergroup-authorship": "Aùtorstwò wkładu",
+       "rcfilters-filter-editsbyself-label": "Zjinaczi, chtërne zrobił jem jô.",
+       "rcfilters-filter-editsbyself-description": "Twòje gwôsné dzejania.",
+       "rcfilters-filter-editsbyother-label": "Zjinaczi, chtërne zrobilë jinszi brëkòwnicë.",
+       "rcfilters-filter-editsbyother-description": "Wszëtczé zjinaczi, òkróm twòjich.",
+       "rcfilters-filtergroup-userExpLevel": "Registracjô brëkòwnika i jegò doswiôdczenié",
+       "rcfilters-filter-user-experience-level-registered-label": "Zaregistrowóni",
+       "rcfilters-filter-user-experience-level-registered-description": "Zalogòwóni brëkòwnicë",
+       "rcfilters-filter-user-experience-level-unregistered-label": "Niezaregistrowóni",
+       "rcfilters-filter-user-experience-level-unregistered-description": "Niezalogòwóni brëkòwnicë",
+       "rcfilters-filter-user-experience-level-newcomer-label": "Pòczãtniczi",
+       "rcfilters-filter-user-experience-level-newcomer-description": "Zalogòwóni brëkòwnicë, jaczi mają mni jak 10 edicjów abò mni jak 4 dni aktiwnoscë.",
+       "rcfilters-filter-user-experience-level-learner-label": "Karno ùczącëch sã",
+       "rcfilters-filter-user-experience-level-learner-description": "Zaregistrowóni brëkòwnicë z doswiôdczenim wikszim niż „Pòczãtniczi”, ale miészim niż „Doswiôdczony brëkòwnicë”.",
+       "rcfilters-filter-user-experience-level-experienced-label": "Doswiôdczony brëkòwnicë.",
+       "rcfilters-filter-user-experience-level-experienced-description": "Zaregistrowóni brëkòwnicë, chtërny mają wicy jak 500 edicjów i 30 dni aktiwnoscë.",
+       "rcfilters-filter-bots-description": "Zjinaczi wëkònóné przë pòmòcë aùtomaticznëch nôrzãdzów.",
        "rcfilters-filter-humans-label": "Człowiek (nie bòt)",
+       "rcfilters-filter-humans-description": "Zjinaczi dokònany przez lëdzy",
+       "rcfilters-filter-minor-label": "Drobné zjinaczi",
+       "rcfilters-filter-minor-description": "Zjinaczi, chtërne aùtor nacéchòwôł jakò „drobné”.",
+       "rcfilters-filter-major-description": "Zjinaczi nie nacéchòwóné jakò „drobné”.",
+       "rcfilters-filtergroup-changetype": "Ôrt zjinaków",
+       "rcfilters-filter-pageedits-label": "Edicje starnë",
+       "rcfilters-filter-pageedits-description": "Edicje zamkłoscë, starnów diskùsje, òpisënków kategòrii...",
+       "rcfilters-filter-newpages-label": "Ùsôdzanié starnów",
+       "rcfilters-filter-newpages-description": "Zjinaczi, chtërne ùsôdzają nowé starnë.",
+       "rcfilters-filter-logactions-label": "Dzejania notérowóny w registru",
+       "rcfilters-filter-logactions-description": "Dzejania sprôwników, ùsôdzanié kònt, rëmanié starnów, sélanié lopków...",
+       "rcfilters-filtergroup-lastRevision": "Òstatné wersje.",
+       "rcfilters-filter-lastrevision-label": "Nônowszô wersjô",
+       "rcfilters-filter-lastrevision-description": "Leno nônowszi zjinaczi dlô kòżdi starnë.",
+       "rcfilters-filter-previousrevision-label": "Wersje jiné niż nônowszô.",
+       "rcfilters-filter-previousrevision-description": "Wszëtczé edicje, jaczé nie są nônowszą wersją starnë.",
+       "rcfilters-liveupdates-button": "Òdnôwianié na żëwò",
        "rcnotefrom": "Niżi {{PLURAL:$5|je zjinaka|są zjinaczi}} {{PLURAL:$5|zrobionô|zrobioné}} pò <strong>$3, $4</strong> (nie wicy jak '''$1''' pozycëji).",
        "rclistfrom": "Pòkażë nowé zmianë òd $3 $2",
        "rcshowhideminor": "$1 môłé zmianë",
index 348672e..37f7e83 100644 (file)
        "rcfilters-filter-categorization-description": "Optegnelser af at sider bliver tilføjet til eller fjernet fra kategorier.",
        "rcfilters-filter-logactions-label": "Loggede handlinger",
        "rcfilters-filter-logactions-description": "Administrative handlinger, kontooprettelser, sidesletninger, uploads...",
-       "rcfilters-filtergroup-lastRevision": "Sidste revision",
+       "rcfilters-filtergroup-lastRevision": "Seneste revisioner",
        "rcfilters-filter-lastrevision-label": "Seneste revision",
-       "rcfilters-filter-lastrevision-description": "Den nyeste ændring af en side.",
-       "rcfilters-filter-previousrevision-label": "Tidligere revisioner",
+       "rcfilters-filter-lastrevision-description": "Kun den seneste ændring af en side.",
+       "rcfilters-filter-previousrevision-label": "Ikke den seneste revision",
        "rcfilters-filter-previousrevision-description": "Alle ændringer som ikke er »seneste revision«.",
        "rcfilters-filter-excluded": "Ekskluderet",
        "rcfilters-view-tags": "Mærkede redigeringer",
        "rcfilters-watchlist-markseen-button": "Marker alle ændringer som set",
        "rcfilters-watchlist-edit-watchlist-button": "Rediger din liste med overvågede sider",
        "rcfilters-preference-label": "Skjul den forbedrede verson af Seneste ændringer",
+       "rcfilters-target-page-placeholder": "Indtast et sidenavn",
        "rcnotefrom": "Nedenfor er op til '''$1''' {{PLURAL:$5|ændring|ændringer}} siden '''$2''' vist.",
        "rclistfromreset": "Nulstil datovalg",
        "rclistfrom": "Vis nye ændringer startende fra den $3 kl. $2",
        "uploaddisabledtext": "Oplægning af filer er deaktiveret.",
        "php-uploaddisabledtext": "Oplægning af filer er forhindret i PHP. Tjek indstillingen for file_uploads.",
        "uploadscripted": "Denne fil indeholder HTML eller script-kode, der i visse tilfælde can fejlfortolkes af en browser.",
+       "uploaded-href-unsafe-target-svg": "Fandt href til usikre data: URI-mål <code>&lt;$1 $2=\"$3\"&gt;</code> i den overførte SVG-fil.",
        "uploadscriptednamespace": "Denne SVG-fil indeholder et ulovligt navnerum \"<nowiki>$1</nowiki>\"",
        "uploadinvalidxml": "XML i den uploadede fil kunne ikke tolkes.",
        "uploadvirus": "Denne fil indeholder en virus! Virusnavn: $1",
index 2904a9a..dc25eb0 100644 (file)
        "upload": "फाइल अपलोड गरऽ",
        "uploadbtn": "फाइल अपलोड गर्न्या",
        "upload-recreate-warning": "'''चेतावनी: त्यस नाममी रह्याका फाइलहरू सारियाको या हटायाको छ।'''\n\nयै पानाको सारियाको र हटायाको लग तमरो सहजताको लागि दियाको छ।",
+       "uploadlogpage": "अपलोड लग",
        "filedesc": "सारांश:",
        "large-file": "यो सिफारिस गर्याछकि फाइलहरूको आकार $1 भन्दा ठूला हुनु हुँदैन;\nयै फाइलको आकार $2 छ ।",
        "emptyfile": "तमीले अपलोड गर्याको फाइल रित्तो छ ।\nयो फाइल नाम गलत राख्याका कारणले भयाको हुनसकन्छ\nयो फाइल साँच्चै अपलोड गद्दे कुरडीमी निश्चित होइजाओ ।",
        "nolinkstoimage": "यो चित्रसित लिंकभयाकि कोइ पाना नाइथी",
        "morelinkstoimage": "यै फाइलको [[Special:WhatLinksHere/$1|थप लिंकहरू]] हेर ।",
        "sharedupload-desc-here": "यो फाइल $1 बठे हो र और  परियोजनाहरू बठे पन प्रयोग गद्द सकिन्याछ । \nताखाइ यैको [$2 फ़ाइल विवरण पानो]मि रयाका विवरण तल्तिर दियाको छ।",
+       "filepage-nofile": "येइ नाउँ को कोइ लै फाइल नाइथिन।",
        "upload-disallowed-here": "तमलाई यो फाइल अधिलेखन गद्द नाइसक्का ।",
        "filedelete-intro-old": "तमी <strong>[[Media:$1|$1]]</strong> को संस्करणलाई [$4 $3, $2] हुन्या गरि मेट्ट लाग्याछौ ।",
        "filedelete-maintenance": "रखरखाव चलिरह्याको हुनाले अस्थायी रुपमी फाइलहरू मेट्ट्या र मेट्याकोलाई पुनर्बहाली गर्न निष्क्रिय गरियाकोछ।",
        "activeusers-count": "विगत {{PLURAL:$3|दिनमी|$3 दिनहरूमी}}  $1 {{PLURAL:$1|सम्पादन गरियो|सम्पादनहरू गरिया}}",
        "activeusers-from": "यहाँबठे सुरु हुन्या प्रयोगकर्ताहरू धेकाओ:",
        "activeusers-noresult": "प्रयोगकर्ताहरू भेटियानन्",
+       "listgrouprights-members": "(सदस्यअनैः सूची)",
        "mailnologintext": "तमीले अरु प्रयोगकर्तानलाई ईमेल पठाउनको लागि आफु पहिली [[Special:UserLogin|प्रवेश(लगइन)गर्याको]] हुनुपडन्छ र [[Special:Preferences|आफ्नो रोजाइहरूमी]] एउटा वैध ईमेल ठेगाना भयाको हुनुपडन्छ ।",
        "emailuser": "येइ प्रयोगकर्ता लाई इमेल पठाऽ",
        "emailpagetext": "तल दियाको फार्मले तमी यै {{GENDER:$1|प्रयोगकर्ता}}लाई इमेल पठाउन सक्द्या हौ । तमीले जो ठेगाना [[Special:Preferences|आफ्नो प्रयोगकर्ता रोजाईहरू]]मी दियाका छियौ त्यो यै इमेललाई \"पठाउने\" को रूपमी आउन्याछ, अतः प्राप्तकर्ता तमीलाई सिधै जवाफ दिनसक्द्याछ ।",
        "usermaildisabledtext": "यै विकिमी तम और प्रयोगकर्तानलाई ई-मेल पठाउन नाइसक्दा",
+       "watchlist": "अवलोकनसूची",
        "mywatchlist": "मेरो ध्यान सूची",
        "nowatchlist": "तमरो ध्यान सूचीमी कोइ लै सामाग्री नाइथिन् ।",
        "watchlistanontext": "कृपया तमरो ध्यान सूची हेद्द या सम्पादन गद्द कीलाइ लगइन गर ।",
        "restriction-type": "अनुमति:",
        "pagesize": "(अक्षरहरू)",
        "restriction-edit": "सम्पादन",
+       "restriction-move": "सारऽ",
        "undeletepage": "मेट्याका पानाहरू हेद्या र पूर्वरुपमी फर्काउन्या",
        "undeleterevisions": "$1 {{PLURAL:$1|संशोधन|संशोधनहरू}} संग्रहित",
        "undeletehistory": "यदि कुनै पानालाई पुन: स्थापन गरायौ भण्या सम्पूर्ण संस्करणहरू इतिहासमी पुन:स्थापन हुन्याछन् ।\nयदि यै नामबठे  नयाँ पानो निर्माण भैसक्याको छ भण्या पुन: स्थापित संस्करणहरू पूर्व इतिहासको रुपमी स्थापित हुन्याछन् ।",
        "tooltip-namespace_association": "कुरडिकानी या विषय नेमस्पेसहरुलाई सम्वन्धित नेमस्पेसको रुपमि लिनकि लेखा सन्दुकमि चिनो लगाइदिय ।",
        "blanknamespace": "(मुख्य)",
        "contributions": "{{GENDER:$1|प्रयोगकर्ता}}को योगदान",
+       "contributions-title": "प्रयोगकर्ता $1 का योगदानअन",
        "mycontris": "मेरो योगदानहरू",
        "anoncontribs": "योगदान",
        "uctop": "(अइलोऽ)",
        "month": "महिना बठे (लै पैल्ली):",
        "year": "वर्ष बठे( लौ पैल्ली):",
+       "sp-contributions-newbies": "नौला खाताअनाः योगदानअन लाई मात्तरी धेकाऽ",
+       "sp-contributions-uploads": "अपलोडअन",
+       "sp-contributions-logs": "लगअन",
+       "sp-contributions-talk": "कुरड़िकाआनी",
+       "sp-contributions-search": "योगदानअन खिलाइ खोजी अरऽ",
+       "sp-contributions-username": "आइपी(IP) ठेगान या प्रयोगकर्ता नाउँ:",
        "sp-contributions-toponly": "नवीनतम संशोधनका सम्पादनहरू मात्र धेकाओ",
+       "sp-contributions-newonly": "तन सम्पादनअन लाई धेकाऽ जो कि पन्ना सिर्जनाअन हुन",
+       "sp-contributions-submit": "खोजऽ",
        "whatlinkshere": "याँखाइ कि जोणीन्छ",
        "whatlinkshere-title": "$1 सित जोडियाऽ पन्नाअन",
        "whatlinkshere-page": "पानो",
        "blocklist": "ब्लक गर्याका प्रयोगकर्ताहरू",
        "ipblocklist": "ब्लक गर्याका प्रयोगकर्ताहरू",
        "ipblocklist-legend": "ब्लक गर्याका प्रयोगकर्ताहरू खोज",
+       "infiniteblock": "अनन्त",
        "blocklink": "रोक्न्या",
        "contribslink": "योगदानअन",
        "block-log-flags-anononly": "नाम नभयाका प्रयोकर्ताहरू मात्र",
+       "proxyblocker": "प्रोक्सी निषेधक",
        "proxyblockreason": "तमरो IP ठेगानामी रोक लगायाको छ किनकी यो खुला प्रोक्सी हो ।\nकृपया तमरो इन्टरनेट सेवा प्रदायक या प्राविधिक सहायतासँग सम्पर्क गरीबर यै सुरक्षा समस्याका बारेमी जानकारी गराओ ।",
        "sorbsreason": "तमरो IP ठेगाना खुल्ला प्रोक्सीको रुपमी  DNSBL मा सूचीकरण गरिएको छ यैलाई{{SITENAME}}ले प्रयोगमी ल्यायाको छ।",
        "sorbs_create_account_reason": "तमरो IP ठेगाना खुल्ला प्रोक्सीको रुपमी  DNSBL मी सूचीकरण गरियाको छ यैलाई{{SITENAME}}ले प्रयोगमी ल्यायाको छ ।\nतम खाता खोल्न नाइसक्दा ।",
        "import-noarticle": "आयात गद्दाकी लाई पानाहरू नाइथिन्",
        "import-error-edit": "तमलाई सम्पादन गद्या अनुमति नभयाको पानो \"$1\" आयात गरिएन ।",
        "import-error-create": "तमलाई नयाँ बनाउने अनुमति नभयाको पानो \"$1\" आयात गरिएन ।",
+       "importlogpage": "आयात लग",
        "import-logentry-upload-detail": "$1 {{PLURAL:$1|संशोधन|संशोधनहरू}} आयात भयो",
        "tooltip-pt-userpage": "{{GENDER:|तमरो प्रयोगकर्ता}} पान्नो",
        "tooltip-pt-anonuserpage": "तमी जो IP ठेगानाको रुपमी सम्पादन गद्दै छौ , त्यैको प्रयोगकर्ता पानो निम्न छ :",
        "tooltip-ca-undelete": "मेट्याको भया पनि यै पानाको सम्पादनहरू पुन:प्राप्त गर",
        "tooltip-ca-move": "यो पानालाई अर्खिठौर सार",
        "tooltip-ca-watch": "यै पानालाई तमरा ध्यानसूचीमि थपिदिय",
+       "tooltip-ca-unwatch": "यै पानालाई तमरि अवलोकनसूची बठेइ हटाऽ",
        "tooltip-search": "{{SITENAME}}मी खोजऽ",
        "tooltip-search-go": "यदी ठ्याक्कै येइ नाउँ भया: पन्ना रैछ भँण्या तै मी जा:।",
        "tooltip-search-fulltext": "यै पाठका लागि पन्नाअनमी खोज",
        "tooltip-ca-nstab-special": "यो खास पानो हो ,तमी यैलाई आफै सम्पादन गद्द सक्दाइन",
        "tooltip-ca-nstab-project": "आयोजना पानो हेरिदिय",
        "tooltip-ca-nstab-image": "चित्र पानो हेर",
+       "tooltip-ca-nstab-mediawiki": "प्रणाली सन्देश हेरऽ",
        "tooltip-ca-nstab-template": "टेम्प्लेट(नमूना) हेरिदिय",
        "tooltip-ca-nstab-category": "श्रेणी पानो हेर",
        "tooltip-minoredit": "येइ लाई सामान्य सम्पादन भँणिबर चिनो लाऽ",
        "anonusers": "{{SITENAME}} का नाम नभयाका {{PLURAL:$2| प्रयोगकर्ता|प्रयोगकर्ताहरू}} $1",
        "simpleantispam-label": "ऐन्टी-स्प्याम जाँच।\nयैलाई <strong>नाइँ</strong> भद्य्या!",
        "pageinfo-title": "\"$1\" खिलाइ जानकारी",
+       "pageinfo-header-basic": "नानबड़ि जानकारी",
        "pageinfo-header-edits": "इतिहास सम्पादन",
+       "pageinfo-header-restrictions": "पन्ना सुरक्षा",
+       "pageinfo-display-title": "धेकिन्या शीर्षक",
+       "pageinfo-default-sort": "पूर्वनिर्धारित अनुक्रमण साँचो",
+       "pageinfo-length": "पन्ना लम्बाइ (बाइटअन मी)",
+       "pageinfo-article-id": "पन्ना आइडी",
+       "pageinfo-language": "पन्ना सामग्री भाषा",
+       "pageinfo-content-model": "पन्ना सामग्री ढङ्ङ",
        "pageinfo-robot-policy": "रोबटअन हताँ अनुक्रमण",
+       "pageinfo-robot-index": "अनुमति भयाऽ",
+       "pageinfo-robot-noindex": "अनुमति नभयाः",
        "pageinfo-watchers": "पन्ना निगरानी अद्द्याऽ सङ्ख्या",
+       "pageinfo-subpages-name": "येइ पन्नाः उपपन्नाअनोः सङ्ख्या",
        "pageinfo-firstuser": "पन्ना सर्जक",
        "pageinfo-firsttime": "पन्ना सिर्जना मिति",
        "pageinfo-edits": "कूल सम्पादन सङ्ख्या",
+       "pageinfo-magic-words": "जादुयी {{PLURAL:$1|आँखर|आँखरअन}} ($1)",
        "pageinfo-toolboxlink": "पन्नाइ जानकारी",
+       "pageinfo-contentpage": "सामग्री पन्नाः रूप मी गणियाऽ",
+       "pageinfo-contentpage-yes": "हाँ",
        "rcpatroldisabled": "अहिलका परिवर्तनहरू गस्ती निष्क्रिय पार्याको छ ।",
        "rcpatroldisabledtext": "अहिलका परिवर्तनहरू गस्ती गुण अहिलको लागि निष्कृय पारियाको छ ।",
        "markedaspatrollederror-noautopatrol": "तमी आफ्नै सम्पादनलाई गस्ती गरियाको भनि चिनो लगाउन नाइसक्दा ।",
        "watchlistedit-clear-done": "तमरो ध्यान सूची खाली गरीयाको छ।",
        "watchlisttools-view": "आधारित फेरबदलीहरू हेर",
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|कुरडी]])",
+       "redirect-submit": "जाऽ",
+       "redirect-user": "प्रयोगकर्ता आइडी",
+       "redirect-page": "पन्ना आइडी",
+       "redirect-revision": "पन्ना संशोधन",
+       "redirect-file": "फाइलनाउँ",
        "specialpages": "खास पन्नाअन",
        "specialpages-group-changes": "अल्लैका परिवर्तन लगहरू",
        "tags": "मान्य परिवर्तन ट्यागहरू",
        "tag-filter": "[[Special:Tags|पुछड]] छानिन्या",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|ट्याग|ट्यागहरू}}]]: $2)",
+       "tags-active-no": "नाइँ",
        "tags-hitcount": "$1 {{PLURAL:$1|परिवर्तन|परिवर्तनहरू}}",
        "tags-create-no-name": "तमीले ट्याग नाम निर्दिष्ट गद्दु पड्ड्या हुन्छ ।",
        "tags-create-warnings-below": "क्या तमी यो ट्याग बनाउन्या काम जारी राख्न चाहन्छौ ?",
        "logentry-upload-upload": "$1 ले $3 {{GENDER:$2|अपलोड अरेका छन्}}",
        "feedback-bugornote": "यदि तमी कुनै प्राविधिक समस्यालाई विस्तारले सम्झाउन तयार छौ भण्या कृपया [$1 बग राख]।\nयदि हैन, भण्या तमी तल दियाको सरल फारमको प्रयोग गद्दसक्द्याहौ । तमरो टिप्पणी, तमरो प्रयोगकर्ता नाम र तमरो ब्राउजरको नाम सहित \"[$3 $2]\" पानामी जोडिन्याछ ।",
        "searchsuggest-search": "{{SITENAME}} खोजऽ",
+       "duration-days": "$1 {{PLURAL:$1|दिन|दिनअन}}",
        "expand_templates_preview_fail_html": "<em>किनकि {{SITENAME}} सिधै एचटिएमयल सक्षम छ र तमीले लग इन गर्या छैनौ, पूर्वावलोकन लुकाइयाको छ ताकि सम्भावित जाभास्क्रिप्ट आक्रमणलाई रोक्द सकियोस् ।</em>\n\n<strong>यदि यो मान्य पूर्ववावलोकन प्रयास हो भण्या पुन प्रयास गर ।</strong>\nयदि यसले कार्य पूर्ण भएन भण्या [[Special:UserLogout|लग आउट गरिबर]] फेरी लग इन गर्या ।",
        "expand_templates_preview_fail_html_anon": "<em>किनकि {{SITENAME}} सिधै एचटिएमयल सक्षम छ र तमीले लग इन गर्या छैनौ, पूर्वावलोकन लुकाइयाको छ ताकि सम्भावित जाभास्क्रिप्ट आक्रमणलाई रोक्द सकियोस् ।</em>\n\n<strong>यदि यो मान्य पूर्वावलोकन प्रयास हो भण्या कृपया [[Special:UserLogin|लग इन गरिबर]] पुनः प्रयास गर्या ।</strong>",
        "default-skin-not-found": "ओह! तमरो विकिको पूर्व निर्धारित खोल जस्तो कि <code dir=\"ltr\">$wgDefaultSkin</code> मी बताइयाको<code>$1</code>, उपलब्ध नाईथिन् ।\n\nतमरो इन्स्टलेसन यी खोलहरूलाई सम्मिलित गर्दछ {{PLURAL:$4|खोल|खोलहरू}}। हेर [https://www.mediawiki.org/wiki/Manual:Skin_configuration Manual: खोललाई सम्मिलित गर्नु] ताकि तमीलाई जानकारी होस् कि कसरि {{PLURAL:$4|उसलाई|उसलाई सम्मिलित गर्न सकियोस् र निर्धारितलाई तय गद्दे}}।\n\n$2\n\n; यदि तमीले अहिले मीडियाविकि इन्स्टाल गर्याका छौ:\n: तमीले सम्भवत गिटबठे इन्स्टाल गर्याका छौ, वा सिधै स्रोत कोडबठे गर्याका छौ जैको लागि कुनै अर्कै तारिका प्रयोग गरियाको छ । यो आशा अनुरूप छ । कोशिश गर केहि खोलहरू\n[https://www.mediawiki.org/wiki/Category:All_skins mediawiki.org's मीडियाविकिको खोल डाइरेक्ट्रीबाट डाउनलोड गद्या], जैको लागि तमी:\n:* डाउनलोड गर [https://www.mediawiki.org/wiki/Download टरबल इन्स्टालर], जुन कयौं खोलहरू र विस्तारमी उपलब्ध छन्। तमी खोलहरूको कोड <code>skins/</code> त्यसको डाइरेक्ट्रीबाट कपी-पेस्ट गद्द सक्द्या हौ। \n:* व्यक्तिगत खोलहरू टरबलबठे डाउनलोड गर\n[https://www.mediawiki.org/wiki/Special:SkinDistributor मीडिया विकि] बठे।\n:* [https://www.mediawiki.org/wiki/Download_from_Git#Using_Git_to_download_MediaWiki_skins गिटको प्रयोग गरेर डाउनलोड गद्द सकन्छौ]।\n: यदि तमी विकासकर्ता हौ भण्या यसो गद्दा तमरो गिट-रिपजिटरीमी केहि हुनुहुँदैन । \n; यदि तमीले अहिले मीडियाविकिलाई अपग्रेड गर्याका छौ:\n: मीडियाविकि १.२४ र यैको नवीन रूप स्वतः रूपले खोलहरूलाई सक्षम गद्दैनन् (हेर [https://www.mediawiki.org/wiki/Manual:Skin_autodiscovery Manual:खोलहरूको स्वतः खोज])। तमी निम्नलिखितलाई पेस्ट गद्द सकन्छौ: {{PLURAL:$5|लाइन|लाइनहरू}}  <code>LocalSettings.php</code> मी ताकि {{PLURAL:$5|उसले|सबै}} सक्षम होस् जस्तो कि तमीले इन्स्टाल गर्याको {{PLURAL:$5|खोल|खोलहरू}}को मामिलामी:\n\n<pre dir=\"ltr\">$3</pre>\n\n; यदि तमीले अहिले परिवर्तन गर्याका छौ<code>LocalSettings.php</code>:\n: खोल नामहरूको अगाडी डबल-क्लिक गर जसले तमलाई विभिन्न प्रकारहरूको विकल्प दिन्छ।",
index d5bda60..42974b7 100644 (file)
        "nosuchusershort": "Ne ekzistas uzanto kun la nomo \"$1\". Bonvolu kontroli vian ortografion.",
        "nouserspecified": "Vi devas entajpi salutnomon.",
        "login-userblocked": "Ĉi tiu uzanto estas forbarita. Ensalutado ne estas permesita.",
-       "wrongpassword": "Vi tajpis malĝustan pasvorton. Bonvolu provi denove.",
+       "wrongpassword": "Vi tajpis malĝustan uzantnomon aŭ pasvorton.\nBonvolu provi denove.",
        "wrongpasswordempty": "Vi tajpis malplenan pasvorton. Bonvolu provi denove.",
        "passwordtooshort": "Pasvortoj devas esti longaj almenaŭ  $1 {{PLURAL:$1|1 signon|$1 signojn}}.",
        "passwordtoolong": "Pasvorto ne povas esti pli longa ol {{PLURAL:$1|1 signo|$1 signoj}}.",
        "recentchangeslinked-feed": "Rilataj paĝoj",
        "recentchangeslinked-toolbox": "Rilataj ŝanĝoj",
        "recentchangeslinked-title": "Ŝanĝoj rilataj al \"$1\"",
-       "recentchangeslinked-summary": "Jen listo de ŝanĝoj faritaj lastatempe al paĝoj ligitaj el specifa paĝo (aŭ al membroj de specifa kategorio).\nPaĝoj en [[Special:Watchlist|via atentaro]] estas '''grasaj'''.",
+       "recentchangeslinked-summary": "Entajpu nomon de paĝo por vidi ŝanĝojn sur paĝoj ligitaj el aŭ al tiu ĉi paĝo. (Por vidi anojn de kategorio, entajpu Category:nomo de kategorio). Ŝanĝoj sur paĝoj en [[Special:Watchlist|via atentaro]] markiĝas <strong>grase</strong>.",
        "recentchangeslinked-page": "Nomo de paĝo:",
        "recentchangeslinked-to": "Montru ŝanĝojn al paĝoj ligitaj al la specifa paĝo anstataŭe.",
        "recentchanges-page-added-to-category": "[[:$1]] aldonita al la kategorio",
index 64d134b..a3a9cad 100644 (file)
        "botpasswords-insert-failed": "No se pudo agregar el nombre del bot \"$1\". ¿Ya ha sido añadido?",
        "botpasswords-update-failed": "No se pudo actualizar el nombre del bot \"$1\". ¿Ha sido borrado?",
        "botpasswords-created-title": "Se creó la contraseña de bot",
-       "botpasswords-created-body": "Se creó la contraseña del bot llamado \"$1\" del usuario \"$2\".",
+       "botpasswords-created-body": "Se creó la contraseña del robot «$1» perteneciente {{GENDER:$2|al usuario|a la usuaria}} «$2».",
        "botpasswords-updated-title": "Se actualizó la contraseña de bot",
-       "botpasswords-updated-body": "Se actualizó la contraseña del bot llamado \"$1\" del usuario \"$2\".",
+       "botpasswords-updated-body": "Se actualizó la contraseña del robot «$1» perteneciente {{GENDER:$2|al usuario|a la usuaria}} «$2».",
        "botpasswords-deleted-title": "Se eliminó la contraseña de bot",
-       "botpasswords-deleted-body": "Se eliminó la contraseña del bot llamado \"$1\" del usuario \"$2\".",
+       "botpasswords-deleted-body": "Se eliminó la contraseña del robot «$1» perteneciente {{GENDER:$2|al usuario|a la usuaria}} «$2».",
        "botpasswords-newpassword": "La contraseña nueva para acceder con <strong>$1</strong> es <strong>$2</strong>. <em>Guarda esta información para su consulta futura.</em> <br> (En caso de robots antiguos que requieren que el nombre de acceso coincida con el de usuario, también puedes utilizar <strong>$3</strong> como nombre de usuario y <strong>$4</strong> como contraseña.)",
        "botpasswords-no-provider": "BotPasswordsSessionProvider no está disponible.",
        "botpasswords-restriction-failed": "Las restricciones de la contraseña de bot impiden este inicio de sesión.",
        "rcfilters-restore-default-filters": "Restaurar filtros predeterminados",
        "rcfilters-clear-all-filters": "Borrar todos los filtros",
        "rcfilters-show-new-changes": "Ver cambios más recientes",
-       "rcfilters-search-placeholder": "Filtrar cambios (usa el menú o buscar para aplicar filtro)",
+       "rcfilters-search-placeholder": "Filtrar cambios (utiliza el menú o busca el nombre de un filtro)",
        "rcfilters-invalid-filter": "Filtro no válido",
        "rcfilters-empty-filter": "No hay filtros activos. Se muestran todas las contribuciones.",
        "rcfilters-filterlist-title": "Filtros",
        "tooltip-ca-nstab-main": "Ver la página de contenido",
        "tooltip-ca-nstab-user": "Ver la página del usuario",
        "tooltip-ca-nstab-media": "Ver la página de multimedia",
-       "tooltip-ca-nstab-special": "Esta es una página especial, y no puede editarse",
+       "tooltip-ca-nstab-special": "Esta es una página especial y no puede editarse",
        "tooltip-ca-nstab-project": "Ver la página del proyecto",
        "tooltip-ca-nstab-image": "Ver la página del archivo",
        "tooltip-ca-nstab-mediawiki": "Ver el mensaje de sistema",
index 43b246b..004c740 100644 (file)
        "autosumm-blank": "صفحه را خالی کرد",
        "autosumm-replace": "جایگزینی صفحه با '$1'",
        "autoredircomment": "تغییرمسیر به [[$1]]",
+       "autosumm-removed-redirect": "تغییرمسیر به [[$1]] حذف شد",
        "autosumm-new": "صفحه‌ای تازه حاوی «$1» ایجاد کرد",
        "autosumm-newblank": "ایجاد صفحه خالی",
        "size-bytes": "$1 بایت",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|برچسب|برچسب‌ها}}]]: $2)",
        "tag-mw-contentmodelchange": "تغییر مدل محتوا",
        "tag-mw-contentmodelchange-description": "ویرایش‌هایی که [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel مدل محتوای صفحه را تغییر می‌دهند]",
+       "tag-mw-removed-redirect": "تغییرمسیر حذف شد",
+       "tag-mw-rollback": "واگردانی",
        "tags-title": "برچسب‌ها",
        "tags-intro": "این صفحه فهرستی‌است از برچسب‌هایی که نرم‌افزار با آن‌ها ویرایش‌ها را علامت‌گذری می‌کند، به همراه معانی آن‌ها.",
        "tags-tag": "نام برچسب",
index a3c8cd6..eb6f531 100644 (file)
        "logentry-delete-delete": "$1 a supprimé la page $3",
        "logentry-delete-delete_redir": "$1 a {{GENDER:$2|supprimé}} la redirection vers $3 par écrasement",
        "logentry-delete-restore": "$1 a restauré la page $3 ($4)",
-       "logentry-delete-restore-nocount": "$1 {{GENDER:$2|a restauré}} la page $3",
+       "logentry-delete-restore-nocount": "$1 {{GENDER:$2||}}a restauré la page $3",
        "restore-count-revisions": "{{PLURAL:$1|1 révision|$1 révisions}}",
        "restore-count-files": "{{PLURAL:$1|1 fichier|$1 fichiers}}",
        "logentry-delete-event": "$1 {{GENDER:$2|a modifié}} la visibilité {{PLURAL:$5|d'un événement du journal|de $5 événements du journal}} sur $3: $4",
index 4782947..e8ae7e6 100644 (file)
        "timezoneregion-indian": "Océano Índico",
        "timezoneregion-pacific": "Océano Pacífico",
        "allowemail": "Admitir mensaxes de correo electrónico doutros usuarios",
+       "email-allow-new-users-label": "Permite correos electrónicos de usuarios novos",
        "email-blacklist-label": "Prohibir a eses usuarios enviarme correos electrónicosː",
        "prefs-searchoptions": "Procura",
        "prefs-namespaces": "Espazos de nomes",
        "recentchangeslinked-feed": "Cambios relacionados",
        "recentchangeslinked-toolbox": "Cambios relacionados",
        "recentchangeslinked-title": "Cambios relacionados con \"$1\"",
-       "recentchangeslinked-summary": "Esta é unha lista dos cambios que se realizaron recentemente nas páxinas vinculadas a esta (ou nos membros da categoría especificada).\nAs páxinas da súa [[Special:Watchlist|lista de vixilancia]] aparecen en '''negra'''.",
+       "recentchangeslinked-summary": "Introduce un nome de páxina para ver os cambios en páxinas ligadas dende ou ata esa páxina. (Para ver os membros dunha categoría, introduce Categoría:Nome da categoría). Os cambios na túa [[Special:Watchlist|lista de vixiancia]] están en <strong>negra</strong>.",
        "recentchangeslinked-page": "Nome da páxina:",
        "recentchangeslinked-to": "Mostrar os cambios relacionados das páxinas que ligan coa dada",
        "recentchanges-page-added-to-category": "\"[[:$1]]\" engadiuse á categoría",
index 43dff0f..25a09cb 100644 (file)
@@ -84,7 +84,8 @@
                        "चक्रपाणी",
                        "Anamdas",
                        "Sachinkatiyar",
-                       "Rishi.Singh"
+                       "Rishi.Singh",
+                       "Clockery"
                ]
        },
        "tog-underline": "कड़ियाँ अधोरेखन:",
        "watcherrortext": "\"$1\" के लिये आपकी ध्यानसूची सेटिंग बदलते समय त्रुटि हुई।",
        "enotif_reset": "सभी पृष्ठ देखे हुए दर्शाएँ",
        "enotif_impersonal_salutation": "{{SITENAME}} सदस्य",
-       "enotif_subject_deleted": "{{SITENAME}} पृष्ठ $1 को {{gender:$2|$2}} ने हटा दिया है",
-       "enotif_subject_created": "{{SITENAME}} पृष्ठ $1 को {{gender:$2|$2}} ने बना दिया है",
-       "enotif_subject_moved": "{{SITENAME}} पृष्ठ $1 को {{gender:$2|$2}} ने स्थानांतरित कर दिया है",
-       "enotif_subject_restored": "{{SITENAME}} पृष्ठ $1 को {{gender:$2|$2}} ने पुनर्स्थापित कर दिया है",
-       "enotif_subject_changed": "{{SITENAME}} पृष्ठ $1 को {{gender:$2|$2}} ने परिवर्तित किया है",
-       "enotif_body_intro_deleted": "{{SITENAME}} पृष्ठ $1 को {{gender:$2|$2}} ने $PAGEEDITDATE को हटा दिया है, देखें <$3>।",
-       "enotif_body_intro_created": "{{SITENAME}} पृष्ठ $1 को {{gender:$2|$2}} ने $PAGEEDITDATE को बनाया है, वर्तमान अवतरण के लिए $3 देखें।",
-       "enotif_body_intro_moved": "{{SITENAME}} पृष्ठ $1 को {{gender:$2|$2}} ने $PAGEEDITDATE को स्थानांतरित किया है, वर्तमान अवतरण के लिए $3 देखें।",
-       "enotif_body_intro_restored": "{{SITENAME}} पृष्ठ $1 को {{gender:$2|$2}} ने $PAGEEDITDATE को पुनर्स्थापित किया है, वर्तमान अवतरण के लिए $3 देखें।",
-       "enotif_body_intro_changed": "{{SITENAME}} पृष्ठ $1 को {{gender:$2|$2}} ने $PAGEEDITDATE को परिवर्तित किया है, वर्तमान अवतरण के लिए $3 देखें।",
+       "enotif_subject_deleted": "{{SITENAME}} पृष्ठ $1 को {{GENDER:$2|$2}} ने हटा दिया है",
+       "enotif_subject_created": "{{SITENAME}} पृष्ठ $1 को {{GENDER:$2|$2}} ने बना दिया है",
+       "enotif_subject_moved": "{{SITENAME}} पृष्ठ $1 को {{GENDER:$2|$2}} ने स्थानांतरित कर दिया है",
+       "enotif_subject_restored": "{{SITENAME}} पृष्ठ $1 को {{GENDER:$2|$2}} ने पुनर्स्थापित कर दिया है",
+       "enotif_subject_changed": "{{SITENAME}} पृष्ठ $1 को {{GENDER:$2|$2}} ने परिवर्तित किया है",
+       "enotif_body_intro_deleted": "{{SITENAME}} पृष्ठ $1 को {{GENDER:$2|$2}} ने $PAGEEDITDATE को हटा दिया है, देखें $3।",
+       "enotif_body_intro_created": "{{SITENAME}} पृष्ठ $1 को {{GENDER:$2|$2}} ने $PAGEEDITDATE को बनाया है, वर्तमान अवतरण के लिए $3 देखें।",
+       "enotif_body_intro_moved": "{{SITENAME}} पृष्ठ $1 को {{GENDER:$2|$2}} ने $PAGEEDITDATE को स्थानांतरित किया है, वर्तमान अवतरण के लिए $3 देखें।",
+       "enotif_body_intro_restored": "{{SITENAME}} पृष्ठ $1 को {{GENDER:$2|$2}} ने $PAGEEDITDATE को पुनर्स्थापित किया है, वर्तमान अवतरण के लिए $3 देखें।",
+       "enotif_body_intro_changed": "{{SITENAME}} पृष्ठ $1 को {{GENDER:$2|$2}} ने $PAGEEDITDATE को परिवर्तित किया है, वर्तमान अवतरण के लिए $3 देखें।",
        "enotif_lastvisited": "आपकी आखिरी भेंट के बाद हुए बदलाव देखने के लिये $1 देखें।",
        "enotif_lastdiff": "इस बदलाव को देखने के लिये $1 देखें।",
        "enotif_anon_editor": "अनामक सदस्य $1",
index f98d3fe..a29aa2c 100644 (file)
        "nosuchusershort": "Ne postoji suradnik s imenom \"$1\". Provjerite Vaš unos.",
        "nouserspecified": "Molimo navedite suradničko ime.",
        "login-userblocked": "Ovaj je suradnik blokiran. Prijava nije dopuštena.",
-       "wrongpassword": "Zaporka koju ste unijeli nije ispravna. Molimo Vas, pokušajte ponovo.",
+       "wrongpassword": "Suradničko ime ili zaporka koju ste unijeli nije ispravno. Molimo Vas, pokušajte ponovo.",
        "wrongpasswordempty": "Niste unijeli zaporku. Pokušajte ponovno.",
        "passwordtooshort": "Zaporka mora sadržavati najmanje {{PLURAL:$1|1 znak|$1 znaka|$1 znakova}}.",
        "passwordtoolong": "Zaporke ne mogu biti duže od {{PLURAL:$1|jednoga znaka|$1 znaka|$1 znakova}}.",
        "autosumm-blank": "uklonjen cjelokupni sadržaj stranice",
        "autosumm-replace": "Zamijenjen sadržaj stranice s »$1«",
        "autoredircomment": "Preusmjeravanje stranice na [[$1]]",
+       "autosumm-removed-redirect": "Uklonjeno preusmjeravanje na [[$1]]",
+       "autosumm-changed-redirect-target": "Promijenjeno je odredište preusmjeravanja sa stranice [[$1]] na [[$2]]",
        "autosumm-new": "Stvorena nova stranica sa sadržajem: »$1«.",
        "autosumm-newblank": "Stvorena prazna stranica.",
        "size-bytes": "$1 {{PLURAL:$1|bajt|bajta|bajtova}}",
        "watchlistedit-raw-done": "Vaš popis praćenja je snimljen.",
        "watchlistedit-raw-added": "{{PLURAL:$1|1 stranica je dodana|$1 stranice su dodane}}:",
        "watchlistedit-raw-removed": "{{PLURAL:$1|1 stranica je uklonjena|$1 stranice su ukonjene}}:",
-       "watchlistedit-clear-title": "Očišćen popis praćenja",
+       "watchlistedit-clear-title": "Očisti popis praćenja",
        "watchlistedit-clear-legend": "Obriši popis praćenja",
        "watchlistedit-clear-explain": "Sve stavke s popisa praćenja će biti izbrisane",
        "watchlistedit-clear-titles": "Imena stranica:",
index e32e56e..500e25e 100644 (file)
        "tag-filter-submit": "Penyaring",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Tenger|Tenger}}]]: $2)",
        "tag-mw-contentmodelchange": "owahan modhèl isi",
+       "tag-mw-blank": "Ngosongaké",
        "tags-title": "Tag",
        "tags-intro": "Kaca iki isi pratélan tenger sing dienggo nandhani besutan déning piranti alus, sinartan tegesé.",
        "tags-tag": "Jeneng tag",
index 0c314d7..5fb17ba 100644 (file)
        "action-changetags": "თავისუფალი ტეგების დამატება და წაშლა ცალკეულ ცვლილებებსა და ჟურნალების ჩანაწერებში",
        "action-deletechangetags": "მონაცემთა ბაზიდან ტეგების წაშლა",
        "action-purge": "ამ გვერდის წაშლა",
-       "nchanges": "$1 ცვლილება",
+       "nchanges": "$1 {{PLURAL:$1|ცვლილება|ცვლილება}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|ბოლო ვიზიტის შემდეგ}}",
        "enhancedrc-history": "ისტორია",
        "recentchanges": "ბოლო ცვლილებები",
        "rcfilters-activefilters": "აქტიური ფილტრები",
        "rcfilters-advancedfilters": "გაფართოებული ფილტრები",
        "rcfilters-limit-title": "ცვლილელების ნახვა",
-       "rcfilters-limit-and-date-label": "$1 {{PLURAL:$1|ცვლილება|ცვლილება|ცვლილება}}, $2",
+       "rcfilters-limit-and-date-label": "{{PLURAL:$1|ცვლილება|$1 ცვლილება}}, $2",
        "rcfilters-date-popup-title": "საძიებო დროის მონაკვეთი",
        "rcfilters-days-title": "უკანასკნელი დღეები",
        "rcfilters-hours-title": "ბოლო საათები",
        "tags-delete": "წაშლა",
        "tags-activate": "გააქტიურება",
        "tags-deactivate": "დეაქტივაცია",
-       "tags-hitcount": "$1 ცვლილება",
+       "tags-hitcount": "$1 {{PLURAL:$1|ცვლილება|ცვლილება}}",
        "tags-manage-no-permission": "თქვენ არ გაქვთ შეცვლილი დასათაურების მართვის უფლება",
        "tags-manage-blocked": "თქვენ ვერ შეძლებთ ცვლილებების ტეგების მართვას სანამ {{GENDER:$1|თქვენ}} დაბლოკილი ხართ.",
        "tags-create-heading": "ახალი ტეგის შექმნა",
index 20ce7a7..2e93cba 100644 (file)
        "timezoneregion-indian": "Indeschen Ozean",
        "timezoneregion-pacific": "Pazifeschen Ozean",
        "allowemail": "E-Maile vun anere Benotzer kréien.",
+       "email-allow-new-users-label": "E-Maile vu ganz neie Benotzer erlaben",
        "prefs-searchoptions": "Sichen",
        "prefs-namespaces": "Nummraim",
        "default": "Standard",
index a29f1ad..8ec2530 100644 (file)
@@ -36,7 +36,8 @@
                        "Zygimantus",
                        "Matma Rex",
                        "Nemo bis",
-                       "Nersip"
+                       "Nersip",
+                       "Manvydasz"
                ]
        },
        "tog-underline": "Nuorodos pabraukimas:",
        "nosuchusershort": "Nėra jokio naudotojo, pavadinto „$1“. Patikrinkite rašybą.",
        "nouserspecified": "Jums reikia nurodyti naudotojo vardą.",
        "login-userblocked": "Šis naudotojas yra užblokuotas. Prisijungti neleidžiama.",
-       "wrongpassword": "Įvestas neteisingas slaptažodis. Pamėginkite dar kartą.",
+       "wrongpassword": "Įvestas neteisingas vartotojo vardas ar slaptažodis. Pamėginkite dar kartą.",
        "wrongpasswordempty": "Įvestas slaptažodis yra tuščias. Pamėginkite vėl.",
        "passwordtooshort": "Slaptažodžiai turi būti bent $1 {{PLURAL:$1|simbolio|simbolių|simbolių}} ilgio.",
        "passwordtoolong": "Slaptažodžiai negali būti ilgesni nei {{PLURAL:$1|1 simbolis|$1 simboliai}}.",
        "prefs-help-recentchangescount": "Į tai įeina naujausi keitimai, puslapių istorijos ir specialiųjų veiksmų sąrašai.",
        "prefs-help-watchlist-token2": "Tai yra slaptas jūsų stebimųjų sąrašo raktas, skirtas žiniatinkliui.\nKiekvienas, kurį jį žino, gali skaityti jūsų stebimųjų puslapių sąrašą, taigi, juo nesidalinkite.\nJei reikia jį anuliuoti, [[Special:ResetTokens|spauskite čia]].",
        "savedprefs": "Nustatymai sėkmingai išsaugoti.",
-       "savedrights": "Naudotojo teisės {{GENDER:$1|$1}} buvo išsaugotos.",
+       "savedrights": "Naudotojo {{GENDER:$1|$1}} grupės buvo išsaugotos.",
        "timezonelegend": "Laiko juosta:",
        "localtime": "Vietinis laikas:",
        "timezoneuseserverdefault": "Naudoti wiki pradinį ($1)",
        "timezoneregion-indian": "Indijos vandenynas",
        "timezoneregion-pacific": "Ramusis vandenynas",
        "allowemail": "Leisti kitiems naudotojams siųsti man el. laiškus",
+       "email-allow-new-users-label": "Leidžia el. laiškus iš naujų vartotojų",
        "email-blacklist-label": "Neleisti šiems vartotojams siųsti man el. laiškų:",
        "prefs-searchoptions": "Paieška",
        "prefs-namespaces": "Vardų sritys",
        "action-deletedhistory": "žiūrėti puslapio ištrintą istoriją",
        "action-browsearchive": "ieškoti ištrintų puslapių",
        "action-undelete": "atkurti puslapius",
-       "action-suppressrevision": "peržiūrėti ir atkurti šią paslėptą versiją",
+       "action-suppressrevision": "peržiūrėti ir atkurti paslėptas versijas",
        "action-suppressionlog": "peržiūrėti šį privatų registrą",
        "action-block": "neleisti šiam naudotojui redaguoti",
        "action-protect": "pakeisti apsaugos lygius šiam puslapiui",
        "rcfilters-activefilters": "Aktyvūs filtrai",
        "rcfilters-advancedfilters": "Detalūs filtrai",
        "rcfilters-quickfilters": "Išsaugoti filtrai",
-       "rcfilters-quickfilters-placeholder-title": "Nėra išsaugotų nuorodų",
+       "rcfilters-quickfilters-placeholder-title": "Nėra išsaugotų filtrų",
        "rcfilters-savedqueries-defaultlabel": "Išsaugoti filtrai",
        "rcfilters-savedqueries-rename": "Pervadinti",
        "rcfilters-savedqueries-setdefault": "Nustatyti kaip numatytą",
        "rcfilters-savedqueries-remove": "Pašalinti",
        "rcfilters-savedqueries-new-name-label": "Pavadinimas",
        "rcfilters-savedqueries-new-name-placeholder": "Apibūdinkite šio filtro tikslą.",
-       "rcfilters-savedqueries-apply-label": "Išsaugoti nustatymus",
+       "rcfilters-savedqueries-apply-label": "Sukurti filtrą",
        "rcfilters-savedqueries-cancel-label": "Atšaukti",
        "rcfilters-savedqueries-add-new-title": "Išsaugoti dabartinius filtro nustatymus",
        "rcfilters-restore-default-filters": "Atstatyti numatytuosius filtrus",
        "rcfilters-invalid-filter": "Negalimas filtras",
        "rcfilters-empty-filter": "Nėra aktyvių filtrų. Rodomi visi indeliai.",
        "rcfilters-filterlist-title": "Filtrai",
-       "rcfilters-filterlist-whatsthis": "Kas tai?",
-       "rcfilters-filterlist-feedbacklink": "Pateikite atsiliepimą apie naujus (beta) filtrus",
+       "rcfilters-filterlist-whatsthis": "Kaip tai veikia?",
+       "rcfilters-filterlist-feedbacklink": "Pateikite atsiliepimą apie šiuos (naujus) filtravimo įrankius",
        "rcfilters-highlightbutton-title": "Paryškinti rezultatus",
        "rcfilters-highlightmenu-title": "Pasirinkite spalvą",
        "rcfilters-highlightmenu-help": "Pasirinkite spalvą šio elemento paryškinimui",
        "rcfilters-filter-editsbyother-description": "Visi keitimai, išskyrus jūsų.",
        "rcfilters-filtergroup-userExpLevel": "Patirties lygis (tik registruotiems vartotojams)",
        "rcfilters-filter-user-experience-level-registered-label": "Registruoti",
-       "rcfilters-filter-user-experience-level-registered-description": "Prisijungę redaktoriai.",
+       "rcfilters-filter-user-experience-level-registered-description": "Prisijungę naudotojai.",
        "rcfilters-filter-user-experience-level-unregistered-label": "Neregistruoti",
-       "rcfilters-filter-user-experience-level-unregistered-description": "Redaktoriai, kurie nėra prisijungę.",
+       "rcfilters-filter-user-experience-level-unregistered-description": "Naudotojai, kurie nėra prisijungę.",
        "rcfilters-filter-user-experience-level-newcomer-label": "Naujokai",
        "rcfilters-filter-user-experience-level-newcomer-description": "Mažiau nei 10 keitimų ir 4 dienų aktyvumo.",
        "rcfilters-filter-user-experience-level-learner-label": "Mokiniai",
        "rcfilters-filter-humans-label": "Žmogaus (ne roboto)",
        "rcfilters-filter-humans-description": "Keitimai atlikti žmonių.",
        "rcfilters-filtergroup-reviewstatus": "Peržiūrėti statusą",
+       "rcfilters-filter-patrolled-label": "Stebimas",
+       "rcfilters-filter-patrolled-description": "Pakeitimai pažymėti kaip stebimi.",
+       "rcfilters-filter-unpatrolled-label": "Nestebimas",
+       "rcfilters-filter-unpatrolled-description": "Pakeitimai pažymėti kaip nestebimi.",
        "rcfilters-filtergroup-significance": "Reikšmė",
        "rcfilters-filter-minor-label": "Smulkūs pakeitimai",
        "rcfilters-filter-minor-description": "Keitimai, kuriuos autorius pažymėjo kaip mažus.",
        "rcfilters-filter-watchlist-watched-description": "Pakeitimai puslapiuose, jūsų Stebimųjų sąraše.",
        "rcfilters-filter-watchlist-watchednew-label": "Nauji Stebimųjų sąrašo pakeitimai",
        "rcfilters-filter-watchlist-notwatched-label": "Nėra Stebimųjų sąraše",
+       "rcfilters-filter-watchlistactivity-unseen-label": "Neperžiūrėti pakeitimai",
+       "rcfilters-filter-watchlistactivity-seen-label": "Peržiūrėti pakeitimai",
        "rcfilters-filtergroup-changetype": "Pakeitimo tipas",
        "rcfilters-filter-pageedits-label": "Puslapių keitimai",
        "rcfilters-filter-newpages-label": "Puslapių sukūrimai",
        "rcfilters-filter-previousrevision-description": "Visi keitimai, kurie nėra naujausi puslapio keitimai.",
        "rcfilters-view-tags": "Pažymėti keitimai",
        "rcfilters-view-tags-help-icon-tooltip": "Sužinoti daugiau apie Pažymėtus pakeitimus",
+       "rcfilters-liveupdates-button": "Gyvi atnaujinimai",
+       "rcfilters-liveupdates-button-title-on": "Išjungti gyvus atnaujinimus",
+       "rcfilters-watchlist-markseen-button": "Pažymėti visus pakeitimus kaip peržiūrėtus",
+       "rcfilters-watchlist-edit-watchlist-button": "Redaguoti stebimųjų sąrašą",
+       "rcfilters-watchlist-showupdated": "Puslapiai pakeisti nuo tada, kai paskutinį kartą apsilankėte juose, yra <strong>paryškinti</strong>.",
+       "rcfilters-preference-label": "Slėpti patobulintą naujausių pakeitimų versiją",
+       "rcfilters-filter-showlinkedfrom-label": "Rodyti pakeitimus puslapiuose, iš kurių esate nukreipti",
+       "rcfilters-target-page-placeholder": "Įveskite puslapio pavadinimą",
        "rcnotefrom": "Žemiau yra {{PLURAL:$5|pakeitimas|pakeitimai}} pradedant <strong>$3, $4</strong> (rodoma iki <strong>$1</strong> pakeitimų).",
        "rclistfromreset": "Nustatyti duomenų pasirinkimą iš naujo",
        "rclistfrom": "Rodyti naujus pakeitimus pradedant $3 $2",
        "listfiles_size": "Dydis",
        "listfiles_description": "Aprašymas",
        "listfiles_count": "Versijos",
-       "listfiles-show-all": "Įtraukti senesnes paveikslėlių versijas",
+       "listfiles-show-all": "Įtraukti senesnes rinkmenų versijas",
        "listfiles-latestversion": "Dabartinė versija",
        "listfiles-latestversion-yes": "Taip",
        "listfiles-latestversion-no": "Ne",
        "enotif_lastdiff": "Užeikite į $1, jei norite pamatyti šį pakeitimą.",
        "enotif_anon_editor": "anoniminis naudotojas $1",
        "enotif_body": "$WATCHINGUSERNAME,\n\n\n$PAGEEDITDATE {{SITENAME}} projekte $PAGEEDITOR $CHANGEDORCREATED puslapį „$PAGETITLE“, dabartinę versiją rasite adresu $PAGETITLE_URL.\n\n$NEWPAGE\n\nRedaguotojo komentaras: $PAGESUMMARY $PAGEMINOREDIT\n\nSusisiekti su redaguotoju:\nel. paštu: $PAGEEDITOR_EMAIL\nwiki: $PAGEEDITOR_WIKI\n\nDaugiau pranešimų apie vėlesnius pakeitimus nebus siunčiama, jei neapsilankysite puslapyje.\nJūs taip pat galite išjungti pranešimo žymę visiems jūsų stebimiems puslapiams savo stebimųjų sąraše.\n\n Jūsų draugiškoji projekto {{SITENAME}} pranešimų sistema\n\n--\nNorėdami pakeisti e-paštu siunčiamų pranešimų nustatymus, užeikite į\n{{canonicalurl:{{#special:Preferences}}}}\n\nNorėdami pakeisti stebimųjų puslapių nustatymus, užeikite į\n{{canonicalurl:{{#special:EditWatchlist}}}}\n\nNorėdami puslapį iš stebimųjų puslapių sąrašo, užeikite į\n$UNWATCHURL\n\nAtsiliepimai ir pagalba:\n$HELPPAGE",
+       "enotif_minoredit": "Tai smulkus pakeitimas",
        "created": "sukurė",
        "changed": "pakeitė",
        "deletepage": "Trinti puslapį",
        "anonymous": "{{SITENAME}} {{PLURAL:$1|anoniminis naudotojas|anoniminiai naudotojai}}",
        "siteuser": "{{SITENAME}} {{GENDER:$2|naudotojas|naudotoja}} $1",
        "anonuser": "{{SITENAME}} anoniminis naudotojas $1",
-       "lastmodifiedatby": "Šį puslapį paskutinį kartą redagavo $3 $2, $1.",
+       "lastmodifiedatby": "Šį puslapį paskutinį kartą redagavo $2, $1, $3.",
        "othercontribs": "Paremta $1 darbu.",
        "others": "kiti",
        "siteusers": "{{SITENAME}} {{PLURAL:$2|naudotojas|naudotojai}} $1",
        "compare-invalid-title": "Jūsų nurodytas pavadinimas neleistinas.",
        "compare-title-not-exists": "Pavadinimas, kurį nurodėte, neegzistuoja.",
        "compare-revision-not-exists": "Keitimas, kurį nurodėte, neegzistuoja.",
-       "diff-form": "'''forma'''",
+       "diff-form": "Skirtumai",
        "dberr-problems": "Atsiprašome! Svetainei iškilo techninių problemų.",
        "dberr-again": "Palaukite kelias minutes ir perkraukite puslapį.",
        "dberr-info": "(Nepavyksta pasiekti duomenų bazės: $1)",
index 5a71f4e..bda6a87 100644 (file)
        "year": "No gada (un senāki):",
        "sp-contributions-newbies": "Rādīt jauno lietotāju devumu",
        "sp-contributions-newbies-sub": "Jaunie lietotāji",
+       "sp-contributions-newbies-title": "Jauno dalībnieku devums",
        "sp-contributions-blocklog": "bloķēšanas reģistrs",
        "sp-contributions-suppresslog": "cenzēja {{GENDER:$1|dalībnieka|dalībnieces}} devumu",
        "sp-contributions-deleted": "dzēstais {{GENDER:$1|dalībnieka|dalībnieces}} devums",
        "ipb_blocked_as_range": "Kļūda: IP $1 nav bloķēta tieši, tāpēc to nevar atbloķēt.\nTā ir bloķēta kā daļa no IP adrešu diapazona $2, kuru var atbloķēt.",
        "ip_range_invalid": "Nederīgs IP diapazons",
        "proxyblocker": "Starpniekservera bloķētājs",
+       "softblockrangesreason": "No tavas IP adreses ($1) nav atļauts anonīms devums. Lūdzu, pieslēdzies.",
        "ipbblocked": "Tu nevar bloķēt vai atbloķēt lietotājus, jo Tu pats esi bloķēts",
        "ipbnounblockself": "Tev nav atļauts sevi atbloķēt",
        "lockdb": "Bloķēt datubāzi",
        "newimages-legend": "Filtrs",
        "newimages-label": "Faila nosaukums (vai tā daļa):",
        "newimages-user": "IP adrese vai lietotājvārds",
+       "newimages-newbies": "Rādīt tikai jaunu dalībnieku devumu",
        "newimages-showbots": "Parādīt botu augšupielādētos failus",
        "newimages-hidepatrolled": "Paslēpt pārbaudītās augšupielādes",
        "noimages": "Nav nekā ko redzēt.",
index fd101a9..9ffc4f9 100644 (file)
        "listusers": "Brukarliste",
        "listusers-editsonly": "Vis berre brukarar med endringar",
        "listusers-creationsort": "Sorter etter opprettingsdato",
+       "listusers-desc": "Sorter i minkande rekkjefylgd",
        "usereditcount": "{{PLURAL:$1|éi endring|$1 endringar}}",
        "usercreated": "{{GENDER:$3|Oppretta}} den $1 $2",
        "newpages": "Nye sider",
        "tag-filter": "[[Special:Tags|Merke]]filter:",
        "tag-filter-submit": "Filtrer",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Merke}}]]: $2)",
+       "tag-mw-contentmodelchange": "endring av innhaldsmodell",
+       "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",
        "tag-mw-blank-description": "Endringar som tømmer ei side",
+       "tag-mw-replace": "Bytte ut",
        "tag-mw-replace-description": "Endringar som fjernar meir enn 90&nbsp;% av innhaldet på ei side",
        "tag-mw-rollback": "Attenderulling",
        "tags-title": "Merke",
index 7427f03..9c13b73 100644 (file)
        "blankarticle": "<strong>خبرتیا:</strong> تاسو د یو خالي مخ جوړلو په حال کي ياست.\nکه «$1» دوهم ځلي کښي کاږي، نو مخ به د معلوماتو بغير جوړ سي.",
        "anoneditwarning": "<strong>گواښنه:</strong>  تاسې غونډال کې نه ياست ننوتي. که تاسې کوم سمونونه ترسره کوۍ نو ستاسې IP پته به ټولو ته د دې مخ د سمونونو په پېښليک کې ښکاري. که تاسې په خپل نوم <strong>[$1 کې ننوځئ]</strong> يا <strong>[$2 يو گڼون جوړ کړئ]</strong>، نو ستاسې سمونونه به ستاسې کارن-نوم اړونده ثبت شي چې ډېرې نورې گټې هم لري.",
        "anonpreviewwarning": "''تاسې غونډال ته نه ياست ننوتي. خوندي کولو سره به ستاسې IP پته به د دې مخ د سمونونو په پېښليک کې ثبت شي.''",
-       "missingsummary": "<strong>یادونه:</strong> تاسو د سمون لنډیز ندی چمتو کړی.\nکه تاسو \"$1\" ټک وکړئبیا به ستاسو بدلون پرته له دې چې يو وي خوندي شي.",
+       "missingsummary": "<strong>یادونه:</strong> تاسو د سمون لنډیز ندی چمتو کړی.\nکه تاسو \"$1\" کليک کړي نو بیا به ستاسو بدلون پرته له کوم انتظاره خوندي شي.",
        "selfredirect": "<strong>خبرداری:</strong> تاسو دا پاڼه دپاڼي خپل مخ ته استوي.ښایي تاسو د ګرځولو لپاره ناسم هدف مشخص کړی وي، یا تاسو ممکن په غلطه پاڼه سمونه کوي.\nکه تاسو \"$1\" بيا کلیک کړي، د مخ ورګرځونه به په هر دليل جوړه شي.",
        "missingcommenttext": "لطفاً کمينټ لاندې وليکۍ.",
-       "missingcommentheader": "<strong>یادونه:</strong> تاسو د سمون لنډیز ندی چمتو کړی.\nکه تاسو \"$1\" ټک وکړئبیا به ستاسو بدلون پرته له دې چې يو وي خوندي شي.",
+       "missingcommentheader": "<strong>یادونه:</strong> تاسو د سمون لنډیز ندی چمتو کړی.\nکه تاسو \"$1\" کليک کړي نو بیا به ستاسو بدلون پرته له کوم انتظاره خوندي شي.",
        "summary-preview": "د لنډيز مخليدنه:",
        "subject-preview": "د پروژې بيا ليدنه:",
        "previewerrortext": "د بدلونونو د مخليدنو په وخت کې مو يوه ستونزه رامېنځ ته شوه.",
index 1c3f0a1..03fddda 100644 (file)
        "tag-mw-changed-redirect-target-description": "Правки, которые изменяют цель перенаправления",
        "tag-mw-blank": "Очистка",
        "tag-mw-blank-description": "Правки, которые очищают страницу",
-       "tag-mw-replace": "Ð\97аменено",
+       "tag-mw-replace": "заменено",
        "tag-mw-replace-description": "Правки, которые удаляют более 90 % содержимого страницы",
        "tag-mw-rollback": "откат",
        "tag-mw-rollback-description": "Правки, которые откатывают предыдущие правки по нажатию ссылки отката",
index 84ba0f3..46ba7d1 100644 (file)
        "timezoneregion-indian": "Indiska oceanen",
        "timezoneregion-pacific": "Stilla havet",
        "allowemail": "Låt andra användare skicka e-post till mig",
+       "email-allow-new-users-label": "Tillåt e-post från nyregistrerade användare",
        "email-blacklist-label": "Förhindra följande användare att skicka e-post till mig:",
        "prefs-searchoptions": "Sök",
        "prefs-namespaces": "Namnrymder",
index 403c30b..94708e5 100644 (file)
        "page_last": "son",
        "histlegend": "Fark seçimi: Karşılaştırmayı istediğiniz 2 sürümün önündeki daireleri işaretleyip, \"{{int:Compareselectedversions}}\" düğmesine basın.<br />\nTanımlar: '''({{int:cur}})''' = son revizyon ile arasındaki fark, '''({{int:last}})''' = bir önceki revizyon ile arasındaki fark, '''{{int:minoreditletter}}''' = küçük değişiklik.",
        "history-fieldset-title": "Geçmişe gözat",
-       "history-show-deleted": "Sadece silinenler",
+       "history-show-deleted": "Sadece silinen sürümler",
        "histfirst": "en eski",
        "histlast": "en yeni",
        "historysize": "({{PLURAL:$1|1 bayt|$1 bayt}})",
        "search-file-match": "(dosya içeriğiyle eşleşiyor)",
        "search-suggest": "Bunu mu demek istediniz: $1",
        "search-rewritten": "$1 için sonuçlar gösteriliyor. Bunun yerine $2 için arama yapılsın mı?",
-       "search-interwiki-caption": "Kardeş projeler",
+       "search-interwiki-caption": "Kardeş projelerden sonuçlar",
        "search-interwiki-default": "$1 sonuçları:",
        "search-interwiki-more": "(daha çok)",
        "search-interwiki-more-results": "daha fazla sonuç",
        "prefs-editwatchlist-clear": "İzleme listenizi temizleyin",
        "prefs-watchlist-days": "İzleme listesinde görüntülenecek gün sayısı:",
        "prefs-watchlist-days-max": "en fazla $1 {{PLURAL:$1|gün|gün}}",
-       "prefs-watchlist-edits": "Genişletilmiş izleme listesinde gösterilecek değişiklik sayısı:",
+       "prefs-watchlist-edits": "İzleme listesinde gösterilecek en fazla değişiklik sayısı:",
        "prefs-watchlist-edits-max": "En fazla sayı: 1000",
        "prefs-watchlist-token": "İzleme listesi anahtarı:",
        "prefs-misc": "Diğer ayarlar",
        "recentchanges-submit": "Göster",
        "rcfilters-group-results-by-page": "Sayfalandırılmış grup sonuçları",
        "rcfilters-activefilters": "Etkin süzgeçler",
+       "rcfilters-advancedfilters": "Gelişmiş süzgeçler",
+       "rcfilters-quickfilters": "Kaydedilmiş süzgeçler",
+       "rcfilters-quickfilters-placeholder-title": "Henüz hiçbir süzgeç kaydedilmedi",
+       "rcfilters-quickfilters-placeholder-description": "Süzgeç ayarlarınızı kaydetmek ve sonrasında bunları kullanmak için, aşağıda Aktif Süzgeçler alanındaki yer imi simgesine tıklayın.",
+       "rcfilters-savedqueries-defaultlabel": "Kaydedilmiş süzgeçler",
+       "rcfilters-savedqueries-rename": "Yeniden adlandır",
+       "rcfilters-savedqueries-setdefault": "Varsayılan olarak belirle",
+       "rcfilters-savedqueries-unsetdefault": "Varsayılan olmaktan çıkar",
+       "rcfilters-savedqueries-remove": "Kaldır",
+       "rcfilters-savedqueries-new-name-label": "Ad",
+       "rcfilters-savedqueries-new-name-placeholder": "Süzgecin amacını tanımlayın",
+       "rcfilters-savedqueries-apply-label": "Süzgeç oluştur",
+       "rcfilters-savedqueries-add-new-title": "Mevcut süzgeç ayarlarını kaydet",
        "rcfilters-restore-default-filters": "Varsayılan süzgeçleri geri getir",
        "rcfilters-clear-all-filters": "Tüm süzgeçleri temizle",
-       "rcfilters-search-placeholder": "Son değişiklikleri filtrele (gözatın veya yazmaya başlayın)",
+       "rcfilters-show-new-changes": "Yeni değişiklikleri görüntüle",
+       "rcfilters-search-placeholder": "Son değişiklikleri filtrele (menüyü kullanın veya süzgeç adını arayın)",
        "rcfilters-invalid-filter": "Geçersiz süzgeç",
        "rcfilters-empty-filter": "Etkin süzgeç bulunmuyor. Tüm katkıları gösteriliyor.",
        "rcfilters-filterlist-title": "Süzgeçler",
-       "rcfilters-filterlist-whatsthis": "Bu nedir?",
-       "rcfilters-filterlist-feedbacklink": "Yeni (beta) süzgeçler konusunda geribildirim verin",
+       "rcfilters-filterlist-whatsthis": "Bunlar nasıl çalışır?",
+       "rcfilters-filterlist-feedbacklink": "Bu (yeni) süzgeç araçları konusunda ne düşündüğünüzü bize aktarın",
        "rcfilters-highlightbutton-title": "Sonuçları vurgula",
        "rcfilters-highlightmenu-title": "Bir renk seçin",
        "rcfilters-highlightmenu-help": "Bu özelliği vurgulamak için bir renk seçin",
        "rcfilters-filterlist-noresults": "Süzgeç bulunamadı",
+       "rcfilters-noresults-conflict": "Hiçbir sonuç bulunamadı çünkü arama kriterleri çelişkili",
        "rcfilters-filtergroup-authorship": "Düzenleme sahipliği",
        "rcfilters-filter-editsbyself-label": "Senin değişiklikleriniz",
        "rcfilters-filter-editsbyself-description": "Kendi katkılarınız.",
        "rcfilters-filter-major-description": "Küçük olarak etiketlenmemiş düzenlemeler.",
        "rcfilters-filtergroup-changetype": "Değişiklik türü",
        "rcfilters-filter-pageedits-label": "Sayfa düzenlemeleri",
-       "rcfilters-filter-pageedits-description": "Viki içeriği, tartışmalar, kategori açıklamalarındaki düzenlemeler....",
+       "rcfilters-filter-pageedits-description": "Viki içeriği, tartışmalar, kategori açıklamalarındaki düzenlemeler...",
        "rcfilters-filter-newpages-label": "Sayfa oluşturmalar",
        "rcfilters-filter-newpages-description": "Yeni sayfa oluşturan düzenlemeler.",
        "rcfilters-filter-categorization-label": "Kategori değişiklikleri",
        "rcfilters-filter-categorization-description": "Kategorilere eklenen veya kaldırılan sayfaların kayıtları.",
        "rcfilters-filter-logactions-label": "Günlüğü tutulan işlemler",
-       "rcfilters-filter-logactions-description": "Hizmetli işlemleri, hesap oluşturmalar, sayfa silmeler, yüklemeler....",
+       "rcfilters-filter-logactions-description": "Hizmetli işlemleri, hesap oluşturmalar, sayfa silmeler, yüklemeler...",
+       "rcfilters-liveupdates-button": "Canlı güncelleme",
+       "rcfilters-liveupdates-button-title-on": "Canlı güncellemeyi kapat",
+       "rcfilters-liveupdates-button-title-off": "Yeni değişiklikleri yapıldıkları anda görüntüleyin",
+       "rcfilters-watchlist-markseen-button": "Tüm değişiklileri görüldü olarak işaretle",
        "rcnotefrom": "<strong>$3, $4</strong> tarihinden itibaren yapılan {{PLURAL:$5|değişiklik|değişiklik}} aşağıdadır (<strong>$1</strong> tarhine kadar olanlar gösterilmektedir).",
        "rclistfrom": "$3 $2 tarihinden itibaren yeni değişiklikleri göster",
        "rcshowhideminor": "Küçük değişiklikleri $1",
        "recentchangeslinked-page": "Sayfa adı:",
        "recentchangeslinked-to": "Belirtilen sayfadan verilenler yerine, sayfaya verilen bağlantıları göster.",
        "recentchanges-page-added-to-category": "[[:$1]] kategoriye eklendi",
+       "recentchanges-page-removed-from-category": "[[:$1]] kategoriden çıkarıldı",
        "upload": "Dosya yükle",
        "uploadbtn": "Dosya yükle",
        "reuploaddesc": "Yükleme formuna geri dön.",
        "listfiles_size": "Boyut (bayt)",
        "listfiles_description": "Tanım",
        "listfiles_count": "Sürümler",
-       "listfiles-show-all": "Görüntülerin eski sürümlerini içer",
+       "listfiles-show-all": "Dosyaların eski sürümlerini dahil et",
        "listfiles-latestversion": "Geçerli sürüm",
        "listfiles-latestversion-yes": "Evet",
        "listfiles-latestversion-no": "Hayır",
        "enotif_lastdiff": "Bu değişikliği görmek için, $1 sayfasına bakınız.",
        "enotif_anon_editor": "anonim kullanıcı $1",
        "enotif_body": "Sayın $WATCHINGUSERNAME,\n\n$PAGEINTRO $NEWPAGE\n\nEditörün girdiği özet: $PAGESUMMARY $PAGEMINOREDIT\n\nEditörün iletişim bilgileri:\ne-posta: $PAGEEDITOR_EMAIL\nviki: $PAGEEDITOR_WIKI\n\nBahsi geçen sayfayı oturum açarak ziyaret edinceye kadar sayfayla ilgili başka bildirim gönderilmeyecektir. Ayrıca izleme listenizdeki tüm sayfaların bildirim durumlarını sıfırlayabilirsiniz.\n\n{{SITENAME}} bildirim sistemi\n\n--\nE-posta bildirim ayarlarınızı değiştirmek için aşağıdaki sayfayı ziyaret ediniz:\n{{canonicalurl:{{#special:Preferences}}}}\n\nİzleme listesi ayarlarınızı değiştirmek için aşağıdaki sayfayı ziyaret ediniz:\n{{canonicalurl:{{#special:EditWatchlist}}}}\n\nSayfayı izleme listenizden silmek için aşağıdaki sayfayı ziyaret ediniz:\n$UNWATCHURL\n\nGeri bildirim ve daha fazla yardım için:\n$HELPPAGE",
+       "enotif_minoredit": "Bu küçük bir değişiklik",
        "created": "oluşturuldu",
        "changed": "değiştirildi",
        "deletepage": "Sayfayı sil",
index 9f48204..19b51eb 100644 (file)
@@ -760,11 +760,6 @@ table.floatleft {
        vertical-align: baseline;
        /* Reset line-height; headings tend to have it set to larger values */
        line-height: 1em;
-       /* As .mw-editsection is a <span> (inline element), it is treated as part */
-       /* of the heading content when selecting text by multiple clicks and thus */
-       /* selected together with heading content, despite the user-select: none; */
-       /* rule set above. This enforces non-selection without changing the look. */
-       display: inline-block;
 }
 
 /* Correct directionality when page dir is different from site/user dir */
index a5c4688..4d39f7b 100644 (file)
@@ -7,6 +7,8 @@ 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
@@ -331,6 +333,8 @@ 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 91dbf2c..9ab76c8 100644 (file)
@@ -1,4 +1,8 @@
 <?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.
@@ -72,6 +76,7 @@ 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
@@ -102,6 +107,14 @@ 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';
                }
@@ -110,6 +123,10 @@ 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 );
@@ -202,14 +219,23 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                $revId = $rev->insertOn( wfGetDB( DB_MASTER ) );
 
                $this->assertInternalType( 'integer', $revId );
-               $this->assertInternalType( 'integer', $rev->getTextId() );
                $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->assertSelect(
                        'text',
                        [ 'old_id', 'old_text' ],
-                       "old_id = {$rev->getTextId()}",
-                       [ [ strval( $rev->getTextId() ), 'Revision Text' ] ]
+                       "old_id = $textId",
+                       [ [ strval( $textId ), 'Revision Text' ] ]
                );
                $this->assertSelect(
                        'revision',
@@ -228,7 +254,7 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                        [ [
                                strval( $rev->getId() ),
                                strval( $this->testPage->getId() ),
-                               strval( $rev->getTextId() ),
+                               strval( $textId ),
                                '0',
                                '0',
                                '0',
@@ -246,11 +272,12 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                // If an ExternalStore is set don't use it.
                $this->setMwGlobals( 'wgDefaultExternalStore', false );
                $this->setExpectedException(
-                       MWException::class,
-                       "Cannot insert revision: page ID must be nonzero"
+                       IncompleteRevisionException::class,
+                       "rev_page field must not be 0!"
                );
 
-               $rev = new Revision( [] );
+               $title = Title::newFromText( 'Nonexistant-' . __METHOD__ );
+               $rev = new Revision( [], 0, $title );
 
                $rev->insertOn( wfGetDB( DB_MASTER ) );
        }
@@ -321,12 +348,42 @@ 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;
+                       },
+               ];
        }
 
        /**
@@ -334,6 +391,17 @@ 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',
@@ -354,6 +422,8 @@ 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 );
@@ -382,7 +452,7 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                $row = $res->fetchObject();
                $res->free();
 
-               $rev = Revision::newFromArchiveRow( $row, [ 'comment' => 'SOMEOVERRIDE' ] );
+               $rev = Revision::newFromArchiveRow( $row, [ 'comment_text' => 'SOMEOVERRIDE' ] );
 
                $this->assertNotEquals( $orig->getComment(), $rev->getComment() );
                $this->assertEquals( 'SOMEOVERRIDE', $rev->getComment() );
@@ -426,7 +496,8 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
         * @covers Revision::newFromPageId
         */
        public function testNewFromPageIdWithNotLatestId() {
-               $this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
+               $content = new WikitextContent( __METHOD__ );
+               $this->testPage->doEditContent( $content, __METHOD__ );
                $rev = Revision::newFromPageId(
                        $this->testPage->getId(),
                        $this->testPage->getRevision()->getPrevious()->getId()
@@ -447,6 +518,7 @@ 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
@@ -455,8 +527,7 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                        $rows[$row->rev_id] = $row;
                }
 
-               $this->assertEquals( 1, count( $rows ), 'expected exactly one revision' );
-               $this->assertArrayHasKey( $id, $rows, 'missing revision with id ' . $id );
+               $this->assertEmpty( $rows, 'expected empty set' );
        }
 
        /**
@@ -541,6 +612,10 @@ 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() );
        }
 
@@ -606,7 +681,7 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                        'user' => $userA->getId(),
                        'text' => 'zero',
                        'content_model' => CONTENT_MODEL_WIKITEXT,
-                       'summary' => 'edit zero'
+                       'comment' => 'edit zero'
                ] );
                $revisions[0]->insertOn( $dbw );
 
@@ -618,7 +693,7 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                        'user' => $userA->getId(),
                        'text' => 'one',
                        'content_model' => CONTENT_MODEL_WIKITEXT,
-                       'summary' => 'edit one'
+                       'comment' => 'edit one'
                ] );
                $revisions[1]->insertOn( $dbw );
 
@@ -629,7 +704,7 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                        'user' => $userB->getId(),
                        'text' => 'two',
                        'content_model' => CONTENT_MODEL_WIKITEXT,
-                       'summary' => 'edit two'
+                       'comment' => 'edit two'
                ] );
                $revisions[2]->insertOn( $dbw );
 
@@ -640,7 +715,7 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                        'user' => $userA->getId(),
                        'text' => 'three',
                        'content_model' => CONTENT_MODEL_WIKITEXT,
-                       'summary' => 'edit three'
+                       'comment' => 'edit three'
                ] );
                $revisions[3]->insertOn( $dbw );
 
@@ -651,13 +726,24 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                        'user' => $userA->getId(),
                        'text' => 'zero',
                        'content_model' => CONTENT_MODEL_WIKITEXT,
-                       'summary' => 'edit four'
+                       'comment' => '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 );
@@ -805,12 +891,16 @@ 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() {
@@ -904,6 +994,7 @@ 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() )
@@ -1026,7 +1117,7 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                $rev[1] = $this->testPage->getLatest();
 
                $this->assertSame(
-                       [ $rev[1] => strval( $textLength ) ],
+                       [ $rev[1] => $textLength ],
                        Revision::getParentLengths(
                                wfGetDB( DB_MASTER ),
                                [ $rev[1] ]
@@ -1049,7 +1140,7 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                $rev[2] = $this->testPage->getLatest();
 
                $this->assertSame(
-                       [ $rev[1] => strval( $textOneLength ), $rev[2] => strval( $textTwoLength ) ],
+                       [ $rev[1] => $textOneLength, $rev[2] => $textTwoLength ],
                        Revision::getParentLengths(
                                wfGetDB( DB_MASTER ),
                                [ $rev[1], $rev[2] ]
@@ -1080,14 +1171,6 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                );
        }
 
-       /**
-        * @covers Revision::getTitle
-        */
-       public function testGetTitle_forBadRevision() {
-               $rev = new Revision( [] );
-               $this->assertNull( $rev->getTitle() );
-       }
-
        /**
         * @covers Revision::isMinor
         */
@@ -1263,14 +1346,21 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                $rev = $this->testPage->getRevision();
 
                // Clear any previous cache for the revision during creation
-               $key = $cache->makeGlobalKey( 'revision', $db->getDomainID(), $rev->getPage(), $rev->getId() );
+               $key = $cache->makeGlobalKey( 'revision-row-1.29',
+                       $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 );
-               $this->assertRevEquals( $rev, $cache->get( $key ) );
+
+               $cachedRow = $cache->get( $key );
+               $this->assertNotFalse( $cachedRow );
+               $this->assertEquals( $rev->getId(), $cachedRow->rev_id );
        }
 
        public function provideUserCanBitfield() {
@@ -1377,7 +1467,7 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                        ]
                );
                $user = $this->getTestUser( $userGroups )->getUser();
-               $revision = new Revision( [ 'deleted' => $bitField ] );
+               $revision = new Revision( [ 'deleted' => $bitField ], 0, $this->testPage->getTitle() );
 
                $this->assertSame(
                        $expected,
index 361984b..b7f1a47 100644 (file)
@@ -1,6 +1,9 @@
 <?php
 
-use Wikimedia\TestingAccessWrapper;
+use MediaWiki\Storage\RevisionStore;
+use MediaWiki\Storage\SqlBlobStore;
+use Wikimedia\Rdbms\IDatabase;
+use Wikimedia\Rdbms\LoadBalancer;
 
 /**
  * Test cases in RevisionTest should not interact with the Database.
@@ -20,6 +23,35 @@ class RevisionTest extends MediaWikiTestCase {
                                'content' => new JavaScriptContent( 'hellow world.' )
                        ],
                ];
+               // FIXME: test with and without user ID, and with a user object.
+               // We can't prepare that here though, since we don't yet have a dummy DB
+       }
+
+       /**
+        * @param string $model
+        * @return Title
+        */
+       public function getMockTitle( $model = CONTENT_MODEL_WIKITEXT ) {
+               $mock = $this->getMockBuilder( Title::class )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               $mock->expects( $this->any() )
+                       ->method( 'getNamespace' )
+                       ->will( $this->returnValue( $this->getDefaultWikitextNS() ) );
+               $mock->expects( $this->any() )
+                       ->method( 'getPrefixedText' )
+                       ->will( $this->returnValue( 'RevisionTest' ) );
+               $mock->expects( $this->any() )
+                       ->method( 'getDBKey' )
+                       ->will( $this->returnValue( 'RevisionTest' ) );
+               $mock->expects( $this->any() )
+                       ->method( 'getArticleID' )
+                       ->will( $this->returnValue( 23 ) );
+               $mock->expects( $this->any() )
+                       ->method( 'getModel' )
+                       ->will( $this->returnValue( $model ) );
+
+               return $mock;
        }
 
        /**
@@ -27,13 +59,22 @@ class RevisionTest extends MediaWikiTestCase {
         * @covers Revision::__construct
         * @covers Revision::constructFromRowArray
         */
-       public function testConstructFromArray( array $rowArray ) {
-               $rev = new Revision( $rowArray );
+       public function testConstructFromArray( $rowArray ) {
+               $rev = new Revision( $rowArray, 0, $this->getMockTitle() );
                $this->assertNotNull( $rev->getContent(), 'no content object available' );
                $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContent()->getModel() );
                $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContentModel() );
        }
 
+       /**
+        * @covers Revision::__construct
+        * @covers Revision::constructFromRowArray
+        */
+       public function testConstructFromEmptyArray() {
+               $rev = new Revision( [], 0, $this->getMockTitle() );
+               $this->assertNull( $rev->getContent(), 'no content object should be available' );
+       }
+
        public function provideConstructFromArray_userSetAsExpected() {
                yield 'no user defaults to wgUser' => [
                        [
@@ -52,24 +93,14 @@ class RevisionTest extends MediaWikiTestCase {
                        99,
                        'SomeTextUserName',
                ];
-               // Note: the below XXX test cases are odd and probably result in unexpected behaviour if used
-               // in production code.
-               yield 'XXX: user text only' => [
+               yield 'user text only' => [
                        [
                                'content' => new JavaScriptContent( 'hello world.' ),
                                'user_text' => '111.111.111.111',
                        ],
-                       null,
+                       0,
                        '111.111.111.111',
                ];
-               yield 'XXX: user id only' => [
-                       [
-                               'content' => new JavaScriptContent( 'hello world.' ),
-                               'user' => 9989,
-                       ],
-                       9989,
-                       null,
-               ];
        }
 
        /**
@@ -95,7 +126,7 @@ class RevisionTest extends MediaWikiTestCase {
                        $expectedUserName = $testUser->getName();
                }
 
-               $rev = new Revision( $rowArray );
+               $rev = new Revision( $rowArray, 0, $this->getMockTitle() );
                $this->assertEquals( $expectedUserId, $rev->getUser() );
                $this->assertEquals( $expectedUserName, $rev->getUserText() );
        }
@@ -105,28 +136,37 @@ 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 MWException( 'Revision constructor passed invalid row format.' )
+                       new InvalidArgumentException(
+                               '$row must be a row object, an associative array, or a RevisionRecord'
+                       )
                ];
        }
 
        /**
         * @dataProvider provideConstructFromArrayThrowsExceptions
         * @covers Revision::__construct
-        * @covers Revision::constructFromRowArray
+        * @covers RevisionStore::newMutableRevisionFromArray
         */
        public function testConstructFromArrayThrowsExceptions( $rowArray, Exception $expectedException ) {
                $this->setExpectedException(
@@ -134,14 +174,25 @@ class RevisionTest extends MediaWikiTestCase {
                        $expectedException->getMessage(),
                        $expectedException->getCode()
                );
-               new Revision( $rowArray );
+               new Revision( $rowArray, 0, $this->getMockTitle() );
+       }
+
+       /**
+        * @covers Revision::__construct
+        * @covers RevisionStore::newMutableRevisionFromArray
+        */
+       public function testConstructFromNothing() {
+               $this->setExpectedException(
+                       InvalidArgumentException::class
+               );
+               new Revision( [] );
        }
 
        public function provideConstructFromRow() {
                yield 'Full construction' => [
                        [
-                               'rev_id' => '2',
-                               'rev_page' => '1',
+                               'rev_id' => '42',
+                               'rev_page' => '23',
                                'rev_text_id' => '2',
                                'rev_timestamp' => '20171017114835',
                                'rev_user_text' => '127.0.0.1',
@@ -158,8 +209,8 @@ class RevisionTest extends MediaWikiTestCase {
                                'rev_content_model' => 'GOATMODEL',
                        ],
                        function ( RevisionTest $testCase, Revision $rev ) {
-                               $testCase->assertSame( 2, $rev->getId() );
-                               $testCase->assertSame( 1, $rev->getPage() );
+                               $testCase->assertSame( 42, $rev->getId() );
+                               $testCase->assertSame( 23, $rev->getPage() );
                                $testCase->assertSame( 2, $rev->getTextId() );
                                $testCase->assertSame( '20171017114835', $rev->getTimestamp() );
                                $testCase->assertSame( '127.0.0.1', $rev->getUserText() );
@@ -174,10 +225,10 @@ class RevisionTest extends MediaWikiTestCase {
                                $testCase->assertSame( 'GOATMODEL', $rev->getContentModel() );
                        }
                ];
-               yield 'null fields' => [
+               yield 'default field values' => [
                        [
-                               'rev_id' => '2',
-                               'rev_page' => '1',
+                               'rev_id' => '42',
+                               'rev_page' => '23',
                                'rev_text_id' => '2',
                                'rev_timestamp' => '20171017114835',
                                'rev_user_text' => '127.0.0.1',
@@ -189,11 +240,24 @@ class RevisionTest extends MediaWikiTestCase {
                                'rev_comment_cid' => null,
                        ],
                        function ( RevisionTest $testCase, Revision $rev ) {
-                               $testCase->assertNull( $rev->getSize() );
-                               $testCase->assertNull( $rev->getParentId() );
-                               $testCase->assertNull( $rev->getSha1() );
-                               $testCase->assertSame( 'text/x-wiki', $rev->getContentFormat() );
-                               $testCase->assertSame( 'wikitext', $rev->getContentModel() );
+                               // 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' );
                        }
                ];
        }
@@ -201,11 +265,34 @@ class RevisionTest extends MediaWikiTestCase {
        /**
         * @dataProvider provideConstructFromRow
         * @covers Revision::__construct
-        * @covers Revision::constructFromDbRowObject
+        * @covers RevisionStore::newRevisionFromRow
         */
        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 );
+               $rev = new Revision( $row, 0, $this->getMockTitle() );
                $assertions( $this, $rev );
        }
 
@@ -235,7 +322,7 @@ class RevisionTest extends MediaWikiTestCase {
         * @covers Revision::getId
         */
        public function testGetId( $rowArray, $expectedId ) {
-               $rev = new Revision( $rowArray );
+               $rev = new Revision( $rowArray, 0, $this->getMockTitle() );
                $this->assertEquals( $expectedId, $rev->getId() );
        }
 
@@ -249,7 +336,7 @@ class RevisionTest extends MediaWikiTestCase {
         * @covers Revision::setId
         */
        public function testSetId( $input, $expected ) {
-               $rev = new Revision( [] );
+               $rev = new Revision( [], 0, $this->getMockTitle() );
                $rev->setId( $input );
                $this->assertSame( $expected, $rev->getId() );
        }
@@ -264,7 +351,7 @@ class RevisionTest extends MediaWikiTestCase {
         * @covers Revision::setUserIdAndName
         */
        public function testSetUserIdAndName( $inputId, $expectedId, $name ) {
-               $rev = new Revision( [] );
+               $rev = new Revision( [], 0, $this->getMockTitle() );
                $rev->setUserIdAndName( $inputId, $name );
                $this->assertSame( $expectedId, $rev->getUser( Revision::RAW ) );
                $this->assertEquals( $name, $rev->getUserText( Revision::RAW ) );
@@ -281,7 +368,7 @@ class RevisionTest extends MediaWikiTestCase {
         * @covers Revision::getTextId()
         */
        public function testGetTextId( $rowArray, $expected ) {
-               $rev = new Revision( $rowArray );
+               $rev = new Revision( $rowArray, 0, $this->getMockTitle() );
                $this->assertSame( $expected, $rev->getTextId() );
        }
 
@@ -296,7 +383,7 @@ class RevisionTest extends MediaWikiTestCase {
         * @covers Revision::getParentId()
         */
        public function testGetParentId( $rowArray, $expected ) {
-               $rev = new Revision( $rowArray );
+               $rev = new Revision( $rowArray, 0, $this->getMockTitle() );
                $this->assertSame( $expected, $rev->getParentId() );
        }
 
@@ -329,9 +416,44 @@ 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',
@@ -340,6 +462,7 @@ class RevisionTest extends MediaWikiTestCase {
                ];
                yield 'Utf8Legacy' => [
                        "Wiki est l'\xc3\xa9cole superieur !",
+                       'fr',
                        'iso-8859-1',
                        [
                                'old_flags' => '',
@@ -352,8 +475,11 @@ class RevisionTest extends MediaWikiTestCase {
         * @covers Revision::getRevisionText
         * @dataProvider provideGetRevisionTextWithLegacyEncoding
         */
-       public function testGetRevisionWithLegacyEncoding( $expected, $encoding, $rowData ) {
-               $this->setMwGlobals( 'wgLegacyEncoding', $encoding );
+       public function testGetRevisionWithLegacyEncoding( $expected, $lang, $encoding, $rowData ) {
+               $blobStore = $this->getBlobStore();
+               $blobStore->setLegacyEncoding( $encoding, Language::factory( $lang ) );
+               $this->setService( 'BlobStore', $blobStore );
+
                $this->testGetRevisionText( $expected, $rowData );
        }
 
@@ -365,6 +491,7 @@ class RevisionTest extends MediaWikiTestCase {
                 */
                yield 'Utf8NativeGzip' => [
                        "Wiki est l'\xc3\xa9cole superieur !",
+                       'fr',
                        'iso-8859-1',
                        [
                                'old_flags' => 'gzip,utf-8',
@@ -373,6 +500,7 @@ class RevisionTest extends MediaWikiTestCase {
                ];
                yield 'Utf8LegacyGzip' => [
                        "Wiki est l'\xc3\xa9cole superieur !",
+                       'fr',
                        'iso-8859-1',
                        [
                                'old_flags' => 'gzip',
@@ -385,9 +513,13 @@ class RevisionTest extends MediaWikiTestCase {
         * @covers Revision::getRevisionText
         * @dataProvider provideGetRevisionTextWithGzipAndLegacyEncoding
         */
-       public function testGetRevisionWithGzipAndLegacyEncoding( $expected, $encoding, $rowData ) {
+       public function testGetRevisionWithGzipAndLegacyEncoding( $expected, $lang, $encoding, $rowData ) {
                $this->checkPHPExtension( 'zlib' );
-               $this->setMwGlobals( 'wgLegacyEncoding', $encoding );
+
+               $blobStore = $this->getBlobStore();
+               $blobStore->setLegacyEncoding( $encoding, Language::factory( $lang ) );
+               $this->setService( 'BlobStore', $blobStore );
+
                $this->testGetRevisionText( $expected, $rowData );
        }
 
@@ -413,7 +545,10 @@ class RevisionTest extends MediaWikiTestCase {
         */
        public function testCompressRevisionTextUtf8Gzip() {
                $this->checkPHPExtension( 'zlib' );
-               $this->setMwGlobals( 'wgCompressRevisions', true );
+
+               $blobStore = $this->getBlobStore();
+               $blobStore->setCompressBlobs( true );
+               $this->setService( 'BlobStore', $blobStore );
 
                $row = new stdClass;
                $row->old_text = "Wiki est l'\xc3\xa9cole superieur !";
@@ -428,20 +563,41 @@ class RevisionTest extends MediaWikiTestCase {
                        Revision::getRevisionText( $row ), "getRevisionText" );
        }
 
-       public function provideFetchFromConds() {
-               yield [ 0, [] ];
-               yield [ Revision::READ_LOCKING, [ 'FOR UPDATE' ] ];
-       }
-
        /**
-        * @dataProvider provideFetchFromConds
-        * @covers Revision::fetchFromConds
+        * @covers Revision::loadFromTitle
         */
-       public function testFetchFromConds( $flags, array $options ) {
-               $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', MIGRATION_OLD );
-               $conditions = [ 'conditionsArray' ];
+       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',
+               ];
 
                $db = $this->getMock( IDatabase::class );
+               $db->expects( $this->any() )
+                       ->method( 'getDomainId' )
+                       ->will( $this->returnValue( wfWikiID() ) );
                $db->expects( $this->once() )
                        ->method( 'selectRow' )
                        ->with(
@@ -450,17 +606,24 @@ class RevisionTest extends MediaWikiTestCase {
                                $this->isType( 'array' ),
                                $this->equalTo( $conditions ),
                                // Method name
-                               $this->equalTo( 'Revision::fetchFromConds' ),
-                               $this->equalTo( $options ),
+                               $this->stringContains( 'fetchRevisionRowFromConds' ),
+                               // We don't really care about the options here
+                               $this->isType( 'array' ),
                                // We don't really care about the join conds are they come from the joinCond methods
                                $this->isType( 'array' )
                        )
-                       ->willReturn( 'RETURNVALUE' );
+                       ->willReturn( $row );
 
-               $wrapper = TestingAccessWrapper::newFromClass( Revision::class );
-               $result = $wrapper->fetchFromConds( $db, $conditions, $flags );
+               $revision = Revision::loadFromTitle( $db, $title );
 
-               $this->assertEquals( 'RETURNVALUE', $result );
+               $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() );
        }
 
        public function provideDecompressRevisionText() {
@@ -525,8 +688,12 @@ class RevisionTest extends MediaWikiTestCase {
         * @param mixed $expected
         */
        public function testDecompressRevisionText( $legacyEncoding, $text, $flags, $expected ) {
-               $this->setMwGlobals( 'wgLegacyEncoding', $legacyEncoding );
-               $this->setMwGlobals( 'wgLanguageCode', 'en' );
+               $blobStore = $this->getBlobStore();
+               if ( $legacyEncoding ) {
+                       $blobStore->setLegacyEncoding( $legacyEncoding, Language::factory( 'en' ) );
+               }
+
+               $this->setService( 'BlobStore', $blobStore );
                $this->assertSame(
                        $expected,
                        Revision::decompressRevisionText( $text, $flags )
@@ -622,14 +789,20 @@ class RevisionTest extends MediaWikiTestCase {
         * @covers Revision::getRevisionText
         */
        public function testGetRevisionText_external_oldId() {
-               $cache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
+               $cache = $this->getWANObjectCache();
                $this->setService( 'MainWANObjectCache', $cache );
+
                $this->setService(
                        'ExternalStoreFactory',
                        new ExternalStoreFactory( [ 'ForTesting' ] )
                );
 
-               $cacheKey = $cache->makeKey( 'revisiontext', 'textid', '7777' );
+               $lb = $this->getMockBuilder( LoadBalancer::class )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+
+               $blobStore = new SqlBlobStore( $lb, $cache );
+               $this->setService( 'BlobStore', $blobStore );
 
                $this->assertSame(
                        'AAAABBAAA',
@@ -641,6 +814,8 @@ class RevisionTest extends MediaWikiTestCase {
                                ]
                        )
                );
+
+               $cacheKey = $cache->makeKey( 'revisiontext', 'textid', 'tt:7777' );
                $this->assertSame( 'AAAABBAAA', $cache->get( $cacheKey ) );
        }
 
@@ -836,6 +1011,8 @@ class RevisionTest extends MediaWikiTestCase {
                                'fields' => [
                                        'ar_id',
                                        'ar_page_id',
+                                       'ar_namespace',
+                                       'ar_title',
                                        'ar_rev_id',
                                        'ar_text',
                                        'ar_text_id',
@@ -864,6 +1041,8 @@ class RevisionTest extends MediaWikiTestCase {
                                'fields' => [
                                        'ar_id',
                                        'ar_page_id',
+                                       'ar_namespace',
+                                       'ar_title',
                                        'ar_rev_id',
                                        'ar_text',
                                        'ar_text_id',
@@ -897,6 +1076,8 @@ class RevisionTest extends MediaWikiTestCase {
                                'fields' => [
                                        'ar_id',
                                        'ar_page_id',
+                                       'ar_namespace',
+                                       'ar_title',
                                        'ar_rev_id',
                                        'ar_text',
                                        'ar_text_id',
@@ -933,6 +1114,8 @@ class RevisionTest extends MediaWikiTestCase {
                                'fields' => [
                                        'ar_id',
                                        'ar_page_id',
+                                       'ar_namespace',
+                                       'ar_title',
                                        'ar_rev_id',
                                        'ar_text',
                                        'ar_text_id',
@@ -969,6 +1152,8 @@ class RevisionTest extends MediaWikiTestCase {
                                'fields' => [
                                        'ar_id',
                                        'ar_page_id',
+                                       'ar_namespace',
+                                       'ar_title',
                                        'ar_rev_id',
                                        'ar_text',
                                        'ar_text_id',
@@ -1000,6 +1185,11 @@ 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()
@@ -1351,6 +1541,11 @@ 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 )
index db85727..aa59a5b 100644 (file)
@@ -300,28 +300,28 @@ class RevisionStoreRecordTest extends MediaWikiTestCase {
 
        private function provideAudienceCheckData( $field ) {
                yield 'field accessible for oversighter (ALL)' => [
-                       Revisionrecord::SUPPRESSED_ALL,
+                       RevisionRecord::SUPPRESSED_ALL,
                        [ 'oversight' ],
                        true,
                        false
                ];
 
                yield 'field accessible for oversighter' => [
-                       Revisionrecord::DELETED_RESTRICTED | $field,
+                       RevisionRecord::DELETED_RESTRICTED | $field,
                        [ 'oversight' ],
                        true,
                        false
                ];
 
                yield 'field not accessible for sysops (ALL)' => [
-                       Revisionrecord::SUPPRESSED_ALL,
+                       RevisionRecord::SUPPRESSED_ALL,
                        [ 'sysop' ],
                        false,
                        false
                ];
 
                yield 'field not accessible for sysops' => [
-                       Revisionrecord::DELETED_RESTRICTED | $field,
+                       RevisionRecord::DELETED_RESTRICTED | $field,
                        [ 'sysop' ],
                        false,
                        false
@@ -342,9 +342,9 @@ class RevisionStoreRecordTest extends MediaWikiTestCase {
                ];
 
                yield 'unrelated field suppressed' => [
-                       $field === Revisionrecord::DELETED_COMMENT
-                               ? Revisionrecord::DELETED_USER
-                               : Revisionrecord::DELETED_COMMENT,
+                       $field === RevisionRecord::DELETED_COMMENT
+                               ? RevisionRecord::DELETED_USER
+                               : RevisionRecord::DELETED_COMMENT,
                        [ 'user' ],
                        true,
                        true
index b6af2b4..6d2b09b 100644 (file)
@@ -112,25 +112,25 @@ class SqlBlobStoreTest extends MediaWikiTestCase {
                ];
                yield '(ISO-8859-1 encoding), string in string out' => [
                        'ISO-8859-1',
-                       iconv( 'utf8', 'ISO-8859-1', "1®Àþ1" ),
+                       iconv( 'utf-8', 'ISO-8859-1', "1®Àþ1" ),
                        [],
                        '1®Àþ1',
                ];
                yield '(ISO-8859-1 encoding), serialized object in with gzip flags returns string' => [
                        'ISO-8859-1',
-                       gzdeflate( iconv( 'utf8', 'ISO-8859-1', "4®Àþ4" ) ),
+                       gzdeflate( iconv( 'utf-8', 'ISO-8859-1', "4®Àþ4" ) ),
                        [ 'gzip' ],
                        '4®Àþ4',
                ];
                yield '(ISO-8859-1 encoding), serialized object in with object flags returns string' => [
                        'ISO-8859-1',
-                       serialize( new TitleValue( 0, iconv( 'utf8', 'ISO-8859-1', "3®Àþ3" ) ) ),
+                       serialize( new TitleValue( 0, iconv( 'utf-8', 'ISO-8859-1', "3®Àþ3" ) ) ),
                        [ 'object' ],
                        '3®Àþ3',
                ];
                yield '(ISO-8859-1 encoding), serialized object in with object & gzip flags returns string' => [
                        'ISO-8859-1',
-                       gzdeflate( serialize( new TitleValue( 0, iconv( 'utf8', 'ISO-8859-1', "2®Àþ2" ) ) ) ),
+                       gzdeflate( serialize( new TitleValue( 0, iconv( 'utf-8', 'ISO-8859-1', "2®Àþ2" ) ) ) ),
                        [ 'gzip', 'object' ],
                        '2®Àþ2',
                ];
index e2aacae..395d12c 100644 (file)
@@ -101,7 +101,7 @@ class JobTest extends MediaWikiTestCase {
         * @covers Job::factory
         */
        public function testJobFactory( $handler ) {
-               $this->mergeMWGlobalArrayValue( 'wgJobClasses', [ 'testdummy' => $handler ] );
+               $this->mergeMwGlobalArrayValue( 'wgJobClasses', [ 'testdummy' => $handler ] );
 
                $job = Job::factory( 'testdummy', Title::newMainPage(), [] );
                $this->assertInstanceOf( NullJob::class, $job );
index b8480dd..2b803ae 100644 (file)
@@ -1678,7 +1678,7 @@ more stuff
        public function testInsertOn_idSpecified() {
                $title = Title::newFromText( __METHOD__ );
                $page = new WikiPage( $title );
-               $id = 3478952189;
+               $id = 1478952189;
 
                $result = $page->insertOn( $this->db, $id );
 
index d31779d..62ddace 100644 (file)
@@ -55,7 +55,7 @@ class ResourcesTest extends MediaWikiTestCase {
        public function testIllegalDependencies() {
                $data = self::getAllModules();
 
-               $illegalDeps = ResourceLoaderStartupModule::getStartupModules();
+               $illegalDeps = ResourceLoaderStartUpModule::getStartupModules();
                foreach ( $data['modules'] as $moduleName => $module ) {
                        if ( $module->isRaw() ) {
                                $illegalDeps[] = $moduleName;