have been removed.
* The $wgUseAjax setting, deprecated in 1.31, is now ignored.
* The $wgSiteSupportPage setting, unused since 1.5, was removed.
+* The $wgBrowserBlacklist setting, deprecated in 1.30, was removed.
* The default quality of JPEG thumbnails generated by GD was reduced from 95 to
80. The quality of JPEG thumbnails is now configurable through the new setting
$wgJpegQuality (default 80). This aligns the quality to what ImageMagick uses.
use 'EditPageGetCheckboxesDefinition' instead.
* Linker::getLinkColour() and DummyLinker::getLinkColour(), deprecated since
1.28, were removed. LinkRenderer::getLinkClasses() should be used instead.
+* Wikimedia\Rdbms\LoadBalancer::getLaggedSlaveMode(), deprecated in 1.28, has
+ been removed. Use Wikimedia\Rdbms\LoadBalancer::getLaggedReplicaMode()
+ instead.
* mw.widgets.CategoryMultiselectWidget now uses TagMultiselectWidget instead of
CapsuleMultiselectWidget. The following methods may no longer be used:
* setItemsFromData: Use setValue instead
*/
$wgLegacyEncoding = false;
-/**
- * @deprecated since 1.30, does nothing
- */
-$wgBrowserBlackList = [];
-
/**
* If set to true, the MediaWiki 1.4 to 1.5 schema conversion will
* create stub reference rows in the text table instead of copying
* Maintain a log of page creations at Special:Log/create?
* @since 1.32
*/
-$wgPageCreationLog = false;
+$wgPageCreationLog = true;
/** @} */ # end logging }
$wgCommentTableSchemaMigrationStage = MIGRATION_OLD;
/**
- * RevisionStore table schema migration stage (content, slots, content_models & slot_roles tables)
+ * RevisionStore table schema migration stage (content, slots, content_models & slot_roles tables).
+ * Use the SCHEMA_COMPAT_XXX flags. Supported values:
+ *
+ * - SCHEMA_COMPAT_OLD
+ * - SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
+ * - SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW
+ * - SCHEMA_COMPAT_OLD
+ *
+ * Note that reading the old and new schema at the same time is not supported.
+ * Attempting to set both read bits in $wgMultiContentRevisionSchemaMigrationStage
+ * will result in an InvalidArgumentException.
*
* @see Task: https://phabricator.wikimedia.org/T174028
* @see Commit: https://gerrit.wikimedia.org/r/#/c/378724/
*
* @since 1.32
- * @var int One of the MIGRATION_* constants
+ * @var int An appropriate combination of SCHEMA_COMPAT_XXX flags.
*/
$wgMultiContentRevisionSchemaMigrationStage = MIGRATION_OLD;
define( 'SHELL_MAX_ARG_STRLEN', '100000' );
/**@}*/
+/**@{
+ * Schema compatibility flags.
+ *
+ * Used as flags in a bit field that indicates whether the old or new schema (or both)
+ * are read or written.
+ *
+ * - SCHEMA_COMPAT_WRITE_OLD: Whether information is written to the old schema.
+ * - SCHEMA_COMPAT_READ_OLD: Whether information stored in the old schema is read.
+ * - SCHEMA_COMPAT_WRITE_NEW: Whether information is written to the new schema.
+ * - SCHEMA_COMPAT_READ_NEW: Whether information stored in the new schema is read.
+ */
+define( 'SCHEMA_COMPAT_WRITE_OLD', 0x01 );
+define( 'SCHEMA_COMPAT_READ_OLD', 0x02 );
+define( 'SCHEMA_COMPAT_WRITE_NEW', 0x10 );
+define( 'SCHEMA_COMPAT_READ_NEW', 0x20 );
+define( 'SCHEMA_COMPAT_WRITE_BOTH', SCHEMA_COMPAT_WRITE_OLD | SCHEMA_COMPAT_WRITE_NEW );
+define( 'SCHEMA_COMPAT_READ_BOTH', SCHEMA_COMPAT_READ_OLD | SCHEMA_COMPAT_READ_NEW );
+define( 'SCHEMA_COMPAT_OLD', SCHEMA_COMPAT_WRITE_OLD | SCHEMA_COMPAT_READ_OLD );
+define( 'SCHEMA_COMPAT_NEW', SCHEMA_COMPAT_WRITE_NEW | SCHEMA_COMPAT_READ_NEW );
+/**@}*/
+
/**@{
* Schema change migration flags.
*
* Used as values of a feature flag for an orderly transition from an old
- * schema to a new schema.
+ * schema to a new schema. The numeric values of these constants are compatible with the
+ * SCHEMA_COMPAT_XXX bitfield semantics. High bits are used to ensure that the numeric
+ * ordering follows the order in which the migration stages should be used.
*
* - MIGRATION_OLD: Only read and write the old schema. The new schema need not
* even exist. This is used from when the patch is merged until the schema
* - MIGRATION_NEW: Only read and write the new schema. The old schema (and the
* feature flag) may now be removed.
*/
-define( 'MIGRATION_OLD', 0 );
-define( 'MIGRATION_WRITE_BOTH', 1 );
-define( 'MIGRATION_WRITE_NEW', 2 );
-define( 'MIGRATION_NEW', 3 );
+define( 'MIGRATION_OLD', 0x00000000 | SCHEMA_COMPAT_OLD );
+define( 'MIGRATION_WRITE_BOTH', 0x10000000 | SCHEMA_COMPAT_READ_BOTH | SCHEMA_COMPAT_WRITE_BOTH );
+define( 'MIGRATION_WRITE_NEW', 0x20000000 | SCHEMA_COMPAT_READ_BOTH | SCHEMA_COMPAT_WRITE_NEW );
+define( 'MIGRATION_NEW', 0x30000000 | SCHEMA_COMPAT_NEW );
/**@}*/
} else {
// If we receive the last parameter of the request, we can fairly
// claim the POST request has not been truncated.
-
- // TODO: softened the check for cutover. Once we determine
- // that it is safe, we should complete the transition by
- // removing the "edittime" clause.
- $this->incompleteForm = ( !$request->getVal( 'wpUltimateParam' )
- && is_null( $this->edittime ) );
+ $this->incompleteForm = !$request->getVal( 'wpUltimateParam' );
}
if ( $this->incompleteForm ) {
# If the form is incomplete, force to preview.
use MediaWiki\Shell\Shell;
use Wikimedia\ScopedCallback;
use Wikimedia\Rdbms\DBReplicationWaitError;
+use Wikimedia\WrappedString;
/**
* Load an extension
use MediaWiki\Storage\RevisionFactory;
use MediaWiki\Storage\RevisionLookup;
use MediaWiki\Storage\RevisionStore;
-use MediaWiki\Storage\RevisionStoreFactory;
use OldRevisionImporter;
use UploadRevisionImporter;
use Wikimedia\Rdbms\LBFactory;
return $this->getService( 'RevisionStore' );
}
- /**
- * @since 1.32
- * @return RevisionStoreFactory
- */
- public function getRevisionStoreFactory() {
- return $this->getService( 'RevisionStoreFactory' );
- }
-
/**
* @since 1.31
* @return RevisionLookup
use MediaWiki\Logger\LoggerFactory;
use MediaWiki\MediaWikiServices;
use MediaWiki\Session\SessionManager;
+use Wikimedia\Rdbms\IResultWrapper;
use Wikimedia\RelPath;
use Wikimedia\WrappedString;
use Wikimedia\WrappedStringList;
/**
* @param array $categories
- * @return bool|ResultWrapper
+ * @return bool|IResultWrapper
*/
protected function addCategoryLinksToLBAndGetResult( array $categories ) {
# Add the links to a LinkBatch
use MediaWiki\Shell\CommandFactory;
use MediaWiki\Storage\BlobStoreFactory;
use MediaWiki\Storage\NameTableStore;
-use MediaWiki\Storage\RevisionStoreFactory;
+use MediaWiki\Storage\RevisionStore;
use MediaWiki\Storage\SqlBlobStore;
use Wikimedia\ObjectFactory;
},
'RevisionStore' => function ( MediaWikiServices $services ) {
- return $services->getRevisionStoreFactory()->getRevisionStore();
- },
-
- 'RevisionStoreFactory' => function ( MediaWikiServices $services ) {
/** @var SqlBlobStore $blobStore */
$blobStore = $services->getService( '_SqlBlobStore' );
- $config = $services->getMainConfig();
- $store = new RevisionStoreFactory(
+ $store = new RevisionStore(
$services->getDBLoadBalancer(),
$blobStore,
$services->getMainWANObjectCache(),
$services->getContentModelStore(),
$services->getSlotRoleStore(),
$services->getMainConfig()->get( 'MultiContentRevisionSchemaMigrationStage' ),
- $services->getActorMigration(),
- LoggerFactory::getInstance( 'RevisionStore' ),
- $config->get( 'ContentHandlerUseDB' )
+ $services->getActorMigration()
);
+ $store->setLogger( LoggerFactory::getInstance( 'RevisionStore' ) );
+
+ $config = $services->getMainConfig();
+ $store->setContentHandlerUseDB( $config->get( 'ContentHandlerUseDB' ) );
+
return $store;
},
return $this->loadBalancer->getConnection( $index, [], $this->wikiId, $flags );
}
+ /**
+ * Gets the cache key for names.
+ *
+ * The cache key is constructed based on the wiki ID passed to the constructor, and allows
+ * sharing of name tables cached for a specific database between wikis.
+ *
+ * @return string
+ */
private function getCacheKey() {
- return $this->cache->makeKey( 'NameTableSqlStore', $this->table, $this->wikiId );
+ return $this->cache->makeGlobalKey(
+ 'NameTableSqlStore',
+ $this->table,
+ $this->loadBalancer->resolveDomainID( $this->wikiId )
+ );
}
/**
// TODO: MCR: check the role and the content's model against the list of supported
// roles, see T194046.
+ if ( $role !== 'main' ) {
+ throw new InvalidArgumentException( 'Only the main slot is presently supported' );
+ }
+
$this->slotsUpdate->modifyContent( $role, $content );
}
*/
private $slotRoleStore;
- /** @var int One of the MIGRATION_* constants */
+ /** @var int An appropriate combination of SCHEMA_COMPAT_XXX flags. */
private $mcrMigrationStage;
/**
* @param CommentStore $commentStore
* @param NameTableStore $contentModelStore
* @param NameTableStore $slotRoleStore
- * @param int $migrationStage
+ * @param int $mcrMigrationStage An appropriate combination of SCHEMA_COMPAT_XXX flags
* @param ActorMigration $actorMigration
* @param bool|string $wikiId
+ *
+ * @throws MWException if $mcrMigrationStage or $wikiId is invalid.
*/
public function __construct(
LoadBalancer $loadBalancer,
CommentStore $commentStore,
NameTableStore $contentModelStore,
NameTableStore $slotRoleStore,
- $migrationStage,
+ $mcrMigrationStage,
ActorMigration $actorMigration,
$wikiId = false
) {
Assert::parameterType( 'string|boolean', $wikiId, '$wikiId' );
- Assert::parameterType( 'integer', $migrationStage, '$migrationStage' );
+ Assert::parameterType( 'integer', $mcrMigrationStage, '$mcrMigrationStage' );
+ Assert::parameter(
+ ( $mcrMigrationStage & SCHEMA_COMPAT_READ_BOTH ) !== SCHEMA_COMPAT_READ_BOTH,
+ '$mcrMigrationStage',
+ 'Reading from the old and the new schema at the same time is not supported.'
+ );
+ Assert::parameter(
+ ( $mcrMigrationStage & SCHEMA_COMPAT_READ_BOTH ) !== 0,
+ '$mcrMigrationStage',
+ 'Reading needs to be enabled for the old or the new schema.'
+ );
+ Assert::parameter(
+ ( $mcrMigrationStage & SCHEMA_COMPAT_WRITE_BOTH ) !== 0,
+ '$mcrMigrationStage',
+ 'Writing needs to be enabled for the old or the new schema.'
+ );
+ Assert::parameter(
+ ( $mcrMigrationStage & SCHEMA_COMPAT_READ_OLD ) === 0
+ || ( $mcrMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) !== 0,
+ '$mcrMigrationStage',
+ 'Cannot read the old schema when not also writing it.'
+ );
+ Assert::parameter(
+ ( $mcrMigrationStage & SCHEMA_COMPAT_READ_NEW ) === 0
+ || ( $mcrMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) !== 0,
+ '$mcrMigrationStage',
+ 'Cannot read the new schema when not also writing it.'
+ );
$this->loadBalancer = $loadBalancer;
$this->blobStore = $blobStore;
$this->commentStore = $commentStore;
$this->contentModelStore = $contentModelStore;
$this->slotRoleStore = $slotRoleStore;
- $this->mcrMigrationStage = $migrationStage;
+ $this->mcrMigrationStage = $mcrMigrationStage;
$this->actorMigration = $actorMigration;
$this->wikiId = $wikiId;
$this->logger = new NullLogger();
}
+ /**
+ * @param int $flags A combination of SCHEMA_COMPAT_XXX flags.
+ * @return bool True if all the given flags were set in the $mcrMigrationStage
+ * parameter passed to the constructor.
+ */
+ private function hasMcrSchemaFlags( $flags ) {
+ return ( $this->mcrMigrationStage & $flags ) === $flags;
+ }
+
public function setLogger( LoggerInterface $logger ) {
$this->logger = $logger;
}
* @throws MWException
*/
public function setContentHandlerUseDB( $contentHandlerUseDB ) {
- if ( !$contentHandlerUseDB && $this->mcrMigrationStage > MIGRATION_OLD ) {
- throw new MWException(
- 'Content model must be stored in the database for multi content revision migration.'
- );
+ if ( $this->hasMcrSchemaFlags( SCHEMA_COMPAT_WRITE_NEW )
+ || $this->hasMcrSchemaFlags( SCHEMA_COMPAT_READ_NEW )
+ ) {
+ if ( !$contentHandlerUseDB ) {
+ throw new MWException(
+ 'Content model must be stored in the database for multi content revision migration.'
+ );
+ }
}
$this->contentHandlerUseDB = $contentHandlerUseDB;
}
);
}
- // While inserting into the old schema make sure only the main slot is allowed.
- // TODO: support extra slots in MIGRATION_WRITE_BOTH mode!
- if ( $this->mcrMigrationStage <= MIGRATION_WRITE_BOTH && $slotRoles !== [ 'main' ] ) {
+ // If we are not writing into the new schema, we can't support extra slots.
+ if ( !$this->hasMcrSchemaFlags( SCHEMA_COMPAT_WRITE_NEW ) && $slotRoles !== [ 'main' ] ) {
throw new InvalidArgumentException(
- 'Only the main slot is supported with MCR migration mode <= MIGRATION_WRITE_BOTH!'
+ 'Only the main slot is supported when not writing to the MCR enabled schema!'
+ );
+ }
+
+ // As long as we are not reading from the new schema, we don't want to write extra slots.
+ if ( !$this->hasMcrSchemaFlags( SCHEMA_COMPAT_READ_NEW ) && $slotRoles !== [ 'main' ] ) {
+ throw new InvalidArgumentException(
+ 'Only the main slot is supported when not reading from the MCR enabled schema!'
);
}
);
// Trigger exception if the main slot is missing.
- // Technically, this could go away with MIGRATION_NEW: while
+ // Technically, this could go away after MCR migration: while
// calling code may require a main slot to exist, RevisionStore
// really should not know or care about that requirement.
$rev->getSlot( 'main', RevisionRecord::RAW );
$newSlots[$role] = $slot;
// Write the main slot's text ID to the revision table for backwards compatibility
- if ( $slot->getRole() === 'main' && $this->mcrMigrationStage <= MIGRATION_WRITE_BOTH ) {
+ if ( $slot->getRole() === 'main'
+ && $this->hasMcrSchemaFlags( SCHEMA_COMPAT_WRITE_OLD )
+ ) {
$blobAddress = $slot->getAddress();
$this->updateRevisionTextId( $dbw, $revisionId, $blobAddress );
}
}
// Write the main slot's text ID to the revision table for backwards compatibility
- if ( $protoSlot->getRole() === 'main' && $this->mcrMigrationStage <= MIGRATION_WRITE_BOTH ) {
+ if ( $protoSlot->getRole() === 'main'
+ && $this->hasMcrSchemaFlags( SCHEMA_COMPAT_WRITE_OLD )
+ ) {
$this->updateRevisionTextId( $dbw, $revisionId, $blobAddress );
}
- if ( $this->mcrMigrationStage >= MIGRATION_WRITE_BOTH ) {
+ if ( $this->hasMcrSchemaFlags( SCHEMA_COMPAT_WRITE_NEW ) ) {
if ( $protoSlot->hasContentId() ) {
$contentId = $protoSlot->getContentId();
} else {
$revisionRow['rev_id'] = $rev->getId();
}
- if ( $this->mcrMigrationStage <= MIGRATION_WRITE_BOTH ) {
- // In non MCR more this IF section will relate to the main slot
+ if ( $this->hasMcrSchemaFlags( SCHEMA_COMPAT_WRITE_OLD ) ) {
+ // In non MCR mode this IF section will relate to the main slot
$mainSlot = $rev->getSlot( 'main' );
$model = $mainSlot->getModel();
$format = $mainSlot->getFormat();
$blobFlags = null;
if ( is_object( $row ) ) {
- if ( $this->mcrMigrationStage >= MIGRATION_NEW ) {
+ if ( $this->hasMcrSchemaFlags( SCHEMA_COMPAT_READ_NEW ) ) {
// Don't emulate from a row when using the new schema.
// Emulating from an array is still OK.
throw new LogicException( 'Can\'t emulate the main slot when using MCR schema.' );
$queryFlags,
Title $title
) {
- if ( $this->mcrMigrationStage < MIGRATION_NEW ) {
- // TODO: in MIGRATION_WRITE_BOTH, we could use the old and the new method:
- // e.g. call emulateMainSlot_1_29() if loadSlotRecords() fails.
-
+ if ( !$this->hasMcrSchemaFlags( SCHEMA_COMPAT_READ_NEW ) ) {
$mainSlot = $this->emulateMainSlot_1_29( $revisionRow, $queryFlags, $title );
$slots = new RevisionSlots( [ 'main' => $mainSlot ] );
} else {
}
if ( !empty( $fields['text_id'] ) ) {
- if ( $this->mcrMigrationStage >= MIGRATION_NEW ) {
- throw new MWException( "Cannot use text_id field with MCR schema" );
+ if ( !$this->hasMcrSchemaFlags( SCHEMA_COMPAT_READ_OLD ) ) {
+ throw new MWException( "The text_id field is only available in the pre-MCR schema" );
}
if ( !empty( $fields['content'] ) ) {
/**
* Finds the ID of a content row for a given revision and slot role.
* This can be used to re-use content rows even while the content ID
- * is still missing from SlotRecords, in MIGRATION_WRITE_BOTH mode.
+ * is still missing from SlotRecords, when writing to both the old and
+ * the new schema during MCR schema migration.
*
* @todo remove after MCR schema migration is complete.
*
* @return int|null
*/
private function findSlotContentId( IDatabase $db, $revId, $role ) {
- if ( $this->mcrMigrationStage < MIGRATION_WRITE_BOTH ) {
+ if ( !$this->hasMcrSchemaFlags( SCHEMA_COMPAT_WRITE_NEW ) ) {
return null;
}
* - 'page': Join with the page table, and select fields to identify the page
* - 'user': Join with the user table, and select the user name
* - 'text': Join with the text table, and select fields to load page text. This
- * option is deprecated in MW 1.32 with MCR migration stage MIGRATION_WRITE_BOTH,
- * and disallowed with MIGRATION_MEW.
+ * option is deprecated in MW 1.32 when the MCR migration flag SCHEMA_COMPAT_WRITE_NEW
+ * is set, and disallowed when SCHEMA_COMPAT_READ_OLD is not set.
*
* @return array With three keys:
* - tables: (string[]) to include in the `$table` to `IDatabase->select()`
$ret['fields'] = array_merge( $ret['fields'], $actorQuery['fields'] );
$ret['joins'] = array_merge( $ret['joins'], $actorQuery['joins'] );
- if ( $this->mcrMigrationStage < MIGRATION_NEW ) {
+ if ( $this->hasMcrSchemaFlags( SCHEMA_COMPAT_READ_OLD ) ) {
$ret['fields'][] = 'rev_text_id';
if ( $this->contentHandlerUseDB ) {
}
if ( in_array( 'text', $options, true ) ) {
- if ( $this->mcrMigrationStage === MIGRATION_NEW ) {
+ if ( !$this->hasMcrSchemaFlags( SCHEMA_COMPAT_WRITE_OLD ) ) {
throw new InvalidArgumentException( 'text table can no longer be joined directly' );
- } elseif ( $this->mcrMigrationStage >= MIGRATION_WRITE_BOTH ) {
+ } elseif ( !$this->hasMcrSchemaFlags( SCHEMA_COMPAT_READ_OLD ) ) {
+ // NOTE: even when this class is set to not read from the old schema, callers
+ // should still be able to join against the text table, as long as we are still
+ // writing the old schema for compatibility.
wfDeprecated( __METHOD__ . ' with `text` option', '1.32' );
}
'joins' => [],
];
- if ( $this->mcrMigrationStage < MIGRATION_NEW ) {
+ if ( $this->hasMcrSchemaFlags( SCHEMA_COMPAT_READ_OLD ) ) {
$db = $this->getDBConnectionRef( DB_REPLICA );
$ret['tables']['slots'] = 'revision';
$ret['fields']['model_name'] = 'NULL';
}
}
-
- // XXX: in MIGRATION_WRITE_BOTH mode, emulate *and* select - using a UNION?
- // See Anomie's idea at <https://gerrit.wikimedia.org/r/c/416465/
- // 8..10/includes/Storage/RevisionStore.php#2113>
} else {
$ret['tables'][] = 'slots';
$ret['tables'][] = 'slot_roles';
'joins' => $commentQuery['joins'] + $actorQuery['joins'],
];
- if ( $this->mcrMigrationStage < MIGRATION_NEW ) {
+ if ( $this->hasMcrSchemaFlags( SCHEMA_COMPAT_READ_OLD ) ) {
$ret['fields'][] = 'ar_text_id';
if ( $this->contentHandlerUseDB ) {
+++ /dev/null
-<?php
-
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * Attribution notice: when this file was created, much of its content was taken
- * from the Revision.php file as present in release 1.30. Refer to the history
- * of that file for original authorship.
- *
- * @file
- */
-
-namespace MediaWiki\Storage;
-
-use ActorMigration;
-use CommentStore;
-use Psr\Log\LoggerInterface;
-use WANObjectCache;
-use Wikimedia\Assert\Assert;
-use Wikimedia\Rdbms\LoadBalancer;
-
-/**
- * @since 1.32
- */
-class RevisionStoreFactory {
-
- /** @var SqlBlobStore */
- private $blobStore;
-
- /** @var LoadBalancer */
- private $loadBalancer;
-
- /** @var WANObjectCache */
- private $cache;
-
- /** @var CommentStore */
- private $commentStore;
-
- /** @var ActorMigration */
- private $actorMigration;
-
- /** @var NameTableStore */
- private $contentModelStore;
-
- /** @var NameTableStore */
- private $slotRoleStore;
-
- /** @var int One of the MIGRATION_* constants */
- private $mcrMigrationStage;
-
- /**
- * @var bool
- * @see $wgContentHandlerUseDB
- */
- private $contentHandlerUseDB;
-
- /** @var LoggerInterface */
- private $logger;
-
- /**
- * @todo $blobStore should be allowed to be any BlobStore!
- *
- * @param LoadBalancer $loadBalancer
- * @param SqlBlobStore $blobStore
- * @param WANObjectCache $cache
- * @param CommentStore $commentStore
- * @param NameTableStore $contentModelStore
- * @param NameTableStore $slotRoleStore
- * @param int $migrationStage
- * @param ActorMigration $actorMigration
- * @param LoggerInterface $logger
- * @param bool $contentHandlerUseDB see {@link $wgContentHandlerUseDB}
- */
- public function __construct(
- LoadBalancer $loadBalancer,
- SqlBlobStore $blobStore,
- WANObjectCache $cache,
- CommentStore $commentStore,
- NameTableStore $contentModelStore,
- NameTableStore $slotRoleStore,
- $migrationStage,
- ActorMigration $actorMigration,
- LoggerInterface $logger,
- $contentHandlerUseDB
- ) {
- Assert::parameterType( 'integer', $migrationStage, '$migrationStage' );
-
- $this->loadBalancer = $loadBalancer;
- $this->blobStore = $blobStore;
- $this->cache = $cache;
- $this->commentStore = $commentStore;
- $this->contentModelStore = $contentModelStore;
- $this->slotRoleStore = $slotRoleStore;
- $this->mcrMigrationStage = $migrationStage;
- $this->actorMigration = $actorMigration;
- $this->logger = $logger;
- $this->contentHandlerUseDB = $contentHandlerUseDB;
- }
-
- /**
- * @since 1.32
- *
- * @param bool|string $wikiId false for the current domain / wikid
- *
- * @return RevisionStore for the given wikiId with all necessary services and a logger
- */
- public function getRevisionStore( $wikiId = false ) {
- Assert::parameterType( 'string|boolean', $wikiId, '$wikiId' );
-
- $store = new RevisionStore(
- $this->loadBalancer,
- $this->blobStore,
- $this->cache,
- $this->commentStore,
- $this->contentModelStore,
- $this->slotRoleStore,
- $this->mcrMigrationStage,
- $this->actorMigration,
- $wikiId
- );
-
- $store->setLogger( $this->logger );
- $store->setContentHandlerUseDB( $this->contentHandlerUseDB );
-
- return $store;
- }
-
-}
// No negative caching; negative hits on text rows may be due to corrupted replica DBs
$blob = $this->cache->getWithSetCallback(
- // TODO: change key, since this is not necessarily revision text!
- $this->cache->makeKey( 'revisiontext', 'textid', $blobAddress ),
+ $this->getCacheKey( $blobAddress ),
$this->getCacheTTL(),
function ( $unused, &$ttl, &$setOpts ) use ( $blobAddress, $queryFlags ) {
list( $index ) = DBAccessObjectUtils::getDBOptions( $queryFlags );
return $blob;
}
+ /**
+ * Get a cache key for a given Blob address.
+ *
+ * The cache key is constructed in a way that allows cached blobs from the same database
+ * to be re-used between wikis. For example, enwiki and frwiki will use the same cache keys
+ * for blobs from the wikidatawiki database.
+ *
+ * @param string $blobAddress
+ * @return string
+ */
+ private function getCacheKey( $blobAddress ) {
+ return $this->cache->makeGlobalKey(
+ 'BlobStore',
+ 'address',
+ $this->dbLoadBalancer->resolveDomainID( $this->wikiId ),
+ $blobAddress
+ );
+ }
+
/**
* Expand a raw data blob according to the flags given.
*
* @param string|string[] $flags Blob flags, such as 'external' or 'gzip'.
* Note that not including 'utf-8' in $flags will cause the data to be decoded
* according to the legacy encoding specified via setLegacyEncoding.
- * @param string|null $cacheKey May be used for caching if given
+ * @param string|null $cacheKey A blob address for use in the cache key. If not given,
+ * caching is disabled.
*
* @return false|string The expanded blob or false on failure
*/
return false;
}
- if ( $cacheKey && $this->wikiId === false ) {
- // Make use of the wiki-local revision text cache.
+ if ( $cacheKey ) {
// The cached value should be decompressed, so handle that and return here.
- // NOTE: we rely on $this->cache being the right cache for $this->wikiId!
return $this->cache->getWithSetCallback(
- // TODO: change key, since this is not necessarily revision text!
- $this->cache->makeKey( 'revisiontext', 'textid', $cacheKey ),
+ $this->getCacheKey( $cacheKey ),
$this->getCacheTTL(),
function () use ( $url, $flags ) {
// No negative caching per BlobStore::getBlob()
"apihelp-protect-example-protect": "Een pagina beveiligen.",
"apihelp-purge-param-forcelinkupdate": "Werk de koppelingstabellen bij.",
"apihelp-purge-param-forcerecursivelinkupdate": "Werk de koppelingentabel bij, en werk de koppelingstabellen bij voor alle pagina's die deze pagina als sjabloon gebruiken.",
+ "apihelp-query+allcategories-summary": "Alle categorieën doorlopen.",
"apihelp-query+allcategories-param-dir": "Richting om in te sorteren.",
"apihelp-query+allcategories-param-limit": "Hoeveel categorieën te tonen.",
"apihelp-query+allcategories-paramvalue-prop-size": "Voegt het aantal pagina's in de categorie toe.",
"apihelp-query+allpages-param-limit": "Het totaal aantal pagina's dat getoont moeten worden.",
"apihelp-query+allredirects-summary": "Toon alle doorverwijzingen naar een naamruimte.",
"apihelp-query+allredirects-paramvalue-prop-title": "Voegt de titel van de doorverwijzing toe.",
+ "apihelp-query+allredirects-param-limit": "Hoeveel items er in totaal moeten worden getoond.",
"apihelp-query+allrevisions-summary": "Toon alle versies.",
+ "apihelp-query+allrevisions-param-namespace": "Alleen pagina's in deze naamruimte weergeven.",
"apihelp-query+allrevisions-example-user": "Toon de laatste 50 bijdragen van gebruiker <kbd>Example</kbd>.",
"apihelp-query+mystashedfiles-paramvalue-prop-type": "Vraag het MIME- en mediatype van het bestand op.",
"apihelp-query+mystashedfiles-param-limit": "Hoeveel bestanden te tonen.",
"apihelp-query+alltransclusions-param-namespace": "De door te lopen naamruimte.",
+ "apihelp-query+alltransclusions-param-limit": "Hoeveel items er in totaal moeten worden getoond.",
"apihelp-query+allusers-param-dir": "Richting om in te sorteren.",
"apihelp-query+allusers-param-excludegroup": "Sluit gebruikers in de gegeven groepen uit.",
"apihelp-query+allusers-paramvalue-prop-blockinfo": "Voegt informatie over een actuele blokkade van de gebruiker toe.",
"apihelp-query+categorymembers-param-dir": "Richting om in te sorteren.",
"apihelp-query+categorymembers-example-simple": "Toon de eerste 10 pagina's in <kbd>Category:Physics</kbd>.",
"apihelp-query+deletedrevisions-param-tag": "Alleen revisies met dit label weergeven.",
+ "apihelp-query+deletedrevs-paraminfo-modes": "{{PLURAL:$1|Modus|Modi}}: $2",
"apihelp-query+deletedrevs-param-tag": "Alleen revisies met dit label weergeven.",
"apihelp-query+embeddedin-param-namespace": "De door te lopen naamruimte.",
+ "apihelp-query+filearchive-example-simple": "Toon een lijst met alle verwijderde bestanden.",
"apihelp-query+fileusage-paramvalue-prop-pageid": "Pagina-ID van elke pagina.",
"apihelp-query+fileusage-paramvalue-prop-title": "Titel van elke pagina.",
+ "apihelp-query+imageinfo-paramvalue-prop-mediatype": "Voegt het mediatype van het bestand toe.",
"apihelp-query+imageusage-param-namespace": "De door te lopen naamruimte.",
"apihelp-query+imageusage-example-simple": "Toon pagina's die [[:File:Albert Einstein Head.jpg]] gebruiken.",
"apihelp-query+imageusage-example-generator": "Toon informatie over pagina's die [[:File:Albert Einstein Head.jpg]] gebruiken.",
"apihelp-query+iwbacklinks-param-prefix": "Voorvoegsel voor de interwiki.",
+ "apihelp-query+iwlinks-paramvalue-prop-url": "Voegt de volledige URL toe.",
"apihelp-query+langbacklinks-example-simple": "Toon de pagina's die verwijzen naar [[:fr:Test]].",
+ "apihelp-query+langlinks-paramvalue-prop-url": "Voegt de volledige URL toe.",
"apihelp-query+linkshere-paramvalue-prop-pageid": "Pagina-ID van elke pagina.",
"apihelp-query+linkshere-paramvalue-prop-title": "Titel van elke pagina.",
"apihelp-query+linkshere-param-namespace": "Toon alleen pagina's in deze naamruimten.",
const LOCK_TTL = 30;
/**
- * Process local cache of loaded messages that are defined in
- * MediaWiki namespace. First array level is a language code,
- * second level is message key and the values are either message
- * content prefixed with space, or !NONEXISTENT for negative
- * caching.
- * @var array $mCache
+ * Process cache of loaded messages that are defined in MediaWiki namespace
+ *
+ * @var MapCacheLRU Map of (language code => key => " <MESSAGE>" or "!TOO BIG")
*/
- protected $mCache;
+ protected $cache;
/**
* @var bool[] Map of (language code => boolean)
/** @var Parser */
protected $mParser;
- /**
- * Variable for tracking which variables are already loaded
- * @var array $mLoadedLanguages
- */
- protected $mLoadedLanguages = [];
-
/**
* @var bool $mInParser
*/
$this->clusterCache = $clusterCache;
$this->srvCache = $serverCache;
+ $this->cache = new MapCacheLRU( 5 ); // limit size for sanity
+
$this->mDisable = !$useDB;
$this->mExpiry = $expiry;
}
*
* @param string $code Language to which load messages
* @param int $mode Use MessageCache::FOR_UPDATE to skip process cache [optional]
- * @throws MWException
+ * @throws InvalidArgumentException
* @return bool
*/
protected function load( $code, $mode = null ) {
}
# Don't do double loading...
- if ( isset( $this->mLoadedLanguages[$code] ) && $mode != self::FOR_UPDATE ) {
+ if ( $this->cache->has( $code ) && $mode != self::FOR_UPDATE ) {
return true;
}
$staleCache = $cache;
} else {
$where[] = 'got from local cache';
+ $this->cache->set( $code, $cache );
$success = true;
- $this->mCache[$code] = $cache;
}
if ( !$success ) {
$staleCache = $cache;
} else {
$where[] = 'got from global cache';
- $this->mCache[$code] = $cache;
+ $this->cache->set( $code, $cache );
$this->saveToCaches( $cache, 'local-only', $code );
$success = true;
}
} elseif ( $staleCache ) {
# Use the stale cache while some other thread constructs the new one
$where[] = 'using stale cache';
- $this->mCache[$code] = $staleCache;
+ $this->cache->set( $code, $staleCache );
$success = true;
break;
} elseif ( $failedAttempts > 0 ) {
if ( !$success ) {
$where[] = 'loading FAILED - cache is disabled';
$this->mDisable = true;
- $this->mCache = false;
+ $this->cache->set( $code, null );
wfDebugLog( 'MessageCacheError', __METHOD__ . ": Failed to load $code\n" );
# This used to throw an exception, but that led to nasty side effects like
# the whole wiki being instantly down if the memcached server died
- } else {
- # All good, just record the success
- $this->mLoadedLanguages[$code] = true;
+ }
+
+ if ( !$this->cache->has( $code ) ) { // sanity
+ throw new LogicException( "Process cache for '$code' should be set by now." );
}
$info = implode( ', ', $where );
}
$cache = $this->loadFromDB( $code, $mode );
- $this->mCache[$code] = $cache;
+ $this->cache->set( $code, $cache );
$saveSuccess = $this->saveToCaches( $cache, 'all', $code );
if ( !$saveSuccess ) {
$mostused = [];
if ( $wgAdaptiveMessageCache && $code !== $wgLanguageCode ) {
- if ( !isset( $this->mCache[$wgLanguageCode] ) ) {
+ if ( !$this->cache->has( $wgLanguageCode ) ) {
$this->load( $wgLanguageCode );
}
- $mostused = array_keys( $this->mCache[$wgLanguageCode] );
+ $mostused = array_keys( $this->cache->get( $wgLanguageCode ) );
foreach ( $mostused as $key => $value ) {
$mostused[$key] = "$value/$code";
}
// (a) Update the process cache with the new message text
if ( $text === false ) {
// Page deleted
- $this->mCache[$code][$title] = '!NONEXISTENT';
+ $this->cache->setField( $code, $title, '!NONEXISTENT' );
} else {
// Ignore $wgMaxMsgCacheEntrySize so the process cache is up to date
- $this->mCache[$code][$title] = ' ' . $text;
+ $this->cache->setField( $code, $title, ' ' . $text );
}
// (b) Update the shared caches in a deferred update with a fresh DB snapshot
}
// Load the messages from the master DB to avoid race conditions
$cache = $this->loadFromDB( $code, self::FOR_UPDATE );
- $this->mCache[$code] = $cache;
- // Load the process cache values and set the per-title cache keys
+ // Check if an individual cache key should exist and update cache accordingly
$page = WikiPage::factory( Title::makeTitle( NS_MEDIAWIKI, $title ) );
$page->loadPageData( $page::READ_LATEST );
$text = $this->getMessageTextFromContent( $page->getContent() );
- // Check if an individual cache key should exist and update cache accordingly
if ( is_string( $text ) && strlen( $text ) > $wgMaxMsgCacheEntrySize ) {
- $titleKey = $this->bigMessageCacheKey( $this->mCache[$code]['HASH'], $title );
- $this->wanCache->set( $titleKey, ' ' . $text, $this->mExpiry );
+ $this->wanCache->set(
+ $this->bigMessageCacheKey( $cache['HASH'], $title ),
+ ' ' . $text,
+ $this->mExpiry
+ );
}
// Mark this cache as definitely being "latest" (non-volatile) so
- // load() calls do try to refresh the cache with replica DB data
- $this->mCache[$code]['LATEST'] = time();
+ // load() calls do not try to refresh the cache with replica DB data
+ $cache['LATEST'] = time();
+ // Update the process cache
+ $this->cache->set( $code, $cache );
// Pre-emptively update the local datacenter cache so things like edit filter and
// blacklist changes are reflected immediately; these often use MediaWiki: pages.
// The datacenter handling replace() calls should be the same one handling edits
// as they require HTTP POST.
- $this->saveToCaches( $this->mCache[$code], 'all', $code );
+ $this->saveToCaches( $cache, 'all', $code );
// Release the lock now that the cache is saved
ScopedCallback::consume( $scopedLock );
public function getMsgFromNamespace( $title, $code ) {
$this->load( $code );
- if ( isset( $this->mCache[$code][$title] ) ) {
- $entry = $this->mCache[$code][$title];
+ if ( $this->cache->hasField( $code, $title ) ) {
+ $entry = $this->cache->getField( $code, $title );
if ( substr( $entry, 0, 1 ) === ' ' ) {
// The message exists and is not '!TOO BIG'
return (string)substr( $entry, 1 );
$message = false;
Hooks::run( 'MessagesPreLoad', [ $title, &$message, $code ] );
if ( $message !== false ) {
- $this->mCache[$code][$title] = ' ' . $message;
+ $this->cache->setField( $code, $title, ' ' . $message );
} else {
- $this->mCache[$code][$title] = '!NONEXISTENT';
+ $this->cache->setField( $code, $title, '!NONEXISTENT' );
}
return $message;
}
// Individual message cache key
- $titleKey = $this->bigMessageCacheKey( $this->mCache[$code]['HASH'], $title );
+ $bigKey = $this->bigMessageCacheKey( $this->cache->getField( $code, 'HASH' ), $title );
if ( $this->mCacheVolatile[$code] ) {
$entry = false;
// Make sure that individual keys respect the WAN cache holdoff period too
LoggerFactory::getInstance( 'MessageCache' )->debug(
__METHOD__ . ': loading volatile key \'{titleKey}\'',
- [ 'titleKey' => $titleKey, 'code' => $code ] );
+ [ 'titleKey' => $bigKey, 'code' => $code ] );
} else {
// Try the individual message cache
- $entry = $this->wanCache->get( $titleKey );
+ $entry = $this->wanCache->get( $bigKey );
}
if ( $entry !== false ) {
if ( substr( $entry, 0, 1 ) === ' ' ) {
- $this->mCache[$code][$title] = $entry;
+ $this->cache->setField( $code, $title, $entry );
// The message exists, so make sure a string is returned
return (string)substr( $entry, 1 );
} elseif ( $entry === '!NONEXISTENT' ) {
- $this->mCache[$code][$title] = '!NONEXISTENT';
+ $this->cache->setField( $code, $title, '!NONEXISTENT' );
return false;
} else {
// Corrupt/obsolete entry, delete it
- $this->wanCache->delete( $titleKey );
+ $this->wanCache->delete( $bigKey );
}
}
if ( $content ) {
$message = $this->getMessageTextFromContent( $content );
if ( is_string( $message ) ) {
- $this->mCache[$code][$title] = ' ' . $message;
- $this->wanCache->set( $titleKey, ' ' . $message, $this->mExpiry, $cacheOpts );
+ $this->cache->setField( $code, $title, ' ' . $message );
+ $this->wanCache->set( $bigKey, ' ' . $message, $this->mExpiry, $cacheOpts );
}
} else {
// A possibly temporary loading failure
LoggerFactory::getInstance( 'MessageCache' )->warning(
__METHOD__ . ': failed to load message page text for \'{titleKey}\'',
- [ 'titleKey' => $titleKey, 'code' => $code ] );
+ [ 'titleKey' => $bigKey, 'code' => $code ] );
$message = null; // no negative caching
}
} else {
if ( $message === false ) {
// Negative caching in case a "too big" message is no longer available (deleted)
- $this->mCache[$code][$title] = '!NONEXISTENT';
- $this->wanCache->set( $titleKey, '!NONEXISTENT', $this->mExpiry, $cacheOpts );
+ $this->cache->setField( $code, $title, '!NONEXISTENT' );
+ $this->wanCache->set( $bigKey, '!NONEXISTENT', $this->mExpiry, $cacheOpts );
}
return $message;
*
* Mainly used after a mass rebuild
*/
- function clear() {
+ public function clear() {
$langs = Language::fetchLanguageNames( null, 'mw' );
foreach ( array_keys( $langs ) as $code ) {
$this->wanCache->touchCheckKey( $this->getCheckKey( $code ) );
}
-
- $this->mLoadedLanguages = [];
+ $this->cache->clear();
}
/**
global $wgContLang;
$this->load( $code );
- if ( !isset( $this->mCache[$code] ) ) {
+ if ( !$this->cache->has( $code ) ) {
// Apparently load() failed
return null;
}
// Remove administrative keys
- $cache = $this->mCache[$code];
+ $cache = $this->cache->get( $code );
unset( $cache['VERSION'] );
unset( $cache['EXPIRY'] );
unset( $cache['EXCESSIVE'] );
# works correctly across DB engines, we need to change the pre-
# fix back and forth so tableName() works right.
- self::changePrefix( $this->oldTablePrefix );
+ $this->db->tablePrefix( $this->oldTablePrefix );
$oldTableName = $this->db->tableName( $tbl, 'raw' );
- self::changePrefix( $this->newTablePrefix );
+ $this->db->tablePrefix( $this->newTablePrefix );
$newTableName = $this->db->tableName( $tbl, 'raw' );
// Postgres: Temp tables are automatically deleted upon end of session
*/
public function destroy( $dropTables = false ) {
if ( $dropTables ) {
- self::changePrefix( $this->newTablePrefix );
+ $this->db->tablePrefix( $this->newTablePrefix );
foreach ( $this->tablesToClone as $tbl ) {
$this->db->dropTable( $tbl );
}
}
- self::changePrefix( $this->oldTablePrefix );
+ $this->db->tablePrefix( $this->oldTablePrefix );
}
/**
static $externalBlobCache = [];
$cacheID = ( $itemID === false ) ? "$cluster/$id" : "$cluster/$id/";
+
+ $wiki = $this->params['wiki'] ?? false;
+ $cacheID = ( $wiki === false ) ? $cacheID : "$cacheID@$wiki";
+
if ( isset( $externalBlobCache[$cacheID] ) ) {
wfDebugLog( 'ExternalStoreDB-cache',
"ExternalStoreDB::fetchBlob cache hit on $cacheID" );
*/
public function destroy();
+ /**
+ * Get the local (and default) database domain ID of connection handles
+ *
+ * @see DatabaseDomain
+ * @return string Database domain ID; this specifies DB name, schema, and table prefix
+ * @since 1.32
+ */
+ public function getLocalDomainID();
+
+ /**
+ * @param DatabaseDomain|string|bool $domain Database domain
+ * @return string Value of $domain if provided or the local domain otherwise
+ * @since 1.32
+ */
+ public function resolveDomainID( $domain );
+
/**
* Create a new load balancer object. The resulting object will be untracked,
* not chronology-protected, and the caller is responsible for cleaning it up.
$this->forEachLBCallMethod( 'disable' );
}
+ public function getLocalDomainID() {
+ return $this->localDomain->getId();
+ }
+
+ public function resolveDomainID( $domain ) {
+ return ( $domain !== false ) ? (string)$domain : $this->getLocalDomainID();
+ }
+
public function shutdown(
$mode = self::SHUTDOWN_CHRONPROT_SYNC,
callable $workCallback = null,
*/
public function __construct( array $params );
+ /**
+ * Get the local (and default) database domain ID of connection handles
+ *
+ * @see DatabaseDomain
+ * @return string Database domain ID; this specifies DB name, schema, and table prefix
+ * @since 1.31
+ */
+ public function getLocalDomainID();
+
+ /**
+ * @param DatabaseDomain|string|bool $domain Database domain
+ * @return string Value of $domain if provided or the local domain otherwise
+ * @since 1.32
+ */
+ public function resolveDomainID( $domain );
+
/**
* Get the index of the reader connection, which may be a replica DB
*
$this->defaultGroup = $params['defaultGroup'] ?? null;
}
- /**
- * Get the local (and default) database domain ID of connection handles
- *
- * @see DatabaseDomain
- * @return string Database domain ID; this specifies DB name, schema, and table prefix
- * @since 1.31
- */
public function getLocalDomainID() {
return $this->localDomain->getId();
}
+ public function resolveDomainID( $domain ) {
+ return ( $domain !== false ) ? (string)$domain : $this->getLocalDomainID();
+ }
+
/**
* Get a LoadMonitor instance
*
}
public function getConnectionRef( $db, $groups = [], $domain = false, $flags = 0 ) {
- $domain = ( $domain !== false ) ? $domain : $this->localDomain;
+ $domain = $this->resolveDomainID( $domain );
return new DBConnRef( $this, $this->getConnection( $db, $groups, $domain, $flags ) );
}
public function getLazyConnectionRef( $db, $groups = [], $domain = false, $flags = 0 ) {
- $domain = ( $domain !== false ) ? $domain : $this->localDomain;
+ $domain = $this->resolveDomainID( $domain );
return new DBConnRef( $this, [ $db, $groups, $domain, $flags ] );
}
public function getMaintenanceConnectionRef( $db, $groups = [], $domain = false, $flags = 0 ) {
- $domain = ( $domain !== false ) ? $domain : $this->localDomain;
+ $domain = $this->resolveDomainID( $domain );
return new MaintainableDBConnRef(
$this, $this->getConnection( $db, $groups, $domain, $flags ) );
// Fetch all rows in case the DB needs that to properly lock them.
}
- // Get all of the page revisions
+ // If SCHEMA_COMPAT_WRITE_OLD is set, also select all extra fields we still write,
+ // so we can copy it to the archive table.
+ // We know the fields exist, otherwise SCHEMA_COMPAT_WRITE_OLD could not function.
+ if ( $wgMultiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
+ $revQuery['fields'][] = 'rev_text_id';
+
+ if ( $wgContentHandlerUseDB ) {
+ $revQuery['fields'][] = 'rev_content_model';
+ $revQuery['fields'][] = 'rev_content_format';
+ }
+ }
+
+ // Get all of the page revisions
$res = $dbw->select(
$revQuery['tables'],
$revQuery['fields'],
] + $commentStore->insert( $dbw, 'ar_comment', $comment )
+ $actorMigration->getInsertValues( $dbw, 'ar_user', $user );
- if ( $wgMultiContentRevisionSchemaMigrationStage < MIGRATION_NEW ) {
+ if ( $wgMultiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
$rowInsert['ar_text_id'] = $row->rev_text_id;
- }
- if (
- $wgContentHandlerUseDB &&
- $wgMultiContentRevisionSchemaMigrationStage <= MIGRATION_WRITE_BOTH
- ) {
- $rowInsert['ar_content_model'] = $row->rev_content_model;
- $rowInsert['ar_content_format'] = $row->rev_content_format;
+ if ( $wgContentHandlerUseDB ) {
+ $rowInsert['ar_content_model'] = $row->rev_content_model;
+ $rowInsert['ar_content_format'] = $row->rev_content_format;
+ }
}
+
$rowsInsert[] = $rowInsert;
$revids[] = $row->rev_id;
* @ingroup Search
*/
+use Wikimedia\Rdbms\IResultWrapper;
+
/**
* Search engine hook base class for Mssql (ConText).
* @ingroup Search
* @param int $id
* @param string $title
* @param string $text
- * @return bool|ResultWrapper
+ * @return bool|IResultWrapper
*/
function update( $id, $title, $text ) {
// We store the column data as UTF-8 byte order marked binary stream
*
* @param int $id
* @param string $title
- * @return bool|ResultWrapper
+ * @return bool|IResultWrapper
*/
function updateTitle( $id, $title ) {
$table = $this->db->tableName( 'searchindex' );
namespace MediaWiki\Session;
use Language;
+use Message;
/**
* This exists to make IDEs happy, so they don't see the
* @ingroup Pager
*/
use MediaWiki\MediaWikiServices;
+use Wikimedia\Rdbms\IDatabase;
use Wikimedia\Rdbms\IResultWrapper;
use Wikimedia\Rdbms\FakeResultWrapper;
"welcomecreation-msg": "Creóse la to cuenta.\nNun t'escaezas de camudar les tos [[Special:Preferences|preferencies de {{SITENAME}}]].",
"yourname": "Nome d'usuariu:",
"userlogin-yourname": "Nome d'usuariu",
- "userlogin-yourname-ph": "Escriba'l so nome d'usuariu",
- "createacct-another-username-ph": "Escriba'l nome d'usuariu",
+ "userlogin-yourname-ph": "Escribe'l to nome d'usuariu",
+ "createacct-another-username-ph": "Escribe'l nome d'usuariu",
"yourpassword": "Contraseña:",
"userlogin-yourpassword": "Contraseña",
- "userlogin-yourpassword-ph": "Escriba la so contraseña",
- "createacct-yourpassword-ph": "Escriba una contraseña",
+ "userlogin-yourpassword-ph": "Escribe la contraseña",
+ "createacct-yourpassword-ph": "Escribe una contraseña",
"yourpasswordagain": "Escribi otra vuelta la contraseña:",
"createacct-yourpasswordagain": "Confirmar la contraseña",
- "createacct-yourpasswordagain-ph": "Escriba nuevamente la contraseña",
+ "createacct-yourpasswordagain-ph": "Escribe nuevamente la contraseña",
"userlogin-remembermypassword": "Caltener abierta la sesión",
"userlogin-signwithsecure": "Usar una conexón segura",
"cannotlogin-title": "Nun pudo aniciase sesión",
"logout": "Salir",
"userlogout": "Salir",
"notloggedin": "Nun aniciasti sesión",
- "userlogin-noaccount": "¿Nun tien una cuenta?",
+ "userlogin-noaccount": "¿Nun tienes una cuenta?",
"userlogin-joinproject": "Xunise a {{SITENAME}}",
"createaccount": "Crear una cuenta",
- "userlogin-resetpassword-link": "¿Escaeció la contraseña?",
+ "userlogin-resetpassword-link": "¿Escaecisti la contraseña?",
"userlogin-helplink2": "Ayuda del aniciu de sesión",
"userlogin-loggedin": "Yá anició sesión como {{GENDER:$1|$1}}.\nUtilice'l formulariu de más abaxo p'aniciar sesión como otru usuariu.",
"userlogin-reauth": "Tienes d'aniciar sesión de nueves pa comprobar que yes {{GENDER:$1|$1}}.",
"userlogin-createanother": "Crear otra cuenta",
"createacct-emailrequired": "Direición de corréu electrónicu",
"createacct-emailoptional": "Direición de corréu electrónicu (opcional)",
- "createacct-email-ph": "Escriba la so direición de corréu electrónicu",
- "createacct-another-email-ph": "Escriba la direición de corréu electrónicu",
+ "createacct-email-ph": "Escribe la to direición de corréu electrónicu",
+ "createacct-another-email-ph": "Escribe la direición de corréu electrónicu",
"createaccountmail": "Usar una contraseña al debalu temporal y unviala a la direición de corréu electrónicu conseñada",
"createaccountmail-help": "Pue usase pa crear una cuenta pa otra persona ensin saber la contraseña.",
"createacct-realname": "Nome real (opcional)",
"createacct-reason": "Motivu",
- "createacct-reason-ph": "Por qué quier crear otra cuenta",
+ "createacct-reason-ph": "Por qué vas crear otra cuenta",
"createacct-reason-help": "Mensaxe que s'amuesa nel rexistru de creación de cuentes",
"createacct-submit": "Crear la cuenta",
"createacct-another-submit": "Crear una cuenta",
"createacct-continue-submit": "Siguir cola creación de la cuenta",
"createacct-another-continue-submit": "Siguir cola creación de la cuenta",
- "createacct-benefit-heading": "{{SITENAME}} failu xente como vusté.",
+ "createacct-benefit-heading": "Persones como tu son les que construyen {{SITENAME}}.",
"createacct-benefit-body1": "{{PLURAL:$1|edición|ediciones}}",
"createacct-benefit-body2": "{{PLURAL:$1|páxina|páxines}}",
"createacct-benefit-body3": "{{PLURAL:$1|collaborador|collaboradores}} de recién",
"userexists": "El nome d'usuariu conseñáu yá ta usándose.\nPor favor escueyi un nome diferente.",
"loginerror": "Error d'aniciu de sesión",
"createacct-error": "Error de creación de cuenta",
- "createaccounterror": "Nun se pudo crear la cuenta: $1",
+ "createaccounterror": "Nun pudo crease la cuenta: $1",
"nocookiesnew": "La cuenta d'usuariu ta creada, pero nun aniciasti sesión.\n{{SITENAME}} usa «cookies» pa identificar a los usuarios.\nTienes les «cookies» desactivaes.\nPor favor actívales y anicia sesión col nuevu nome d'usuariu y contraseña.",
- "nocookieslogin": "{{SITENAME}} usa «cookies» pa identificar a los usuarios.\nTien les «cookies» desactivaes.\nPor favor activeles y vuelva a intentalo.",
- "nocookiesfornew": "La cuenta nun se creó porque nun pudimos confirmar l'orixe.\nComprueba que tienes activaes les «cookies», recarga esta páxina y vuelvi a intentalo.",
+ "nocookieslogin": "{{SITENAME}} usa «cookies» pa identificar a los usuarios.\nTien les «cookies» desactivaes.\nPor favor actívales y tenta otra vuelta.",
+ "nocookiesfornew": "La cuenta nun se creó porque nun pudimos confirmar l'orixe.\nComprueba que tienes activaes les «cookies», recarga esta páxina y tenta otra vuelta.",
"createacct-loginerror": "La cuenta creóse correchamente, pero nun pudo aniciase sesión automáticamente. Sigui col [[Special:UserLogin|accesu manual]].",
"noname": "Nun conseñasti un nome d'usuariu válidu.",
"loginsuccesstitle": "Identificáu",
"nouserspecified": "Has d'especificar un nome d'usuariu.",
"login-userblocked": "Esti usuariu ta bloquiáu. Nun se permite l'aniciu de sesión.",
"wrongpassword": "Escribisti un nome d'usuariu o contraseña incorreutu.\nTenta otra vuelta.",
- "wrongpasswordempty": "La contraseña taba en blanco.\nVuelvi a intentalo.",
+ "wrongpasswordempty": "La contraseña taba en blancu.\nTenta otra vuelta.",
"passwordtooshort": "Les contraseñes han de tener polo menos {{PLURAL:$1|1 caráuter|$1 caráuteres}}.",
"passwordtoolong": "Les contraseñes nun puen ser mayores de {{PLURAL:$1|1 caráuter|$1 caráuteres}}.",
"passwordtoopopular": "Les contraseñes más escoyíes de vezu nun pueden usase. Escueye una contraseña más difícil d'aldovinar.",
"throttled-mailpassword": "Yá s'unvió un corréu de reaniciu la clave {{PLURAL:$1|na postrer hora|nes postreres $1 hores}}.\nPa evitar abusos, namái s'unviará un corréu de reaniciu cada {{PLURAL:$1|hora|$1 hores}}.",
"mailerror": "Fallu al unviar el corréu: $1",
"acct_creation_throttle_hit": "Los visitantes d'esta wiki qu'usen la to direición IP yá crearon {{PLURAL:$1|1 cuenta|$1 cuentes}} nel periodu de $2, que ye'l máximu almitíu nesi tiempu.\nPoro, los visitantes qu'usen esta direición IP nun pueden crear más cuentes pol momentu.",
- "emailauthenticated": "La so direición de corréu electrónicu confirmóse'l $2 a les $3.",
- "emailnotauthenticated": "La so direición de corréu electrónicu inda nun se confirmó.\nNun s'unviará corréu pa nenguna de les funciones siguientes.",
+ "emailauthenticated": "La direición de corréu electrónicu confirmóse'l $2 a les $3.",
+ "emailnotauthenticated": "La direición de corréu electrónicu inda nun se confirmó.\nNun s'unviará corréu pa nenguna de les funciones siguientes.",
"noemailprefs": "Conseña una direición de corréu electrónicu nes tos preferencies pa que funcionen eses carauterístiques.",
"emailconfirmlink": "Confirmar la direición de corréu electrónicu",
"invalidemailaddress": "La direición de corréu electrónicu nun pue aceutase yá que paez tener un formatu inválidu.\nPor favor conseña una direición con formatu afayadizu o dexa baleru'l campu.",
"pt-createaccount": "Crear una cuenta",
"pt-userlogout": "Salir",
"php-mail-error-unknown": "Fallu desconocíu na función mail() de PHP.",
- "user-mail-no-addy": "Intentasti unviar un corréu electrónicu ensin direición de corréu.",
- "user-mail-no-body": "Trató d'unviar un corréu electrónicu con un cuerpu baleru o curtiu enforma.",
+ "user-mail-no-addy": "Tentasti unviar un corréu electrónicu ensin direición de corréu.",
+ "user-mail-no-body": "Tentasti unviar un corréu electrónicu col cuerpu vaciu o curtiu enforma.",
"changepassword": "Camudar la contraseña",
- "resetpass_announce": "P'acabar d'aniciar sesión, tien de definir equí una contraseña nueva.",
+ "resetpass_announce": "P'acabar d'aniciar sesión, tienes de configurar una contraseña nueva.",
"resetpass_text": "<!-- Amestar testu equí -->",
"resetpass_header": "Camudar la contraseña de la cuenta",
"oldpassword": "Contraseña antigua:",
"retypenew": "Vuelvi a escribir la contraseña nueva:",
"resetpass_submit": "Configurar la contraseña y aniciar sesión",
"changepassword-success": "Camudóse la contraseña.",
- "changepassword-throttled": "Ficisti demasiaos intentos d'aniciu de sesión recientes.\nPor favor espera $1 enantes d'intentalo otra vuelta.",
+ "changepassword-throttled": "Ficisti demasiaos intentos d'aniciar sesión de recien.\nPor favor espera $1 enantes de tentar otra vuelta.",
"botpasswords": "Contraseñes de bots",
"botpasswords-summary": "Les <em>contraseñes de bot</em> permiten l'accesu a una cuenta d'usuariu por aciu de la API sin usar les credenciales d'accesu de la cuenta principal. Los permisos d'usuariu disponibles al aniciar sesión con una contraseña de bot puen tar torgaos.\n\nSi nun sabes pa qué val esto, probablemente nun tendríes d'usalo. Naide tendría de pidite nunca que xeneres una d'estes y que-y la deas.",
"botpasswords-disabled": "Les contraseñes de bot tán desactivaes.",
"botpasswords-label-delete": "Desaniciar",
"botpasswords-label-resetpassword": "Reestablecer la contraseña",
"botpasswords-label-grants": "Permisos aplicables:",
- "botpasswords-help-grants": "Los permisos dan accesu a los permisos d'usuariu que yá tenga la cuenta. Activar un permisu equí nun da accesu a nengún permisu que la to cuenta nun tenga d'otra miente. Mira la [[Special:ListGrants|tabla de permisos]] pa más información.",
+ "botpasswords-help-grants": "Los permisos dan accesu a los permisos d'usuariu que yá tengas na cuenta. Activar un permisu equí nun da accesu a nengún permisu que la to cuenta nun tenga d'otra miente. Mira la [[Special:ListGrants|tabla de permisos]] pa más información.",
"botpasswords-label-grants-column": "Permitío",
"botpasswords-bad-appid": "El nome del bot \"$1\" nun ye válidu.",
"botpasswords-insert-failed": "Nun pudo amestase'l nome de bot «$1». ¿Taba añadíu yá?",
"resetpass-temp-emailed": "Anició sesión con un códigu temporal unviáu per corréu electrónicu.\nPa completar l'aniciu de sesión, tien de definir una nueva contraseña equí:",
"resetpass-temp-password": "Contraseña temporal:",
"resetpass-abort-generic": "Una estensión encaboxó'l cambiu de la contraseña.",
- "resetpass-expired": "La so contraseña caducó. Defina una nueva contraseña p'aniciar sesión.",
+ "resetpass-expired": "La to contraseña caducó. Configura una nueva contraseña p'aniciar sesión.",
"resetpass-expired-soft": "La contraseña caducó y precisa cambiase. Escueye agora una contraseña nueva, o pulsia «{{int:authprovider-resetpass-skip-label}}» pa cambiala sero.",
"resetpass-validity-soft": "La contraseña nun ye válida: $1\n\nEscueye agora una contraseña nueva, o pulsia «{{int:authprovider-resetpass-skip-label}}» pa cambiala sero.",
"passwordreset": "Reaniciar contraseña",
- "passwordreset-text-one": "Complete esti formulariu pa reaniciar la contraseña.",
- "passwordreset-text-many": "{{PLURAL:$1|Rellene unu de los campos pa recibir una contraseña temporal per corréu.}}",
+ "passwordreset-text-one": "Completa esti formulariu pa recibir per corréu una contraseña temporal.",
+ "passwordreset-text-many": "{{PLURAL:$1|Rellena unu de los campos pa recibir una contraseña temporal per corréu.}}",
"passwordreset-disabled": "Los reanicios de contraseña tán desactivaos nesta wiki.",
"passwordreset-emaildisabled": "Les funciones de corréu electrónicu tan desactivaes nesta wiki.",
"passwordreset-username": "Nome d'usuariu:",
"passwordreset-domain": "Dominiu:",
"passwordreset-email": "Direición de corréu electrónicu:",
"passwordreset-emailtitle": "Detalles de la cuenta en {{SITENAME}}",
- "passwordreset-emailtext-ip": "Dalguién (seique vusté, dende la direición IP $1)solicitó'l reaniciu de la so contraseña de {{SITENAME}} ($4).\n{{PLURAL:$3|La cuenta d'usuariu siguiente ta asociada|Les cuentes d'usuariu siguientes tán asociaes}}\na esta direición de corréu electrónicu:\n\n$2\n\n{{PLURAL:$3|Esta contraseña provisional caduca|Estes contraseñes provisionales caduquen}} {{PLURAL:$5|nun día|en $5 díes}}.\nTendría d'aniciar sesión y escoyer una contraseña nueva agora. Si esta solicitú la fizo otra persona,\no si recordó la clave orixinal y yá nun quier camudala, pue escaecer esti mensaxe y siguir\nusando la contraseña antigua.",
- "passwordreset-emailtext-user": "L'usuariu $1 de {{SITENAME}} solicitó un reaniciu de la so contraseña de {{SITENAME}} ($4). {{PLURAL:$3|La cuenta d'usuariu siguiente ta asociada|Les cuentes d'usuariu siguientes tán asociaes}} con esta direición de corréu electrónicu:\n\n$2\n\n{{PLURAL:$3|Esta contraseña provisional caduca|Estes contraseñes provisionales caduquen}} {{PLURAL:$5|nun día|en $5 díes}}.\nTendría d'aniciar sesión y escoyer una contraseña nueva agora. Si esta solicitú la fizo otra persona, o si recordó la clave orixinal y yá nun quier camudala, pue escaecer esti mensaxe y siguir usando la contraseña antigua.",
+ "passwordreset-emailtext-ip": "Dalguién (seique tu, dende la direición IP $1)solicitó'l reaniciu de la contraseña de {{SITENAME}} ($4).\n{{PLURAL:$3|La cuenta d'usuariu siguiente ta asociada|Les cuentes d'usuariu siguientes tán asociaes}}\na esta direición de corréu electrónicu:\n\n$2\n\n{{PLURAL:$3|Esta contraseña provisional caduca|Estes contraseñes provisionales caduquen}} {{PLURAL:$5|nun día|en $5 díes}}.\nTendríes d'aniciar sesión y escoyer una contraseña nueva agora. Si esta solicitú ye d'otra persona,\no si recordasti la clave orixinal y yá nun quies camudala, inora esti mensaxe y sigue\nusando la contraseña antigua.",
+ "passwordreset-emailtext-user": "L'usuariu $1 de {{SITENAME}} solicitó un reaniciu de la contraseña de {{SITENAME}} ($4). {{PLURAL:$3|La cuenta d'usuariu siguiente ta asociada|Les cuentes d'usuariu siguientes tán asociaes}} con esta direición de corréu electrónicu:\n\n$2\n\n{{PLURAL:$3|Esta contraseña provisional caduca|Estes contraseñes provisionales caduquen}} {{PLURAL:$5|nun día|en $5 díes}}.\nTendríes d'aniciar sesión y escoyer una contraseña nueva agora. Si esta solicitú ye d'otra persona, o si recordasti la clave orixinal y yá nun quies camudala, pues inorar esti mensaxe y siguir usando la contraseña antigua.",
"passwordreset-emailelement": "Nome d'usuariu: \n$1\n\nContraseña temporal: \n$2",
"passwordreset-emailsentemail": "Si esta direición de corréu electrónicu ta asociada cola to cuenta, unviaráse un corréu pa reaniciar la contraseña.",
"passwordreset-emailsentusername": "Si hai una direición de corréu electrónicu asociada con esti nome d'usuariu, unviaráse un corréu electrónicu pa reaniciar la contraseña.",
"fileexists-no-change": "Гэтая загрузка зьяўляецца дакладнай копіяй цяперашняй вэрсіі <strong>[[:$1]]</strong>.",
"fileexists-duplicate-version": "Гэтая загрузка зьяўляецца дакладнай копіяй {{PLURAL:$2|1=старой вэрсіі|старых вэрсіяў}} файлу <strong>[[:$1]]</strong>.",
"file-exists-duplicate": "Гэты файл дублюе {{PLURAL:$1|1=наступны файл|наступныя файлы}}:",
- "file-deleted-duplicate": "Ð\9fадобнÑ\8b Ñ\84айл ([[:$1]]) Ñ\83жо вÑ\8bдалÑ\8fÑ\9eÑ\81Ñ\8f. Ð\9aалÑ\96 лаÑ\81ка, паглÑ\8fдзÑ\96Ñ\86е гÑ\96Ñ\81Ñ\82оÑ\80Ñ\8bÑ\8e вÑ\8bдаленÑ\8cнÑ\8fÑ\9e гÑ\8dÑ\82ага Ñ\84айла перад яго паўторнай загрузкай.",
+ "file-deleted-duplicate": "Ð\86дÑ\8dнÑ\82Ñ\8bÑ\87нÑ\8b Ñ\84айл ([[:$1]]) Ñ\83жо Ñ\80аней вÑ\8bдалÑ\8fÑ\9eÑ\81Ñ\8f. Ð\92ам Ñ\82Ñ\80Ñ\8dба паглÑ\8fдзеÑ\86Ñ\8c гÑ\96Ñ\81Ñ\82оÑ\80Ñ\8bÑ\8e вÑ\8bдаленÑ\8cнÑ\8fÑ\9e гÑ\8dÑ\82ага Ñ\84айлÑ\83 перад яго паўторнай загрузкай.",
"file-deleted-duplicate-notitle": "Файл, ідэнтычны гэтаму файлу, раней ужо быў выдалены, а назва файла была забароненая.\nВам трэба зьвярнуцца да некага з правамі прагляду зьвестак забароненых файлаў, каб прааналізаваць сытуацыю перад тым, як загружаць файл ізноў.",
"uploadwarning": "Папярэджаньне",
"uploadwarning-text": "Калі ласка, зьмяніце апісаньне файла ніжэй і паспрабуйце ізноў.",
"confirm-unwatch-top": "Voleu treure aquesta pàgina de la llista de seguiment?",
"confirm-rollback-button": "D'acord",
"confirm-rollback-top": "Voleu revertir les modificacions a la pàgina?",
+ "colon-separator": ": ",
"quotation-marks": "«$1»",
"imgmultipageprev": "← pàgina anterior",
"imgmultipagenext": "pàgina següent →",
"rcfilters-view-tags-help-icon-tooltip": "زیاتر بزانە لەسەر دەستکارییە تاگکراوەکان",
"rcfilters-liveupdates-button": "نوێکردنەوەی زیندوو",
"rcnotefrom": "ژێرەوە {{PLURAL:$5|گۆڕانکارییەکەیە|گۆڕانکارییەکانە}} لە <strong>$3، $4</strong>ەوە (ھەتا <strong>$1</strong> نیشان دراوە).",
+ "rclistfromreset": "گەڕاندنەوەی ھەڵبژاردەی بەروار",
"rclistfrom": "گۆڕانکارییە نوێکان نیشان بدە بە دەستپێکردن لە $3 $2",
"rcshowhideminor": "دەستکارییە بچووکەکان $1",
"rcshowhideminor-show": "نیشان بدە",
"Alp Er Tunqa",
"Baloch Khan",
"Fitoschido",
- "Alireza Ivaz"
+ "Alireza Ivaz",
+ "Iriman"
]
},
"tog-underline": "خط کشیدن زیر پیوندها:",
"filehist-dimensions": "ابعاد",
"filehist-filesize": "اندازهٔ پرونده",
"filehist-comment": "توضیح",
- "imagelinks": "بهکاررفتن پرونده",
+ "imagelinks": "کاربرد پرونده",
"linkstoimage": "{{PLURAL:$1|صفحهٔ|صفحههای}} زیر به این تصویر پیوند {{PLURAL:$1|دارد|دارند}}:",
"linkstoimage-more": "بیش از $1 صفحه به این پرونده پیوند {{PLURAL:$1|دارد|دارند}}.\nفهرست زیر تنها {{PLURAL:$1|اولین پیوند|اولین $1 پیوند}} به این صفحه را نشان میدهد.\n[[Special:WhatLinksHere/$2|فهرست کامل]] نیز موجود است.",
"nolinkstoimage": "این پرونده در هیچ صفحهای به کار نرفتهاست.",
"nav-login-createaccount": "Oanmelde",
"logout": "Ofmelde",
"userlogout": "Ofmelde",
- "notloggedin": "Net oanmelde",
+ "notloggedin": "Net oanmeld",
"userlogin-noaccount": "Hasto gjin akkount?",
"userlogin-joinproject": "Meidwaan {{SITENAME}}",
"createaccount": "Registrearje",
"upload": "Bied triem oan",
"uploadbtn": "Bied triem oan",
"reuploaddesc": "Werom nei oanbied-side.",
- "uploadnologin": "Net oanmelde",
+ "uploadnologin": "Net oanmeld",
"uploadnologintext": "Jo moatte [[Special:UserLogin|oanmeld]] wêze om in triem oanbiede te kinnen.",
"upload_directory_missing": "De heechlaadmap ($1) is der net en koe net oanmakke wurde troch de webserver.",
"upload_directory_read_only": "De webserver kin net skriuwe yn de oanbiedpad ($1).",
"rcfilters-other-review-tools": "Ostali alati za pregledavanje:",
"rcfilters-group-results-by-page": "Grupiranje rezultata po stranici",
"rcfilters-activefilters": "Aktivni filtri",
+ "rcfilters-activefilters-hide": "Skrij",
+ "rcfilters-activefilters-show": "Pokaži",
"rcfilters-advancedfilters": "Napredni filtri",
"rcfilters-limit-title": "Rezultata za prikaz",
"rcfilters-limit-and-date-label": "{{PLURAL:$1|$1 izmjena|$1 izmjene|$1 izmjena}}, $2",
"rcfilters-activefilters": "Aktív szűrők",
"rcfilters-activefilters-hide": "Elrejt",
"rcfilters-activefilters-show": "Mutat",
+ "rcfilters-activefilters-hide-tooltip": "Aktív szűrők dobozának elrejtése",
+ "rcfilters-activefilters-show-tooltip": "Aktív szűrők dobozának megjelenítése",
"rcfilters-advancedfilters": "Haladó szűrők",
"rcfilters-limit-title": "Megjelenítendő találatok száma",
"rcfilters-limit-and-date-label": "$1 változtatás, $2",
"resetpass-submit-loggedin": "Cambiar contrasigno",
"resetpass-submit-cancel": "Cancellar",
"resetpass-wrong-oldpass": "Le contrasigno temporari o actual es invalide.\nEs possibile que tu ha ja cambiate tu contrasigno o requestate un nove contrasigno temporari.",
- "resetpass-recycled": "Redefini tu contrasigno a un differente del actual, per favor.",
+ "resetpass-recycled": "Cambia tu contrasigno a un differente del actual, per favor.",
"resetpass-temp-emailed": "Tu ha aperite session con un codice temporari que tu recipeva in e-mail.\nPro completar le accesso, tu debe definir un nove contrasigno hic:",
"resetpass-temp-password": "Contrasigno temporari:",
"resetpass-abort-generic": "Le cambio del contrasigno ha essite abortate per un extension.",
"resetpass-expired": "Le contrasigno ha expirate. Per favor defini un nove contrasigno pro aperir session.",
- "resetpass-expired-soft": "Le contrasigno ha expirate e debe esser redefinite. Per favor elige un nove contrasigno ora, o clicca sur \"{{int:authprovider-resetpass-skip-label}}\" pro redefinir lo plus tarde.",
- "resetpass-validity-soft": "Le contrasigno non es valide: $1\n\nPer favor elige un nove contrasigno ora, o clicca sur \"{{int:authprovider-resetpass-skip-label}}\" pro redefinir lo plus tarde.",
+ "resetpass-expired-soft": "Le contrasigno ha expirate e debe esser cambiate. Per favor, elige un nove contrasigno ora, o clicca sur \"{{int:authprovider-resetpass-skip-label}}\" pro cambiar lo plus tarde.",
+ "resetpass-validity-soft": "Le contrasigno non es valide: $1\n\nPer favor, elige un nove contrasigno ora, o clicca sur \"{{int:authprovider-resetpass-skip-label}}\" pro cambiar lo plus tarde.",
"passwordreset": "Reinitialisar contrasigno",
"passwordreset-text-one": "Completa iste formulario pro reinitialisar tu contrasigno.",
"passwordreset-text-many": "{{PLURAL:$1|Completa un de iste campos pro reciper un contrasigno temporari in e-mail.}}",
"converter-manual-rule-error": "Error detegite in le regula manual de conversion de lingua",
"undo-success": "Le modification pote esser disfacite.\nPer favor controla le comparation infra pro verificar que tu vole facer isto, e postea salveguarda le modificationes infra pro assi disfacer le modification.",
"undo-failure": "Le modification non poteva esser annullate a causa de conflicto con modificationes intermedie.",
+ "undo-main-slot-only": "Le modification non poteva esser disfacite perque illo implica contento foras del cannellatura principal.",
"undo-norev": "Impossibile annullar le modification proque illo non existe o esseva delite.",
"undo-nochange": "Pare que iste modification ha jam essite disfacite.",
"undo-summary": "Annullava le version $1 per [[Special:Contributions/$2|$2]] ([[User talk:$2|Discussion]] | [[Special:Contributions/$2|{{MediaWiki:Contribslink}}]])",
"rcfilters-other-review-tools": "Altere instrumentos de revision",
"rcfilters-group-results-by-page": "Gruppar resultatos per pagina",
"rcfilters-activefilters": "Filtros active",
+ "rcfilters-activefilters-hide": "Celar",
+ "rcfilters-activefilters-show": "Monstrar",
+ "rcfilters-activefilters-hide-tooltip": "Celar le area de filtros active",
+ "rcfilters-activefilters-show-tooltip": "Monstrar le area de filtros active",
"rcfilters-advancedfilters": "Filtros avantiate",
"rcfilters-limit-title": "Resultatos a monstrar",
"rcfilters-limit-and-date-label": "$1 modification{{PLURAL:$1||es}}, $2",
"rcfilters-savedqueries-rename": "Renominar",
"rcfilters-savedqueries-setdefault": "Predefinir",
"rcfilters-savedqueries-unsetdefault": "Remover predefinition",
- "rcfilters-savedqueries-remove": "Remover",
+ "rcfilters-savedqueries-remove": "Deler",
"rcfilters-savedqueries-new-name-label": "Nomine",
"rcfilters-savedqueries-new-name-placeholder": "Describe le proposito del filtro",
"rcfilters-savedqueries-apply-label": "Crear filtro",
"rcfilters-empty-filter": "Nulle filtro active. Tote le contributiones es monstrate.",
"rcfilters-filterlist-title": "Filtros",
"rcfilters-filterlist-whatsthis": "Como functiona istes?",
- "rcfilters-filterlist-feedbacklink": "Da nos tu opinion sur iste (nove) instrumentos de filtrage",
+ "rcfilters-filterlist-feedbacklink": "Da nos tu opinion sur iste instrumentos de filtrage",
"rcfilters-highlightbutton-title": "Colorar le resultatos",
"rcfilters-highlightmenu-title": "Selige un color",
"rcfilters-highlightmenu-help": "Selige un color pro illuminar iste proprietate",
"cascadeprotected": "Ankehitriny dia voaaro ity pejy ity satria misy pejy voaaro {{PLURAL:$1|iray|$1}} mampiasa ity pejy ity. Io pejy io dia mampiasa ny fiarovana \"mirihana\":\n\n$2",
"namespaceprotected": "Tsy manana alalàna manova ny toeran'anarana « '''$1''' » ianao.",
"customcssprotected": "Tsy afaka manova ity pejy CSS ity ianao satria misy ny safidy manokan'ny mpikambana hafa.",
+ "customjsonprotected": "Tsy manana alalalana manova ity pejy JSON ity ianao satria misy ny safidin'olon-kafa izy ity.",
"customjsprotected": "Tsy afaka manova ity pejy JavaScript ity inaao satria misy ny safidin'ny mpikambana hafa.",
"mycustomcssprotected": "Tsy manana ny alalana ahafahana manova ity pejy CSS ity ianao.",
+ "mycustomjsonprotected": "Tsy manana alalana manova ity pejy JSON ity ianao.",
"mycustomjsprotected": "Tsy manana ny alalana ahafahana manova ity pejy JavaScript ity ianao.",
"myprivateinfoprotected": "Tsy manana alalana ahafahana manova ny fampahalalana tsy sarababem-bahoakanao ianao.",
"mypreferencesprotected": "Tsy manana alalana ahafahana manova ny safidinao ianao.",
"changepassword-success": "Voaova soa aman-tsara ny tenimiafinao!",
"changepassword-throttled": "Betsaka loatra ny andram-pidirana nataonao.\nAndraso $1 aloha ny mamerina.",
"botpasswords": "Tenimiafin-drôbô",
+ "botpasswords-summary": "Ny <em>Tenimiafina rôbô</em> dia manome alalana ny mampiasa ny kaontim-pikambana iray amin'ny alalana API ka tsy mila mampiasa ny tenimiafin'ny mpikambana. Ny zom-pikambana ananana rehefa tafiditra amin'ny alalana tenimiafina rôbô dia mety ho voafetra.\n\nRaha tsy fantatrao ny antony hanaovanao izany dia tsy tokony hanao izany ianao. Tsy tokony hisy ny olona hangataka anao hamoaka iray amin'itony ary hanome azy.",
"botpasswords-disabled": "Tsy ampiasaina ny tenimiafin-drôbô.",
+ "botpasswords-no-central-id": "Raha hampiasa tenimiafina rôbô dia tsy maintsy tafiditra anaty kaonty iombonan-tsehatra ianao.",
"botpasswords-existing": "Tenimiafin-drôbô efa misy",
"botpasswords-createnew": "Hamorona tenimiafina rôbô vaovao",
"botpasswords-editexisting": "Hanova tenimiafina rôbô efa misy",
+ "botpasswords-label-needsreset": "(mila famerenana ny tenimiafina)",
"botpasswords-label-appid": "Anarana rôbô:",
"botpasswords-label-create": "Foronina",
"botpasswords-label-update": "Vaozina",
"savechanges": "Hitahiry ny fiovana",
"publishpage": "Hamoaka pejy",
"publishchanges": "Hamoaka ny fiovana",
+ "savearticle-start": "Hitahiry pejy...",
+ "savechanges-start": "Mitahiry ny fiovana...",
+ "publishpage-start": "Hamoaka ny pejy...",
+ "publishchanges-start": "Hamoaka ny fiovana...",
"preview": "Topi-maso",
"showpreview": "Asehoy aloha",
"showdiff": "Asehoy ny fiovana",
"anoneditwarning": "<strong>Fampitandremana :</strong> Tsy niditra tamin'ny kaontinao ianao. Ho hitan'ny vahoaka ny adiresy IP-nao raha manova inona na inona ianao. Raha <strong>[$1 miditra amin'ny kaontinao]</strong> ianao dia ho anisan'ny tombontsoa anananao ny fanaovana ny fiovana amin'ny solonanaranao.",
"anonpreviewwarning": "''Tsy niditra ianao. Hampitahiry ny adiresy IP anao ao amin'ny tantaram-panovan'ity pejy ity ny fitehirizana ny fanovana.''",
"missingsummary": "'''Hafatra fampantsiahivana''' : tsy mbola nanome ny ambangovangom-panovanao ianao.\nRaha mbola tsindriano fanindroany eo amin'ny bokotra $1, ho voatahiry tsy fanambarana ny fanovanao.",
- "missingcommenttext": "Ampidiro ny ambangovangony azafady.",
+ "missingcommenttext": "Mampidira ambangovangony azafady.",
"missingcommentheader": "<strong>Fampatsiahivana: </strong> Tsy nampiditra lohahevitra ho an'ity hafatra ity ianao. Raha tsindrianao fanindroany \"$1\" dia ho tehirizina tsy misy lohahevitra ny hafatrao.",
"summary-preview": "Topi-mason'ny ambangovangom-panovana :",
"subject-preview": "Topi-mason-dohahevitra:",
"createaccounterror": "$1 ꯑꯦꯀꯥꯎꯟ ꯁꯥꯕꯥ ꯌꯥꯗꯔꯦ",
"nocookiesnew": "The user account was created, but you are not logged in.\n{{SITENAME}} uses cookies to log in users.\nYou have cookies disabled.\nPlease enable them, then log in with your new username and password.",
"nocookieslogin": "{{SITENAME}} uses cookies to log in users.\nYou have cookies disabled.\nPlease enable them and try again.",
+ "loginsuccesstitle": "ꯂꯣꯒ ꯏꯟ",
"login-userblocked": "ꯃꯁꯤꯒꯤ ꯁꯤꯖꯤꯟꯅꯔꯤꯕꯥꯁꯤ ꯊꯤꯡꯖꯤꯟꯈꯔꯦ? ꯂꯣꯒ ꯏꯟ ꯌꯥꯔꯥꯔꯣꯏ",
"passwordtooshort": "ꯄꯥꯁꯋꯔꯇ ꯁꯤ ꯌꯥꯝꯗꯔꯕꯗꯥ ꯃꯁꯤ ꯈꯔꯥꯁꯤ ꯌꯥꯎꯒꯗꯕꯅꯤ {{PLURAL:$1|1 character|$1 characters}}.",
"mailmypassword": "ꯄꯥꯁꯋ꯭ꯇ ꯁꯦꯝꯗꯣꯛꯄꯥ",
"pt-login-continue-button": "ꯂꯣꯘ ꯏꯟ ꯃꯈꯥ ꯆꯠꯊꯧ",
"pt-createaccount": "ꯑꯩꯒꯤ ꯑꯣꯏꯕꯥ ꯑꯃꯥ ꯁꯦꯝꯕꯥ",
"pt-userlogout": "Log out",
+ "changepassword": "ꯄꯥꯁꯋ꯭ꯔꯇ ꯍꯣꯡꯗꯣꯛꯄꯥ",
"oldpassword": "ꯑꯔꯤꯕꯥ ꯄꯥꯁꯋꯔꯇ",
"newpassword": "ꯑꯅꯧꯕꯥ ꯄꯥꯁꯋꯔꯇ",
"retypenew": "ꯑꯃꯨꯛꯍꯟꯅꯥ ꯑꯅꯧꯕꯥ ꯄꯥꯁꯋꯔꯇ ꯅꯝꯃꯨ",
"blankarticle": "<strong>Warning:</strong> The page you are creating is blank.\nIf you click \"$1\" again, the page will be created without any content.",
"anoneditwarning": "<strong>Warning:</strong> You are not logged in. Your IP address will be publicly visible if you make any edits. If you <strong>[$1 log in]</strong> or <strong>[$2 create an account]</strong>, your edits will be attributed to your username, along with other benefits.",
"loginreqlink": "Chang Sinba",
+ "accmailtitle": "ꯄꯥꯁꯋ꯭ꯔꯇ ꯊꯥꯕ",
"newarticle": "ꯑꯅꯧꯕꯥ",
"newarticletext": "You have followed a link to a page that does not exist yet.\nTo create the page, start typing in the box below (see the [$1 help page] for more info).\nIf you are here by mistake, click your browser's <strong>back</strong> button.",
"anontalkpagetext": "----\n<em>This is the discussion page for an anonymous user who has not created an account yet, or who does not use it.</em>\nWe therefore have to use the numerical IP address to identify him/her.\nSuch an IP address can be shared by several users.\nIf you are an anonymous user and feel that irrelevant comments have been directed at you, please [[Special:CreateAccount|create an account]] or [[Special:UserLogin|log in]] to avoid future confusion with other anonymous users.",
"editing": "$1 ꯁꯦꯝꯒꯠꯂꯤ",
"creating": "Creating $1",
"editingsection": "Editing $1 (section)",
+ "yourtext": "ꯅꯪꯒꯤ ꯇꯦꯀꯁ",
+ "yourdiff": "ꯈꯦꯠꯅꯕꯥ ꯁꯤꯡ",
+ "copyrightwarning": "Please note that all contributions to {{SITENAME}} are considered to be released under the $2 (see $1 for details).\nIf you do not want your writing to be edited mercilessly and redistributed at will, then do not submit it here.<br />\nYou are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource.\n<strong>Do not submit copyrighted work without permission!</strong>",
"templatesused": "{{PLURAL:$1|Template|Templates}} used on this page:",
"template-protected": "ꯉꯥꯛꯊꯣꯛꯂꯕꯥ",
"template-semiprotected": "ꯇꯪꯈꯥꯏ ꯉꯥꯛꯊꯣꯛꯂꯕꯥ",
"hiddencategories": "This page is a member of {{PLURAL:$1|1 hidden category|$1 hidden categories}}:",
+ "permissionserrors": "ꯑꯌꯥꯕꯥꯗꯨ ꯁꯣꯏꯔꯦ",
"permissionserrorstext-withaction": "You do not have permission to $2, for the following {{PLURAL:$1|reason|reasons}}:",
"moveddeleted-notice": "ꯃꯁꯤꯒꯤ ꯂꯥꯃꯥꯏꯁꯤ ꯀꯛꯊꯠꯈꯔꯦ. \nꯀꯛꯊꯠꯄꯥ, ꯉꯥꯛꯊꯣꯛꯄꯥ ꯑꯃꯗꯤ ꯂꯣꯒ ꯂꯦꯡꯍꯟꯕꯥ ꯂꯥꯃꯥꯏꯒꯤꯗꯃꯛ ꯇꯨ ꯃꯈꯥꯒꯤ ꯁꯤꯗꯥ ꯔꯤꯐꯔꯦꯟꯁ ꯎꯨꯠꯂꯦ",
+ "edit-conflict": "ꯁꯦꯝꯒꯠꯐꯝꯒꯤ ꯈꯠꯅ ꯆꯩꯅꯕꯥ",
+ "content-json-empty-object": "ꯑꯍꯥꯡꯕꯥ ꯄꯣꯠꯁꯛ",
"viewpagelogs": "ꯃꯁꯤꯒꯤ ꯂꯥꯃꯥꯏꯁꯤꯒꯤ ꯅꯧꯅ ꯆꯪꯉꯨ",
"currentrev-asof": "$1 ꯒꯤ ꯅꯧꯅꯥ ꯑꯃꯨꯛꯍꯟꯅꯥ ꯌꯦꯡꯕꯥ ꯃꯤꯠꯌꯦꯡ",
"revisionasof": "Revision as of $1",
"currentrevisionlink": "ꯈꯋꯥꯏꯗꯒꯤ ꯅꯧꯅꯥ ꯑꯃꯨꯛ ꯌꯦꯡꯕꯥ",
"cur": "ꯍꯧ",
"last": "ꯃꯥꯃꯥꯡꯒꯤ",
+ "page_first": "ꯑꯍꯥꯟꯕ",
+ "page_last": "ꯑꯔꯣꯏꯕ",
"histlegend": "Diff selection: Mark the radio boxes of the revisions to compare and hit enter or the button at the bottom.<br />\nLegend: <strong>({{int:cur}})</strong> = difference with latest revision, <strong>({{int:last}})</strong> = difference with preceding revision, <strong>{{int:minoreditletter}}</strong> = minor edit.",
"history-fieldset-title": "ꯊꯤꯋꯨ ꯑꯃꯨꯛ ꯍꯝꯁꯟꯅꯥ ꯌꯦꯡꯅꯕꯥ",
"histfirst": "ꯈꯋꯥꯏꯗꯒꯤ ꯑꯔꯤꯕꯥ",
"histlast": "ꯑꯅꯧꯕꯥ",
"rev-delundel": "ꯑꯍꯣꯡꯕꯥ ꯎꯍꯟꯂꯤꯕꯥ",
+ "revdelete-show-file-submit": "ꯍꯣꯏ",
+ "revdelete-radio-unset": "ꯎꯍꯟꯕ",
"history-title": "Revision history of \"$1\"",
"difference-title": "$1 ꯒꯤ ꯑꯃꯨꯛꯍꯟꯕꯥ ꯈꯦꯠꯅꯕꯥꯒꯤ ꯃꯔꯛ",
"lineno": "ꯂꯥ ꯏ $1",
"tooltip-undo": "\"Undo\" reverts this edit and opens the edit form in preview mode. It allows adding a reason in the summary.",
"tooltip-summary": "ꯑꯇꯦꯟꯕꯥ ꯀꯨꯞꯅꯥ ꯁꯟꯗꯣꯛꯅꯩ ꯇꯥꯛꯄꯥ ꯏꯌꯨ",
"simpleantispam-label": "Anti-spam check.\nDo <strong>not</strong> fill this in!",
+ "pageinfo-header-edits": "ꯄꯨꯋꯥꯔꯤ ꯁꯦꯝꯒꯠꯄ",
"pageinfo-header-restrictions": "ꯉꯥꯛꯊꯣꯛꯂꯕꯥ ꯂꯥꯃꯥꯏ",
"pageinfo-robot-noindex": "ꯌꯥꯍꯟꯗꯕꯥ",
"pageinfo-subpages-name": "ꯂꯥꯃꯥꯏꯁꯤ ꯒꯤ ꯃꯅꯨꯡ ꯆꯟꯕꯥ ꯀꯨꯞꯊꯕꯥ ꯂꯥꯃꯥꯏꯁꯤꯡ",
"pageinfo-subpages-value": "$1 ($2 {{PLURAL:$2|redirect|redirects}}; $3 {{PLURAL:$3|non-redirect|non-redirects}})",
+ "pageinfo-firstuser": "ꯂꯥꯃꯥꯏ ꯁꯥꯔꯤꯕ ꯃꯤꯑꯣꯏꯁꯤꯡ",
+ "pageinfo-firsttime": "ꯂꯥꯃꯥꯏ ꯁꯥꯈꯤꯕꯒꯤ ꯆꯩꯆꯠ",
+ "pageinfo-lastuser": "ꯈꯋꯥꯏꯗꯒꯤ ꯅꯧꯕ ꯁꯦꯝꯒꯠꯂꯛꯂꯤꯕꯁꯤꯡ",
+ "pageinfo-lasttime": "ꯅꯧꯔꯤꯕ ꯁꯦꯝꯒꯠꯄꯒꯤ ꯆꯩꯆꯠ",
+ "pageinfo-edits": "ꯑꯄꯨꯟꯕ ꯁꯦꯝꯒꯠꯄꯒꯤ ꯃꯁꯤꯡ",
+ "pageinfo-authors": "ꯑꯄꯨꯟꯕ ꯑꯈꯟꯅꯕ ꯑꯌꯤꯕꯁꯤꯡꯒꯤ ꯃꯁꯤꯡ",
"pageinfo-magic-words": "Magic {{PLURAL:$1|word|words}} ($1)",
"pageinfo-toolboxlink": "ꯂꯥꯃꯥꯏꯒꯤ ꯃꯇꯥꯡꯗꯥ",
"pageinfo-contentpage-yes": "ꯍꯣꯏ",
"resetpass-submit-loggedin": "Endre passord",
"resetpass-submit-cancel": "Avbryt",
"resetpass-wrong-oldpass": "Ugyldig midlertidig eller aktivt passord.\nDet kan tenkes at allerede har gjennomført et vellykket bytte av passord, eller bedt om et nytt midlertidig passord.",
- "resetpass-recycled": "Vær vennlig å endre passordet til noe annen enn gjeldende passord.",
+ "resetpass-recycled": "Endre passordet ditt til noe annet enn det nåværende passordet.",
"resetpass-temp-emailed": "Du logget inn med en midlertidig kode sendt på e-post.\nFor å avslutte innloggingen må du angi et nytt passord her:",
"resetpass-temp-password": "Midlertidig passord:",
"resetpass-abort-generic": "Endring av passord har blitt avbrutt av en utvidelse.",
"resetpass-expired": "Passordet ditt har utløpt. Vær vennlig å angi et nytt passord for å logge inn.",
"resetpass-expired-soft": "Passordet ditt har utløpt og må endres. Vær vennlig å angi et nytt passord, eller klikk \"{{int:authprovider-resetpass-skip-label}}\" for å endre det senere.",
- "resetpass-validity-soft": "Ditt passord er ikke gyldig: $1",
+ "resetpass-validity-soft": "Passordet ditt er ikke gyldig: $1\n\nVelg et nytt passord nå, eller klikk på «{{int:authprovider-resetpass-skip-label}}» for å endre det seinere.",
"passwordreset": "Tilbakestilling av passord",
"passwordreset-text-one": "Fyll ut skjemaet for å tilbakestille passordet",
"passwordreset-text-many": "{{PLURAL:$1|Fyll inn ett av datafeltene for å tilbakestille passordet ditt via epost.}}",
"previewerrortext": "En feil oppsto mens dine endringer skulle forhåndsvises.",
"blockedtitle": "Brukeren er blokkert",
"blockedtext": "<strong>Ditt brukernavn eller din IP-adresse har blitt blokkert.</strong>\n\nBlokkeringen ble utført av $1. Grunnen som ble oppgitt var <em>$2</em>.\n\n* Blokkeringen begynte: $8\n* Blokkeringen opphører: $6\n* Blokkeringen ment for: $7\n\nDu kan kontakte $1 eller en annen [[{{MediaWiki:Grouppage-sysop}}|administrator]] for å diskutere blokkeringen.\nDu kan ikke bruke \"{{int:emailuser}}\"-funksjonen med mindre du har oppgitt en gyldig e-postadresse i [[Special:Preferences|innstillingene dine]] og du ikke har blitt blokkert fra å sende e-post.\nDin nåværende IP-adresse er $3, og blokkerings-ID-en er #$5.\nVennligst ta med all denne informasjonen ved henvendelser.",
- "autoblockedtext": "Din IP-adresse har blitt automatisk blokkert fordi den ble brukt av en annen bruker som ble blokkert av $1.\nDen oppgitte grunnen var:\n\n:'''$2'''\n\n* Blokkeringen begynte: $8\n* Blokkeringen utgår: $6\n* Blokkeringen er ment for: $7\n\nDu kan kontakte $1 eller en av de andre [[{{MediaWiki:Grouppage-sysop}}|administratorene]] for å diskutere blokkeringen.\n\nMerk at du ikke kan bruke «E-post til denne brukeren»-funksjonen med mindre du har registrert en gyldig e-postadresse i [[Special:Preferences|innstillingene dine]].\n\nDin IP-adresse er $3, og blokkerings-ID-en er #$5.\nVennligst ta med all denne informasjonen ved henvendelser.",
+ "autoblockedtext": "Din IP-adresse har blitt automatisk blokkert fordi den ble brukt av en annen bruker som ble blokkert av $1.\nDen oppgitte grunnen var:\n\n:'''$2'''\n\n* Blokkeringen begynte: $8\n* Blokkeringen utgår: $6\n* Blokkeringen er ment for: $7\n\nDu kan kontakte $1 eller en av de andre [[{{MediaWiki:Grouppage-sysop}}|administratorene]] for å diskutere blokkeringen.\n\nMerk at du ikke kan bruke «{{int:emailuser}}»-funksjonen med mindre du har registrert en gyldig e-postadresse i [[Special:Preferences|innstillingene dine]].\n\nDin IP-adresse er $3, og blokkerings-ID-en er #$5.\nVennligst ta med all denne informasjonen ved henvendelser.",
"systemblockedtext": "Ditt brukernavn eller IP-adresse har blitt blokkert automatisk av MediaWiki.\n\nBlokkeringen grunnes:\n\n:<em>$2</em>\n\n* Blokkeringen startet: $8\n* Blokkeringen gjelder til: $6\n* Blokkeringen er ment for: $7\n\nDin nåværende IP-adresse er $3.\nVennligst inkluder informasjonen over i alle spørsmål du spør angående dette.",
"blockednoreason": "ingen grunn gitt",
"whitelistedittext": "Du må $1 for å redigere artikler.",
"converter-manual-rule-error": "En feil ble oppdaget i en manuell språkkonverteringsregel",
"undo-success": "Redigeringen kan omgjøres. Sjekk sammenligningen under for å bekrefte at du vil gjøre dette, og lagre endringene for å fullføre omgjøringen.",
"undo-failure": "Redigeringen kunne ikke omgjøres på grunn av konflikterende etterfølgende redigeringer.",
+ "undo-main-slot-only": "Redigeringen kunne ikke omgjøres fordi den involverer innhold utenfor hovedspalten.",
"undo-norev": "Redigeringen kunne ikke fjernes fordi den ikke eksisterer eller ble slettet",
"undo-nochange": "Det ser ut til at redigeringen allerede er tilbakestilt.",
"undo-summary": "Fjerner revisjon $1 av [[Special:Contributions/$2|$2]] ([[User talk:$2|diskusjon]])",
"rcfilters-other-review-tools": "Andre gjennomgangsverktøy",
"rcfilters-group-results-by-page": "Grupper resultater etter side",
"rcfilters-activefilters": "Aktive filtre",
+ "rcfilters-activefilters-hide": "Skjul",
+ "rcfilters-activefilters-show": "Vis",
+ "rcfilters-activefilters-hide-tooltip": "Skjul området for aktive filtre",
+ "rcfilters-activefilters-show-tooltip": "Vis området for aktive filtre",
"rcfilters-advancedfilters": "Avanserte filtre",
"rcfilters-limit-title": "Antall resultater som skal vises",
"rcfilters-limit-and-date-label": "$1 {{PLURAL:$1|endring|endringer}}, $2",
"rcfilters-savedqueries-rename": "Gi nytt navn",
"rcfilters-savedqueries-setdefault": "Sett som standard",
"rcfilters-savedqueries-unsetdefault": "Fjern som standard",
- "rcfilters-savedqueries-remove": "Fjern",
+ "rcfilters-savedqueries-remove": "Slett",
"rcfilters-savedqueries-new-name-label": "Navn",
"rcfilters-savedqueries-new-name-placeholder": "Beskriv formålet til filteret",
"rcfilters-savedqueries-apply-label": "Opprett filter",
"rcfilters-empty-filter": "Ingen aktive filtre. Alle bidrag vises.",
"rcfilters-filterlist-title": "Filtre",
"rcfilters-filterlist-whatsthis": "Hvordan virker dette?",
- "rcfilters-filterlist-feedbacklink": "Gi tilbakemelding på disse (nye) filterverktøyene",
+ "rcfilters-filterlist-feedbacklink": "Gi tilbakemelding på disse filterverktøyene",
"rcfilters-highlightbutton-title": "Marker resultater",
"rcfilters-highlightmenu-title": "Velg en farge",
"rcfilters-highlightmenu-help": "Velg en farge for å merke denne egenskapen",
"expansion-depth-exceeded-warning": "De pagina bevat te veel sjablonen",
"parser-unstrip-loop-warning": "Er is een \"unstrip\"-lus gedetecteerd",
"unstrip-depth-warning": "De recursielimiet ($1) voor \"unstrip\" is overschreden",
+ "unstrip-depth-category": "Pagina's waar de \"unstrip\" dieptelimiet is overschreden.",
+ "unstrip-size-warning": "De groottelimiet ($1) voor \"unstrip\" is overschreden",
+ "unstrip-size-category": "Pagina's waar de \"unstrip\" groottelimiet is overschreden",
"converter-manual-rule-error": "Er is een fout gedetecteerd in een handmatig toegevoegde taalconversieregel.",
"undo-success": "Deze bewerking kan ongedaan gemaakt worden.\nHieronder staat de tekst waarin de wijziging ongedaan is gemaakt.\nControleer voor het opslaan of het resultaat gewenst is.",
"undo-failure": "De wijziging kan niet ongedaan gemaakt worden vanwege andere strijdige wijzigingen.",
"limitreport-cputime-value": "$1 {{PLURAL:$1|seconde|seconden}}",
"limitreport-walltime": "Werkelijk tijdsgebruik",
"limitreport-walltime-value": "$1 {{PLURAL:$1|seconde|seconden}}",
- "limitreport-ppvisitednodes": "Aantal nodes bekeken tijdens de voorverwerking:",
- "limitreport-ppgeneratednodes": "Aantal nodes aangemaakt tijdens de voorverwerking:",
+ "limitreport-ppvisitednodes": "Aantal nodes bekeken tijdens de voorverwerking",
+ "limitreport-ppgeneratednodes": "Aantal nodes aangemaakt tijdens de voorverwerking",
"limitreport-postexpandincludesize": "Inclusiegrootte na uitbreiden",
"limitreport-postexpandincludesize-value": "$1/$2 {{PLURAL:$2|byte|bytes}}",
"limitreport-templateargumentsize": "Grootte sjabloonparameters",
"limitreport-templateargumentsize-value": "$1 / $2 {{PLURAL:$2|byte|bytes}}",
"limitreport-expansiondepth": "Hoogste uitbreidingsdiepte",
"limitreport-expensivefunctioncount": "Aantal kostbare parserfuncties",
+ "limitreport-unstrip-depth": "\"Unstrip\" recursiediepte",
+ "limitreport-unstrip-size": "\"Unstrip\" grootte na uitbreiden",
"limitreport-unstrip-size-value": "$1/$2 {{PLURAL:$2|byte|bytes}}",
"expandtemplates": "Sjablonen substitueren",
"expand_templates_intro": "Deze speciale pagina leest de opgegeven wikitekst in en substitueert recursief alle sjablonen in de wikitekst.\nHet substitueert ook alle parserfuncties zoals\n<code><nowiki>{{</nowiki>#language:…}}</code> en\nvariabelen als <code><nowiki>{{</nowiki>CURRENTDAY}}</code>.\nVrijwel alles tussen dubbele accolades wordt gesubstitueerd.",
"prefs-diffs": "Skilnader",
"prefs-help-prefershttps": "Denne innstillinga vil verta verksam neste gongen du loggar inn.",
"userrights": "Administrering av brukartilgang",
- "userrights-lookup-user": "Administrer brukargrupper",
+ "userrights-lookup-user": "Vel ein brukar",
"userrights-user-editname": "Skriv inn brukarnamn:",
- "editusergroup": "Endre brukargrupper",
+ "editusergroup": "Last inn brukargrupper",
"editinguser": "Endrar brukarrettane til brukaren '''[[User:$1|$1]]''' $2",
+ "viewinguserrights": "Viser brukarrettane til {{GENDER:$1|brukaren}} <strong>[[User:$1|$1]]</strong> $2",
"userrights-editusergroup": "Endre brukargrupper",
+ "userrights-viewusergroup": "Sjå {{GENDER:$1|brukargrupper}}",
"saveusergroups": "Lagre brukargrupper",
"userrights-groupsmember": "Medlem av:",
"userrights-groupsmember-auto": "Implisitt medlem av:",
"booksources-search": "Søk",
"booksources-text": "Nedanfor finn du ei liste over lenkjer til andre nettstader som sel nye og brukte bøker, og desse kan ha meir informasjon om bøker du leitar etter:",
"booksources-invalid-isbn": "Det oppgjevne ISBN-nummeret er ugyldig; sjekk med kjelda di om du har oppgjeve det rett.",
+ "magiclink-tracking-rfc": "Sider som nyttar magiske RFC-lenkjer",
+ "magiclink-tracking-pmid": "Sider som nyttar magiske PMID-lenkjer",
+ "magiclink-tracking-isbn": "Sider som nyttar magiske ISBN-lenkjer",
"specialloguserlabel": "Utøvar:",
"speciallogtitlelabel": "Mål (tittel eller {{ns:user}}:brukarnamn for brukar):",
"log": "Loggar",
"trackingcategories-msg": "Sporingskategori",
"trackingcategories-name": "Meldingsnamn",
"trackingcategories-desc": "Inkluderingsgrunnlag",
+ "restricted-displaytitle-ignored": "Sider med ignorerte visingstitlar",
+ "restricted-displaytitle-ignored-desc": "Ein <code><nowiki>{{DISPLAYTITLE}}</nowiki></code>-funksjon på denne sida er sett bort ifrå av di argumentet ikkje svarar til den faktiske tittelen til sida.",
"noindex-category-desc": "Sida vert ikkje indeksert av robotar av di ho inneheld trylleordet <code><nowiki>__NOINDEX__</nowiki></code> og er i eit namnerom der dette flagget er tillate.",
"trackingcategories-nodesc": "Inga skilding er tilgjengeleg.",
"trackingcategories-disabled": "Kategorien er avslegen",
"watchlisttools-raw": "Endre på overvakingslista i råformat",
"signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|diskusjon]])",
"duplicate-defaultsort": "Åtvaring: Standardsorteringa «$2» tar over for den tidlegare sorteringa «$1».",
+ "restricted-displaytitle": "<strong>Åtvaring:</strong> Visingstittelen «$1» vart sett bort frå sidan han ikkje svarar til den faktiske tittelen til sida.",
"version": "Versjon",
"version-extensions": "Installerte utvidingar",
"version-skins": "Installerte drakter",
"rcfilters-savedqueries-apply-and-setdefault-label": "د فرض په ډول د فيلټر جوړول",
"rcfilters-savedqueries-cancel-label": "ناگارل",
"rcfilters-savedqueries-add-new-title": "د امستنې اوسنۍ فيلټر خوندي کړي",
+ "rcfilters-restore-default-filters": "د ډيفاولټ فلټرونه بیا تازه کول",
"rcfilters-search-placeholder": "د فلټر بدلونونه (د مینو کارول یا د فلټر نوم لټونه)",
"rcfilters-invalid-filter": "غلط فلټر",
"rcfilters-empty-filter": "هيڅ فعال فلټر نشته. ټولي سمونې ښکاره شوي.",
"RadiX",
"MokaAkashiyaPT",
"Athena in Wonderland",
- "Fitoschido"
+ "Fitoschido",
+ "Ldacosta"
]
},
"tog-underline": "Sublinhar hiperligações:",
"viewhelppage": "Ver página de ajuda",
"categorypage": "Ver página de categoria",
"viewtalkpage": "Ver discussão",
- "otherlanguages": "Noutras línguas",
+ "otherlanguages": "Em outros idiomas",
"redirectedfrom": "(Redirecionado de $1)",
"redirectpagesub": "Página de redirecionamento",
"redirectto": "Redireciona para:",
"nowikiemailtext": "Este utilizador optou por não receber correio eletrónico de outros utilizadores.",
"emailnotarget": "O nome do destinatário não existe ou é inválido.",
"emailtarget": "Introduza o nome do destinatário",
- "emailusername": "Utilizador:",
+ "emailusername": "Nome de Utilizador:",
"emailusernamesubmit": "Enviar",
"email-legend": "Enviar uma mensagem a outro utilizador da wiki {{SITENAME}}",
"emailfrom": "De:",
"prefs-editor": "Used in [[Special:Preferences]], tab \"Editing\" ({{int:prefs-editing}}).\n\n{{Identical|Editor}}",
"prefs-preview": "Used in [[Special:Preferences]], tab \"Editing\".\n{{Identical|Preview}}",
"prefs-advancedrc": "Used in [[Special:Preferences]], tab \"Recent changes\".\n{{Identical|Advanced options}}",
- "prefs-opt-out": "Used in [[Special:Preferences]], tab \"Recent changes\".",
+ "prefs-opt-out": "Used in [[Special:Preferences]], tabs \"Recent changes\" and \"Watchlist\".",
"prefs-advancedrendering": "Used in [[Special:Preferences]], tab \"Appearence\".\n{{Identical|Advanced options}}",
"prefs-advancedsearchoptions": "Used in [[Special:Preferences]], tab \"Search options\".\n{{Identical|Advanced options}}",
"prefs-advancedwatchlist": "Used in [[Special:Preferences]], tab \"Watchlist\".\n{{Identical|Advanced options}}",
"december-date": "$1. децембар",
"period-am": "преподне",
"period-pm": "поподне",
- "pagecategories": "{{PLURAL:$1|Категорија|Категорије|Категорија}}",
+ "pagecategories": "{{PLURAL:$1|Категорија|Категорије}}",
"category_header": "Странице у категорији „$1“",
"subcategories": "Поткатегорије",
"category-media-header": "Датотеке у категорији „$1“",
"permalink": "Трајна веза",
"print": "Штампај",
"view": "Погледај",
- "view-foreign": "Ð\92иди на пројекту $1",
+ "view-foreign": "Ð\9fогледаÑ\98 на пројекту $1",
"edit": "Уреди",
"edit-local": "Уреди локални опис",
"create": "Направи",
"pagetitle-view-mainpage": "{{SITENAME}}",
"backlinksubtitle": "← $1",
"retrievedfrom": "Преузето из „$1“",
- "youhavenewmessages": "Имате $1 ($2).",
+ "youhavenewmessages": "{{PLURAL:$3|Имате}} $1 ($2).",
"youhavenewmessagesfromusers": "{{PLURAL:$4|Имате}} $1 од {{PLURAL:$3|другог корисника|$3 корисника}} ($2).",
"youhavenewmessagesmanyusers": "Имате $1 од много корисника ($2).",
"newmessageslinkplural": "{{PLURAL:$1|нову поруку|нове поруке|нових порука}}",
"sort-descending": "Поређај опадајуће",
"sort-ascending": "Поређај растуће",
"nstab-main": "Страница",
- "nstab-user": "{{GENDER:{{BASEPAGENAME}}|Корисник|Корисница|Корисник}}",
+ "nstab-user": "Корисничка страница",
"nstab-media": "Медији",
- "nstab-special": "Ð\9fоÑ\81ебно",
- "nstab-project": "Ð\9fÑ\80оÑ\98екаÑ\82",
+ "nstab-special": "Ð\9fоÑ\81ебна Ñ\81Ñ\82Ñ\80аниÑ\86а",
+ "nstab-project": "СÑ\82Ñ\80аниÑ\86а пÑ\80оÑ\98екÑ\82а",
"nstab-image": "Датотека",
"nstab-mediawiki": "Порука",
"nstab-template": "Шаблон",
"userlogin-yourpassword-ph": "Унесите своју лозинку",
"createacct-yourpassword-ph": "Унесите лозинку",
"yourpasswordagain": "Поново унеси лозинку:",
- "createacct-yourpasswordagain": "Потврди лозинку",
+ "createacct-yourpasswordagain": "Потврдите лозинку",
"createacct-yourpasswordagain-ph": "Унесите лозинку поново",
"userlogin-remembermypassword": "Остави ме пријављеног/у",
"userlogin-signwithsecure": "Користите сигурну конекцију",
"showpreview": "Прикажи претпреглед",
"showdiff": "Прикажи измене",
"blankarticle": "<strong>Упозорење:</strong> Страница коју правите је празна.\nАко још једном притиснете „$1”, страница ће бити направљена без икаквог садржаја.",
- "anoneditwarning": "<strong>УпозоÑ\80еÑ\9aе:</strong> Ð\9dиÑ\81Ñ\82е пÑ\80иÑ\98авÑ\99ени. Ð\90ко обÑ\98авиÑ\82е Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83, Ð\92аÑ\88а IP адÑ\80еÑ\81а Ñ\9bе биÑ\82и Ñ\98авно видÑ\99ива Ñ\83 Ñ\9aеноÑ\98 иÑ\81Ñ\82оÑ\80иÑ\98и измена и дÑ\80Ñ\83где. Ð\90ко Ñ\81е <strong>[$1 пÑ\80иÑ\98авиÑ\82е]</strong> или <strong>[$2 оÑ\82воÑ\80иÑ\82е налог]</strong>, поÑ\80ед оÑ\81Ñ\82алиÑ\85 погодноÑ\81Ñ\82и коÑ\98е добиÑ\98аÑ\82е Ð\92аÑ\88е измене Ñ\9bе биÑ\82и пÑ\80ипиÑ\81иване Ð\92аÑ\88ем коÑ\80иÑ\81ниÑ\87ком именÑ\83.",
+ "anoneditwarning": "<strong>УпозоÑ\80еÑ\9aе:</strong> Ð\9dиÑ\81Ñ\82е пÑ\80иÑ\98авÑ\99ени. Ð\92аÑ\88а IP адÑ\80еÑ\81а Ñ\9bе биÑ\82и Ñ\98авно видÑ\99ива ако напÑ\80авиÑ\82е некÑ\83 изменÑ\83. Ð\90ко Ñ\81е <strong>[$1 пÑ\80иÑ\98авиÑ\82е]</strong> или <strong>[$2 оÑ\82воÑ\80иÑ\82е налог]</strong>, Ð\92аÑ\88е измене Ñ\9bе биÑ\82и пÑ\80ипиÑ\81иване Ð\92аÑ\88ем коÑ\80иÑ\81ниÑ\87ком именÑ\83, поÑ\80ед дÑ\80Ñ\83гиÑ\85 погодноÑ\81Ñ\82и.",
"anonpreviewwarning": "<em>Нисте пријављени. Ако објавите страницу, Ваша IP адреса ће бити јавно видљива у њеној историји измена и другде.</em>",
"missingsummary": "<strong>Подсетник:</strong> Нисте унели опис измене.\nАко поново кликнете на „$1”, Ваша измена ће бити сачувана без описа.",
"selfredirect": "<strong>Упозорење:</strong> Преусмеравате ову страницу на њу саму.\nМожда вам је одредишна страница за преусмерење погрешна или уређујете погрешну страницу.\nАко још једном притиснете „$1”, преусмерење ће свеједно бити направљено.",
"newarticle": "(нови)",
"newarticletext": "Дошли сте на страницу која још не постоји.\nДа бисте је направили, почните да куцате у прозор испод овог текста (погледајте [$1 страницу за помоћ]).\nАко сте овде дошли грешком, вратите се на претходну страницу.",
"anontalkpagetext": "----\n<em>Ово је страница за разговор с анонимним корисником који још нема налог или га не користи.</em>\nЗбог тога морамо да користимо бројчану IP адресу како бисмо га препознали.\nТакву адресу може делити више корисника.\nАко сте анонимни корисник и мислите да су вам упућене примедбе, [[Special:CreateAccount|отворите налог]] или се [[Special:UserLogin|пријавите]] да бисте избегли будућу забуну с осталим анонимним корисницима.",
- "noarticletext": "Ð\9dа овоÑ\98 Ñ\81Ñ\82Ñ\80аниÑ\86и Ñ\82Ñ\80енÑ\83Ñ\82но нема Ñ\81адÑ\80жаÑ\98а.\nМожете [[Special:Search/{{PAGENAME}}|потражити овај наслов]] на другим страницама,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} претражити сродне извештаје] или [{{fullurl:{{FULLPAGENAME}}|action=edit}} направити ову страницу]</span>.",
- "noarticletext-nopermission": "Ð\9dа овоÑ\98 Ñ\81Ñ\82Ñ\80аниÑ\86и Ñ\82Ñ\80енÑ\83Ñ\82но нема Ñ\81адÑ\80жаÑ\98а.\nМожете [[Special:Search/{{PAGENAME}}|потражити овај наслов]] на другим страницама или <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} претражити сродне дневнике]</span>, али немате дозволу да направите ову страницу.",
+ "noarticletext": "Ð\9dа овоÑ\98 Ñ\81Ñ\82Ñ\80аниÑ\86и Ñ\82Ñ\80енÑ\83Ñ\82но нема Ñ\82екÑ\81Ñ\82а.\nМожете [[Special:Search/{{PAGENAME}}|потражити овај наслов]] на другим страницама,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} претражити сродне извештаје] или [{{fullurl:{{FULLPAGENAME}}|action=edit}} направити ову страницу]</span>.",
+ "noarticletext-nopermission": "Ð\9dа овоÑ\98 Ñ\81Ñ\82Ñ\80аниÑ\86и Ñ\82Ñ\80енÑ\83Ñ\82но нема Ñ\82екÑ\81Ñ\82а.\nМожете [[Special:Search/{{PAGENAME}}|потражити овај наслов]] на другим страницама или <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} претражити сродне дневнике]</span>, али немате дозволу да направите ову страницу.",
"missing-revision": "Не могу да пронађем измену бр. $1 на страници под називом „{{FULLPAGENAME}}“.\n\nОво се обично дешава када пратите застарелу везу до странице која је обрисана.\nВише информација можете пронаћи у [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} дневнику брисања].",
"userpage-userdoesnotexist": "Кориснички налог „<nowiki>$1</nowiki>“ није отворен.\nРазмислите да ли заиста желите да направите/уредите ову страницу.",
"userpage-userdoesnotexist-view": "Кориснички налог „$1“ није отворен.",
"viewpagelogs": "Дневници ове странице",
"nohistory": "Не постоји историја измена ове странице.",
"currentrev": "Тренутна измена",
- "currentrev-asof": "Ð\9fоÑ\81ледÑ\9aа измена на датум $2 у $3",
+ "currentrev-asof": "ТÑ\80енÑ\83Ñ\82на измена на датум $2 у $3",
"revisionasof": "Измена на датум $2 у $3",
"revision-info": "Измена од $1 од стране {{GENDER:$6|корисника $2|кориснице $2}}$7",
"previousrevision": "← Старија измена",
"next-page": "следећа страница",
"prevn-title": "$1 {{PLURAL:$1|претходни резултат|претходна резултата|претходних резултата}}",
"nextn-title": "$1 {{PLURAL:$1|следећи резултат|следећа резултата|следећих резултата}}",
- "shown-title": "Прикажи $1 {{PLURAL:$1|резултат|резултата|резултата}} по страници",
+ "shown-title": "Прикажи $1 {{PLURAL:$1|резултат|резултата}} по страници",
"viewprevnext": "Погледај ($1 {{int:pipe-separator}} $2) ($3).",
"searchmenu-exists": "<strong>Постоји страница под називом „[[:$1]]”!</strong> {{PLURAL:$2|0=|Такође погледајте друге пронађене резултате претраге.}}",
- "searchmenu-new": "<strong>Направите страницу „[[:$1]]”!</strong> {{PLURAL:$2|0=|Такође погледајте резултат претраге.|Такође погледајте резултате претраге.}}",
+ "searchmenu-new": "<strong>Направите страницу „[[:$1]]” на овом викију!</strong> {{PLURAL:$2|0=|Такође погледајте страницу пронађену претрагом.|Такође погледајте резултате претраге.}}",
"searchprofile-articles": "Странице са садржајем",
"searchprofile-images": "Датотеке",
"searchprofile-everything": "Све",
"searchprofile-images-tooltip": "Претражите датотеке",
"searchprofile-everything-tooltip": "Претражите сав садржај (укључујући странице за разговор)",
"searchprofile-advanced-tooltip": "Претражите прилагођене именске просторе",
- "search-result-size": "$1 ({{PLURAL:$2|1 реч|$2 речи|$2 речи}})",
+ "search-result-size": "$1 ({{PLURAL:$2|1 реч|$2 речи}})",
"search-result-category-size": "{{PLURAL:$1|1 члан|$1 члана|$1 чланова}}, ({{PLURAL:$2|1 поткатегорија|$2 поткатегорије|$2 поткатегорија}}, {{PLURAL:$3|1 датотека|$3 датотеке|$3 датотека}})",
- "search-redirect": "(преусмерено са $1)",
+ "search-redirect": "(преусмерење са $1)",
"search-section": "(одељак $1)",
"search-category": "(категорија $1)",
"search-file-match": "(подудара се садржај датотеке)",
- "search-suggest": "Да ли сте мислили на: $1",
+ "search-suggest": "Да ли сте мислили: $1",
"search-rewritten": "Приказани резултати за $1. Ипак претражи $2.",
"search-interwiki-caption": "Резултати са сестринских пројеката",
"search-interwiki-default": "Резултати са $1:",
"showingresults": "Испод {{PLURAL:$1|је приказан <strong>1</strong> резултат|су приказана <strong>$1</strong> резултата|је приказано <strong>$1</strong> резултата}}, почев од броја <strong>$2</strong>.",
"showingresultsinrange": "Испод {{PLURAL:$1|је приказан <strong>1</strong> резултат|су приказана <strong>$1</strong> резултата|је приказано <strong>$1</strong> резултата}}, у распону од <strong>$2</strong> до <strong>$3</strong>.",
"search-showingresults": "{{PLURAL:$4|Резултат <strong>$1</strong> од <strong>$3</strong>|Резултати <strong>$1—$2</strong> од <strong>$3</strong>}}",
- "search-nonefound": "Ð\9dема поклапаÑ\9aа.",
+ "search-nonefound": "Ð\9dиÑ\81Ñ\83 пÑ\80онаÑ\92ени Ñ\80езÑ\83лÑ\82аÑ\82и коÑ\98и одговаÑ\80аÑ\98Ñ\83 Ñ\83пиÑ\82Ñ\83.",
"search-nonefound-thiswiki": "Нема резултата на овом сајту који се поклапају са термином претраге.",
"powersearch-legend": "Напредна претрага",
"powersearch-ns": "Претрага по именским просторима:",
"right-bot": "сматрање измена као аутоматски процес",
"right-nominornewtalk": "непоседовање мањих измена на страницама за разговор отвара прозор за нове поруке",
"right-apihighlimits": "коришћење виших граница за упите из API-ја",
- "right-writeapi": "пиÑ\81аÑ\9aе API-ја",
+ "right-writeapi": "могÑ\83Ñ\9bноÑ\81Ñ\82 пиÑ\81аÑ\9aа API-ја",
"right-delete": "брисање страница",
"right-bigdelete": "брисање страница с великом историјом",
"right-deletelogentry": "брисање и враћање одређених ставки у дневнику",
"recentchanges-label-plusminus": "Промена величине странице у бајтовима",
"recentchanges-legend-heading": "<strong>Легенда:</strong>",
"recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (такође погледајте [[Special:NewPages|списак нових страница]])",
+ "recentchanges-legend-plusminus": "(<em>±123</em>)",
"recentchanges-submit": "Прикажи",
"rcfilters-tag-remove": "Уклоните филтер „$1“",
"rcfilters-legend-heading": "<strong>Списак скраћеница:</strong>",
"rcfilters-target-page-placeholder": "Унесите име странице (или категорије)",
"rcnotefrom": "Испод {{PLURAL:$5|је измена|су измене}} од <strong>$3, $4</strong> (до <strong>$1</strong> приказано).",
"rclistfromreset": "Ресетуј одабир датума",
- "rclistfrom": "Прикажи нове измене почев од $2, $3",
+ "rclistfrom": "Прикажи нове измене почев од $3 у $2",
"rcshowhideminor": "$1 мање измене",
"rcshowhideminor-show": "Прикажи",
"rcshowhideminor-hide": "Сакриј",
"recentchangeslinked-feed": "Сродне измене",
"recentchangeslinked-toolbox": "Сродне измене",
"recentchangeslinked-title": "Сродне измене са „$1“",
- "recentchangeslinked-summary": "УнеÑ\81иÑ\82е име Ñ\81Ñ\82Ñ\80аниÑ\86е да биÑ\81Ñ\82е видели пÑ\80омене на Ñ\81Ñ\82Ñ\80аниÑ\86ама коÑ\98е Ñ\81Ñ\83 повезане Ñ\81а или Ñ\81а Ñ\82е Ñ\81Ñ\82Ñ\80аниÑ\86е. (Ð\94а биÑ\81Ñ\82е видели Ñ\87ланове каÑ\82егоÑ\80иÑ\98е, Ñ\83неÑ\81иÑ\82е {{ns:category}}:Ð\98ме каÑ\82егоÑ\80иÑ\98е). Ð\9fÑ\80омене на Ñ\81Ñ\82Ñ\80аниÑ\86ама коÑ\98е Ñ\81Ñ\83 на [[Special:Watchlist|Вашем списку надгледања]] су <strong>подебљане</strong>.",
+ "recentchangeslinked-summary": "УнеÑ\81иÑ\82е име Ñ\81Ñ\82Ñ\80аниÑ\86е да биÑ\81Ñ\82е видели измене Ñ\81Ñ\82Ñ\80аниÑ\86а коÑ\98е Ñ\81Ñ\83 повезане Ñ\81а или Ñ\81а Ñ\82е Ñ\81Ñ\82Ñ\80аниÑ\86е. (Ð\94а биÑ\81Ñ\82е видели Ñ\87ланове каÑ\82егоÑ\80иÑ\98е, Ñ\83неÑ\81иÑ\82е {{ns:category}}:Ð\98ме каÑ\82егоÑ\80иÑ\98е). Ð\98змене Ñ\81Ñ\82Ñ\80аниÑ\86а на [[Special:Watchlist|Вашем списку надгледања]] су <strong>подебљане</strong>.",
"recentchangeslinked-page": "Назив странице:",
"recentchangeslinked-to": "Прикажи измене страница које су повезане с датом страницом",
"recentchanges-page-added-to-category": "[[:$1]] је додата у категорију",
"upload-curl-error28": "Отпремање је истекло",
"upload-curl-error28-text": "Сервер не одговара на упит.\nПроверите да ли сајт ради, мало осачекајте и покушајте поново.\nПробајте касније када буде мање оптерећење.",
"license": "Лиценца:",
- "license-header": "Ð\9bиÑ\86енÑ\86а:",
+ "license-header": "Ð\9bиÑ\86енÑ\86иÑ\80аÑ\9aе",
"nolicense": "Није изабрано",
"licenses-edit": "Уреди избор лиценци",
"license-nopreview": "(преглед није доступан)",
"apisandbox-multivalue-all-namespaces": "$1 (сви именски простори)",
"apisandbox-multivalue-all-values": "$1 (све вредности)",
"booksources": "Штампани извори",
- "booksources-search-legend": "ТÑ\80ажи кÑ\9aижевне изворе",
+ "booksources-search-legend": "Ð\9fÑ\80еÑ\82Ñ\80ажи Ñ\88Ñ\82ампане изворе",
"booksources-isbn": "ISBN:",
"booksources-search": "Претражи",
"booksources-text": "Испод се налази списак веза ка сајтовима који се баве продајом нових и половних књига, а који би могли имати додатне податке о књигама које тражите:",
"watchnologin": "Нисте пријављени",
"addwatch": "Додај на списак надгледања",
"addedwatchtext": "Страница „[[:$1]]“ и њена страница за разговор је додата на Ваш [[Special:Watchlist|списак надгледања]].",
+ "addedwatchtext-talk": "Страница „[[:$1]]” и њена придружена страница је додата на Ваш [[Special:Watchlist|списак надгледања]]",
"addedwatchtext-short": "Страница „$1“ је додата на Ваш списак надгледања.",
"removewatch": "Уклони са списка надгледања",
- "removedwatchtext": "Страница „[[:$1]]“ и њена страница за разговор је уклоњена с вашег [[Special:Watchlist|списка надгледања]].",
+ "removedwatchtext": "Страница „[[:$1]]“ и њена страница за разговор је уклоњена са Вашег [[Special:Watchlist|списка надгледања]].",
"removedwatchtext-short": "Страница „$1“ је уклоњена с вашег списка надгледања.",
"watch": "Надгледај",
"watchthispage": "Надгледај ову страницу",
"wlshowhidemine": "моје измене",
"wlshowhidecategorization": "категоризацију страница",
"watchlist-options": "Опције списка надгледања",
- "watching": "Надгледање…",
- "unwatching": "УклаÑ\9aаÑ\9aе Ñ\81а Ñ\81пиÑ\81ка надгледаÑ\9aа...",
+ "watching": "Надгледам…",
+ "unwatching": "Ð\9fÑ\80еÑ\81Ñ\82аÑ\98ем да надгледам...",
"watcherrortext": "Дошло је до грешке при промени поставки вашег списка надгледања за „$1“.",
"enotif_reset": "Означи све странице као посећене",
"enotif_impersonal_salutation": "{{SITENAME}} корисник",
"dellogpage": "Дневник брисања",
"dellogpagetext": "Испод је списак последњих брисања.",
"deletionlog": "дневник брисања",
- "logentry-create-create": "$1 {{GENDER:$2|креирао је|креирала је}} страницу $3",
+ "logentry-create-create": "$1 је {{GENDER:$2|направио|направила}} страницу $3",
"reverted": "Враћено на ранију измену",
"deletecomment": "Разлог:",
"deleteotherreason": "Други/додатни разлог:",
"logentry-contentmodel-change-revert": "врати",
"protectlogpage": "Дневник заштите",
"protectlogtext": "Испод је списак заштићених страница.\nПогледајте [[Special:ProtectedPages|списак заштићених страница]] за више детаља.",
- "protectedarticle": "је заштитио „[[$1]]“",
- "modifiedarticleprotection": "промењен степен заштите за „[[$1]]“",
+ "protectedarticle": "је {{GENDER:|заштитио|заштитила}} страницу „[[$1]]“",
+ "modifiedarticleprotection": "је {{GENDER:|променио|променила}} степен заштите странице „[[$1]]“",
"unprotectedarticle": "је скинуо заштиту са странице „[[$1]]“",
"movedarticleprotection": "је преместио подешавања заштите са „[[$2]]“ на „[[$1]]“",
"protectedarticle-comment": "{{GENDER:$2|Заштићена}} страница [[$1]]",
"contribsub2": "За {{GENDER:$3|$1}} ($2)",
"contributions-userdoesnotexist": "Кориснички налог „$1“ није отворен.",
"nocontribs": "Нема измена које одговарају наведеним критеријумима.",
- "uctop": "(последња)",
+ "uctop": "(тренутна)",
"month": "од месеца (и раније):",
"year": "од године (и раније):",
"sp-contributions-newbies": "Прикажи само доприносе нових корисника",
"blocklogpage": "Дневник блокирања",
"blocklog-showlog": "{{GENDER:$1|Овај корисник је раније блокиран|Ова корисница је раније блокирана}}.\nИсторија блокирања се налази испод:",
"blocklog-showsuppresslog": "{{GENDER:$1|Овај корисник је раније блокиран и сакривен|Ова корисница је раније блокирана и сакривена}}.\nИсторија сакривања се налази испод:",
- "blocklogentry": "је блокирао [[$1]] с роком истицања од $2 $3",
- "reblock-logentry": "{{GENDER:|је променио|је променила|је променио}} подешавања за блокирање {{GENDER:$1|корисника|кориснице|корисника}} [[$1]] с роком истека од $2 ($3)",
+ "blocklogentry": "је блокирао [[$1]] са временом истицања од $2 $3",
+ "reblock-logentry": "{{GENDER:|је променио|је променила}} подешавања за блокирање {{GENDER:$1|корисника|кориснице}} [[$1]] са временом истека од $2 ($3)",
"blocklogtext": "Ово је дневник блокирања и деблокирања корисника.\nАутоматски блокиране ИП адресе нису наведене.\nТекуће забране и блокирања можете наћи [[Special:BlockList|овде]].",
"unblocklogentry": "је деблокирао $1",
"block-log-flags-anononly": "само анонимни корисници",
"tooltip-pt-login-private": "Морате да се пријавите да бисте користили овај Вики",
"tooltip-pt-logout": "Одјавите се",
"tooltip-pt-createaccount": "Предлажемо Вам да отворите налог и пријавите се, иако то није обавезно",
- "tooltip-ca-talk": "Разговор о садржају",
+ "tooltip-ca-talk": "Разговор о страници са садржајем",
"tooltip-ca-edit": "Уредите ову страницу",
"tooltip-ca-addsection": "Започните нови одељак",
"tooltip-ca-viewsource": "Ова страница је закључана. \nМожете да погледате њен изворни кôд",
"tooltip-ca-delete": "Обришите ову страницу",
"tooltip-ca-undelete": "Врати измене направљене на овој страници пре него што буде обрисана",
"tooltip-ca-move": "Премести ову страницу",
- "tooltip-ca-watch": "Додајте ову страницу на списак надгледања",
+ "tooltip-ca-watch": "Ð\94одаÑ\98Ñ\82е овÑ\83 Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 на Ñ\81воÑ\98 Ñ\81пиÑ\81ак надгледаÑ\9aа",
"tooltip-ca-unwatch": "Уклоните ову страницу са списка надгледања",
"tooltip-search": "Претражите пројекат {{SITENAME}}",
"tooltip-search-go": "Идите на страницу са тачно овим именом ако постоји",
"tooltip-n-mainpage": "Посетите главну страну",
"tooltip-n-mainpage-description": "Посетите главну страну",
"tooltip-n-portal": "О пројекту, шта можете да радите и где да пронађете ствари",
- "tooltip-n-currentevents": "Пронађите додатне информације о тренутним догађајима",
+ "tooltip-n-currentevents": "Пронађите додатне информације о актуелностима",
"tooltip-n-recentchanges": "Списак скорашњих измена на викију",
"tooltip-n-randompage": "Учитајте случајну страницу",
"tooltip-n-help": "Место где можете да научите нешто",
"tooltip-t-permalink": "Трајна веза ка овој измени странице",
"tooltip-ca-nstab-main": "Погледајте страницу са садржајем",
"tooltip-ca-nstab-user": "Погледајте корисничку страницу",
- "tooltip-ca-nstab-media": "Погледајте мултимедијалну датотеку",
+ "tooltip-ca-nstab-media": "Погледајте медијску страницу",
"tooltip-ca-nstab-special": "Ово је посебна страница. Не можете је мењати.",
"tooltip-ca-nstab-project": "Погледајте страницу пројекта",
- "tooltip-ca-nstab-image": "Прикажи страницу датотеке",
+ "tooltip-ca-nstab-image": "Погледајте страницу датотеке",
"tooltip-ca-nstab-mediawiki": "Погледајте системску поруку",
"tooltip-ca-nstab-template": "Погледајте шаблон",
"tooltip-ca-nstab-help": "Погледајте страницу за помоћ",
- "tooltip-ca-nstab-category": "Ð\9fогледаÑ\98Ñ\82е Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 каÑ\82егоÑ\80иÑ\98а",
+ "tooltip-ca-nstab-category": "Ð\9fогледаÑ\98Ñ\82е Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 каÑ\82егоÑ\80иÑ\98е",
"tooltip-minoredit": "Означите као мању измену",
"tooltip-save": "Сачувајте своје измене",
"tooltip-publish": "Објавите своје измене",
"tooltip-preview": "Прегледајте своје измене. Користите ово дугме пре чувања.",
- "tooltip-diff": "Погледајте које измене сте направили на тексту",
+ "tooltip-diff": "Погледајте које измене сте направили у тексту",
"tooltip-compareselectedversions": "Погледаjте разлике између две изабране измене ове странице.",
"tooltip-watch": "Додајте ову страницу на свој списак надгледања",
"tooltip-watchlistedit-normal-submit": "Уклоните наслове",
"tooltip-watchlistedit-raw-submit": "Ажурирај списак",
"tooltip-recreate": "Поново направите страницу иако је обрисана",
"tooltip-upload": "Започните отпремање",
- "tooltip-rollback": "â\80\9eÐ\92Ñ\80аÑ\82иâ\80\9c вÑ\80аÑ\9bа измене поÑ\81ледÑ\9aег коÑ\80иÑ\81ника једним кликом",
- "tooltip-undo": "Опција „поништи” враћа ову измену и отвара образац за уређивање у претпрегледном моду. Омогућава додавање разлога у опису измене.",
+ "tooltip-rollback": "â\80\9eÐ\92Ñ\80аÑ\82иâ\80\9c вÑ\80аÑ\9bа измене поÑ\81ледÑ\9aег допÑ\80иноÑ\81иоÑ\86а ове Ñ\81Ñ\82Ñ\80аниÑ\86е једним кликом",
+ "tooltip-undo": "„Поништи” враћа ову измену и отвара образац за уређивање у претпрегледном моду. Дозвољава додавање разлога у опису измене.",
"tooltip-preferences-save": "Сачувај подешавања",
"tooltip-summary": "Унесите кратак опис",
"interlanguage-link-title": "$1 — $2",
"spam_blanking": "Све измене садрже везе до $1. Чистим",
"spam_deleting": "Све измене садрже везе до $1. Бришем",
"simpleantispam-label": "Анти-спам провера. \n<strong>Не</strong> попуњавај ово унутра!",
- "pageinfo-title": "Ð\9fодаÑ\86и о „$1“",
+ "pageinfo-title": "Ð\98нÑ\84оÑ\80маÑ\86иÑ\98е за „$1“",
"pageinfo-not-current": "Нажалост, немогуће је прибавити ове податке за старије измене.",
- "pageinfo-header-basic": "Ð\9eÑ\81новни подаÑ\86и",
+ "pageinfo-header-basic": "Ð\9eÑ\81новне инÑ\84оÑ\80маÑ\86иÑ\98е",
"pageinfo-header-edits": "Историја измена",
"pageinfo-header-restrictions": "Заштита странице",
"pageinfo-header-properties": "Својства странице",
"pageinfo-robot-noindex": "Није дозвољено",
"pageinfo-watchers": "Број надгледача странице",
"pageinfo-visiting-watchers": "Број надгледача странице који су посетили скорашње измене",
- "pageinfo-few-watchers": "Ð\9cаÑ\9aе од $1 {{PLURAL:$1|пÑ\80аÑ\82иоÑ\86а|пÑ\80аÑ\82иоÑ\86а|пÑ\80аÑ\82илаÑ\86а}}",
+ "pageinfo-few-watchers": "Ð\9cаÑ\9aе од $1 {{PLURAL:$1|надгледаÑ\87а}}",
"pageinfo-redirects-name": "Број преусмерења на ову страницу",
"pageinfo-redirects-value": "$1",
- "pageinfo-subpages-name": "Ð\9fодÑ\81Ñ\82Ñ\80аниÑ\86е ове странице",
+ "pageinfo-subpages-name": "Ð\91Ñ\80оÑ\98 подÑ\81Ñ\82Ñ\80аниÑ\86а ове странице",
"pageinfo-subpages-value": "$1 ($2 {{PLURAL:$2|преусмерење|преусмерења|преусмерења}}; $3 {{PLURAL:$3|непреусмерење|непреусмерења|непреусмерења}})",
"pageinfo-firstuser": "Аутор странице",
"pageinfo-firsttime": "Датум стварања странице",
"svg-long-desc": "SVG датотека, номинално $1 × $2 пиксела, величина: $3",
"svg-long-desc-animated": "Анимирана SVG датотека, номинално: $1 × $2 пиксела, величина: $3",
"svg-long-error": "Неисправна SVG датотека: $1",
- "show-big-image": "Ð\9fÑ\83на велиÑ\87ина",
+ "show-big-image": "Ð\98звоÑ\80на даÑ\82оÑ\82ека",
"show-big-image-preview": "Величина овог приказа: $1.",
"show-big-image-preview-differ": "Величина $3 прегледа за ову $2 датотеку је $1.",
"show-big-image-other": "$2 {{PLURAL:$2|друга резолуција|друге резолуције|других резолуција}}: $1.",
"htmlform-title-not-exists": "$1 не постоји.",
"htmlform-user-not-exists": "<strong>$1</strong> не постоји.",
"htmlform-user-not-valid": "<strong>$1</strong> није исправно корисничко име.",
- "logentry-delete-delete": "$1 је {{GENDER:$2|обрисао|обрисала|обрисао}} страницу $3",
+ "logentry-delete-delete": "$1 је {{GENDER:$2|обрисао|обрисала}} страницу $3",
"logentry-delete-delete_redir": "$1 је {{GENDER:$2|обрисао|обрисала}} преусмерење $3 преписивањем",
"logentry-delete-restore": "$1 је {{GENDER:$2|вратио|вратила}} страницу $3 ($4)",
"logentry-delete-restore-nocount": "$1 је {{GENDER:$2|вратио|вратила}} страницу $3",
"dellogpage": "తొలగింపుల చిట్టా",
"dellogpagetext": "ఇది ఇటీవలి తుడిచివేతల జాబితా.",
"deletionlog": "తొలగింపుల చిట్టా",
+ "logentry-create-create": "$3 పేజీని $1 {{GENDER:$2|సృష్టించారు}}",
"reverted": "పాత కూర్పుకు తీసుకువెళ్ళాం.",
"deletecomment": "కారణం:",
"deleteotherreason": "ఇతర/అదనపు కారణం:",
"newimages-summary": "ఇటీవలే ఎగుమతైన ఫైళ్ళను ఈ ప్రత్యేక పేజీ చూపిస్తుంది.",
"newimages-legend": "పడపోత",
"newimages-label": "ఫైలుపేరు (లేదా దానిలోని భాగం):",
+ "newimages-user": "ఐపీ చిరునామా లేదా వాడుకరి పేరు",
+ "newimages-newbies": "కొత్త ఖాతాల రచనలని మాత్రమే చూపించు",
"newimages-showbots": "బాట్లు చేసిన అప్లోడ్లు చూపించు",
"newimages-mediatype": "మాధ్యమ రకం:",
"noimages": "చూసేందుకు ఏమీ లేదు.",
"compare-title-not-exists": "మీరు పేర్కొన్న శీర్షిక లేనే లేదు.",
"compare-revision-not-exists": "మీరు పేర్కొన్న కూర్పు లేనే లేదు.",
"diff-form": "తేడాలు",
+ "permanentlink": "స్థిర లంకె",
"dberr-problems": "క్షమించండి! ఈ సైటు సాంకేతిక సమస్యలని ఎదుర్కొంటుంది.",
"dberr-again": "కొన్ని నిమిషాలాగి మళ్ళీ ప్రయత్నించండి.",
"dberr-info": "(డేటాబేసును చేరలేకున్నాం: $1)",
"log-action-filter-managetags-delete": "ట్యాగు తొలగింపు",
"log-action-filter-managetags-activate": "ట్యాగు చేతనం",
"log-action-filter-managetags-deactivate": "ట్యాగు అచేతనం",
+ "log-action-filter-protect-protect": "సంరక్షణ",
+ "log-action-filter-upload-upload": "కొత్త ఎక్కింపు",
"authmanager-userdoesnotexist": "వాడుకరి ఖాతా \"$1\" నమోదయి లేదు.",
"authmanager-userlogin-remembermypassword-help": "సెషను ముగిసిన తరువాత కూడా సంకేతపదాన్ని గుర్తుంచుకోమంటారా",
"authmanager-username-help": "ధ్రువీకరణ కోసం వాడుకరిపేరు.",
"restrictionsfield-help": "వరుసకొక్క ఐపీ అడ్రసు లేదా CIDR శ్రేణి. ప్రతీ ఒక్కదాన్నీ చేతనం చేసేందుకు, వాడండి:<pre>0.0.0.0/0\n::/0</pre>",
"revid": "కూర్పు $1",
"pageid": "పేజీ ఐడీ $1",
+ "pagedata-bad-title": "చెల్లని శీర్షిక: $1.",
+ "passwordpolicies": "సంకేతపదపు విధానాలు",
+ "passwordpolicies-group": "సమూహం",
"passwordpolicies-policies": "విధానాలు"
}
"recentchangeslinked-feed": "متعلقہ تبدیلیاں",
"recentchangeslinked-toolbox": "متعلقہ تبدیلیاں",
"recentchangeslinked-title": "\"$1\" سے متعلقہ تبدیلیاں",
- "recentchangeslinked-summary": "یہ ان تبدیلیوں کی فہرست ہے جو حال ہی میں کسی مخصوص صفحہ سے مربوط صفحات (یا مخصوص زمرہ کے اراکین) میں کی گئی ہیں۔\n\n[[Special:Watchlist|آپ کی زیر نظر فہرست]] میں یہ صفحات <strong>جلی</strong نظر آئیں گےـ",
+ "recentchangeslinked-summary": "یہ ان تبدیلیوں کی فہرست ہے جو حال ہی میں کسی مخصوص صفحہ سے مربوط صفحات (یا مخصوص زمرہ کے اراکین) میں کی گئی ہیں۔\n\n[[Special:Watchlist|آپ کی زیر نظر فہرست]] میں یہ صفحات <strong>جلی</strong> نظر آئیں گےـ",
"recentchangeslinked-page": "صفحہ کا نام:",
"recentchangeslinked-to": "اس کی بجائے درج کردہ صفحہ سے مربوط صفحات کی تبدیلیاں دکھائیں",
"recentchanges-page-added-to-category": "[[:$1]] کو زمرہ میں شامل کیا گیا",
"right-override-export-depth": "匯出頁面包含連結內容,深度上限為 5 層",
"right-sendemail": "傳送電子郵件聯絡其他使用者",
"right-managechangetags": "建立並自資料庫 (取消) 啟用 [[Special:Tags|標籤]]",
- "right-applychangetags": "連同某個人的變更一起套用[[Special:Tags|標籤]]",
+ "right-applychangetags": "連同自己的變更一起套用[[Special:Tags|標籤]]",
"right-changetags": "加入與移除任何於各別修訂與日誌項目的[[Special:Tags|標籤]]",
"right-deletechangetags": "從資料庫刪除 [[Special:Tags|標籤]]",
"grant-generic": "\"$1\" 權限組合",
/**
* Do the actual work. All child classes will need to implement this
*
- * @return bool|null True for success, false for failure. Not returning
+ * @return bool|null|void True for success, false for failure. Not returning
* a value, or returning null, is also interpreted as success. Returning
* false for failure will cause doMaintenance.php to exit the process
* with a non-zero exit status.
$dbw = $this->lbFactory->getMainLB()->getConnection( DB_MASTER );
$sleep = (int)$this->getOption( 'sleep', 10 );
$lastId = 0;
- $this->output( "Starting to add ct_tag_id = {$tagId} for ct_tag = {$tagName}" );
+ $this->output( "Starting to add ct_tag_id = {$tagId} for ct_tag = {$tagName}\n" );
while ( true ) {
// Given that indexes might not be there, it's better to use replica
$ids = $dbr->selectFieldValues(
);
continue;
} else {
- $this->output( "Updating ct_tag_id = {$tagId} up to row ct_id = {$lastId}" );
+ $this->output( "Updating ct_tag_id = {$tagId} up to row ct_id = {$lastId}\n" );
}
$dbw->update(
}
}
- $this->output( "Finished adding ct_tag_id = {$tagId} for ct_tag = {$tagName}" );
+ $this->output( "Finished adding ct_tag_id = {$tagId} for ct_tag = {$tagName}\n" );
}
}
$t0 = microtime( true );
- if ( $wgMultiContentRevisionSchemaMigrationStage < MIGRATION_WRITE_BOTH ) {
+ if ( ( $wgMultiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) === 0 ) {
$this->writeln(
- "...cannot update while \$wgMultiContentRevisionSchemaMigrationStage < MIGRATION_WRITE_BOTH"
+ '...cannot update while \$wgMultiContentRevisionSchemaMigrationStage '
+ . 'does not have the SCHEMA_COMPAT_WRITE_NEW bit set.'
);
return false;
}
use MediaWiki\Logger\LegacyLogger;
use MediaWiki\MediaWikiServices;
+use Wikimedia\Rdbms\IDatabase;
$optionsWithArgs = RecompressTracked::getOptionsWithArgs();
require __DIR__ . '/../commandLine.inc';
/**
* Gets a DB master connection for the given external cluster name
* @param string $cluster
- * @return Database
+ * @return IDatabase
*/
function getExtDB( $cluster ) {
$lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
background-color: #eaecf0;
background-size: cover;
background-position: center center;
- padding: 1.5em;
- margin: -1.5em;
- margin-bottom: 1.5em;
+ /* Same as padding on `.oo-ui-bookletLayout-stackLayout > .oo-ui-panelLayout`,
+ * equals 20px at default font size */
+ padding: 1.42857143em;
+ margin: -1.42857143em;
+ margin-bottom: 1.42857143em;
position: relative;
}
'MediaWiki\Tests\Storage\McrSchemaDetection' => "$testDir/phpunit/includes/Storage/McrSchemaDetection.php",
'MediaWiki\Tests\Storage\McrSchemaOverride' => "$testDir/phpunit/includes/Storage/McrSchemaOverride.php",
'MediaWiki\Tests\Storage\McrWriteBothSchemaOverride' => "$testDir/phpunit/includes/Storage/McrWriteBothSchemaOverride.php",
+ 'MediaWiki\Tests\Storage\McrReadNewSchemaOverride' => "$testDir/phpunit/includes/Storage/McrReadNewSchemaOverride.php",
'MediaWiki\Tests\Storage\RevisionSlotsTest' => "$testDir/phpunit/includes/Storage/RevisionSlotsTest.php",
'MediaWiki\Tests\Storage\RevisionRecordTests' => "$testDir/phpunit/includes/Storage/RevisionRecordTests.php",
'MediaWiki\Tests\Storage\RevisionStoreDbTestBase' => "$testDir/phpunit/includes/Storage/RevisionStoreDbTestBase.php",
* @since 1.18
*/
public function dbPrefix() {
- return $this->db->getType() == 'oracle' ? self::ORA_DB_PREFIX : self::DB_PREFIX;
+ return self::getTestPrefixFor( $this->db );
+ }
+
+ /**
+ * @param IDatabase $db
+ * @return string
+ * @since 1.32
+ */
+ public static function getTestPrefixFor( IDatabase $db ) {
+ return $db->getType() == 'oracle' ? self::ORA_DB_PREFIX : self::DB_PREFIX;
}
/**
}
/**
- * Setups a database with the given prefix.
+ * Prepares the given database connection for usage in the context of usage tests.
+ * This sets up clones database tables and changes the table prefix as appropriate.
+ * If the database connection already has cloned tables, calling this method has no
+ * effect. The tables are not re-cloned or reset in that case.
+ *
+ * @param IMaintainableDatabase $db
+ */
+ protected function prepareConnectionForTesting( IMaintainableDatabase $db ) {
+ if ( !self::$dbSetup ) {
+ throw new LogicException(
+ 'Cannot use prepareConnectionForTesting()'
+ . ' if the test case is not defined to use the database!'
+ );
+ }
+
+ if ( isset( $db->_originalTablePrefix ) ) {
+ // The DB connection was already prepared for testing.
+ return;
+ }
+
+ $testPrefix = self::getTestPrefixFor( $db );
+ $oldPrefix = $db->tablePrefix();
+
+ $tablesCloned = self::listTables( $db );
+
+ if ( $oldPrefix === $testPrefix ) {
+ // The database connection already has the test prefix, but presumably not
+ // the cloned tables. This is the typical case, since the LBFactory will
+ // have the prefix set during testing, but LoadBalancers will still return
+ // connections that don't have the cloned table structure.
+ $oldPrefix = self::$oldTablePrefix;
+ }
+
+ $dbClone = new CloneDatabase( $db, $tablesCloned, $testPrefix, $oldPrefix );
+ $dbClone->useTemporaryTables( self::$useTemporaryTables );
+
+ $db->_originalTablePrefix = $oldPrefix;
+
+ if ( ( $db->getType() == 'oracle' || !self::$useTemporaryTables ) && self::$reuseDB ) {
+ throw new LogicException( 'Cannot clone database tables' );
+ } else {
+ $dbClone->cloneTableStructure();
+ }
+ }
+
+ /**
+ * Setups a database with cloned tables using the given prefix.
*
* If reuseDB is true and certain conditions apply, it will just change the prefix.
* Otherwise, it will clone the tables and change the prefix.
*
- * Clones all tables in the given database (whatever database that connection has
- * open), to versions with the test prefix.
- *
* @param IMaintainableDatabase $db Database to use
- * @param string $prefix Prefix to use for test tables
+ * @param string $prefix Prefix to use for test tables. If not given, the prefix is determined
+ * automatically for $db.
* @return bool True if tables were cloned, false if only the prefix was changed
*/
- protected static function setupDatabaseWithTestPrefix( IMaintainableDatabase $db, $prefix ) {
- $tablesCloned = self::listTables( $db );
- $dbClone = new CloneDatabase( $db, $tablesCloned, $prefix );
- $dbClone->useTemporaryTables( self::$useTemporaryTables );
-
- $db->_originalTablePrefix = $db->tablePrefix();
+ protected static function setupDatabaseWithTestPrefix(
+ IMaintainableDatabase $db,
+ $prefix = null
+ ) {
+ if ( $prefix === null ) {
+ $prefix = self::getTestPrefixFor( $db );
+ }
if ( ( $db->getType() == 'oracle' || !self::$useTemporaryTables ) && self::$reuseDB ) {
- CloneDatabase::changePrefix( $prefix );
-
+ $db->tablePrefix( $prefix );
return false;
- } else {
+ }
+
+ if ( !isset( $db->_originalTablePrefix ) ) {
+ $oldPrefix = $db->tablePrefix();
+
+ if ( $oldPrefix === $prefix ) {
+ // table already has the correct prefix, but presumably no cloned tables
+ $oldPrefix = self::$oldTablePrefix;
+ }
+
+ $db->tablePrefix( $oldPrefix );
+ $tablesCloned = self::listTables( $db );
+ $dbClone = new CloneDatabase( $db, $tablesCloned, $prefix, $oldPrefix );
+ $dbClone->useTemporaryTables( self::$useTemporaryTables );
+
$dbClone->cloneTableStructure();
- return true;
+
+ $db->tablePrefix( $prefix );
+ $db->_originalTablePrefix = $oldPrefix;
}
+
+ return true;
}
/**
if ( self::isUsingExternalStoreDB() ) {
self::setupExternalStoreTestDBs( $testPrefix );
}
+
+ // NOTE: Change the prefix in the LBFactory and $wgDBprefix, to prevent
+ // *any* database connections to operate on live data.
+ CloneDatabase::changePrefix( $testPrefix );
}
/**
/**
* Clones the External Store database(s) for testing
*
- * @param string $testPrefix Prefix for test tables
+ * @param string|null $testPrefix Prefix for test tables. Will be determined automatically
+ * if not given.
*/
- protected static function setupExternalStoreTestDBs( $testPrefix ) {
+ protected static function setupExternalStoreTestDBs( $testPrefix = null ) {
$connections = self::getExternalStoreDatabaseConnections();
foreach ( $connections as $dbw ) {
- // Hack: cloneTableStructure sets $wgDBprefix to the unit test
- // prefix,. Even though listTables now uses tablePrefix, that
- // itself is populated from $wgDBprefix by default.
-
- // We have to set it back, or we won't find the original 'blobs'
- // table to copy.
-
- $dbw->tablePrefix( self::$oldTablePrefix );
self::setupDatabaseWithTestPrefix( $dbw, $testPrefix );
}
}
return $tables;
}
+ /**
+ * Copy test data from one database connection to another.
+ *
+ * This should only be used for small data sets.
+ *
+ * @param IDatabase $source
+ * @param IDatabase $target
+ */
+ public function copyTestData( IDatabase $source, IDatabase $target ) {
+ $tables = self::listOriginalTables( $source, 'unprefixed' );
+
+ foreach ( $tables as $table ) {
+ $res = $source->select( $table, '*', [], __METHOD__ );
+ $allRows = [];
+
+ foreach ( $res as $row ) {
+ $allRows[] = (array)$row;
+ }
+
+ $target->insert( $table, $allRows, __METHOD__, [ 'IGNORE' ] );
+ }
+ }
+
/**
* @throws MWException
* @since 1.18
}
$stages = [
- MIGRATION_OLD => [ MIGRATION_OLD, MIGRATION_WRITE_NEW ],
- MIGRATION_WRITE_BOTH => [ MIGRATION_OLD, MIGRATION_NEW ],
- MIGRATION_WRITE_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_NEW ],
- MIGRATION_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_NEW ],
+ MIGRATION_OLD => [ MIGRATION_OLD, MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW ],
+ MIGRATION_WRITE_BOTH => [ MIGRATION_OLD, MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW,
+ MIGRATION_NEW ],
+ MIGRATION_WRITE_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ],
+ MIGRATION_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ],
];
$nameKey = $key . '_text';
$actorKey = $key === 'ipb_by' ? 'ipb_by_actor' : substr( $key, 0, -5 ) . '_actor';
- foreach ( $stages as $writeStage => $readRange ) {
+ foreach ( $stages as $writeStage => $possibleReadStages ) {
if ( $key === 'ipb_by' ) {
$extraFields['ipb_address'] = __CLASS__ . "#$writeStage";
}
$callback( $id, $extraFields );
}
- for ( $readStage = $readRange[0]; $readStage <= $readRange[1]; $readStage++ ) {
+ foreach ( $possibleReadStages as $readStage ) {
$r = $this->makeMigration( $readStage );
$queryInfo = $r->getJoin( $key );
];
$stages = [
- MIGRATION_OLD => [ MIGRATION_OLD, MIGRATION_WRITE_NEW ],
- MIGRATION_WRITE_BOTH => [ MIGRATION_OLD, MIGRATION_NEW ],
- MIGRATION_WRITE_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_NEW ],
- MIGRATION_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_NEW ],
+ MIGRATION_OLD => [ MIGRATION_OLD, MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW ],
+ MIGRATION_WRITE_BOTH => [ MIGRATION_OLD, MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW,
+ MIGRATION_NEW ],
+ MIGRATION_WRITE_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ],
+ MIGRATION_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ],
];
- foreach ( $stages as $writeStage => $readRange ) {
+ foreach ( $stages as $writeStage => $possibleReadStages ) {
if ( $key === 'ipb_reason' ) {
$extraFields['ipb_address'] = __CLASS__ . "#$writeStage";
}
$callback( $id );
}
- for ( $readStage = $readRange[0]; $readStage <= $readRange[1]; $readStage++ ) {
+ foreach ( $possibleReadStages as $readStage ) {
$rstore = $this->makeStore( $readStage );
$fieldRow = $this->db->selectRow(
];
$stages = [
- MIGRATION_OLD => [ MIGRATION_OLD, MIGRATION_WRITE_NEW ],
- MIGRATION_WRITE_BOTH => [ MIGRATION_OLD, MIGRATION_NEW ],
- MIGRATION_WRITE_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_NEW ],
- MIGRATION_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_NEW ],
+ MIGRATION_OLD => [ MIGRATION_OLD, MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW ],
+ MIGRATION_WRITE_BOTH => [ MIGRATION_OLD, MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW,
+ MIGRATION_NEW ],
+ MIGRATION_WRITE_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ],
+ MIGRATION_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ],
];
- foreach ( $stages as $writeStage => $readRange ) {
+ foreach ( $stages as $writeStage => $possibleReadStages ) {
if ( $key === 'ipb_reason' ) {
$extraFields['ipb_address'] = __CLASS__ . "#$writeStage";
}
$callback( $id );
}
- for ( $readStage = $readRange[0]; $readStage <= $readRange[1]; $readStage++ ) {
+ foreach ( $possibleReadStages as $readStage ) {
$rstore = $this->makeStoreWithKey( $readStage, $key );
$fieldRow = $this->db->selectRow(
use MediaWiki\Storage\RevisionFactory;
use MediaWiki\Storage\RevisionLookup;
use MediaWiki\Storage\RevisionStore;
-use MediaWiki\Storage\RevisionStoreFactory;
use MediaWiki\Storage\SqlBlobStore;
/**
'BlobStore' => [ 'BlobStore', BlobStore::class ],
'_SqlBlobStore' => [ '_SqlBlobStore', SqlBlobStore::class ],
'RevisionStore' => [ 'RevisionStore', RevisionStore::class ],
- 'RevisionStoreFactory' => [ 'RevisionStoreFactory', RevisionStoreFactory::class ],
'RevisionLookup' => [ 'RevisionLookup', RevisionLookup::class ],
'RevisionFactory' => [ 'RevisionFactory', RevisionFactory::class ],
'ContentModelStore' => [ 'ContentModelStore', NameTableStore::class ],
$this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', MIGRATION_OLD );
$this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_OLD );
+ $this->setMwGlobals( 'wgMultiContentRevisionSchemaMigrationStage', SCHEMA_COMPAT_OLD );
$this->overrideMwServices();
// First create our dummy page
*/
public function testListRevisions() {
$this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', MIGRATION_OLD );
+ $this->setMwGlobals( 'wgMultiContentRevisionSchemaMigrationStage', SCHEMA_COMPAT_OLD );
$this->overrideMwServices();
$revisions = $this->archivedPage->listRevisions();
$rev = $this->testPage->getRevision();
// Clear any previous cache for the revision during creation
- $key = $cache->makeGlobalKey( RevisionStore::ROW_CACHE_KEY,
+ $key = $cache->makeGlobalKey(
+ RevisionStore::ROW_CACHE_KEY,
$db->getDomainID(),
$rev->getPage(),
$rev->getId()
--- /dev/null
+<?php
+use MediaWiki\Tests\Storage\McrReadNewSchemaOverride;
+
+/**
+ * Tests Revision against the intermediate MCR DB schema for use during schema migration.
+ *
+ * @covers Revision
+ *
+ * @group Revision
+ * @group Storage
+ * @group ContentHandler
+ * @group Database
+ * @group medium
+ */
+class RevisionMcrReadNewDbTest extends RevisionDbTestBase {
+
+ use McrReadNewSchemaOverride;
+
+ protected function getContentHandlerUseDB() {
+ return true;
+ }
+
+}
)
);
- $cacheKey = $cache->makeKey( 'revisiontext', 'textid', 'tt:7777' );
+ $cacheKey = $cache->makeGlobalKey(
+ 'BlobStore',
+ 'address',
+ $lb->getLocalDomainID(),
+ 'tt:7777'
+ );
$this->assertSame( 'AAAABBAAA', $cache->get( $cacheKey ) );
}
--- /dev/null
+<?php
+namespace MediaWiki\Tests\Storage;
+
+use CommentStoreComment;
+use MediaWiki\MediaWikiServices;
+use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Storage\SlotRecord;
+use TextContent;
+use Title;
+use WikitextContent;
+
+/**
+ * Tests RevisionStore against the intermediate MCR DB schema for use during schema migration.
+ *
+ * @covers \MediaWiki\Storage\RevisionStore
+ *
+ * @group RevisionStore
+ * @group Storage
+ * @group Database
+ * @group medium
+ */
+class McrReadNewRevisionStoreDbTest extends RevisionStoreDbTestBase {
+
+ use McrReadNewSchemaOverride;
+
+ protected function assertRevisionExistsInDatabase( RevisionRecord $rev ) {
+ $numberOfSlots = count( $rev->getSlotRoles() );
+
+ // new schema is written
+ $this->assertSelect(
+ 'slots',
+ [ 'count(*)' ],
+ [ 'slot_revision_id' => $rev->getId() ],
+ [ [ (string)$numberOfSlots ] ]
+ );
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $revQuery = $store->getSlotsQueryInfo( [ 'content' ] );
+
+ $this->assertSelect(
+ $revQuery['tables'],
+ [ 'count(*)' ],
+ [
+ 'slot_revision_id' => $rev->getId(),
+ ],
+ [ [ (string)$numberOfSlots ] ],
+ [],
+ $revQuery['joins']
+ );
+
+ // Legacy schema is still being written
+ $this->assertSelect(
+ [ 'revision', 'text' ],
+ [ 'count(*)' ],
+ [ 'rev_id' => $rev->getId(), 'rev_text_id > 0' ],
+ [ [ 1 ] ],
+ [],
+ [ 'text' => [ 'INNER JOIN', [ 'rev_text_id = old_id' ] ] ]
+ );
+
+ parent::assertRevisionExistsInDatabase( $rev );
+ }
+
+ /**
+ * @param SlotRecord $a
+ * @param SlotRecord $b
+ */
+ protected function assertSameSlotContent( SlotRecord $a, SlotRecord $b ) {
+ parent::assertSameSlotContent( $a, $b );
+
+ // Assert that the same content ID has been used
+ $this->assertSame( $a->getContentId(), $b->getContentId() );
+ }
+
+ public function provideInsertRevisionOn_successes() {
+ foreach ( parent::provideInsertRevisionOn_successes() as $case ) {
+ yield $case;
+ }
+
+ yield 'Multi-slot revision insertion' => [
+ [
+ 'content' => [
+ 'main' => new WikitextContent( 'Chicken' ),
+ 'aux' => new TextContent( 'Egg' ),
+ ],
+ 'page' => true,
+ 'comment' => $this->getRandomCommentStoreComment(),
+ 'timestamp' => '20171117010101',
+ 'user' => true,
+ ],
+ ];
+ }
+
+ public function provideNewNullRevision() {
+ foreach ( parent::provideNewNullRevision() as $case ) {
+ yield $case;
+ }
+
+ yield [
+ Title::newFromText( 'UTPage_notAutoCreated' ),
+ [
+ 'content' => [
+ 'main' => new WikitextContent( 'Chicken' ),
+ 'aux' => new WikitextContent( 'Omelet' ),
+ ],
+ ],
+ CommentStoreComment::newUnsavedComment( __METHOD__ . ' comment multi' ),
+ ];
+ }
+
+ public function testGetQueryInfo_NoSlotDataJoin() {
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $queryInfo = $store->getQueryInfo();
+
+ // with the new schema enabled, query info should not join the main slot info
+ $this->assertFalse( array_key_exists( 'a_slot_data', $queryInfo['tables'] ) );
+ $this->assertFalse( array_key_exists( 'a_slot_data', $queryInfo['joins'] ) );
+ }
+
+ public function provideGetArchiveQueryInfo() {
+ yield [
+ [
+ 'tables' => [
+ 'archive',
+ ],
+ 'fields' => array_merge(
+ $this->getDefaultArchiveFields( false ),
+ [
+ 'ar_comment_text' => 'ar_comment',
+ 'ar_comment_data' => 'NULL',
+ 'ar_comment_cid' => 'NULL',
+ 'ar_user_text' => 'ar_user_text',
+ 'ar_user' => 'ar_user',
+ 'ar_actor' => 'NULL',
+ ]
+ ),
+ 'joins' => [
+ ],
+ ]
+ ];
+ }
+
+ public function provideGetQueryInfo() {
+ // TODO: more option variations
+ yield [
+ [ 'page', 'user' ],
+ [
+ 'tables' => [
+ 'revision',
+ 'page',
+ 'user',
+ ],
+ 'fields' => array_merge(
+ $this->getDefaultQueryFields( false ),
+ $this->getCommentQueryFields(),
+ $this->getActorQueryFields(),
+ [
+ 'page_namespace',
+ 'page_title',
+ 'page_id',
+ 'page_latest',
+ 'page_is_redirect',
+ 'page_len',
+ 'user_name',
+ ]
+ ),
+ 'joins' => [
+ 'page' => [ 'INNER JOIN', [ 'page_id = rev_page' ] ],
+ 'user' => [ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] ],
+ ],
+ ]
+ ];
+ }
+
+ public function provideGetSlotsQueryInfo() {
+ yield [
+ [],
+ [
+ 'tables' => [
+ 'slots',
+ 'slot_roles',
+ ],
+ 'fields' => array_merge(
+ [
+ 'slot_revision_id',
+ 'slot_content_id',
+ 'slot_origin',
+ 'role_name',
+ ]
+ ),
+ 'joins' => [
+ 'slot_roles' => [ 'INNER JOIN', [ 'slot_role_id = role_id' ] ],
+ ],
+ ]
+ ];
+ yield [
+ [ 'content' ],
+ [
+ 'tables' => [
+ 'slots',
+ 'slot_roles',
+ 'content',
+ 'content_models',
+ ],
+ 'fields' => array_merge(
+ [
+ 'slot_revision_id',
+ 'slot_content_id',
+ 'slot_origin',
+ 'role_name',
+ 'content_size',
+ 'content_sha1',
+ 'content_address',
+ 'model_name',
+ ]
+ ),
+ 'joins' => [
+ 'slot_roles' => [ 'INNER JOIN', [ 'slot_role_id = role_id' ] ],
+ 'content' => [ 'INNER JOIN', [ 'slot_content_id = content_id' ] ],
+ 'content_models' => [ 'INNER JOIN', [ 'content_model = model_id' ] ],
+ ],
+ ]
+ ];
+ }
+
+ public function provideNewMutableRevisionFromArray() {
+ foreach ( parent::provideNewMutableRevisionFromArray() as $case ) {
+ yield $case;
+ }
+
+ yield 'Basic array, multiple roles' => [
+ [
+ 'id' => 2,
+ 'page' => 1,
+ 'timestamp' => '20171017114835',
+ 'user_text' => '111.0.1.2',
+ 'user' => 0,
+ 'minor_edit' => false,
+ 'deleted' => 0,
+ 'len' => 29,
+ 'parent_id' => 1,
+ 'sha1' => '89qs83keq9c9ccw9olvvm4oc9oq50ii',
+ 'comment' => 'Goat Comment!',
+ 'content' => [
+ 'main' => new WikitextContent( 'Söme Cöntent' ),
+ 'aux' => new TextContent( 'Öther Cöntent' ),
+ ]
+ ]
+ ];
+ }
+
+}
--- /dev/null
+<?php
+namespace MediaWiki\Tests\Storage;
+
+use Wikimedia\Rdbms\IMaintainableDatabase;
+use MediaWiki\DB\PatchFileLocation;
+
+/**
+ * Trait providing schema overrides that allow tests to run against the intermediate MCR database
+ * schema for use during schema migration.
+ */
+trait McrReadNewSchemaOverride {
+
+ use PatchFileLocation;
+ use McrSchemaDetection;
+
+ /**
+ * @return int
+ */
+ protected function getMcrMigrationStage() {
+ return SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW;
+ }
+
+ /**
+ * @return string[]
+ */
+ protected function getMcrTablesToReset() {
+ return [ 'content', 'content_models', 'slots', 'slot_roles' ];
+ }
+
+ /**
+ * @override MediaWikiTestCase::getSchemaOverrides
+ * @return array[]
+ */
+ protected function getSchemaOverrides( IMaintainableDatabase $db ) {
+ $overrides = [
+ 'scripts' => [],
+ 'drop' => [],
+ 'create' => [],
+ 'alter' => [],
+ ];
+
+ if ( !$this->hasMcrTables( $db ) ) {
+ $overrides['create'] = [ 'slots', 'content', 'slot_roles', 'content_models', ];
+ $overrides['scripts'][] = $this->getSqlPatchPath( $db, 'patch-slot_roles' );
+ $overrides['scripts'][] = $this->getSqlPatchPath( $db, 'patch-content_models' );
+ $overrides['scripts'][] = $this->getSqlPatchPath( $db, 'patch-content' );
+ $overrides['scripts'][] = $this->getSqlPatchPath( $db, 'patch-slots' );
+ }
+
+ if ( !$this->hasPreMcrFields( $db ) ) {
+ $overrides['alter'][] = 'revision';
+ $overrides['scripts'][] = $this->getSqlPatchPath( $db, 'create-pre-mcr-fields', __DIR__ );
+ }
+
+ return $overrides;
+ }
+
+}
protected function assertRevisionExistsInDatabase( RevisionRecord $rev ) {
$numberOfSlots = count( $rev->getSlotRoles() );
+ // new schema is written
$this->assertSelect(
'slots',
[ 'count(*)' ],
$revQuery['joins']
);
- $this->assertSelect(
- 'content',
- [ 'count(*)' ],
- [ 'content_address' => $rev->getSlot( 'main' )->getAddress() ],
- [ [ 1 ] ]
- );
-
parent::assertRevisionExistsInDatabase( $rev );
}
}
protected function assertRevisionExistsInDatabase( RevisionRecord $rev ) {
+ // New schema is being written
$this->assertSelect(
'slots',
[ 'count(*)' ],
[ [ '1' ] ]
);
+ // Legacy schema is still being written
+ $this->assertSelect(
+ [ 'revision', 'text' ],
+ [ 'count(*)' ],
+ [ 'rev_id' => $rev->getId(), 'rev_text_id > 0' ],
+ [ [ 1 ] ],
+ [],
+ [ 'text' => [ 'INNER JOIN', [ 'rev_text_id = old_id' ] ] ]
+ );
+
parent::assertRevisionExistsInDatabase( $rev );
}
'role_name' => $db->addQuotes( 'main' ),
'content_size' => 'slots.rev_len',
'content_sha1' => 'slots.rev_sha1',
- 'content_address' =>
- 'CONCAT(' . $db->addQuotes( 'tt:' ) . ',slots.rev_text_id)',
+ 'content_address' => $db->buildConcat( [
+ $db->addQuotes( 'tt:' ), 'slots.rev_text_id' ] ),
'model_name' => 'slots.rev_content_model',
]
),
* @return int
*/
protected function getMcrMigrationStage() {
- return MIGRATION_WRITE_BOTH;
+ return SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD;
}
/**
'content_size' => 'slots.rev_len',
'content_sha1' => 'slots.rev_sha1',
'content_address' =>
- 'CONCAT(' . $db->addQuotes( 'tt:' ) . ',slots.rev_text_id)',
+ $db->buildConcat( [ $db->addQuotes( 'tt:' ), 'slots.rev_text_id' ] ),
'model_name' => 'NULL',
]
),
namespace MediaWiki\Tests\Storage;
use InvalidArgumentException;
+use MediaWiki\Storage\RevisionRecord;
use Revision;
use WikitextContent;
return $row;
}
+ protected function assertRevisionExistsInDatabase( RevisionRecord $rev ) {
+ // Legacy schema is still being written
+ $this->assertSelect(
+ [ 'revision', 'text' ],
+ [ 'count(*)' ],
+ [ 'rev_id' => $rev->getId(), 'rev_text_id > 0' ],
+ [ [ 1 ] ],
+ [],
+ [ 'text' => [ 'INNER JOIN', [ 'rev_text_id = old_id' ] ] ]
+ );
+
+ parent::assertRevisionExistsInDatabase( $rev );
+ }
+
public function provideGetArchiveQueryInfo() {
yield [
[
'content_size' => 'slots.rev_len',
'content_sha1' => 'slots.rev_sha1',
'content_address' =>
- 'CONCAT(' . $db->addQuotes( 'tt:' ) . ',slots.rev_text_id)',
+ $db->buildConcat( [ $db->addQuotes( 'tt:' ), 'slots.rev_text_id' ] ),
'model_name' => 'slots.rev_content_model',
]
),
+++ /dev/null
-<?php
-
-namespace MediaWiki\Tests\Storage;
-
-use ActorMigration;
-use CommentStore;
-use MediaWiki\Logger\LoggerFactory;
-use MediaWiki\Storage\NameTableStore;
-use MediaWiki\Storage\RevisionStore;
-use MediaWiki\Storage\RevisionStoreFactory;
-use MediaWiki\Storage\SqlBlobStore;
-use MediaWikiTestCase;
-use WANObjectCache;
-use Wikimedia\Rdbms\LoadBalancer;
-use Wikimedia\TestingAccessWrapper;
-
-class RevisionStoreFactoryTest extends MediaWikiTestCase {
-
- public function testValidConstruction_doesntCauseErrors() {
- new RevisionStoreFactory(
- $this->getMockLoadBalancer(),
- $this->getMockSqlBlobStore(),
- $this->getHashWANObjectCache(),
- $this->getMockCommentStore(),
- $this->getMockNameTableStore(),
- $this->getMockNameTableStore(),
- MIGRATION_OLD,
- ActorMigration::newMigration(),
- LoggerFactory::getInstance( 'someInstance' ),
- true
- );
- $this->assertTrue( true );
- }
-
- public function provideWikiIds() {
- yield [ true ];
- yield [ false ];
- yield [ 'somewiki' ];
- yield [ 'somewiki', MIGRATION_OLD , false ];
- yield [ 'somewiki', MIGRATION_NEW , true ];
- }
-
- /**
- * @dataProvider provideWikiIds
- */
- public function testGetRevisionStore(
- $wikiId,
- $mcrMigrationStage = MIGRATION_OLD,
- $contentHandlerUseDb = true
- ) {
- $lb = $this->getMockLoadBalancer();
- $blobStore = $this->getMockSqlBlobStore();
- $cache = $this->getHashWANObjectCache();
- $commentStore = $this->getMockCommentStore();
- $contentModelStore = $this->getMockNameTableStore();
- $slotRoleStore = $this->getMockNameTableStore();
- $actorMigration = ActorMigration::newMigration();
- $logger = LoggerFactory::getInstance( 'someInstance' );
-
- $factory = new RevisionStoreFactory(
- $lb,
- $blobStore,
- $cache,
- $commentStore,
- $contentModelStore,
- $slotRoleStore,
- $mcrMigrationStage,
- $actorMigration,
- $logger,
- $contentHandlerUseDb
- );
-
- $store = $factory->getRevisionStore( $wikiId );
- $wrapper = TestingAccessWrapper::newFromObject( $store );
-
- // ensure the correct object type is returned
- $this->assertInstanceOf( RevisionStore::class, $store );
-
- // ensure the RevisionStore is for the given wikiId
- $this->assertSame( $wikiId, $wrapper->wikiId );
-
- // ensure all other required services are correctly set
- $this->assertSame( $lb, $wrapper->loadBalancer );
- $this->assertSame( $blobStore, $wrapper->blobStore );
- $this->assertSame( $cache, $wrapper->cache );
- $this->assertSame( $commentStore, $wrapper->commentStore );
- $this->assertSame( $contentModelStore, $wrapper->contentModelStore );
- $this->assertSame( $slotRoleStore, $wrapper->slotRoleStore );
- $this->assertSame( $mcrMigrationStage, $wrapper->mcrMigrationStage );
- $this->assertSame( $actorMigration, $wrapper->actorMigration );
- $this->assertSame( $logger, $wrapper->logger );
- $this->assertSame( $contentHandlerUseDb, $store->getContentHandlerUseDB() );
- }
-
- /**
- * @return \PHPUnit_Framework_MockObject_MockObject|NameTableStore
- */
- private function getMockNameTableStore() {
- return $this->getMockBuilder( NameTableStore::class )
- ->disableOriginalConstructor()->getMock();
- }
-
- /**
- * @return \PHPUnit_Framework_MockObject_MockObject|LoadBalancer
- */
- private function getMockLoadBalancer() {
- return $this->getMockBuilder( LoadBalancer::class )
- ->disableOriginalConstructor()->getMock();
- }
-
- /**
- * @return \PHPUnit_Framework_MockObject_MockObject|SqlBlobStore
- */
- private function getMockSqlBlobStore() {
- return $this->getMockBuilder( SqlBlobStore::class )
- ->disableOriginalConstructor()->getMock();
- }
-
- /**
- * @return \PHPUnit_Framework_MockObject_MockObject|CommentStore
- */
- private function getMockCommentStore() {
- return $this->getMockBuilder( CommentStore::class )
- ->disableOriginalConstructor()->getMock();
- }
-
- private function getHashWANObjectCache() {
- return new WANObjectCache( [ 'cache' => new \HashBagOStuff() ] );
- }
-
-}
public function provideSetContentHandlerUseDB() {
return [
- // ContentHandlerUseDB can be true of false pre migration
- [ false, MIGRATION_OLD, false ],
- [ true, MIGRATION_OLD, false ],
- // During migration it can not be false
- [ false, MIGRATION_WRITE_BOTH, true ],
- // But it can be true
- [ true, MIGRATION_WRITE_BOTH, false ],
+ // ContentHandlerUseDB can be true of false pre migration.
+ [ false, SCHEMA_COMPAT_OLD, false ],
+ [ true, SCHEMA_COMPAT_OLD, false ],
+ // During and after migration it can not be false...
+ [ false, SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, true ],
+ [ false, SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, true ],
+ [ false, SCHEMA_COMPAT_NEW, true ],
+ // ...but it can be true.
+ [ true, SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, false ],
+ [ true, SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, false ],
+ [ true, SCHEMA_COMPAT_NEW, false ],
];
}
public function provideMigrationConstruction() {
return [
- [ MIGRATION_OLD, false ],
- [ MIGRATION_WRITE_BOTH, false ],
+ [ SCHEMA_COMPAT_OLD, false ],
+ [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, false ],
+ [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, false ],
+ [ SCHEMA_COMPAT_NEW, false ],
+ [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_BOTH, true ],
+ [ SCHEMA_COMPAT_WRITE_OLD | SCHEMA_COMPAT_READ_BOTH, true ],
+ [ SCHEMA_COMPAT_WRITE_NEW | SCHEMA_COMPAT_READ_BOTH, true ],
];
}
];
}
+ /**
+ * @covers LBFactory::getLocalDomainID()
+ * @covers LBFactory::resolveDomainID()
+ */
public function testLBFactorySimpleServer() {
global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
$dbr = $lb->getConnection( DB_REPLICA );
$this->assertTrue( $dbr->getLBInfo( 'master' ), 'DB_REPLICA also gets the master' );
+ $this->assertSame( 'my_test_wiki', $factory->resolveDomainID( 'my_test_wiki' ) );
+ $this->assertSame( $factory->getLocalDomainID(), $factory->resolveDomainID( false ) );
+
$factory->shutdown();
$lb->closeAll();
}
];
}
+ /**
+ * @covers LoadBalancer::getLocalDomainID()
+ * @covers LoadBalancer::resolveDomainID()
+ */
public function testWithoutReplica() {
global $wgDBname;
$ld = DatabaseDomain::newFromId( $lb->getLocalDomainID() );
$this->assertEquals( $wgDBname, $ld->getDatabase(), 'local domain DB set' );
$this->assertEquals( $this->dbPrefix(), $ld->getTablePrefix(), 'local domain prefix set' );
+ $this->assertSame( 'my_test_wiki', $lb->resolveDomainID( 'my_test_wiki' ) );
+ $this->assertSame( $ld->getId(), $lb->resolveDomainID( false ) );
+ $this->assertSame( $ld->getId(), $lb->resolveDomainID( $ld ) );
$this->assertFalse( $called );
$dbw = $lb->getConnection( DB_MASTER );
--- /dev/null
+<?php
+use MediaWiki\Tests\Storage\McrReadNewSchemaOverride;
+
+/**
+ * Tests WikiPage against the intermediate MCR DB schema for use during schema migration.
+ *
+ * @covers WikiPage
+ *
+ * @group WikiPage
+ * @group Storage
+ * @group ContentHandler
+ * @group Database
+ * @group medium
+ */
+class WikiPageMcrReadNewDbTest extends WikiPageDbTestBase {
+
+ use McrReadNewSchemaOverride;
+
+ protected function getContentHandlerUseDB() {
+ return true;
+ }
+
+}