'maintenance/language/',
// External class
'includes/libs/jsminplus.php',
+ // External class
+ 'includes/libs/objectcache/utils/MemcachedClient.php',
];
+// NOTE: If you're facing an issue which you cannot easily fix, DO NOT add it here. Suppress it
+// either in-line with @phan-suppress-next-line and similar, at block-level (via @suppress), or at
+// file-level (with @phan-file-suppress), so that it stays enabled for the rest of the codebase.
$cfg['suppress_issue_types'] = array_merge( $cfg['suppress_issue_types'], [
// approximate error count: 19
"PhanParamReqAfterOpt", // False positives with nullables (phan issue #3159). Use real nullables
// approximate error count: 110
"PhanParamTooMany", // False positives with variargs. Unsuppress after dropping HHVM
- // approximate error count: 60
+ // approximate error count: 45
"PhanTypeMismatchArgument",
- // approximate error count: 752
+ // approximate error count: 693
"PhanUndeclaredProperty",
] );
+// This helps a lot in discovering bad code, but unfortunately it will always fail for
+// hooks + pass by reference, see phan issue #2943.
+// @todo Enable when the issue above is resolved and we update our config!
+$cfg['redundant_condition_detection'] = false;
+
$cfg['ignore_undeclared_variables_in_global_scope'] = true;
// @todo It'd be great if we could just make phan read these from DefaultSettings, to avoid
// duplicating the types.
'wgWANObjectCaches' => 'array[]',
'wgLocalInterwikis' => 'string[]',
'wgDebugLogGroups' => 'string|false|array{destination:string,sample?:int,level:int}',
+ 'wgCookiePrefix' => 'string|false',
+ 'wgOut' => 'OutputPage',
+ 'wgExtraNamespaces' => 'string[]',
] );
return $cfg;
"wikimedia/cldr-plural-rule-parser": "1.0.0",
"wikimedia/composer-merge-plugin": "1.4.1",
"wikimedia/html-formatter": "1.0.2",
- "wikimedia/ip-set": "2.0.1",
+ "wikimedia/ip-set": "2.1.0",
"wikimedia/less.php": "1.8.0",
"wikimedia/object-factory": "2.1.0",
"wikimedia/password-blacklist": "0.1.4",
"wikimedia/avro": "1.8.0",
"wikimedia/testing-access-wrapper": "~1.0",
"wmde/hamcrest-html-matchers": "^0.1.0",
- "mediawiki/mediawiki-phan-config": "0.6.1",
+ "mediawiki/mediawiki-phan-config": "0.7.1",
"symfony/yaml": "3.4.28",
"johnkary/phpunit-speedtrap": "^1.0 | ^2.0"
},
} elseif ( !$status->isOK() ) {
# ...or the hook could be expecting us to produce an error
// FIXME this sucks, we should just use the Status object throughout
+ if ( !$status->getErrors() ) {
+ // Provide a fallback error message if none was set
+ $status->fatal( 'hookaborted' );
+ }
$this->hookError = $this->formatStatusErrors( $status );
- $status->fatal( 'hookaborted' );
$status->value = self::AS_HOOK_ERROR_EXPECTED;
return false;
}
}
$includeStderr = isset( $options['duplicateStderr'] ) && $options['duplicateStderr'];
- // @phan-suppress-next-line PhanTypeInvalidDimOffset
$profileMethod = $options['profileMethod'] ?? wfGetCaller();
try {
// Give site config file a chance to run the script in a wrapper.
// The caller may likely want to call wfBasename() on $script.
Hooks::run( 'wfShellWikiCmd', [ &$script, &$parameters, &$options ] );
- // @phan-suppress-next-line PhanTypeInvalidDimOffset
$cmd = [ $options['php'] ?? $wgPhpCli ];
if ( isset( $options['wrapper'] ) ) {
$cmd[] = $options['wrapper'];
$result = unpack( $format, $data );
Wikimedia\restoreWarnings();
- // @phan-suppress-next-line PhanTypeComparisonFromArray Phan issue #3160
if ( $result === false ) {
// If it cannot extract the packed data.
throw new MWException( "unpack could not unpack binary data" );
throw new InvalidArgumentException( 'Mismatching wiki ID ' . $rev->getWikiId() );
}
- // @phan-suppress-next-line PhanTypeInvalidDimOffset
$audience = $hints['audience']
?? ( $forUser ? RevisionRecord::FOR_THIS_USER : RevisionRecord::FOR_PUBLIC );
$options = ParserOptions::newCanonical( $forUser ?: 'canonical' );
}
- // @phan-suppress-next-line PhanTypeInvalidDimOffset
$useMaster = $hints['use-master'] ?? false;
$dbIndex = $useMaster
use RecentChange;
use Revision;
use RuntimeException;
+use StatusValue;
use stdClass;
use Title;
+use Traversable;
use User;
use WANObjectCache;
use Wikimedia\Assert\Assert;
return $rev;
}
+ /**
+ * Construct a RevisionRecord instance for each row in $rows,
+ * and return them as an associative array indexed by revision ID.
+ * @param Traversable|array $rows the rows to construct revision records from
+ * @param array $options Supports the following options:
+ * 'slots' - whether metadata about revision slots should be
+ * loaded immediately. Supports falsy or truthy value as well
+ * as an explicit list of slot role names.
+ * 'content'- whether the actual content of the slots should be
+ * preloaded. TODO: no supported yet.
+ * @param int $queryFlags
+ * @param Title|null $title
+ * @return StatusValue a status with a RevisionRecord[] of successfully fetched revisions
+ * and an array of errors for the revisions failed to fetch.
+ */
+ public function newRevisionsFromBatch(
+ $rows,
+ array $options = [],
+ $queryFlags = 0,
+ Title $title = null
+ ) {
+ $result = new StatusValue();
+
+ $rowsByRevId = [];
+ $pageIds = [];
+ $titlesByPageId = [];
+ foreach ( $rows as $row ) {
+ if ( isset( $rowsByRevId[$row->rev_id] ) ) {
+ throw new InvalidArgumentException( "Duplicate rows in newRevisionsFromBatch {$row->rev_id}" );
+ }
+ if ( $title && $row->rev_page != $title->getArticleID() ) {
+ throw new InvalidArgumentException(
+ "Revision {$row->rev_id} doesn't belong to page {$title->getArticleID()}"
+ );
+ }
+ $pageIds[] = $row->rev_page;
+ $rowsByRevId[$row->rev_id] = $row;
+ }
+
+ if ( empty( $rowsByRevId ) ) {
+ $result->setResult( true, [] );
+ return $result;
+ }
+
+ // If the title is not supplied, batch-fetch Title objects.
+ if ( $title ) {
+ $titlesByPageId[$title->getArticleID()] = $title;
+ } else {
+ $pageIds = array_unique( $pageIds );
+ foreach ( Title::newFromIDs( $pageIds ) as $t ) {
+ $titlesByPageId[$t->getArticleID()] = $t;
+ }
+ }
+
+ if ( !isset( $options['slots'] ) || $this->hasMcrSchemaFlags( SCHEMA_COMPAT_READ_OLD ) ) {
+ $result->setResult( true,
+ array_map( function ( $row ) use ( $queryFlags, $titlesByPageId, $result ) {
+ try {
+ return $this->newRevisionFromRow(
+ $row,
+ $queryFlags,
+ $titlesByPageId[$row->rev_page]
+ );
+ } catch ( MWException $e ) {
+ $result->warning( 'internalerror', $e->getMessage() );
+ return null;
+ }
+ }, $rowsByRevId )
+ );
+ return $result;
+ }
+
+ $slotQueryConds = [ 'slot_revision_id' => array_keys( $rowsByRevId ) ];
+ if ( is_array( $options['slots'] ) ) {
+ $slotQueryConds['slot_role_id'] = array_map( function ( $slot_name ) {
+ return $this->slotRoleStore->getId( $slot_name );
+ }, $options['slots'] );
+ }
+
+ // TODO: Support optional fetching of the content
+ $queryInfo = self::getSlotsQueryInfo( [ 'content' ] );
+ $db = $this->getDBConnectionRefForQueryFlags( $queryFlags );
+ $slotRows = $db->select(
+ $queryInfo['tables'],
+ $queryInfo['fields'],
+ $slotQueryConds,
+ __METHOD__,
+ [],
+ $queryInfo['joins']
+ );
+
+ $slotRowsByRevId = [];
+ foreach ( $slotRows as $slotRow ) {
+ $slotRowsByRevId[$slotRow->slot_revision_id][] = $slotRow;
+ }
+ $result->setResult( true, array_map( function ( $row ) use
+ ( $slotRowsByRevId, $queryFlags, $titlesByPageId, $result ) {
+ if ( !isset( $slotRowsByRevId[$row->rev_id] ) ) {
+ $result->warning(
+ 'internalerror',
+ "Couldn't find slots for rev {$row->rev_id}"
+ );
+ return null;
+ }
+ try {
+ return $this->newRevisionFromRowAndSlots(
+ $row,
+ $slotRowsByRevId[$row->rev_id],
+ $queryFlags,
+ $titlesByPageId[$row->rev_page]
+ );
+ } catch ( MWException $e ) {
+ $result->warning( 'internalerror', $e->getMessage() );
+ return null;
+ }
+ }, $rowsByRevId ) );
+ return $result;
+ }
+
/**
* Constructs a new MutableRevisionRecord based on the given associative array following
* the MW1.29 convention for the Revision constructor.
private function preCacheMessages() {
// Precache various messages
if ( !isset( $this->message ) ) {
+ $this->message = [];
$msgs = [ 'cur', 'last', 'pipe-separator' ];
foreach ( $msgs as $msg ) {
$this->message[$msg] = $this->msg( $msg )->escaped();
}
break;
case 'limit':
+ // Must be a number or 'max'
+ if ( $value !== 'max' ) {
+ $value = (int)$value;
+ }
+ if ( $multi ) {
+ self::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
+ }
if ( !$parseLimit ) {
- // Don't do any validation whatsoever
+ // Don't do min/max validation and don't parse 'max'
break;
}
if ( !isset( $paramSettings[self::PARAM_MAX] )
"MAX1 or MAX2 are not defined for the limit $encParamName"
);
}
- if ( $multi ) {
- self::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
- }
- $min = $paramSettings[self::PARAM_MIN] ?? 0;
- if ( $value == 'max' ) {
+ if ( $value === 'max' ) {
$value = $this->getMain()->canApiHighLimits()
? $paramSettings[self::PARAM_MAX2]
: $paramSettings[self::PARAM_MAX];
$this->getResult()->addParsedLimit( $this->getModuleName(), $value );
} else {
- $value = (int)$value;
$this->validateLimit(
$paramName,
$value,
- $min,
+ $paramSettings[self::PARAM_MIN] ?? 0,
$paramSettings[self::PARAM_MAX],
$paramSettings[self::PARAM_MAX2]
);
if ( isset( $prop['parsetree'] ) || $params['generatexml'] ) {
$parser->startExternalParse( $titleObj, $options, Parser::OT_PREPROCESS );
$dom = $parser->preprocessToDom( $params['text'] );
+ // @phan-suppress-next-line PhanUndeclaredMethodInCallable
if ( is_callable( [ $dom, 'saveXML' ] ) ) {
// @phan-suppress-next-line PhanUndeclaredMethod
$xml = $dom->saveXML();
* @return string
*/
protected function encodeRequestLogValue( $s ) {
- static $table;
+ static $table = [];
if ( !$table ) {
$chars = ';@$!*(),/:';
$numChars = strlen( $chars );
];
}
+ /**
+ * @inheritDoc
+ * @phan-param array{nolead?:bool,headerlevel?:int,tocnumber?:int[]} $options
+ */
public function modifyHelp( array &$help, array $options, array &$tocData ) {
// Wish PHP had an "array_insert_before". Instead, we have to manually
// reindex the array to get 'permissions' in the right place.
/**
* @param ApiPageSet $resultPageSet
* @return void
- * @suppress PhanTypeInvalidDimOffset
*/
private function run( $resultPageSet = null ) {
$this->params = $this->extractRequestParams( false );
Parser::OT_PREPROCESS
);
$dom = $parser->preprocessToDom( $t );
+ // @phan-suppress-next-line PhanUndeclaredMethodInCallable
if ( is_callable( [ $dom, 'saveXML' ] ) ) {
// @phan-suppress-next-line PhanUndeclaredMethod
$xml = $dom->saveXML();
"apihelp-main-param-action": "Qué acción se realizará.",
"apihelp-main-param-format": "El formato de la salida.",
"apihelp-main-param-maxlag": "Se puede usar el retardo máximo cuando se instala MediaWiki en un clúster replicado de base de datos. Para evitar acciones que causen más retardo en la replicación del sitio, este parámetro puede hacer que el cliente espere hasta que el retardo en la replicación sea menor que el valor especificado. En caso de retardo excesivo, se devuelve el código de error <samp>maxlag</samp> con un mensaje como <samp>Esperando a $host: $lag segundos de retardo</samp>.<br />Consulta [[mw:Special:MyLanguage/Manual:Maxlag_parameter|Manual: parámetro Maxlag]] para más información.",
- "apihelp-main-param-smaxage": "Establece la cabecera HTTP <code>s-maxage</code> de control de caché a esta cantidad de segundos. Los errores nunca se almacenan en caché.",
- "apihelp-main-param-maxage": "Establece la cabecera HTTP <code>max-age</code> de control de caché a esta cantidad de segundos. Los errores nunca se almacenan en la caché.",
+ "apihelp-main-param-smaxage": "Establece la cabecera HTTP <code>s-maxage</code> de control de antememoria a esta cantidad de segundos. Los errores nunca se almacenan en la antememoria.",
+ "apihelp-main-param-maxage": "Establece la cabecera HTTP <code>max-age</code> de control de antememoria a esta cantidad de segundos. Los errores nunca se almacenan en la antememoria.",
"apihelp-main-param-assert": "Comprobar que el usuario haya iniciado sesión si el valor es <kbd>user</kbd> o si tiene el permiso de bot si es <kbd>bot</kbd>.",
"apihelp-main-param-assertuser": "Verificar el usuario actual es el usuario nombrado.",
"apihelp-main-param-requestid": "Cualquier valor dado aquí se incluirá en la respuesta. Se puede utilizar para distinguir solicitudes.",
"apihelp-edit-param-text": "Contenido de la página.",
"apihelp-edit-param-summary": "Editar resumen. Además de la sección del título cuando $1section=new y $1sectiontitle no están establecidos.",
"apihelp-edit-param-tags": "Cambia las etiquetas para aplicarlas a la revisión.",
- "apihelp-edit-param-minor": "Edición menor.",
+ "apihelp-edit-param-minor": "Marcar esta edición como menor.",
"apihelp-edit-param-notminor": "Edición no menor.",
"apihelp-edit-param-bot": "Marcar esta como una edición de bot.",
"apihelp-edit-param-basetimestamp": "Marca de tiempo de la revisión base, usada para detectar conflictos de edición. Se puede obtener mediante [[Special:ApiHelp/query+revisions|action=query&prop=revisions&rvprop=timestamp]]",
* @return AuthenticationRequest
*/
public static function __set_state( $data ) {
- // @phan-suppress-next-line PhanTypeInstantiateAbstract
+ // @phan-suppress-next-line PhanTypeInstantiateAbstractStatic
$ret = new static();
foreach ( $data as $k => $v ) {
$ret->$k = $v;
* @inheritDoc
*/
public static function newFromRow( \stdClass $row ) {
- // @phan-suppress-next-line PhanTypeInstantiateAbstract
+ // @phan-suppress-next-line PhanTypeInstantiateAbstractStatic
return new static( $row->ir_ipb_id, $row->ir_value );
}
*/
private function preCacheMessages() {
if ( !isset( $this->message ) ) {
+ $this->message = [];
foreach ( [
'cur', 'diff', 'hist', 'enhancedrc-history', 'last', 'blocklink', 'history',
'semicolon-separator', 'pipe-separator' ] as $msg
if ( is_array( $etcdResponse['config'] ) ) {
// Avoid having all servers expire cache keys at the same time
$expiry = microtime( true ) + $this->baseCacheTTL;
+ // @phan-suppress-next-line PhanTypeMismatchArgumentInternal
$expiry += mt_rand( 0, 1e6 ) / 1e6 * $this->skewCacheTTL;
$data = [
'config' => $etcdResponse['config'],
* @param Revision|Content $undoafter Must be from an earlier revision than $undo
* @param bool $undoIsLatest Set true if $undo is from the current revision (since 1.32)
*
- * @return mixed Content on success, false on failure
+ * @return Content|false Content on success, false on failure
*/
public function getUndoContent( $current, $undo, $undoafter, $undoIsLatest = false ) {
Assert::parameterType( Revision::class . '|' . Content::class, $current, '$current' );
*/
$max = min( $this->m, $this->n );
for ( $forwardBound = 0; $forwardBound < $max
- // @phan-suppress-next-line PhanTypeInvalidDimOffset
&& $this->from[$forwardBound] === $this->to[$forwardBound];
++$forwardBound
) {
*/
private $progress;
+ /**
+ * @param DumpOutput &$sink
+ * @param BackupDumper &$progress
+ */
function __construct( &$sink, &$progress ) {
parent::__construct( $sink );
$this->progress = $progress;
* in the wiki's own database. This is the most commonly used repository class.
*
* @ingroup FileRepo
+ * @method LocalFile|null newFile( $title, $time = false )
*/
class LocalRepo extends FileRepo {
/** @var callable */
* be found.
* latest: If true, load from the latest available data into File objects
* @phan-param array{time?:mixed,ignoreRedirect?:bool,private?:bool,latest?:bool} $options
- * @suppress PhanTypeInvalidDimOffset
* @return File|bool False if title is not found
*/
function findFile( $title, $options = [] ) {
array_shift( $urls ); // don't purge directory
// Give media handler a chance to filter the file purge list
- // @phan-suppress-next-line PhanTypeInvalidDimOffset
if ( !empty( $options['forThumbRefresh'] ) ) {
$handler = $this->getHandler();
if ( $handler ) {
public function execute() {
$repo = $this->file->repo;
$status = $repo->newGood();
- /** @var LocalFile $destFile */
$destFile = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo()
->newFile( $this->target );
- '@phan-var LocalFile $destFile';
$this->file->lock();
$destFile->lock(); // quickly fail if destination is not available
* The old name of autocomplete-data[-messages] was autocomplete[-messages] which is still
* recognized but deprecated since MediaWiki 1.29 since it conflicts with how autocomplete is
* used in HTMLTextField.
- *
- * @phan-file-suppress PhanTypeMismatchProperty This is doing weird things with mClass
*/
class HTMLAutoCompleteSelectField extends HTMLTextField {
protected $autocompleteData = [];
$ret = $select->getHTML() . "<br />\n";
- // @phan-suppress-next-line PhanTypeMismatchDimEmpty
$this->mClass[] = 'mw-htmlform-hide-if';
}
}
}
- // @phan-suppress-next-line PhanTypeMismatchDimEmpty
$this->mClass[] = 'mw-htmlform-autocomplete';
$ret .= parent::getInputHTML( $valInSelect ? '' : $value );
$this->mClass = $oldClass;
$this->url = wfExpandUrl( $url, PROTO_HTTP );
$this->parsedUrl = wfParseUrl( $this->url );
- // @phan-suppress-next-line PhanTypeInvalidDimOffset
$this->logger = $options['logger'] ?? new NullLogger();
if ( !$this->parsedUrl || !Http::isValidURI( $this->url ) ) {
*/
public function getSha1Base36();
+ /**
+ * @since 1.34
+ * @return string[]
+ */
+ public function getTags();
+
}
$revision->insertOn( $dbw );
$changed = $page->updateIfNewerOn( $dbw, $revision );
+ $tags = $importableRevision->getTags();
+ if ( $tags !== [] ) {
+ ChangeTags::addTags( $tags, null, $revision->getId() );
+ }
+
if ( $changed !== false && $this->doUpdates ) {
$this->logger->debug( __METHOD__ . ": running updates\n" );
// countable/oldcountable stuff is handled in WikiImporter::finishImportPage
$user
);
} else {
- '@phan-var LocalFile $file';
$flags = 0;
$status = $file->upload(
$source,
*/
public $sha1base36 = false;
+ /**
+ * @since 1.34
+ * @var string[]
+ */
+ protected $tags = [];
+
/**
* @since 1.17
* @var string
$this->sha1base36 = $sha1base36;
}
+ /**
+ * @since 1.34
+ * @param string[] $tags
+ */
+ public function setTags( array $tags ) {
+ $this->tags = $tags;
+ }
+
/**
* @since 1.12.2
* @param string $filename
return false;
}
+ /**
+ * @since 1.34
+ * @return string[]
+ */
+ public function getTags() {
+ return $this->tags;
+ }
+
/**
* @since 1.17
* @return string
"config-apc": "[https://www.php.net/apc APC] está instalado",
"config-apcu": "[https://www.php.net/apcu APCu] está instalado",
"config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] está instalado",
- "config-no-cache-apcu": "<strong>Atención:</strong> no se pudo encontrar [https://www.php.net/apcu APCu] o [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nEl almacenamiento en caché de objetos no está activado.",
+ "config-no-cache-apcu": "<strong>Atención:</strong> no se pudo encontrar [https://www.php.net/apcu APCu] o [https://www.iis.net/downloads/microsoft/wincache-extension WinCache].\nEl almacenamiento en antememoria de objetos no está activado.",
"config-mod-security": "<strong>Advertencia:</strong> tu servidor web tiene activado [https://modsecurity.org/ mod_security]/mod_security2. Muchas de sus configuraciones comunes pueden causar problemas a MediaWiki u otro software que permita a los usuarios publicar contenido arbitrario. De ser posible, deberías desactivarlo. Si no, consulta la [https://modsecurity.org/documentation/ documentación de mod_security] o contacta con el administrador de tu servidor si encuentras errores aleatorios.",
"config-diff3-bad": "GNU diff3 no se encuentra.",
"config-git": "Se encontró el software de control de versiones Git: <code>$1</code>.",
"config-cc-again": "Elegir otra vez...",
"config-cc-not-chosen": "Elige la licencia Creative Commons que desees y haz clic en \"proceed\".",
"config-advanced-settings": "Configuración avanzada",
- "config-cache-options": "Configuración de la caché de objetos:",
+ "config-cache-options": "Configuración de la antememoria de objetos:",
"config-cache-help": "El almacenamiento en caché de objetos se utiliza para mejorar la velocidad de MediaWiki mediante el almacenamiento en caché los datos usados más frecuentemente.\nA los sitios medianos y grandes se les recomienda que permitirlo. También es beneficioso para los sitios pequeños.",
"config-cache-none": "Sin almacenamiento en caché (no se pierde ninguna funcionalidad, pero la velocidad puede resentirse en sitios grandes)",
- "config-cache-accel": "Almacenamiento en caché de objetos PHP (APC, APCu o WinCache)",
+ "config-cache-accel": "Almacenamiento en antememoria de objetos PHP (APC, APCu o WinCache)",
"config-cache-memcached": "Utilizar Memcached (necesita ser instalado y configurado aparte)",
"config-memcached-servers": "Servidores Memcached:",
"config-memcached-help": "Lista de direcciones IP que serán usadas por Memcached.\nDeben especificarse una por cada línea y especificar el puerto a utilizar. Por ejemplo:\n127.0.0.1:11211\n192.168.1.25:1234",
"config-page-existingwiki": "Bestaande wiki",
"config-help-restart": "Wilt u alle opgeslagen gegevens die u hebt ingevoerd wissen en het installatieproces opnieuw starten?",
"config-restart": "Ja, opnieuw starten",
- "config-welcome": "=== Controle omgeving ===\nEr worden een aantal basiscontroles uitgevoerd met als doel vast te stellen of deze omgeving geschikt is voor een installatie van MediaWiki.\nLever deze gegevens aan als u ondersteuning vraagt bij de installatie.",
+ "config-welcome": "=== Omgevingscontrole ===\nEr worden een aantal basiscontroles uitgevoerd met als doel vast te stellen of deze omgeving geschikt is voor een installatie van MediaWiki.\nLever deze gegevens aan als u ondersteuning vraagt bij de installatie.",
"config-welcome-section-copyright": "=== Auteursrechten en voorwaarden ===\n\n$1\n\nDit programma is vrije software. U mag het verder verspreiden en/of aanpassen in overeenstemming met de voorwaarden van de GNU General Public License zoals uitgegeven door de Free Software Foundation; ofwel versie 2 van de Licentie of - naar uw keuze - enige latere versie.\n\nDit programma wordt verspreid in de hoop dat het nuttig is, maar '''zonder enige garantie''', zelfs zonder de impliciete garantie van '''verkoopbaarheid''' of '''geschiktheid voor een bepaald doel'''.\nZie de GNU General Public License voor meer informatie.\n\nSamen met dit programma hoort u een [$2 exemplaar van de GNU General Public License] ontvangen te hebben; zo niet, schrijf dan aan de Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, Verenigde Staten. Of [https://www.gnu.org/copyleft/gpl.html lees de licentie online].",
"config-sidebar": "* [https://www.mediawiki.org MediaWiki-thuispagina]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Gebruikershandleiding]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Beheerdershandleiding]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Veelgestelde vragen]",
"config-sidebar-readme": "Leesmij",
"config-pcre-no-utf8": "<strong>Onherstelbare fout:</strong> de module PRCE van PHP lijkt te zijn gecompileerd zonder ondersteuning voor PCRE_UTF8.\nMediaWiki heeft ondersteuning voor UTF-8 nodig om correct te kunnen werken.",
"config-memory-raised": "PHP's <code>memory_limit</code> is $1 en is verhoogd tot $2.",
"config-memory-bad": "'''Waarschuwing:''' PHP's <code>memory_limit</code> is $1.\nDit is waarschijnlijk te laag.\nDe installatie kan mislukken!",
- "config-apc": "[https://www.php.net/apc APC] is op dit moment geïnstalleerd",
+ "config-apc": "[https://www.php.net/apc APC] is geïnstalleerd",
"config-apcu": "[https://www.php.net/apcu APCu] is geïnstalleerd",
- "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] is op dit moment geïnstalleerd",
+ "config-wincache": "[https://www.iis.net/downloads/microsoft/wincache-extension WinCache] is geïnstalleerd",
"config-no-cache-apcu": "<strong>Waarschuwing:</strong> [https://www.php.net/apcu APCu], [http://xcache.lighttpd.net/ XCache] of [https://www.iis.net/downloads/microsoft/wincache-extension WinCache] is niet aangetroffen.\nHet cachen van objecten is niet ingeschakeld.",
"config-mod-security": "<strong>Waarschuwing:</strong> Uw webserver heeft de module [https://modsecurity.org/ mod_security]/mod_security2 ingeschakeld. Veel standaard instellingen hiervan zorgen voor problemen in combinatie met MediaWiki en andere software die gebruikers in staat stelt willekeurige inhoud te posten.\nIndien mogelijk, zou deze moeten worden uitgeschakeld. Lees anders de [https://modsecurity.org/documentation/ documentatie over mod_security] of neem contact op met de helpdesk van uw provider als u tegen problemen aanloopt.",
"config-diff3-bad": "GNU diff3 niet aangetroffen. U kunt dit voorlopig negeren, maar bewerkingsconflicten kunnen vaker voorkomen.",
}
parent::__construct( $baseIterator );
$this->vCallback = $vCallback;
- // @phan-suppress-next-line PhanTypeInvalidDimOffset
$this->aCallback = $options['accept'] ?? null;
}
}
}
- return null;
+ return null; // invalid
}
/**
protected function doGetFileStat( array $params ) {
$source = $this->resolveToFSPath( $params['src'] );
if ( $source === null ) {
- return false; // invalid storage path
+ return self::$RES_ERROR; // invalid storage path
}
$this->trapWarnings(); // don't trust 'false' if there were errors
$stat = is_file( $source ) ? stat( $source ) : false; // regular files only
$hadError = $this->untrapWarnings();
- if ( $stat ) {
+ if ( is_array( $stat ) ) {
$ct = new ConvertibleTimestamp( $stat['mtime'] );
return [
'mtime' => $ct->getTimestamp( TS_MW ),
'size' => $stat['size']
];
- } elseif ( !$hadError ) {
- return false; // file does not exist
- } else {
- return self::UNKNOWN; // failure
}
+
+ return $hadError ? self::$RES_ERROR : self::$RES_ABSENT;
}
protected function doClearCache( array $paths = null ) {
$exists = is_dir( $dir );
$hadError = $this->untrapWarnings();
- return $hadError ? self::UNKNOWN : $exists;
+ return $hadError ? self::$RES_ERROR : $exists;
}
/**
list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
$contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
$dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
+
+ $this->trapWarnings(); // don't trust 'false' if there were errors
$exists = is_dir( $dir );
- if ( !$exists ) {
- $this->logger->warning( __METHOD__ . "() given directory does not exist: '$dir'\n" );
+ $isReadable = $exists ? is_readable( $dir ) : false;
+ $hadError = $this->untrapWarnings();
- return []; // nothing under this dir
- } elseif ( !is_readable( $dir ) ) {
- $this->logger->warning( __METHOD__ . "() given directory is unreadable: '$dir'\n" );
+ if ( $isReadable ) {
+ return new FSFileBackendDirList( $dir, $params );
+ } elseif ( $exists ) {
+ $this->logger->warning( __METHOD__ . ": given directory is unreadable: '$dir'" );
- return self::UNKNOWN; // bad permissions?
- }
+ return self::$RES_ERROR; // bad permissions?
+ } elseif ( $hadError ) {
+ $this->logger->warning( __METHOD__ . ": given directory was unreachable: '$dir'" );
+
+ return self::$RES_ERROR;
+ } else {
+ $this->logger->info( __METHOD__ . ": given directory does not exist: '$dir'" );
- return new FSFileBackendDirList( $dir, $params );
+ return []; // nothing under this dir
+ }
}
/**
list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
$contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
$dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
+
+ $this->trapWarnings(); // don't trust 'false' if there were errors
$exists = is_dir( $dir );
- if ( !$exists ) {
- $this->logger->warning( __METHOD__ . "() given directory does not exist: '$dir'\n" );
+ $isReadable = $exists ? is_readable( $dir ) : false;
+ $hadError = $this->untrapWarnings();
- return []; // nothing under this dir
- } elseif ( !is_readable( $dir ) ) {
- $this->logger->warning( __METHOD__ . "() given directory is unreadable: '$dir'\n" );
+ if ( $exists && $isReadable ) {
+ return new FSFileBackendFileList( $dir, $params );
+ } elseif ( $exists ) {
+ $this->logger->warning( __METHOD__ . ": given directory is unreadable: '$dir'\n" );
- return self::UNKNOWN; // bad permissions?
- }
+ return self::$RES_ERROR; // bad permissions?
+ } elseif ( $hadError ) {
+ $this->logger->warning( __METHOD__ . ": given directory was unreachable: '$dir'\n" );
- return new FSFileBackendFileList( $dir, $params );
+ return self::$RES_ERROR;
+ } else {
+ $this->logger->info( __METHOD__ . ": given directory does not exist: '$dir'\n" );
+
+ return []; // nothing under this dir
+ }
}
protected function doGetLocalReferenceMulti( array $params ) {
foreach ( $params['srcs'] as $src ) {
$source = $this->resolveToFSPath( $src );
- if ( $source === null || !is_file( $source ) ) {
- $fsFiles[$src] = null; // invalid path or file does not exist
- } else {
+ if ( $source === null ) {
+ $fsFiles[$src] = self::$RES_ERROR; // invalid path
+ continue;
+ }
+
+ $this->trapWarnings(); // don't trust 'false' if there were errors
+ $isFile = is_file( $source ); // regular files only
+ $hadError = $this->untrapWarnings();
+
+ if ( $isFile ) {
$fsFiles[$src] = new FSFile( $source );
+ } elseif ( $hadError ) {
+ $fsFiles[$src] = self::$RES_ERROR;
+ } else {
+ $fsFiles[$src] = self::$RES_ABSENT;
}
}
foreach ( $params['srcs'] as $src ) {
$source = $this->resolveToFSPath( $src );
if ( $source === null ) {
- $tmpFiles[$src] = null; // invalid path
+ $tmpFiles[$src] = self::$RES_ERROR; // invalid path
+ continue;
+ }
+ // Create a new temporary file with the same extension...
+ $ext = FileBackend::extensionFromPath( $src );
+ $tmpFile = $this->tmpFileFactory->newTempFSFile( 'localcopy_', $ext );
+ if ( !$tmpFile ) {
+ $tmpFiles[$src] = self::$RES_ERROR;
+ continue;
+ }
+
+ $tmpPath = $tmpFile->getPath();
+ // Copy the source file over the temp file
+ $this->trapWarnings();
+ $isFile = is_file( $source ); // regular files only
+ $copySuccess = $isFile ? copy( $source, $tmpPath ) : false;
+ $hadError = $this->untrapWarnings();
+
+ if ( $copySuccess ) {
+ $this->chmod( $tmpPath );
+ $tmpFiles[$src] = $tmpFile;
+ } elseif ( $hadError ) {
+ $tmpFiles[$src] = self::$RES_ERROR; // copy failed
} else {
- // Create a new temporary file with the same extension...
- $ext = FileBackend::extensionFromPath( $src );
- $tmpFile = $this->tmpFileFactory->newTempFSFile( 'localcopy_', $ext );
- if ( !$tmpFile ) {
- $tmpFiles[$src] = null;
- } else {
- $tmpPath = $tmpFile->getPath();
- // Copy the source file over the temp file
- $this->trapWarnings();
- $ok = copy( $source, $tmpPath );
- $this->untrapWarnings();
- if ( !$ok ) {
- $tmpFiles[$src] = null;
- } else {
- $this->chmod( $tmpPath );
- $tmpFiles[$src] = $tmpFile;
- }
- }
+ $tmpFiles[$src] = self::$RES_ABSENT;
}
}
const ATTR_METADATA = 2; // files can be stored with metadata key/values
const ATTR_UNICODE_PATHS = 4; // files can have Unicode paths (not just ASCII)
- /** @var null Idiom for "could not determine due to I/O errors" */
- const UNKNOWN = null;
+ /** @var false Idiom for "no info; non-existant file" (since 1.34) */
+ const STAT_ABSENT = false;
+
+ /** @var null Idiom for "no info; I/O errors" (since 1.34) */
+ const STAT_ERROR = null;
+ /** @var null Idiom for "no file/directory list; I/O errors" (since 1.34) */
+ const LIST_ERROR = null;
+ /** @var null Idiom for "no temp URL; not supported or I/O errors" (since 1.34) */
+ const TEMPURL_ERROR = null;
+ /** @var null Idiom for "existence unknown; I/O errors" (since 1.34) */
+ const EXISTENCE_ERROR = null;
+
+ /** @var false Idiom for "no timestamp; missing file or I/O errors" (since 1.34) */
+ const TIMESTAMP_FAIL = false;
+ /** @var false Idiom for "no content; missing file or I/O errors" (since 1.34) */
+ const CONTENT_FAIL = false;
+ /** @var false Idiom for "no metadata; missing file or I/O errors" (since 1.34) */
+ const XATTRS_FAIL = false;
+ /** @var false Idiom for "no size; missing file or I/O errors" (since 1.34) */
+ const SIZE_FAIL = false;
+ /** @var false Idiom for "no SHA1 hash; missing file or I/O errors" (since 1.34) */
+ const SHA1_FAIL = false;
/**
* Create a new backend instance from configuration.
* Allowed values are "implicit", "explicit" and "off".
* - concurrency : How many file operations can be done in parallel.
* - tmpDirectory : Directory to use for temporary files.
- * - tmpFileFactory : Optional TempFSFileFactory object. Only has an effect if tmpDirectory is
- * not set. If both are unset or null, then the backend will try to discover a usable
- * temporary directory.
+ * - tmpFileFactory : Optional TempFSFileFactory object. Only has an effect if
+ * tmpDirectory is not set. If both are unset or null, then the backend will
+ * try to discover a usable temporary directory.
* - obResetFunc : alternative callback to clear the output buffer
* - streamMimeFunc : alternative method to determine the content type from the path
* - logger : Optional PSR logger object.
* Check if a file exists at a storage path in the backend.
* This returns false if only a directory exists at the path.
*
+ * Callers that only care if a file is readily accessible can use non-strict
+ * comparisons on the result. If "does not exist" and "existence is unknown"
+ * must be distinguished, then strict comparisons to true/null should be used.
+ *
+ * @see FileBackend::EXISTENCE_ERROR
+ * @see FileBackend::directoryExists()
+ *
* @param array $params Parameters include:
* - src : source storage path
* - latest : use the latest available data
- * @return bool|null Returns null on failure
+ * @return bool|null Whether the file exists or null (I/O error)
*/
abstract public function fileExists( array $params );
/**
* Get the last-modified timestamp of the file at a storage path.
*
+ * @see FileBackend::TIMESTAMP_FAIL
+ *
* @param array $params Parameters include:
* - src : source storage path
* - latest : use the latest available data
- * @return string|bool TS_MW timestamp or false on failure
+ * @return string|false TS_MW timestamp or false (missing file or I/O error)
*/
abstract public function getFileTimestamp( array $params );
* Get the contents of a file at a storage path in the backend.
* This should be avoided for potentially large files.
*
+ * @see FileBackend::CONTENT_FAIL
+ *
* @param array $params Parameters include:
* - src : source storage path
* - latest : use the latest available data
- * @return string|bool Returns false on failure
+ * @return string|false Content string or false (missing file or I/O error)
*/
final public function getFileContents( array $params ) {
- $contents = $this->getFileContentsMulti(
- [ 'srcs' => [ $params['src'] ] ] + $params );
+ $contents = $this->getFileContentsMulti( [ 'srcs' => [ $params['src'] ] ] + $params );
return $contents[$params['src']];
}
/**
* Like getFileContents() except it takes an array of storage paths
- * and returns a map of storage paths to strings (or null on failure).
- * The map keys (paths) are in the same order as the provided list of paths.
+ * and returns an order preserved map of storage paths to their content.
*
* @see FileBackend::getFileContents()
*
* - srcs : list of source storage paths
* - latest : use the latest available data
* - parallelize : try to do operations in parallel when possible
- * @return array Map of (path name => string or false on failure)
+ * @return string[]|false[] Map of (path name => file content or false on failure)
* @since 1.20
*/
abstract public function getFileContentsMulti( array $params );
*
* Use FileBackend::hasFeatures() to check how well this is supported.
*
+ * @see FileBackend::XATTRS_FAIL
+ *
* @param array $params
* $params include:
* - src : source storage path
* - latest : use the latest available data
- * @return array|bool Returns false on failure
+ * @return array|false File metadata array or false (missing file or I/O error)
* @since 1.23
*/
abstract public function getFileXAttributes( array $params );
/**
* Get the size (bytes) of a file at a storage path in the backend.
*
+ * @see FileBackend::SIZE_FAIL
+ *
* @param array $params Parameters include:
* - src : source storage path
* - latest : use the latest available data
- * @return int|bool Returns false on failure
+ * @return int|false File size in bytes or false (missing file or I/O error)
*/
abstract public function getFileSize( array $params );
* - size : the file size (bytes)
* Additional values may be included for internal use only.
*
+ * @see FileBackend::STAT_ABSENT
+ * @see FileBackend::STAT_ERROR
+ *
* @param array $params Parameters include:
* - src : source storage path
* - latest : use the latest available data
- * @return array|bool|null Returns null on failure
+ * @return array|false|null Attribute map, false (missing file), or null (I/O error)
*/
abstract public function getFileStat( array $params );
/**
- * Get a SHA-1 hash of the file at a storage path in the backend.
+ * Get a SHA-1 hash of the content of the file at a storage path in the backend.
+ *
+ * @see FileBackend::SHA1_FAIL
*
* @param array $params Parameters include:
* - src : source storage path
* - latest : use the latest available data
- * @return string|bool Hash string or false on failure
+ * @return string|false Hash string or false (missing file or I/O error)
*/
abstract public function getFileSha1Base36( array $params );
/**
- * Get the properties of the file at a storage path in the backend.
+ * Get the properties of the content of the file at a storage path in the backend.
* This gives the result of FSFile::getProps() on a local copy of the file.
*
* @param array $params Parameters include:
* - src : source storage path
* - latest : use the latest available data
- * @return array Returns FSFile::placeholderProps() on failure
+ * @return array Properties map; FSFile::placeholderProps() if file missing or on I/O error
*/
abstract public function getFileProps( array $params );
/**
- * Stream the file at a storage path in the backend.
+ * Stream the content of the file at a storage path in the backend.
*
* If the file does not exists, an HTTP 404 error will be given.
* Appropriate HTTP headers (Status, Content-Type, Content-Length)
abstract public function streamFile( array $params );
/**
- * Returns a file system file, identical to the file at a storage path.
+ * Returns a file system file, identical in content to the file at a storage path.
* The file returned is either:
- * - a) A local copy of the file at a storage path in the backend.
+ * - a) A TempFSFile local copy of the file at a storage path in the backend.
* The temporary copy will have the same extension as the source.
- * - b) An original of the file at a storage path in the backend.
- * Temporary files may be purged when the file object falls out of scope.
+ * Temporary files may be purged when the file object falls out of scope.
+ * - b) An FSFile pointing to the original file at a storage path in the backend.
+ * This is applicable for backends layered directly on top of file systems.
*
- * Write operations should *never* be done on this file as some backends
- * may do internal tracking or may be instances of FileBackendMultiWrite.
- * In that latter case, there are copies of the file that must stay in sync.
- * Additionally, further calls to this function may return the same file.
+ * Never modify the returned file since it might be the original, it might be shared
+ * among multiple callers of this method, or the backend might internally keep FSFile
+ * references for deferred operations.
*
* @param array $params Parameters include:
* - src : source storage path
* - latest : use the latest available data
- * @return FSFile|null Returns null on failure
+ * @return FSFile|null Local file copy or null (missing file or I/O error)
*/
final public function getLocalReference( array $params ) {
- $fsFiles = $this->getLocalReferenceMulti(
- [ 'srcs' => [ $params['src'] ] ] + $params );
+ $fsFiles = $this->getLocalReferenceMulti( [ 'srcs' => [ $params['src'] ] ] + $params );
return $fsFiles[$params['src']];
}
/**
- * Like getLocalReference() except it takes an array of storage paths
- * and returns a map of storage paths to FSFile objects (or null on failure).
- * The map keys (paths) are in the same order as the provided list of paths.
+ * Like getLocalReference() except it takes an array of storage paths and
+ * yields an order-preserved map of storage paths to temporary local file copies.
+ *
+ * Never modify the returned files since they might be originals, they might be shared
+ * among multiple callers of this method, or the backend might internally keep FSFile
+ * references for deferred operations.
*
* @see FileBackend::getLocalReference()
*
* The temporary copy will have the same file extension as the source.
* Temporary files may be purged when the file object falls out of scope.
*
+ * Multiple calls to this method for the same path will create new copies.
+ *
* @param array $params Parameters include:
* - src : source storage path
* - latest : use the latest available data
- * @return TempFSFile|null Returns null on failure
+ * @return TempFSFile|null Temporary local file copy or null (missing file or I/O error)
*/
final public function getLocalCopy( array $params ) {
- $tmpFiles = $this->getLocalCopyMulti(
- [ 'srcs' => [ $params['src'] ] ] + $params );
+ $tmpFiles = $this->getLocalCopyMulti( [ 'srcs' => [ $params['src'] ] ] + $params );
return $tmpFiles[$params['src']];
}
/**
- * Like getLocalCopy() except it takes an array of storage paths and
- * returns a map of storage paths to TempFSFile objects (or null on failure).
- * The map keys (paths) are in the same order as the provided list of paths.
+ * Like getLocalCopy() except it takes an array of storage paths and yields
+ * an order preserved-map of storage paths to temporary local file copies.
+ *
+ * Multiple calls to this method for the same path will create new copies.
*
* @see FileBackend::getLocalCopy()
*
* Otherwise, one would need to use getLocalReference(), which involves loading
* the entire file on to local disk.
*
+ * @see FileBackend::TEMPURL_ERROR
+ *
* @param array $params Parameters include:
* - src : source storage path
* - ttl : lifetime (seconds) if pre-authenticated; default is 1 day
- * @return string|null
+ * @return string|null URL or null (not supported or I/O error)
* @since 1.21
*/
abstract public function getFileHttpUrl( array $params );
*
* Storage backends with eventual consistency might return stale data.
*
+ * @see FileBackend::EXISTENCE_ERROR
* @see FileBackend::clean()
*
* @param array $params Parameters include:
* - dir : storage directory
- * @return bool|null Whether a directory exists or null on failure
+ * @return bool|null Whether a directory exists or null (I/O error)
* @since 1.20
*/
abstract public function directoryExists( array $params );
*
* Failures during iteration can result in FileBackendError exceptions (since 1.22).
*
+ * @see FileBackend::LIST_ERROR
* @see FileBackend::directoryExists()
*
* @param array $params Parameters include:
* - dir : storage directory
* - topOnly : only return direct child dirs of the directory
- * @return Traversable|array|null Directory list enumerator null on failure
+ * @return Traversable|array|null Directory list enumerator or null (initial I/O error)
* @since 1.20
*/
abstract public function getDirectoryList( array $params );
*
* Failures during iteration can result in FileBackendError exceptions (since 1.22).
*
+ * @see FileBackend::LIST_ERROR
* @see FileBackend::directoryExists()
*
* @param array $params Parameters include:
* - dir : storage directory
- * @return Traversable|array|null Directory list enumerator or null on failure
+ * @return Traversable|array|null Directory list enumerator or null (initial I/O error)
* @since 1.20
*/
final public function getTopDirectoryList( array $params ) {
*
* Failures during iteration can result in FileBackendError exceptions (since 1.22).
*
+ * @see FileBackend::LIST_ERROR
+ *
* @param array $params Parameters include:
* - dir : storage directory
* - topOnly : only return direct child files of the directory (since 1.20)
* - adviseStat : set to true if stat requests will be made on the files (since 1.22)
- * @return Traversable|array|null File list enumerator or null on failure
+ * @return Traversable|array|null File list enumerator or null (initial I/O error)
*/
abstract public function getFileList( array $params );
*
* Failures during iteration can result in FileBackendError exceptions (since 1.22).
*
+ * @see FileBackend::LIST_ERROR
+ *
* @param array $params Parameters include:
* - dir : storage directory
* - adviseStat : set to true if stat requests will be made on the files (since 1.22)
* @param int|string $type LockManager::LOCK_* constant or "mixed"
* @param StatusValue $status StatusValue to update on lock/unlock
* @param int $timeout Timeout in seconds (0 means non-blocking) (since 1.24)
- * @return ScopedLock|null Returns null on failure
+ * @return ScopedLock|null RAII-style self-unlocking lock or null on failure
*/
final public function getScopedFileLocks(
array $paths, $type, StatusValue $status, $timeout = 0
*
* @param array $ops List of file operations to FileBackend::doOperations()
* @param StatusValue $status StatusValue to update on lock/unlock
- * @return ScopedLock|null
+ * @return ScopedLock|null RAII-style self-unlocking lock or null on failure
* @since 1.20
*/
abstract public function getScopedLocksForOps( array $ops, StatusValue $status );
* Returns null if the path is not of the format of a valid storage path.
*
* @param string $storagePath
- * @return string|null
+ * @return string|null Normalized storage path or null on failure
*/
final public static function normalizeStoragePath( $storagePath ) {
list( $backend, $container, $relPath ) = self::splitStoragePath( $storagePath );
* "mwstore://backend/container/...", or null if there is no parent.
*
* @param string $storagePath
- * @return string|null
+ * @return string|null Parent storage path or null on failure
*/
final public static function parentStoragePath( $storagePath ) {
$storagePath = dirname( $storagePath );
* This uses the same traversal protection as Title::secureAndSplit().
*
* @param string $path Storage path relative to a container
- * @return string|null
+ * @return string|null Normalized container path or null on failure
*/
final protected static function normalizeContainerPath( $path ) {
// Normalize directory separators
$masterBackend = $this->backends[$this->masterIndex];
$masterParams = $this->substOpPaths( $params, $masterBackend );
$masterStat = $masterBackend->getFileStat( $masterParams );
- if ( $masterStat === self::UNKNOWN ) {
+ if ( $masterStat === self::STAT_ERROR ) {
$status->fatal( 'backend-fail-stat', $path );
continue;
}
$masterParams = $this->substOpPaths( $params, $masterBackend );
$masterPath = $masterParams['src'];
$masterStat = $masterBackend->getFileStat( $masterParams );
- if ( $masterStat === self::UNKNOWN ) {
+ if ( $masterStat === self::STAT_ERROR ) {
$status->fatal( 'backend-fail-stat', $path );
$this->logger->error( "$fname: file '$masterPath' is not available" );
continue;
$cloneParams = $this->substOpPaths( $params, $cloneBackend );
$clonePath = $cloneParams['src'];
$cloneStat = $cloneBackend->getFileStat( $cloneParams );
- if ( $cloneStat === self::UNKNOWN ) {
+ if ( $cloneStat === self::STAT_ERROR ) {
$status->fatal( 'backend-fail-stat', $path );
$this->logger->error( "$fname: file '$clonePath' is not available" );
continue;
*
* @param array|string $paths List of paths or single string path
* @param FileBackendStore $backend
- * @return array|string
+ * @return string[]|string
*/
protected function substPaths( $paths, FileBackendStore $backend ) {
return preg_replace(
* Substitute the backend of internal storage paths with the proxy backend's name
*
* @param array|string $paths List of paths or single string path
- * @return array|string
+ * @param FileBackendStore $backend internal storage backend
+ * @return string[]|string
*/
- protected function unsubstPaths( $paths ) {
+ protected function unsubstPaths( $paths, FileBackendStore $backend ) {
return preg_replace(
- '!^mwstore://([^/]+)!',
- StringUtils::escapeRegexReplacement( "mwstore://{$this->name}" ),
+ '!^mwstore://' . preg_quote( $backend->getName(), '!' ) . '/!',
+ StringUtils::escapeRegexReplacement( "mwstore://{$this->name}/" ),
$paths // string or array
);
}
$contents = []; // (path => FSFile) mapping using the proxy backend's name
foreach ( $contentsM as $path => $data ) {
- $contents[$this->unsubstPaths( $path )] = $data;
+ $contents[$this->unsubstPaths( $path, $this->backends[$index] )] = $data;
}
return $contents;
$fsFiles = []; // (path => FSFile) mapping using the proxy backend's name
foreach ( $fsFilesM as $path => $fsFile ) {
- $fsFiles[$this->unsubstPaths( $path )] = $fsFile;
+ $fsFiles[$this->unsubstPaths( $path, $this->backends[$index] )] = $fsFile;
}
return $fsFiles;
$tempFiles = []; // (path => TempFSFile) mapping using the proxy backend's name
foreach ( $tempFilesM as $path => $tempFile ) {
- $tempFiles[$this->unsubstPaths( $path )] = $tempFile;
+ $tempFiles[$this->unsubstPaths( $path, $this->backends[$index] )] = $tempFile;
}
return $tempFiles;
$paths = $this->backends[$this->masterIndex]->getPathsToLockForOpsInternal( $fileOps );
// Get the paths under the proxy backend's name
$pbPaths = [
- LockManager::LOCK_UW => $this->unsubstPaths( $paths[LockManager::LOCK_UW] ),
- LockManager::LOCK_EX => $this->unsubstPaths( $paths[LockManager::LOCK_EX] )
+ LockManager::LOCK_UW => $this->unsubstPaths(
+ $paths[LockManager::LOCK_UW],
+ $this->backends[$this->masterIndex]
+ ),
+ LockManager::LOCK_EX => $this->unsubstPaths(
+ $paths[LockManager::LOCK_EX],
+ $this->backends[$this->masterIndex]
+ )
];
// Actually acquire the locks
const CACHE_CHEAP_SIZE = 500; // integer; max entries in "cheap cache"
const CACHE_EXPENSIVE_SIZE = 5; // integer; max entries in "expensive cache"
+ /** @var false Idiom for "no result due to missing file" (since 1.34) */
+ protected static $RES_ABSENT = false;
+ /** @var null Idiom for "no result due to I/O errors" (since 1.34) */
+ protected static $RES_ERROR = null;
+
+ /** @var string File does not exist according to a normal stat query */
+ protected static $ABSENT_NORMAL = 'FNE-N';
+ /** @var string File does not exist according to a "latest"-mode stat query */
+ protected static $ABSENT_LATEST = 'FNE-L';
+
/**
* @see FileBackend::__construct()
* Additional $config params include:
}
/**
- * Check if a file can be created or changed at a given storage path.
- * FS backends should check if the parent directory exists, files can be
- * written under it, and that any file already there is writable.
+ * Check if a file can be created or changed at a given storage path in the backend
+ *
+ * FS backends should check that the parent directory exists, files can be written
+ * under it, and that any file already there is both readable and writable.
* Backends using key/value stores should check if the container exists.
*
* @param string $storagePath
final public function createInternal( array $params ) {
/** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
+
if ( strlen( $params['content'] ) > $this->maxFileSizeInternal() ) {
$status = $this->newStatus( 'backend-fail-maxsize',
$params['dst'], $this->maxFileSizeInternal() );
final public function storeInternal( array $params ) {
/** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
+
if ( filesize( $params['src'] ) > $this->maxFileSizeInternal() ) {
$status = $this->newStatus( 'backend-fail-maxsize',
$params['dst'], $this->maxFileSizeInternal() );
final public function copyInternal( array $params ) {
/** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
+
$status = $this->doCopyInternal( $params );
$this->clearCache( [ $params['dst'] ] );
if ( !isset( $params['dstExists'] ) || $params['dstExists'] ) {
final public function deleteInternal( array $params ) {
/** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
+
$status = $this->doDeleteInternal( $params );
$this->clearCache( [ $params['src'] ] );
$this->deleteFileCache( $params['src'] ); // persistent cache
final public function moveInternal( array $params ) {
/** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
+
$status = $this->doMoveInternal( $params );
$this->clearCache( [ $params['src'], $params['dst'] ] );
$this->deleteFileCache( $params['src'] ); // persistent cache
final public function describeInternal( array $params ) {
/** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
+
if ( count( $params['headers'] ) ) {
$status = $this->doDescribeInternal( $params );
$this->clearCache( [ $params['src'] ] );
final public function fileExists( array $params ) {
/** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
+
$stat = $this->getFileStat( $params );
+ if ( is_array( $stat ) ) {
+ return true;
+ }
- return ( $stat === self::UNKNOWN ) ? self::UNKNOWN : (bool)$stat;
+ return ( $stat === self::$RES_ABSENT ) ? false : self::EXISTENCE_ERROR;
}
final public function getFileTimestamp( array $params ) {
/** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
+
$stat = $this->getFileStat( $params );
+ if ( is_array( $stat ) ) {
+ return $stat['mtime'];
+ }
- return $stat ? $stat['mtime'] : false;
+ return self::TIMESTAMP_FAIL; // all failure cases
}
final public function getFileSize( array $params ) {
/** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
+
$stat = $this->getFileStat( $params );
+ if ( is_array( $stat ) ) {
+ return $stat['size'];
+ }
- return $stat ? $stat['size'] : false;
+ return self::SIZE_FAIL; // all failure cases
}
final public function getFileStat( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
+ $ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
+
$path = self::normalizeStoragePath( $params['src'] );
if ( $path === null ) {
- return false; // invalid storage path
+ return self::STAT_ERROR; // invalid storage path
}
- /** @noinspection PhpUnusedLocalVariableInspection */
- $ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
- $latest = !empty( $params['latest'] ); // use latest data?
- $requireSHA1 = !empty( $params['requireSHA1'] ); // require SHA-1 if file exists?
+ // Whether to bypass cache except for process cache entries loaded directly from
+ // high consistency backend queries (caller handles any cache flushing and locking)
+ $latest = !empty( $params['latest'] );
+ // Whether to ignore cache entries missing the SHA-1 field for existing files
+ $requireSHA1 = !empty( $params['requireSHA1'] );
+ $stat = $this->cheapCache->getField( $path, 'stat', self::CACHE_TTL );
+ // Load the persistent stat cache into process cache if needed
if ( !$latest ) {
- $stat = $this->cheapCache->getField( $path, 'stat', self::CACHE_TTL );
- // Note that some backends, like SwiftFileBackend, sometimes set file stat process
- // cache entries from mass object listings that do not include the SHA-1. In that
- // case, loading the persistent stat cache will likely yield the SHA-1.
if (
- $stat === self::UNKNOWN ||
+ // File stat is not in process cache
+ $stat === null ||
+ // Key/value store backends might opportunistically set file stat process
+ // cache entries from object listings that do not include the SHA-1. In that
+ // case, loading the persistent stat cache will likely yield the SHA-1.
( $requireSHA1 && is_array( $stat ) && !isset( $stat['sha1'] ) )
) {
- $this->primeFileCache( [ $path ] ); // check persistent cache
+ $this->primeFileCache( [ $path ] );
+ // Get any newly process-cached entry
+ $stat = $this->cheapCache->getField( $path, 'stat', self::CACHE_TTL );
}
}
- $stat = $this->cheapCache->getField( $path, 'stat', self::CACHE_TTL );
- // If we want the latest data, check that this cached
- // value was in fact fetched with the latest available data.
if ( is_array( $stat ) ) {
if (
( !$latest || $stat['latest'] ) &&
) {
return $stat;
}
- } elseif ( in_array( $stat, [ 'NOT_EXIST', 'NOT_EXIST_LATEST' ], true ) ) {
- if ( !$latest || $stat === 'NOT_EXIST_LATEST' ) {
- return false;
+ } elseif ( $stat === self::$ABSENT_LATEST ) {
+ return self::STAT_ABSENT;
+ } elseif ( $stat === self::$ABSENT_NORMAL ) {
+ if ( !$latest ) {
+ return self::STAT_ABSENT;
}
}
+ // Load the file stat from the backend and update caches
$stat = $this->doGetFileStat( $params );
+ $this->ingestFreshFileStats( [ $path => $stat ], $latest );
- if ( is_array( $stat ) ) { // file exists
- // Strongly consistent backends can automatically set "latest"
- $stat['latest'] = $stat['latest'] ?? $latest;
- $this->cheapCache->setField( $path, 'stat', $stat );
- $this->setFileCache( $path, $stat ); // update persistent cache
- if ( isset( $stat['sha1'] ) ) { // some backends store SHA-1 as metadata
- $this->cheapCache->setField( $path, 'sha1',
- [ 'hash' => $stat['sha1'], 'latest' => $latest ] );
- }
- if ( isset( $stat['xattr'] ) ) { // some backends store headers/metadata
- $stat['xattr'] = self::normalizeXAttributes( $stat['xattr'] );
- $this->cheapCache->setField( $path, 'xattr',
- [ 'map' => $stat['xattr'], 'latest' => $latest ] );
+ if ( is_array( $stat ) ) {
+ return $stat;
+ }
+
+ return ( $stat === self::$RES_ERROR ) ? self::STAT_ERROR : self::STAT_ABSENT;
+ }
+
+ /**
+ * Ingest file stat entries that just came from querying the backend (not cache)
+ *
+ * @param array[]|bool[]|null[] $stats Map of (path => doGetFileStat() stype result)
+ * @param bool $latest Whether doGetFileStat()/doGetFileStatMulti() had the 'latest' flag
+ * @return bool Whether all files have non-error stat replies
+ */
+ final protected function ingestFreshFileStats( array $stats, $latest ) {
+ $success = true;
+
+ foreach ( $stats as $path => $stat ) {
+ if ( is_array( $stat ) ) {
+ // Strongly consistent backends might automatically set this flag
+ $stat['latest'] = $stat['latest'] ?? $latest;
+
+ $this->cheapCache->setField( $path, 'stat', $stat );
+ if ( isset( $stat['sha1'] ) ) {
+ // Some backends store the SHA-1 hash as metadata
+ $this->cheapCache->setField(
+ $path,
+ 'sha1',
+ [ 'hash' => $stat['sha1'], 'latest' => $latest ]
+ );
+ }
+ if ( isset( $stat['xattr'] ) ) {
+ // Some backends store custom headers/metadata
+ $stat['xattr'] = self::normalizeXAttributes( $stat['xattr'] );
+ $this->cheapCache->setField(
+ $path,
+ 'xattr',
+ [ 'map' => $stat['xattr'], 'latest' => $latest ]
+ );
+ }
+ // Update persistent cache (@TODO: set all entries in one batch)
+ $this->setFileCache( $path, $stat );
+ } elseif ( $stat === self::$RES_ABSENT ) {
+ $this->cheapCache->setField(
+ $path,
+ 'stat',
+ $latest ? self::$ABSENT_LATEST : self::$ABSENT_NORMAL
+ );
+ $this->cheapCache->setField(
+ $path,
+ 'xattr',
+ [ 'map' => self::XATTRS_FAIL, 'latest' => $latest ]
+ );
+ $this->cheapCache->setField(
+ $path,
+ 'sha1',
+ [ 'hash' => self::SHA1_FAIL, 'latest' => $latest ]
+ );
+ $this->logger->debug(
+ __METHOD__ . ': File {path} does not exist',
+ [ 'path' => $path ]
+ );
+ } else {
+ $success = false;
+ $this->logger->error(
+ __METHOD__ . ': Could not stat file {path}',
+ [ 'path' => $path ]
+ );
}
- } elseif ( $stat === false ) { // file does not exist
- $this->cheapCache->setField( $path, 'stat', $latest ? 'NOT_EXIST_LATEST' : 'NOT_EXIST' );
- $this->cheapCache->setField( $path, 'xattr', [ 'map' => false, 'latest' => $latest ] );
- $this->cheapCache->setField( $path, 'sha1', [ 'hash' => false, 'latest' => $latest ] );
- $this->logger->debug( __METHOD__ . ': File {path} does not exist', [
- 'path' => $path,
- ] );
- } else { // an error occurred
- $this->logger->warning( __METHOD__ . ': Could not stat file {path}', [
- 'path' => $path,
- ] );
}
- return $stat;
+ return $success;
}
/**
$params = $this->setConcurrencyFlags( $params );
$contents = $this->doGetFileContentsMulti( $params );
+ foreach ( $contents as $path => $content ) {
+ if ( !is_string( $content ) ) {
+ $contents[$path] = self::CONTENT_FAIL; // used for all failure cases
+ }
+ }
return $contents;
}
/**
* @see FileBackendStore::getFileContentsMulti()
* @param array $params
- * @return array
+ * @return string[]|bool[]|null[] Map of (path => string, false (missing), or null (error))
*/
protected function doGetFileContentsMulti( array $params ) {
$contents = [];
foreach ( $this->doGetLocalReferenceMulti( $params ) as $path => $fsFile ) {
- AtEase::suppressWarnings();
- $contents[$path] = $fsFile ? file_get_contents( $fsFile->getPath() ) : false;
- AtEase::restoreWarnings();
+ if ( $fsFile instanceof FSFile ) {
+ AtEase::suppressWarnings();
+ $content = file_get_contents( $fsFile->getPath() );
+ AtEase::restoreWarnings();
+ $contents[$path] = is_string( $content ) ? $content : self::$RES_ERROR;
+ } elseif ( $fsFile === self::$RES_ABSENT ) {
+ $contents[$path] = self::$RES_ABSENT;
+ } else {
+ $contents[$path] = self::$RES_ERROR;
+ }
}
return $contents;
}
final public function getFileXAttributes( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
+ $ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
+
$path = self::normalizeStoragePath( $params['src'] );
if ( $path === null ) {
- return false; // invalid storage path
+ return self::XATTRS_FAIL; // invalid storage path
}
- /** @noinspection PhpUnusedLocalVariableInspection */
- $ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$latest = !empty( $params['latest'] ); // use latest data?
if ( $this->cheapCache->hasField( $path, 'xattr', self::CACHE_TTL ) ) {
$stat = $this->cheapCache->getField( $path, 'xattr' );
}
}
$fields = $this->doGetFileXAttributes( $params );
- $fields = is_array( $fields ) ? self::normalizeXAttributes( $fields ) : false;
- $this->cheapCache->setField( $path, 'xattr', [ 'map' => $fields, 'latest' => $latest ] );
+ if ( is_array( $fields ) ) {
+ $fields = self::normalizeXAttributes( $fields );
+ $this->cheapCache->setField(
+ $path,
+ 'xattr',
+ [ 'map' => $fields, 'latest' => $latest ]
+ );
+ } elseif ( $fields === self::$RES_ABSENT ) {
+ $this->cheapCache->setField(
+ $path,
+ 'xattr',
+ [ 'map' => self::XATTRS_FAIL, 'latest' => $latest ]
+ );
+ } else {
+ $fields = self::XATTRS_FAIL; // used for all failure cases
+ }
return $fields;
}
/**
* @see FileBackendStore::getFileXAttributes()
* @param array $params
- * @return array[][]|false
+ * @return array[][]|false|null Attributes, false (missing file), or null (error)
*/
protected function doGetFileXAttributes( array $params ) {
return [ 'headers' => [], 'metadata' => [] ]; // not supported
}
final public function getFileSha1Base36( array $params ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
+ $ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
+
$path = self::normalizeStoragePath( $params['src'] );
if ( $path === null ) {
- return false; // invalid storage path
+ return self::SHA1_FAIL; // invalid storage path
}
- /** @noinspection PhpUnusedLocalVariableInspection */
- $ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$latest = !empty( $params['latest'] ); // use latest data?
if ( $this->cheapCache->hasField( $path, 'sha1', self::CACHE_TTL ) ) {
$stat = $this->cheapCache->getField( $path, 'sha1' );
return $stat['hash'];
}
}
- $hash = $this->doGetFileSha1Base36( $params );
- $this->cheapCache->setField( $path, 'sha1', [ 'hash' => $hash, 'latest' => $latest ] );
+ $sha1 = $this->doGetFileSha1Base36( $params );
+ if ( is_string( $sha1 ) ) {
+ $this->cheapCache->setField(
+ $path,
+ 'sha1',
+ [ 'hash' => $sha1, 'latest' => $latest ]
+ );
+ } elseif ( $sha1 === self::$RES_ABSENT ) {
+ $this->cheapCache->setField(
+ $path,
+ 'sha1',
+ [ 'hash' => self::SHA1_FAIL, 'latest' => $latest ]
+ );
+ } else {
+ $sha1 = self::SHA1_FAIL; // used for all failure cases
+ }
- return $hash;
+ return $sha1;
}
/**
* @see FileBackendStore::getFileSha1Base36()
* @param array $params
- * @return bool|string
+ * @return bool|string|null SHA1, false (missing file), or null (error)
*/
protected function doGetFileSha1Base36( array $params ) {
$fsFile = $this->getLocalReference( $params );
- if ( !$fsFile ) {
- return false;
- } else {
- return $fsFile->getSha1Base36();
+ if ( $fsFile instanceof FSFile ) {
+ $sha1 = $fsFile->getSha1Base36();
+
+ return is_string( $sha1 ) ? $sha1 : self::$RES_ERROR;
}
+
+ return ( $fsFile === self::$RES_ERROR ) ? self::$RES_ERROR : self::$RES_ABSENT;
}
final public function getFileProps( array $params ) {
/** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
+
$fsFile = $this->getLocalReference( $params );
- $props = $fsFile ? $fsFile->getProps() : FSFile::placeholderProps();
- return $props;
+ return $fsFile ? $fsFile->getProps() : FSFile::placeholderProps();
}
final public function getLocalReferenceMulti( array $params ) {
// Fetch local references of any remaning files...
$params['srcs'] = array_diff( $params['srcs'], array_keys( $fsFiles ) );
foreach ( $this->doGetLocalReferenceMulti( $params ) as $path => $fsFile ) {
- $fsFiles[$path] = $fsFile;
- if ( $fsFile ) { // update the process cache...
- $this->expensiveCache->setField( $path, 'localRef',
- [ 'object' => $fsFile, 'latest' => $latest ] );
+ if ( $fsFile instanceof FSFile ) {
+ $fsFiles[$path] = $fsFile;
+ $this->expensiveCache->setField(
+ $path,
+ 'localRef',
+ [ 'object' => $fsFile, 'latest' => $latest ]
+ );
+ } else {
+ $fsFiles[$path] = null; // used for all failure cases
}
}
/**
* @see FileBackendStore::getLocalReferenceMulti()
* @param array $params
- * @return array
+ * @return string[]|bool[]|null[] Map of (path => FSFile, false (missing), or null (error))
*/
protected function doGetLocalReferenceMulti( array $params ) {
return $this->doGetLocalCopyMulti( $params );
$params = $this->setConcurrencyFlags( $params );
$tmpFiles = $this->doGetLocalCopyMulti( $params );
+ foreach ( $tmpFiles as $path => $tmpFile ) {
+ if ( !$tmpFile ) {
+ $tmpFiles[$path] = null; // used for all failure cases
+ }
+ }
return $tmpFiles;
}
/**
* @see FileBackendStore::getLocalCopyMulti()
* @param array $params
- * @return array
+ * @return string[]|bool[]|null[] Map of (path => TempFSFile, false (missing), or null (error))
*/
abstract protected function doGetLocalCopyMulti( array $params );
* @return string|null
*/
public function getFileHttpUrl( array $params ) {
- return null; // not supported
+ return self::TEMPURL_ERROR; // not supported
}
final public function streamFile( array $params ) {
final public function directoryExists( array $params ) {
list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
if ( $dir === null ) {
- return false; // invalid storage path
+ return self::EXISTENCE_ERROR; // invalid storage path
}
if ( $shard !== null ) { // confined to a single container/shard
return $this->doDirectoryExists( $fullCont, $dir, $params );
$res = false; // response
foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) {
$exists = $this->doDirectoryExists( "{$fullCont}{$suffix}", $dir, $params );
- if ( $exists ) {
+ if ( $exists === true ) {
$res = true;
break; // found one!
- } elseif ( $exists === null ) { // error?
- $res = self::UNKNOWN; // if we don't find anything, it is indeterminate
+ } elseif ( $exists === self::$RES_ERROR ) {
+ $res = self::EXISTENCE_ERROR;
}
}
final public function getDirectoryList( array $params ) {
list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
- if ( $dir === null ) { // invalid storage path
- return self::UNKNOWN;
+ if ( $dir === null ) {
+ return self::EXISTENCE_ERROR; // invalid storage path
}
if ( $shard !== null ) {
// File listing is confined to a single container/shard
* @param string $container Resolved container name
* @param string $dir Resolved path relative to container
* @param array $params
- * @return Traversable|array|null Returns null on failure
+ * @return Traversable|array|null Iterable list or null (error)
*/
abstract public function getDirectoryListInternal( $container, $dir, array $params );
final public function getFileList( array $params ) {
list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
- if ( $dir === null ) { // invalid storage path
- return self::UNKNOWN;
+ if ( $dir === null ) {
+ return self::LIST_ERROR; // invalid storage path
}
if ( $shard !== null ) {
// File listing is confined to a single container/shard
* @param string $container Resolved container name
* @param string $dir Resolved path relative to container
* @param array $params
- * @return Traversable|string[]|null Returns null on failure
+ * @return Traversable|string[]|null Iterable list or null (error)
*/
abstract public function getFileListInternal( $container, $dir, array $params );
final public function preloadFileStat( array $params ) {
/** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
- $success = true; // no network errors
$params['concurrency'] = ( $this->parallelize !== 'off' ) ? $this->concurrency : 1;
$stats = $this->doGetFileStatMulti( $params );
return true; // not supported
}
- $latest = !empty( $params['latest'] ); // use latest data?
- foreach ( $stats as $path => $stat ) {
- $path = FileBackend::normalizeStoragePath( $path );
- if ( $path === null ) {
- continue; // this shouldn't happen
- }
- if ( is_array( $stat ) ) { // file exists
- // Strongly consistent backends can automatically set "latest"
- $stat['latest'] = $stat['latest'] ?? $latest;
- $this->cheapCache->setField( $path, 'stat', $stat );
- $this->setFileCache( $path, $stat ); // update persistent cache
- if ( isset( $stat['sha1'] ) ) { // some backends store SHA-1 as metadata
- $this->cheapCache->setField( $path, 'sha1',
- [ 'hash' => $stat['sha1'], 'latest' => $latest ] );
- }
- if ( isset( $stat['xattr'] ) ) { // some backends store headers/metadata
- $stat['xattr'] = self::normalizeXAttributes( $stat['xattr'] );
- $this->cheapCache->setField( $path, 'xattr',
- [ 'map' => $stat['xattr'], 'latest' => $latest ] );
- }
- } elseif ( $stat === false ) { // file does not exist
- $this->cheapCache->setField( $path, 'stat',
- $latest ? 'NOT_EXIST_LATEST' : 'NOT_EXIST' );
- $this->cheapCache->setField( $path, 'xattr',
- [ 'map' => false, 'latest' => $latest ] );
- $this->cheapCache->setField( $path, 'sha1',
- [ 'hash' => false, 'latest' => $latest ] );
- $this->logger->debug( __METHOD__ . ': File {path} does not exist', [
- 'path' => $path,
- ] );
- } else { // an error occurred
- $success = false;
- $this->logger->warning( __METHOD__ . ': Could not stat file {path}', [
- 'path' => $path,
- ] );
- }
- }
+ // Whether this queried the backend in high consistency mode
+ $latest = !empty( $params['latest'] );
- return $success;
+ return $this->ingestFreshFileStats( $stats, $latest );
}
/**
$paths[] = FileBackend::normalizeStoragePath( $item );
}
}
- // Get rid of any paths that failed normalization...
+ // Get rid of any paths that failed normalization
$paths = array_filter( $paths, 'strlen' ); // remove nulls
// Get all the corresponding cache keys for paths...
foreach ( $paths as $path ) {
$pathNames[$this->fileCacheKey( $path )] = $path;
}
}
- // Get all cache entries for these file cache keys...
+ // Get all cache entries for these file cache keys.
+ // Note that negatives are not cached by getFileStat()/preloadFileStat().
$values = $this->memCache->getMulti( array_keys( $pathNames ) );
- foreach ( $values as $cacheKey => $val ) {
+ // Load all of the results into process cache...
+ foreach ( array_filter( $values, 'is_array' ) as $cacheKey => $stat ) {
$path = $pathNames[$cacheKey];
- if ( is_array( $val ) ) {
- $val['latest'] = false; // never completely trust cache
- $this->cheapCache->setField( $path, 'stat', $val );
- if ( isset( $val['sha1'] ) ) { // some backends store SHA-1 as metadata
- $this->cheapCache->setField( $path, 'sha1',
- [ 'hash' => $val['sha1'], 'latest' => false ] );
- }
- if ( isset( $val['xattr'] ) ) { // some backends store headers/metadata
- $val['xattr'] = self::normalizeXAttributes( $val['xattr'] );
- $this->cheapCache->setField( $path, 'xattr',
- [ 'map' => $val['xattr'], 'latest' => false ] );
- }
+ // Sanity; this flag only applies to stat info loaded directly
+ // from a high consistency backend query to the process cache
+ unset( $stat['latest'] );
+
+ $this->cheapCache->setField( $path, 'stat', $stat );
+ if ( isset( $stat['sha1'] ) && strlen( $stat['sha1'] ) == 31 ) {
+ // Some backends store SHA-1 as metadata
+ $this->cheapCache->setField(
+ $path,
+ 'sha1',
+ [ 'hash' => $stat['sha1'], 'latest' => false ]
+ );
+ }
+ if ( isset( $stat['xattr'] ) && is_array( $stat['xattr'] ) ) {
+ // Some backends store custom headers/metadata
+ $stat['xattr'] = self::normalizeXAttributes( $stat['xattr'] );
+ $this->cheapCache->setField(
+ $path,
+ 'xattr',
+ [ 'map' => $stat['xattr'], 'latest' => false ]
+ );
}
}
}
}
public function isPathUsableInternal( $storagePath ) {
- return true;
+ return ( $this->resolveHashKey( $storagePath ) !== null );
}
protected function doCreateInternal( array $params ) {
protected function doGetFileStat( array $params ) {
$src = $this->resolveHashKey( $params['src'] );
if ( $src === null ) {
- return false; // invalid path
+ return self::$RES_ERROR; // invalid path
}
if ( isset( $this->files[$src] ) ) {
];
}
- return false;
+ return self::$RES_ABSENT;
}
protected function doGetLocalCopyMulti( array $params ) {
$tmpFiles = []; // (path => TempFSFile)
foreach ( $params['srcs'] as $srcPath ) {
$src = $this->resolveHashKey( $srcPath );
- if ( $src === null || !isset( $this->files[$src] ) ) {
- $fsFile = null;
+ if ( $src === null ) {
+ $fsFile = self::$RES_ERROR;
+ } elseif ( !isset( $this->files[$src] ) ) {
+ $fsFile = self::$RES_ABSENT;
} else {
// Create a new temporary file with the same extension...
$ext = FileBackend::extensionFromPath( $src );
if ( $fsFile ) {
$bytes = file_put_contents( $fsFile->getPath(), $this->files[$src]['data'] );
if ( $bytes !== strlen( $this->files[$src]['data'] ) ) {
- $fsFile = null;
+ $fsFile = self::$RES_ERROR;
}
}
}
}
public function getFeatures() {
- return ( FileBackend::ATTR_UNICODE_PATHS |
- FileBackend::ATTR_HEADERS | FileBackend::ATTR_METADATA );
+ return (
+ FileBackend::ATTR_UNICODE_PATHS |
+ FileBackend::ATTR_HEADERS |
+ FileBackend::ATTR_METADATA
+ );
}
protected function resolveContainerPath( $container, $relStoragePath ) {
$stat = $this->getContainerStat( $fullCont );
if ( is_array( $stat ) ) {
return $status; // already there
- } elseif ( $stat === self::UNKNOWN ) {
+ } elseif ( $stat === self::$RES_ERROR ) {
$status->fatal( 'backend-fail-internal', $this->name );
$this->logger->error( __METHOD__ . ': cannot get container stat' );
}
protected function doGetFileContentsMulti( array $params ) {
- $contents = [];
-
$auth = $this->getAuthentication();
$ep = array_diff_key( $params, [ 'srcs' => 1 ] ); // for error logging
// if the file does not exist. Do not waste time doing file stats here.
$reqs = []; // (path => op)
+ // Initial dummy values to preserve path order
+ $contents = array_fill_keys( $params['srcs'], self::$RES_ERROR );
foreach ( $params['srcs'] as $path ) { // each path in this concurrent batch
list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $path );
if ( $srcRel === null || !$auth ) {
- $contents[$path] = false;
- continue;
+ continue; // invalid storage path or auth error
}
// Create a new temporary memory file...
$handle = fopen( 'php://temp', 'wb' );
'stream' => $handle,
];
}
- $contents[$path] = false;
}
$opts = [ 'maxConnsPerHost' => $params['concurrency'] ];
list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $op['response'];
if ( $rcode >= 200 && $rcode <= 299 ) {
rewind( $op['stream'] ); // start from the beginning
- $contents[$path] = stream_get_contents( $op['stream'] );
+ $content = (string)stream_get_contents( $op['stream'] );
+ $size = strlen( $content );
+ // Make sure that stream finished
+ if ( $size === (int)$rhdrs['content-length'] ) {
+ $contents[$path] = $content;
+ } else {
+ $contents[$path] = self::$RES_ERROR;
+ $rerr = "Got {$size}/{$rhdrs['content-length']} bytes";
+ $this->onError( null, __METHOD__,
+ [ 'src' => $path ] + $ep, $rerr, $rcode, $rdesc );
+ }
} elseif ( $rcode === 404 ) {
- $contents[$path] = false;
+ $contents[$path] = self::$RES_ABSENT;
} else {
+ $contents[$path] = self::$RES_ERROR;
$this->onError( null, __METHOD__,
[ 'src' => $path ] + $ep, $rerr, $rcode, $rdesc );
}
return ( count( $status->value ) ) > 0;
}
- return self::UNKNOWN; // error
+ return self::$RES_ERROR;
}
/**
return $dirs; // nothing more
}
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$prefix = ( $dir == '' ) ? null : "{$dir}/";
return $files; // nothing more
}
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
$prefix = ( $dir == '' ) ? null : "{$dir}/";
- // $objects will contain a list of unfiltered names or CF_Object items
+ // $objects will contain a list of unfiltered names or stdClass items
// Non-recursive: only list files right under $dir
if ( !empty( $params['topOnly'] ) ) {
if ( !empty( $params['adviseStat'] ) ) {
}
$objects = $status->value;
- $files = $this->buildFileObjectListing( $params, $dir, $objects );
+ $files = $this->buildFileObjectListing( $objects );
// Page on the unfiltered object listing (what is returned may be filtered)
if ( count( $objects ) < $limit ) {
/**
* Build a list of file objects, filtering out any directories
- * and extracting any stat info if provided in $objects (for CF_Objects)
+ * and extracting any stat info if provided in $objects
*
- * @param array $params Parameters for getDirectoryList()
- * @param string $dir Resolved container directory path
- * @param array $objects List of CF_Object items or object names
+ * @param stdClass[]|string[] $objects List of stdClass items or object names
* @return array List of (names,stat array or null) entries
*/
- private function buildFileObjectListing( array $params, $dir, array $objects ) {
+ private function buildFileObjectListing( array $objects ) {
$names = [];
foreach ( $objects as $object ) {
if ( is_object( $object ) ) {
protected function doGetFileXAttributes( array $params ) {
$stat = $this->getFileStat( $params );
- if ( $stat ) {
- if ( !isset( $stat['xattr'] ) ) {
- // Stat entries filled by file listings don't include metadata/headers
- $this->clearCache( [ $params['src'] ] );
- $stat = $this->getFileStat( $params );
- }
+ // Stat entries filled by file listings don't include metadata/headers
+ if ( is_array( $stat ) && !isset( $stat['xattr'] ) ) {
+ $this->clearCache( [ $params['src'] ] );
+ $stat = $this->getFileStat( $params );
+ }
- // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
+ if ( is_array( $stat ) ) {
return $stat['xattr'];
- } else {
- return false;
}
+
+ return ( $stat === self::$RES_ERROR ) ? self::$RES_ERROR : self::$RES_ABSENT;
}
protected function doGetFileSha1base36( array $params ) {
$params['requireSHA1'] = true;
$stat = $this->getFileStat( $params );
- if ( $stat ) {
+ if ( is_array( $stat ) ) {
return $stat['sha1'];
- } else {
- return false;
}
+
+ return ( $stat === self::$RES_ERROR ) ? self::$RES_ERROR : self::$RES_ABSENT;
}
protected function doStreamFile( array $params ) {
}
protected function doGetLocalCopyMulti( array $params ) {
- /** @var TempFSFile[] $tmpFiles */
- $tmpFiles = [];
-
$auth = $this->getAuthentication();
$ep = array_diff_key( $params, [ 'srcs' => 1 ] ); // for error logging
// if the file does not exist. Do not waste time doing file stats here.
$reqs = []; // (path => op)
+ // Initial dummy values to preserve path order
+ $tmpFiles = array_fill_keys( $params['srcs'], self::$RES_ERROR );
foreach ( $params['srcs'] as $path ) { // each path in this concurrent batch
list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $path );
if ( $srcRel === null || !$auth ) {
- $tmpFiles[$path] = null;
- continue;
+ continue; // invalid storage path or auth error
}
// Get source file extension
$ext = FileBackend::extensionFromPath( $path );
// Create a new temporary file...
$tmpFile = $this->tmpFileFactory->newTempFSFile( 'localcopy_', $ext );
- if ( $tmpFile ) {
- $handle = fopen( $tmpFile->getPath(), 'wb' );
- if ( $handle ) {
- $reqs[$path] = [
- 'method' => 'GET',
- 'url' => $this->storageUrl( $auth, $srcCont, $srcRel ),
- 'headers' => $this->authTokenHeaders( $auth )
- + $this->headersFromParams( $params ),
- 'stream' => $handle,
- ];
- } else {
- $tmpFile = null;
- }
+ $handle = $tmpFile ? fopen( $tmpFile->getPath(), 'wb' ) : false;
+ if ( $handle ) {
+ $reqs[$path] = [
+ 'method' => 'GET',
+ 'url' => $this->storageUrl( $auth, $srcCont, $srcRel ),
+ 'headers' => $this->authTokenHeaders( $auth )
+ + $this->headersFromParams( $params ),
+ 'stream' => $handle,
+ ];
+ $tmpFiles[$path] = $tmpFile;
}
- $tmpFiles[$path] = $tmpFile;
}
- $isLatest = ( $this->isRGW || !empty( $params['latest'] ) );
+ // Ceph RADOS Gateway is in use (strong consistency) or X-Newest will be used
+ $latest = ( $this->isRGW || !empty( $params['latest'] ) );
+
$opts = [ 'maxConnsPerHost' => $params['concurrency'] ];
$reqs = $this->http->runMulti( $reqs, $opts );
foreach ( $reqs as $path => $op ) {
list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $op['response'];
fclose( $op['stream'] ); // close open handle
if ( $rcode >= 200 && $rcode <= 299 ) {
- $size = $tmpFiles[$path] ? $tmpFiles[$path]->getSize() : 0;
- // Double check that the disk is not full/broken
- if ( $size != $rhdrs['content-length'] ) {
- $tmpFiles[$path] = null;
+ /** @var TempFSFile $tmpFile */
+ $tmpFile = $tmpFiles[$path];
+ // Make sure that the stream finished and fully wrote to disk
+ $size = $tmpFile->getSize();
+ if ( $size !== (int)$rhdrs['content-length'] ) {
+ $tmpFiles[$path] = self::$RES_ERROR;
$rerr = "Got {$size}/{$rhdrs['content-length']} bytes";
$this->onError( null, __METHOD__,
[ 'src' => $path ] + $ep, $rerr, $rcode, $rdesc );
}
// Set the file stat process cache in passing
$stat = $this->getStatFromHeaders( $rhdrs );
- $stat['latest'] = $isLatest;
+ $stat['latest'] = $latest;
$this->cheapCache->setField( $path, 'stat', $stat );
} elseif ( $rcode === 404 ) {
- $tmpFiles[$path] = false;
+ $tmpFiles[$path] = self::$RES_ABSENT;
+ $this->cheapCache->setField(
+ $path,
+ 'stat',
+ $latest ? self::$ABSENT_LATEST : self::$ABSENT_NORMAL
+ );
} else {
- $tmpFiles[$path] = null;
+ $tmpFiles[$path] = self::$RES_ERROR;
$this->onError( null, __METHOD__,
[ 'src' => $path ] + $ep, $rerr, $rcode, $rdesc );
}
) {
list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
if ( $srcRel === null ) {
- return null; // invalid path
+ return self::TEMPURL_ERROR; // invalid path
}
$auth = $this->getAuthentication();
if ( !$auth ) {
- return null;
+ return self::TEMPURL_ERROR;
}
$ttl = $params['ttl'] ?? 86400;
}
}
- return null;
+ return self::TEMPURL_ERROR;
}
protected function directoriesAreVirtual() {
* @return array|bool|null False on 404, null on failure
*/
protected function getContainerStat( $container, $bypassCache = false ) {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
if ( $bypassCache ) { // purge cache
if ( !$this->containerStatCache->hasField( $container, 'stat' ) ) {
$auth = $this->getAuthentication();
if ( !$auth ) {
- return self::UNKNOWN;
+ return self::$RES_ERROR;
}
list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->http->run( [
$this->setContainerCache( $container, $stat ); // update persistent cache
}
} elseif ( $rcode === 404 ) {
- return false;
+ return self::$RES_ABSENT;
} else {
$this->onError( null, __METHOD__,
[ 'cont' => $container ], $rerr, $rcode, $rdesc );
- return self::UNKNOWN;
+ return self::$RES_ERROR;
}
}
$auth = $this->getAuthentication();
- $reqs = [];
+ $reqs = []; // (path => op)
+ // (a) Check the containers of the paths...
foreach ( $params['srcs'] as $path ) {
list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $path );
- if ( $srcRel === null ) {
- $stats[$path] = false;
- continue; // invalid storage path
- } elseif ( !$auth ) {
- $stats[$path] = self::UNKNOWN;
- continue;
+ if ( $srcRel === null || !$auth ) {
+ $stats[$path] = self::$RES_ERROR;
+ continue; // invalid storage path or auth error
}
- // (a) Check the container
$cstat = $this->getContainerStat( $srcCont );
- if ( $cstat === false ) {
- $stats[$path] = false;
+ if ( $cstat === self::$RES_ABSENT ) {
+ $stats[$path] = self::$RES_ABSENT;
continue; // ok, nothing to do
} elseif ( !is_array( $cstat ) ) {
- $stats[$path] = self::UNKNOWN;
+ $stats[$path] = self::$RES_ERROR;
continue;
}
];
}
+ // (b) Check the files themselves...
$opts = [ 'maxConnsPerHost' => $params['concurrency'] ];
$reqs = $this->http->runMulti( $reqs, $opts );
-
- foreach ( $params['srcs'] as $path ) {
- if ( array_key_exists( $path, $stats ) ) {
- continue; // some sort of failure above
- }
- // (b) Check the file
- list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $reqs[$path]['response'];
+ foreach ( $reqs as $path => $op ) {
+ list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $op['response'];
if ( $rcode === 200 || $rcode === 204 ) {
// Update the object if it is missing some headers
if ( !empty( $params['requireSHA1'] ) ) {
$stat['latest'] = true; // strong consistency
}
} elseif ( $rcode === 404 ) {
- $stat = false;
+ $stat = self::$RES_ABSENT;
} else {
- $stat = self::UNKNOWN;
+ $stat = self::$RES_ERROR;
$this->onError( null, __METHOD__, $params, $rerr, $rcode, $rdesc );
}
$stats[$path] = $stat;
/** @var array List of path or (path,stat array) entries */
protected $bufferIter = [];
- /** @var string List items *after* this path */
+ /** @var string|null List items *after* this path */
protected $bufferAfter = null;
/** @var int */
$this->pos = 0;
$this->bufferAfter = null;
$this->bufferIter = $this->pageFromList(
+ // @phan-suppress-next-line PhanTypeMismatchArgumentPropertyReferenceReal
$this->container, $this->dir, $this->bufferAfter, self::PAGE_SIZE, $this->params
); // updates $this->bufferAfter
}
protected function doPrecheck( array &$predicates ) {
$status = StatusValue::newGood();
- // Check if the source file exists
- if ( !$this->fileExists( $this->params['src'], $predicates ) ) {
+
+ // Check source file existence
+ $srcExists = $this->fileExists( $this->params['src'], $predicates );
+ if ( $srcExists === false ) {
if ( $this->getParam( 'ignoreMissingSource' ) ) {
$this->doOperation = false; // no-op
// Update file existence predicates (cache 404s)
return $status;
}
- // Check if a file can be placed/changed at the destination
- } elseif ( !$this->backend->isPathUsableInternal( $this->params['dst'] ) ) {
- $status->fatal( 'backend-fail-usable', $this->params['dst'] );
- $status->fatal( 'backend-fail-copy', $this->params['src'], $this->params['dst'] );
+ } elseif ( $srcExists === FileBackend::EXISTENCE_ERROR ) {
+ $status->fatal( 'backend-fail-stat', $this->params['src'] );
return $status;
}
protected function doPrecheck( array &$predicates ) {
$status = StatusValue::newGood();
- // Check if the source data is too big
- if ( strlen( $this->getParam( 'content' ) ) > $this->backend->maxFileSizeInternal() ) {
- $status->fatal( 'backend-fail-maxsize',
- $this->params['dst'], $this->backend->maxFileSizeInternal() );
- $status->fatal( 'backend-fail-create', $this->params['dst'] );
- return $status;
- // Check if a file can be placed/changed at the destination
- } elseif ( !$this->backend->isPathUsableInternal( $this->params['dst'] ) ) {
- $status->fatal( 'backend-fail-usable', $this->params['dst'] );
- $status->fatal( 'backend-fail-create', $this->params['dst'] );
+ // Check if the source data is too big
+ $maxBytes = $this->backend->maxFileSizeInternal();
+ if ( strlen( $this->getParam( 'content' ) ) > $maxBytes ) {
+ $status->fatal( 'backend-fail-maxsize', $this->params['dst'], $maxBytes );
return $status;
}
- // Check if destination file exists
+ // Check if an incompatible destination file exists
$status->merge( $this->precheckDestExistence( $predicates ) );
$this->params['dstExists'] = $this->destExists; // see FileBackendStore::setFileCache()
if ( $status->isOK() ) {
}
protected function doAttempt() {
- if ( !$this->overwriteSameCase ) {
+ if ( $this->overwriteSameCase ) {
+ $status = StatusValue::newGood(); // nothing to do
+ } else {
// Create the file at the destination
- return $this->backend->createInternal( $this->setFlags( $this->params ) );
+ $status = $this->backend->createInternal( $this->setFlags( $this->params ) );
}
- return StatusValue::newGood();
+ return $status;
}
protected function getSourceSha1Base36() {
protected function doPrecheck( array &$predicates ) {
$status = StatusValue::newGood();
- // Check if the source file exists
- if ( !$this->fileExists( $this->params['src'], $predicates ) ) {
+
+ // Check source file existence
+ $srcExists = $this->fileExists( $this->params['src'], $predicates );
+ if ( $srcExists === false ) {
if ( $this->getParam( 'ignoreMissingSource' ) ) {
$this->doOperation = false; // no-op
// Update file existence predicates (cache 404s)
return $status;
}
- // Check if a file can be placed/changed at the source
- } elseif ( !$this->backend->isPathUsableInternal( $this->params['src'] ) ) {
- $status->fatal( 'backend-fail-usable', $this->params['src'] );
- $status->fatal( 'backend-fail-delete', $this->params['src'] );
+ } elseif ( $srcExists === FileBackend::EXISTENCE_ERROR ) {
+ $status->fatal( 'backend-fail-stat', $this->params['src'] );
return $status;
}
protected function doPrecheck( array &$predicates ) {
$status = StatusValue::newGood();
- // Check if the source file exists
- if ( !$this->fileExists( $this->params['src'], $predicates ) ) {
+
+ // Check source file existence
+ $srcExists = $this->fileExists( $this->params['src'], $predicates );
+ if ( $srcExists === false ) {
$status->fatal( 'backend-fail-notexists', $this->params['src'] );
return $status;
- // Check if a file can be placed/changed at the source
- } elseif ( !$this->backend->isPathUsableInternal( $this->params['src'] ) ) {
- $status->fatal( 'backend-fail-usable', $this->params['src'] );
- $status->fatal( 'backend-fail-describe', $this->params['src'] );
+ } elseif ( $srcExists === FileBackend::EXISTENCE_ERROR ) {
+ $status->fatal( 'backend-fail-stat', $this->params['src'] );
return $status;
}
// Update file existence predicates
- $predicates['exists'][$this->params['src']] =
- $this->fileExists( $this->params['src'], $predicates );
+ $predicates['exists'][$this->params['src']] = $srcExists;
$predicates['sha1'][$this->params['src']] =
$this->fileSha1( $this->params['src'], $predicates );
return StatusValue::newFatal( 'fileop-fail-state', self::STATE_NEW, $this->state );
}
$this->state = self::STATE_CHECKED;
+
+ $status = StatusValue::newGood();
+ $storagePaths = array_merge( $this->storagePathsRead(), $this->storagePathsChanged() );
+ foreach ( array_unique( $storagePaths ) as $storagePath ) {
+ if ( !$this->backend->isPathUsableInternal( $storagePath ) ) {
+ $status->fatal( 'backend-fail-usable', $storagePath );
+ }
+ }
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+
$status = $this->doPrecheck( $predicates );
if ( !$status->isOK() ) {
$this->failed = true;
return $status;
}
+ } elseif ( $this->destExists === FileBackend::EXISTENCE_ERROR ) {
+ $status->fatal( 'backend-fail-stat', $this->params['dst'] );
}
return $status;
/**
* Check if a file will exist in storage when this operation is attempted
*
+ * Ideally, the file stat entry should already be preloaded via preloadFileStat().
+ * Otherwise, this will query the backend.
+ *
* @param string $source Storage path
* @param array $predicates
- * @return bool
+ * @return bool|null Whether the file will exist or null on error
*/
final protected function fileExists( $source, array $predicates ) {
if ( isset( $predicates['exists'][$source] ) ) {
}
/**
- * Get the SHA-1 of a file in storage when this operation is attempted
+ * Get the SHA-1 hash a file in storage will have when this operation is attempted
+ *
+ * Ideally, file the stat entry should already be preloaded via preloadFileStat() and
+ * the backend tracks hashes as extended attributes. Otherwise, this will query the backend.
*
* @param string $source Storage path
* @param array $predicates
- * @return string|bool False on failure
+ * @return string|bool The SHA-1 hash the file will have or false if non-existent or on error
*/
final protected function fileSha1( $source, array $predicates ) {
if ( isset( $predicates['sha1'][$source] ) ) {
protected function doPrecheck( array &$predicates ) {
$status = StatusValue::newGood();
- // Check if the source file exists
- if ( !$this->fileExists( $this->params['src'], $predicates ) ) {
+
+ // Check source file existence
+ $srcExists = $this->fileExists( $this->params['src'], $predicates );
+ if ( $srcExists === false ) {
if ( $this->getParam( 'ignoreMissingSource' ) ) {
$this->doOperation = false; // no-op
// Update file existence predicates (cache 404s)
return $status;
}
- // Check if a file can be placed/changed at the destination
- } elseif ( !$this->backend->isPathUsableInternal( $this->params['dst'] ) ) {
- $status->fatal( 'backend-fail-usable', $this->params['dst'] );
- $status->fatal( 'backend-fail-move', $this->params['src'], $this->params['dst'] );
+ } elseif ( $srcExists === FileBackend::EXISTENCE_ERROR ) {
+ $status->fatal( 'backend-fail-stat', $this->params['src'] );
return $status;
}
- // Check if destination file exists
+ // Check if an incompatible destination file exists
$status->merge( $this->precheckDestExistence( $predicates ) );
$this->params['dstExists'] = $this->destExists; // see FileBackendStore::setFileCache()
if ( $status->isOK() ) {
protected function doPrecheck( array &$predicates ) {
$status = StatusValue::newGood();
- // Check if the source file exists on the file system
+
+ // Check if the source file exists in the file system and is not too big
if ( !is_file( $this->params['src'] ) ) {
$status->fatal( 'backend-fail-notexists', $this->params['src'] );
return $status;
- // Check if the source file is too big
- } elseif ( filesize( $this->params['src'] ) > $this->backend->maxFileSizeInternal() ) {
- $status->fatal( 'backend-fail-maxsize',
- $this->params['dst'], $this->backend->maxFileSizeInternal() );
- $status->fatal( 'backend-fail-store', $this->params['src'], $this->params['dst'] );
-
- return $status;
- // Check if a file can be placed/changed at the destination
- } elseif ( !$this->backend->isPathUsableInternal( $this->params['dst'] ) ) {
- $status->fatal( 'backend-fail-usable', $this->params['dst'] );
- $status->fatal( 'backend-fail-store', $this->params['src'], $this->params['dst'] );
+ }
+ // Check if the source file is too big
+ $maxBytes = $this->backend->maxFileSizeInternal();
+ if ( filesize( $this->params['src'] ) > $maxBytes ) {
+ $status->fatal( 'backend-fail-maxsize', $this->params['dst'], $maxBytes );
return $status;
}
- // Check if destination file exists
+ // Check if an incompatible destination file exists
$status->merge( $this->precheckDestExistence( $predicates ) );
$this->params['dstExists'] = $this->destExists; // see FileBackendStore::setFileCache()
if ( $status->isOK() ) {
}
protected function doAttempt() {
- if ( !$this->overwriteSameCase ) {
+ if ( $this->overwriteSameCase ) {
+ $status = StatusValue::newGood(); // nothing to do
+ } else {
// Store the file at the destination
- return $this->backend->storeInternal( $this->setFlags( $this->params ) );
+ $status = $this->backend->storeInternal( $this->setFlags( $this->params ) );
}
- return StatusValue::newGood();
+ return $status;
}
protected function getSourceSha1Base36() {
}
curl_setopt( $ch, CURLOPT_READFUNCTION,
function ( $ch, $fd, $length ) {
- $data = fread( $fd, $length );
- $len = strlen( $data );
- return $data;
+ return (string)fread( $fd, $length );
}
);
} elseif ( $req['method'] === 'POST' ) {
$name = strtolower( $name );
$value = trim( $value );
if ( isset( $req['response']['headers'][$name] ) ) {
- // @phan-suppress-next-line PhanTypeInvalidDimOffset
$req['response']['headers'][$name] .= ', ' . $value;
} else {
$req['response']['headers'][$name] = $value;
* @codingStandardsIgnoreStart
* @phan-param array{logger?:Psr\Log\LoggerInterface,asyncHandler?:callable,keyspace?:string,reportDupes?:bool,syncTimeout?:int,segmentationSize?:int,segmentedValueMaxSize?:int,maxKeys?:int} $params
* @codingStandardsIgnoreEnd
- * @suppress PhanTypeInvalidDimOffset
*/
function __construct( $params = [] ) {
$params['segmentationSize'] = $params['segmentationSize'] ?? INF;
* @note Options added in 1.33: creating
* @note Options added in 1.34: version, walltime
* @return bool Success
- * @suppress PhanTypeInvalidDimOffset
*/
final public function set( $key, $value, $ttl = self::TTL_INDEFINITE, array $opts = [] ) {
$now = $this->getCurrentTime();
* @note Options added in 1.31: staleTTL, graceTTL
* @note Options added in 1.33: touchedCallback
* @note Callable type hints are not used to avoid class-autoloading
- * @suppress PhanTypeInvalidDimOffset
*/
final public function getWithSetCallback( $key, $ttl, $callback, array $opts = [] ) {
$version = $opts['version'] ?? null;
$this->setInterimValue( $key, $value, $lockTSE, $version, $walltime );
} else {
$finalSetOpts = [
- // @phan-suppress-next-line PhanTypeInvalidDimOffset
'since' => $setOpts['since'] ?? $preCallbackTime,
'version' => $version,
'staleTTL' => $staleTTL,
$chance = ( 1 - $curTTL / $lowTTL );
+ // @phan-suppress-next-line PhanTypeMismatchArgumentInternal
return mt_rand( 1, 1e9 ) <= 1e9 * $chance;
}
// Ramp up $chance from 0 to its nominal value over RAMPUP_TTL seconds to avoid stampedes
$chance *= ( $timeOld <= self::$RAMPUP_TTL ) ? $timeOld / self::$RAMPUP_TTL : 1;
+ // @phan-suppress-next-line PhanTypeMismatchArgumentInternal
return mt_rand( 1, 1e9 ) <= 1e9 * $chance;
}
$key = $this->getCacheKey( $serverIndexes );
# Randomize TTLs to reduce stampedes (4.0 - 5.0 sec)
+ // @phan-suppress-next-line PhanTypeMismatchArgumentInternal
$ttl = mt_rand( 4e6, 5e6 ) / 1e6;
# Keep keys around longer as fallbacks
$staleTTL = 60;
* All metadata related, since both JPEG and TIFF support Exif.
*
* @ingroup Media
- * @phan-file-suppress PhanUndeclaredConstant Phan doesn't read constants in MediaHandler
- * when accessed via self::
*/
class ExifBitmapHandler extends BitmapHandler {
const BROKEN_FILE = '-1'; // error extracting metadata
* @param bool $isMain
* @return mixed|string
* @private
- * @suppress PhanTypeInvalidDimOffset
*/
public function formatHeadings( $text, $origText, $isMain = true ) {
# Inhibit editsection links if requested in the page
Hooks::run( 'ParserMakeImageParams', [ $title, $file, &$params, $this ] );
# Linker does the rest
- // @phan-suppress-next-line PhanTypeInvalidDimOffset
$time = $options['time'] ?? false;
$ret = Linker::makeImageLink( $this, $title, $file, $params['frame'], $params['handler'],
$time, $descQuery, $this->mOptions->getThumbSize() );
if ( !is_null( $this->parsers ) ) {
return;
}
+ $this->parsers = [];
if ( isset( $this->conf['shortOutput'] ) ) {
$this->shortOutput = $this->conf['shortOutput'];
// @codeCoverageIgnoreStart
// T109544 - If a feed formatter returns null, this will otherwise cause an
// error in at least RedisPubSubFeedEngine. Not sure best to handle this.
- // @phan-suppress-next-line PhanTypeMismatchReturn
return;
// @codeCoverageIgnoreEnd
}
$idx = -1;
foreach ( $grpModules as $name => $module ) {
$shouldEmbed = $module->shouldEmbedModule( $context );
- // @phan-suppress-next-line PhanTypeInvalidDimOffset
if ( !$moduleSets || $moduleSets[$idx][0] !== $shouldEmbed ) {
$moduleSets[++$idx] = [ $shouldEmbed, [] ];
}
$dataPath->getRemoteBasePath()
);
} else {
- // @phan-suppress-next-line PhanTypeSuspiciousStringExpression
$path = dirname( $dataPath ) . '/' . $path;
}
};
}
public function doPostCommitUpdates( array $visibilityChangeMap ) {
- /** @var LocalFile $file */
$file = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo()
->newFile( $this->title );
- '@phan-var LocalFile $file';
$file->purgeCache();
$file->purgeDescription();
"$provider returned empty session info with id flagged unsafe"
);
}
- // @phan-suppress-next-line PhanTypeInvalidDimOffset
$compare = $infos ? SessionInfo::compare( $infos[0], $info ) : -1;
if ( $compare > 0 ) {
continue;
// Give site config file a chance to run the script in a wrapper.
// The caller may likely want to call wfBasename() on $script.
Hooks::run( 'wfShellWikiCmd', [ &$script, &$parameters, &$options ] );
- // @phan-suppress-next-line PhanTypeInvalidDimOffset
$cmd = [ $options['php'] ?? $wgPhpCli ];
if ( isset( $options['wrapper'] ) ) {
$cmd[] = $options['wrapper'];
if ( isset( $item['itemtitle'] ) ) {
$attrs['title'] = $item['itemtitle'];
}
- // @phan-suppress-next-line PhanTypeInvalidDimOffset
return Html::rawElement( $options['tag'] ?? 'li', $attrs, $html );
}
*/
private function getExceptionList() {
if ( $this->exceptionList === null ) {
+ $this->exceptionList = [];
$exList = $this->msg( 'uncategorized-categories-exceptionlist' )
->inContentLanguage()->plain();
$proposedTitles = explode( "\n", $exList );
* @param string $name
* @param string $value
* @return string
- * @suppress PhanTypeArraySuspiciousNullable,PhanTypeArraySuspicious
+ * @suppress PhanTypeArraySuspicious
*/
function formatValue( $name, $value ) {
static $msg = null;
public static $canonicalNames = [
NS_MEDIA => 'Media',
NS_SPECIAL => 'Special',
+ NS_MAIN => '',
NS_TALK => 'Talk',
NS_USER => 'User',
NS_USER_TALK => 'User_talk',
* Returns array of all defined namespaces with their canonical
* (English) names.
*
- * @return array
+ * @return string[]
*/
public function getCanonicalNamespaces() {
if ( $this->canonicalNamespaces === null ) {
*/
public function getValidNamespaces() {
if ( is_null( $this->validNamespaces ) ) {
+ $this->validNamespaces = [];
foreach ( array_keys( $this->getCanonicalNamespaces() ) as $ns ) {
if ( $ns >= 0 ) {
$this->validNamespaces[] = $ns;
return $this->$name;
} else {
wfLogWarning( 'tried to get non-visible property' );
- return null;
+ $null = null;
+ return $null;
}
}
}
# Sometimes a language will be localised but not actually exist on this wiki.
- // @phan-suppress-next-line PhanTypeMismatchForeach
foreach ( $this->namespaceNames as $key => $text ) {
if ( !isset( $validNamespaces[$key] ) ) {
unset( $this->namespaceNames[$key] );
# The above mixing may leave namespaces out of canonical order.
# Re-order by namespace ID number...
- // @phan-suppress-next-line PhanTypeMismatchArgumentInternal
ksort( $this->namespaceNames );
Hooks::run( 'LanguageGetNamespaces', [ &$this->namespaceNames ] );
$fallbackChain = array_reverse( $fallbackChain );
foreach ( $fallbackChain as $code ) {
if ( isset( $newWords[$code] ) ) {
- // @phan-suppress-next-line PhanTypeMismatchProperty
$this->mMagicExtensions = $newWords[$code] + $this->mMagicExtensions;
}
}
"backend-fail-batchsize": "أعطت خلفية التخزين دفعة $1 ملف {{PLURAL:$1|عملية|عمليات}}; الحد الأقصى هو $2 {{PLURAL:$2|عملية|عمليات}}.",
"backend-fail-usable": "تعذر قراءة أو كتابة الملف \"$1\" لنقص في التراخيص أو فقدان الدلائل/الحاويات.",
"backend-fail-stat": "لا يمكن قراءة حالة الملف \"$1\".",
- "backend-fail-hash": "يمكن تحديد دالة التشفير لملف \"$1\".",
+ "backend-fail-hash": "Ù\84ا Ù\8aÙ\85Ù\83Ù\86 تØدÙ\8aد داÙ\84Ø© اÙ\84تشÙ\81Ù\8aر Ù\84Ù\85Ù\84Ù\81 \"$1\".",
"filejournal-fail-dbconnect": "تعذر ربط الإتصال بقاعدة بيانات خلفية التخزين \"$1\".",
"filejournal-fail-dbquery": "تعذر تحديث قاعدة بيانات خلفية تخزين \"$1\".",
"lockmanager-notlocked": "تعذر فتح \"$1\"، الملف غير مغلق.",
"deletereason-dropdown": "*Əsas silmə səbəbləri\n** Spam\n** Vandalizm\n** Müəllif hüququ pozuntusu\n** Müəllif istəyi\n** Səhv yönləndirmə",
"delete-edit-reasonlist": "Silmə səbəblərinin redaktəsi",
"delete-toobig": "Bu səhifə $1-dən artıq redaktə ilə çox böyük redaktə tarixçəsinə malikdir.\n\"{{SITENAME}}\" saytının fəaliyyətində problemlər yaratmamaq üçün bu cür səhifələrin silinməsi qadağandır.",
+ "deleting-backlinks-warning": "<strong>Xəbərdarlıq:</strong> Silmək istədiyiniz səhifəyə [[Xüsusi:WhatLinksHere/{{FULLPAGENAME}}|başqa səhifələr]]dən keçid verilib.",
"rollback": "əvvəlki halına qaytar",
"rollbacklink": "əvvəlki halına qaytar",
"rollbacklinkcount": "$1 {{PLURAL:$1|dəyişikliyi|dəyişikliyi}} geri qaytar",
"filedelete-submit": "Usap",
"filedelete-success": "<strong>$1</strong> sampun kausapin.",
"filedelete-maintenance-title": "Nénten prasida ngusapin berkas",
- "randompage": "Kaca punapi kémanten",
+ "randompage": "Kaca ulah-aluh",
"statistics": "Statistik",
"statistics-articles": "Kaca daging",
"brokenredirects-edit": "uah",
"tooltip-n-portal": "Indik proyék, sané prasida kalaksanayang, genah ngrereh wantuan",
"tooltip-n-currentevents": "Rereh pidarta indik kawéntenan sané pinih anyar",
"tooltip-n-recentchanges": "Bacakan uahan sané mangkin ring wiki",
- "tooltip-n-randompage": "Cihnayang kaca napi kémanten",
+ "tooltip-n-randompage": "Cihnayang kaca ulah-aluh",
"tooltip-n-help": "Genah ngrereh wantuan",
"tooltip-t-whatlinkshere": "Bacakan makasami kaca ring wiki sané nuju iriki",
"tooltip-t-recentchangeslinked": "Uahan sané mangkin saking kaca-kaca sané linked ring kaca puniki",
"pageinfo-header-restrictions": "Saiban kaca",
"pageinfo-header-properties": "Properti suratan",
"pageinfo-display-title": "Edengang judul",
+ "pageinfo-length": "Dawannyané kaca (ring bita)",
"pageinfo-namespace": "Genah wastan",
"pageinfo-article-id": "ID kaca",
"pageinfo-language": "Basa ring daging kaca",
"confirmemail_body_changed": "Alguien, probablemente usted,\nha modificado la dirección de correo electrónico asociado a la cuenta \"$2\" hacia esta en {{SITENAME}}, desde la dirección IP $1.\n\nPara confirmar que esta cuenta realmente le pertenece y reactivar las funciones de correo electrónico en {{SITENAME}}, abra este enlace en su navegador:\n\n$3\n\nSi la cuenta *no* le pertenece, sigua el siguiente enlace para cancelar la confirmación:\n\n$5\n\nEste código de confirmación expirará el $4.",
"deletedwhileediting": "'''Aviso''': ¡Esta página fue borrada después de que usted empezara a editar!",
"confirmrecreate": "{{GENDER:$1|El usuario|La usuaria}} [[User:$1|$1]] ([[User talk:$1|discusión]]) borró esta página después de que usted comenzara a editarla, por este motivo:\n: <em>$2</em>\nPor favor confirme que realmente quiere volver a crearla.",
+ "confirm-purge-top": "¿Quiere vaciar la antememoria de esta página?",
"watchlistedit-normal-explain": "Los títulos de su lista de seguimiento se muestran debajo.\nPara eliminar un título, marque la casilla junto a él, y haga clic en ''{{int:Watchlistedit-normal-submit}}''.\nTambién puede [[Special:EditWatchlist/raw|editar la lista de en crudo]].",
"watchlistedit-raw-explain": "Los títulos de su lista de seguimiento se muestran debajo. Esta lista puede ser editada añadiendo o eliminando líneas de la lista;\nun título por línea.\nCuando acabe, haga clic en \"{{int:Watchlistedit-raw-submit}}\".\nTambién puede [[Special:EditWatchlist|usar el editor estándar]].",
"watchlistedit-raw-done": "Su lista de seguimiento ha sido actualizada.",
"nocreate-loggedin": "No tienes permiso para crear páginas nuevas.",
"sectioneditnotsupported-title": "Edición de sección no admitida",
"sectioneditnotsupported-text": "No se admite la edición de secciones en esta página.",
+ "modeleditnotsupported-title": "No se admite la edición",
+ "modeleditnotsupported-text": "No se admite la edición en el modelo de contenidos $1.",
"permissionserrors": "Error de permisos",
"permissionserrorstext": "No tienes permiso para hacer eso, por {{PLURAL:$1|el siguiente motivo|los siguientes motivos}}:",
"permissionserrorstext-withaction": "No tienes permiso para $2, por {{PLURAL:$1|el siguiente motivo|los siguientes motivos}}:",
"content-model-json": "JSON",
"content-json-empty-object": "Objeto vacío",
"content-json-empty-array": "Matriz vacía",
+ "unsupported-content-model": "<strong>Atención:</strong> en este wiki no se admite el modelo de contenidos $1.</strong>",
+ "unsupported-content-diff": "No se admiten las diferencias en el modelo de contenidos $1.",
+ "unsupported-content-diff2": "En este wiki no se admiten las diferencias entre los modelos de contenidos $1 y $2.",
"deprecated-self-close-category": "Páginas que utilizan etiquetas HTML autocerradas no válidas",
"deprecated-self-close-category-desc": "Esta página contiene etiquetas HTML autocerradas no válidas, tales como <code><b/></code> o <code><span/></code>. El comportamiento de estas cambiará pronto para ser coherente con la especificación de HTML5, por lo que su utilización en el wikitexto está obsoleta.",
"duplicate-args-warning": "<strong>Aviso:</strong> [[:$1]] llama a [[:$2]] con más de un valor para el parámetro «$3». Se usará solo el último valor proporcionado.",
"right-reupload-own": "Subir una nueva versión de un archivo propio",
"right-reupload-shared": "Sobrescribir localmente archivos presentes en el repositorio multimedia compartido",
"right-upload_by_url": "Subir un archivo a traves de un URL",
- "right-purge": "Purgar la caché de una página en el servidor",
+ "right-purge": "Purgar la antememoria de una página en el servidor",
"right-autoconfirmed": "No resultar afectado por los límites de frecuencia de edición para las IP",
"right-bot": "Ser tratado como un proceso automático",
"right-nominornewtalk": "No accionar el aviso de mensajes nuevos al realizar ediciones menores en páginas de discusión",
"delete_and_move_reason": "Borrada para permitir el traslado de \"[[$1]]\"",
"selfmove": "El título es el mismo;\nno se puede trasladar una página sobre sí misma.",
"immobile-source-namespace": "No se pueden trasladar páginas en el espacio de nombres «$1»",
+ "immobile-source-namespace-iw": "No es posible trasladar páginas de otros wikis desde este.",
"immobile-target-namespace": "No se puede trasladar páginas al espacio de nombres «$1»",
"immobile-target-namespace-iw": "Un enlace interwiki no es un destino válido para trasladar una página.",
"immobile-source-page": "Esta página no se puede renombrar.",
"immobile-target-page": "No se puede trasladar a ese título.",
+ "movepage-invalid-target-title": "El nombre solicitado no es válido.",
"bad-target-model": "El destino deseado utiliza un modelo diferente de contenido. No se puede realizar la conversión de $1 a $2.",
"imagenocrossnamespace": "No se puede trasladar el archivo a un espacio de nombres que no es para archivos.",
"nonfile-cannot-move-to-file": "No es posible trasladar lo que no es un archivo al espacio de nombres de archivos.",
"recreate": "Recrear",
"confirm-purge-title": "Purgar esta página",
"confirm_purge_button": "Aceptar",
- "confirm-purge-top": "¿Quieres vaciar la caché de esta página?",
- "confirm-purge-bottom": "Purgar una página limpia la caché y fuerza a que aparezca la versión más actual.",
+ "confirm-purge-top": "¿Quieres vaciar la antememoria de esta página?",
+ "confirm-purge-bottom": "Purgar una página limpia la antememoria y fuerza a que aparezca la versión más actual.",
"confirm-watch-button": "Aceptar",
"confirm-watch-top": "¿Añadir esta página a tu lista de seguimiento?",
"confirm-unwatch-button": "Aceptar",
"exif-scenetype-1": "Direktno fotografisana slika",
"exif-customrendered-0": "Normalni proces",
"exif-customrendered-1": "Podešeni proces",
+ "exif-customrendered-2": "HDR (bez spremlenog originala)",
+ "exif-customrendered-3": "HDR (sa spremlenim originalom)",
+ "exif-customrendered-4": "Original (za HDR)",
"exif-exposuremode-0": "Automatska ekpozicija",
"exif-exposuremode-1": "Ručna ekspozicija",
"exif-exposuremode-2": "Automatski određen raspon",
"sessionfailure": "Näyttää siltä, että tämänhetkisessä istunnossasi on jokin ongelma; \ntämä toiminto on peruutettu varotoimena istunnon kaappaamisen estämiseksi.\nLähetä lomake uudelleen.",
"changecontentmodel": "Muuta sivun sisältömallia",
"changecontentmodel-legend": "Muuta sisältömallia",
- "changecontentmodel-title-label": "Sivun otsikko",
- "changecontentmodel-model-label": "Uusi sisältömalli",
+ "changecontentmodel-title-label": "Sivun otsikko:",
+ "changecontentmodel-model-label": "Uusi sisältömalli:",
"changecontentmodel-reason-label": "Syy",
"changecontentmodel-submit": "Tee muutos",
"changecontentmodel-success-title": "Sisältömallia on muutettu",
"fri": "pe",
"sat": "la",
"january": "janyaari",
- "february": "helmikuu",
+ "february": "febryaari",
"march": "maaliskuu",
"april": "huhtikuu",
"may_long": "toukokuu",
"june": "kesäkuu",
- "july": "jyyli",
+ "july": "heinäkuu",
"august": "elokuu",
"september": "septempäri",
"october": "lokakuu",
"november": "marraskuu",
"december": "tesämperi",
"january-gen": "janyaarin",
- "february-gen": "helmikuun",
+ "february-gen": "febryaarin",
"march-gen": "maaliskuun",
"april-gen": "huhtikuun",
"may-gen": "toukokuun",
"june-gen": "kesäkuun",
- "july-gen": "jyylin",
+ "july-gen": "heinäkuun",
"august-gen": "elokuun",
"september-gen": "septempäri",
"october-gen": "lokakuun",
"jun": "kesäkuu",
"jul": "heinäkuu",
"aug": "elokuu",
- "sep": "syyskuu",
+ "sep": "septempäri",
"oct": "lokakuu",
"nov": "marraskuu",
"dec": "joulukuu",
"createaccount": "Luo käyttäjäkonttu",
"createacct-emailrequired": "E-postin atressi",
"createacct-another-submit": "Luo konttu",
+ "createacct-benefit-body1": "{{PLURAL:$1|mookkaus|mookhausta}}",
"mailmypassword": "Uusi salasana",
"loginlanguagelabel": "Kieli: $1",
"pt-login": "Lokkaa sisäle",
"loginreqlink": "lokkaa sisäle",
"newarticle": "(Uusi)",
"newarticletext": "Linkki vei sinun sivule, joka ei vielä ole.\nSaatat luoa sivun kirjottamalla alla olehvaan kenthään (katto [$1 apusivu] lisää tietoja).\nJos et halua luoa sivua, käytä browserin <strong>takashii</strong> knappia.",
- "noarticletext": "Tällä hetkellä tällä sivulla ei ole tekstiä.\nTällä hetkelä tällä sivula ei ole tekstiä.\nSaatat [[Special:Search/{{PAGENAME}}|hakea sivun nimelä]] muilta sivuilta,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} hakea aiheesheen liittyviä lokkia]\neli [{{fullurl:{{FULLPAGENAME}}|action=edit}} mookata tätä sivua]</span>.",
+ "noarticletext": "Tällä hetkelä tällä sivula ei ole tekstiä.\nSaatat [[Special:Search/{{PAGENAME}}|hakea sivun nimelä]] muilta sivuilta,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} hakea aiheesheen liittyviä lokkia]\neli [{{fullurl:{{FULLPAGENAME}}|action=edit}} luoa tämä sivu]</span>.",
"noarticletext-nopermission": "Tällä hetkelä tällä sivula ei ole tekstiä.\nSaatat [[Special:Search/{{PAGENAME}}|hakea sivun nimelä]] muilta sivuilta,\neli <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} hakea relevantista lokista]\neli [{{fullurl:{{FULLPAGENAME}}|action=edit}} mookata tätä sivua]</span>.",
"previewnote": "'''Tämä on vasta sivun etukattelu. Sivua ei ole vielä säästetty!'''",
"editing": "Mookathaan sivua $1",
"right-upload": "Lattaa ylös fiiliä",
"newuserlogpage": "Uuitten käyttäjitten loki",
"action-edit": "mookkaa tätä sivua",
+ "action-createaccount": "luoa tätä käyttäjäkontthua",
"action-move-categorypages": "siirä katekuurisivuja",
"nchanges": "$1 {{PLURAL:$1|muutos|muutosta}}",
"enhancedrc-history": "histuuria",
"rclistfrom": "Näytä uuet muutokset jälkhiin $3 $2",
"rcshowhideminor": "$1 pienet muutokset",
"rcshowhidebots": "$1 ropootit",
- "rcshowhideliu": "\n$1 sisäle lokaattuja käyttäjiä",
+ "rcshowhideliu": "$1 rekisteröityneihtä käyttäjiä",
"rcshowhideanons": "$1 anonyymit käyttäjät",
"rcshowhidepatr": "$1 tarkistetut muutokset",
"rcshowhidemine": "$1 omat muutokset",
"filehist-dimensions": "Timensuunit",
"filehist-comment": "Komentti",
"imagelinks": "Fiilin käyttö",
- "linkstoimage": "Seuraava {{PLURAL:$1|sivu |$1 sivut }} länkkaavat tähhään fiilhiin:",
- "nolinkstoimage": "Ei ole yhtään sivua joka linkkaa tähhään fiilhiin.",
+ "linkstoimage": "{{PLURAL:$1|Seuraava sivu|Seuraavat $1 sivua}} käytthävät tätä fiilhiä:",
+ "nolinkstoimage": "Ei ole yhtään sivua joka käyttää tätä fiilhiä.",
"sharedupload-desc-here": "Tämä fiili on jaettu kohtheesta $1 ja muut prujektit saattavat käyttää sitä.\nTiot [$2 fiilin kuvvaussivulta] näkyvät tässä alla.",
"filedelete": "Ota poies $1",
"filedelete-legend": "Ota poies fiili",
"tooltip-t-permalink": "Ikunen linkki tämän sivun versuunhiin",
"tooltip-ca-nstab-main": "Näytä sisältösivu",
"tooltip-ca-nstab-user": "Näytä käyttäjäsivu",
- "tooltip-ca-nstab-special": "Tämä on spesiaalisivu; sie et saata mookata itteä sivua",
+ "tooltip-ca-nstab-special": "Tämä on spesiaalisivu, sitä ei saata mookata",
"tooltip-ca-nstab-project": "Näytä prujektisivu",
"tooltip-ca-nstab-image": "Näytä fiilisivu",
"tooltip-ca-nstab-template": "Näytä mallia",
"tooltip-undo": "\"Kumota\" palauttaa tämän muutoksen ja aukasee artikkelin mookkausruutun esitarkastuksen kansa. Antaa maholisuuen kirjottaa mutiveerinkin mookkaajan yhteenvethoon",
"tooltip-preferences-save": "Säästä inställninkit",
"tooltip-summary": "Kirjota lyhy yhteenveto",
+ "pageinfo-header-edits": "Mookkaushistuuria",
"pageinfo-hidden-categories": "{{PLURAL:$1|Piilotettu katekuuri|Piilotetut katekuurit}} ($1)",
"pageinfo-category-info": "Katekuuridetaljit",
"previousdiff": "Vanheempi muutos",
"tag-filter": "[[Special:Tags|Merkki]] filtteri:",
"tags-delete": "ota poies",
"restore-count-files": "{{PLURAL:$1|1 fiili|$1 fiilit}}",
+ "logentry-upload-upload": "$1 {{GENDER:$2|ylöslattasi}} $3",
"searchsuggest-search": "Hae {{SITENAME}}",
"mediastatistics-header-total": "Kaikki fiilit",
"mw-widgets-categoryselector-add-category-placeholder": "Lissää katekuuri...",
"backend-fail-contenttype": "Impossible de déterminer le type de contenu du fichier à stocker en « $1 ».",
"backend-fail-batchsize": "On a fourni au support de stockage un lot de $1 {{PLURAL:$1|opération|opérations}} de fichier; la limite est $2 {{PLURAL:$2|opération|opérations}}.",
"backend-fail-usable": "Impossible de lire ou d’écrire le fichier « $1 » en raison de droits insuffisants ou de répertoires/conteneurs manquants.",
+ "backend-fail-stat": "Impossible de lire l’état du fichier « $1 ».",
+ "backend-fail-hash": "Impossible de déterminer le hachage cryptographique du fichier « $1 ».",
"filejournal-fail-dbconnect": "Impossible de se connecter à la base de données du journal pour le terminal de stockage « $1 ».",
"filejournal-fail-dbquery": "Impossible de mettre à jour la base de données du journal pour le terminal de stockage « $1 ».",
"lockmanager-notlocked": "Impossible de déverrouiller « $1 » ; elle n'est pas verrouillée.",
"contributions": "{{GENDER:$1|वापरपी}} योगदानां",
"contributions-title": "$1 खातीर वापरप्याचीं योगदानां",
"mycontris": "योगदान",
+ "anoncontribs": "योगदान",
"uctop": "हालीचें",
"month": "ह्या म्हयन्या सावन (आनी आदलें):",
"year": "ह्या वर्सा सावन (आनी आदलें):",
"whatlinkshere-links": "← दुवे",
"whatlinkshere-hideredirs": "$1 पुनर्निर्देशन",
"whatlinkshere-hidetrans": "$1 दूस्रात-समावेश",
- "whatlinkshere-hidelinks": "$1 à¤\9cà¥\8bडणà¥\8dयà¥\8b",
+ "whatlinkshere-hidelinks": "$1 दà¥\81वà¥\87",
"whatlinkshere-hideimages": "$1 फायल दुवे",
"whatlinkshere-filters": "गाळणे",
"ipboptions": "2 वरां: 2hours ,1 दीस:1 day,3 दीस:3 days,1 सुमान:1 week,2 सुमनां:2 weeks,1 म्हयनो:1 month,3 म्हयने:3 months,6 म्हयने:6 months,1 वर्स:1 year,अनिश्चीत:infinte",
"thumbnail_error": "$1ः लघुप्रतिमा करतांनाची चूक",
"tooltip-pt-userpage": "तुमचें वापरपाचें पान",
"tooltip-pt-mytalk": "तुमचें चर्चेचें पान",
- "tooltip-pt-preferences": "तुमची पसंती",
+ "tooltip-pt-preferences": "{{GENDER:|तुमची}} पसंती",
"tooltip-pt-watchlist": "तुमी बदल करपा खातीर देखरेख करतात त्या पानांची वळेरी",
"tooltip-pt-mycontris": "तुमच्या योगदानांची वळेरी",
"tooltip-pt-login": "सत्रारंभ करप बरें, पूण तशी सक्ती ना.",
"tooltip-summary": "आपरोसाची नोंदणी करात",
"simpleantispam-label": "एन्टी-स्पैम तपासप.\nहे भरी<strong>नकाय</strong>!",
"pageinfo-toolboxlink": "पानाची म्हायती",
+ "pageinfo-contentpage-yes": "हय",
"previousdiff": "← आदलें संपादन",
"nextdiff": "नवें संपादन →",
"file-info-size": "$1 × $2 चित्रतत्व, फायलीचो आकार: $3, माइम प्रकार: $4",
"watchlisttools-view": "प्रस्तूत बदल पळयात.",
"watchlisttools-edit": "सादुरवळेरी पळय आनी संपादीत करात",
"signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|उलयात]])",
+ "redirect-submit": "वचात",
+ "redirect-value": "मोल:",
"specialpages": "विशेश पानां",
"tag-filter": "[[Special:Tags|कुर्वेचीट]] गाळणो:",
"tag-list-wrapper": "[[Special:Tags|{{PLURAL:$1|कुरवेचीट|कुरवेचीटी}}]]: $2",
+ "tags-active-yes": "हय",
"htmlform-title-not-exists": "$1 अस्तित्वांत ना.",
"logentry-delete-delete": "$1 {{GENDER:$2|काडून उडयल्ले पान}} $3",
"logentry-move-move": "$1 हाणें $3 पानाक $4 {{GENDER:$2|हालयला}}",
"logentry-newusers-create": "उपयोगकत्याचें $1 {{GENDER:$2|तयार केलें}}",
"logentry-upload-upload": "$1 {{GENDER:$2|अपलोड केला}} $3",
- "searchsuggest-search": "सोद",
+ "searchsuggest-search": "{{SITENAME}} सोद",
"special-characters-group-latin": "रोमी",
"special-characters-group-latinextended": "रोमी (आनिंक-उइ)",
"special-characters-group-ipa": "IPA",
"yourrealname": "Khorem nanv:",
"prefs-help-email": "Email potto sokticho na, pun tum gupitutor visroxi zalear gupitutor punorsthapon korunk email pottechi goroz podta.",
"prefs-help-email-others": "Tujean dusreank tujea vapurpeacho panar vo bhasabhasache panar aslele eke email duve vorvim tuje xim sompork korunk diunk zata.\nDusre tuje xim sompork kortat tednam tuzo email potto tankam kollchenam.",
+ "userrights-user-editname": "Ek vapurpeachem nanv ghal:",
"group-bot": "Robotam",
"group-sysop": "Karbhari",
"group-all": "(soglle)",
"recentchanges-legend-heading": "<strong>Kunji:</strong>",
"recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} ([[Special:NewPages|nove pananchi suchi]]-ui polloi)",
"rcfilters-tag-remove": "'$1' kadd",
- "rcfilters-activefilters": "Kriaxil gallnneo",
+ "rcfilters-legend-heading": "<strong>Sonkxepachi volleri:</strong>",
+ "rcfilters-activefilters": "Kriaxil challnneo",
"rcfilters-activefilters-hide": "Lipoi",
"rcfilters-activefilters-show": "Dakhoi",
- "rcfilters-advancedfilters": "Sudarit gallnneo",
+ "rcfilters-advancedfilters": "Sudarit challnneo",
+ "rcfilters-limit-title": "Dakhovpache porinnam",
"rcfilters-limit-and-date-label": "$1 {{PLURAL:$1|bodol}}, $2",
"rcfilters-days-title": "Halinche dis",
- "rcfilters-hours-title": "Halinche voram",
+ "rcfilters-hours-title": "Halinchim voram",
"rcfilters-days-show-days": "$1 {{PLURAL:$1|dis}}",
"rcfilters-days-show-hours": "$1 {{PLURAL:$1|vor|voram}}",
- "rcfilters-quickfilters": "Samball’lleleo gallnneo",
- "rcfilters-savedqueries-defaultlabel": "Samball’lleleo gallnneo",
+ "rcfilters-quickfilters": "Samball’lleleo challnneo",
+ "rcfilters-savedqueries-defaultlabel": "Samball’lleleo challnneo",
"rcfilters-savedqueries-rename": "Nanv bodol",
+ "rcfilters-savedqueries-setdefault": "Default koxem bosoi",
+ "rcfilters-savedqueries-unsetdefault": "Default aslolem kaddun uddoi",
"rcfilters-savedqueries-remove": "Kadun udoi",
"rcfilters-savedqueries-new-name-label": "Nanv",
+ "rcfilters-savedqueries-apply-label": "Challnni roch",
"rcfilters-savedqueries-cancel-label": "Rod'd kor",
"rcfilters-show-new-changes": "$1 savn noveo bodol polloi",
- "rcfilters-search-placeholder-mobile": "Gallnneo",
+ "rcfilters-search-placeholder-mobile": "Challnneo",
+ "rcfilters-invalid-filter": "Ovoid challnni",
+ "rcfilters-empty-filter": "Kriaxil challneo nant. Sogllem iogdan dakhoilam.",
+ "rcfilters-filterlist-whatsthis": "Heo koxeo kam kortat?",
+ "rcfilters-filterlist-feedbacklink": "Hea challnnechea avtam vixim tuka kitem dista tem amkam sang",
+ "rcfilters-highlightmenu-title": "Ek rong vinch",
+ "rcfilters-filterlist-noresults": "Kosleoch challnneo mellunk nant",
+ "rcfilters-filtergroup-authorship": "Iogdanachem borovp",
"rcfilters-filter-editsbyself-label": "Tuven kel'leo bodol",
"rcfilters-filter-editsbyself-description": "Tujeo svotacheo yogdanam.",
"rcfilters-filter-editsbyother-label": "Dusreanim kel'le bodol",
+ "rcfilters-filter-editsbyother-description": "Tuje khas bhairavn, soglle bodol",
+ "rcfilters-filtergroup-user-experience-level": "Vaporpeachi nondnni ani onnbhov",
+ "rcfilters-filtergroup-automated": "Apoap zalolem iogdan",
"rcfilters-filter-bots-label": "Robot",
+ "rcfilters-filter-bots-description": "Apoap avtamni kelolem sompadon",
+ "rcfilters-filter-humans-label": "Monxan kelolem (nhoi robotan)",
+ "rcfilters-filter-humans-description": "Monxani kelolem sompadon",
+ "rcfilters-filter-reviewstatus-unpatrolled-description": "Paro kela mhonn khunnavnk naslolem sompadon.",
+ "rcfilters-filtergroup-significance": "Mhotv",
"rcfilters-filter-minor-label": "Dhakte bodol",
+ "rcfilters-filter-minor-description": "Borovpean dhaktem mhonn khunne chitt kelolem sompadon",
+ "rcfilters-filter-major-label": "Dhakte naslolem sompadon",
+ "rcfilters-filter-major-description": "Dhaktem mhonn khunne chitt korunk naslolem sompadon",
+ "rcfilters-filtergroup-watchlist": "Sadurvollerintlim panam",
+ "rcfilters-filter-watchlist-watchednew-description": "Bodol ghoddlea uprant tuvem bhett divnk na tea sadurvollerintlim panache bodol.",
+ "rcfilters-filtergroup-watchlistactivity": "Sadurvollerichem kario",
+ "rcfilters-filter-watchlistactivity-unseen-label": "Pollovnk naslole bodol",
+ "rcfilters-filter-watchlistactivity-unseen-description": "Bodol ghoddlea uprant tuvem bhett divnk na tea panache bodol.",
+ "rcfilters-filter-watchlistactivity-seen-label": "Polloilole bodol",
+ "rcfilters-filter-watchlistactivity-seen-description": "Bodol ghoddlea uprant tuvem bhett dilolea tea panache bodol.",
+ "rcfilters-filtergroup-changetype": "Bodolacho prokar",
"rcfilters-filter-pageedits-label": "Panacheo sompadonam",
"rcfilters-filter-categorization-label": "Vorgache bodol",
+ "rcfilters-filter-categorization-description": "Vorgant savn pana zoddloleachi vo kaddloleachi nond",
+ "rcfilters-filter-logactions-label": "Sotran nond zal’leo kario",
+ "rcfilters-filter-logactions-description": "Karbhari kario, khatem rochop, pana kaddun uddovp, uploads...",
"rcfilters-filtergroup-lastrevision": "Akherchim uzollnnim",
"rcfilters-filter-lastrevision-label": "Sogleanvon novi uzollnni",
+ "rcfilters-filter-lastrevision-description": "Ek panak fokot nimannem bodol",
+ "rcfilters-filter-previousrevision-description": "Soglle bodol je \"halinchi uzollnni\" nant.",
"rcfilters-tag-prefix-namespace-inverted": "$1 <strong>:nhoi</strong>",
+ "rcfilters-view-tags": "Khunnechittichem sompadon",
+ "rcfilters-view-tags-help-icon-tooltip": "Khunnechittichem sompadona babtint odik xikun ghe",
+ "rcfilters-target-page-placeholder": "Ek panache nanv ( vo vorg) ghal",
"rcnotefrom": "Sokoil <strong>$3, $4<strong> savn {{PLURAL:$5|zalelem bodol dilam|zalelem bodol dileant}} (<strong>$1<strong> meren {{PLURAL:$5|dakhoilam|dakhoileant}}).",
"rclistfrom": "$3 $2 savn suru zatelim nove bodol dakhoi",
"rcshowhideminor": "$1 dhakte bodol",
"prefixindex": "Panam jenche nanvache survatek asa...",
"shortpages": "Dhaktim panam",
"longpages": "Lamb panam",
- "protectedpages-filters": "Gallnneo:",
+ "protectedpages-filters": "Challnneo:",
"listusers": "Vaporpeanchi volleri",
"usercreated": "$3 hannem $1 disa $2 vaztam rochlelem",
"newpages": "Novim panam",
"allpagessubmit": "Voch",
"allpages-hide-redirects": "Punornirdexonam lipoi",
"categories": "Vorg",
+ "sp-deletedcontributions-contribs": "iogdan",
"linksearch-ns": "Nanv-tholl:",
"linksearch-ok": "Sod",
"linksearch-line": "$1 $2 savn zoddlelem asa",
"tag-filter": "[[Special:Tags|Kurvechit]] challni:",
"tag-list-wrapper": "[[Special:Tags|{{PLURAL:$1|Kurvechit|Kurvechiti}}]]: $2",
"tags-title": "Kurvechitti",
+ "tags-hitcount-header": "Khunnechittichem bodol",
"tags-active-yes": "Hoi",
"tags-active-no": "Na",
"tags-hitcount": "$1 {{PLURAL:$1|bodol}}",
"rcfilters-filter-editsbyself-label": "Vua modifikuri",
"rcfilters-filter-editsbyself-description": "Vua propra kontributaji",
"rcfilters-filter-editsbyother-label": "Modifikuri da altri",
- "rcfilters-filter-editsbyother-description": "Omna modififuri, ecepte vua propra.",
+ "rcfilters-filter-editsbyother-description": "Omna modifikuri, ecepte vua propra.",
"rcfilters-filtergroup-user-experience-level": "Registro e nivelo di konoco dil uzero",
"rcfilters-filter-user-experience-level-registered-label": "Enrejistrita",
"rcfilters-filter-user-experience-level-registered-description": "Enrejistrita redakteri.",
"rcfilters-filter-watchlist-watched-label": "En mea surveyo-listo",
"rcfilters-filter-watchlist-watched-description": "Modifikuri en pagini de vua surveyo-listo.",
"rcfilters-filter-watchlist-watchednew-label": "Nova modifikuri en la surveyo-listo",
+ "rcfilters-filter-watchlist-watchednew-description": "Chanji en pagini quin vu surveyas, ma quin vu ne vizitis pos ke la modifikuri eventis.",
"rcfilters-filter-watchlist-notwatched-label": "Ne en surveyo-listo",
"rcfilters-filter-watchlist-notwatched-description": "Omni, ecepte modifikuri en la pagini de vua surveyo-listo.",
"rcfilters-filter-watchlistactivity-unseen-description": "Modifikuri en la pagini quin vu ne vizitis pos ke la modifikuri facesis.",
"move-page-legend": "문서 이동",
"movepagetext": "아래 양식을 채워 문서의 이름을 바꾸고 모든 역사를 새 이름으로 된 문서로 이동할 수 있습니다.\n원래의 문서는 새 문서로 넘겨주는 링크로만 남게 되고,\n원래 이름을 가리키는 넘겨주기는 자동으로 갱신됩니다.\n만약 이 설정을 선택하지 않았다면 [[Special:DoubleRedirects|이중 넘겨주기]]와 [[Special:BrokenRedirects|끊긴 넘겨주기]]를 확인해주세요.\n당신은 링크와 가리키는 대상이 서로 일치하도록 해야 할 책임이 있습니다.\n\n만약 이미 있는 문서의 이름을 새 이름으로 입력했을 때는 그 문서가 넘겨주기 문서이고 문서 역사가 없어야만 이동이 됩니다. 그렇지 않을 경우에는 이동되지 <strong>않습니다</strong>.\n이것은 실수로 이동한 문서를 되돌릴 수는 있지만, 이미 존재하는 문서 위에 덮어씌울 수는 없다는 것을 의미합니다.\n\n<strong>주의!</strong>\n자주 사용하는 문서를 이동하면 해결하기 어려운 문제를 일으킬 수도 있습니다.\n이동하기 전에 반드시 이 문서를 이동해도 문제가 없는지 확인해주세요.",
"movepagetext-noredirectfixer": "아래 양식을 채워 문서의 이름을 바꾸고 모든 역사를 새 이름으로 된 문서로 이동할 수 있습니다.\n원래의 문서는 새 문서로 넘겨주는 링크로만 남게 됩니다.\n[[Special:DoubleRedirects|이중 넘겨주기]]와 [[Special:BrokenRedirects|끊긴 넘겨주기]]를 확인해주세요.\n당신은 링크와 가리키는 대상이 서로 일치하도록 해야 할 책임이 있습니다.\n\n만약 이미 있는 문서의 이름을 새 이름으로 입력했을 때는 그 문서가 넘겨주기 문서이고 문서 역사가 없어야만 이동이 됩니다. 그렇지 않을 경우에는 이동되지 <strong>않습니다</strong>.\n이것은 실수로 이동한 문서를 되돌릴 수는 있지만, 이미 존재하는 문서 위에 덮어씌울 수는 없다는 것을 의미합니다.\n\n<strong>주의!</strong>\n자주 사용하는 문서를 이동하면 해결하기 어려운 문제를 일으킬 수도 있습니다.\n이동하기 전에 반드시 이 문서를 이동해도 문제가 없는지 확인해주세요.",
+ "movepagetext-noredirectsupport": "아래 양식을 채워 문서의 이름을 바꾸고 모든 역사를 새 이름으로 된 문서로 이동할 수 있습니다.\n당신은 링크와 가리키는 대상이 서로 일치하도록 해야 할 책임이 있습니다.\n\n만약 이미 있는 문서의 제목을 새 제목으로 입력했을 때는 그 문서가 이동되지 <strong>않습니다</strong>.\n이것은 실수로 이동한 문서를 되돌릴 수는 있지만, 이미 존재하는 문서 위에 덮어씌울 수는 없다는 것을 의미합니다.\n\n<strong>주의:</strong>\n자주 사용하는 문서를 이동하면 해결하기 어려운 문제를 일으킬 수도 있습니다.\n이동하기 전에 반드시 이 문서를 이동해도 문제가 없는지 확인해주세요.",
"movepagetalktext": "이 칸에 체크하면, 딸린 토론 문서가 자동으로 이동됩니다. 다만 비어있지 않은 토론 문서가 있다면 이동되지 않습니다.\n\n이러한 경우에는 수동으로 이동하거나 합쳐야 합니다.",
"moveuserpage-warning": "<strong>경고:</strong> 사용자 문서를 이동하려고 하고 있습니다. 사용자 문서만 이동되며 사용자 이름이 바뀌지 <strong>않는다</strong>는 점을 참고하세요.",
"movecategorypage-warning": "<strong>경고:</strong> 분류 문서를 이동하려고 합니다. 해당 문서만 이동되고 옛 분류에 있는 문서는 새 분류 안에 다시 분류되지 <em>않음</em>을 참고하세요.",
"blockedtext": "'''Tavs lietotāja vārds vai IP adrese ir nobloķēta.'''\n\n$1 nobloķēja tavu lietotāja vārdu vai IP adresi.\nBloķējot norādītais iemesls bija: ''$2''.\n\n*Bloka sākums: $8\n*Bloka beigas: $6\n*Bija domāts nobloķēt: $7\n\nTu vari sazināties ar $1 vai kādu citu [[{{MediaWiki:Grouppage-sysop}}|administratoru]] lai apspriestu šo bloku.\n\nPievērs uzmanību, tam, ka ja tu neesi norādījis derīgu e-pasta adresi ''[[Special:Preferences|savās izvēlēs]]'', tev nedarbosies \"sūtīt e-pastu\" iespēja.\n\nTava IP adrese ir $3 un bloka identifikators ir #$5. Lūdzu iekļauj vienu no tiem, vai abus, visos turpmākajos pieprasījumos.",
"autoblockedtext": "Tava IP adrese ir tikusi automātiski nobloķēta, tāpēc, ka to (nupat kā) ir lietojis cits dalībnieks, kuru nobloķēja $1.\nNorādītais bloķēšanas iemesls bija:\n\n:''$2''\n\n* Bloka sākums: $8\n* Bloka beigas: $6\n* Bija domāts nobloķēt: $7\n\nTu vari sazināties ar $1 vai kādu citu [[{{MediaWiki:Grouppage-sysop}}|adminu]] lai apspriestu šo bloku.\n\nAtceries, ka tu nevari lietot \"sūtīt e-pastu šim dalībniekam\" iespēju, ja tu neesi norādījis derīgu e-pasta adresi savās [[Special:Preferences|dalībnieka izvelēs]] un bloķējot tev nav aizbloķēta iespēja sūtīt e-pastu.\n\nTava pašreizējā IP adrese ir $3 un bloka ID ir $5.\nLūdzu iekļauj šos visos ziņojumos, kurus sūti adminiem, apspriežot šo bloku.",
"blockednoreason": "iemesls nav norādīts",
+ "blockedtext-composite-no-ids": "Tava IP adrese ir iekļauta vairākos melnajos sarakstos",
"whitelistedittext": "Lūdzu $1, lai varētu labot lapas.",
"confirmedittext": "Lai varētu izmainīt lapas, vispirms jāapstiprina savu e-pasta adresi.\nNorādi un apstiprini e-pasta adresi savos [[Special:Preferences|lietotāja uzstādījumos]].",
"nosuchsectiontitle": "Nevaru atrast sadaļu",
"uploadstash-bad-path-unknown-type": "Nezināms tips \"$1\".",
"uploadstash-bad-path-unrecognized-thumb-name": "Neatpazīts sīktēla nosaukums.",
"uploadstash-file-not-found-no-thumb": "Nevarēja iegūt sīkbildi.",
+ "uploadstash-no-extension": "Paplašinājums ir tukšs.",
"uploadstash-zero-length": "Faila garums ir nulle.",
"img-auth-accessdenied": "Pieeja liegta",
"img-auth-nopathinfo": "Trūkst PATH_INFO.\nJūsu serveris nav konfigurēts nodot šo informāciju.\nTas var būt bāzēts uz CGI un neatbalstīt img_auth.\nSkatīt https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization.",
"nocreate-loggedin": "Du har ikke tillatelse til å opprette sider.",
"sectioneditnotsupported-title": "Seksjonsredigering støttes ikke",
"sectioneditnotsupported-text": "Seksjonsredigering støttes ikke på denne siden.",
+ "modeleditnotsupported-title": "Redigering støttes ikke",
+ "modeleditnotsupported-text": "Redigering støttes ikke for innholdsmodellen $1.",
"permissionserrors": "Rettighetsfeil",
"permissionserrorstext": "Du har ikke tillatelse til å utføre dette, av følgende {{PLURAL:$1|grunn|grunner}}:",
"permissionserrorstext-withaction": "Du har ikke tillatelse til å $2 {{PLURAL:$1|fordi|av følgende grunner}}:",
"content-model-css": "CSS",
"content-json-empty-object": "Tomt objekt",
"content-json-empty-array": "Tom matrise",
+ "unsupported-content-model": "<strong>Advarsel:</strong> Innholdsmodellen $1 støttes ikke på denne wikien.",
+ "unsupported-content-diff": "Differ støttes ikke for innholdsmodellen $1.",
+ "unsupported-content-diff2": "Differ mellom innholdsmodellene $1 og $2 støttes ikke på denne wikien.",
"deprecated-self-close-category": "Sider som bruker ugyldige, balanserte HTML-tagger.",
"deprecated-self-close-category-desc": "Siden inneholder balanserte HTML-tagger slike som <code><b/></code> or <code><span/></code>. Oppførselen til disse vil snart endret for å være i samsvar med HTML-spesifikasjonen, og da vil de ikke kunne brukes som wikitekst.",
"duplicate-args-warning": "<strong>Advarsel:</strong> [[:$1]] kaller [[:$2]] med flere enn en verdi for \"$3\"-parameteren. Bare den sist angitte verdien vil brukes.",
"rcfilters-filter-showlinkedto-label": "Vis endringer på sider som lenker til",
"rcfilters-filter-showlinkedto-option-label": "<strong>Sider som lenker til</strong> den valgte siden",
"rcfilters-target-page-placeholder": "Skriv inn et sidenavn (eller en kategori)",
+ "rcfilters-allcontents-label": "Alt innhold",
+ "rcfilters-alldiscussions-label": "Alle diskusjoner",
"rcnotefrom": "Nedenfor er vist {{PLURAL:$5|endringen|endringene}} som er gjort siden <strong>$3, $4</strong> (frem til <strong>$1</strong>).",
"rclistfromreset": "Nullstill datovalg",
"rclistfrom": "Vis nye endringer fra og med $3 $2",
"backend-fail-contenttype": "Kunne ikke avgjøre innholdstypen til filen som skal lagres på «$1».",
"backend-fail-batchsize": "Bakgrunnsprosesseringen belastet med {{PLURAL:$1|en filoperasjon|en samling av $1 filoperasjoner}}; grensen er $2.",
"backend-fail-usable": "Kunne ikke lese eller skrive fila «$1» på grunn av utilstrekkelige tillatelser eller manglende mapper/beholdere.",
+ "backend-fail-stat": "Kunne ikke lese statusen til fila «$1».",
+ "backend-fail-hash": "Kunne ikke bestemme den kryptografiske signaturen til fila «$1».",
"filejournal-fail-dbconnect": "Kunne ikke koble til journaldatabasen for lagringssystemet «$1».",
"filejournal-fail-dbquery": "Kunne ikke oppdatere journaldatabasen for lagringssystemet «$1».",
"lockmanager-notlocked": "Kunne ikke låse opp «$1» fordi den er ikke låst.",
"sessionfailure": "Det ser ut til å være et problem med innloggingen din, og handlingen ble avbrutt av sikkerhetshensyn. Vennlgist prøv å sende skjemaet en gang til.",
"changecontentmodel": "Endre innholdsmodell for en side",
"changecontentmodel-legend": "Endre innholdsmodell",
- "changecontentmodel-title-label": "Sidetittel",
+ "changecontentmodel-title-label": "Sidetittel:",
"changecontentmodel-current-label": "Nåværende innholdsmodell:",
- "changecontentmodel-model-label": "Ny innholdsmodell",
+ "changecontentmodel-model-label": "Ny innholdsmodell:",
"changecontentmodel-reason-label": "Begrunnelse:",
"changecontentmodel-submit": "Endre",
"changecontentmodel-success-title": "Innholdsmodellen ble endret",
"move-subpages": "Flytt alle undersider (opp til $1)",
"move-talk-subpages": "Flytt alle undersider av diskusjonssiden (opp til $1)",
"movepage-page-exists": "Siden $1 finnes allerede og kan ikke overskrives automatisk.",
+ "movepage-source-doesnt-exist": "Sida $1 eksisterer ikke, og kan derfor ikke flyttes.",
"movepage-page-moved": "Siden $1 har blitt flyttet til $2.",
"movepage-page-unmoved": "Siden $1 kunne ikke flyttes til $2.",
"movepage-max-pages": "Grensen på {{PLURAL:$1|én side|$1 sider}} er nådd; ingen flere sider vil bli flyttet automatisk.",
"delete_and_move_reason": "Slettet for å muliggjøre flytting fra \"[[$1]]\"",
"selfmove": "Tittelen er den samme; kan ikke flytte en side til seg selv.",
"immobile-source-namespace": "Kan ikke flytte sider i navnerommet «$1»",
+ "immobile-source-namespace-iw": "Sider på andre wikier kan ikke flyttes fra denne wikien.",
"immobile-target-namespace": "Kan ikke flytte sider til navnerommet «$1»",
"immobile-target-namespace-iw": "Du kan ikke flytte en side til et navn som er en interwikilenke.",
"immobile-source-page": "Denne siden kan ikke flyttes.",
"immobile-target-page": "Kan ikke flytte til det navnet.",
+ "movepage-invalid-target-title": "Det forespurte navnet er ugyldig.",
"bad-target-model": "Det ønskede målet bruker en annen innholdsmodell. Kan ikke konvertere fra $1 til $2.",
"imagenocrossnamespace": "Kan ikke flytte filer til andre navnerom enn filnavnerommet",
"nonfile-cannot-move-to-file": "Kan ikke flytte ikke-filer til filnavnerom",
"category-empty": "''In disse kategoria staon op t moment nog gien artikels of media.''",
"hidden-categories": "Verbörgen {{PLURAL:$1|kategory|kategoryen}}",
"hidden-category-category": "Verbörgen kategorieën",
- "category-subcat-count": "{{PLURAL:$2|Disse kategorie hef de volgende subkategorie.|Disse kategorie hef de volgende {{PLURAL:$1|subkategorie|$1 subkategorieën}}, van in totaal $2.}}",
+ "category-subcat-count": "{{PLURAL:$2|Disse kategory hevt de volgende subkategory.|Disse kategory hevt de volgende {{PLURAL:$1|subkategory|$1 subkategoryen}}, van in totaal $2.}}",
"category-subcat-count-limited": "Disse kategorie hef de volgende {{PLURAL:$1|subkategorie|$1 subkategorieën}}.",
- "category-article-count": "{{PLURAL:$2|In disse kategorie steet allinnig de volgende zied.|De volgende {{PLURAL:$1|zied steet|$1 ziejen staon}} in disse kategorie, van in totaal $2.}}",
+ "category-article-count": "{{PLURAL:$2|In disse kategory steyt allinnig de volgende syde.|De volgende {{PLURAL:$1|syde steyt|$1 syden stån}} in disse kategory, van in totaal $2.}}",
"category-article-count-limited": "In disse kategorie {{PLURAL:$1|steet de volgende zied|staon de volgende $1 ziejen}}.",
"category-file-count": "In disse kategorie {{PLURAL:$2|steet t volgende bestaand|staon de volgende $1 bestaanden, van in totaal $2}}.",
"category-file-count-limited": "In disse kategorie {{PLURAL:$1|steet t volgende bestaand|staon de volgende $1 bestaanden}}.",
"searchbutton": "Söken",
"go": "Artikel",
"searcharticle": "Artikel",
- "history": "Geschiedenisse",
+ "history": "Geskydenisse",
"history_short": "Geskydenisse",
"updatedmarker": "bie-ewörken sinds mien leste bezeuk",
"printableversion": "Afdrükbåre versy",
"subject": "Onderwarp:",
"minoredit": "kleine wysiging",
"watchthis": "volg disse syde",
- "savearticle": "Zied opslaon",
+ "savearticle": "Syde upslån",
"savechanges": "Wysigingen upslån",
"publishpage": "Zied uutbrengen",
"publishchanges": "Wysigingen üütbrengen",
"nosuchsectiontitle": "Disse seksie besteet niet",
"nosuchsectiontext": "Je proberen n seksie te bewarken dat niet besteet.\nt Kan ween dat t herneumd is of dat t vortedaon is to jie t an t bekieken waren.",
"loginreqtitle": "Anmelden verplicht",
- "loginreqlink": "Anmelden",
+ "loginreqlink": "anmelden",
"loginreqpagetext": "Je mutten $1 um disse zied te bekieken.",
"accmailtitle": "Wachtwoord is verstuurd.",
"accmailtext": "Der is n willekeurig wachtwoord veur [[User talk:$1|$1]] verstuurd naor $2. t Kan ewiezigd wörden op de zied ''[[Special:ChangePassword|wachtwoord wiezigen]]'' naoda'j an-emeld bin.",
"newarticle": "(Niej)",
"newarticletext": "Disse zied besteet nog niet.\nIn t veld hieronder ku'j wat schrieven um disse zied an te maken (meer informasie vie'j op de [$1 hulpzied]).\nA'j hier per ongelok terechtekeumen bin gebruuk dan de knoppe '''veurige''' um weerumme te gaon.",
"anontalkpagetext": "---- ''Disse overlegzied heurt bie n anonieme gebruker die nog gien gebrukersnaam hef, of t niet gebruukt. We gebruken daorumme t IP-adres um hum of heur te herkennen, mer t kan oek ween dat meerdere personen t zelfde IP-adres gebruken, en da'j hiermee berichten ontvangen die niet veur joe bedoeld bin. A'j dit veurkoemen willen, dan ku'j t best [[Special:CreateAccount|n gebrukersnaam anmaken]] of [[Special:UserLogin|anmelden]].''",
- "noarticletext": "Der steet noen gien tekste op disse zied.\nJe kunnen [[Special:Search/{{PAGENAME}}|de titel opzeuken]] in aandere ziejen,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} zeuken in de logboeken],\nof [{{fullurl:{{FULLPAGENAME}}|action=edit}} disse zied anmaken]</span>.",
+ "noarticletext": "Der steyt nun geen tekst up disse syde.\nJy künnet [[Special:Search/{{PAGENAME}}|de titel upsöken]] in andere syden,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} söken in de logboken],\nof [{{fullurl:{{FULLPAGENAME}}|action=edit}} disse syde anmaken]</span>.",
"noarticletext-nopermission": "Op disse zied steet gien tekste.\nJe kunnen [[Special:Search/{{PAGENAME}}|zeuken naor disse term]] in aandere ziejen of\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} de logboeken deurzeuken]</span>, mer je hebben gien rechten um disse zied an te maken.",
"missing-revision": "De versie #$1 van de zied \"{{FULLPAGENAME}} besteet niet.\n\nDit kömp meestentieds deur t volgen van n verouwerde verwiezing naor n zied die vortedaon is.\nWaorschienlik ku'j der meer gegevens over vienen in t [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} vortdologboek].",
"userpage-userdoesnotexist": "Je bewarken n gebrukerszied van n gebruker die niet besteet (gebruker \"<nowiki>$1</nowiki>\"). Kiek effen nao o'j disse zied wel anmaken/bewarken willen.",
"previousrevision": "← eyrere versy",
"nextrevision": "niejere versie →",
"currentrevisionlink": "versie zo as t noen is",
- "cur": "noen",
+ "cur": "aktueel",
"next": "Volgende",
- "last": "leste",
+ "last": "lätste",
"page_first": "eerste",
"page_last": "leste",
"histlegend": "Verklaoring aofkortingen: (noen) = verschil mit de op-esleugen versie, (veurige) = verschil mit de veurige versie, K = kleine wieziging",
"titlematches": "Overeenkomst mit t onderwarp",
"textmatches": "Overeenkomst mit teksten",
"notextmatches": "Gien overeenstemming",
- "prevn": "veurige {{PLURAL:$1|$1}}",
+ "prevn": "vöärige {{PLURAL:$1|$1}}",
"nextn": "volgende {{PLURAL:$1|$1}}",
"prevn-title": "{{PLURAL:$1|Veurig resultaot|Veurige $1 resultaoten}}",
"nextn-title": "{{PLURAL:$1|Volgend resultaot|Volgende $1 resultaoten}}",
"searchprofile-articles": "Artikels",
"searchprofile-images": "Multimedia",
"searchprofile-everything": "Alles",
- "searchprofile-advanced": "Uutwyded",
+ "searchprofile-advanced": "Uutbreided",
"searchprofile-articles-tooltip": "Söken in $1",
"searchprofile-images-tooltip": "Söken nå bestanden",
"searchprofile-everything-tooltip": "Alle inhold döärsöken (ouk oaverlegsyden)",
"enhancedrc-since-last-visit": "$1 {{PLURAL:$1|sinds joew leste bezeuk}}",
"enhancedrc-history": "geschiedenisse",
"recentchanges": "Lätste wysigingen",
- "recentchanges-legend": "Opsies veur leste wiezigingen",
+ "recentchanges-legend": "Optys vöär lätste wysigingen",
"recentchanges-summary": "Up disse syde kün jy de lätste wysigingen van disse wiki bekyken.",
"recentchanges-noresult": "Der waren in disse periode gien wiezigingen die an de kriteria voldoon.",
"recentchanges-feed-description": "Zeuk naor de alderleste wiezingen op disse wiki in disse voer.",
"recentchanges-label-minor": "Dit is een kleine wysiging",
"recentchanges-label-bot": "Disse bewarking is uutvoord döär een bot",
"recentchanges-label-unpatrolled": "Disse bewarking is noch neet nåkeaken",
- "recentchanges-label-plusminus": "Disse sydgroutte is mid dit antal bytes wysigd",
+ "recentchanges-label-plusminus": "Disse sydgroutde is mid dit antal bytes wysigd",
"recentchanges-legend-heading": "<strong>Legenda:</strong>",
"recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}}<br />(see ouk de [[Special:NewPages|lyste mid nye syden]])",
"recentchanges-submit": "Bekiek",
"rcfilters-liveupdates-button": "Rechtstreakse aktualisering",
"rcfilters-liveupdates-button-title-off": "Nye wysigingen voorddalik låten seen",
"rcnotefrom": "Wysigingen sinds <strong>$3, $4</strong> (maximaal <strong>$1</strong> {{PLURAL:$1|wysiging|wysigingen}}).",
- "rclistfrom": "Bekiek wiezigingen vanaof $3 $2",
- "rcshowhideminor": "$1 kleine wiezigingen",
+ "rclistfrom": "Bekyk wysigingen vanaf $3 $2",
+ "rcshowhideminor": "$1 kleine wysigingen",
"rcshowhideminor-show": "Bekiek",
"rcshowhideminor-hide": "Verbarg",
- "rcshowhidebots": "$1 botgebrukers",
+ "rcshowhidebots": "$1 botbrukers",
"rcshowhidebots-show": "Bekiek",
"rcshowhidebots-hide": "Verbarg",
- "rcshowhideliu": "$1 eregistreerden gebrukers",
+ "rcshowhideliu": "$1 registreerde brukers",
"rcshowhideliu-show": "Bekiek",
"rcshowhideliu-hide": "Verbarg",
- "rcshowhideanons": "$1 anonieme gebrukers",
+ "rcshowhideanons": "$1 anonime brukers",
"rcshowhideanons-show": "Bekiek",
"rcshowhideanons-hide": "Verbarg",
"rcshowhidepatr": "$1 nao-ekeken bewarkingen",
"rcshowhidepatr-show": "Bekiek",
"rcshowhidepatr-hide": "Verbarg",
- "rcshowhidemine": "$1 mien bewarkingen",
+ "rcshowhidemine": "$1 myn bewarkingen",
"rcshowhidemine-show": "Bekiek",
"rcshowhidemine-hide": "Verbarg",
"rcshowhidecategorization": "$1 kategorisering van ziejen",
"rcshowhidecategorization-show": "Bekiek",
"rcshowhidecategorization-hide": "Verbarg",
- "rclinks": "Bekiek de leste $1 wiezigingen van de aofgeleupen $2 dagen",
+ "rclinks": "Bekyk de lätste $1 wysigingen van de vöärbye $2 dagen",
"diff": "verskil",
"hist": "geskydenisse",
"hide": "verbarg",
"filehist-filesize": "Bestaandsgrootte",
"filehist-comment": "Kommentaar",
"imagelinks": "Bestandsbruuk",
- "linkstoimage": "Disse holder wördt up de volgende {{PLURAL:$1|syde|$1 syden}} gebrüked:",
+ "linkstoimage": "Dit bestand wördt up de volgende {{PLURAL:$1|syde|$1 syden}} bruked:",
"linkstoimage-more": "Der {{PLURAL:$2|is|bin}} meer as $1 {{PLURAL:$1|verwiezing|verwiezingen}} naor dit bestaand.\nDe volgende lieste gif allinnig de eerste {{PLURAL:$1|verwiezing|$1 verwiezingen}} naor dit bestaand weer.\nDe [[Special:WhatLinksHere/$2|hele lieste]] is oek beschikbaor.",
"nolinkstoimage": "Geen enkelde syde gebrüükt disse holder.",
"morelinkstoimage": "[[Special:WhatLinksHere/$1|Meer verwiezingen]] naor dit bestaand bekieken.",
"undelete-show-file-submit": "Ja",
"namespace": "Naamruumde:",
"invert": "Selekty ümmekeyren",
- "tooltip-invert": "Vink dit vakjen an um wiezigingen an ziejen binnen de ekeuzen naamruumte te verbargen (en de biebeheurende naamruumte as dat an-evinkt is)",
- "namespace_association": "Naamruumte die hieran ekoppeld is",
- "tooltip-namespace_association": "Vink dit vakjen an um oek de overlegnaamruumte, of in t ummekeren geval de naamruumte zelf, derbie te doon die bie disse naamruumte heurt.",
+ "tooltip-invert": "Vink dit vakjen an um wysigingen an syden binnen de eköäsen naamruumde te verbargen (en de bybehöyrende naamruumde as dat anvinked is)",
+ "namespace_association": "Bybehöyrende naamruumde",
+ "tooltip-namespace_association": "Vink dit vakjen an um ouk de oaverleg- en underwarpnaamruumde in te slüten dee by de selekteerde naamruumde höyret.",
"blanknamespace": "(Höyvdnaamruumde)",
"contributions": "{{GENDER:$1|Gebrukersbydragen}}",
"contributions-title": "Biedragen van $1",
"tooltip-pt-createaccount": "Skryv juw eigen vöäral in en meld juw eigen an. Dit is lykewels neet verplicht.",
"tooltip-ca-talk": "Låt een oaverlegtekst oaver disse syde seen",
"tooltip-ca-edit": "Bewark disse syde",
- "tooltip-ca-addsection": "Niej oonderwaerp tovogen",
+ "tooltip-ca-addsection": "Ny underwarp tovogen",
"tooltip-ca-viewsource": "Disse ziede is beveiligd taegen veraanderen. Iej könt wal kieken noar de ziede",
"tooltip-ca-history": "Oldere versys van disse syde",
"tooltip-ca-protect": "Beveilig disse ziede taegen veraanderen",
"tooltip-ca-nstab-mediawiki": "Loat de systeemtekstbladziede zeen",
"tooltip-ca-nstab-template": "Loat de malbladziede zeen",
"tooltip-ca-nstab-help": "Loat de hölpbladziede zeen",
- "tooltip-ca-nstab-category": "Loat de rubriekbladziede zeen",
+ "tooltip-ca-nstab-category": "Låt de kategorysyde seen",
"tooltip-minoredit": "Markeer as n klaene wieziging",
"tooltip-save": "Wiezigingen opsloan",
"tooltip-preview": "Bekiek oew versie vuurda'j t opsloan (anbeveulen)!",
"thumbsize": "Grootte van de miniatuuraofbeelding:",
"widthheightpage": "$1 × $2, $3 {{PLURAL:$3|zied|ziejen}}",
"file-info": "Bestaandsgrootte: $1, MIME-type: $2",
- "file-info-size": "$1 × $2 beeldpunten, bestaandsgrootte: $3, MIME-type: $4",
+ "file-info-size": "$1 × $2 bealdpunten, bestandsgroutde: $3, MIME-type: $4",
"file-info-size-pages": "$1 × $2 beeldpunten, bestaandsgrootte: $3, MIME-type: $4, $5 {{PLURAL:$5|zied|ziejen}}",
"file-nohires": "Gien hogere resolusie beschikbaor.",
"svg-long-desc": "SVG-bestaand, uutgangsgrootte $1 × $2 beeldpunten, bestaandsgrootte: $3",
"svg-long-desc-animated": "Bewegend SVG-bestaand, uutgangsgrootte $1 × $2 beeldpunten, bestaandsgrootte: $3",
"svg-long-error": "Ongeldig SVG-bestaand: $1",
- "show-big-image": "Oorspronkelik bestaand",
- "show-big-image-preview": "Grootte van disse weergave: $1.",
- "show-big-image-other": "Aandere {{PLURAL:$2|resolusie|resolusies}}: $1.",
+ "show-big-image": "Oorsprungelik bestand",
+ "show-big-image-preview": "Groutde van disse weadergåve: $1.",
+ "show-big-image-other": "Andere {{PLURAL:$2|resoluty|resolutys}}: $1.",
"show-big-image-size": "$1 × $2 bealdpunten",
"file-info-gif-looped": "herhaolend",
"file-info-gif-frames": "$1 {{PLURAL:$1|beeld|beelden}}",
"nocreate-loggedin": "U hebt geen rechten om nieuwe pagina's te maken.",
"sectioneditnotsupported-title": "Het is niet mogelijk om paragrafen te bewerken",
"sectioneditnotsupported-text": "Het is op deze pagina niet mogelijk om paragrafen te bewerken.",
+ "modeleditnotsupported-title": "Bewerken wordt niet ondersteunt",
+ "modeleditnotsupported-text": "Het inhoudsmodel $1 kan niet worden bewerkt.",
"permissionserrors": "Fouten in rechten",
"permissionserrorstext": "U hebt geen rechten om dit te doen om de volgende {{PLURAL:$1|reden|redenen}}:",
"permissionserrorstext-withaction": "U hebt geen toestemming om $2, {{PLURAL:$1|want}}:",
"content-model-css": "CSS",
"content-json-empty-object": "Leeg object",
"content-json-empty-array": "Lege reeks",
+ "unsupported-content-model": "<strong>Let op:</strong> het inhoudsmodel $1 wordt niet ondersteunt op deze wiki.",
+ "unsupported-content-diff": "Wijzigingen worden niet ondersteund door het inhoudsmodel $1.",
+ "unsupported-content-diff2": "Wijzigingen tussen de inhoudsmodellen $1 en $2 worden niet ondersteund op deze wiki.",
"deprecated-self-close-category": "Pagina's met ongeldige zelfsluitende HTML-tags",
"deprecated-self-close-category-desc": "De pagina bevat ongeldige zelf-afgesloten HTML-tags, zoals <code><b/></code> of <code><span/></code>. Het gedrag van deze tags zal binnenkort veranderd worden zodat dit overeenkomt met de HTML5-specificatie, dus het gebruik hiervan is verouderd en wordt afgeraden.",
"duplicate-args-warning": "<strong>Waarschuwing:</strong> [[:$1]] roept [[:$2]] aan met meer dan één waarde voor de parameter \"$3\". Alleen de laatste waarde wordt gebruikt.",
"backend-fail-contenttype": "Het inhoudstype van het bestand om in de opslag \"$1\" op te slaan kon niet bepaald worden.",
"backend-fail-batchsize": "Taak met $1 {{PLURAL:$1|bestandshandeling|bestandshandelingen}} in het opslagbackend; de limiet is $2 {{PLURAL:$2|handeling|handelingen}}.",
"backend-fail-usable": "Het was niet mogelijk naar het bestand $1 te schrijven of eruit te lezen vanwege onvoldoende rechten of niet-aanwezige mappen of containers.",
+ "backend-fail-stat": "Kan de bestandsstatus van \"$1\" niet lezen.",
+ "backend-fail-hash": "Kan de cryptografische hash van het bestand \"$1\" niet bepalen.",
"filejournal-fail-dbconnect": "Het was niet mogelijk een verbinding te maken met de journaldatabase voor het opslagbackend \"$1\".",
"filejournal-fail-dbquery": "Het was niet mogelijk de journaldatabase bij te werken voor het opslagbackend \"$1\".",
"lockmanager-notlocked": "Het was niet mogelijk \"$1\" vrij te geven; dit object is niet vergrendeld.",
"sessionfailure": "Er lijkt een probleem te zijn met uw aanmeldsessie.\nUw handeling is gestopt uit voorzorg tegen een beveiligingsrisico (dat bestaat uit mogelijke \"hijacking\" van deze sessie).\nProbeer het formulier opnieuw te versturen.",
"changecontentmodel": "Inhoudsmodel van pagina bewerken",
"changecontentmodel-legend": "Inhoudsmodel wijzigen",
- "changecontentmodel-title-label": "Paginanaam",
+ "changecontentmodel-title-label": "Paginanaam:",
"changecontentmodel-current-label": "Huidige inhoudsmodel:",
- "changecontentmodel-model-label": "Nieuw inhoudsmodel",
+ "changecontentmodel-model-label": "Nieuw inhoudsmodel:",
"changecontentmodel-reason-label": "Reden:",
"changecontentmodel-submit": "Wijzigen",
"changecontentmodel-success-title": "Het inhoudsmodel is gewijzigd",
"nospecialpagetext": "<strong>ߊߟߎ߫ ߓߘߊ߫ ߞߐߜߍ߫ ߓߟߏߡߊߞߊ߬ߣߍ߲ ߘߏ߫ ߢߌߣߌ߲߫ ߡߍ߲ ߕߺߴߦߋ߲߬.</strong>\nߞߐߜߍ߫ ߓߟߏߡߊߞߊ߬ߣߍ߲߫ ߓߘߍ߬ߡߊ ߟߎ߬ ߛߙߍߘߍ ߦߋ߫ ߢߌ߲߬ ߠߋ߫ ߞߊ߲߬ [[Special:SpecialPages|{{int:specialpages}}]].",
"error": "ߝߎ߬ߕߎ߲߬ߕߌ",
"databaseerror": "ߓߟߏߡߟߊ ߟߎ߬ ߝߊ߲ ߝߎ߬ߕߎ߲߬ߕߌ",
+ "databaseerror-textcl": "ߓߟߏߡߟߊߝߊ߲ ߢߌ߬ߣߌ߲߬ߞߊ߬ߟߌ ߝߎ߬ߕߎ߲߬ߕߌ ߘߏ߫ ߓߘߊ߫ ߓߌ߬ߟߵߊ߬ ߘߐ߫.",
"databaseerror-query": "ߢߌ߬ߣߌ߲߬ߞߊ߬ߟߌ $1",
"databaseerror-function": "ߗߋߘߊ $1",
"databaseerror-error": "ߝߎ߬ߕߎ߲߬ߕߌ: $1",
"uploaddisabledtext": "ߞߐߕߐ߮ ߟߊߦߟߍ߬ߟߌ ߓߐ߫ ߣߴߊ߬ ߟߊ߫.",
"php-uploaddisabledtext": "ߞߐߕߐ߮ ߟߊߦߟߍ߬ߟߌ ߓߐ߫ ߣߴߊ߬ ߟߊ߫ PHP ߘߐ߫.\nߞߐߕߐ߮ ߟߊߦߟߍ߬ߟߌ ߟߊ߬ߓߍ߲߬ߢߐ߲߰ߡߊ ߝߛߍ߬ߝߛߍ߫ ߖߊ߰ߣߌ߲߬.",
"upload-scripted-pi-callback": "ߊ߬ ߕߍߣߊ߬ ߛߐ߲߬ ߠߊ߫ ߞߐߕߐ߯ ߟߊߦߟߍ߬ ߟߊ߫ XML-stylesheet ߟߐ߲ߣߌ߲ߦߊ ߘߊ߲ߘߊߟߌ.",
+ "uploadvirus": "ߓߊ߰ߙߋ߲ ߦߋ߫ ߞߐߕߐ߮ ߣߌ߲߬ ߠߊ߫߹\nߝߊߙߊ߲ߝߊ߯ߛߟߌ:$1",
"upload-source": "ߞߐߕߐ߮ ߛߎ߲",
"sourcefilename": "ߞߐߕߐ߮ ߕߐ߮ ߛߎ߲:",
"sourceurl": "URL ߛߎ߲:",
"backend-fail-batchsize": "Wewnętrzne funkcje magazynowania otrzymały $1 {{PLURAL:$1|operację|operacje|operacji}} na pliku; limit to $2 {{PLURAL:$2|operacja|operacje|operacji}}.",
"backend-fail-usable": "Nie można zapisać lub czytać z pliku \"$1\" ze względu na niewystarczające uprawnienia lub brak katalogów/kontenerów.",
"backend-fail-stat": "Nie udało się odczytać statusu pliku „$1”.",
+ "backend-fail-hash": "Nie udało się zdefiniować hasha kryptograficznego pliku „$1”.",
"filejournal-fail-dbconnect": "Nie można połączyć się z bazą danych dziennika dla backendu magazynowania \"$1\".",
"filejournal-fail-dbquery": "Nie można zaktualizować bazy danych dziennika dla backendu magazynowania\"$1\".",
"lockmanager-notlocked": "Nie można odblokować \"$1\", ponieważ nie jest on zablokowany.",
"history": "Version pì veje",
"history_short": "Stòria",
"history_small": "stòria",
- "updatedmarker": "agiornà da l'ùltima vira che i son passà",
+ "updatedmarker": "agiornà da l'ùltima vira ch'it ses passà",
"printableversion": "Version bon-a për stampé",
"permalink": "Anliura fissa",
"print": "Stampé",
"ns-specialprotected": "As peulo nen modifichesse le pàgine speciaj.",
"titleprotected": "La creassion ëd pàgine con ës tìtol-sì a l'é stàita proibìa da [[User:$1|$1]].\nComa rason a l'ha butà: <em>$2</em>.",
"filereadonlyerror": "As peul pa modifichesse l'archivi «$1» përchè ël depòsit d'archivi «$2» a l'é an sola letura.\n\nL'aministrator ch'a l'ha blocalo a l'ha lassà sta spiegassion: «$3».",
+ "invalidtitle": "Cost tìtol a va nen bin!",
"invalidtitle-knownnamespace": "Tìtol ch'a va nen bin con lë spassi nominal «$2» e ël test «$3»",
"invalidtitle-unknownnamespace": "Tìtol pa bon con nùmer dë spassi nominal $1 e test «$2» sconossù",
"exception-nologin": "Nen rintrà ant ël sistema",
"password-change-forbidden": "A peul pa modifiché le ciav dzora a costa wiki.",
"externaldberror": "Ò che a l'é rivaje n'eror con la base ëd dàit d'autenticassion esterna, ò pura a l'é chiel che a l'é nen autorisà a agiornesse sò cont estern.",
"login": "Conession",
+ "login-security": "Verìfica toa identità!",
"nav-login-createaccount": "Creé un cont o rintré ant ël sistema",
"logout": "Seurte da 'nt ël sistema",
"userlogout": "Dësconession",
"hidden-category-category": "لڪل زمرا",
"category-subcat-count": "{{PLURAL:$2|ھن زمري ۾ رڳو ھيٺيون ذيلي زمرو آهي.|هن زمري ۾ ڪل $2 مان ھيٺيان {{PLURAL:$1|subcategory|$1 ذيلي زمرا}} آھن.}}",
"category-subcat-count-limited": "هن زمري ۾ هيٺيان {{PLURAL:$1|ننڍا زمرا آهن|$1 subcategories}}.",
- "category-article-count": "{{PLURAL:$2|Ù\87Ù\86 زÙ\85رÙ\8a Û¾ صرÙ\81 Ù\87Ù\8aÙºÙ\8aÙ\88Ù\86 صÙ\81ØÙ\88 Ø¢Ù\87Ù\8a.|Ù\87Ù\8aÙºÙ\8aاÙ\86 {{PLURAL:$1|صÙ\81ØÙ\88 Ø¢Ù\87Ù\8a|$1 صÙ\81Øا Ø¢Ù\87Ù\86}} Ù\87Ù\86 زÙ\85رÙ\8a Û¾Ø\8c سÙ\85Ù\88رÙ\86 $2 Ù\85اÙ\86.}}",
+ "category-article-count": "{{PLURAL:$2|Ù\87Ù\8a زÙ\85رÙ\88 رڳÙ\88 Ù\87Ù\8aÙºÙ\8aÙ\88Ù\86 صÙ\81ØÙ\88 رکÙ\8a Ù¿Ù\88.|سÙ\85Ù\88رÙ\86 $2 Ù\85اÙ\86Ø\8c Ù\87Ù\8aÙºÙ\8aÙ\88Ù\86(Ù\8aاÙ\86) {{PLURAL:$1|صÙ\81ØÙ\88|$1 صÙ\81Øا}} Ù\87Ù\86 زÙ\85رÙ\8a Û¾ Ø¢Ú¾Ù\8a(Ø¢Ú¾Ù\86).}}",
"category-article-count-limited": "هيٺِون {{PLURAL:$1|صفحو آهي|$1 صفحا آهن}} تازي زمري ۾.",
- "category-file-count": "{{PLURAL:$2|Ù\87Ù\86 زÙ\85رÙ\8a Û¾ صرÙ\81 Ù\87Ù\8aÙºÙ\8aÙ\88Ù\86 Ù\81ائÙ\8aÙ\84 Ø¢Ù\87Ù\8a.|Ù\87Ù\8aÙºÙ\8aÙ\88Ù\86 Ù\8aا Ù\87Ù\8aÙºÙ\8aاÙ\86 {{PLURAL:$1|Ù\81ائÙ\8aÙ\84 Ø¢Ù\87Ù\8a|$1 Ù\81ائÙ\8aÙ\84 Ø¢Ù\87Ù\86}} Ù\87Ù\86 زÙ\85رÙ\8a Û¾Ø\8c سÙ\85Ù\88رÙ\86 $2 Ù\85اÙ\86.}}",
+ "category-file-count": "{{PLURAL:$2|Ù\87Ù\8a زÙ\85رÙ\88 رڳÙ\88 Ù\87Ù\8aÙºÙ\8aÙ\88Ù\86 Ù\81ائÙ\8aÙ\84 رکÙ\8a Ù¿Ù\88.|سÙ\85Ù\88رÙ\86 $2 Ù\85اÙ\86Ø\8c Ù\87Ù\8aÙºÙ\8aÙ\88Ù\86(Ù\8aاÙ\86) {{PLURAL:$1|Ù\81ائÙ\8aÙ\84|$1 Ù\81ائÙ\8aÙ\84Ù\8e}} Ù\87Ù\86 زÙ\85رÙ\8a Û¾ Ø¢Ú¾Ù\8a(Ø¢Ú¾Ù\86).}}",
"category-file-count-limited": "هيٺيون يا هيٺيان {{PLURAL:$1|فائيل آهي|$1 فائيل آهن}} هن تازي زمري ۾.",
"listingcontinuesabbrev": "جاري.",
"index-category": "ڏسڻيل صفحا",
"filerenameerror": "\"$1\" نالي فائيل تي نئون نالو \"$2\" رکجي نہ سگھجو.",
"filedeleteerror": "\"$1\" فائيل ڊهي نہ سگھيو.",
"directorycreateerror": "ڊائريڪٽري $1 جُڙي نہ سگھي.",
- "directoryreadonlyerror": "ڊائريڪٽري $1 صرف پڙهي سگھجي ٿي.",
+ "directoryreadonlyerror": "ڊائريڪٽري \"$1\" رڳو-پڙھي-سگھجي-ٿي.",
"directorynotreadableerror": "ڊائريڪٽري $1 پڙهي نہ ٿي سگھجي.",
"filenotfound": "\"$1\" نالي فائيل لڀجي نہ سگھيو.",
"unexpected": "غير متوقع قدر: \"$1\"=\"$2\".",
"editingcomment": "(نئون ڀاڱو) $1 سنواريندي",
"editconflict": "سنوار تڪرار: $1",
"yourtext": "توهان جو متن",
- "storedversion": "ساÙ\86Ú\8dÙ\8aÙ\84 Ù\85سÙ\88دÙ\88",
+ "storedversion": "ساÙ\86Ú\8dÙ\8aÙ\84 Ù\88رجاءÙ\8f",
"yourdiff": "تفاوت",
"copyrightwarning": "ياد رکندا تہ {{SITENAME}} لاءِ سموريون ڀاڱيداريون $2 ھيٺ ڏنل ڄاتيون وڃن ٿيون (تفصيلن لاءِ $1 ڏسندا).\nجيڪڏهن اوهان نٿا چاهيو تہ اوهان جي لکڻيءَ کي بي رحميءَ سان سنواريو وڃي يا ورهائي عام ڪيو وڃي تہ پوءِ ان کي هتي اماڻيو.<br />\nتوهان اسان سان اھو بہ وچن ڪريو ٿا تہ ھي توهان پاڻ لکيو آھي يا وري ڪنھن مفت وسيلي يا عوامي ڊومين تان نقل ڪيو آهي.\n<strong>حق-۽-واسطا-رکندڙ ڪم کان اجازت سواءِ نہ اماڻيو.</strong>",
"copyrightwarning2": "ياد رکندا تہ {{SITENAME}} لاءِ سموريون ڀاڱيدارين کي ٻيا ڀاڱيدار سنواري، بدلائي، يا ڊاهي سگھن ٿا. جيڪڏهن اوهان نہ ٿا چاهيو تہ اوهان جي لکڻين کي بي رحميءَ سان سنواريون وڃي يا ورهائي عام ڪيو وڃي تہ پوءِ پنهنجي لکڻي هتي جمع نہ ڪرايو.</br>\nتوهان اهڙي پڪ ڏيڻ جا پابند پڻ آهيو تہ توهان جو جمع ڪرايل مواد توهان جو پنهنجو لکيل آهي يا وري توهان ڪنهن اهڙي ئي مفت عوامي وسيلي تان ڪاپي ڪيو آهي. (تفصيلن لاءِ $1 ڏسندا).\n\n<strong>تحفظيل حق ۽ واسطا رکندڙ مواد واسطيدار مالڪ کان اڳواٽ اجازت وٺڻ بنان هتي جمع نہ ڪريو.</strong>",
"nohistory": "هن صفحي جي ڪا بہ سوانح نہ آهي.",
"currentrev": "تازو-ترين ورجاءُ",
"currentrev-asof": "تازو-ترين ترين ورجاءُ بمطابق $1",
- "revisionasof": "$1 Ù\88ارÙ\88 پرت",
- "revision-info": "$1 جÙ\88 {{GENDER:$6|$2}}$7 جÙ\8a سÙ\86Ù\88ار بعد Ù\85سÙ\88دÙ\88",
- "previousrevision": "â\86\90اÚ\83ا پراڻÙ\88 پرت",
- "nextrevision": "اÚ\83ا Ù\86ئÙ\88Ù\86 پرتâ\86\92",
- "currentrevisionlink": "هاڻوڪو پرت",
+ "revisionasof": "$1 Ù\88ارÙ\88 Ù\88رجاءÙ\8f",
+ "revision-info": "$1 جÙ\88 {{GENDER:$6|$2}}$7 طرÙ\81اÙ\86 Ù\88رجاءÙ\8f",
+ "previousrevision": "â\86\92 اÚ\83ا پراڻÙ\88 Ù\88رجاءÙ\8f",
+ "nextrevision": "اÚ\83ا Ù\86ئÙ\88Ù\86 Ù\88رجاءÙ\8f â\86\90",
+ "currentrevisionlink": "تازو-ترين ورجاءُ",
"cur": "ھاڻوڪو",
"next": "اڳيون",
"last": "پويون",
"page_first": "پھريون",
"page_last": "آخري",
- "history-fieldset-title": "Ù\85سÙ\88دا ڇاڻيو",
- "history-show-deleted": "رڳو ڊاٺل مسودا",
+ "history-fieldset-title": "Ù\88رجاءÙ\8e ڇاڻيو",
+ "history-show-deleted": "رڳو ڊاھيل ورجاء",
"histfirst": "اوائلي-ترين",
"histlast": "نئون-ترين",
"historysize": "({{PLURAL:$1|1 بائيٽ|$1 بائيٽون}})",
"rev-deleted-user": "(واپرائيندڙ-نانءُ ڊاٿو ويو)",
"rev-deleted-event": "(لاگ تفصيل هٽايا ويا)",
"rev-deleted-user-contribs": "[واپرائيندڙ-نانءُ يا آءِپِي پتو مِٽايو ويو - ڀاڱيدارين مان سنوار لڪائي وئي]",
- "rev-suppressed-no-diff": "تÙ\88Ù\87اÙ\86 اÙ\87Ù\88 تÙ\81اÙ\88ت Ù\86ٿا Ú\8fسÙ\8a سگھÙ\88Ø\8c Ú\87اڪاڻ تÛ\81 Ù\85سÙ\88دن مان ڪو ھڪ <strong>ڊاھيو ويو آھي</strong>.",
+ "rev-suppressed-no-diff": "تÙ\88Ù\87اÙ\86 اÙ\87Ù\88 تÙ\81اÙ\88ت Ù\86ٿا Ú\8fسÙ\8a سگھÙ\88Ø\8c Ú\87اڪاڻ تÛ\81 Ù\88رجائن مان ڪو ھڪ <strong>ڊاھيو ويو آھي</strong>.",
"rev-delundel": "نمائش تبديل ڪريو",
"rev-showdeleted": "ڏيکاريو",
"revisiondelete": "ورجاءَ ڊاهيو/اڻڊاهيو",
"diff-empty": "(ڪو بہ تفاوت ڪونھي)",
"diff-multi-sameuser": "({{PLURAL:$1|هڪ وچولو ورجاءُ|$1 وچولا ورجاءَ}} ساڳي واپرائيندڙ طرفان ظاھر نہ ٿيندا)",
"searchresults": "ڳولا نتيجا",
- "search-filter-title-prefix": "صرÙ\81 انھن صفحن ۾ ڳوليندي جن جو عنوان \"$1\" سان شروع ٿي ٿو.",
+ "search-filter-title-prefix": "رڳÙ\88 انھن صفحن ۾ ڳوليندي جن جو عنوان \"$1\" سان شروع ٿي ٿو.",
"search-filter-title-prefix-reset": "سڀ صفحا ڳوليو",
"searchresults-title": "”$1“ لاءِ ڳولا نتيجا",
"titlematches": "صفحي جو عنوان مشابھت رکي ٿو",
"rcfilters-filter-logactions-label": "لاگڊ عمل",
"rcfilters-filtergroup-lastrevision": "تازا-ترين ورجاءَ",
"rcfilters-filter-lastrevision-label": "تازو-ترين ورجاءُ",
- "rcfilters-filter-lastrevision-description": "ÚªÙ\86Ú¾Ù\86 صÙ\81ØÙ\8a Û¾ صرÙ\81 تازÙ\8a ترين تبديلي.",
+ "rcfilters-filter-lastrevision-description": "ÚªÙ\86Ú¾Ù\86 صÙ\81ØÙ\8a Û¾ رڳÙ\88 تازÙ\8a-ترين تبديلي.",
"rcfilters-filter-previousrevision-label": "تازو-ترين ورجاءُ نہ",
"rcfilters-filter-previousrevision-description": "سڀ تبديليون جيڪي \"تازو-ترين ورجاءُ\" ناھن.",
"rcfilters-tag-prefix-namespace-inverted": "<strong>:نہ</strong> $1",
"nlinks": "$1 {{PLURAL:$1|ڳنڍڻو|ڳنڍڻا}}",
"nmembers": "$1 {{PLURAL:$1|رڪن|رڪنَ}}",
"nmemberschanged": "$1 → $2 {{PLURAL:$2|رڪن|رڪنَ}}",
- "nrevisions": "$1 {{PLURAL:$1|Ù\85سÙ\88دÙ\88|Ù\85سÙ\88دا}}",
+ "nrevisions": "$1 {{PLURAL:$1|Ù\88رجاءÙ\8f|Ù\88رجاءÙ\8e}}",
"nimagelinks": "$1 {{PLURAL:$1|صفحي|صفحن}} ۾ استعمال ٿيل",
"ntransclusions": "$1 {{PLURAL:$1|صفحي|صفحن}} ۾ استعمال ٿيل",
"specialpage-empty": "ھن رپورٽ لاءِ ڪي بہ نتيجا ناھن.",
"protectedtitles": "تحفظيل عنوان",
"protectedtitles-submit": "عنوان ڏيکاريو",
"listusers": "واپرائيندڙن جي فهرست",
- "listusers-editsonly": "صرÙ\81 سنوارن وارا واپرائيندڙ ڏيکاريو",
+ "listusers-editsonly": "رڳÙ\88 سنوارن وارا واپرائيندڙ ڏيکاريو",
"listusers-temporarygroupsonly": "صرف عارضي واپرائيندڙ گروھن ۾ واپرائيندڙ ڏيکاريو",
"listusers-creationsort": "سرجڻ جي تاريخ سان مرتب ڪريو",
"listusers-desc": "گھٽجندڙ ترتيب ۾ مرتب ڪريو",
"protect-default": "سڀ واپرائيندڙن کي اجازت ڏيو",
"protect-fallback": "\"$1\" جي اجازت وارن واپرائيندڙن کي اجازت ڏيو",
"protect-level-autoconfirmed": "خودڪار نموني پڪ ڪيل واپرائيندڙن کي اجازت ڏيو",
- "protect-level-sysop": "صرÙ\81 Ù\85Ù\86تظÙ\85Ù\8aن کي اجازت ڏيو",
+ "protect-level-sysop": "رڳÙ\88 Ù\85Ù\86تظÙ\85ن کي اجازت ڏيو",
"protect-summary-cascade": "تحفظ در تحفظ",
"protect-expiry-indefinite": "لامحدود",
"protect-cascade": "هن صفحي ۾ شامل صفحن کي تحفظيو (تحفظ در تحفظ)",
"protect-existing-expiry": "موجوده پڄاڻي جو وقت: $3, $2",
"protect-existing-expiry-infinity": "موجوده پڄاڻي جو وقت: لامحدود",
"protect-otherreason-op": "ٻيو سبب",
- "protect-expiry-options": "1 ڪلاڪ:1 hour,1 ڏينهن:1 day,1 هفتو:1 week,2 هفتو:2 weeks,1 مهينا:1 month,3 مهينا:3 months,6 مهينا:6 months,1 سال:1 year,اڻ کٽ:infinite",
+ "protect-expiry-options": "1 ڪلاڪ:1 hour,1 ڏينھن:1 day,1 هفتو:1 week,2 هفتو:2 weeks,1 مھينا:1 month,3 مھينا:3 months,6 مھينا:6 months,1 سال:1 year,اڻ-کٽ:infinite",
"restriction-type": "اجازتنامو:",
"restriction-level": "روڪ سطح:",
"minimum-size": "ننڍي ماپ ۾",
"restriction-level-autoconfirmed": "نيم تحفظيل",
"undelete": "ڊاٺل صفحا ڏسو",
"viewdeletedpage": "ڊاٺل صفحا ڏسو",
- "undelete-nodiff": "ڪوبہ اڳيون مسودو نہ لڌو",
+ "undelete-nodiff": "ڪوبہ پويون ورجاءُ نہ لڌو.",
"undeletebtn": "بحاليو",
"undeleteviewlink": "ڏسو",
"undeletecomment": "سبب:",
"sp-contributions-userrights": "{{GENDER:$1|واپرائيندڙ}} حقن-جي سنڀال",
"sp-contributions-search": "ڀاڱيدارين لاءِ ڳولا ڪريو",
"sp-contributions-username": "آءِپي پتو يا واپرائيندڙ-نانءُ:",
- "sp-contributions-toponly": "صرÙ\81 اھÙ\8a سÙ\86Ù\88ارÙ\88Ù\86 Ú\8fÙ\8aکارÙ\8aÙ\88 جÙ\8aÚªÙ\8a تازا ترÙ\8aÙ\86 Ù\85سÙ\88دا آھن",
+ "sp-contributions-toponly": "رڳÙ\88 اھÙ\8a سÙ\86Ù\88ارÙ\88Ù\86 Ú\8fÙ\8aکارÙ\8aÙ\88 جÙ\8aÚªÙ\8a تازا-ترÙ\8aÙ\86 Ù\88رجاءÙ\8e آھن",
"sp-contributions-newonly": "صرف اھي سنوارون ڏيکاريو جيڪي صفحي سرجايون آھن",
"sp-contributions-hideminor": "معمولي سنوارون لڪايو",
"sp-contributions-submit": "ڳوليو",
"movepagetext": "هيٺيون فارم استعمال ڪندي ڪنھن صفحي کي نئون عنوان ڏئي سگھجي ٿو، جنھن سان سمورو صفحو نئين عنوان ڏانھن هليو ويندو. \nاڳوڻو عنوان نئين عنوان ڏانھن چورڻو بڻجي ويندو. \nتوهان چورڻن کي سنواري سگھو ٿا جيڪي اصل عنوان ڏانهن خودبخود اشارو ڪن ٿا.\nانهي ڳالھ جي پڪ ڪري وٺو تہ [[Special:BrokenRedirects|ٽٽل چورڻا]] يا [[Special:DoubleRedirects|ٻٽا چورڻا]] نہ هجن.\nان ڳالھ جي پڪ ڪرڻ ذميواري توهان تي آهي تہ ڳنڍڻا اتي ئي وٺي وڃن ٿا جتي انھن کي وٺي وڃڻ گھرجي.\n\nياد رکندا تہ جيڪڏهن نئين عنوان سان اڳي ئي ڪو مضمون موجود آهي ته پوءِ صفحو '''نہ''' چوريو ويندو، سواءِ ان جي تہ موجوده صفحو محظ خالي آهي يا ڪا بہ سوانح نہ رکندڙ ڪو چورڻو آهي.\n\n<strong>نوٽ!</strong>\nاها هڪ مقبول صفحي لاءِ ڪا غير متوقع ۽ انتھائي اڻوڻندڙ تبديلي ثابت ٿي سگھي ٿي؛ براءِ مھرباني اڳتي وڌڻ کان اڳ پڪ ڪندا تہ توهان اها تبديلي آڻڻ جي نتيجن کان چڱيءَ ريت واقف آهيو.",
"movepagetext-noredirectfixer": "هيٺيون فارم استعمال ڪندي ڪنھن صفحي کي نئون عنوان ڏئي سگھجي ٿو، جنھن سان سمورو صفحو نئين عنوان ڏانھن هليو ويندو. \nاڳوڻو عنوان نئين عنوان ڏانھن چورڻو بڻجي ويندو. \nتوهان چورڻن کي سنواري سگھو ٿا جيڪي اصل عنوان ڏانھن خودبخود اشارو ڪن ٿا.\nانهي ڳالھ جي پڪ ڪري وٺو تہ [[Special:BrokenRedirects|ٽٽل چورڻا]] يا [[Special:DoubleRedirects|ٻٽا چورڻا]] نہ هجن.\nان ڳالھ جي پڪ ڪرڻ ذميواري توهان تي آهي تہ ڳنڍڻا اتي ئي وٺي وڃن ٿا جتي انھن کي وٺي وڃڻ گھرجي.\n\nياد رکندا تہ جيڪڏهن نئين عنوان سان اڳي ئي ڪو مضمون موجود آهي ته پوءِ صفحو '''نہ''' چوريو ويندو، سوا ان جي تہ موجوده صفحو محظ خالي آهي يا ڪا بہ سوانح نہ رکندڙ ڪو چورڻو آهي.\n\n<strong>نوٽ!</strong>\nاها هڪ مقبول صفحي لاءِ ڪا غير متوقع ۽ انتھائي اڻوڻندڙ تبديلي ثابت ٿي سگھي ٿي؛ مھرباني ڪري اڳتي وڌڻ کان اڳ پڪ ڪندا تہ توهان اها تبديلي آڻڻ جي نتيجن کان چڱيءَ ريت واقف آهيو.",
"movepagetalktext": "جيڪڏهن توهان هن خاني کي نشان لڳائيندئو، واسطيدار مباحثي صفحو پاڻ ئي چوريو ويندو ماسواءِ اتي ڪو اڳ ئي ڪو غيرخالي مباحثي صفحو موجود هجي.\n\nان صورت ۾، جيڪڏهن توهان چاهيو ته صفحي کي پاڻ چوري يا ضم ڪري سگھو ٿا.",
- "movecategorypage-warning": "<strong>Ú\86تاءÙ\8f:</strong> اÙ\88Ù\87اÙ\86 زÙ\85رÙ\8a Ù\88ارÙ\8a صÙ\81ØÙ\8a Ú©Ù\8a Ú\86Ù\88رڻ Ù\88Ú\83Ù\8a رÙ\87Ù\8aا Ø¢Ù\87Ù\8aÙ\88. Ù\8aاد رکÙ\88 صرÙ\81 صÙ\81ØÙ\88 Ú\86Ù\88رÙ\86دÙ\88Ø\8c جÙ\8aÚªÚ\8fÙ\87Ù\86 ÚªÙ\8a بÙ\87 صÙ\81Øا پراڻÙ\8a زÙ\85رÙ\8a Û¾ شاÙ\85Ù\84 Ø¢Ù\87Ù\86Ø\8c اÙ\86Ù\87Ù\86 جÙ\8a Ù\86ئÙ\8aÙ\86 زÙ\85رÙ\8a Û¾ درجابÙ\86دÙ\8a <em>Ù\86Ù\87</em> Ù¿يندي.",
+ "movecategorypage-warning": "<strong>Ú\86تاءÙ\8f:</strong> اÙ\88Ù\87اÙ\86 زÙ\85رÙ\8a Ù\88ارÙ\8a صÙ\81ØÙ\8a Ú©Ù\8a Ú\86Ù\88رڻ Ù\88Ú\83Ù\8a رÙ\87Ù\8aا Ø¢Ù\87Ù\8aÙ\88. Ù\8aاد رکÙ\88 رڳÙ\88 صÙ\81ØÙ\88 Ú\86رÙ\86دÙ\88Ø\8c پراڻÙ\8a زÙ\85رÙ\8a Û¾ ÚªÙ\86 بÛ\81 صÙ\81ØÙ\86 جÙ\8a Ù\86ئÙ\8aÙ\86 صÙ\81ØÙ\8a Û¾ Ù»Ù\8aھر-زÙ\85راڪارÙ\8a <em>Ù\86Û\81</em> ڪئÙ\8a Ù\88يندي.",
"movenotallowed": "توهان کي صفحا چورڻ جي اجازت حاصل ڪانهي.",
"movenotallowedfile": "توهان کي فائيلس چورڻ جي اجازت حاصل ڪانهي.",
"newtitle": "نئون عنوان:",
"redirect-value": "قدر:",
"redirect-user": "واپرائيندڙ آءِڊي",
"redirect-page": "صفحي جي آءِڊي",
- "redirect-revision": "صÙ\81ØÙ\8a جÙ\88 Ù\85سÙ\88دÙ\88",
+ "redirect-revision": "صÙ\81ØÙ\8a جÙ\88 Ù\88رجاءÙ\8f",
"redirect-file": "فائيلنانءُ",
"fileduplicatesearch-filename": "فائيلنانءُ:",
"fileduplicatesearch-submit": "ڳوليو",
"htmlform-title-not-exists": "$1 وجود نٿو رکي.",
"logentry-delete-delete": "$1 {{GENDER:$2|ڊاٿو}} صفحو $3",
"logentry-delete-restore": "$1 {{GENDER:$2|بحاليو}} صفحو $3 ($4)",
- "logentry-delete-revision": "$1 $3: $4 صÙ\81ØÙ\8a تÙ\8a {{PLURAL:$5|Ú¾Úª Ù\85سÙ\88دÙ\8a|$5 Ù\85سÙ\88دن}} جي ظاھريت {{GENDER:$2|تبديل ڪئي}}",
+ "logentry-delete-revision": "$1 $3: $4 صÙ\81ØÙ\8a تÙ\8a {{PLURAL:$5|Ú¾Úª Ù\88رجاءÙ\8e|$5 Ù\88رجائن}} جي ظاھريت {{GENDER:$2|تبديل ڪئي}}",
"revdelete-content-hid": "مواد لڪيل",
"revdelete-uname-hid": "واپرائيندڙ-نانءُ لڪل",
"revdelete-unrestricted": "منتظمن تان پابنديون ھٽايون ويون",
"backend-fail-contenttype": "Ne mogu da utvrdim kakav sadržaj ima datoteka koju treba da smestim u „$1“.",
"backend-fail-batchsize": "Skladišna osnova je dobila blokadu od $1 {{PLURAL:$1|operacije|operacije|operacija}}; ograničenje je $2 {{PLURAL:$2|operacija|operacije|operacija}}.",
"backend-fail-usable": "Ne mogu pročitati ni snimiti datoteku $1 zbog nedovoljno dozvola ili nedostattnih direktorija/sadržaoca.",
+ "backend-fail-stat": "Nisam mogao pročitati stanje datoteke \"$1\".",
+ "backend-fail-hash": "Nisam mogao odrediti kriptografsku tarabu datoteke \"$1\".",
"filejournal-fail-dbconnect": "Ne mogu da se povežem s novinarskom bazom za skladišnu osnovu „$1“.",
"filejournal-fail-dbquery": "Ne mogu da ažuriram novinarsku bazu za skladišnu osnovu „$1“.",
"lockmanager-notlocked": "Ne mogu da otključam „$1“ jer nije zaključan.",
"sessionfailure": "Izgleda da postoji problem sa vašom sesijom;\nova radnja je otkazana kao prevencija protiv napadanja sesija.\nMolimo ponovno pošaljite obrazac.",
"changecontentmodel": "Promijeni model sadržaja stranice",
"changecontentmodel-legend": "Promijeni model sadržaja",
- "changecontentmodel-title-label": "Naslov stranice",
+ "changecontentmodel-title-label": "Naslov stranice:",
"changecontentmodel-current-label": "Trenutni sadržajni model:",
- "changecontentmodel-model-label": "Novi model sadržaja",
+ "changecontentmodel-model-label": "Novi model sadržaja:",
"changecontentmodel-reason-label": "Razlog:",
"changecontentmodel-submit": "Smijeni",
"changecontentmodel-success-title": "Model sadržaja je promijenjen",
"nocreate-loggedin": "Za ustvarjanje novih strani nimate dovoljenja.",
"sectioneditnotsupported-title": "Urejanje razdelkov ni podprto",
"sectioneditnotsupported-text": "Urejanje razdelkov ni podprto na tej strani.",
+ "modeleditnotsupported-title": "Urejanje ni podprto",
+ "modeleditnotsupported-text": "Urejanje ni podprto za model vsebine $1.",
"permissionserrors": "Napaka dovoljenja",
"permissionserrorstext": "Za izvedbo dejanja nimate dovoljenja zaradi {{PLURAL:$1|naslednjega razloga|naslednjih razlogov}}:",
"permissionserrorstext-withaction": "Za $2 zaradi {{PLURAL:$1|naslednjega razloga|naslednjih razlogov}} nimate dovoljenja:",
"content-model-css": "CSS",
"content-json-empty-object": "Prazen objekt",
"content-json-empty-array": "Prazno polje",
+ "unsupported-content-model": "<strong>Opozorilo:</strong> Model vsebine $1 ni podprt na tem wikiju.",
+ "unsupported-content-diff": "Primerjave niso podprte za model vsebine $1.",
+ "unsupported-content-diff2": "Primerjave med modeloma vsebine $1 in $2 niso podprte na tem wikiju.",
"deprecated-self-close-category": "Strani, ki uporabljajo neveljavne samozaključljive oznake HTML",
"deprecated-self-close-category-desc": "Stran uporablja neveljavne samozaključljive oznake HTML, kot sta <code><b/></code> ali <code><span/></code>. Njihovo vedenje se bo kmalu spremenilo, da bo v skladu s specifikacijo HTML5, zato njihova uporaba v wikibesedilu ni zaželena.",
"duplicate-args-warning": "<strong>Opozorilo:</strong> [[:$1]] kliče [[:$2]] z več kot eno vrednostjo za parameter »$3«. Uporabili bomo samo zadnjo navedeno vrednost.",
"backend-fail-contenttype": "Ne morem določiti vrsto vsebine datoteke za shranjevanje pri »$1«.",
"backend-fail-batchsize": "Skladiščnemu zaledju je dana vrsta $1 {{PLURAL:$1|datotečne operacije|datotečnih operacij}}; omejitev {{PLURAL:$2|je $2 operacija|sta $2 operaciji|so $2 operacije|je $2 operacij}}.",
"backend-fail-usable": "Ne morem prebrati ali zapisati datoteke »$1« zaradi nezadostnih dovoljenj ali manjkajočega imenika/vsebnika.",
+ "backend-fail-stat": "Ne moremo prebrati stanja datoteke »$1«.",
+ "backend-fail-hash": "Ne moremo določiti kriptografske zgoščene vrednosti datoteke »$1«.",
"filejournal-fail-dbconnect": "Ne morem se povezati z listovno zbirko podatkov za skladiščno zaledje »$1«.",
"filejournal-fail-dbquery": "Ne morem posodobiti listovne zbirke podatkov za skladiščno zaledje »$1«.",
"lockmanager-notlocked": "Ne morem odkleniti »$1«, saj ni zaklenjeno.",
"sessionfailure": "Zdi se, da z vašo sejo prijave obstaja težava;\nto dejanje smo preklicali, da bi preprečili morebitno ugrabitev seje. Prosimo, ponovno potrdite obrazec.",
"changecontentmodel": "Spremeni model vsebine strani",
"changecontentmodel-legend": "Spremeni model vsebine",
- "changecontentmodel-title-label": "Naslov strani",
+ "changecontentmodel-title-label": "Naslov strani:",
"changecontentmodel-current-label": "Trenutni model vsebine:",
- "changecontentmodel-model-label": "Novi model vsebine",
+ "changecontentmodel-model-label": "Novi model vsebine:",
"changecontentmodel-reason-label": "Razlog:",
"changecontentmodel-submit": "Spremeni",
"changecontentmodel-success-title": "Spremenili smo model vsebine",
"editpage-notsupportedcontentformat-title": "Формат садржаја није подржан",
"editpage-notsupportedcontentformat-text": "Формат садржаја $1 није подржан за модел садржаја $2.",
"slot-name-main": "Главни",
- "content-model-wikitext": "викитекста",
+ "content-model-wikitext": "викитекст",
"content-model-text": "чистог текста",
"content-model-javascript": "јаваскрипта",
"content-model-css": "Це-Ес-Еса",
"editpage-notsupportedcontentformat-title": "Format sadržaja nije podržan",
"editpage-notsupportedcontentformat-text": "Format sadržaja $1 nije podržan za model sadržaja $2.",
"slot-name-main": "Glavni",
- "content-model-wikitext": "vikiteksta",
+ "content-model-wikitext": "vikitekst",
"content-model-text": "čistog teksta",
"content-model-javascript": "JavaScript-a",
"content-model-css": "CSS-a",
"talk": "Dyskusyjŏ",
"views": "Widoki",
"toolbox": "Nŏrzyńdzia",
+ "tool-link-emailuser": "Wyślij e-mail do {{GENDER:$1|tego używŏcza|tyj używŏczki|tego używŏcza}}",
"imagepage": "Uobejrz zajta pliku",
"mediawikipage": "Zajta komuńikata",
"templatepage": "Zajta mustra",
"logouttext": "'''Terozki jeżeś wylůgowany'''.\n\nDej pozůr, co na ńykerych zajtach przeglůndarka może dali pokozywać co jeżeś zalůgowany, a bydźe tak aże uodśwjyżysz jeij cache.",
"welcomeuser": "Witej, $1",
"welcomecreation-msg": "Uotwarli my sam lo Ćebje kůnto.\nPamjyntej coby posztalować [[Special:Preferences|preferencyji]]",
- "yourname": "Mjano użytkowńika:",
+ "yourname": "Miano ôd używŏcza:",
"userlogin-yourname": "Miano używŏcza",
"userlogin-yourname-ph": "Wkludź swoje miano używŏcza",
- "createacct-another-username-ph": "Wszkryflej mjano użytkowńika",
+ "createacct-another-username-ph": "Wkludź miano ôd używŏcza",
"yourpassword": "Hasło:",
"userlogin-yourpassword": "Hasło",
"userlogin-yourpassword-ph": "Wkludź swoje hasło",
"loginsuccess": "'''Terozki jeżeś zalogowany do {{SITENAME}} kej \"$1\".'''",
"nosuchuser": "Niy ma używŏcza ô mianie \"$1\".\nBadnij szrajbōng, abo [[Special:CreateAccount|sprŏw nowe kōnto]].",
"nosuchusershort": "Ńy mo sam użytkowńika uo mjańe \"$1\".",
- "nouserspecified": "Podej mjano użytkowńika.",
+ "nouserspecified": "Musisz podać miano ôd używŏcza.",
"login-userblocked": "Tyn sprowjorz mo zawarte sprowjyńa. Ńy idźe śe zalogować.",
"wrongpassword": "Hasło kere żeś naszkryfloł je felerne. Poprůbůj naszkryflać je jeszcze roz.",
"wrongpasswordempty": "Hasło kere żeś podou je uostawjůne blank. Naszkryflej je jeszcze roz.",
"undo-success": "Sprowjyńy zostoło wycofane. Prosza pomjarkować ukozane půniżyj dyferencyje mjyndzy wersyjůma, coby zweryfikować jejich poprawność, potym zaś naszkryflać pomjyńańo coby zakończyć uoperacyjo.",
"undo-failure": "Ta edycyjŏ niy może być cŏfniyntŏ skuli kōnfliktu ze wersyjami postrzednimi.",
"undo-norev": "Edycyje niy idzie cŏfnōńć, bo ôna niy istniyje abo była wyciepniyntŏ.",
- "undo-summary": "WycůfaÅ\84y wersyji $1 naszkryflanej bez [[Special:Contributions/$2|$2]] ([[User talk:$2|godka]])",
+ "undo-summary": "WycÅ\8ffanie wersyje $1 ôd [[Special:Contributions/$2|$2]] ([[User talk:$2|dyskusyjÅ\8f]])",
"cantcreateaccount-text": "Tworzyńy kůnta s tygo adresu IP ('''$1''') uostoło zawarte bez użytkowńika [[User:$3|$3]].\n\nSkuli: ''$2''",
"viewpagelogs": "Ôbejzdrz regesty dlŏ tyj strōny",
"nohistory": "Ta zajta ńy mo swojij historyje sprowjyń.",
"revdelete-modify-no-access": "Feler przy zmjyńe widoczności wersyji $2, $1. Ńy mosz uprawńeń lo ńygo.",
"revdelete-modify-missing": "Feler. Ńy mo tajli $1 w baźe.",
"revdelete-no-change": "''''Dej pozůr''': element $2, $1 mo już ustawjonům widoczność.",
- "revdelete-concurrent-change": "Feler. Pomjyno już element $2, $1. Prosza uoboczyć to w rejerze.",
+ "revdelete-concurrent-change": "Feler przi modyfikacyji elymyntu ze $2 $1: Wyglōndŏ na to, że jego status bōł zmiyniōny ôd kogoś w czasie Twojij roboty.\nWejzdrzij do regestu.",
"revdelete-only-restricted": "Ńy do śe ukryć tajli $2, $1 przed administracyjom. Wybjer jydnom ze uopcyji.",
"revdelete-reason-dropdown": "* Kůmyntorze lo wyćepańa\n** NPA\n** Prywatność",
"revdelete-otherreason": "Inkszy/dodatkowy powůd:",
"yourrealname": "Prawdźiwe mjano",
"yourlanguage": "Godka interfejsu",
"yournick": "Twoja szrajbka:",
- "badsig": "Felerno szrajbka, sprawdź znaczńiki HTML.",
+ "badsig": "Felerny podpis, wejzdrzij na znaczniki HTML.",
"badsiglength": "Twojo szrajbka je za dugo. Ji maksymalno dugość to $1 {{PLURAL:$1|buchsztaby|buchsztabůw}}",
"yourgender": "Płeć:",
"gender-unknown": "ńyznano",
"usereditcount": "$1 {{PLURAL:$1|sprowjyńe|sprowjyńa|sprowjyń}}",
"usercreated": "{{GENDER:$3|Utworzono}} $1 uo $2",
"newpages": "Nowe strōny",
- "newpages-username": "Mjano użytkowńika:",
+ "newpages-username": "Miano ôd używŏcza:",
"ancientpages": "Nojstarše artikle",
"move": "Przeniyś",
"movethispage": "Přećepej ta zajta",
"mailnologin": "Brak adresu",
"mailnologintext": "Muśyš śe [[Special:UserLogin|zalůgować]] i mjeć wpisany aktualny adres e-brif w swojich [[Special:Preferences|preferyncyjach]], coby můc wysuać e-brif do inkšygo užytkowńika.",
"emailuser": "Poślij tymu używŏczowi e-mail",
+ "emailuser-title-target": "Wyślij e-mail do {{GENDER:$1|tego używŏcza|tyj używŏczki|tego używŏcza}}",
"emailpagetext": "Możesz użyć půńiższygo formularza, coby wysłać wjadůmość e-brif do tygo użytkowńika.\nAdres e-brifa, kery zostoł bez Ćebje wkludzůny we [[Special:Preferences|Twojich sztalowańach]], pojawi śe we polu „Uod”, bez cůż uodbjorca bydźe můg Ći uodpedźeć.",
"defemailsubject": "{{SITENAME}} - e-mail ôd używŏcza \"$1\"",
"usermaildisabled": "E-mail ôd używŏcza je zastŏwiōny",
"protect-expiry-options": "2 godźiny:2 hours,1 dźyń:1 day,3 dńi:3 days,1 tydźyń:1 week,2 tygodńy:2 weeks,1 mjeśůnc:1 month,3 mjeśůnce:3 months,6 mjeśency:6 months,1 rok:1 year,ńyskůńčůny:infińite",
"restriction-type": "Pozwolyńy:",
"restriction-level": "Poźům:",
- "minimum-size": "Min. wjelgość",
- "maximum-size": "Maksymalno wjelgość",
- "pagesize": "(bajtůw)",
+ "minimum-size": "Minimalnŏ srogość",
+ "maximum-size": "Maksymalnŏ srogość:",
+ "pagesize": "(bajtÅ\8dw)",
"restriction-edit": "Edytuj",
"restriction-move": "Pōnknij",
"restriction-create": "Stwůř",
"tooltip-invert": "Ôznŏcz te pole, coby skryć zmiany na strōnach we ôbranyj przestrzyni mian (i swiōnzanōm z niōm inkszōm przestrzyniōm mian, jeźli je ôznaczōnŏ)",
"namespace_association": "Swiōnzanŏ przestrzyń mian",
"tooltip-namespace_association": "Ôznŏcz te pole, coby przidać strōnã dyskusyje i tymat swiōnzane ze ôbranōm przestrzyniōm mian",
- "blanknamespace": "(przodńo)",
+ "blanknamespace": "(Przodniŏ)",
"contributions": "Wkłŏd ôd {{GENDER:$1|używŏcza|używŏczki}}",
"contributions-title": "Wkłŏd {{GENDER:$1|używŏcza|używŏczki}} $1",
"mycontris": "Edycyje",
"autosumm-blank": "POZŮR! Usůńjyńće treśći (zajta pozostoła pusto)!",
"autosumm-replace": "POZŮR! Zastůmpjyńy treśći hasua bardzo krůtkym tekstym: „$1”",
"autoredircomment": "Przekerowanie do [[$1]]",
- "autosumm-new": "Wćepano nowo zajta: \"$1\"",
+ "autosumm-new": "Stworzōnŏ nowõ strōnã: \"$1\"",
"lag-warn-normal": "Na tyj liśće zmjany nowsze jak {{PLURAL:$1|sekůnda|sekůnd}} můgům ńy być widoczne.",
"lag-warn-high": "S kuli srogigo uobćůnżyńo serwerůw bazy danych, na tyj liśće zmjany nowše jak {{PLURAL:$1|sekůnda|sekůnd}} můgům ńy być widoczne.",
"watchlistedit-normal-title": "Sprowjej lista zajtůw na kere dowom pozůr",
"fileduplicatesearch-summary": "Šnupej za duplikatůma plika na podstawje wartośći fůnkcyji skrůtu.",
"fileduplicatesearch-filename": "Mjano pliku:",
"fileduplicatesearch-submit": "Šnupej",
- "fileduplicatesearch-info": "$1 × $2 pikseli<br />Wjelgość plika: $3<br />Typ MIME: $4",
+ "fileduplicatesearch-info": "$1 × $2 pikselōw<br />Srogość zbioru: $3<br />Typ MIME: $4",
"fileduplicatesearch-result-1": "Ńy ma duplikatu pliku „$1”.",
"fileduplicatesearch-result-n": "We {{GRAMMAR:MS.lp|{{SITENAME}}}} {{PLURAL:$2|je dodatkowo kopia|sům $2 dodatkowe kopje|je $2 dodatkowych kopii}} plika „$1”.",
"specialpages": "Ekstra strōny",
// Const for getStdin()
const STDIN_ALL = 'all';
- // Array of desired/allowed params
+ /**
+ * Array of desired/allowed params
+ * @var array[]
+ * @phan-var array<string,array{desc:string,require:bool,withArg:string,shortName:string,multiOccurrence:bool}>
+ */
protected $mParams = [];
// Array of mapping short parameters to long ones
*/
protected $mBatchSize = null;
- // Generic options added by addDefaultParams()
+ /**
+ * Generic options added by addDefaultParams()
+ * @var array[]
+ * @phan-var array<string,array{desc:string,require:bool,withArg:string,shortName:string,multiOccurrence:bool}>
+ */
private $mGenericParameters = [];
- // Generic options which might or not be supported by the script
+ /**
+ * Generic options which might or not be supported by the script
+ * @var array[]
+ * @phan-var array<string,array{desc:string,require:bool,withArg:string,shortName:string,multiOccurrence:bool}>
+ */
private $mDependantParameters = [];
/**
$min = $times[0];
$max = end( $times );
if ( $n % 2 ) {
+ // @phan-suppress-next-line PhanTypeMismatchDimFetch
$median = $times[ ( $n - 1 ) / 2 ];
} else {
$median = ( $times[$n / 2] + $times[$n / 2 - 1] ) / 2;
/**
* @param string $realName
* @param array[] $value
- * @suppress PhanTypeInvalidDimOffset
*/
protected function handleResourceModules( $realName, $value ) {
$defaults = [];
continue;
}
- /** @var LocalFile $file */
$file = $repo->newFile( $row->fa_name );
- '@phan-var LocalFile $file';
try {
$file->lock();
} catch ( LocalFileLockError $e ) {
if ( $this->hasOption( 'dry' ) ) {
$this->output( "done.\n" );
- // @phan-suppress-next-line PhanUndeclaredMethod
} elseif ( $image->recordUpload2(
$archive->value,
$summary,
# Protect the file
$this->output( "\nWaiting for replica DBs...\n" );
// Wait for replica DBs.
- sleep( 2.0 ); # Why this sleep?
+ sleep( 2 ); # Why this sleep?
wfWaitForSlaves();
$this->output( "\nSetting image restrictions ... " );
"Show some statistics on the blob_orphans table, created with trackBlobs.php" );
}
- protected function &getDB( $cluster, $groups = [], $wiki = false ) {
+ protected function getDB( $cluster, $groups = [], $wiki = false ) {
$lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
$lb = $lbFactory->getExternalLB( $cluster );
return [ 'slot_revision_id' => $revId ];
}
+ /**
+ * @covers \MediaWiki\Revision\RevisionStore::newRevisionsFromBatch
+ * @throws \MWException
+ */
+ public function testNewRevisionsFromBatch_error() {
+ $page = $this->getTestPage();
+ $text = __METHOD__ . 'b-ä';
+ /** @var Revision $rev1 */
+ $rev1 = $page->doEditContent(
+ new WikitextContent( $text . '1' ),
+ __METHOD__ . 'b',
+ 0,
+ false,
+ $this->getTestUser()->getUser()
+ )->value['revision'];
+ $invalidRow = $this->revisionToRow( $rev1 );
+ $invalidRow->rev_id = 100500;
+ $result = MediaWikiServices::getInstance()->getRevisionStore()
+ ->newRevisionsFromBatch(
+ [ $this->revisionToRow( $rev1 ), $invalidRow ],
+ [
+ 'slots' => [ SlotRecord::MAIN ],
+ 'content' => true
+ ]
+ );
+ $this->assertFalse( $result->isGood() );
+ $this->assertNotEmpty( $result->getErrors() );
+ $records = $result->getValue();
+ $this->assertRevisionRecordMatchesRevision( $rev1, $records[$rev1->getId()] );
+ $this->assertSame( $text . '1',
+ $records[$rev1->getId()]->getContent( SlotRecord::MAIN )->serialize() );
+ $this->assertEquals( $page->getTitle()->getDBkey(),
+ $records[$rev1->getId()]->getPageAsLinkTarget()->getDBkey() );
+ $this->assertNull( $records[$invalidRow->rev_id] );
+ $this->assertSame( [ [
+ 'type' => 'warning',
+ 'message' => 'internalerror',
+ 'params' => [
+ "Couldn't find slots for rev 100500"
+ ]
+ ] ], $result->getErrors() );
+ }
}
}
/**
+ * @param string|null $pageTitle whether to force-create a new page
* @return WikiPage
*/
- protected function getTestPage() {
- if ( $this->testPage ) {
+ protected function getTestPage( $pageTitle = null ) {
+ if ( !is_null( $pageTitle ) && $this->testPage ) {
return $this->testPage;
}
- $title = $this->getTestPageTitle();
- $this->testPage = WikiPage::factory( $title );
+ $title = is_null( $pageTitle ) ? $this->getTestPageTitle() : Title::newFromText( $pageTitle );
+ $page = WikiPage::factory( $title );
- if ( !$this->testPage->exists() ) {
+ if ( !$page->exists() ) {
// Make sure we don't write to the live db.
$this->ensureMockDatabaseConnection( wfGetDB( DB_MASTER ) );
$user = static::getTestSysop()->getUser();
- $this->testPage->doEditContent(
+ $page->doEditContent(
new WikitextContent( 'UTContent-' . __CLASS__ ),
'UTPageSummary-' . __CLASS__,
EDIT_NEW | EDIT_SUPPRESS_RC,
);
}
- return $this->testPage;
+ if ( is_null( $pageTitle ) ) {
+ $this->testPage = $page;
+ }
+ return $page;
}
/**
$this->assertSame( RevisionRecord::DELETED_TEXT, $deletedAfter );
}
+ public function provideNewRevisionsFromBatchOptions() {
+ yield 'No preload slots or content, single page' => [
+ null,
+ []
+ ];
+ yield 'Preload slots and content, single page' => [
+ null,
+ [
+ 'slots' => [ SlotRecord::MAIN ],
+ 'content' => true
+ ]
+ ];
+ yield 'No preload slots or content, multiple pages' => [
+ 'Other_Page',
+ []
+ ];
+ yield 'Preload slots and content, multiple pages' => [
+ 'Other_Page',
+ [
+ 'slots' => [ SlotRecord::MAIN ],
+ 'content' => true
+ ]
+ ];
+ }
+
+ /**
+ * @dataProvider provideNewRevisionsFromBatchOptions
+ * @covers \MediaWiki\Revision\RevisionStore::newRevisionsFromBatch
+ * @param string|null $otherPageTitle
+ * @param array|null $options
+ * @throws \MWException
+ */
+ public function testNewRevisionsFromBatch_preloadContent(
+ $otherPageTitle = null,
+ array $options = []
+ ) {
+ $page1 = $this->getTestPage();
+ $text = __METHOD__ . 'b-ä';
+ /** @var Revision $rev1 */
+ $rev1 = $page1->doEditContent(
+ new WikitextContent( $text . '1' ),
+ __METHOD__ . 'b',
+ 0,
+ false,
+ $this->getTestUser()->getUser()
+ )->value['revision'];
+ $page2 = $this->getTestPage( $otherPageTitle );
+ /** @var Revision $rev2 */
+ $rev2 = $page2->doEditContent(
+ new WikitextContent( $text . '2' ),
+ __METHOD__ . 'b',
+ 0,
+ false,
+ $this->getTestUser()->getUser()
+ )->value['revision'];
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $result = $store->newRevisionsFromBatch(
+ [ $this->revisionToRow( $rev1 ), $this->revisionToRow( $rev2 ) ],
+ $options
+ );
+ $this->assertTrue( $result->isGood() );
+ $this->assertEmpty( $result->getErrors() );
+ $records = $result->getValue();
+ $this->assertRevisionRecordMatchesRevision( $rev1, $records[$rev1->getId()] );
+ $this->assertRevisionRecordMatchesRevision( $rev2, $records[$rev2->getId()] );
+
+ $this->assertSame( $text . '1',
+ $records[$rev1->getId()]->getContent( SlotRecord::MAIN )->serialize() );
+ $this->assertSame( $text . '2',
+ $records[$rev2->getId()]->getContent( SlotRecord::MAIN )->serialize() );
+ $this->assertEquals( $page1->getTitle()->getDBkey(),
+ $records[$rev1->getId()]->getPageAsLinkTarget()->getDBkey() );
+ $this->assertEquals( $page2->getTitle()->getDBkey(),
+ $records[$rev2->getId()]->getPageAsLinkTarget()->getDBkey() );
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\RevisionStore::newRevisionsFromBatch
+ */
+ public function testNewRevisionsFromBatch_emptyBatch() {
+ $result = MediaWikiServices::getInstance()->getRevisionStore()
+ ->newRevisionsFromBatch(
+ [],
+ [
+ 'slots' => [ SlotRecord::MAIN ],
+ 'content' => true
+ ]
+ );
+ $this->assertTrue( $result->isGood() );
+ $this->assertEmpty( $result->getValue() );
+ $this->assertEmpty( $result->getErrors() );
+ }
}
[],
[ 'internalmode' => false ],
],
- 'Limit with parseLimits false' => [
+ 'Limit with parseLimits false (numeric)' => [
'100',
[ ApiBase::PARAM_TYPE => 'limit' ],
- '100',
+ 100,
+ [],
+ [ 'parseLimits' => false ],
+ ],
+ 'Limit with parseLimits false (max)' => [
+ 'max',
+ [ ApiBase::PARAM_TYPE => 'limit' ],
+ 'max',
+ [],
+ [ 'parseLimits' => false ],
+ ],
+ 'Limit with parseLimits false (invalid)' => [
+ 'kitten',
+ [ ApiBase::PARAM_TYPE => 'limit' ],
+ 0,
[],
[ 'parseLimits' => false ],
],
[
ApiBase::PARAM_TYPE => 'limit',
ApiBase::PARAM_MAX2 => 10,
- ApiBase::PARAM_ISMULTI => true,
],
new MWException(
'Internal error in ApiBase::getParameterFromSettings: ' .
[
ApiBase::PARAM_TYPE => 'limit',
ApiBase::PARAM_MAX => 10,
- ApiBase::PARAM_ISMULTI => true,
],
new MWException(
'Internal error in ApiBase::getParameterFromSettings: ' .
$this->assertEquals( $props1, $props2,
"Source and destination have the same props ($backendName)." );
- $this->assertBackendPathsConsistent( [ $dest ] );
+ $this->assertBackendPathsConsistent( [ $dest ], true );
}
public static function provider_testStore() {
/**
* @dataProvider provider_testCopy
*/
- public function testCopy( $op ) {
+ public function testCopy( $op, $srcContent, $dstContent, $okStatus, $okSyncStatus ) {
$this->backend = $this->singleBackend;
$this->tearDownFiles();
- $this->doTestCopy( $op );
+ $this->doTestCopy( $op, $srcContent, $dstContent, $okStatus, $okSyncStatus );
$this->tearDownFiles();
$this->backend = $this->multiBackend;
$this->tearDownFiles();
- $this->doTestCopy( $op );
+ $this->doTestCopy( $op, $srcContent, $dstContent, $okStatus, $okSyncStatus );
$this->tearDownFiles();
}
- private function doTestCopy( $op ) {
+ private function doTestCopy( $op, $srcContent, $dstContent, $okStatus, $okSyncStatus ) {
$backendName = $this->backendClass();
$source = $op['src'];
$this->prepare( [ 'dir' => dirname( $source ) ] );
$this->prepare( [ 'dir' => dirname( $dest ) ] );
- if ( isset( $op['ignoreMissingSource'] ) ) {
- $status = $this->backend->doOperation( $op );
- $this->assertGoodStatus( $status,
- "Move from $source to $dest succeeded without warnings ($backendName)." );
- $this->assertEquals( [ 0 => true ], $status->success,
- "Move from $source to $dest has proper 'success' field in Status ($backendName)." );
- $this->assertEquals( false, $this->backend->fileExists( [ 'src' => $source ] ),
- "Source file $source does not exist ($backendName)." );
- $this->assertEquals( false, $this->backend->fileExists( [ 'src' => $dest ] ),
- "Destination file $dest does not exist ($backendName)." );
-
- return;
+ if ( is_string( $srcContent ) ) {
+ $status = $this->backend->create( [ 'content' => $srcContent, 'dst' => $source ] );
+ $this->assertGoodStatus( $status, "Creation of $source succeeded ($backendName)." );
}
-
- $status = $this->backend->doOperation(
- [ 'op' => 'create', 'content' => 'blahblah', 'dst' => $source ] );
- $this->assertGoodStatus( $status,
- "Creation of file at $source succeeded ($backendName)." );
-
- if ( isset( $op['overwrite'] ) || isset( $op['overwriteSame'] ) ) {
- $this->backend->copy( $op );
+ if ( is_string( $dstContent ) ) {
+ $status = $this->backend->create( [ 'content' => $dstContent, 'dst' => $dest ] );
+ $this->assertGoodStatus( $status, "Creation of $dest succeeded ($backendName)." );
}
$status = $this->backend->doOperation( $op );
- $this->assertGoodStatus( $status,
- "Copy from $source to $dest succeeded without warnings ($backendName)." );
- $this->assertEquals( true, $status->isOK(),
- "Copy from $source to $dest succeeded ($backendName)." );
- $this->assertEquals( [ 0 => true ], $status->success,
- "Copy from $source to $dest has proper 'success' field in Status ($backendName)." );
- $this->assertEquals( true, $this->backend->fileExists( [ 'src' => $source ] ),
- "Source file $source still exists ($backendName)." );
- $this->assertEquals( true, $this->backend->fileExists( [ 'src' => $dest ] ),
- "Destination file $dest exists after copy ($backendName)." );
-
- $this->assertEquals(
- $this->backend->getFileSize( [ 'src' => $source ] ),
- $this->backend->getFileSize( [ 'src' => $dest ] ),
- "Destination file $dest has correct size ($backendName)." );
+ if ( $okStatus ) {
+ $this->assertGoodStatus(
+ $status,
+ "Copy from $source to $dest succeeded without warnings ($backendName)." );
+ $this->assertEquals( true, $status->isOK(),
+ "Copy from $source to $dest succeeded ($backendName)." );
+ $this->assertEquals( [ 0 => true ], $status->success,
+ "Copy from $source to $dest has proper 'success' field in Status ($backendName)." );
+ if ( !is_string( $srcContent ) ) {
+ $this->assertSame(
+ is_string( $dstContent ),
+ $this->backend->fileExists( [ 'src' => $dest ] ),
+ "Destination file $dest unchanged after no-op copy ($backendName)." );
+ $this->assertSame(
+ $dstContent,
+ $this->backend->getFileContents( [ 'src' => $dest ] ),
+ "Destination file $dest unchanged after no-op copy ($backendName)." );
+ } else {
+ $this->assertEquals(
+ $this->backend->getFileSize( [ 'src' => $source ] ),
+ $this->backend->getFileSize( [ 'src' => $dest ] ),
+ "Destination file $dest has correct size ($backendName)." );
+ $props1 = $this->backend->getFileProps( [ 'src' => $source ] );
+ $props2 = $this->backend->getFileProps( [ 'src' => $dest ] );
+ $this->assertEquals(
+ $props1,
+ $props2,
+ "Source and destination have the same props ($backendName)." );
+ }
+ } else {
+ $this->assertBadStatus(
+ $status,
+ "Copy from $source to $dest fails ($backendName)." );
+ $this->assertSame(
+ is_string( $dstContent ),
+ (bool)$this->backend->fileExists( [ 'src' => $dest ] ),
+ "Destination file $dest unchanged after failed copy ($backendName)." );
+ $this->assertSame(
+ $dstContent,
+ $this->backend->getFileContents( [ 'src' => $dest ] ),
+ "Destination file $dest unchanged after failed copy ($backendName)." );
+ }
- $props1 = $this->backend->getFileProps( [ 'src' => $source ] );
- $props2 = $this->backend->getFileProps( [ 'src' => $dest ] );
- $this->assertEquals( $props1, $props2,
- "Source and destination have the same props ($backendName)." );
+ $this->assertSame(
+ is_string( $srcContent ),
+ (bool)$this->backend->fileExists( [ 'src' => $source ] ),
+ "Source file $source unchanged after copy ($backendName)."
+ );
+ $this->assertSame(
+ $srcContent,
+ $this->backend->getFileContents( [ 'src' => $source ] ),
+ "Source file $source unchanged after copy ($backendName)."
+ );
+ if ( is_string( $dstContent ) ) {
+ $this->assertTrue(
+ (bool)$this->backend->fileExists( [ 'src' => $dest ] ),
+ "Destination file $dest exists after copy ($backendName)." );
+ }
- $this->assertBackendPathsConsistent( [ $source, $dest ] );
+ $this->assertBackendPathsConsistent( [ $source, $dest ], $okSyncStatus );
}
+ /**
+ * @return array (op, source exists, dest exists, op succeeds, sync check succeeds)
+ */
public static function provider_testCopy() {
$cases = [];
$source = self::baseStorePath() . '/unittest-cont1/e/file.txt';
- $dest = self::baseStorePath() . '/unittest-cont2/a/fileMoved.txt';
+ $dest = self::baseStorePath() . '/unittest-cont2/a/fileCopied.txt';
+ $opBase = [ 'op' => 'copy', 'src' => $source, 'dst' => $dest ];
- $op = [ 'op' => 'copy', 'src' => $source, 'dst' => $dest ];
- $cases[] = [
- $op, // operation
- $source, // source
- $dest, // dest
- ];
+ $op = $opBase;
+ $cases[] = [ $op, 'yyy', false, true, true ];
- $op2 = $op;
- $op2['overwrite'] = true;
- $cases[] = [
- $op2, // operation
- $source, // source
- $dest, // dest
- ];
+ $op = $opBase;
+ $op['overwrite'] = true;
+ $cases[] = [ $op, 'yyy', false, true, true ];
- $op2 = $op;
- $op2['overwriteSame'] = true;
- $cases[] = [
- $op2, // operation
- $source, // source
- $dest, // dest
- ];
+ $op = $opBase;
+ $op['overwrite'] = true;
+ $cases[] = [ $op, 'yyy', 'xxx', true, true ];
- $op2 = $op;
- $op2['ignoreMissingSource'] = true;
- $cases[] = [
- $op2, // operation
- $source, // source
- $dest, // dest
- ];
+ $op = $opBase;
+ $op['overwriteSame'] = true;
+ $cases[] = [ $op, 'yyy', false, true, true ];
- $op2 = $op;
- $op2['ignoreMissingSource'] = true;
- $cases[] = [
- $op2, // operation
- self::baseStorePath() . '/unittest-cont-bad/e/file.txt', // source
- $dest, // dest
- ];
+ $op = $opBase;
+ $op['overwriteSame'] = true;
+ $cases[] = [ $op, 'yyy', 'yyy', true, true ];
+
+ $op = $opBase;
+ $op['overwriteSame'] = true;
+ $cases[] = [ $op, 'yyy', 'zzz', false, true ];
+
+ $op = $opBase;
+ $op['ignoreMissingSource'] = true;
+ $cases[] = [ $op, 'xxx', false, true, true ];
+
+ $op = $opBase;
+ $op['ignoreMissingSource'] = true;
+ $cases[] = [ $op, false, false, true, true ];
+
+ $op = $opBase;
+ $op['ignoreMissingSource'] = true;
+ $cases[] = [ $op, false, 'xxx', true, true ];
+
+ $op = $opBase;
+ $op['src'] = 'mwstore://wrongbackend/unittest-cont1/e/file.txt';
+ $op['ignoreMissingSource'] = true;
+ $cases[] = [ $op, false, false, false, false ];
return $cases;
}
/**
* @dataProvider provider_testMove
*/
- public function testMove( $op ) {
+ public function testMove( $op, $srcContent, $dstContent, $okStatus, $okSyncStatus ) {
$this->backend = $this->singleBackend;
$this->tearDownFiles();
- $this->doTestMove( $op );
+ $this->doTestMove( $op, $srcContent, $dstContent, $okStatus, $okSyncStatus );
$this->tearDownFiles();
$this->backend = $this->multiBackend;
$this->tearDownFiles();
- $this->doTestMove( $op );
+ $this->doTestMove( $op, $srcContent, $dstContent, $okStatus, $okSyncStatus );
$this->tearDownFiles();
}
- private function doTestMove( $op ) {
+ private function doTestMove( $op, $srcContent, $dstContent, $okStatus, $okSyncStatus ) {
$backendName = $this->backendClass();
$source = $op['src'];
$this->prepare( [ 'dir' => dirname( $source ) ] );
$this->prepare( [ 'dir' => dirname( $dest ) ] );
- if ( isset( $op['ignoreMissingSource'] ) ) {
- $status = $this->backend->doOperation( $op );
- $this->assertGoodStatus( $status,
- "Move from $source to $dest succeeded without warnings ($backendName)." );
- $this->assertEquals( [ 0 => true ], $status->success,
- "Move from $source to $dest has proper 'success' field in Status ($backendName)." );
- $this->assertEquals( false, $this->backend->fileExists( [ 'src' => $source ] ),
- "Source file $source does not exist ($backendName)." );
- $this->assertEquals( false, $this->backend->fileExists( [ 'src' => $dest ] ),
- "Destination file $dest does not exist ($backendName)." );
-
- return;
+ if ( is_string( $srcContent ) ) {
+ $status = $this->backend->create( [ 'content' => $srcContent, 'dst' => $source ] );
+ $this->assertGoodStatus( $status, "Creation of $source succeeded ($backendName)." );
}
-
- $status = $this->backend->doOperation(
- [ 'op' => 'create', 'content' => 'blahblah', 'dst' => $source ] );
- $this->assertGoodStatus( $status,
- "Creation of file at $source succeeded ($backendName)." );
-
- if ( isset( $op['overwrite'] ) || isset( $op['overwriteSame'] ) ) {
- $this->backend->copy( $op );
+ if ( is_string( $dstContent ) ) {
+ $status = $this->backend->create( [ 'content' => $dstContent, 'dst' => $dest ] );
+ $this->assertGoodStatus( $status, "Creation of $dest succeeded ($backendName)." );
}
+ $oldSrcProps = $this->backend->getFileProps( [ 'src' => $source ] );
+
$status = $this->backend->doOperation( $op );
- $this->assertGoodStatus( $status,
- "Move from $source to $dest succeeded without warnings ($backendName)." );
- $this->assertEquals( true, $status->isOK(),
- "Move from $source to $dest succeeded ($backendName)." );
- $this->assertEquals( [ 0 => true ], $status->success,
- "Move from $source to $dest has proper 'success' field in Status ($backendName)." );
- $this->assertEquals( false, $this->backend->fileExists( [ 'src' => $source ] ),
- "Source file $source does not still exists ($backendName)." );
- $this->assertEquals( true, $this->backend->fileExists( [ 'src' => $dest ] ),
- "Destination file $dest exists after move ($backendName)." );
- $this->assertNotEquals(
- $this->backend->getFileSize( [ 'src' => $source ] ),
- $this->backend->getFileSize( [ 'src' => $dest ] ),
- "Destination file $dest has correct size ($backendName)." );
+ if ( $okStatus ) {
+ $this->assertGoodStatus(
+ $status,
+ "Move from $source to $dest succeeded without warnings ($backendName)." );
+ $this->assertEquals( true, $status->isOK(),
+ "Move from $source to $dest succeeded ($backendName)." );
+ $this->assertEquals( [ 0 => true ], $status->success,
+ "Move from $source to $dest has proper 'success' field in Status ($backendName)." );
+ if ( !is_string( $srcContent ) ) {
+ $this->assertSame(
+ is_string( $dstContent ),
+ $this->backend->fileExists( [ 'src' => $dest ] ),
+ "Destination file $dest unchanged after no-op move ($backendName)." );
+ $this->assertSame(
+ $dstContent,
+ $this->backend->getFileContents( [ 'src' => $dest ] ),
+ "Destination file $dest unchanged after no-op move ($backendName)." );
+ } else {
+ $this->assertEquals(
+ $this->backend->getFileSize( [ 'src' => $dest ] ),
+ strlen( $srcContent ),
+ "Destination file $dest has correct size ($backendName)." );
+ $this->assertEquals(
+ $oldSrcProps,
+ $this->backend->getFileProps( [ 'src' => $dest ] ),
+ "Source and destination have the same props ($backendName)." );
+ }
+ } else {
+ $this->assertBadStatus(
+ $status,
+ "Move from $source to $dest fails ($backendName)." );
+ $this->assertSame(
+ is_string( $dstContent ),
+ (bool)$this->backend->fileExists( [ 'src' => $dest ] ),
+ "Destination file $dest unchanged after failed move ($backendName)." );
+ $this->assertSame(
+ $dstContent,
+ $this->backend->getFileContents( [ 'src' => $dest ] ),
+ "Destination file $dest unchanged after failed move ($backendName)." );
+ $this->assertSame(
+ is_string( $srcContent ),
+ (bool)$this->backend->fileExists( [ 'src' => $source ] ),
+ "Source file $source unchanged after failed move ($backendName)."
+ );
+ $this->assertSame(
+ $srcContent,
+ $this->backend->getFileContents( [ 'src' => $source ] ),
+ "Source file $source unchanged after failed move ($backendName)."
+ );
+ }
- $props1 = $this->backend->getFileProps( [ 'src' => $source ] );
- $props2 = $this->backend->getFileProps( [ 'src' => $dest ] );
- $this->assertEquals( false, $props1['fileExists'],
- "Source file does not exist accourding to props ($backendName)." );
- $this->assertEquals( true, $props2['fileExists'],
- "Destination file exists accourding to props ($backendName)." );
+ if ( is_string( $dstContent ) ) {
+ $this->assertTrue(
+ (bool)$this->backend->fileExists( [ 'src' => $dest ] ),
+ "Destination file $dest exists after move ($backendName)." );
+ }
- $this->assertBackendPathsConsistent( [ $source, $dest ] );
+ $this->assertBackendPathsConsistent( [ $source, $dest ], $okSyncStatus );
}
+ /**
+ * @return array (op, source exists, dest exists, op succeeds, sync check succeeds)
+ */
public static function provider_testMove() {
$cases = [];
$source = self::baseStorePath() . '/unittest-cont1/e/file.txt';
$dest = self::baseStorePath() . '/unittest-cont2/a/fileMoved.txt';
+ $opBase = [ 'op' => 'move', 'src' => $source, 'dst' => $dest ];
- $op = [ 'op' => 'move', 'src' => $source, 'dst' => $dest ];
- $cases[] = [
- $op, // operation
- $source, // source
- $dest, // dest
- ];
+ $op = $opBase;
+ $cases[] = [ $op, 'yyy', false, true, true ];
- $op2 = $op;
- $op2['overwrite'] = true;
- $cases[] = [
- $op2, // operation
- $source, // source
- $dest, // dest
- ];
+ $op = $opBase;
+ $op['overwrite'] = true;
+ $cases[] = [ $op, 'yyy', false, true, true ];
- $op2 = $op;
- $op2['overwriteSame'] = true;
- $cases[] = [
- $op2, // operation
- $source, // source
- $dest, // dest
- ];
+ $op = $opBase;
+ $op['overwrite'] = true;
+ $cases[] = [ $op, 'yyy', 'xxx', true, true ];
- $op2 = $op;
- $op2['ignoreMissingSource'] = true;
- $cases[] = [
- $op2, // operation
- $source, // source
- $dest, // dest
- ];
+ $op = $opBase;
+ $op['overwriteSame'] = true;
+ $cases[] = [ $op, 'yyy', false, true, true ];
- $op2 = $op;
- $op2['ignoreMissingSource'] = true;
- $cases[] = [
- $op2, // operation
- self::baseStorePath() . '/unittest-cont-bad/e/file.txt', // source
- $dest, // dest
- ];
+ $op = $opBase;
+ $op['overwriteSame'] = true;
+ $cases[] = [ $op, 'yyy', 'yyy', true, true ];
+
+ $op = $opBase;
+ $op['overwriteSame'] = true;
+ $cases[] = [ $op, 'yyy', 'zzz', false, true ];
+
+ $op = $opBase;
+ $op['ignoreMissingSource'] = true;
+ $cases[] = [ $op, 'xxx', false, true, true ];
+
+ $op = $opBase;
+ $op['ignoreMissingSource'] = true;
+ $cases[] = [ $op, false, false, true, true ];
+
+ $op = $opBase;
+ $op['ignoreMissingSource'] = true;
+ $cases[] = [ $op, false, 'xxx', true, true ];
+
+ $op = $opBase;
+ $op['src'] = 'mwstore://wrongbackend/unittest-cont1/e/file.txt';
+ $op['ignoreMissingSource'] = true;
+ $cases[] = [ $op, false, false, false, false ];
return $cases;
}
/**
* @dataProvider provider_testDelete
*/
- public function testDelete( $op, $withSource, $okStatus ) {
+ public function testDelete( $op, $srcContent, $okStatus, $okSyncStatus ) {
$this->backend = $this->singleBackend;
$this->tearDownFiles();
- $this->doTestDelete( $op, $withSource, $okStatus );
+ $this->doTestDelete( $op, $srcContent, $okStatus, $okSyncStatus );
$this->tearDownFiles();
$this->backend = $this->multiBackend;
$this->tearDownFiles();
- $this->doTestDelete( $op, $withSource, $okStatus );
+ $this->doTestDelete( $op, $srcContent, $okStatus, $okSyncStatus );
$this->tearDownFiles();
}
- private function doTestDelete( $op, $withSource, $okStatus ) {
+ private function doTestDelete( $op, $srcContent, $okStatus, $okSyncStatus ) {
$backendName = $this->backendClass();
$source = $op['src'];
$this->prepare( [ 'dir' => dirname( $source ) ] );
- if ( $withSource ) {
+ if ( is_string( $srcContent ) ) {
$status = $this->backend->doOperation(
- [ 'op' => 'create', 'content' => 'blahblah', 'dst' => $source ] );
+ [ 'op' => 'create', 'content' => $srcContent, 'dst' => $source ] );
$this->assertGoodStatus( $status,
"Creation of file at $source succeeded ($backendName)." );
}
"Deletion of file at $source failed ($backendName)." );
}
- $this->assertEquals( false, $this->backend->fileExists( [ 'src' => $source ] ),
+ $this->assertFalse(
+ (bool)$this->backend->fileExists( [ 'src' => $source ] ),
"Source file $source does not exist after move ($backendName)." );
$this->assertFalse(
"Source file $source has correct size (false) ($backendName)." );
$props1 = $this->backend->getFileProps( [ 'src' => $source ] );
- $this->assertFalse( $props1['fileExists'],
+ $this->assertFalse(
+ $props1['fileExists'],
"Source file $source does not exist according to props ($backendName)." );
- $this->assertBackendPathsConsistent( [ $source ] );
+ $this->assertBackendPathsConsistent( [ $source ], $okSyncStatus );
}
+ /**
+ * @return array (op, source content, op succeeds, sync check succeeds)
+ */
public static function provider_testDelete() {
$cases = [];
$source = self::baseStorePath() . '/unittest-cont1/e/myfacefile.txt';
+ $baseOp = [ 'op' => 'delete', 'src' => $source ];
- $op = [ 'op' => 'delete', 'src' => $source ];
- $cases[] = [
- $op, // operation
- true, // with source
- true // succeeds
- ];
+ $op = $baseOp;
+ $cases[] = [ $op, 'xxx', true, true ];
- $cases[] = [
- $op, // operation
- false, // without source
- false // fails
- ];
+ $op = $baseOp;
+ $op['ignoreMissingSource'] = true;
+ $cases[] = [ $op, 'xxx', true, true ];
+
+ $op = $baseOp;
+ $cases[] = [ $op, false, false, true ];
+ $op = $baseOp;
$op['ignoreMissingSource'] = true;
- $cases[] = [
- $op, // operation
- false, // without source
- true // succeeds
- ];
+ $cases[] = [ $op, false, true, true ];
+ $op = $baseOp;
$op['ignoreMissingSource'] = true;
- $op['src'] = self::baseStorePath() . '/unittest-cont-bad/e/file.txt';
- $cases[] = [
- $op, // operation
- false, // without source
- true // succeeds
- ];
+ $op['src'] = 'mwstore://wrongbackend/unittest-cont1/e/file.txt';
+ $cases[] = [ $op, false, false, false ];
return $cases;
}
"Describe of file at $source failed ($backendName)." );
}
- $this->assertBackendPathsConsistent( [ $source ] );
+ $this->assertBackendPathsConsistent( [ $source ], true );
}
private function assertHasHeaders( array $headers, array $attr ) {
"Destination file $dest has original size according to props ($backendName)." );
}
- $this->assertBackendPathsConsistent( [ $dest ] );
+ $this->assertBackendPathsConsistent( [ $dest ], true );
}
/**
}
function tearDownFiles() {
- $containers = [ 'unittest-cont1', 'unittest-cont2', 'unittest-cont-bad' ];
+ $containers = [ 'unittest-cont1', 'unittest-cont2' ];
foreach ( $containers as $container ) {
$this->deleteFiles( $container );
}
$this->backend->clean( [ 'dir' => "$base/$container", 'recursive' => 1 ] );
}
- function assertBackendPathsConsistent( array $paths ) {
- if ( $this->backend instanceof FileBackendMultiWrite ) {
- $status = $this->backend->consistencyCheck( $paths );
+ private function assertBackendPathsConsistent( array $paths, $okSyncStatus ) {
+ if ( !$this->backend instanceof FileBackendMultiWrite ) {
+ return;
+ }
+
+ $status = $this->backend->consistencyCheck( $paths );
+ if ( $okSyncStatus ) {
$this->assertGoodStatus( $status, "Files synced: " . implode( ',', $paths ) );
+ } else {
+ $this->assertBadStatus( $status, "Files not synced: " . implode( ',', $paths ) );
}
}
- function assertGoodStatus( StatusValue $status, $msg ) {
+ private function assertGoodStatus( StatusValue $status, $msg ) {
$this->assertEquals( print_r( [], 1 ), print_r( $status->getErrors(), 1 ), $msg );
}
+
+ private function assertBadStatus( StatusValue $status, $msg ) {
+ $this->assertNotEquals( print_r( [], 1 ), print_r( $status->getErrors(), 1 ), $msg );
+ }
}
--- /dev/null
+<?php
+
+use MediaWiki\MediaWikiServices;
+use Psr\Log\NullLogger;
+
+/**
+ * @group Database
+ * @coversDefaultClass ImportableOldRevisionImporter
+ */
+class ImportableOldRevisionImporterTest extends MediaWikiIntegrationTestCase {
+
+ public function setUp() {
+ parent::setUp();
+
+ $this->tablesUsed[] = 'change_tag';
+ $this->tablesUsed[] = 'change_tag_def';
+
+ ChangeTags::defineTag( 'tag1' );
+ }
+
+ public function provideTestCases() {
+ yield [ [] ];
+ yield [ [ "tag1" ] ];
+ }
+
+ /**
+ * @covers ::import
+ * @param $expectedTags
+ * @dataProvider provideTestCases
+ */
+ public function testImport( $expectedTags ) {
+ $services = MediaWikiServices::getInstance();
+
+ $title = Title::newFromText( __CLASS__ . rand() );
+ $revision = new WikiRevision( $services->getMainConfig() );
+ $revision->setTitle( $title );
+ $revision->setTags( $expectedTags );
+ $revision->setText( "dummy edit" );
+
+ $importer = new ImportableOldRevisionImporter(
+ true,
+ new NullLogger(),
+ $services->getDBLoadBalancer()
+ );
+ $result = $importer->import( $revision );
+ $this->assertTrue( $result );
+
+ $page = WikiPage::factory( $title );
+ $tags = ChangeTags::getTags(
+ $services->getDBLoadBalancer()->getConnection( DB_MASTER ),
+ null,
+ $page->getLatest()
+ );
+ $this->assertSame( $expectedTags, $tags );
+ }
+}