* 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.
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
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.
}
}
-Autoloader::$psr4Namespaces = AutoLoader::getAutoloadNamespaces();
+AutoLoader::$psr4Namespaces = AutoLoader::getAutoloadNamespaces();
spl_autoload_register( [ 'AutoLoader', 'autoload' ] );
*
* @file
*/
+use MediaWiki\MediaWikiServices;
use Wikimedia\Timestamp\TimestampException;
use Wikimedia\Rdbms\IDatabase;
}
$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 );
* @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;
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.
* @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 );
}
/**
* @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 );
}
/**
* @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
* @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 );
}
/**
* 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 );
}
/**
* 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 );
}
/**
* 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 );
}
/**
*
* @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() {
* 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() {
/**
* 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() {
/**
* 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() {
/**
* 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() {
/**
* 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() {
/**
* 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() {
* 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
* - 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();
}
/**
* @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;
}
/**
* @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' );
+ }
}
/**
*
* 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()
+ );
+ }
}
/**
* @return int|null
*/
public function getPage() {
- return $this->mPage;
+ return $this->mRecord->getPageId();
}
/**
* @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;
}
/**
* @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() : '';
}
/**
* @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;
}
/**
* @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 );
}
/**
* @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 );
}
/**
* @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 );
}
/**
* @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
* @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();
}
}
* 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;
}
/**
* @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();
}
/**
* @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 );
}
/**
* @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 );
}
/**
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 );
}
/**
* @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 );
}
/**
* @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 );
}
/**
* @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();
}
/**
* @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 );
}
/**
* @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 );
}
/**
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 );
}
/**
* @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 );
}
/**
* @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 );
}
/**
* @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 );
}
/**
* @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 );
}
/**
*
* 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;
}
}
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();
* @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() ),
*/
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;
}
$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,
'',
$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,
'',
"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",
"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.",
if ( $titleObj->getLatestRevID() ) {
$revision = Revision::newKnownCurrent(
$dbr,
- $titleObj->getArticleID(),
- $titleObj->getLatestRevID()
+ $titleObj
);
} else {
$revision = false;
* @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 ) {
return false;
}
- private function hasConflictWithFilter( ChangeslistFilter $filter ) {
+ private function hasConflictWithFilter( ChangesListFilter $filter ) {
return in_array( $filter, $this->getConflictingFilters() );
}
$tooltipAttribs = [
'class' => "mw-htmlform-tooltip $tooltipClass",
'title' => $this->mParams['tooltips'][$rowLabel],
+ 'aria-label' => $this->mParams['tooltips'][$rowLabel]
];
$rowLabel .= ' ' . Html::element( 'span', $tooltipAttribs, '' );
}
"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:",
"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",
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;
$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
$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,
* @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;
}
# 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 ) {
}
if ( isset( $info['autoloaderNS'] ) ) {
- Autoloader::$psr4Namespaces += $info['autoloaderNS'];
+ AutoLoader::$psr4Namespaces += $info['autoloaderNS'];
}
foreach ( $info['defines'] as $name => $val ) {
* @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!' );
/**
* @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 );
}
/**
// 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 ];
* @param string[] $paths
*/
public function setExcludePaths( array $paths ) {
- $this->excludePaths = $paths;
+ foreach ( $paths as $path ) {
+ $this->excludePaths[] = self::normalizePathSeparator( $path );
+ }
}
/**
"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": "إعادة ضبط خيار التاريخ",
"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": "Імпартаваньне старонак…",
"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]] е добавена към категория",
"timezoneregion-indian": "ভারত মহাসাগর",
"timezoneregion-pacific": "প্রশান্ত মহাসাগর",
"allowemail": "অন্য ব্যবহারকারীদেরকে আমাকে ইমেল করতে অনুমতি দিন",
+ "email-allow-new-users-label": "নতুন ব্যবহারকারীদেরকে ইমেলের অনুমতি দিন",
"email-blacklist-label": "আমাকে ইমেইল পাঠানো থেকে এই ব্যবহারকারীদের বিরত রাখুন:",
"prefs-searchoptions": "অনুসন্ধান",
"prefs-namespaces": "নামস্থানসমূহ",
"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",
"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",
"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ë",
"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><$1 $2=\"$3\"></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",
"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: खोल नामहरूको अगाडी डबल-क्लिक गर जसले तमलाई विभिन्न प्रकारहरूको विकल्प दिन्छ।",
"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",
"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",
"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": "نام برچسب",
"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",
"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",
"चक्रपाणी",
"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",
"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:",
"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",
"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": "ახალი ტეგის შექმნა",
"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",
"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)",
"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.",
"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 % av innhaldet på ei side",
"tag-mw-rollback": "Attenderulling",
"tags-title": "Merke",
"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": "د بدلونونو د مخليدنو په وخت کې مو يوه ستونزه رامېنځ ته شوه.",
"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": "Правки, которые откатывают предыдущие правки по нажатию ссылки отката",
"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",
"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",
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 */
use MediaWiki\Services\SalvageableService;
use MediaWiki\Services\ServiceDisabledException;
use MediaWiki\Shell\CommandFactory;
+use MediaWiki\Storage\BlobStore;
+use MediaWiki\Storage\RevisionStore;
/**
* @covers MediaWiki\MediaWikiServices
'LocalServerObjectCache' => [ 'LocalServerObjectCache', BagOStuff::class ],
'VirtualRESTServiceClient' => [ 'VirtualRESTServiceClient', VirtualRESTServiceClient::class ],
'ShellCommandFactory' => [ 'ShellCommandFactory', CommandFactory::class ],
+ 'BlobStore' => [ 'BlobStore', BlobStore::class ],
+ 'RevisionStore' => [ 'RevisionStore', RevisionStore::class ],
];
}
<?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.
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
$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';
}
$props['page'] = $this->testPage->getId();
}
+ if ( !isset( $props['content_model'] ) ) {
+ $props['content_model'] = CONTENT_MODEL_WIKITEXT;
+ }
+
$rev = new Revision( $props );
$dbw = wfGetDB( DB_MASTER );
$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',
[ [
strval( $rev->getId() ),
strval( $this->testPage->getId() ),
- strval( $rev->getTextId() ),
+ strval( $textId ),
'0',
'0',
'0',
// 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 ) );
}
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;
+ },
+ ];
}
/**
* @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',
$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 );
$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() );
* @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()
$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
$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' );
}
/**
'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() );
}
'user' => $userA->getId(),
'text' => 'zero',
'content_model' => CONTENT_MODEL_WIKITEXT,
- 'summary' => 'edit zero'
+ 'comment' => 'edit zero'
] );
$revisions[0]->insertOn( $dbw );
'user' => $userA->getId(),
'text' => 'one',
'content_model' => CONTENT_MODEL_WIKITEXT,
- 'summary' => 'edit one'
+ 'comment' => 'edit one'
] );
$revisions[1]->insertOn( $dbw );
'user' => $userB->getId(),
'text' => 'two',
'content_model' => CONTENT_MODEL_WIKITEXT,
- 'summary' => 'edit two'
+ 'comment' => 'edit two'
] );
$revisions[2]->insertOn( $dbw );
'user' => $userA->getId(),
'text' => 'three',
'content_model' => CONTENT_MODEL_WIKITEXT,
- 'summary' => 'edit three'
+ 'comment' => 'edit three'
] );
$revisions[3]->insertOn( $dbw );
'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 );
'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() {
*/
public function testLoadFromId() {
$rev = $this->testPage->getRevision();
+ $this->hideDeprecated( 'Revision::loadFromId' );
$this->assertRevEquals(
$rev,
Revision::loadFromId( wfGetDB( DB_MASTER ), $rev->getId() )
$rev[1] = $this->testPage->getLatest();
$this->assertSame(
- [ $rev[1] => strval( $textLength ) ],
+ [ $rev[1] => $textLength ],
Revision::getParentLengths(
wfGetDB( DB_MASTER ),
[ $rev[1] ]
$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] ]
);
}
- /**
- * @covers Revision::getTitle
- */
- public function testGetTitle_forBadRevision() {
- $rev = new Revision( [] );
- $this->assertNull( $rev->getTitle() );
- }
-
/**
* @covers Revision::isMinor
*/
$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() {
]
);
$user = $this->getTestUser( $userGroups )->getUser();
- $revision = new Revision( [ 'deleted' => $bitField ] );
+ $revision = new Revision( [ 'deleted' => $bitField ], 0, $this->testPage->getTitle() );
$this->assertSame(
$expected,
<?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.
'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;
}
/**
* @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' => [
[
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,
- ];
}
/**
$expectedUserName = $testUser->getName();
}
- $rev = new Revision( $rowArray );
+ $rev = new Revision( $rowArray, 0, $this->getMockTitle() );
$this->assertEquals( $expectedUserId, $rev->getUser() );
$this->assertEquals( $expectedUserName, $rev->getUserText() );
}
[
'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(
$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',
'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() );
$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',
'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' );
}
];
}
/**
* @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 );
}
* @covers Revision::getId
*/
public function testGetId( $rowArray, $expectedId ) {
- $rev = new Revision( $rowArray );
+ $rev = new Revision( $rowArray, 0, $this->getMockTitle() );
$this->assertEquals( $expectedId, $rev->getId() );
}
* @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() );
}
* @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 ) );
* @covers Revision::getTextId()
*/
public function testGetTextId( $rowArray, $expected ) {
- $rev = new Revision( $rowArray );
+ $rev = new Revision( $rowArray, 0, $this->getMockTitle() );
$this->assertSame( $expected, $rev->getTextId() );
}
* @covers Revision::getParentId()
*/
public function testGetParentId( $rowArray, $expected ) {
- $rev = new Revision( $rowArray );
+ $rev = new Revision( $rowArray, 0, $this->getMockTitle() );
$this->assertSame( $expected, $rev->getParentId() );
}
$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',
];
yield 'Utf8Legacy' => [
"Wiki est l'\xc3\xa9cole superieur !",
+ 'fr',
'iso-8859-1',
[
'old_flags' => '',
* @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 );
}
*/
yield 'Utf8NativeGzip' => [
"Wiki est l'\xc3\xa9cole superieur !",
+ 'fr',
'iso-8859-1',
[
'old_flags' => 'gzip,utf-8',
];
yield 'Utf8LegacyGzip' => [
"Wiki est l'\xc3\xa9cole superieur !",
+ 'fr',
'iso-8859-1',
[
'old_flags' => 'gzip',
* @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 );
}
*/
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 !";
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(
$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() {
* @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 )
* @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',
]
)
);
+
+ $cacheKey = $cache->makeKey( 'revisiontext', 'textid', 'tt:7777' );
$this->assertSame( 'AAAABBAAA', $cache->get( $cacheKey ) );
}
'fields' => [
'ar_id',
'ar_page_id',
+ 'ar_namespace',
+ 'ar_title',
'ar_rev_id',
'ar_text',
'ar_text_id',
'fields' => [
'ar_id',
'ar_page_id',
+ 'ar_namespace',
+ 'ar_title',
'ar_rev_id',
'ar_text',
'ar_text_id',
'fields' => [
'ar_id',
'ar_page_id',
+ 'ar_namespace',
+ 'ar_title',
'ar_rev_id',
'ar_text',
'ar_text_id',
'fields' => [
'ar_id',
'ar_page_id',
+ 'ar_namespace',
+ 'ar_title',
'ar_rev_id',
'ar_text',
'ar_text_id',
'fields' => [
'ar_id',
'ar_page_id',
+ 'ar_namespace',
+ 'ar_title',
'ar_rev_id',
'ar_text',
'ar_text_id',
*/
public function testGetArchiveQueryInfo( $globals, $expected ) {
$this->setMwGlobals( $globals );
+
+ $revisionStore = $this->getRevisionStore();
+ $revisionStore->setContentHandlerUseDB( $globals['wgContentHandlerUseDB'] );
+ $this->setService( 'RevisionStore', $revisionStore );
+
$this->assertEquals(
$expected,
Revision::getArchiveQueryInfo()
*/
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 )
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
];
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
];
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',
];
* @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 );
public function testInsertOn_idSpecified() {
$title = Title::newFromText( __METHOD__ );
$page = new WikiPage( $title );
- $id = 3478952189;
+ $id = 1478952189;
$result = $page->insertOn( $this->db, $id );
public function testIllegalDependencies() {
$data = self::getAllModules();
- $illegalDeps = ResourceLoaderStartupModule::getStartupModules();
+ $illegalDeps = ResourceLoaderStartUpModule::getStartupModules();
foreach ( $data['modules'] as $moduleName => $module ) {
if ( $module->isRaw() ) {
$illegalDeps[] = $moduleName;